Module: Schnorr

Defined in:
lib/schnorr.rb,
lib/schnorr/version.rb,
lib/schnorr/signature.rb

Defined Under Namespace

Classes: InvalidSignatureError, Signature

Constant Summary collapse

GROUP =
ECDSA::Group::Secp256k1
VERSION =
"0.4.0"

Class Method Summary collapse

Class Method Details

.check_sig!(message, public_key, signature) ⇒ Boolean

Verifies the given Signature and raises an InvalidSignatureError if it is invalid.

Parameters:

  • message (String)

    A message to be signed with binary format.

  • public_key (String)

    The public key with binary format.

  • signature (String)

    The signature with binary format.

Returns:

  • (Boolean)

Raises:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/schnorr.rb', line 59

def check_sig!(message, public_key, signature)
  raise InvalidSignatureError, 'The message must be a 32-byte array.' unless message.bytesize == 32
  raise InvalidSignatureError, 'The public key must be a 32-byte array.' unless public_key.bytesize == 32

  sig = Schnorr::Signature.decode(signature)
  pubkey = ECDSA::Format::PointOctetString.decode_from_x(public_key, GROUP)
  field = GROUP.field

  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is zero.' if sig.r.zero?
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is zero.' if sig.s.zero?
  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is larger than field size.' if sig.r >= field.prime
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is larger than group order.' if sig.s >= GROUP.order

  e = create_challenge(sig.r, pubkey, message)

  r = GROUP.new_point(sig.s) + pubkey.multiply_by_scalar(GROUP.order - e)

  if r.infinity? || !r.has_even_y? || r.x != sig.r
    raise Schnorr::InvalidSignatureError, 'signature verification failed.'
  end

  true
end

.create_challenge(x, p, message) ⇒ Integer

create signature digest.

Parameters:

Returns:



87
88
89
90
# File 'lib/schnorr.rb', line 87

def create_challenge(x, p, message)
  r_x = ECDSA::Format::IntegerOctetString.encode(x, GROUP.byte_length)
  (ECDSA.normalize_digest(tagged_hash('BIP0340/challenge', r_x + p.encode(true) + message), GROUP.bit_length)) % GROUP.order
end

.sign(message, private_key, aux_rand = nil) ⇒ Schnorr::Signature

Generate schnorr signature. If not specified, random data is not used and the private key is used to calculate the nonce.

Parameters:

  • message (String)

    A message to be signed with binary format.

  • private_key (String)

    The private key with binary format.

  • aux_rand (String) (defaults to: nil)

    The auxiliary random data with binary format.

Returns:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/schnorr.rb', line 17

def sign(message, private_key, aux_rand = nil)
  raise 'The message must be a 32-byte array.' unless message.bytesize == 32

  d0 = private_key.unpack1('H*').to_i(16)
  raise 'private_key must be an integer in the range 1..n-1.' unless 0 < d0 && d0 <= (GROUP.order - 1)
  raise 'aux_rand must be 32 bytes.' if !aux_rand.nil? && aux_rand.bytesize != 32

  p = GROUP.new_point(d0)
  d = p.has_even_y? ? d0 : GROUP.order - d0

  t = aux_rand.nil? ? d : d ^ tagged_hash('BIP0340/aux', aux_rand).unpack1('H*').to_i(16)
  t = ECDSA::Format::IntegerOctetString.encode(t, GROUP.byte_length)

  k0 = ECDSA::Format::IntegerOctetString.decode(tagged_hash('BIP0340/nonce', t + p.encode(true) + message)) % GROUP.order
  raise 'Creation of signature failed. k is zero' if k0.zero?

  r = GROUP.new_point(k0)
  k = r.has_even_y? ? k0 : GROUP.order - k0
  e = create_challenge(r.x, p, message)

  sig = Schnorr::Signature.new(r.x, (k + e * d) % GROUP.order)
  raise 'The created signature does not pass verification.' unless valid_sig?(message, p.encode(true), sig.encode)

  sig
end

.tagged_hash(tag, msg) ⇒ String

Generate tagged hash value.

Parameters:

  • tag (String)

    tag value.

  • msg (String)

    the message to be hashed.

Returns:

  • (String)

    the hash value with binary format.



96
97
98
99
# File 'lib/schnorr.rb', line 96

def tagged_hash(tag, msg)
  tag_hash = Digest::SHA256.digest(tag)
  Digest::SHA256.digest(tag_hash + tag_hash + msg)
end

.valid_sig?(message, public_key, signature) ⇒ Boolean

Verifies the given Signature and returns true if it is valid.

Parameters:

  • message (String)

    A message to be signed with binary format.

  • public_key (String)

    The public key with binary format.

  • signature (String)

    The signature with binary format.

Returns:

  • (Boolean)

    whether signature is valid.



48
49
50
51
52
# File 'lib/schnorr.rb', line 48

def valid_sig?(message, public_key, signature)
  check_sig!(message, public_key, signature)
rescue InvalidSignatureError, ECDSA::Format::DecodeError
  false
end