| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Web3::Tiny::Secp256k1; |
|
2
|
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
79900
|
use strict; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
80
|
|
|
4
|
3
|
|
|
3
|
|
37
|
use warnings; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
109
|
|
|
5
|
3
|
|
|
3
|
|
12
|
use Exporter 'import'; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
87
|
|
|
6
|
3
|
|
|
3
|
|
1573
|
use Crypt::PK::ECC; |
|
|
3
|
|
|
|
|
48443
|
|
|
|
3
|
|
|
|
|
179
|
|
|
7
|
3
|
|
|
3
|
|
4141
|
use Math::BigInt try => 'GMP,Pari'; |
|
|
3
|
|
|
|
|
106348
|
|
|
|
3
|
|
|
|
|
15
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
3
|
|
|
3
|
|
76978
|
use Web3::Tiny::Keccak256 qw(keccak256); |
|
|
3
|
|
|
|
|
10
|
|
|
|
3
|
|
|
|
|
2269
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
|
12
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
|
13
|
|
|
|
|
|
|
privkey_to_pubkey pubkey_to_address privkey_to_address |
|
14
|
|
|
|
|
|
|
sign_hash recover_pubkey verify_hash |
|
15
|
|
|
|
|
|
|
); |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# secp256k1 group order (as used by Bitcoin and Ethereum), needed only for |
|
18
|
|
|
|
|
|
|
# the low-S normalization required by Ethereum since EIP-2 / Homestead. |
|
19
|
|
|
|
|
|
|
my $N = Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); |
|
20
|
|
|
|
|
|
|
my $HALF_N = $N->copy->bsub(1)->bdiv(2); |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub _bytes_to_bi { |
|
23
|
10
|
|
|
10
|
|
33
|
my ($bytes) = @_; |
|
24
|
10
|
|
|
|
|
141
|
return Math::BigInt->from_hex('0x' . unpack('H*', $bytes)); |
|
25
|
|
|
|
|
|
|
} |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub _bi_to_bytes { |
|
28
|
5
|
|
|
5
|
|
809
|
my ($bi, $len) = @_; |
|
29
|
5
|
|
|
|
|
22
|
my $hex = $bi->as_hex; |
|
30
|
5
|
|
|
|
|
3468
|
$hex =~ s/^0x//; |
|
31
|
5
|
100
|
|
|
|
18
|
$hex = '0' . $hex if length($hex) % 2; |
|
32
|
5
|
|
|
|
|
26
|
my $bytes = pack('H*', $hex); |
|
33
|
5
|
50
|
|
|
|
15
|
$bytes = ("\x00" x ($len - length($bytes))) . $bytes if length($bytes) < $len; |
|
34
|
5
|
|
|
|
|
10
|
return $bytes; |
|
35
|
|
|
|
|
|
|
} |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# privkey_to_pubkey($privkey_bytes) -> ($x_bytes32, $y_bytes32) |
|
38
|
|
|
|
|
|
|
sub privkey_to_pubkey { |
|
39
|
5
|
|
|
5
|
0
|
10
|
my ($priv_bytes) = @_; |
|
40
|
5
|
|
|
|
|
31
|
my $pk = Crypt::PK::ECC->new; |
|
41
|
5
|
|
|
|
|
450
|
eval { $pk->import_key_raw($priv_bytes, 'secp256k1') }; |
|
|
5
|
|
|
|
|
25047
|
|
|
42
|
5
|
50
|
|
|
|
35
|
die "Web3::Tiny::Secp256k1: private key out of range\n" if $@; |
|
43
|
|
|
|
|
|
|
|
|
44
|
5
|
|
|
|
|
90
|
my $pub = $pk->export_key_raw('public'); # 0x04 || X(32) || Y(32) |
|
45
|
5
|
|
|
|
|
158
|
return (substr($pub, 1, 32), substr($pub, 33, 32)); |
|
46
|
|
|
|
|
|
|
} |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# pubkey_to_address($x_bytes32, $y_bytes32) -> 20 raw bytes |
|
49
|
|
|
|
|
|
|
sub pubkey_to_address { |
|
50
|
5
|
|
|
5
|
0
|
15
|
my ($x, $y) = @_; |
|
51
|
5
|
|
|
|
|
34
|
my $hash = keccak256($x . $y); |
|
52
|
5
|
|
|
|
|
96
|
return substr($hash, 12, 20); |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub privkey_to_address { |
|
56
|
3
|
|
|
3
|
0
|
142699
|
my ($priv_bytes) = @_; |
|
57
|
3
|
|
|
|
|
9
|
my ($x, $y) = privkey_to_pubkey($priv_bytes); |
|
58
|
3
|
|
|
|
|
10
|
return pubkey_to_address($x, $y); |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# sign_hash($hash32_bytes, $priv_bytes) -> ($r_bytes32, $s_bytes32, $recid) |
|
62
|
|
|
|
|
|
|
# |
|
63
|
|
|
|
|
|
|
# RFC 6979 deterministic (HMAC-SHA256 nonce), normalized to low-s form |
|
64
|
|
|
|
|
|
|
# (s <= N/2) as required by Ethereum since EIP-2 / Homestead. CryptX signs |
|
65
|
|
|
|
|
|
|
# but does not itself enforce low-s, so that normalization happens here. |
|
66
|
|
|
|
|
|
|
sub sign_hash { |
|
67
|
10
|
|
|
10
|
0
|
1414
|
my ($hash, $priv_bytes) = @_; |
|
68
|
10
|
50
|
|
|
|
35
|
die "Web3::Tiny::Secp256k1: hash must be 32 bytes\n" unless length($hash) == 32; |
|
69
|
|
|
|
|
|
|
|
|
70
|
10
|
|
|
|
|
63
|
my $pk = Crypt::PK::ECC->new; |
|
71
|
10
|
|
|
|
|
748
|
eval { $pk->import_key_raw($priv_bytes, 'secp256k1') }; |
|
|
10
|
|
|
|
|
63591
|
|
|
72
|
10
|
50
|
|
|
|
60
|
die "Web3::Tiny::Secp256k1: private key out of range\n" if $@; |
|
73
|
|
|
|
|
|
|
|
|
74
|
10
|
|
|
|
|
64370
|
my $sig = $pk->sign_hash_eth($hash, 'SHA256'); |
|
75
|
10
|
|
|
|
|
91
|
my $r = substr($sig, 0, 32); |
|
76
|
10
|
|
|
|
|
30
|
my $s = substr($sig, 32, 32); |
|
77
|
10
|
|
|
|
|
60
|
my $recid = unpack('C', substr($sig, 64, 1)) - 27; |
|
78
|
|
|
|
|
|
|
|
|
79
|
10
|
|
|
|
|
96
|
my $s_bi = _bytes_to_bi($s); |
|
80
|
10
|
100
|
|
|
|
11815
|
if ($s_bi->bcmp($HALF_N) > 0) { |
|
81
|
5
|
|
|
|
|
198
|
$s = _bi_to_bytes($N->copy->bsub($s_bi), 32); |
|
82
|
5
|
|
|
|
|
23
|
$recid ^= 1; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
10
|
|
|
|
|
714
|
return ($r, $s, $recid); |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# recover_pubkey($hash32, $r_bytes32, $s_bytes32, $recid) -> ($x_bytes32, $y_bytes32) |
|
89
|
|
|
|
|
|
|
sub recover_pubkey { |
|
90
|
8
|
|
|
8
|
0
|
68
|
my ($hash, $r_bytes, $s_bytes, $recid) = @_; |
|
91
|
|
|
|
|
|
|
|
|
92
|
8
|
|
|
|
|
34
|
my $sig = $r_bytes . $s_bytes . pack('C', 27 + $recid); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# recovery_pub_eth needs a curve context to recover into; the private |
|
95
|
|
|
|
|
|
|
# key generated here is discarded immediately, only its curve sticks. |
|
96
|
8
|
|
|
|
|
43
|
my $pk = Crypt::PK::ECC->new; |
|
97
|
8
|
|
|
|
|
26478
|
$pk->generate_key('secp256k1'); |
|
98
|
8
|
|
|
|
|
27
|
eval { $pk->recovery_pub_eth($sig, $hash) }; |
|
|
8
|
|
|
|
|
59354
|
|
|
99
|
8
|
50
|
|
|
|
51
|
die "Web3::Tiny::Secp256k1: invalid recovery id\n" if $@; |
|
100
|
|
|
|
|
|
|
|
|
101
|
8
|
|
|
|
|
107
|
my $pub = $pk->export_key_raw('public'); |
|
102
|
8
|
|
|
|
|
296
|
return (substr($pub, 1, 32), substr($pub, 33, 32)); |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub verify_hash { |
|
106
|
2
|
|
|
2
|
0
|
43
|
my ($hash, $r_bytes, $s_bytes, $pub_x, $pub_y) = @_; |
|
107
|
|
|
|
|
|
|
|
|
108
|
2
|
|
|
|
|
10
|
my $pk = Crypt::PK::ECC->new; |
|
109
|
2
|
|
|
|
|
104
|
eval { $pk->import_key_raw("\x04" . $pub_x . $pub_y, 'secp256k1') }; |
|
|
2
|
|
|
|
|
5671
|
|
|
110
|
2
|
50
|
|
|
|
20
|
return 0 if $@; |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# the recovery-id byte is irrelevant to verification, only to recovery |
|
113
|
2
|
|
|
|
|
5
|
my $sig = $r_bytes . $s_bytes . "\x1b"; |
|
114
|
2
|
100
|
|
|
|
3661
|
return $pk->verify_hash_eth($sig, $hash) ? 1 : 0; |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
1; |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
__END__ |