Module: FROST

Defined in:
lib/frost.rb,
lib/frost/dkg.rb,
lib/frost/hash.rb,
lib/frost/nonce.rb,
lib/frost/version.rb,
lib/frost/signature.rb,
lib/frost/polynomial.rb,
lib/frost/repairable.rb,
lib/frost/commitments.rb,
lib/frost/dkg/package.rb,
lib/frost/signing_key.rb,
lib/frost/secret_share.rb,
lib/frost/dkg/secret_package.rb

Defined Under Namespace

Modules: DKG, Hash, Repairable Classes: Commitments, Error, Nonce, Polynomial, SecretShare, Signature, SigningKey

Constant Summary collapse

VERSION =
"0.3.0"

Class Method Summary collapse

Class Method Details

.aggregate(commitment_list, msg, group_pubkey, sig_shares) ⇒ FROST::Signature

Aggregates the signature shares to produce a final signature that can be verified with the group public key.

Parameters:

  • commitment_list (Array)

    A list of commitments issued by each participant.

  • msg (String)

    The message to be signed.

  • group_pubkey (ECDSA::Point)

    Public key corresponding to the group signing key.

  • sig_shares (Array)

    A set of signature shares z_i, integer values.

Returns:

Raises:

  • (ArgumentError)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/frost.rb', line 121

def aggregate(commitment_list, msg, group_pubkey, sig_shares)
  raise ArgumentError, "msg must be String." unless msg.is_a?(String)
  raise ArgumentError, "group_pubkey must be ECDSA::Point." unless group_pubkey.is_a?(ECDSA::Point)
  raise ArgumentError, "The numbers of commitment_list and sig_shares do not match." unless commitment_list.length == sig_shares.length

  binding_factors = compute_binding_factors(group_pubkey, commitment_list, msg)
  group_commitment = compute_group_commitment(commitment_list, binding_factors)

  field = ECDSA::PrimeField.new(group_pubkey.group.order)
  s = sig_shares.inject(0) do |sum, z_i|
    raise ArgumentError, "sig_shares must be array of integer" unless z_i.is_a?(Integer)
    field.mod(sum + z_i)
  end

  Signature.new(group_commitment, field.mod(s))
end

.compute_binding_factors(group_pubkey, commitment_list, msg) ⇒ Hash

Compute binding factors. www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-binding-factors-computation This list must be sorted in ascending order by identifier.

Parameters:

  • group_pubkey (ECDSA::Point)
  • commitment_list (Array)

    The list of commitments issued by each participants.

  • msg (String)

    The message to be signed.

Returns:

  • (Hash)

    The hash of binding factor.

Raises:

  • (ArgumentError)


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/frost.rb', line 44

def compute_binding_factors(group_pubkey, commitment_list, msg)
  raise ArgumentError, "group_pubkey must be ECDSA::Point." unless group_pubkey.is_a?(ECDSA::Point)
  raise ArgumentError, "msg must be String." unless msg.is_a?(String)

  msg_hash = Hash.h4(msg, group_pubkey.group)
  encoded_commitment = Commitments.encode_group_commitment(commitment_list)
  encoded_commitment_hash = Hash.h5(encoded_commitment, group_pubkey.group)
  rho_input_prefix = [group_pubkey.to_hex].pack("H*") + msg_hash + encoded_commitment_hash
  binding_factors = {}
  commitment_list.each do |commitments|
    preimage = rho_input_prefix + encode_identifier(commitments.identifier, group_pubkey.group)
    binding_factors[commitments.identifier] = Hash.h1(preimage, group_pubkey.group)
  end
  binding_factors
end

.compute_challenge(group_commitment, group_pubkey, msg) ⇒ Object

Create the per-message challenge.

Parameters:

  • group_commitment (ECDSA::Point)

    The group commitment.

  • group_pubkey (ECDSA::Point)

    The public key corresponding to the group signing key.

  • msg (String)

    The message to be signed.

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
# File 'lib/frost.rb', line 76

def compute_challenge(group_commitment, group_pubkey, msg)
  raise ArgumentError, "group_commitment must be ECDSA::Point." unless group_commitment.is_a?(ECDSA::Point)
  raise ArgumentError, "group_pubkey must be ECDSA::Point." unless group_pubkey.is_a?(ECDSA::Point)
  raise ArgumentError, "msg must be String." unless msg.is_a?(String)

  input = [group_commitment.to_hex + group_pubkey.to_hex].pack("H*") + msg
  Hash.h2(input, group_commitment.group)
end

.compute_group_commitment(commitment_list, binding_factors) ⇒ ECDSA::Point

Compute the group commitment

Parameters:

  • commitment_list (Array)

    The list of commitments.

  • binding_factors (Hash)

    The map of binding factors.

Returns:

  • (ECDSA::Point)


64
65
66
67
68
69
70
# File 'lib/frost.rb', line 64

def compute_group_commitment(commitment_list, binding_factors)
  commitment_list.inject(commitment_list.first.hiding.group.infinity) do |sum, commitments|
    binding_factor = binding_factors[commitments.identifier]
    binding_nonce = commitments.binding * binding_factor
    binding_nonce + commitments.hiding + sum
  end
end

.encode_identifier(identifier, group) ⇒ String

Encode identifier

Parameters:

  • identifier (Integer)
  • group (ECDSA::Group)

Returns:

  • (String)

    The encoded identifier



28
29
30
31
32
33
34
35
# File 'lib/frost.rb', line 28

def encode_identifier(identifier, group)
  case group
  when ECDSA::Group::Secp256k1, ECDSA::Group::Secp256r1
    ECDSA::Format::IntegerOctetString.encode(identifier, 32)
  else
    raise RuntimeError, "group #{group} dose not supported."
  end
end

.sign(secret_share, group_pubkey, nonces, msg, commitment_list) ⇒ Integer

Generate signature share.

Parameters:

  • secret_share (FROST::SecretShare)

    Signer secret key share.

  • group_pubkey (ECDSA::Point)

    Public key corresponding to the group signing key.

  • nonces (Array)

    Pair of nonce values (hiding_nonce, binding_nonce) for signer_i.

  • msg (String)

    The message to be signed

  • commitment_list (Array)

    A list of commitments issued by each participant.

Returns:

  • (Integer)

    A signature share.



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

def sign(secret_share, group_pubkey, nonces, msg, commitment_list)
  identifier = secret_share.identifier
  # Compute binding factors
  binding_factors = compute_binding_factors(group_pubkey, commitment_list, msg)
  binding_factor = binding_factors[identifier]

  # Compute group commitment
  group_commitment = compute_group_commitment(commitment_list, binding_factors)

  # Compute Lagrange coefficient
  identifiers = commitment_list.map(&:identifier)
  lambda_i = Polynomial.derive_interpolating_value(identifiers, identifier, group_pubkey.group)

  # Compute the per-message challenge
  challenge = compute_challenge(group_commitment, group_pubkey, msg)

  # Compute the signature share
  hiding_nonce, binding_nonce = nonces
  field = ECDSA::PrimeField.new(group_pubkey.group.order)
  field.mod(hiding_nonce.value +
              field.mod(binding_nonce.value * binding_factor) + field.mod(lambda_i * secret_share.share * challenge))
end

.verify(signature, public_key, msg) ⇒ Boolean

Verify signature.

Parameters:

Returns:

  • (Boolean)

    Verification result.

Raises:

  • (ArgumentError)


176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/frost.rb', line 176

def verify(signature, public_key, msg)
  raise ArgumentError, "signature must be FROST::Signature" unless signature.is_a?(FROST::Signature)
  raise ArgumentError, "public_key must be ECDSA::Point" unless public_key.is_a?(ECDSA::Point)
  raise ArgumentError, "msg must be String." unless msg.is_a?(String)

  # Compute challenge
  challenge = compute_challenge(signature.r, public_key, msg)

  s_g = public_key.group.generator * signature.s
  c_p = public_key * challenge
  result = (s_g + signature.r.negate + c_p.negate) * public_key.group.cofactor
  result.infinity?
end

.verify_share(identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg) ⇒ Boolean

Verify signature share. in round two from the i-th participant.

Parameters:

  • identifier (Integer)

    Identifier i of the participant.

  • pubkey_i (ECDSA::Point)

    The public key for the i-th participant

  • sig_share_i (Integer)

    Integer value indicating the signature share as produced

  • commitment_list (Array)

    A list of commitments issued by each participant.

  • group_pubkey (ECDSA::Point)

    Public key corresponding to the group signing key.

  • msg (String)

    The message to be signed.

Returns:

  • (Boolean)

    Verification result.

Raises:

  • (ArgumentError)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/frost.rb', line 147

def verify_share(identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg)
  raise ArgumentError, "identifier must be Integer." unless identifier.is_a?(Integer)
  raise ArgumentError, "sig_share_i must be Integer." unless sig_share_i.is_a?(Integer)
  raise ArgumentError, "pubkey_i must be ECDSA::Point." unless pubkey_i.is_a?(ECDSA::Point)
  raise ArgumentError, "group_pubkey must be ECDSA::Point." unless group_pubkey.is_a?(ECDSA::Point)

  binding_factors = compute_binding_factors(group_pubkey, commitment_list, msg)
  binding_factor = binding_factors[identifier]
  group_commitment = compute_group_commitment(commitment_list, binding_factors)
  comm_i = commitment_list.find{|c| c.identifier == identifier}
  hiding_commitment = comm_i.hiding
  binding_commitment = comm_i.binding
  raise ArgumentError, "hiding_commitment must be ECDSA::Point." unless hiding_commitment.is_a?(ECDSA::Point)
  raise ArgumentError, "binding_commitment must be ECDSA::Point." unless binding_commitment.is_a?(ECDSA::Point)

  comm_share = hiding_commitment + binding_commitment * binding_factor
  challenge = compute_challenge(group_commitment, group_pubkey, msg)
  identifiers = commitment_list.map(&:identifier)
  lambda_i = Polynomial.derive_interpolating_value(identifiers, identifier, group_pubkey.group)
  l = group_pubkey.group.generator * sig_share_i
  r = comm_share + pubkey_i * (challenge * lambda_i)
  l == r
end