Completed
Pull Request — master (#758)
by thomas
25:29
created

SchnorrSigner   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Test Coverage

Coverage 97.56%

Importance

Changes 0
Metric Value
eloc 36
dl 0
loc 94
ccs 40
cts 41
cp 0.9756
rs 10
c 0
b 0
f 0
wmc 11

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sign() 0 14 2
A verify() 0 20 5
A tob32() 0 3 1
A hashPublicData() 0 8 1
A __construct() 0 3 1
A hashPrivateData() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Adapter\EcAdapter;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PrivateKey;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
10
use BitWasp\Buffertools\BufferInterface;
11
12
class SchnorrSigner
13
{
14
    /**
15
     * @var EcAdapter
16
     */
17
    private $adapter;
18
19 8
    public function __construct(EcAdapter $ecAdapter)
20
    {
21 8
        $this->adapter = $ecAdapter;
22 8
    }
23
24
    /**
25
     * @param PrivateKey $privateKey
26
     * @param BufferInterface $message32
27
     * @return Signature
28
     */
29 3
    public function sign(PrivateKey $privateKey, BufferInterface $message32): Signature
30
    {
31 3
        $G = $this->adapter->getGenerator();
32 3
        $n = $G->getOrder();
33 3
        $k = $this->hashPrivateData($privateKey, $message32, $n);
34 3
        $R = $G->mul($k);
35
36 3
        if (gmp_cmp(gmp_jacobi($R->getY(), $G->getCurve()->getPrime()), 1) !== 0) {
37 1
            $k = gmp_sub($G->getOrder(), $k);
38
        }
39
40 3
        $e = $this->hashPublicData($R->getX(), $privateKey->getPublicKey(), $message32, $n);
41 3
        $s = gmp_mod(gmp_add($k, gmp_mod(gmp_mul($e, $privateKey->getSecret()), $n)), $n);
42 3
        return new Signature($this->adapter, $R->getX(), $s);
43
    }
44
45
    /**
46
     * @param \GMP $n
47
     * @return string
48
     */
49 8
    private function tob32(\GMP $n): string
50
    {
51 8
        return $this->adapter->getMath()->intToFixedSizeString($n, 32);
52
    }
53
54
    /**
55
     * @param PrivateKey $privateKey
56
     * @param BufferInterface $message32
57
     * @param \GMP $n
58
     * @return \GMP
59
     */
60 3
    private function hashPrivateData(PrivateKey $privateKey, BufferInterface $message32, \GMP $n): \GMP
61
    {
62 3
        $hasher = hash_init('sha256');
63 3
        hash_update($hasher, $this->tob32($privateKey->getSecret()));
64 3
        hash_update($hasher, $message32->getBinary());
65 3
        return gmp_mod(gmp_init(hash_final($hasher, false), 16), $n);
66
    }
67
68
    /**
69
     * @param \GMP $Rx
70
     * @param PublicKey $publicKey
71
     * @param BufferInterface $message32
72
     * @param \GMP $n
73
     * @param string|null $rxBytes
74
     * @return \GMP
75
     */
76 8
    private function hashPublicData(\GMP $Rx, PublicKey $publicKey, BufferInterface $message32, \GMP $n, string &$rxBytes = null): \GMP
77
    {
78 8
        $hasher = hash_init('sha256');
79 8
        $rxBytes = $this->tob32($Rx);
80 8
        hash_update($hasher, $rxBytes);
81 8
        hash_update($hasher, $publicKey->getBinary());
82 8
        hash_update($hasher, $message32->getBinary());
83 8
        return gmp_mod(gmp_init(hash_final($hasher, false), 16), $n);
84
    }
85
86 8
    public function verify(BufferInterface $message32, PublicKey $publicKey, Signature $signature): bool
87
    {
88 8
        $G = $this->adapter->getGenerator();
89 8
        $n = $G->getOrder();
90 8
        $p = $G->getCurve()->getPrime();
91
92 8
        if (gmp_cmp($signature->getR(), $p) >= 0 || gmp_cmp($signature->getR(), $n) >= 0) {
93
            return false;
94
        }
95
96 8
        $RxBytes = null;
97 8
        $e = $this->hashPublicData($signature->getR(), $publicKey, $message32, $n, $RxBytes);
98 8
        $R = $G->mul($signature->getS())->add($publicKey->tweakMul(gmp_sub($G->getOrder(), $e))->getPoint());
99
100 8
        $jacobiNotOne = gmp_cmp(gmp_jacobi($R->getY(), $p), 1) !== 0;
101 8
        $rxNotEquals = !hash_equals($RxBytes, $this->tob32($R->getX()));
102 8
        if ($jacobiNotOne || $rxNotEquals) {
103 4
            return false;
104
        }
105 4
        return true;
106
    }
107
}
108