Module: FROST
- Defined in:
- lib/frost.rb,
lib/frost/dkg.rb,
lib/frost/hash.rb,
lib/frost/nonce.rb,
lib/frost/dealer.rb,
lib/frost/context.rb,
lib/frost/version.rb,
lib/frost/signature.rb,
lib/frost/polynomial.rb,
lib/frost/repairable.rb,
lib/frost/commitments.rb,
lib/frost/signing_key.rb,
lib/frost/secret_share.rb,
lib/frost/dkg/public_package.rb,
lib/frost/dkg/secret_package.rb
Defined Under Namespace
Modules: DKG, Hash, Repairable, Type Classes: Commitments, Context, Dealer, Error, Nonce, Polynomial, SecretShare, Signature, SigningKey
Constant Summary collapse
- VERSION =
"0.5.0"
Class Method Summary collapse
-
.aggregate(context, 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(context, group_pubkey, commitment_list, msg) ⇒ Hash
Compute binding factors.
-
.compute_challenge(context, group_commitment, group_pubkey, msg) ⇒ Integer
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(context, secret_share, group_pubkey, nonces, msg, commitment_list) ⇒ Integer
Generate signature share.
-
.verify(signature, public_key, msg) ⇒ Boolean
Verify signature.
-
.verify_share(context, identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg) ⇒ Boolean
Verify signature share.
Class Method Details
.aggregate(context, 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.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/frost.rb', line 158 def aggregate(context, commitment_list, msg, group_pubkey, sig_shares) raise ArgumentError "context must be FROST::Context." unless context.is_a?(FROST::Context) 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, "group_pubkey and context groups are different." unless context.group == group_pubkey.group 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(context, group_pubkey, commitment_list, msg) group_commitment = compute_group_commitment(commitment_list, binding_factors) field = ECDSA::PrimeField.new(context.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(context, group_commitment, field.mod(s)) end |
.compute_binding_factors(context, 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.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/frost.rb', line 68 def compute_binding_factors(context, group_pubkey, commitment_list, msg) raise ArgumentError "context must be FROST::Context." unless context.is_a?(FROST::Context) 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) raise ArgumentError, "group_pubkey and context groups are different." unless context.group == group_pubkey.group msg_hash = Hash.h4(msg, context) encoded_commitment = Commitments.encode_group_commitment(commitment_list) encoded_commitment_hash = Hash.h5(encoded_commitment, context) 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, context.group) binding_factors[commitments.identifier] = Hash.h1(preimage, context) end binding_factors end |
.compute_challenge(context, group_commitment, group_pubkey, msg) ⇒ Integer
Create the per-message challenge. If context type is BIP-340(taproot), only the X coordinate of R and group_pubkey are hashed, unlike vanilla FROST.
104 105 106 107 108 109 110 111 112 113 |
# File 'lib/frost.rb', line 104 def compute_challenge(context, group_commitment, group_pubkey, msg) raise ArgumentError "context must be FROST::Context." unless context.is_a?(FROST::Context) 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) raise ArgumentError, "group_pubkey and context groups are different." unless context.group == group_pubkey.group preimage = context.normalize(group_commitment) + context.normalize(group_pubkey) + msg Hash.h2(preimage, context) end |
.compute_group_commitment(commitment_list, binding_factors) ⇒ ECDSA::Point
Compute the group commitment
90 91 92 93 94 95 96 |
# File 'lib/frost.rb', line 90 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
51 52 53 54 55 56 57 58 |
# File 'lib/frost.rb', line 51 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(context, secret_share, group_pubkey, nonces, msg, commitment_list) ⇒ Integer
Generate signature share.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/frost.rb', line 123 def sign(context, secret_share, group_pubkey, nonces, msg, commitment_list) raise ArgumentError "context must be FROST::Context." unless context.is_a?(FROST::Context) 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, "group_pubkey and context groups are different." unless context.group == group_pubkey.group identifier = secret_share.identifier # Compute binding factors binding_factors = compute_binding_factors(context, 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, context.group) # Compute the per-message challenge challenge = compute_challenge(context, group_commitment, group_pubkey, msg) # Compute the signature share hiding_nonce, binding_nonce = context.convert_signer_nocnes(group_commitment, 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.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/frost.rb', line 218 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, "public_key and context groups are different." unless signature.context.group == public_key.group raise ArgumentError, "msg must be String." unless msg.is_a?(String) context = signature.context public_key, signature = context.pre_verify(public_key, signature) # Compute challenge challenge = compute_challenge(context, 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(context, identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg) ⇒ Boolean
Verify signature share. in round two from the i-th participant.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/frost.rb', line 187 def verify_share(context, identifier, pubkey_i, sig_share_i, commitment_list, group_pubkey, msg) raise ArgumentError "context must be FROST::Context." unless context.is_a?(FROST::Context) 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) raise ArgumentError, "group_pubkey and context groups are different." unless context.group == group_pubkey.group binding_factors = compute_binding_factors(context, 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 = context.convert_commitment_share(group_commitment, hiding_commitment + binding_commitment * binding_factor) challenge = compute_challenge(context, group_commitment, group_pubkey, msg) identifiers = commitment_list.map(&:identifier) lambda_i = Polynomial.derive_interpolating_value(identifiers, identifier, context.group) l = context.group.generator * sig_share_i r = comm_share + pubkey_i * (challenge * lambda_i) l == r end |