Class: Schnorr::MuSig2::SessionContext

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/schnorr/musig2/context/session.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util

#hex2bin, #hex_string?, #string2point

Constructor Details

#initialize(agg_nonce, pubkeys, msg, tweaks = [], modes = []) ⇒ SessionContext

Returns a new instance of SessionContext.

Parameters:

  • agg_nonce (String)
  • pubkeys (Array(String))

    An array of public keys.

  • msg (String)

    A message to be signed.

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

    An array of tweaks(32 bytes).

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

    An array of tweak mode(Boolean).



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/schnorr/musig2/context/session.rb', line 12

def initialize(agg_nonce, pubkeys, msg, tweaks = [], modes = [])
  @used_secnonces = []
  @agg_nonce = hex2bin(agg_nonce)
  @pubkeys = pubkeys.map do |pubkey|
    pubkey = hex2bin(pubkey)
    raise ArgumentError, 'pubkey must be 33 bytes' unless pubkey.bytesize == 33
    pubkey
  end
  @msg = hex2bin(msg)
  @tweaks = tweaks
  @modes = modes
  @modes.each do |mode|
    raise ArgumentError, 'mode must be Boolean.' unless [TrueClass, FalseClass].include?(mode.class)
  end
  @agg_ctx = MuSig2.aggregate_with_tweaks(@pubkeys, @tweaks, @modes)
  @b = Schnorr.tagged_hash('MuSig/noncecoef', @agg_nonce + agg_ctx.q.encode(true) + @msg).bti
  begin
    r1 = string2point(@agg_nonce[0...33]).to_jacobian
    r2 = string2point(@agg_nonce[33...66]).to_jacobian
  rescue ECDSA::Format::DecodeError
    raise ArgumentError, 'Invalid agg_nonce'
  end
  r = (r1 + r2 * @b).to_affine
  @r = r.infinity? ? GROUP.generator : r
end

Instance Attribute Details

#agg_ctxObject (readonly)

Returns the value of attribute agg_ctx.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def agg_ctx
  @agg_ctx
end

#agg_nonceObject (readonly)

Returns the value of attribute agg_nonce.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def agg_nonce
  @agg_nonce
end

#bObject (readonly)

Returns the value of attribute b.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def b
  @b
end

#modesObject (readonly)

Returns the value of attribute modes.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def modes
  @modes
end

#msgObject (readonly)

Returns the value of attribute msg.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def msg
  @msg
end

#pubkeysObject (readonly)

Returns the value of attribute pubkeys.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def pubkeys
  @pubkeys
end

#rObject (readonly)

Returns the value of attribute r.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def r
  @r
end

#tweaksObject (readonly)

Returns the value of attribute tweaks.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def tweaks
  @tweaks
end

#used_secnoncesObject (readonly)

Returns the value of attribute used_secnonces.



5
6
7
# File 'lib/schnorr/musig2/context/session.rb', line 5

def used_secnonces
  @used_secnonces
end

Instance Method Details

#aggregate_partial_sigs(partial_sigs) ⇒ Schnorr::Signature

Aggregate partial signatures.

Parameters:

  • partial_sigs (Array)

    An array of partial signature.

Returns:



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/schnorr/musig2/context/session.rb', line 102

def aggregate_partial_sigs(partial_sigs)
  s = 0
  partial_sigs.each do |partial_sig|
    s_i = hex2bin(partial_sig).bti
    raise ArgumentError, 'Invalid partial sig.' if s_i >= GROUP.order
    s = (s + s_i) % GROUP.order
  end
  g = agg_ctx.q.has_even_y? ? 1 : GROUP.order - 1
  s = (s + e * g * agg_ctx.tacc) % GROUP.order
  Schnorr::Signature.new(r.x, s)
end

#eInteger

Get message digest.

Returns:



40
41
42
# File 'lib/schnorr/musig2/context/session.rb', line 40

def e
  Schnorr.tagged_hash('BIP0340/challenge', r.encode(true) + agg_ctx.q.encode(true) + msg).bti
end

#sign(nonce, sk) ⇒ String

Create partial signature.

Parameters:

  • nonce (String)

    The secret nonce.

  • sk (String)

    The secret key.

Returns:

  • (String)

    Partial signature with hex format.

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/schnorr/musig2/context/session.rb', line 48

def sign(nonce, sk)
  nonce = hex2bin(nonce)
  raise ArgumentError, 'Same nonce already used.' if used_secnonces.include?(nonce)
  sk = hex2bin(sk)
  k1 = nonce[0...32].bti
  k2 = nonce[32...64].bti
  raise ArgumentError, 'first nonce value is out of range.' if k1 <= 0 || GROUP.order <= k1
  raise ArgumentError, 'second nonce value is out of range.' if k2 <= 0 || GROUP.order <= k2
  k1 = r.has_even_y? ? k1 : GROUP.order - k1
  k2 = r.has_even_y? ? k2 : GROUP.order - k2
  d = sk.bti
  raise ArgumentError, 'secret key value is out of range.' if d <= 0 || GROUP.order <= d
  p = (GROUP.generator.to_jacobian * d).to_affine.encode
  raise ArgumentError, 'Public key does not match nonce_gen argument' unless p == nonce[64...97]
  a = key_agg_coeff(pubkeys, p)
  g = agg_ctx.q.has_even_y? ? 1 : GROUP.order - 1
  d = (g * agg_ctx.gacc * d)  % GROUP.order
  s = (k1 + b * k2 + e * a * d) % GROUP.order
  r1 = (GROUP.generator.to_jacobian * k1).to_affine
  r2 = (GROUP.generator.to_jacobian * k2).to_affine
  raise ArgumentError, 'R1 can not be infinity.' if r1.infinity?
  raise ArgumentError, 'R2 can not be infinity.' if r2.infinity?
  used_secnonces << nonce
  ECDSA::Format::IntegerOctetString.encode(s, GROUP.byte_length).unpack1('H*')
end

#valid_partial_sig?(partial_sig, pub_nonce, signer_index) ⇒ Boolean

Verify partial signature.

Parameters:

  • partial_sig (String)

    The partial signature.

  • pub_nonce (String)

    A public nonce.

  • signer_index (Integer)

    The index of signer.

Returns:

  • (Boolean)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/schnorr/musig2/context/session.rb', line 79

def valid_partial_sig?(partial_sig, pub_nonce, signer_index)
  begin
    partial_sig = hex2bin(partial_sig)
    pub_nonce = hex2bin(pub_nonce)
    s = partial_sig.bti
    return false if s >= GROUP.order
    r1 = string2point(pub_nonce[0...33]).to_jacobian
    r2 = string2point(pub_nonce[33...66]).to_jacobian
    r_s = (r1 + r2 * b).to_affine
    r_s = r.has_even_y? ? r_s : r_s.negate
    pk = string2point(pubkeys[signer_index])
    a = key_agg_coeff(pubkeys, pubkeys[signer_index])
    g = agg_ctx.q.has_even_y? ? 1 : GROUP.order - 1
    g = (g * agg_ctx.gacc) % GROUP.order
    GROUP.generator.to_jacobian * s == r_s.to_jacobian + pk.to_jacobian * (e * a * g % GROUP.order)
  rescue ECDSA::Format::DecodeError => e
    raise ArgumentError, e
  end
end