1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* |
5
|
|
|
* @author Jan Moritz Lindemann |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace BitcoinPHP\BitcoinECDSA; |
9
|
|
|
|
10
|
|
|
if (!extension_loaded('gmp')) { |
11
|
|
|
throw new \Exception('GMP extension seems not to be installed'); |
12
|
|
|
} |
13
|
|
|
|
14
|
|
|
class BitcoinECDSA |
15
|
|
|
{ |
16
|
|
|
|
17
|
|
|
public $k; |
18
|
|
|
public $a; |
19
|
|
|
public $b; |
20
|
|
|
public $p; |
21
|
|
|
public $n; |
22
|
|
|
public $G; |
23
|
|
|
public $networkPrefix; |
24
|
|
|
|
25
|
|
|
public function __construct() |
26
|
|
|
{ |
27
|
|
|
$this->a = gmp_init('0', 10); |
28
|
|
|
$this->b = gmp_init('7', 10); |
29
|
|
|
$this->p = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16); |
30
|
|
|
$this->n = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16); |
31
|
|
|
|
32
|
|
|
$this->G = [ |
33
|
|
|
'x' => gmp_init('55066263022277343669578718895168534326250603453777594175500187360389116729240'), |
34
|
|
|
'y' => gmp_init('32670510020758816978083085130507043184471273380659243275938904335757337482424') |
35
|
|
|
]; |
36
|
|
|
|
37
|
|
|
$this->networkPrefix = '00'; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/*** |
41
|
|
|
* Convert a number to a compact Int |
42
|
|
|
* taken from https://github.com/scintill/php-bitcoin-signature-routines/blob/master/verifymessage.php |
43
|
|
|
* |
44
|
|
|
* @param int $i |
45
|
|
|
* @return string (bin) |
46
|
|
|
* @throws \Exception |
47
|
|
|
*/ |
48
|
|
|
public function numToVarIntString($i) { |
49
|
|
|
if ($i < 0xfd) { |
50
|
|
|
return chr($i); |
51
|
|
|
} else if ($i <= 0xffff) { |
52
|
|
|
return pack('Cv', 0xfd, $i); |
53
|
|
|
} else if ($i <= 0xffffffff) { |
54
|
|
|
return pack('CV', 0xfe, $i); |
55
|
|
|
} else { |
56
|
|
|
throw new \Exception('int too large'); |
57
|
|
|
} |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/*** |
61
|
|
|
* Set the network prefix, '00' = main network, '6f' = test network. |
62
|
|
|
* |
63
|
|
|
* @param string $prefix (hexa) |
64
|
|
|
*/ |
65
|
|
|
public function setNetworkPrefix($prefix) |
66
|
|
|
{ |
67
|
|
|
$this->networkPrefix = $prefix; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns the current network prefix, '00' = main network, '6f' = test network. |
72
|
|
|
* |
73
|
|
|
* @return string (hexa) |
74
|
|
|
*/ |
75
|
|
|
public function getNetworkPrefix() |
76
|
|
|
{ |
77
|
|
|
return $this->networkPrefix; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/*** |
81
|
|
|
* Permutation table used for Base58 encoding and decoding. |
82
|
|
|
* |
83
|
|
|
* @param string $char |
84
|
|
|
* @param bool $reverse |
85
|
|
|
* @return string|null |
86
|
|
|
*/ |
87
|
|
|
public function base58_permutation($char, $reverse = false) |
88
|
|
|
{ |
89
|
|
|
$table = [ |
90
|
|
|
'1','2','3','4','5','6','7','8','9','A','B','C','D', |
91
|
|
|
'E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W', |
92
|
|
|
'X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','m','n','o', |
93
|
|
|
'p','q','r','s','t','u','v','w','x','y','z' |
94
|
|
|
]; |
95
|
|
|
|
96
|
|
|
if($reverse) |
97
|
|
|
{ |
98
|
|
|
$reversedTable = []; |
99
|
|
|
foreach($table as $key => $element) |
100
|
|
|
{ |
101
|
|
|
$reversedTable[$element] = $key; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if(isset($reversedTable[$char])) |
105
|
|
|
return $reversedTable[$char]; |
106
|
|
|
else |
107
|
|
|
return null; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
if(isset($table[$char])) |
111
|
|
|
return $table[$char]; |
112
|
|
|
else |
113
|
|
|
return null; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/*** |
117
|
|
|
* Bitcoin standard 256 bit hash function : double sha256 |
118
|
|
|
* |
119
|
|
|
* @param string $data |
120
|
|
|
* @return string (hexa) |
121
|
|
|
*/ |
122
|
|
|
public function hash256($data) |
123
|
|
|
{ |
124
|
|
|
return hash('sha256', hex2bin(hash('sha256', $data))); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @param string $data |
129
|
|
|
* @return string (hexa) |
130
|
|
|
*/ |
131
|
|
|
public function hash160($data) |
132
|
|
|
{ |
133
|
|
|
return hash('ripemd160', hex2bin(hash('sha256', $data))); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Generates a random 256 bytes hexadecimal encoded string that is smaller than n |
138
|
|
|
* |
139
|
|
|
* @param string $extra |
140
|
|
|
* @return string (hexa) |
141
|
|
|
* @throws \Exception |
142
|
|
|
*/ |
143
|
|
|
public function generateRandom256BitsHexaString($extra = 'FkejkzqesrfeifH3ioio9hb55sdssdsdfOO:ss') |
144
|
|
|
{ |
145
|
|
|
do |
146
|
|
|
{ |
147
|
|
|
$bytes = openssl_random_pseudo_bytes(256, $cStrong); |
148
|
|
|
$hex = bin2hex($bytes); |
149
|
|
|
$random = $hex . microtime(true) . $extra; |
150
|
|
|
|
151
|
|
|
if ($cStrong === false) { |
152
|
|
|
throw new \Exception('Your system is not able to generate strong enough random numbers'); |
153
|
|
|
} |
154
|
|
|
$res = $this->hash256($random); |
155
|
|
|
|
156
|
|
|
} while(gmp_cmp(gmp_init($res, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1); // make sure the generate string is smaller than n |
157
|
|
|
|
158
|
|
|
return $res; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/*** |
162
|
|
|
* encode a hexadecimal string in Base58. |
163
|
|
|
* |
164
|
|
|
* @param string $data (hexa) |
165
|
|
|
* @param bool $littleEndian |
166
|
|
|
* @return string (base58) |
167
|
|
|
* @throws \Exception |
168
|
|
|
*/ |
169
|
|
|
public function base58_encode($data, $littleEndian = true) |
170
|
|
|
{ |
171
|
|
|
$res = ''; |
172
|
|
|
$dataIntVal = gmp_init($data, 16); |
173
|
|
|
while(gmp_cmp($dataIntVal, gmp_init(0, 10)) > 0) |
174
|
|
|
{ |
175
|
|
|
$qr = gmp_div_qr($dataIntVal, gmp_init(58, 10)); |
176
|
|
|
$dataIntVal = $qr[0]; |
177
|
|
|
$reminder = gmp_strval($qr[1]); |
178
|
|
|
if(!$this->base58_permutation($reminder)) |
179
|
|
|
{ |
180
|
|
|
throw new \Exception('Something went wrong during base58 encoding'); |
181
|
|
|
} |
182
|
|
|
$res .= $this->base58_permutation($reminder); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
//get number of leading zeros |
186
|
|
|
$leading = ''; |
187
|
|
|
$i = 0; |
188
|
|
|
while(substr($data, $i, 1) === '0') |
189
|
|
|
{ |
190
|
|
|
if($i!== 0 && $i%2) |
191
|
|
|
{ |
192
|
|
|
$leading .= '1'; |
193
|
|
|
} |
194
|
|
|
$i++; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
if($littleEndian) |
198
|
|
|
return strrev($res . $leading); |
199
|
|
|
else |
200
|
|
|
return $res.$leading; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/*** |
204
|
|
|
* Decode a Base58 encoded string and returns it's value as a hexadecimal string |
205
|
|
|
* |
206
|
|
|
* @param string $encodedData (base58) |
207
|
|
|
* @param bool $littleEndian |
208
|
|
|
* @return string (hexa) |
209
|
|
|
*/ |
210
|
|
|
public function base58_decode($encodedData, $littleEndian = true) |
211
|
|
|
{ |
212
|
|
|
$res = gmp_init(0, 10); |
213
|
|
|
$length = strlen($encodedData); |
214
|
|
|
if($littleEndian) |
215
|
|
|
{ |
216
|
|
|
$encodedData = strrev($encodedData); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
for($i = $length - 1; $i >= 0; $i--) |
220
|
|
|
{ |
221
|
|
|
$res = gmp_add( |
222
|
|
|
gmp_mul( |
223
|
|
|
$res, |
224
|
|
|
gmp_init(58, 10) |
225
|
|
|
), |
226
|
|
|
$this->base58_permutation(substr($encodedData, $i, 1), true) |
227
|
|
|
); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$res = gmp_strval($res, 16); |
231
|
|
|
$i = $length - 1; |
232
|
|
|
while(substr($encodedData, $i, 1) === '1') |
233
|
|
|
{ |
234
|
|
|
$res = '00' . $res; |
235
|
|
|
$i--; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
if(strlen($res)%2 !== 0) |
239
|
|
|
{ |
240
|
|
|
$res = '0' . $res; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
return $res; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/*** |
247
|
|
|
* Computes the result of a point addition and returns the resulting point as an Array. |
248
|
|
|
* |
249
|
|
|
* @param Array $pt |
250
|
|
|
* @return Array Point |
251
|
|
|
* @throws \Exception |
252
|
|
|
*/ |
253
|
|
|
public function doublePoint(Array $pt) |
254
|
|
|
{ |
255
|
|
|
$a = $this->a; |
256
|
|
|
$p = $this->p; |
257
|
|
|
|
258
|
|
|
$gcd = gmp_strval(gmp_gcd(gmp_mod(gmp_mul(gmp_init(2, 10), $pt['y']), $p),$p)); |
259
|
|
|
if($gcd !== '1') |
260
|
|
|
{ |
261
|
|
|
throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9'); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// SLOPE = (3 * ptX^2 + a )/( 2*ptY ) |
265
|
|
|
// Equals (3 * ptX^2 + a ) * ( 2*ptY )^-1 |
266
|
|
|
$slope = gmp_mod( |
267
|
|
|
gmp_mul( |
268
|
|
|
gmp_invert( |
269
|
|
|
gmp_mod( |
270
|
|
|
gmp_mul( |
271
|
|
|
gmp_init(2, 10), |
272
|
|
|
$pt['y'] |
273
|
|
|
), |
274
|
|
|
$p |
275
|
|
|
), |
276
|
|
|
$p |
277
|
|
|
), |
278
|
|
|
gmp_add( |
279
|
|
|
gmp_mul( |
280
|
|
|
gmp_init(3, 10), |
281
|
|
|
gmp_pow($pt['x'], 2) |
282
|
|
|
), |
283
|
|
|
$a |
284
|
|
|
) |
285
|
|
|
), |
286
|
|
|
$p |
287
|
|
|
); |
288
|
|
|
|
289
|
|
|
// nPtX = slope^2 - 2 * ptX |
290
|
|
|
// Equals slope^2 - ptX - ptX |
291
|
|
|
$nPt = []; |
292
|
|
|
$nPt['x'] = gmp_mod( |
293
|
|
|
gmp_sub( |
294
|
|
|
gmp_sub( |
295
|
|
|
gmp_pow($slope, 2), |
296
|
|
|
$pt['x'] |
297
|
|
|
), |
298
|
|
|
$pt['x'] |
299
|
|
|
), |
300
|
|
|
$p |
301
|
|
|
); |
302
|
|
|
|
303
|
|
|
// nPtY = slope * (ptX - nPtx) - ptY |
304
|
|
|
$nPt['y'] = gmp_mod( |
305
|
|
|
gmp_sub( |
306
|
|
|
gmp_mul( |
307
|
|
|
$slope, |
308
|
|
|
gmp_sub( |
309
|
|
|
$pt['x'], |
310
|
|
|
$nPt['x'] |
311
|
|
|
) |
312
|
|
|
), |
313
|
|
|
$pt['y'] |
314
|
|
|
), |
315
|
|
|
$p |
316
|
|
|
); |
317
|
|
|
|
318
|
|
|
return $nPt; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/*** |
322
|
|
|
* Computes the result of a point addition and returns the resulting point as an Array. |
323
|
|
|
* |
324
|
|
|
* @param Array $pt1 |
325
|
|
|
* @param Array $pt2 |
326
|
|
|
* @return Array Point |
327
|
|
|
* @throws \Exception |
328
|
|
|
*/ |
329
|
|
|
public function addPoints(Array $pt1, Array $pt2) |
330
|
|
|
{ |
331
|
|
|
$p = $this->p; |
332
|
|
|
if(gmp_cmp($pt1['x'], $pt2['x']) === 0 && gmp_cmp($pt1['y'], $pt2['y']) === 0) //if identical |
333
|
|
|
{ |
334
|
|
|
return $this->doublePoint($pt1); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
$gcd = gmp_strval(gmp_gcd(gmp_sub($pt1['x'], $pt2['x']), $p)); |
338
|
|
|
if($gcd !== '1') |
339
|
|
|
{ |
340
|
|
|
throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9'); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
// SLOPE = (pt1Y - pt2Y)/( pt1X - pt2X ) |
344
|
|
|
// Equals (pt1Y - pt2Y) * ( pt1X - pt2X )^-1 |
345
|
|
|
$slope = gmp_mod( |
346
|
|
|
gmp_mul( |
347
|
|
|
gmp_sub( |
348
|
|
|
$pt1['y'], |
349
|
|
|
$pt2['y'] |
350
|
|
|
), |
351
|
|
|
gmp_invert( |
352
|
|
|
gmp_sub( |
353
|
|
|
$pt1['x'], |
354
|
|
|
$pt2['x'] |
355
|
|
|
), |
356
|
|
|
$p |
357
|
|
|
) |
358
|
|
|
), |
359
|
|
|
$p |
360
|
|
|
); |
361
|
|
|
|
362
|
|
|
// nPtX = slope^2 - ptX1 - ptX2 |
363
|
|
|
$nPt = []; |
364
|
|
|
$nPt['x'] = gmp_mod( |
365
|
|
|
gmp_sub( |
366
|
|
|
gmp_sub( |
367
|
|
|
gmp_pow($slope, 2), |
368
|
|
|
$pt1['x'] |
369
|
|
|
), |
370
|
|
|
$pt2['x'] |
371
|
|
|
), |
372
|
|
|
$p |
373
|
|
|
); |
374
|
|
|
|
375
|
|
|
// nPtX = slope * (ptX1 - nPtX) - ptY1 |
376
|
|
|
$nPt['y'] = gmp_mod( |
377
|
|
|
gmp_sub( |
378
|
|
|
gmp_mul( |
379
|
|
|
$slope, |
380
|
|
|
gmp_sub( |
381
|
|
|
$pt1['x'], |
382
|
|
|
$nPt['x'] |
383
|
|
|
) |
384
|
|
|
), |
385
|
|
|
$pt1['y'] |
386
|
|
|
), |
387
|
|
|
$p |
388
|
|
|
); |
389
|
|
|
|
390
|
|
|
return $nPt; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/*** |
394
|
|
|
* Computes the result of a point multiplication and returns the resulting point as an Array. |
395
|
|
|
* |
396
|
|
|
* @param string|resource $k (hexa|GMP|Other bases definded in base) |
397
|
|
|
* @param Array $pG |
398
|
|
|
* @param $base |
399
|
|
|
* @throws \Exception |
400
|
|
|
* @return Array Point |
401
|
|
|
*/ |
402
|
|
|
public function mulPoint($k, Array $pG, $base = null) |
403
|
|
|
{ |
404
|
|
|
//in order to calculate k*G |
405
|
|
|
if($base === 16 || $base === null || is_resource($base)) |
406
|
|
|
$k = gmp_init($k, 16); |
407
|
|
|
if($base === 10) |
408
|
|
|
$k = gmp_init($k, 10); |
409
|
|
|
$kBin = gmp_strval($k, 2); |
410
|
|
|
|
411
|
|
|
$lastPoint = $pG; |
412
|
|
|
for($i = 1; $i < strlen($kBin); $i++) |
413
|
|
|
{ |
414
|
|
|
if(substr($kBin, $i, 1) === '1') |
415
|
|
|
{ |
416
|
|
|
$dPt = $this->doublePoint($lastPoint); |
417
|
|
|
$lastPoint = $this->addPoints($dPt, $pG); |
418
|
|
|
} |
419
|
|
|
else |
420
|
|
|
{ |
421
|
|
|
$lastPoint = $this->doublePoint($lastPoint); |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
if(!$this->validatePoint(gmp_strval($lastPoint['x'], 16), gmp_strval($lastPoint['y'], 16))) |
425
|
|
|
throw new \Exception('The resulting point is not on the curve.'); |
426
|
|
|
return $lastPoint; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/*** |
430
|
|
|
* Calculates the square root of $a mod p and returns the 2 solutions as an array. |
431
|
|
|
* |
432
|
|
|
* @param resource $a (GMP) |
433
|
|
|
* @return array|null |
434
|
|
|
* @throws \Exception |
435
|
|
|
*/ |
436
|
|
|
public function sqrt($a) |
437
|
|
|
{ |
438
|
|
|
$p = $this->p; |
439
|
|
|
|
440
|
|
|
if(gmp_legendre($a, $p) !== 1) |
441
|
|
|
{ |
442
|
|
|
//no result |
443
|
|
|
return null; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
if(gmp_strval(gmp_mod($p, gmp_init(4, 10)), 10) === '3') |
447
|
|
|
{ |
448
|
|
|
$sqrt1 = gmp_powm( |
449
|
|
|
$a, |
450
|
|
|
gmp_div_q( |
451
|
|
|
gmp_add($p, gmp_init(1, 10)), |
452
|
|
|
gmp_init(4, 10) |
453
|
|
|
), |
454
|
|
|
$p |
455
|
|
|
); |
456
|
|
|
// there are always 2 results for a square root |
457
|
|
|
// In an infinite number field you have -2^2 = 2^2 = 4 |
458
|
|
|
// In a finite number field you have a^2 = (p-a)^2 |
459
|
|
|
$sqrt2 = gmp_mod(gmp_sub($p, $sqrt1), $p); |
460
|
|
|
return [$sqrt1, $sqrt2]; |
461
|
|
|
} |
462
|
|
|
else |
463
|
|
|
{ |
464
|
|
|
throw new \Exception('P % 4 != 3 , this isn\'t supported yet.'); |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/*** |
469
|
|
|
* Calculate the Y coordinates for a given X coordinate. |
470
|
|
|
* |
471
|
|
|
* @param string $x (hexa) |
472
|
|
|
* @param null $derEvenOrOddCode |
473
|
|
|
* @return array|null|String |
474
|
|
|
*/ |
475
|
|
|
public function calculateYWithX($x, $derEvenOrOddCode = null) |
476
|
|
|
{ |
477
|
|
|
$a = $this->a; |
478
|
|
|
$b = $this->b; |
479
|
|
|
$p = $this->p; |
480
|
|
|
|
481
|
|
|
$x = gmp_init($x, 16); |
482
|
|
|
$y2 = gmp_mod( |
483
|
|
|
gmp_add( |
484
|
|
|
gmp_add( |
485
|
|
|
gmp_powm($x, gmp_init(3, 10), $p), |
486
|
|
|
gmp_mul($a, $x) |
487
|
|
|
), |
488
|
|
|
$b |
489
|
|
|
), |
490
|
|
|
$p |
491
|
|
|
); |
492
|
|
|
|
493
|
|
|
$y = $this->sqrt($y2); |
494
|
|
|
|
495
|
|
|
if($y === null) //if there is no result |
496
|
|
|
{ |
497
|
|
|
return null; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
if($derEvenOrOddCode === null) |
501
|
|
|
{ |
502
|
|
|
return $y; |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
else if($derEvenOrOddCode === '02') // even |
506
|
|
|
{ |
507
|
|
|
$resY = null; |
508
|
|
View Code Duplication |
if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '0') |
|
|
|
|
509
|
|
|
$resY = gmp_strval($y[0], 16); |
510
|
|
View Code Duplication |
if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '0') |
|
|
|
|
511
|
|
|
$resY = gmp_strval($y[1], 16); |
512
|
|
View Code Duplication |
if($resY !== null) |
|
|
|
|
513
|
|
|
{ |
514
|
|
|
while(strlen($resY) < 64) |
515
|
|
|
{ |
516
|
|
|
$resY = '0' . $resY; |
517
|
|
|
} |
518
|
|
|
} |
519
|
|
|
return $resY; |
520
|
|
|
} |
521
|
|
|
else if($derEvenOrOddCode === '03') // odd |
522
|
|
|
{ |
523
|
|
|
$resY = null; |
524
|
|
View Code Duplication |
if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '1') |
|
|
|
|
525
|
|
|
$resY = gmp_strval($y[0], 16); |
526
|
|
View Code Duplication |
if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '1') |
|
|
|
|
527
|
|
|
$resY = gmp_strval($y[1], 16); |
528
|
|
View Code Duplication |
if($resY !== null) |
|
|
|
|
529
|
|
|
{ |
530
|
|
|
while(strlen($resY) < 64) |
531
|
|
|
{ |
532
|
|
|
$resY = '0' . $resY; |
533
|
|
|
} |
534
|
|
|
} |
535
|
|
|
return $resY; |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
return null; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
/*** |
542
|
|
|
* returns the public key coordinates as an array. |
543
|
|
|
* |
544
|
|
|
* @param string $derPubKey (hexa) |
545
|
|
|
* @return array |
546
|
|
|
* @throws \Exception |
547
|
|
|
*/ |
548
|
|
|
public function getPubKeyPointsWithDerPubKey($derPubKey) |
549
|
|
|
{ |
550
|
|
|
if(substr($derPubKey, 0, 2) === '04' && strlen($derPubKey) === 130) |
551
|
|
|
{ |
552
|
|
|
//uncompressed der encoded public key |
553
|
|
|
$x = substr($derPubKey, 2, 64); |
554
|
|
|
$y = substr($derPubKey, 66, 64); |
555
|
|
|
return ['x' => $x, 'y' => $y]; |
556
|
|
|
} |
557
|
|
|
else if((substr($derPubKey, 0, 2) === '02' || substr($derPubKey, 0, 2) === '03') && strlen($derPubKey) === 66) |
558
|
|
|
{ |
559
|
|
|
//compressed der encoded public key |
560
|
|
|
$x = substr($derPubKey, 2, 64); |
561
|
|
|
$y = $this->calculateYWithX($x, substr($derPubKey, 0, 2)); |
562
|
|
|
return ['x' => $x, 'y' => $y]; |
563
|
|
|
} |
564
|
|
|
else |
565
|
|
|
{ |
566
|
|
|
throw new \Exception('Invalid derPubKey format : ' . $derPubKey); |
567
|
|
|
} |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* @param array $pubKey (array <x:string, y:string>) |
573
|
|
|
* @param bool $compressed |
574
|
|
|
* @return string |
575
|
|
|
*/ |
576
|
|
|
public function getDerPubKeyWithPubKeyPoints($pubKey, $compressed = true) |
577
|
|
|
{ |
578
|
|
|
if($compressed === false) |
579
|
|
|
{ |
580
|
|
|
return '04' . $pubKey['x'] . $pubKey['y']; |
581
|
|
|
} |
582
|
|
View Code Duplication |
else |
|
|
|
|
583
|
|
|
{ |
584
|
|
|
if(gmp_strval(gmp_mod(gmp_init($pubKey['y'], 16), gmp_init(2, 10))) === '0') |
585
|
|
|
$pubKey = '02' . $pubKey['x']; //if $pubKey['y'] is even |
586
|
|
|
else |
587
|
|
|
$pubKey = '03' . $pubKey['x']; //if $pubKey['y'] is odd |
588
|
|
|
|
589
|
|
|
return $pubKey; |
590
|
|
|
} |
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
/*** |
594
|
|
|
* Returns true if the point is on the curve and false if it isn't. |
595
|
|
|
* |
596
|
|
|
* @param string $x (hexa) |
597
|
|
|
* @param string $y (hexa) |
598
|
|
|
* @return bool |
599
|
|
|
*/ |
600
|
|
|
public function validatePoint($x, $y) |
601
|
|
|
{ |
602
|
|
|
$a = $this->a; |
603
|
|
|
$b = $this->b; |
604
|
|
|
$p = $this->p; |
605
|
|
|
|
606
|
|
|
$x = gmp_init($x, 16); |
607
|
|
|
$y2 = gmp_mod( |
608
|
|
|
gmp_add( |
609
|
|
|
gmp_add( |
610
|
|
|
gmp_powm($x, gmp_init(3, 10), $p), |
611
|
|
|
gmp_mul($a, $x) |
612
|
|
|
), |
613
|
|
|
$b |
614
|
|
|
), |
615
|
|
|
$p |
616
|
|
|
); |
617
|
|
|
$y = gmp_mod(gmp_pow(gmp_init($y, 16), 2), $p); |
618
|
|
|
|
619
|
|
|
if(gmp_cmp($y2, $y) === 0) |
620
|
|
|
return true; |
621
|
|
|
else |
622
|
|
|
return false; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/*** |
626
|
|
|
* returns the X and Y point coordinates of the public key. |
627
|
|
|
* |
628
|
|
|
* @return Array Point |
629
|
|
|
* @throws \Exception |
630
|
|
|
*/ |
631
|
|
|
public function getPubKeyPoints() |
632
|
|
|
{ |
633
|
|
|
$G = $this->G; |
634
|
|
|
$k = $this->k; |
635
|
|
|
|
636
|
|
|
if(!isset($this->k)) |
637
|
|
|
{ |
638
|
|
|
throw new \Exception('No Private Key was defined'); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
$pubKey = $this->mulPoint( |
642
|
|
|
$k, |
643
|
|
|
['x' => $G['x'], 'y' => $G['y']] |
644
|
|
|
); |
645
|
|
|
|
646
|
|
|
$pubKey['x'] = gmp_strval($pubKey['x'], 16); |
647
|
|
|
$pubKey['y'] = gmp_strval($pubKey['y'], 16); |
648
|
|
|
|
649
|
|
View Code Duplication |
while(strlen($pubKey['x']) < 64) |
|
|
|
|
650
|
|
|
{ |
651
|
|
|
$pubKey['x'] = '0' . $pubKey['x']; |
652
|
|
|
} |
653
|
|
|
|
654
|
|
View Code Duplication |
while(strlen($pubKey['y']) < 64) |
|
|
|
|
655
|
|
|
{ |
656
|
|
|
$pubKey['y'] = '0' . $pubKey['y']; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return $pubKey; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/*** |
663
|
|
|
* returns the uncompressed DER encoded public key. |
664
|
|
|
* |
665
|
|
|
* @param array $pubKeyPts (array <x:string, y:string>) |
666
|
|
|
* @return string (hexa) |
667
|
|
|
* @throws \Exception |
668
|
|
|
*/ |
669
|
|
|
public function getUncompressedPubKey(array $pubKeyPts = []) |
670
|
|
|
{ |
671
|
|
|
if(empty($pubKeyPts)) |
672
|
|
|
$pubKeyPts = $this->getPubKeyPoints(); |
673
|
|
|
$uncompressedPubKey = '04' . $pubKeyPts['x'] . $pubKeyPts['y']; |
674
|
|
|
|
675
|
|
|
return $uncompressedPubKey; |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
/*** |
679
|
|
|
* returns the compressed DER encoded public key. |
680
|
|
|
* |
681
|
|
|
* @param array $pubKeyPts (array <x:string, y:string>) |
682
|
|
|
* @return array|string |
683
|
|
|
* @throws \Exception |
684
|
|
|
*/ |
685
|
|
|
public function getPubKey(array $pubKeyPts = []) |
686
|
|
|
{ |
687
|
|
|
if(empty($pubKeyPts)) |
688
|
|
|
$pubKeyPts = $this->getPubKeyPoints(); |
689
|
|
|
|
690
|
|
View Code Duplication |
if(gmp_strval(gmp_mod(gmp_init($pubKeyPts['y'], 16), gmp_init(2, 10))) === '0') |
|
|
|
|
691
|
|
|
$compressedPubKey = '02' . $pubKeyPts['x']; //if $pubKey['y'] is even |
692
|
|
|
else |
693
|
|
|
$compressedPubKey = '03' . $pubKeyPts['x']; //if $pubKey['y'] is odd |
694
|
|
|
|
695
|
|
|
return $compressedPubKey; |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
/*** |
699
|
|
|
* returns the uncompressed Bitcoin address generated from the private key if $compressed is false and |
700
|
|
|
* the compressed if $compressed is true. |
701
|
|
|
* |
702
|
|
|
* @param bool $compressed |
703
|
|
|
* @param string $derPubKey (hexa) |
704
|
|
|
* @throws \Exception |
705
|
|
|
* @return String Base58 |
706
|
|
|
*/ |
707
|
|
|
public function getUncompressedAddress($compressed = false, $derPubKey = null) |
708
|
|
|
{ |
709
|
|
|
if($derPubKey !== null) |
710
|
|
|
{ |
711
|
|
|
if($compressed === true) { |
712
|
|
|
$address = $this->getPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey)); |
713
|
|
|
} |
714
|
|
|
else { |
715
|
|
|
$address = $this->getUncompressedPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey)); |
716
|
|
|
} |
717
|
|
|
} |
718
|
|
|
else |
719
|
|
|
{ |
720
|
|
|
if($compressed === true) { |
721
|
|
|
$address = $this->getPubKey(); |
722
|
|
|
} |
723
|
|
|
else { |
724
|
|
|
$address = $this->getUncompressedPubKey(); |
725
|
|
|
} |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
$address = $this->getNetworkPrefix() . $this->hash160(hex2bin($address)); |
729
|
|
|
|
730
|
|
|
//checksum |
731
|
|
|
$address = $address.substr($this->hash256(hex2bin($address)), 0, 8); |
732
|
|
|
$address = $this->base58_encode($address); |
733
|
|
|
|
734
|
|
|
if($this->validateAddress($address)) |
735
|
|
|
return $address; |
736
|
|
|
else |
737
|
|
|
throw new \Exception('the generated address seems not to be valid.'); |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
/*** |
741
|
|
|
* returns the compressed Bitcoin address generated from the private key. |
742
|
|
|
* |
743
|
|
|
* @param string $derPubKey (hexa) |
744
|
|
|
* @return String (base58) |
745
|
|
|
*/ |
746
|
|
|
public function getAddress($derPubKey = null) |
747
|
|
|
{ |
748
|
|
|
return $this->getUncompressedAddress(true, $derPubKey); |
749
|
|
|
} |
750
|
|
|
|
751
|
|
|
/*** |
752
|
|
|
* set a private key. |
753
|
|
|
* |
754
|
|
|
* @param string $k (hexa) |
755
|
|
|
* @throws \Exception |
756
|
|
|
*/ |
757
|
|
|
public function setPrivateKey($k) |
758
|
|
|
{ |
759
|
|
|
//private key has to be passed as an hexadecimal number |
760
|
|
|
if(gmp_cmp(gmp_init($k, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1) |
761
|
|
|
{ |
762
|
|
|
throw new \Exception('Private Key is not in the 1,n-1 range'); |
763
|
|
|
} |
764
|
|
|
$this->k = $k; |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/*** |
768
|
|
|
* return the private key. |
769
|
|
|
* |
770
|
|
|
* @return string (hexa) |
771
|
|
|
*/ |
772
|
|
|
public function getPrivateKey() |
773
|
|
|
{ |
774
|
|
|
return $this->k; |
775
|
|
|
} |
776
|
|
|
|
777
|
|
|
|
778
|
|
|
/*** |
779
|
|
|
* Generate a new random private key. |
780
|
|
|
* The extra parameter can be some random data typed down by the user or mouse movements to add randomness. |
781
|
|
|
* |
782
|
|
|
* @param string $extra |
783
|
|
|
* @throws \Exception |
784
|
|
|
*/ |
785
|
|
|
public function generateRandomPrivateKey($extra = 'FSQF5356dsdsqdfEFEQ3fq4q6dq4s5d') |
786
|
|
|
{ |
787
|
|
|
$this->k = $this->generateRandom256BitsHexaString($extra); |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
/*** |
791
|
|
|
* Tests if the address is valid or not. |
792
|
|
|
* |
793
|
|
|
* @param string $address (base58) |
794
|
|
|
* @return bool |
795
|
|
|
*/ |
796
|
|
|
public function validateAddress($address) |
797
|
|
|
{ |
798
|
|
|
$address = hex2bin($this->base58_decode($address)); |
799
|
|
|
if(strlen($address) !== 25) |
800
|
|
|
return false; |
801
|
|
|
$checksum = substr($address, 21, 4); |
802
|
|
|
$rawAddress = substr($address, 0, 21); |
803
|
|
|
|
804
|
|
|
if(substr(hex2bin($this->hash256($rawAddress)), 0, 4) === $checksum) |
805
|
|
|
return true; |
806
|
|
|
else |
807
|
|
|
return false; |
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
/*** |
811
|
|
|
* returns the private key under the Wallet Import Format |
812
|
|
|
* |
813
|
|
|
* @return string (base58) |
814
|
|
|
* @throws \Exception |
815
|
|
|
*/ |
816
|
|
|
public function getWif($compressed = true) |
817
|
|
|
{ |
818
|
|
|
if(!isset($this->k)) |
819
|
|
|
{ |
820
|
|
|
throw new \Exception('No Private Key was defined'); |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
$k = $this->k; |
824
|
|
|
|
825
|
|
|
while(strlen($k) < 64) |
826
|
|
|
$k = '0' . $k; |
827
|
|
|
|
828
|
|
|
$secretKey = '80' . $k; |
829
|
|
|
|
830
|
|
|
if($compressed) { |
831
|
|
|
$secretKey .= '01'; |
832
|
|
|
} |
833
|
|
|
|
834
|
|
|
$secretKey .= substr($this->hash256(hex2bin($secretKey)), 0, 8); |
835
|
|
|
|
836
|
|
|
return $this->base58_encode($secretKey); |
837
|
|
|
} |
838
|
|
|
|
839
|
|
|
/*** |
840
|
|
|
* returns the private key under the Wallet Import Format for an uncompressed address |
841
|
|
|
* |
842
|
|
|
* @return string (base58) |
843
|
|
|
* @throws \Exception |
844
|
|
|
*/ |
845
|
|
|
public function getUncompressedWif() |
846
|
|
|
{ |
847
|
|
|
return getWif(false); |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
/*** |
851
|
|
|
* Tests if the Wif key (Wallet Import Format) is valid or not. |
852
|
|
|
* |
853
|
|
|
* @param string $wif (base58) |
854
|
|
|
* @return bool |
855
|
|
|
*/ |
856
|
|
|
public function validateWifKey($wif) |
857
|
|
|
{ |
858
|
|
|
$key = $this->base58_decode($wif, true); |
859
|
|
|
$length = strlen($key); |
860
|
|
|
$checksum = $this->hash256(hex2bin(substr($key, 0, $length - 8))); |
861
|
|
|
if(substr($checksum, 0, 8) === substr($key, $length - 8, 8)) |
862
|
|
|
return true; |
863
|
|
|
else |
864
|
|
|
return false; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
/** |
868
|
|
|
* @param string $wif (base58) |
869
|
|
|
* @return bool |
870
|
|
|
*/ |
871
|
|
|
public function setPrivateKeyWithWif($wif) |
872
|
|
|
{ |
873
|
|
|
if(!$this->validateWifKey($wif)) { |
874
|
|
|
throw new \Exception('Invalid WIF'); |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
$key = $this->base58_decode($wif, true); |
878
|
|
|
|
879
|
|
|
$this->setPrivateKey(substr($key, 2, 64)); |
880
|
|
|
} |
881
|
|
|
|
882
|
|
|
/*** |
883
|
|
|
* Sign a hash with the private key that was set and returns signatures as an array (R,S) |
884
|
|
|
* |
885
|
|
|
* @param string $hash (hexa) |
886
|
|
|
* @param null $nonce |
887
|
|
|
* @throws \Exception |
888
|
|
|
* @return Array |
889
|
|
|
*/ |
890
|
|
|
public function getSignatureHashPoints($hash, $nonce = null) |
891
|
|
|
{ |
892
|
|
|
$n = $this->n; |
893
|
|
|
$k = $this->k; |
894
|
|
|
|
895
|
|
|
if(empty($k)) |
896
|
|
|
{ |
897
|
|
|
throw new \Exception('No Private Key was defined'); |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
if($nonce === null) |
901
|
|
|
{ |
902
|
|
|
$nonce = gmp_strval( |
903
|
|
|
gmp_mod( |
904
|
|
|
gmp_init($this->generateRandom256BitsHexaString(), 16), |
905
|
|
|
$n), |
906
|
|
|
16 |
907
|
|
|
); |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
//first part of the signature (R). |
911
|
|
|
|
912
|
|
|
$rPt = $this->mulPoint($nonce, $this->G); |
913
|
|
|
$R = gmp_strval($rPt ['x'], 16); |
914
|
|
|
|
915
|
|
|
while(strlen($R) < 64) |
916
|
|
|
{ |
917
|
|
|
$R = '0' . $R; |
918
|
|
|
} |
919
|
|
|
|
920
|
|
|
//second part of the signature (S). |
921
|
|
|
//S = nonce^-1 (hash + privKey * R) mod p |
922
|
|
|
|
923
|
|
|
|
924
|
|
|
$S = gmp_mod( |
925
|
|
|
gmp_mul( |
926
|
|
|
gmp_invert( |
927
|
|
|
gmp_init($nonce, 16), |
928
|
|
|
$n |
929
|
|
|
), |
930
|
|
|
gmp_add( |
931
|
|
|
gmp_init($hash, 16), |
932
|
|
|
gmp_mul( |
933
|
|
|
gmp_init($k, 16), |
934
|
|
|
gmp_init($R, 16) |
935
|
|
|
) |
936
|
|
|
) |
937
|
|
|
), |
938
|
|
|
$n |
939
|
|
|
); |
940
|
|
|
|
941
|
|
|
//BIP 62, make sure we use the low-s value |
942
|
|
|
if(gmp_cmp($S, gmp_div($n, 2)) === 1) |
943
|
|
|
{ |
944
|
|
|
$S = gmp_sub($n, $S); |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
$S = gmp_strval($S, 16); |
948
|
|
|
|
949
|
|
|
if(strlen($S)%2) |
950
|
|
|
{ |
951
|
|
|
$S = '0' . $S; |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
if(strlen($R)%2) |
955
|
|
|
{ |
956
|
|
|
$R = '0' . $R; |
957
|
|
|
} |
958
|
|
|
|
959
|
|
|
return ['R' => $R, 'S' => $S]; |
960
|
|
|
} |
961
|
|
|
|
962
|
|
|
/*** |
963
|
|
|
* Sign a hash with the private key that was set and returns a DER encoded signature |
964
|
|
|
* |
965
|
|
|
* @param string $hash (hexa) |
966
|
|
|
* @param null $nonce |
967
|
|
|
* @return string |
968
|
|
|
*/ |
969
|
|
|
public function signHash($hash, $nonce = null) |
970
|
|
|
{ |
971
|
|
|
$points = $this->getSignatureHashPoints($hash, $nonce); |
972
|
|
|
|
973
|
|
|
$signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S']; |
974
|
|
|
$signature = '30' . dechex(strlen(hex2bin($signature))) . $signature; |
975
|
|
|
|
976
|
|
|
return $signature; |
977
|
|
|
} |
978
|
|
|
|
979
|
|
|
/*** |
980
|
|
|
* Satoshi client's standard message signature implementation. |
981
|
|
|
* |
982
|
|
|
* @param string $message |
983
|
|
|
* @param bool $onlySignature |
984
|
|
|
* @param bool $compressed |
985
|
|
|
* @param null $nonce |
986
|
|
|
* @return string |
987
|
|
|
* @throws \Exception |
988
|
|
|
*/ |
989
|
|
|
public function signMessage($message, $onlySignature = false ,$compressed = true, $nonce = null) |
990
|
|
|
{ |
991
|
|
|
|
992
|
|
|
$hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)). $message); |
993
|
|
|
$points = $this->getSignatureHashPoints( |
994
|
|
|
$hash, |
995
|
|
|
$nonce |
996
|
|
|
); |
997
|
|
|
|
998
|
|
|
$R = $points['R']; |
999
|
|
|
$S = $points['S']; |
1000
|
|
|
|
1001
|
|
|
while(strlen($R) < 64) |
1002
|
|
|
$R = '0' . $R; |
1003
|
|
|
|
1004
|
|
|
while(strlen($S) < 64) |
1005
|
|
|
$S = '0' . $S; |
1006
|
|
|
|
1007
|
|
|
$res = "\n-----BEGIN BITCOIN SIGNED MESSAGE-----\n"; |
1008
|
|
|
$res .= $message; |
1009
|
|
|
$res .= "\n-----BEGIN SIGNATURE-----\n"; |
1010
|
|
|
if($compressed === true) |
1011
|
|
|
$res .= $this->getAddress() . "\n"; |
1012
|
|
|
else |
1013
|
|
|
$res .= $this->getUncompressedAddress() . "\n"; |
1014
|
|
|
|
1015
|
|
|
$finalFlag = 0; |
1016
|
|
|
for($i = 0; $i < 4; $i++) |
1017
|
|
|
{ |
1018
|
|
|
$flag = 27; |
1019
|
|
|
if($compressed === true) |
1020
|
|
|
$flag += 4; |
1021
|
|
|
$flag += $i; |
1022
|
|
|
|
1023
|
|
|
$pubKeyPts = $this->getPubKeyPoints(); |
1024
|
|
|
|
1025
|
|
|
$recoveredPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash); |
1026
|
|
|
|
1027
|
|
|
if($this->getDerPubKeyWithPubKeyPoints($pubKeyPts, $compressed) === $recoveredPubKey) |
1028
|
|
|
{ |
1029
|
|
|
$finalFlag = $flag; |
1030
|
|
|
} |
1031
|
|
|
} |
1032
|
|
|
|
1033
|
|
|
//echo "Final flag : " . dechex($finalFlag) . "\n"; |
1034
|
|
|
if($finalFlag === 0) |
1035
|
|
|
{ |
1036
|
|
|
throw new \Exception('Unable to get a valid signature flag.'); |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
|
|
$signature = base64_encode(hex2bin(dechex($finalFlag) . $R . $S)); |
1040
|
|
|
|
1041
|
|
|
if($onlySignature) { |
1042
|
|
|
return $signature; |
1043
|
|
|
} |
1044
|
|
|
|
1045
|
|
|
$res .= $signature; |
1046
|
|
|
$res .= "\n-----END BITCOIN SIGNED MESSAGE-----"; |
1047
|
|
|
|
1048
|
|
|
return $res; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
/*** |
1052
|
|
|
* extract the public key from the signature and using the recovery flag. |
1053
|
|
|
* see http://crypto.stackexchange.com/a/18106/10927 |
1054
|
|
|
* based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js |
1055
|
|
|
* possible public keys are r−1(sR−zG) and r−1(sR′−zG) |
1056
|
|
|
* Recovery flag rules are : |
1057
|
|
|
* binary number between 28 and 35 inclusive |
1058
|
|
|
* if the flag is > 30 then the address is compressed. |
1059
|
|
|
* |
1060
|
|
|
* @param int $flag |
1061
|
|
|
* @param string $R (hexa) |
1062
|
|
|
* @param string $S (hexa) |
1063
|
|
|
* @param string $hash (hexa) |
1064
|
|
|
* @return array |
1065
|
|
|
*/ |
1066
|
|
|
public function getPubKeyWithRS($flag, $R, $S, $hash) |
1067
|
|
|
{ |
1068
|
|
|
|
1069
|
|
|
$isCompressed = false; |
1070
|
|
|
|
1071
|
|
|
if ($flag < 27 || $flag >= 35) |
1072
|
|
|
return null; |
1073
|
|
|
|
1074
|
|
|
if($flag >= 31) //if address is compressed |
1075
|
|
|
{ |
1076
|
|
|
$isCompressed = true; |
1077
|
|
|
$flag -= 4; |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
$recid = $flag - 27; |
1081
|
|
|
|
1082
|
|
|
//step 1.1 |
1083
|
|
|
$x = gmp_add( |
1084
|
|
|
gmp_init($R, 16), |
1085
|
|
|
gmp_mul( |
1086
|
|
|
$this->n, |
1087
|
|
|
gmp_div_q( //check if j is equal to 0 or to 1. |
1088
|
|
|
gmp_init($recid, 10), |
1089
|
|
|
gmp_init(2, 10) |
1090
|
|
|
) |
1091
|
|
|
) |
1092
|
|
|
); |
1093
|
|
|
|
1094
|
|
|
//step 1.3 |
1095
|
|
|
$y = null; |
1096
|
|
|
if($flag % 2 === 1) //check if y is even. |
1097
|
|
|
{ |
1098
|
|
|
$gmpY = $this->calculateYWithX(gmp_strval($x, 16), '02'); |
1099
|
|
|
if($gmpY !== null) |
1100
|
|
|
$y = gmp_init($gmpY, 16); |
1101
|
|
|
} |
1102
|
|
|
else |
1103
|
|
|
{ |
1104
|
|
|
$gmpY = $this->calculateYWithX(gmp_strval($x, 16), '03'); |
1105
|
|
|
if($gmpY !== null) |
1106
|
|
|
$y = gmp_init($gmpY, 16); |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
if($y === null) |
1110
|
|
|
return null; |
1111
|
|
|
|
1112
|
|
|
$Rpt = ['x' => $x, 'y' => $y]; |
1113
|
|
|
|
1114
|
|
|
//step 1.6.1 |
1115
|
|
|
//calculate r^-1 (S*Rpt - eG) |
1116
|
|
|
|
1117
|
|
|
$eG = $this->mulPoint($hash, $this->G); |
1118
|
|
|
|
1119
|
|
|
$eG['y'] = gmp_mod(gmp_neg($eG['y']), $this->p); |
1120
|
|
|
|
1121
|
|
|
$SR = $this->mulPoint($S, $Rpt); |
1122
|
|
|
|
1123
|
|
|
$pubKey = $this->mulPoint( |
1124
|
|
|
gmp_strval(gmp_invert(gmp_init($R, 16), $this->n), 16), |
1125
|
|
|
$this->addPoints( |
1126
|
|
|
$SR, |
1127
|
|
|
$eG |
1128
|
|
|
) |
1129
|
|
|
); |
1130
|
|
|
|
1131
|
|
|
$pubKey['x'] = gmp_strval($pubKey['x'], 16); |
1132
|
|
|
$pubKey['y'] = gmp_strval($pubKey['y'], 16); |
1133
|
|
|
|
1134
|
|
View Code Duplication |
while(strlen($pubKey['x']) < 64) |
|
|
|
|
1135
|
|
|
$pubKey['x'] = '0' . $pubKey['x']; |
1136
|
|
|
|
1137
|
|
View Code Duplication |
while(strlen($pubKey['y']) < 64) |
|
|
|
|
1138
|
|
|
$pubKey['y'] = '0' . $pubKey['y']; |
1139
|
|
|
|
1140
|
|
|
$derPubKey = $this->getDerPubKeyWithPubKeyPoints($pubKey, $isCompressed); |
1141
|
|
|
|
1142
|
|
|
|
1143
|
|
|
if($this->checkSignaturePoints($derPubKey, $R, $S, $hash)) |
1144
|
|
|
return $derPubKey; |
1145
|
|
|
else |
1146
|
|
|
return null; |
1147
|
|
|
|
1148
|
|
|
} |
1149
|
|
|
|
1150
|
|
|
/*** |
1151
|
|
|
* Check signature with public key R & S values of the signature and the message hash. |
1152
|
|
|
* |
1153
|
|
|
* @param string $pubKey (hexa) |
1154
|
|
|
* @param string $R (hexa) |
1155
|
|
|
* @param string $S (hexa) |
1156
|
|
|
* @param string $hash (hexa) |
1157
|
|
|
* @return bool |
1158
|
|
|
*/ |
1159
|
|
|
public function checkSignaturePoints($pubKey, $R, $S, $hash) |
1160
|
|
|
{ |
1161
|
|
|
$G = $this->G; |
1162
|
|
|
|
1163
|
|
|
$pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey); |
1164
|
|
|
|
1165
|
|
|
// S^-1* hash * G + S^-1 * R * Qa |
1166
|
|
|
|
1167
|
|
|
// S^-1* hash |
1168
|
|
|
$exp1 = gmp_strval( |
1169
|
|
|
gmp_mul( |
1170
|
|
|
gmp_invert( |
1171
|
|
|
gmp_init($S, 16), |
1172
|
|
|
$this->n |
1173
|
|
|
), |
1174
|
|
|
gmp_init($hash, 16) |
1175
|
|
|
), |
1176
|
|
|
16 |
1177
|
|
|
); |
1178
|
|
|
|
1179
|
|
|
// S^-1* hash * G |
1180
|
|
|
$exp1Pt = $this->mulPoint($exp1, $G); |
1181
|
|
|
|
1182
|
|
|
|
1183
|
|
|
// S^-1 * R |
1184
|
|
|
$exp2 = gmp_strval( |
1185
|
|
|
gmp_mul( |
1186
|
|
|
gmp_invert( |
1187
|
|
|
gmp_init($S, 16), |
1188
|
|
|
$this->n |
1189
|
|
|
), |
1190
|
|
|
gmp_init($R, 16) |
1191
|
|
|
), |
1192
|
|
|
16 |
1193
|
|
|
); |
1194
|
|
|
// S^-1 * R * Qa |
1195
|
|
|
|
1196
|
|
|
$pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16); |
1197
|
|
|
$pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16); |
1198
|
|
|
|
1199
|
|
|
$exp2Pt = $this->mulPoint($exp2,$pubKeyPts); |
1200
|
|
|
|
1201
|
|
|
$resultingPt = $this->addPoints($exp1Pt, $exp2Pt); |
1202
|
|
|
|
1203
|
|
|
$xRes = gmp_strval($resultingPt['x'], 16); |
1204
|
|
|
|
1205
|
|
|
while(strlen($xRes) < 64) |
1206
|
|
|
$xRes = '0' . $xRes; |
1207
|
|
|
|
1208
|
|
|
if(strtoupper($xRes) === strtoupper($R)) |
1209
|
|
|
return true; |
1210
|
|
|
else |
1211
|
|
|
return false; |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
/*** |
1215
|
|
|
* checkSignaturePoints wrapper for DER signatures |
1216
|
|
|
* |
1217
|
|
|
* @param string $pubKey (hexa) |
1218
|
|
|
* @param string $signature (hexa) |
1219
|
|
|
* @param string $hash (hexa) |
1220
|
|
|
* @return bool |
1221
|
|
|
*/ |
1222
|
|
|
public function checkDerSignature($pubKey, $signature, $hash) |
1223
|
|
|
{ |
1224
|
|
|
$signature = hex2bin($signature); |
1225
|
|
|
if(bin2hex(substr($signature, 0, 1)) !== '30') |
1226
|
|
|
return false; |
1227
|
|
|
|
1228
|
|
|
$RLength = hexdec(bin2hex(substr($signature, 3, 1))); |
1229
|
|
|
$R = bin2hex(substr($signature, 4, $RLength)); |
1230
|
|
|
|
1231
|
|
|
$SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1))); |
1232
|
|
|
$S = bin2hex(substr($signature, $RLength + 6, $SLength)); |
1233
|
|
|
|
1234
|
|
|
//echo "\n\nsignature:\n"; |
1235
|
|
|
//print_r(bin2hex($signature)); |
1236
|
|
|
|
1237
|
|
|
//echo "\n\nR:\n"; |
1238
|
|
|
//print_r($R); |
1239
|
|
|
//echo "\n\nS:\n"; |
1240
|
|
|
//print_r($S); |
1241
|
|
|
|
1242
|
|
|
return $this->checkSignaturePoints($pubKey, $R, $S, $hash); |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
/*** |
1246
|
|
|
* checks the signature of a bitcoin signed message. |
1247
|
|
|
* |
1248
|
|
|
* @param string $rawMessage |
1249
|
|
|
* @return bool |
1250
|
|
|
*/ |
1251
|
|
|
public function checkSignatureForRawMessage($rawMessage) |
1252
|
|
|
{ |
1253
|
|
|
//recover message. |
1254
|
|
|
preg_match_all("#-----BEGIN BITCOIN SIGNED MESSAGE-----\n(.{0,})\n-----BEGIN SIGNATURE-----\n#USi", $rawMessage, $out); |
1255
|
|
|
$message = $out[1][0]; |
1256
|
|
|
|
1257
|
|
|
preg_match_all("#\n-----BEGIN SIGNATURE-----\n(.{0,})\n(.{0,})\n-----END BITCOIN SIGNED MESSAGE-----#USi", $rawMessage, $out); |
1258
|
|
|
$address = $out[1][0]; |
1259
|
|
|
$signature = $out[2][0]; |
1260
|
|
|
|
1261
|
|
|
return $this->checkSignatureForMessage($address, $signature, $message); |
1262
|
|
|
} |
1263
|
|
|
|
1264
|
|
|
/*** |
1265
|
|
|
* checks the signature of a bitcoin signed message. |
1266
|
|
|
* |
1267
|
|
|
* @param string $address (base58) |
1268
|
|
|
* @param string $encodedSignature (base64) |
1269
|
|
|
* @param string $message |
1270
|
|
|
* @return bool |
1271
|
|
|
*/ |
1272
|
|
|
public function checkSignatureForMessage($address, $encodedSignature, $message) |
1273
|
|
|
{ |
1274
|
|
|
$hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message); |
1275
|
|
|
|
1276
|
|
|
//recover flag |
1277
|
|
|
$signature = base64_decode($encodedSignature); |
1278
|
|
|
|
1279
|
|
|
$flag = hexdec(bin2hex(substr($signature, 0, 1))); |
1280
|
|
|
|
1281
|
|
|
$isCompressed = false; |
1282
|
|
|
if($flag >= 31 & $flag < 35) //if address is compressed |
1283
|
|
|
{ |
1284
|
|
|
$isCompressed = true; |
1285
|
|
|
} |
1286
|
|
|
|
1287
|
|
|
$R = bin2hex(substr($signature, 1, 32)); |
1288
|
|
|
$S = bin2hex(substr($signature, 33, 32)); |
1289
|
|
|
|
1290
|
|
|
$derPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash); |
1291
|
|
|
|
1292
|
|
|
if($isCompressed === true) |
1293
|
|
|
$recoveredAddress = $this->getAddress($derPubKey); |
1294
|
|
|
else |
1295
|
|
|
$recoveredAddress = $this->getUncompressedAddress(false, $derPubKey); |
1296
|
|
|
|
1297
|
|
|
if($address === $recoveredAddress) |
1298
|
|
|
return true; |
1299
|
|
|
else |
1300
|
|
|
return false; |
1301
|
|
|
} |
1302
|
|
|
} |
1303
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.