Module: Schnorr::MuSig2

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

Defined Under Namespace

Classes: Error, KeyAggContext, SessionContext

Class Method Summary collapse

Methods included from Util

hex2bin, hex_string?, string2point

Class Method Details

.aggregate(pubkeys) ⇒ Schnorr::MuSig2::KeyAggContext

Compute aggregate public key.

Parameters:

  • pubkeys (Array[String])

    An array of public keys.

Returns:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/schnorr/musig2.rb', line 23

def aggregate(pubkeys)
  pubkeys = pubkeys.map do |p|
    pubkey = hex2bin(p)
    raise ArgumentError, "Public key must be 33 bytes." unless pubkey.bytesize == 33
    pubkey
  end
  pk2 = second_key(pubkeys)
  q = ECDSA::Ext::JacobianPoint.infinity_point(GROUP)
  l = hash_keys(pubkeys)
  pubkeys.each do |p|
    begin
      point = string2point(p).to_jacobian
    rescue ECDSA::Format::DecodeError
      raise ArgumentError, 'Invalid public key.'
    end
    coeff = p == pk2 ? 1 : Schnorr.tagged_hash('KeyAgg coefficient', l + p).bti
    q += point * coeff
  end
  KeyAggContext.new(q.to_affine, 1, 0)
end

.aggregate_nonce(pub_nonces) ⇒ String

Aggregate public nonces.

Parameters:

  • pub_nonces (Array)

    Array of public nonce. Each public nonce consists 66 bytes.

Returns:

  • (String)

    An aggregated public nonce(R1 || R2) with hex format.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/schnorr/musig2.rb', line 114

def aggregate_nonce(pub_nonces)
  2.times.map do |i|
    r = GROUP.generator.to_jacobian.infinity_point
    pub_nonces = pub_nonces.each do |nonce|
      nonce = hex2bin(nonce)
      raise ArgumentError, "" unless nonce.bytesize == 66
      begin
        p = string2point(nonce[(i * 33)...(i + 1)*33]).to_jacobian
        raise ArgumentError, 'Public nonce is infinity' if p.infinity?
      rescue ECDSA::Format::DecodeError
        raise ArgumentError, "Invalid public nonce."
      end
      r += p
    end
    r.to_affine.encode.unpack1('H*')
  end.join
end

.aggregate_with_tweaks(pubkeys, tweaks, modes) ⇒ Schnorr::MuSig2::KeyAggContext

Compute aggregate public key with tweaks.

Parameters:

  • pubkeys (Array[String])

    An array of public keys.

  • tweaks (Array)

    An array of tweak.

  • modes (Array)

    An array of x_only mode.

Returns:

Raises:

  • (ArgumentError)


49
50
51
52
53
54
55
56
57
58
# File 'lib/schnorr/musig2.rb', line 49

def aggregate_with_tweaks(pubkeys, tweaks, modes)
  raise ArgumentError, 'tweaks and modes must be same length' unless tweaks.length == modes.length
  agg_ctx = aggregate(pubkeys)
  tweaks.each.with_index do |tweak, i|
    tweak = hex2bin(tweak)
    raise ArgumentError, 'tweak value must be 32 bytes' unless tweak.bytesize == 32
    agg_ctx = agg_ctx.apply_tweak(tweak, modes[i])
  end
  agg_ctx
end

.deterministic_sign(sk, agg_other_nonce, pubkeys, msg, tweaks: [], modes: [], rand: nil) ⇒ Array

Generate deterministic signature.

Parameters:

  • sk (String)

    The secret key string (32 bytes).

  • agg_other_nonce (String)

    Other aggregated nonce.

  • pubkeys (Array)

    An array of public keys.

  • msg (String)

    The message to be signed.

  • tweaks (Array(String)) (defaults to: [])

    (Optional) An array of tweak value.

  • modes (Array(Boolean)) (defaults to: [])

    (Optional) An array of tweak mode.

  • rand (String) (defaults to: nil)

    (Optional) A 32-byte array freshly drawn uniformly at random.

Returns:

  • (Array)
    public nonce, partial signature

Raises:

  • (ArgumentError)


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/schnorr/musig2.rb', line 141

def deterministic_sign(sk, agg_other_nonce, pubkeys, msg, tweaks: [], modes: [], rand: nil)
  raise ArgumentError, 'The tweaks and modes arrays must have the same length.' unless tweaks.length == modes.length
  sk = hex2bin(sk)
  msg = hex2bin(msg)
  agg_other_nonce = hex2bin(agg_other_nonce)
  sk_ = rand ? gen_aux(sk, hex2bin(rand)) : sk
  agg_ctx = aggregate_with_tweaks(pubkeys, tweaks, modes)
  agg_pk = [agg_ctx.x_only_pubkey].pack("H*")
  k1 = deterministic_nonce_hash(sk_, agg_other_nonce, agg_pk, msg, 0).bti
  k2 = deterministic_nonce_hash(sk_, agg_other_nonce, agg_pk, msg, 1).bti
  r1 = (GROUP.generator.to_jacobian * k1).to_affine
  r2 = (GROUP.generator.to_jacobian * k2).to_affine
  raise ArgumentError, 'R1 must not be infinity.' if r1.infinity?
  raise ArgumentError, 'R2 must not be infinity.' if r2.infinity?
  pub_nonce = r1.encode + r2.encode
  pk = (GROUP.generator.to_jacobian * sk.bti).to_affine
  sec_nonce = ECDSA::Format::IntegerOctetString.encode(k1, GROUP.byte_length) +
    ECDSA::Format::IntegerOctetString.encode(k2, GROUP.byte_length) + pk.encode
  agg_nonce = aggregate_nonce([pub_nonce, agg_other_nonce])
  ctx = SessionContext.new(agg_nonce, pubkeys, msg, tweaks, modes)
  sig = ctx.sign(sec_nonce, sk)
  [pub_nonce.unpack1('H*'), sig]
end

.gen_nonce(pk:, sk: nil, agg_pubkey: nil, msg: nil, extra_in: nil, rand: SecureRandom.bytes(32)) ⇒ Array(String)

Generate nonce.

Parameters:

  • pk (String)

    The public key (33 bytes).

  • sk (String) (defaults to: nil)

    (Optional) The secret key string (32 bytes).

  • agg_pubkey (String) (defaults to: nil)

    (Optional) The aggregated public key (32 bytes).

  • msg (String) (defaults to: nil)

    (Optional) The message to be signed.

  • extra_in (String) (defaults to: nil)

    (Optional) The auxiliary input.

  • rand (String) (defaults to: SecureRandom.bytes(32))

    (Optional) A 32-byte array freshly drawn uniformly at random.

Returns:

  • (Array(String))

    The array of sec nonce and pub nonce with hex format.

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/schnorr/musig2.rb', line 68

def gen_nonce(pk: , sk: nil, agg_pubkey: nil, msg: nil, extra_in: nil, rand: SecureRandom.bytes(32))
  rand = hex2bin(rand)
  raise ArgumentError, 'The rand must be 32 bytes.' unless rand.bytesize == 32

  pk = hex2bin(pk)
  raise ArgumentError, 'The pk must be 33 bytes.' unless pk.bytesize == 33

  rand = if sk.nil?
           rand
         else
           sk = hex2bin(sk)
           raise ArgumentError, "The sk must be 32 bytes." unless sk.bytesize == 32
           gen_aux(sk, rand)
         end
  agg_pubkey = if agg_pubkey
                 agg_pubkey = hex2bin(agg_pubkey)
                 raise ArgumentError, 'The agg_pubkey must be 33 bytes.' unless agg_pubkey.bytesize == 32
                 agg_pubkey
               else
                 ''
               end
  msg_prefixed = if msg.nil?
                   [0].pack('C')
                 else
                   msg = hex2bin(msg)
                   [1, msg.bytesize].pack('CQ>') + msg
                 end
  extra_in = extra_in ? hex2bin(extra_in) : ''

  k1 = nonce_hash(rand, pk, agg_pubkey, 0, msg_prefixed, extra_in)
  k1_i = k1.bti % GROUP.order
  k2 = nonce_hash(rand, pk, agg_pubkey, 1, msg_prefixed, extra_in)
  k2_i = k2.bti % GROUP.order
  raise ArgumentError, 'k1 must not be zero.' if k1_i.zero?
  raise ArgumentError, 'k2 must not be zero.' if k2_i.zero?

  r1 = (GROUP.generator.to_jacobian * k1_i).to_affine
  r2 = (GROUP.generator.to_jacobian * k2_i).to_affine
  pub_nonce = r1.encode + r2.encode
  sec_nonce = k1 + k2 + pk
  [sec_nonce.unpack1('H*'), pub_nonce.unpack1('H*')]
end

.hash_keys(pubkeys) ⇒ Object

Compute



173
174
175
# File 'lib/schnorr/musig2.rb', line 173

def hash_keys(pubkeys)
  Schnorr.tagged_hash('KeyAgg list', pubkeys.join)
end

.second_key(pubkeys) ⇒ Object



165
166
167
168
169
170
# File 'lib/schnorr/musig2.rb', line 165

def second_key(pubkeys)
  pubkeys[1..].each do |p|
    return p unless p == pubkeys[0]
  end
  ['00'].pack("H*") * 33
end

.sort_pubkeys(pubkeys) ⇒ Array(String)

Sort the list of public keys in lexicographical order.

Parameters:

  • pubkeys (Array(String))

    An array of public keys.

Returns:

  • (Array(String))

    Sorted public keys with hex format.



16
17
18
# File 'lib/schnorr/musig2.rb', line 16

def sort_pubkeys(pubkeys)
  pubkeys.map{|p| hex_string?(p) ? p : p.unpack1('H*')}.sort
end