schnorr_sig 0.0.0.4 → 0.2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|