Completed
Pull Request — master (#758)
by thomas
22:04
created

SchnorrSigner   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Test Coverage

Coverage 97.56%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 36
c 1
b 0
f 0
dl 0
loc 94
ccs 40
cts 41
cp 0.9756
rs 10
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);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type resource; however, parameter $s of BitWasp\Bitcoin\Crypto\E...ignature::__construct() does only seem to accept GMP, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

42
        return new Signature($this->adapter, $R->getX(), /** @scrutinizer ignore-type */ $s);
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmp_mod(gmp_init(...asher, false), 16), $n) could return the type resource which is incompatible with the type-hinted return GMP. Consider adding an additional type-check to rule them out.
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmp_mod(gmp_init(...asher, false), 16), $n) could return the type resource which is incompatible with the type-hinted return GMP. Consider adding an additional type-check to rule them out.
Loading history...
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());
0 ignored issues
show
Bug introduced by
It seems like gmp_sub($G->getOrder(), $e) can also be of type resource; however, parameter $tweak of BitWasp\Bitcoin\Crypto\E...y\PublicKey::tweakMul() does only seem to accept GMP, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
        $R = $G->mul($signature->getS())->add($publicKey->tweakMul(/** @scrutinizer ignore-type */ gmp_sub($G->getOrder(), $e))->getPoint());
Loading history...
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