Module: Schnorr

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

Defined Under Namespace

Modules: MuSig Classes: InvalidSignatureError, Signature

Constant Summary collapse

VERSION =
"0.4.0"

Class Method Summary collapse

Class Method Details

.check_sig!(message, public_key, signature, group) ⇒ Object

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.

  • group (ECDSA::Group)

    The curve that is being used.

Returns:

  • true

Raises:



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/schnorr.rb', line 92

def check_sig!(message, public_key, signature, group)
  sig = Schnorr::Signature.decode(signature)
  pubkey = ECDSA::Format::PointOctetString.decode(public_key, group)
  field = group.field

  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is not in the field.' unless field.include?(sig.r)
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is not in the field.' unless field.include?(sig.s)
  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is zero.' if sig.r.zero?
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is zero.' if sig.s.zero?

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

  r = group.new_point(sig.s) + pubkey.multiply_by_scalar(e).negate

  if r.infinity? || ECDSA::PrimeField.jacobi(r.y, group.field.prime) != 1 || r.x != sig.r
    raise Schnorr::InvalidSignatureError, 'signature verification failed.'
  end

  true
end

.create_challenge(x, pubkey, message, field, group) ⇒ Object



113
114
115
116
117
# File 'lib/schnorr.rb', line 113

def create_challenge(x, pubkey, message, field, group)
  r = ECDSA::Format::IntegerOctetString.encode(x, group.byte_length)
  public_key = ECDSA::Format::PointOctetString.encode(pubkey, compression: true)
  field.mod(ECDSA.normalize_digest(Digest::SHA256.digest(r + public_key + message), group.bit_length))
end

.sign(message, private_key, group: ECDSA::Group::Secp256k1) ⇒ Schnorr::Signature

Generate schnorr signature. (The number of times to add the generator point to itself to get the public key.)

Parameters:

  • message (String)

    A message to be signed with binary format.

  • private_key (Integer)

    The private key.

  • group (ECDSA::Group) (defaults to: ECDSA::Group::Secp256k1)

    The curve that is being used.

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/schnorr.rb', line 16

def sign(message, private_key, group: ECDSA::Group::Secp256k1)
  secret = ECDSA::Format::IntegerOctetString.encode(private_key, group.byte_length)
  field = ECDSA::PrimeField.new(group.order)
  k = field.mod(ECDSA::Format::IntegerOctetString.decode(Digest::SHA256.digest(secret + message)))
  raise 'Creation of signature failed. k is zero' if k.zero?

  r_point = group.new_point(k)
  unless ECDSA::PrimeField.jacobi(r_point.y, group.field.prime) == 1
    k = group.order - k
  end

  e = create_challenge(r_point.x, group.new_point(private_key), message, field, group)

  Schnorr::Signature.new(r_point.x, field.mod(k + e * private_key))
end

.valid_sig?(message, public_key, signature, group: ECDSA::Group::Secp256k1) ⇒ 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.

  • group (ECDSA::Group) (defaults to: ECDSA::Group::Secp256k1)

    The curve that is being used.

Returns:

  • (Boolean)

    whether signature is valid.



38
39
40
41
42
# File 'lib/schnorr.rb', line 38

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

.valid_sigs?(messages, public_keys, signatures, group: ECDSA::Group::Secp256k1) ⇒ Boolean

Batch verification

Parameters:

  • messages (Array[String])

    The array of message with binary format.

  • public_keys (Array[String])

    The array of public key with binary format.

  • signatures (Array[String])

    The array of signatures with binary format.

  • group (ECDSA::Group) (defaults to: ECDSA::Group::Secp256k1)

    The curve that is being used.

Returns:

  • (Boolean)

    whether signature is valid.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/schnorr.rb', line 50

def valid_sigs?(messages, public_keys, signatures, group: ECDSA::Group::Secp256k1)
  raise ArgumentError, 'all parameters must be an array with the same length.' if messages.size != public_keys.size || public_keys.size != signatures.size
  field = group.field
  pubkeys = public_keys.map{|p| ECDSA::Format::PointOctetString.decode(p, group)}
  sigs = signatures.map do|signature|
    sig = Schnorr::Signature.decode(signature)
    raise Schnorr::InvalidSignatureError, 'Invalid signature: r is not in the field.' unless field.include?(sig.r)
    raise Schnorr::InvalidSignatureError, 'Invalid signature: s is not in the field.' unless field.include?(sig.s)
    raise Schnorr::InvalidSignatureError, 'Invalid signature: r is zero.' if sig.r.zero?
    raise Schnorr::InvalidSignatureError, 'Invalid signature: s is zero.' if sig.s.zero?
    sig
  end
  left = 0
  right = nil
  pubkeys.each_with_index do |pubkey, i|
    r = sigs[i].r
    s = sigs[i].s
    e = create_challenge(r, pubkey, messages[i], field, group)
    c = field.mod(r.pow(3) + 7)
    y = c.pow((field.prime + 1)/4, field.prime)
    raise Schnorr::InvalidSignatureError, 'c is not equal to y^2.' unless c == y.pow(2, field.prime)
    r_point = ECDSA::Point.new(group, r, y)
    if i == 0
      left = s
      right = r_point + pubkey.multiply_by_scalar(e)
    else
      a = 1 + SecureRandom.random_number(group.order - 1)
      left += (a * s)
      right += (r_point.multiply_by_scalar(a) + pubkey.multiply_by_scalar(a * e))
    end
  end
  group.new_point(left) == right
rescue InvalidSignatureError, ECDSA::Format::DecodeError
  false
end