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
-
.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.
-
.compute_binding_factors(group_pubkey, commitment_list, msg) ⇒ Hash
Compute binding factors.
-
.compute_challenge(group_commitment, group_pubkey, msg) ⇒ Object
Create the per-message challenge.
-
.compute_group_commitment(commitment_list, binding_factors) ⇒ ECDSA::Point
Compute the group commitment.
-
.encode_identifier(identifier, group) ⇒ String
Encode identifier.
-
.sign(secret_share, group_pubkey, nonces, msg, commitment_list) ⇒ Integer
Generate signature share.
-
.verify(signature, public_key, msg) ⇒ Boolean
Verify signature.
-
.verify_share(identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg) ⇒ Boolean
Verify signature share.
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.
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.
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.
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
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
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.
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.
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.
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 |