FROST for Ruby Build Status

This library is ruby implementations of 'Two-Round Threshold Schnorr Signatures with FROST'.

Note: This library has not been security audited and tested widely, so should not be used in production.

The cipher suites currently supported by this library are:

Installation

Add this line to your application's Gemfile:

gem 'frostrb', require: 'frost'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install frostrb

Usage

require 'frost'

group = ECDSA::Group::Secp256k1

# Dealer generate secret.
secret = FROST::SigningKey.generate(group)
group_pubkey = secret.to_point

# Generate polynomial(f(x) = ax + b)
polynomial = secret.gen_poly(1)

# Calculate secret shares.
share1 = polynomial.gen_share(1)
share2 = polynomial.gen_share(2)
share3 = polynomial.gen_share(3)

# Round 1: Generate nonce and commitment
## each party generate hiding and binding nonce.
hiding_nonce1 = FROST::Nonce.gen_from_secret(share1)
binding_nonce1 = FROST::Nonce.gen_from_secret(share1)
hiding_nonce3 = FROST::Nonce.gen_from_secret(share3)
binding_nonce3 = FROST::Nonce.gen_from_secret(share3)

comm1 = FROST::Commitments.new(1, hiding_nonce1.to_point, binding_nonce1.to_point)
comm3 = FROST::Commitments.new(3, hiding_nonce3.to_point, binding_nonce3.to_point)
commitment_list = [comm1, comm3]

msg = ["74657374"].pack("H*")

# Round 2: each participant generates their signature share(1 and 3)
sig_share1 = FROST.sign(share1, group_pubkey, [hiding_nonce1, binding_nonce1], msg, commitment_list)
sig_share3 = FROST.sign(share3, group_pubkey, [hiding_nonce3, binding_nonce3], msg, commitment_list)

# verify signature share
FROST.verify_share(1, share1.to_point, sig_share1, commitment_list, group_pubkey, msg)
FROST.verify_share(3, share3.to_point, sig_share3, commitment_list, group_pubkey, msg)

# Aggregation
sig = FROST.aggregate(commitment_list, msg, group_pubkey, [sig_share1, sig_share3])

# verify final signature
FROST.verify(sig, group_pubkey, msg)

Using DKG

DKG can be run as below.

max_signer = 5
min_signer = 3

secret_packages = {}
round1_outputs = {}
# Round 1:
# For each participant, perform the first part of the DKG protocol.
1.upto(max_signer) do |i|
  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.
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|
  secret_package = secret_packages[id]
  packages.each do |package|
    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|
  secret_package = secret_packages[i] # own secret
  1.upto(max_signer) do |o|
    next if i == o
    received_shares[o] ||= []
    received_shares[o] << [i, secret_package.gen_share(o)]
  end
end

# Each participant verify received shares.
1.upto(max_signer) do |i|
  received_shares[i].each do |send_by, share|
    target_package = received_package[i].find { |package| package.identifier == send_by }
    expect(target_package.verify_share(share)).to be true
  end
end

# Each participant compute signing share.
signing_shares = {}
1.upto(max_signer) do |i|
  shares = received_shares[i].map { |_, share| share }
  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(secret_packages[1], received_package[1])

# The subsequent signing phase is the same as above with signing_shares as the secret.

Share repair

Using FROST::Repairable module, you can repair existing (or new) participant's share with the cooperation of T participants.

# Dealer generate shares.
FROST::SigningKey.generate(ECDSA::Group::Secp256k1)
polynomial = dealer.gen_poly(min_signers - 1)
shares = 1.upto(max_signers).map {|identifier| polynomial.gen_share(identifier) }

# Signer 2 will lose their share
# Signers (helpers) 1, 4 and 5 will help signer 2 (participant) to recover their share
helper1 = shares[0]
helper4 = shares[3]
helper5 = shares[4]
helper_shares = [helper1, helper4, helper5]
helpers = helper_shares.map(&:identifier)
participant_share = shares[1]

# Each helper computes delta values.
received_values = {}
helper_shares.each do |helper_share|
  delta_values = FROST::Repairable.step1(helpers, participant_share.identifier, helper_share)
  delta_values.each do |target_id, value|
    received_values[target_id] ||= []
    received_values[target_id] << value
  end
end

# Each helper send sum value to participant.
participant_received_values = []
received_values.each do |_, values|
  participant_received_values << FROST::Repairable.step2(values, ECDSA::Group::Secp256k1)
end

# Participant can obtain his share.
repair_share = FROST::Repairable.step3(2, participant_received_values, ECDSA::Group::Secp256k1)