Module: Schnorr

Extended by:
Util
Defined in:
lib/schnorr.rb,
lib/schnorr/util.rb,
lib/schnorr/musig2.rb,
lib/schnorr/version.rb,
lib/schnorr/signature.rb,
lib/schnorr/musig2/context/key_agg.rb,
lib/schnorr/musig2/context/session.rb

Defined Under Namespace

Modules: MuSig2, Util Classes: InvalidSignatureError, Signature

Constant Summary collapse

GROUP =
ECDSA::Group::Secp256k1
DEFAULT_AUX =
([0x00] * 32).pack('C*')
VERSION =
"0.7.0"

Class Method Summary collapse

Methods included from Util

hex2bin, hex_string?, string2point

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:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/schnorr.rb', line 69

def check_sig!(message, public_key, signature)
  message = hex2bin(message)
  public_key = hex2bin(public_key)
  public_key = [public_key].pack('H*') if hex_string?(public_key)
  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.generator.to_jacobian * sig.s + pubkey.to_jacobian * (GROUP.order - e)).to_affine

  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:



98
99
100
101
# File 'lib/schnorr.rb', line 98

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 aux_rand is nil, it is treated the same as an all-zero one. See BIP-340 “Default Signing” for a full explanation of this argument and for guidance if randomness is expensive.

Parameters:

  • message (String)

    A message to be signed with binary format.

  • private_key (String)

    The private key(binary format or hex format).

  • aux_rand (String) (defaults to: nil)

    The auxiliary random data(binary format or hex format).

Returns:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/schnorr.rb', line 22

def sign(message, private_key, aux_rand = nil)
  private_key = private_key.unpack1('H*') unless hex_string?(private_key)

  d0 = private_key.to_i(16)
  raise 'private_key must be an integer in the range 1..n-1.' unless 0 < d0 && d0 <= (GROUP.order - 1)
  if aux_rand
    aux_rand = [aux_rand].pack("H*") if hex_string?(aux_rand)
    raise 'aux_rand must be 32 bytes.' unless aux_rand.bytesize == 32
  else
    aux_rand = DEFAULT_AUX
  end

  p = (GROUP.generator.to_jacobian * d0).to_affine
  d = p.has_even_y? ? d0 : GROUP.order - d0

  t = aux_rand.nil? ? d : d ^ tagged_hash('BIP0340/aux', aux_rand).bti
  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.generator.to_jacobian * k0).to_affine
  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.



107
108
109
110
# File 'lib/schnorr.rb', line 107

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.



58
59
60
61
62
# File 'lib/schnorr.rb', line 58

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