Module: SchnorrSig
- Defined in:
- lib/schnorr_sig/fast.rb,
lib/schnorr_sig/pure.rb,
lib/schnorr_sig/util.rb
Overview
This implementation is based on the BIP340 spec: bips.xyz/340 re-open SchnorrSig to add more functions, errors, and constants
Defined Under Namespace
Classes: BoundsError, EncodingError, Error, InfinityPoint, InputError, SanityCheck, SizeError, TypeError, VerifyFail
Constant Summary collapse
- CONTEXT =
Secp256k1::Context.create
- FORCE_32_BYTE_MSG =
true- GROUP =
ECDSA::Group::Secp256k1
- P =
smaller than 256**32
GROUP.field.prime
- N =
smaller than P
GROUP.order
- B =
32
GROUP.byte_length
Class Method Summary collapse
-
.big2bin(bignum) ⇒ Object
convert a giant integer to a binary string.
-
.bin2big(str) ⇒ Object
likely returns a Bignum, larger than a 64-bit hardware integer.
-
.bin2hex(str) ⇒ Object
convert a binary string to a lowercase hex string.
-
.bytes(val) ⇒ Object
bytes(val) function signature matches BIP340, returns a binary string.
-
.bytestring!(str, size) ⇒ Object
true or raise.
-
.dot_group(val) ⇒ Object
val (dot) G, returns ECDSA::Point.
-
.hex2bin(hex) ⇒ Object
convert a hex string to a binary string.
- .int ⇒ Object
-
.integer!(i) ⇒ Object
true or raise.
-
.key_pair(sk = nil) ⇒ Object
Input (The secret key, sk: 32 bytes binary) Output Secp256k1::KeyPair.
-
.keypair ⇒ Object
generate a new keypair based on random data.
-
.lift_x(x) ⇒ Object
BIP340: The function lift_x(x), where x is a 256-bit unsigned integer, returns the point P for which x(P) = x and has_even_y(P), or fails if x is greater than p-1 or no such point exists.
-
.pubkey(sk) ⇒ Object
Input The secret key, sk: 32 bytes binary Output 32 bytes binary (represents P.x for point P on the curve).
-
.secure_keypair ⇒ Object
as above, but using SecureRandom.
-
.select_even_y(point, even_val) ⇒ Object
returns even_val or N - even_val.
-
.sign(sk, m, a = Random.bytes(B)) ⇒ Object
Input The secret key, sk: 32 bytes binary The message, m: binary / UTF-8 / agnostic Auxiliary random data, a: 32 bytes binary Output The signature, sig: 64 bytes binary.
-
.signature(str) ⇒ Object
Input The signature, str: 64 bytes binary Output Secp256k1::SchnorrSignature.
-
.string!(str) ⇒ Object
true or raise.
-
.tagged_hash(tag, msg) ⇒ Object
see bips.xyz/340#design (Tagged hashes) Input A tag: UTF-8 > binary > agnostic The payload, msg: UTF-8 / binary / agnostic Output 32 bytes binary.
-
.verify?(pk, m, sig) ⇒ Boolean
Input The public key, pk: 32 bytes binary The message, m: UTF-8 / binary / agnostic A signature, sig: 64 bytes binary Output Boolean.
Class Method Details
.big2bin(bignum) ⇒ Object
convert a giant integer to a binary string
30 31 32 33 |
# File 'lib/schnorr_sig/util.rb', line 30 def self.big2bin(bignum) # much faster than ECDSA::Format -- thanks ParadoxV5 hex2bin(bignum.to_s(16).rjust(B * 2, '0')) end |
.bin2big(str) ⇒ Object
likely returns a Bignum, larger than a 64-bit hardware integer
25 26 27 |
# File 'lib/schnorr_sig/util.rb', line 25 def self.bin2big(str) bin2hex(str).to_i(16) end |
.bin2hex(str) ⇒ Object
convert a binary string to a lowercase hex string
36 37 38 |
# File 'lib/schnorr_sig/util.rb', line 36 def self.bin2hex(str) str.unpack1('H*') end |
.bytes(val) ⇒ Object
bytes(val) function signature matches BIP340, returns a binary string
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/schnorr_sig/pure.rb', line 36 def self.bytes(val) case val when Integer # BIP340: The function bytes(x), where x is an integer, # returns the 32-byte encoding of x, most significant byte first. big2bin(val) when ECDSA::Point # BIP340: The function bytes(P), where P is a point, returns bytes(x(P)). val.infinity? ? raise(InfinityPoint, va.inspect) : big2bin(val.x) else raise(SanityCheck, val.inspect) end end |
.bytestring!(str, size) ⇒ Object
true or raise
18 19 20 21 22 |
# File 'lib/schnorr_sig/util.rb', line 18 def self.bytestring!(str, size) string!(str) raise(EncodingError, str.encoding) unless str.encoding == Encoding::BINARY str.bytesize == size or raise(SizeError, str.bytesize) end |
.dot_group(val) ⇒ Object
val (dot) G, returns ECDSA::Point
20 21 22 23 |
# File 'lib/schnorr_sig/pure.rb', line 20 def self.dot_group(val) # ecdsa_ext uses jacobian projection: 10x faster than GROUP.generator * val (GROUP.generator.to_jacobian * val).to_affine end |
.hex2bin(hex) ⇒ Object
convert a hex string to a binary string
41 42 43 |
# File 'lib/schnorr_sig/util.rb', line 41 def self.hex2bin(hex) [hex].pack('H*') end |
.int ⇒ Object
32 |
# File 'lib/schnorr_sig/pure.rb', line 32 alias_method :int, :bin2big |
.integer!(i) ⇒ Object
true or raise
8 9 10 |
# File 'lib/schnorr_sig/util.rb', line 8 def self.integer!(i) i.is_a?(Integer) or raise(TypeError, i.class) end |
.key_pair(sk = nil) ⇒ Object
Input
(The secret key, sk: 32 bytes binary)
Output
Secp256k1::KeyPair
36 37 38 39 40 41 42 43 |
# File 'lib/schnorr_sig/fast.rb', line 36 def self.key_pair(sk = nil) if sk bytestring!(sk, 32) CONTEXT.key_pair_from_private_key(sk) else CONTEXT.generate_key_pair end end |
.keypair ⇒ Object
generate a new keypair based on random data
49 50 51 52 |
# File 'lib/schnorr_sig/fast.rb', line 49 def self.keypair(sk = nil) kp = self.key_pair(sk) [kp.private_key.data, kp.xonly_public_key.serialized] end |
.lift_x(x) ⇒ Object
BIP340: The function lift_x(x), where x is a 256-bit unsigned integer,
returns the point P for which x(P) = x and has_even_y(P),
or fails if x is greater than p-1 or no such point exists.
Input
A large integer, x
Output
ECDSA::Point
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/schnorr_sig/pure.rb', line 160 def self.lift_x(x) integer!(x) # BIP340: Fail if x >= p raise(BoundsError, "x") if x >= P or x <= 0 # BIP340: Let c = x^3 + 7 mod p c = (x.pow(3, P) + 7) % P # BIP340: Let y = c ^ ((p + 1) / 4) mod p y = c.pow((P + 1) / 4, P) # use pow to avoid Bignum overflow # BIP340: Fail if c != y^2 mod p raise(SanityCheck, "c != y^2 mod p") if c != y.pow(2, P) # BIP340: Return the unique point P such that: # x(P) = x and y(P) = y if y mod 2 = 0 # y(P) = p - y otherwise GROUP.new_point [x, y.even? ? y : P - y] end |
.pubkey(sk) ⇒ Object
Input
The secret key, sk: 32 bytes binary
Output
32 bytes binary (represents P.x for point P on the curve)
58 59 60 |
# File 'lib/schnorr_sig/fast.rb', line 58 def self.pubkey(sk) keypair(sk)[1] end |
.secure_keypair ⇒ Object
as above, but using SecureRandom
203 204 205 206 |
# File 'lib/schnorr_sig/pure.rb', line 203 def self.secure_keypair sk = SecureRandom.bytes(B) [sk, pubkey(sk)] end |
.select_even_y(point, even_val) ⇒ Object
returns even_val or N - even_val
26 27 28 |
# File 'lib/schnorr_sig/pure.rb', line 26 def self.select_even_y(point, even_val) point.y.even? ? even_val : N - even_val end |
.sign(sk, m, a = Random.bytes(B)) ⇒ Object
Input
The secret key, sk: 32 bytes binary
The message, m: binary / UTF-8 / agnostic
Auxiliary random data, a: 32 bytes binary
Output
The signature, sig: 64 bytes binary
15 16 17 18 19 |
# File 'lib/schnorr_sig/fast.rb', line 15 def self.sign(sk, m) bytestring!(sk, 32) and string!(m) m = m[0..31].ljust(32, ' ') if FORCE_32_BYTE_MSG CONTEXT.sign_schnorr(key_pair(sk), m).serialized end |
.signature(str) ⇒ Object
Input
The signature, str: 64 bytes binary
Output
Secp256k1::SchnorrSignature
66 67 68 69 |
# File 'lib/schnorr_sig/fast.rb', line 66 def self.signature(str) bytestring!(str, 64) Secp256k1::SchnorrSignature.from_data(str) end |
.string!(str) ⇒ Object
true or raise
13 14 15 |
# File 'lib/schnorr_sig/util.rb', line 13 def self.string!(str) str.is_a?(String) or raise(TypeError, str.class) end |
.tagged_hash(tag, msg) ⇒ Object
see bips.xyz/340#design (Tagged hashes) Input
A tag: UTF-8 > binary > agnostic
The payload, msg: UTF-8 / binary / agnostic
Output
32 bytes binary
107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/schnorr_sig/pure.rb', line 107 def self.tagged_hash(tag, msg) string!(tag) and string!(msg) warn("tag expected to be UTF-8") unless tag.encoding == Encoding::UTF_8 # BIP340: The function hash[name](x) where x is a byte array # returns the 32-byte hash # SHA256(SHA256(tag) || SHA256(tag) || x) # where tag is the UTF-8 encoding of name. tag_hash = Digest::SHA256.digest(tag) Digest::SHA256.digest(tag_hash + tag_hash + msg) end |
.verify?(pk, m, sig) ⇒ Boolean
Input
The public key, pk: 32 bytes binary
The message, m: UTF-8 / binary / agnostic
A signature, sig: 64 bytes binary
Output
Boolean
27 28 29 30 |
# File 'lib/schnorr_sig/fast.rb', line 27 def self.verify?(pk, m, sig) bytestring!(pk, 32) and string!(m) and bytestring!(sig, 64) signature(sig).verify(m, Secp256k1::XOnlyPublicKey.from_data(pk)) end |