Skip to content

Commit

Permalink
Implement DKG round 1
Browse files Browse the repository at this point in the history
  • Loading branch information
azuchi committed Feb 13, 2024
1 parent 7dfa1af commit 25da50e
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/frost.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Error < StandardError; end
autoload :SecretShare, "frost/secret_share"
autoload :Polynomial, "frost/polynomial"
autoload :SigningKey, "frost/signing_key"
autoload :DKG, "frost/dkg"

module_function

Expand Down
37 changes: 37 additions & 0 deletions lib/frost/dkg.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module FROST
# Distributed Key Generation feature.
module DKG

autoload :Package, "frost/dkg/package"

module_function

# Performs the first part of the 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)
def part1(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, "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))]
end

# Verify proof of knowledge for received commitment.
# @param [FROST::DKG::Package] package Received package.
# @return [Boolean]
def verify_proof_of_knowledge(package)
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*")
challenge = Hash.hdkg(msg, verification_key.group)
package.proof.r == verification_key.group.generator * package.proof.s + (verification_key * challenge).negate
end
end
end
29 changes: 29 additions & 0 deletions lib/frost/dkg/package.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module FROST
module DKG
class Package
attr_reader :identifier
attr_reader :commitments
attr_reader :proof

# Constructor
# @param [Integer] identifier
# @param [Array] commitments The list of commitment.
# @param [FROST::Signature] proof
def initialize(identifier, commitments, proof)
raise ArgumentError, "identifier must be Integer." unless identifier.is_a?(Integer)
raise ArgumentError, "identifier must be greater than 0." if identifier < 1
raise ArgumentError, "proof must be FROST::Signature." unless proof.is_a?(FROST::Signature)

@identifier = identifier
@commitments = commitments
@proof = proof
end

# Get verification key for this proof.
# @return [ECDSA::Point]
def verification_key
commitments.first
end
end
end
end
8 changes: 8 additions & 0 deletions lib/frost/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ def h5(msg, group)
hash(msg, group, "com")
end

# Hash function for a FROST ciphersuite, used for the DKG.
# @param [String] msg The message to be hashed.
# @param [ECDSA::Group] group The elliptic curve group.
# @return [Integer] The hash value.
def hdkg(msg, group)
hash_to_field(msg, group, "dkg")
end

def hash_to_field(msg, group, context)
h2c = case group
when ECDSA::Group::Secp256k1
Expand Down
21 changes: 21 additions & 0 deletions lib/frost/polynomial.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ def gen_share(identifier)
SecretShare.new(identifier, last, group)
end

# Generate coefficient commitments
# @return [Array] A list of coefficient commitment (ECDSA::Point).
def gen_commitments
coefficients.map{|c| group.generator * c }
end

# Generate proof of knowledge for secret.
# @param [Integer] identifier Identifier of the owner of this polynomial.
# @return [FROST::Signature]
def gen_proof_of_knowledge(identifier)
k = SecureRandom.random_number(group.order - 1)
r = group.generator * k
a0 = coefficients.first
a0_g = group.generator * a0
msg = FROST.encode_identifier(identifier, group) + [a0_g.to_hex + r.to_hex].pack("H*")
challenge = Hash.hdkg(msg, group)
field = ECDSA::PrimeField.new(group.order)
s = field.mod(k + a0 * challenge)
FROST::Signature.new(r, s)
end

# Generates the lagrange coefficient for the i'th participant.
# @param [Array] x_coordinates The list of x-coordinates.
# @param [Integer] xi an x-coordinate contained in x_coordinates.
Expand Down
36 changes: 36 additions & 0 deletions spec/frost/dkg_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'spec_helper'

RSpec.describe FROST::DKG do

let(:group) { ECDSA::Group::Secp256k1 }

describe "sign with dkg" do
it do
max_signer = 5
min_signer = 3

secrets = {}
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.part1(i, min_signer, max_signer, group)
secrets[i] = polynomial
round1_outputs[i] = package
end

# Each participant send their commitments and proof to other participants.
received_package = {}
1.upto(max_signer) do |i|
received_package[i] = round1_outputs.select {|k, _| k != i}.values
end

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

end

0 comments on commit 25da50e

Please sign in to comment.