Skip to content

Commit

Permalink
Add threshold and commitment count checks
Browse files Browse the repository at this point in the history
  • Loading branch information
azuchi committed Feb 22, 2024
1 parent a0f917f commit 75ba001
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 44 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ DKG can be run as below.
max_signer = 5
min_signer = 3

secrets = {}
secret_packages = {}
round1_outputs = {}
# Round 1:
# For each participant, perform the first part of the DKG protocol.
1.upto(max_signer) do |i|
polynomial, package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
secrets[i] = polynomial
round1_outputs[i] = package
secret_package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
secret_packages[i] = secret_package
round1_outputs[i] = secret_package.public_package
end

# Each participant sends their commitments and proof to other participants.
Expand All @@ -98,20 +98,21 @@ end

# Each participant verify knowledge of proof in received package.
received_package.each do |id, packages|
secret_package = secret_packages[id]
packages.each do |package|
expect(FROST::DKG.verify_proof_of_knowledge(package)).to be true
expect(FROST::DKG.verify_proof_of_knowledge(secret_package, package)).to be true
end
end

# Round 2:
# Each participant generate share for other participants and send it.
received_shares = {}
1.upto(max_signer) do |i|
polynomial = secrets[i] # own secret
secret_package = secret_packages[i] # own secret
1.upto(max_signer) do |o|
next if i == o
received_shares[o] ||= []
received_shares[o] << [i, polynomial.gen_share(o)]
received_shares[o] << [i, secret_package.gen_share(o)]
end
end

Expand All @@ -127,11 +128,11 @@ end
signing_shares = {}
1.upto(max_signer) do |i|
shares = received_shares[i].map { |_, share| share }
signing_shares[i] = FROST::DKG.compute_signing_share(secrets[i], shares)
signing_shares[i] = FROST::DKG.compute_signing_share(secret_packages[i], shares)
end

# Participant 1 compute group public key.
group_pubkey = FROST::DKG.compute_group_pubkey(secrets[1], received_package[1])
group_pubkey = FROST::DKG.compute_group_pubkey(secret_packages[1], received_package[1])

# The subsequent signing phase is the same as above with signing_shares as the secret.
```
Expand Down
47 changes: 28 additions & 19 deletions lib/frost/dkg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module FROST
# Distributed Key Generation feature.
module DKG

autoload :SecretPackage, "frost/dkg/secret_package"
autoload :Package, "frost/dkg/package"

module_function
Expand All @@ -10,17 +11,18 @@ module DKG
# Participant generate key and commitments, proof of knowledge for secret.
# @param [Integer] identifier
# @param [ECDSA::Group] group Group of elliptic curve.
# @return [Array] The triple of polynomial and public package(FROST::DKG::Package)
# @return [FROST::DKG::SecretPackage] Secret received_package for owner.
def generate_secret(identifier, min_signers, max_signers, group)
raise ArgumentError, "identifier must be Integer" unless identifier.is_a?(Integer)
raise ArgumentError, "identifier must be greater than 0." if identifier < 1
raise ArgumentError, "group must be ECDSA::Group." unless group.is_a?(ECDSA::Group)
raise ArgumentError, "min_signers must be Integer." unless min_signers.is_a?(Integer)
raise ArgumentError, "max_singers must be Integer." unless max_signers.is_a?(Integer)
raise ArgumentError, "max_signers must be greater than or equal to min_signers." if max_signers < min_signers

secret = FROST::SigningKey.generate(group)
# Every participant P_i samples t random values (a_{i0}, ..., a_{i(t−1)}) ← Z_q
polynomial = secret.gen_poly(min_signers - 1)
[polynomial, Package.new(identifier, polynomial.gen_commitments, polynomial.gen_proof_of_knowledge(identifier))]
SecretPackage.new(identifier, min_signers, max_signers, polynomial)
end

# Generate proof of knowledge for secret.
Expand All @@ -43,38 +45,45 @@ def gen_proof_of_knowledge(identifier, polynomial)
end

# Verify proof of knowledge for received commitment.
# @param [FROST::DKG::Package] package Received package.
# @param [FROST::DKG::SecretPackage] secret_package Verifier's secret package.
# @param [FROST::DKG::Package] received_package Received received_package.
# @return [Boolean]
def verify_proof_of_knowledge(package)
raise ArgumentError, "package must be FROST::DKG::Package." unless package.is_a?(FROST::DKG::Package)
def verify_proof_of_knowledge(secret_package, received_package)
raise ArgumentError, "secret_package must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
raise ArgumentError, "received_package must be FROST::DKG::Package." unless received_package.is_a?(FROST::DKG::Package)
raise FROST::Error, "Invalid number of commitments in package." unless secret_package.min_signers == received_package.commitments.length

verification_key = package.verification_key
msg = FROST.encode_identifier(package.identifier, verification_key.group) +
[verification_key.to_hex + package.proof.r.to_hex].pack("H*")
verification_key = received_package.verification_key
msg = FROST.encode_identifier(received_package.identifier, verification_key.group) +
[verification_key.to_hex + received_package.proof.r.to_hex].pack("H*")
challenge = Hash.hdkg(msg, verification_key.group)
package.proof.r == verification_key.group.generator * package.proof.s + (verification_key * challenge).negate
received_package.proof.r == verification_key.group.generator * received_package.proof.s + (verification_key * challenge).negate
end

# Compute signing share using received shares from other participants
# @param [FROST::Polynomial] polynomial Own polynomial contains own secret.
# @param [FROST::DKG::SecretPackage] secret_package Own secret received_package.
# @param [Array] received_shares Array of FROST::SecretShare received by other participants.
# @return [FROST::SecretShare] Signing share.
def compute_signing_share(polynomial, received_shares)
raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
def compute_signing_share(secret_package, received_shares)
raise ArgumentError, "polynomial must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
raise FROST::Error, "Invalid number of received_shares." unless secret_package.max_signers - 1 == received_shares.length

identifier = received_shares.first.identifier
s_id = received_shares.sum {|share| share.share}
field = ECDSA::PrimeField.new(polynomial.group.order)
field = ECDSA::PrimeField.new(secret_package.group.order)
FROST::SecretShare.new(
identifier, field.mod(s_id + polynomial.gen_share(identifier).share), polynomial.group)
identifier, field.mod(s_id + secret_package.gen_share(identifier).share), secret_package.group)
end

# Compute Group public key.
# @param [FROST::Polynomial] polynomial Own polynomial contains own secret.
# @param [FROST::DKG::SecretPackage] secret_package Own secret received_package.
# @param [Array] received_packages Array of FROST::DKG::Package received by other participants.
# @return [ECDSA::Point] Group public key.
def compute_group_pubkey(polynomial, received_packages)
raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
received_packages.inject(polynomial.verification_point) {|sum, package| sum + package.commitments.first }
def compute_group_pubkey(secret_package, received_packages)
raise ArgumentError, "polynomial must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
raise FROST::Error, "Invalid number of received_packages." unless secret_package.max_signers - 1 == received_packages.length

received_packages.inject(secret_package.verification_point) {|sum, package| sum + package.commitments.first }
end
end
end
53 changes: 53 additions & 0 deletions lib/frost/dkg/secret_package.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module FROST
module DKG
# Package to hold participants' secret share.
class SecretPackage

attr_reader :identifier
attr_reader :polynomial
attr_reader :public_package
attr_reader :min_signers
attr_reader :max_signers

# Constructor.
# @param [Integer] identifier The identifier of this owner.
# @param [Integer] min_signers Minimum number of signers.
# @param [Integer] max_signers Maximum number of signers.
# @param [FROST::Polynomial] polynomial Polynomial with secret share.
def initialize(identifier, min_signers, max_signers, polynomial)
raise ArgumentError, "identifier must be Integer." unless identifier.is_a?(Integer)
raise ArgumentError, "identifier must be greater than 0." if identifier < 1
raise ArgumentError, "min_signers must be Integer." unless min_signers.is_a?(Integer)
raise ArgumentError, "max_signers must be Integer." unless max_signers.is_a?(Integer)
raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
raise ArgumentError, "max_signers must be greater than or equal to min_signers." if max_signers < min_signers
raise ArgumentError, "Number of coefficients of polynomial and min_signers do not match." unless min_signers == polynomial.coefficients.length

@identifier = identifier
@min_signers = min_signers
@max_signers = max_signers
@polynomial = polynomial
@public_package = Package.new(identifier, polynomial.gen_commitments, polynomial.gen_proof_of_knowledge(identifier))
end

# Generate secret share for identifier.
# @param [Integer] identifier
# @return [FROST::SecretShare] Generate share.
def gen_share(identifier)
polynomial.gen_share(identifier)
end

# Get group.
# @return [ECDSA::Group]
def group
polynomial.group
end

# Get verification point.
# @return [ECDSA::Point]
def verification_point
polynomial.verification_point
end
end
end
end
34 changes: 18 additions & 16 deletions spec/frost/dkg_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
# Round1
# generate secret and commitments and proof of knowledge.
received_packages = {}
polynomials = {}
secret_packages = {}
participants.each do |p|
identifier = p['identifier']
coeffs = [p['signing_key'].hex, p['coefficient'].hex]
polynomial = FROST::Polynomial.new(coeffs, group)
polynomials[identifier] = polynomial
secret_packages[identifier] = FROST::DKG::SecretPackage.new(identifier, min_signers, max_signers, polynomial)
commitments = polynomial.gen_commitments
p['vss_commitments'].each.with_index do |commitment, i|
expect(commitments[i].to_hex).to eq(commitment)
Expand All @@ -37,8 +37,9 @@
# Each participant verify knowledge of proof in received package.
participants.each do |p|
identifier = p['identifier']
secret_package = secret_packages[identifier]
received_packages[identifier].each do |package|
expect(described_class.verify_proof_of_knowledge(package)).to be true
expect(described_class.verify_proof_of_knowledge(secret_package, package)).to be true
end
end

Expand All @@ -47,11 +48,11 @@
received_shares = {}
participants.each do |participant|
identifier = participant['identifier']
polynomial = polynomials[identifier]
secret_package = secret_packages[identifier]
1.upto(max_signers).each do |target|
next if identifier == target
received_shares[target] ||= []
received_shares[target] << [identifier, polynomial.gen_share(target)]
received_shares[target] << [identifier, secret_package.gen_share(target)]
end
end

Expand All @@ -78,14 +79,14 @@
secret_shares = {}
participants.each do |participant|
identifier = participant['identifier']
share = described_class.compute_signing_share(polynomials[identifier], received_shares[identifier].map{|_, share| share})
share = described_class.compute_signing_share(secret_packages[identifier], received_shares[identifier].map{|_, share| share})
secret_shares[identifier] = share
expect(share.share).to eq(participant['signing_share'].hex)
expect(share.to_point.to_hex).to eq(participant['verifying_share'])
end

# 1 computes group public key.
group_pubkey = described_class.compute_group_pubkey(polynomials[1], received_packages[1])
group_pubkey = described_class.compute_group_pubkey(secret_packages[1], received_packages[1])
expect(group_pubkey.to_hex).to eq(vectors['inputs']['verifying_key'])
end
end
Expand All @@ -108,14 +109,14 @@
max_signer = 5
min_signer = 3

secrets = {}
secret_packages = {}
round1_outputs = {}
# Round 1:
# For each participant, perform the first part of the DKG protocol.
1.upto(max_signer) do |i|
polynomial, package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
secrets[i] = polynomial
round1_outputs[i] = package
secret_package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
secret_packages[i] = secret_package
round1_outputs[i] = secret_package.public_package
end

# Each participant send their commitments and proof to other participants.
Expand All @@ -126,20 +127,21 @@

# Each participant verify knowledge of proof in received package.
received_package.each do |id, packages|
secret_package = secret_packages[id]
packages.each do |package|
expect(FROST::DKG.verify_proof_of_knowledge(package)).to be true
expect(FROST::DKG.verify_proof_of_knowledge(secret_package, package)).to be true
end
end

# Round 2:
# Each participant generate share for other participants and send it.
received_shares = {}
1.upto(max_signer) do |i|
polynomial = secrets[i] # own secret
secret_package = secret_packages[i] # own secret
1.upto(max_signer) do |o|
next if i == o
received_shares[o] ||= []
received_shares[o] << [i, polynomial.gen_share(o)]
received_shares[o] << [i, secret_package.gen_share(o)]
end
end

Expand All @@ -155,12 +157,12 @@
signing_shares = {}
1.upto(max_signer) do |i|
shares = received_shares[i].map{|_, share| share}
signing_shares[i] = FROST::DKG.compute_signing_share(secrets[i], shares)
signing_shares[i] = FROST::DKG.compute_signing_share(secret_packages[i], shares)
end

# Compute group public key.
compute_pubkeys = 1.upto(max_signer).map do |i|
FROST::DKG.compute_group_pubkey(secrets[i], received_package[i])
FROST::DKG.compute_group_pubkey(secret_packages[i], received_package[i])
end
# All participants calculate the same group pubkey.
expect(compute_pubkeys.uniq.length).to eq(1)
Expand Down

0 comments on commit 75ba001

Please sign in to comment.