schnorr_sig 0.0.0.4 → 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +24 -5
- data/VERSION +1 -1
- data/lib/schnorr_sig/fast.rb +2 -2
- data/lib/schnorr_sig/pure.rb +221 -0
- data/lib/schnorr_sig/util.rb +1 -1
- data/lib/schnorr_sig.rb +4 -219
- data/test/vectors.rb +2 -3
- data/test/vectors_extra.rb +1 -2
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37ba87126b971e1e74099bbed572cb7e088902db0cba9fdf38ef5de96ab75f69
|
4
|
+
data.tar.gz: 9001aae50944fcf3daf558db01a44c6b5f4ee7e74f729292026c83fe38c1367d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a85306c995daa8ce6b30b303e530892a4014f367854dccda5a3f119dc2170087e04fd069d6a8516f30bfe5d1b2e5e351ae495f331b65d1ab920ec767d60f154b
|
7
|
+
data.tar.gz: e7abe94145d870f072924e555e7510e886159aef5119375892ac10243abd08ccf72ff078f545b68b3502a9d09f644f38d5512220796e71b138b24f642813bc78
|
data/README.md
CHANGED
@@ -14,6 +14,24 @@ and specifications similar to
|
|
14
14
|
[IETF RFCs](https://en.wikipedia.org/wiki/Request_for_Comments).
|
15
15
|
BIP340 specifies elliptic curve `secp256k1` for use with Schnorr signatures.
|
16
16
|
|
17
|
+
Two separate implementations are provided.
|
18
|
+
|
19
|
+
## Ruby Implementation
|
20
|
+
|
21
|
+
This is the default implementation: entirely Ruby code within this library,
|
22
|
+
with mostly-Ruby dependencies:
|
23
|
+
|
24
|
+
* [ecdsa_ext](https://github.com/azuchi/ruby_ecdsa_ext)
|
25
|
+
- [ecdsa](https://github.com/DavidEGrayson/ruby_ecdsa/)
|
26
|
+
|
27
|
+
## "Fast" Implementation
|
28
|
+
|
29
|
+
This is based on the [rbsecp256k1](https://github.com/etscrivner/rbsecp256k1)
|
30
|
+
gem, which is not installed by default. The gem wraps the
|
31
|
+
[secp256k1](https://github.com/bitcoin-core/secp256k1) library from the
|
32
|
+
Bitcoin project, which provides battle-tested performance, correctness, and
|
33
|
+
security guarantees.
|
34
|
+
|
17
35
|
# Usage
|
18
36
|
|
19
37
|
This library is provided as a RubyGem. It has a single dependency on
|
@@ -77,9 +95,9 @@ require 'schnorr_sig/fast' # not 'schnorr_sig'
|
|
77
95
|
# Elliptic Curves
|
78
96
|
|
79
97
|
Note that [elliptic curves](https://en.wikipedia.org/wiki/Elliptic_curve)
|
80
|
-
are not ellipses, but
|
98
|
+
are not ellipses, but are instead described by cubic equations of
|
81
99
|
the form: `y^2 = x^3 + ax + b` where `a` and `b` are the parameters of the
|
82
|
-
resulting
|
100
|
+
resulting equation. All points `(x, y)` which satisfy a given parameterized
|
83
101
|
equation provide the exact definition of an elliptic curve.
|
84
102
|
|
85
103
|
## Curve `secp256k1`
|
@@ -104,11 +122,11 @@ Here is one
|
|
104
122
|
}
|
105
123
|
```
|
106
124
|
|
107
|
-
* `p` is the prime for the Field, below INTMAX(32) (256^32)
|
125
|
+
* `p` is the prime for the Field, below `INTMAX(32)` (256^32)
|
108
126
|
* `a` is zero, as above
|
109
127
|
* `b` is seven, as above
|
110
|
-
* `g` is the generator point: [x, y]
|
111
|
-
* `n` is the Group order, significantly below INTMAX(32)
|
128
|
+
* `g` is the generator point: `[x, y]`
|
129
|
+
* `n` is the Group order, significantly below `INTMAX(32)`
|
112
130
|
|
113
131
|
Elliptic curves have algebraic structures called
|
114
132
|
[Groups](https://en.wikipedia.org/wiki/Group_\(mathematics\)) and
|
@@ -224,6 +242,7 @@ required.
|
|
224
242
|
* For any given x-value on the curve, the y-value is easily generated
|
225
243
|
* For most curves, there are two different y-values for an x-value
|
226
244
|
* We are always dealing with 32-byte integers: **Bignums**
|
245
|
+
* Bignum math can be expensive
|
227
246
|
* Converting between integer format and 32-byte strings can be expensive
|
228
247
|
* The Schnorr algorithm requires lots of `string <--> integer` conversion
|
229
248
|
* Hex strings are never used internally
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0.1
|
data/lib/schnorr_sig/fast.rb
CHANGED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'schnorr_sig/util' # project
|
2
|
+
require 'ecdsa_ext' # gem
|
3
|
+
autoload :SecureRandom, 'securerandom' # stdlib
|
4
|
+
|
5
|
+
# This implementation is based on the BIP340 spec: https://bips.xyz/340
|
6
|
+
# re-open SchnorrSig to add more functions, errors, and constants
|
7
|
+
module SchnorrSig
|
8
|
+
class Error < RuntimeError; end
|
9
|
+
class BoundsError < Error; end
|
10
|
+
class SanityCheck < Error; end
|
11
|
+
class VerifyFail < Error; end
|
12
|
+
class InfinityPoint < Error; end
|
13
|
+
|
14
|
+
GROUP = ECDSA::Group::Secp256k1
|
15
|
+
P = GROUP.field.prime # smaller than 256**32
|
16
|
+
N = GROUP.order # smaller than P
|
17
|
+
B = GROUP.byte_length # 32
|
18
|
+
|
19
|
+
# val (dot) G, returns ECDSA::Point
|
20
|
+
def self.dot_group(val)
|
21
|
+
# ecdsa_ext uses jacobian projection: 10x faster than GROUP.generator * val
|
22
|
+
(GROUP.generator.to_jacobian * val).to_affine
|
23
|
+
end
|
24
|
+
|
25
|
+
# returns even_val or N - even_val
|
26
|
+
def self.select_even_y(point, even_val)
|
27
|
+
point.y.even? ? even_val : N - even_val
|
28
|
+
end
|
29
|
+
|
30
|
+
# int(x) function signature matches BIP340, returns a bignum (presumably)
|
31
|
+
class << self
|
32
|
+
alias_method :int, :bin2big
|
33
|
+
end
|
34
|
+
|
35
|
+
# bytes(val) function signature matches BIP340, returns a binary string
|
36
|
+
def self.bytes(val)
|
37
|
+
case val
|
38
|
+
when Integer
|
39
|
+
# BIP340: The function bytes(x), where x is an integer,
|
40
|
+
# returns the 32-byte encoding of x, most significant byte first.
|
41
|
+
big2bin(val)
|
42
|
+
when ECDSA::Point
|
43
|
+
# BIP340: The function bytes(P), where P is a point, returns bytes(x(P)).
|
44
|
+
val.infinity? ? raise(InfinityPoint, va.inspect) : big2bin(val.x)
|
45
|
+
else
|
46
|
+
raise(SanityCheck, val.inspect)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Input
|
51
|
+
# The secret key, sk: 32 bytes binary
|
52
|
+
# The message, m: binary / UTF-8 / agnostic
|
53
|
+
# Auxiliary random data, a: 32 bytes binary
|
54
|
+
# Output
|
55
|
+
# The signature, sig: 64 bytes binary
|
56
|
+
def self.sign(sk, m, a = Random.bytes(B))
|
57
|
+
bytestring!(sk, B) and string!(m) and bytestring!(a, B)
|
58
|
+
|
59
|
+
# BIP340: Let d' = int(sk)
|
60
|
+
# BIP340: Fail if d' = 0 or d' >= n
|
61
|
+
d0 = int(sk)
|
62
|
+
raise(BoundsError, "d0") if !d0.positive? or d0 >= N
|
63
|
+
|
64
|
+
# BIP340: Let P = d' . G
|
65
|
+
p = dot_group(d0) # this is a point on the elliptic curve
|
66
|
+
bytes_p = bytes(p)
|
67
|
+
|
68
|
+
# BIP340: Let d = d' if has_even_y(P), otherwise let d = n - d'
|
69
|
+
d = select_even_y(p, d0)
|
70
|
+
|
71
|
+
# BIP340: Let t be the bytewise xor of bytes(d) and hash[BIP0340/aux](a)
|
72
|
+
t = d ^ int(tagged_hash('BIP0340/aux', a))
|
73
|
+
|
74
|
+
# BIP340: Let rand = hash[BIP0340/nonce](t || bytes(P) || m)
|
75
|
+
nonce = tagged_hash('BIP0340/nonce', bytes(t) + bytes_p + m)
|
76
|
+
|
77
|
+
# BIP340: Let k' = int(rand) mod n
|
78
|
+
# BIP340: Fail if k' = 0
|
79
|
+
k0 = int(nonce) % N
|
80
|
+
raise(BoundsError, "k0") if !k0.positive?
|
81
|
+
|
82
|
+
# BIP340: Let R = k' . G
|
83
|
+
r = dot_group(k0) # this is a point on the elliptic curve
|
84
|
+
bytes_r = bytes(r)
|
85
|
+
|
86
|
+
# BIP340: Let k = k' if has_even_y(R), otherwise let k = n - k'
|
87
|
+
k = select_even_y(r, k0)
|
88
|
+
|
89
|
+
# BIP340:
|
90
|
+
# Let e = int(hash[BIP0340/challenge](bytes(R) || bytes(P) || m)) mod n
|
91
|
+
e = int(tagged_hash('BIP0340/challenge', bytes_r + bytes_p + m)) % N
|
92
|
+
|
93
|
+
# BIP340: Let sig = bytes(R) || bytes((k + ed) mod n)
|
94
|
+
# BIP340: Fail unless Verify(bytes(P), m, sig)
|
95
|
+
# BIP340: Return the signature sig
|
96
|
+
sig = bytes_r + bytes((k + e * d) % N)
|
97
|
+
raise(VerifyFail) unless verify?(bytes_p, m, sig)
|
98
|
+
sig
|
99
|
+
end
|
100
|
+
|
101
|
+
# see https://bips.xyz/340#design (Tagged hashes)
|
102
|
+
# Input
|
103
|
+
# A tag: UTF-8 > binary > agnostic
|
104
|
+
# The payload, msg: UTF-8 / binary / agnostic
|
105
|
+
# Output
|
106
|
+
# 32 bytes binary
|
107
|
+
def self.tagged_hash(tag, msg)
|
108
|
+
string!(tag) and string!(msg)
|
109
|
+
warn("tag expected to be UTF-8") unless tag.encoding == Encoding::UTF_8
|
110
|
+
|
111
|
+
# BIP340: The function hash[name](x) where x is a byte array
|
112
|
+
# returns the 32-byte hash
|
113
|
+
# SHA256(SHA256(tag) || SHA256(tag) || x)
|
114
|
+
# where tag is the UTF-8 encoding of name.
|
115
|
+
tag_hash = Digest::SHA256.digest(tag)
|
116
|
+
Digest::SHA256.digest(tag_hash + tag_hash + msg)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Input
|
120
|
+
# The public key, pk: 32 bytes binary
|
121
|
+
# The message, m: UTF-8 / binary / agnostic
|
122
|
+
# A signature, sig: 64 bytes binary
|
123
|
+
# Output
|
124
|
+
# Boolean
|
125
|
+
def self.verify?(pk, m, sig)
|
126
|
+
bytestring!(pk, B) and string!(m) and bytestring!(sig, B * 2)
|
127
|
+
|
128
|
+
# BIP340: Let P = lift_x(int(pk))
|
129
|
+
p = lift_x(int(pk))
|
130
|
+
|
131
|
+
# BIP340: Let r = int(sig[0:32]) fail if r >= p
|
132
|
+
r = int(sig[0..B-1])
|
133
|
+
raise(BoundsError, "r >= p") if r >= P
|
134
|
+
|
135
|
+
# BIP340: Let s = int(sig[32:64]); fail if s >= n
|
136
|
+
s = int(sig[B..-1])
|
137
|
+
raise(BoundsError, "s >= n") if s >= N
|
138
|
+
|
139
|
+
# BIP340:
|
140
|
+
# Let e = int(hash[BIP0340/challenge](bytes(r) || bytes(P) || m)) mod n
|
141
|
+
e = bytes(r) + bytes(p) + m
|
142
|
+
e = int(tagged_hash('BIP0340/challenge', e)) % N
|
143
|
+
|
144
|
+
# BIP340: Let R = s . G - e . P
|
145
|
+
# BIP340: Fail if is_infinite(R)
|
146
|
+
# BIP340: Fail if not has_even_y(R)
|
147
|
+
# BIP340: Fail if x(R) != r
|
148
|
+
# BIP340: Return success iff no failure occurred before reaching this point
|
149
|
+
big_r = dot_group(s) + p.multiply_by_scalar(e).negate
|
150
|
+
!big_r.infinity? and big_r.y.even? and big_r.x == r
|
151
|
+
end
|
152
|
+
|
153
|
+
# BIP340: The function lift_x(x), where x is a 256-bit unsigned integer,
|
154
|
+
# returns the point P for which x(P) = x and has_even_y(P),
|
155
|
+
# or fails if x is greater than p-1 or no such point exists.
|
156
|
+
# Input
|
157
|
+
# A large integer, x
|
158
|
+
# Output
|
159
|
+
# ECDSA::Point
|
160
|
+
def self.lift_x(x)
|
161
|
+
integer!(x)
|
162
|
+
|
163
|
+
# BIP340: Fail if x >= p
|
164
|
+
raise(BoundsError, "x") if x >= P or x <= 0
|
165
|
+
|
166
|
+
# BIP340: Let c = x^3 + 7 mod p
|
167
|
+
c = (x.pow(3, P) + 7) % P
|
168
|
+
|
169
|
+
# BIP340: Let y = c ^ ((p + 1) / 4) mod p
|
170
|
+
y = c.pow((P + 1) / 4, P) # use pow to avoid Bignum overflow
|
171
|
+
|
172
|
+
# BIP340: Fail if c != y^2 mod p
|
173
|
+
raise(SanityCheck, "c != y^2 mod p") if c != y.pow(2, P)
|
174
|
+
|
175
|
+
# BIP340: Return the unique point P such that:
|
176
|
+
# x(P) = x and y(P) = y if y mod 2 = 0
|
177
|
+
# y(P) = p - y otherwise
|
178
|
+
GROUP.new_point [x, y.even? ? y : P - y]
|
179
|
+
end
|
180
|
+
|
181
|
+
# Input
|
182
|
+
# The secret key, sk: 32 bytes binary
|
183
|
+
# Output
|
184
|
+
# 32 bytes binary (represents P.x for point P on the curve)
|
185
|
+
def self.pubkey(sk)
|
186
|
+
bytestring!(sk, B)
|
187
|
+
|
188
|
+
# BIP340: Let d' = int(sk)
|
189
|
+
# BIP340: Fail if d' = 0 or d' >= n
|
190
|
+
# BIP340: Return bytes(d' . G)
|
191
|
+
d0 = int(sk)
|
192
|
+
raise(BoundsError, "d0") if !d0.positive? or d0 >= N
|
193
|
+
bytes(dot_group(d0))
|
194
|
+
end
|
195
|
+
|
196
|
+
# generate a new keypair based on random data
|
197
|
+
def self.keypair
|
198
|
+
sk = Random.bytes(B)
|
199
|
+
[sk, pubkey(sk)]
|
200
|
+
end
|
201
|
+
|
202
|
+
# as above, but using SecureRandom
|
203
|
+
def self.secure_keypair
|
204
|
+
sk = SecureRandom.bytes(B)
|
205
|
+
[sk, pubkey(sk)]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if __FILE__ == $0
|
210
|
+
msg = 'hello world'
|
211
|
+
sk, pk = SchnorrSig.keypair
|
212
|
+
puts "Message: #{msg}"
|
213
|
+
puts "Secret key: #{SchnorrSig.bin2hex(sk)}"
|
214
|
+
puts "Public key: #{SchnorrSig.bin2hex(pk)}"
|
215
|
+
|
216
|
+
sig = SchnorrSig.sign(sk, msg)
|
217
|
+
puts
|
218
|
+
puts "Verified signature: #{SchnorrSig.bin2hex(sig)}"
|
219
|
+
puts "Encoding: #{sig.encoding}"
|
220
|
+
puts "Length: #{sig.length}"
|
221
|
+
end
|
data/lib/schnorr_sig/util.rb
CHANGED
@@ -18,7 +18,7 @@ module SchnorrSig
|
|
18
18
|
def self.bytestring!(str, size)
|
19
19
|
string!(str)
|
20
20
|
raise(EncodingError, str.encoding) unless str.encoding == Encoding::BINARY
|
21
|
-
str.
|
21
|
+
str.bytesize == size or raise(SizeError, str.bytesize)
|
22
22
|
end
|
23
23
|
|
24
24
|
# likely returns a Bignum, larger than a 64-bit hardware integer
|
data/lib/schnorr_sig.rb
CHANGED
@@ -1,220 +1,5 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
# This implementation is based on the BIP340 spec: https://bips.xyz/340
|
6
|
-
# re-open SchnorrSig to add more functions, errors, and constants
|
7
|
-
module SchnorrSig
|
8
|
-
class Error < RuntimeError; end
|
9
|
-
class BoundsError < Error; end
|
10
|
-
class SanityCheck < Error; end
|
11
|
-
class VerifyFail < Error; end
|
12
|
-
|
13
|
-
GROUP = ECDSA::Group::Secp256k1
|
14
|
-
P = GROUP.field.prime # smaller than 256**32
|
15
|
-
N = GROUP.order # smaller than P
|
16
|
-
B = GROUP.byte_length # 32
|
17
|
-
|
18
|
-
# val (dot) G, returns ECDSA::Point
|
19
|
-
def self.dot_group(val)
|
20
|
-
# ecdsa_ext uses jacobian projection: 10x faster than GROUP.generator * val
|
21
|
-
(GROUP.generator.to_jacobian * val).to_affine
|
22
|
-
end
|
23
|
-
|
24
|
-
# returns even_val or N - even_val
|
25
|
-
def self.select_even_y(point, even_val)
|
26
|
-
point.y.even? ? even_val : N - even_val
|
27
|
-
end
|
28
|
-
|
29
|
-
# int(x) function signature matches BIP340, returns a bignum (presumably)
|
30
|
-
class << self
|
31
|
-
alias_method :int, :bin2big
|
32
|
-
end
|
33
|
-
|
34
|
-
# bytes(val) function signature matches BIP340, returns a binary string
|
35
|
-
def self.bytes(val)
|
36
|
-
case val
|
37
|
-
when Integer
|
38
|
-
# BIP340: The function bytes(x), where x is an integer,
|
39
|
-
# returns the 32-byte encoding of x, most significant byte first.
|
40
|
-
big2bin(val)
|
41
|
-
when ECDSA::Point
|
42
|
-
# BIP340: The function bytes(P), where P is a point, returns bytes(x(P)).
|
43
|
-
val.infinity? ? ("\x00" * B).b : big2bin(val.x)
|
44
|
-
else
|
45
|
-
raise(SanityCheck, val.inspect)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Input
|
50
|
-
# The secret key, sk: 32 bytes binary
|
51
|
-
# The message, m: binary / UTF-8 / agnostic
|
52
|
-
# Auxiliary random data, a: 32 bytes binary
|
53
|
-
# Output
|
54
|
-
# The signature, sig: 64 bytes binary
|
55
|
-
def self.sign(sk, m, a = Random.bytes(B))
|
56
|
-
bytestring!(sk, B) and string!(m) and bytestring!(a, B)
|
57
|
-
|
58
|
-
# BIP340: Let d' = int(sk)
|
59
|
-
# BIP340: Fail if d' = 0 or d' >= n
|
60
|
-
d0 = int(sk)
|
61
|
-
raise(BoundsError, "d0") if !d0.positive? or d0 >= N
|
62
|
-
|
63
|
-
# BIP340: Let P = d' . G
|
64
|
-
p = dot_group(d0) # this is a point on the elliptic curve
|
65
|
-
bytes_p = bytes(p)
|
66
|
-
|
67
|
-
# BIP340: Let d = d' if has_even_y(P), otherwise let d = n - d'
|
68
|
-
d = select_even_y(p, d0)
|
69
|
-
|
70
|
-
# BIP340: Let t be the bytewise xor of bytes(d) and hash[BIP0340/aux](a)
|
71
|
-
t = d ^ int(tagged_hash('BIP0340/aux', a))
|
72
|
-
|
73
|
-
# BIP340: Let rand = hash[BIP0340/nonce](t || bytes(P) || m)
|
74
|
-
nonce = tagged_hash('BIP0340/nonce', bytes(t) + bytes_p + m)
|
75
|
-
|
76
|
-
# BIP340: Let k' = int(rand) mod n
|
77
|
-
# BIP340: Fail if k' = 0
|
78
|
-
k0 = int(nonce) % N
|
79
|
-
raise(BoundsError, "k0") if !k0.positive?
|
80
|
-
|
81
|
-
# BIP340: Let R = k' . G
|
82
|
-
r = dot_group(k0) # this is a point on the elliptic curve
|
83
|
-
bytes_r = bytes(r)
|
84
|
-
|
85
|
-
# BIP340: Let k = k' if has_even_y(R), otherwise let k = n - k'
|
86
|
-
k = select_even_y(r, k0)
|
87
|
-
|
88
|
-
# BIP340:
|
89
|
-
# Let e = int(hash[BIP0340/challenge](bytes(R) || bytes(P) || m)) mod n
|
90
|
-
e = int(tagged_hash('BIP0340/challenge', bytes_r + bytes_p + m)) % N
|
91
|
-
|
92
|
-
# BIP340: Let sig = bytes(R) || bytes((k + ed) mod n)
|
93
|
-
# BIP340: Fail unless Verify(bytes(P), m, sig)
|
94
|
-
# BIP340: Return the signature sig
|
95
|
-
sig = bytes_r + bytes((k + e * d) % N)
|
96
|
-
raise(VerifyFail) unless verify?(bytes_p, m, sig)
|
97
|
-
sig
|
98
|
-
end
|
99
|
-
|
100
|
-
# see https://bips.xyz/340#design (Tagged hashes)
|
101
|
-
# Input
|
102
|
-
# A tag: UTF-8 > binary > agnostic
|
103
|
-
# The payload, msg: UTF-8 / binary / agnostic
|
104
|
-
# Output
|
105
|
-
# 32 bytes binary
|
106
|
-
def self.tagged_hash(tag, msg)
|
107
|
-
string!(tag) and string!(msg)
|
108
|
-
warn("tag expected to be UTF-8") unless tag.encoding == Encoding::UTF_8
|
109
|
-
|
110
|
-
# BIP340: The function hash[name](x) where x is a byte array
|
111
|
-
# returns the 32-byte hash
|
112
|
-
# SHA256(SHA256(tag) || SHA256(tag) || x)
|
113
|
-
# where tag is the UTF-8 encoding of name.
|
114
|
-
tag_hash = Digest::SHA256.digest(tag)
|
115
|
-
Digest::SHA256.digest(tag_hash + tag_hash + msg)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Input
|
119
|
-
# The public key, pk: 32 bytes binary
|
120
|
-
# The message, m: UTF-8 / binary / agnostic
|
121
|
-
# A signature, sig: 64 bytes binary
|
122
|
-
# Output
|
123
|
-
# Boolean
|
124
|
-
def self.verify?(pk, m, sig)
|
125
|
-
bytestring!(pk, B) and string!(m) and bytestring!(sig, B * 2)
|
126
|
-
|
127
|
-
# BIP340: Let P = lift_x(int(pk))
|
128
|
-
p = lift_x(int(pk))
|
129
|
-
|
130
|
-
# BIP340: Let r = int(sig[0:32]) fail if r >= p
|
131
|
-
r = int(sig[0..B-1])
|
132
|
-
raise(BoundsError, "r >= p") if r >= P
|
133
|
-
|
134
|
-
# BIP340: Let s = int(sig[32:64]); fail if s >= n
|
135
|
-
s = int(sig[B..-1])
|
136
|
-
raise(BoundsError, "s >= n") if s >= N
|
137
|
-
|
138
|
-
# BIP340:
|
139
|
-
# Let e = int(hash[BIP0340/challenge](bytes(r) || bytes(P) || m)) mod n
|
140
|
-
e = bytes(r) + bytes(p) + m
|
141
|
-
e = int(tagged_hash('BIP0340/challenge', e)) % N
|
142
|
-
|
143
|
-
# BIP340: Let R = s . G - e . P
|
144
|
-
# BIP340: Fail if is_infinite(R)
|
145
|
-
# BIP340: Fail if not has_even_y(R)
|
146
|
-
# BIP340: Fail if x(R) != r
|
147
|
-
# BIP340: Return success iff no failure occurred before reaching this point
|
148
|
-
big_r = dot_group(s) + p.multiply_by_scalar(e).negate
|
149
|
-
!big_r.infinity? and big_r.y.even? and big_r.x == r
|
150
|
-
end
|
151
|
-
|
152
|
-
# BIP340: The function lift_x(x), where x is a 256-bit unsigned integer,
|
153
|
-
# returns the point P for which x(P) = x[10] and has_even_y(P),
|
154
|
-
# or fails if x is greater than p-1 or no such point exists.
|
155
|
-
# Input
|
156
|
-
# A large integer, x
|
157
|
-
# Output
|
158
|
-
# ECDSA::Point
|
159
|
-
def self.lift_x(x)
|
160
|
-
integer!(x)
|
161
|
-
|
162
|
-
# BIP340: Fail if x >= p
|
163
|
-
raise(BoundsError, "x") if x >= P or x <= 0
|
164
|
-
|
165
|
-
# BIP340: Let c = x^3 + 7 mod p
|
166
|
-
c = (x.pow(3, P) + 7) % P
|
167
|
-
|
168
|
-
# BIP340: Let y = c ^ ((p + 1) / 4) mod p
|
169
|
-
y = c.pow((P + 1) / 4, P) # use pow to avoid Bignum overflow
|
170
|
-
|
171
|
-
# BIP340: Fail if c != y^2 mod p
|
172
|
-
raise(SanityCheck, "c != y^2 mod p") if c != y.pow(2, P)
|
173
|
-
|
174
|
-
# BIP340: Return the unique point P such that:
|
175
|
-
# x(P) = x and y(P) = y if y mod 2 = 0
|
176
|
-
# y(P) = p - y otherwise
|
177
|
-
GROUP.new_point [x, y.even? ? y : P - y]
|
178
|
-
end
|
179
|
-
|
180
|
-
# Input
|
181
|
-
# The secret key, sk: 32 bytes binary
|
182
|
-
# Output
|
183
|
-
# 32 bytes binary (represents P.x for point P on the curve)
|
184
|
-
def self.pubkey(sk)
|
185
|
-
bytestring!(sk, B)
|
186
|
-
|
187
|
-
# BIP340: Let d' = int(sk)
|
188
|
-
# BIP340: Fail if d' = 0 or d' >= n
|
189
|
-
# BIP340: Return bytes(d' . G)
|
190
|
-
d0 = int(sk)
|
191
|
-
raise(BoundsError, "d0") if !d0.positive? or d0 >= N
|
192
|
-
bytes(dot_group(d0))
|
193
|
-
end
|
194
|
-
|
195
|
-
# generate a new keypair based on random data
|
196
|
-
def self.keypair
|
197
|
-
sk = Random.bytes(B)
|
198
|
-
[sk, pubkey(sk)]
|
199
|
-
end
|
200
|
-
|
201
|
-
# as above, but using SecureRandom
|
202
|
-
def self.secure_keypair
|
203
|
-
sk = SecureRandom.bytes(B)
|
204
|
-
[sk, pubkey(sk)]
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
if __FILE__ == $0
|
209
|
-
msg = 'hello world'
|
210
|
-
sk, pk = SchnorrSig.keypair
|
211
|
-
puts "Message: #{msg}"
|
212
|
-
puts "Secret key: #{SchnorrSig.bin2hex(sk)}"
|
213
|
-
puts "Public key: #{SchnorrSig.bin2hex(pk)}"
|
214
|
-
|
215
|
-
sig = SchnorrSig.sign(sk, msg)
|
216
|
-
puts
|
217
|
-
puts "Verified signature: #{SchnorrSig.bin2hex(sig)}"
|
218
|
-
puts "Encoding: #{sig.encoding}"
|
219
|
-
puts "Length: #{sig.length}"
|
1
|
+
if ENV['SCHNORR_SIG']&.downcase == 'fast'
|
2
|
+
require 'schnorr_sig/fast'
|
3
|
+
else
|
4
|
+
require 'schnorr_sig/pure'
|
220
5
|
end
|
data/test/vectors.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require
|
2
|
-
'schnorr_sig/fast' : 'schnorr_sig'
|
1
|
+
require 'schnorr_sig'
|
3
2
|
require 'csv'
|
4
3
|
|
5
4
|
path = File.join(__dir__, 'vectors.csv')
|
@@ -29,4 +28,4 @@ puts "Failure: #{failure.count}"
|
|
29
28
|
|
30
29
|
puts failure unless failure.empty?
|
31
30
|
|
32
|
-
exit failure.count
|
31
|
+
# exit failure.count
|
data/test/vectors_extra.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schnorr_sig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- VERSION
|
36
36
|
- lib/schnorr_sig.rb
|
37
37
|
- lib/schnorr_sig/fast.rb
|
38
|
+
- lib/schnorr_sig/pure.rb
|
38
39
|
- lib/schnorr_sig/util.rb
|
39
40
|
- schnorr_sig.gemspec
|
40
41
|
- test/vectors.rb
|