frostrb 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +53 -13
- data/lib/frost/dkg/secret_package.rb +53 -0
- data/lib/frost/dkg.rb +29 -20
- data/lib/frost/polynomial.rb +15 -3
- data/lib/frost/repairable.rb +56 -0
- data/lib/frost/version.rb +1 -1
- data/lib/frost.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3824b64152fafddb21b2189891156852ec6a3b079274d81cbe6e0d5732ee848
|
4
|
+
data.tar.gz: 8e28eeb4c71d9b4d9636e922fd71772c86b98819c4ed2f4ea7416dc085961a7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26b6c5dbfa59991e7ab0f6bddb11ca1dd4219cdd5c6101fb62d106fd7d982c5afb2107149332cba55db3448afce3286b4807a32415f2bcc8b63b90e00871e298
|
7
|
+
data.tar.gz: b80e53aaaff5733bf0b5f1091f2a2a7c5903fbb008551e8924aa285b61e2e8044a2f594a481f5cfca2aefe5fd72de101b6ae9e4533e7b623a6c30687052a652f
|
data/README.md
CHANGED
@@ -80,26 +80,27 @@ DKG can be run as below.
|
|
80
80
|
max_signer = 5
|
81
81
|
min_signer = 3
|
82
82
|
|
83
|
-
|
83
|
+
secret_packages = {}
|
84
84
|
round1_outputs = {}
|
85
85
|
# Round 1:
|
86
86
|
# For each participant, perform the first part of the DKG protocol.
|
87
87
|
1.upto(max_signer) do |i|
|
88
|
-
|
89
|
-
|
90
|
-
round1_outputs[i] =
|
88
|
+
secret_package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
|
89
|
+
secret_packages[i] = secret_package
|
90
|
+
round1_outputs[i] = secret_package.public_package
|
91
91
|
end
|
92
92
|
|
93
93
|
# Each participant sends their commitments and proof to other participants.
|
94
94
|
received_package = {}
|
95
95
|
1.upto(max_signer) do |i|
|
96
|
-
received_package[i] = round1_outputs.select {|k, _| k != i}.values
|
96
|
+
received_package[i] = round1_outputs.select { |k, _| k != i }.values
|
97
97
|
end
|
98
98
|
|
99
99
|
# Each participant verify knowledge of proof in received package.
|
100
100
|
received_package.each do |id, packages|
|
101
|
+
secret_package = secret_packages[id]
|
101
102
|
packages.each do |package|
|
102
|
-
expect(FROST::DKG.verify_proof_of_knowledge(package)).to be true
|
103
|
+
expect(FROST::DKG.verify_proof_of_knowledge(secret_package, package)).to be true
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
@@ -107,18 +108,18 @@ end
|
|
107
108
|
# Each participant generate share for other participants and send it.
|
108
109
|
received_shares = {}
|
109
110
|
1.upto(max_signer) do |i|
|
110
|
-
|
111
|
+
secret_package = secret_packages[i] # own secret
|
111
112
|
1.upto(max_signer) do |o|
|
112
113
|
next if i == o
|
113
114
|
received_shares[o] ||= []
|
114
|
-
received_shares[o] << [i,
|
115
|
+
received_shares[o] << [i, secret_package.gen_share(o)]
|
115
116
|
end
|
116
117
|
end
|
117
118
|
|
118
119
|
# Each participant verify received shares.
|
119
120
|
1.upto(max_signer) do |i|
|
120
121
|
received_shares[i].each do |send_by, share|
|
121
|
-
target_package = received_package[i].find{ |package| package.identifier == send_by }
|
122
|
+
target_package = received_package[i].find { |package| package.identifier == send_by }
|
122
123
|
expect(target_package.verify_share(share)).to be true
|
123
124
|
end
|
124
125
|
end
|
@@ -126,12 +127,51 @@ end
|
|
126
127
|
# Each participant compute signing share.
|
127
128
|
signing_shares = {}
|
128
129
|
1.upto(max_signer) do |i|
|
129
|
-
shares = received_shares[i].map{|_, share| share}
|
130
|
-
signing_shares[i] = FROST::DKG.compute_signing_share(
|
130
|
+
shares = received_shares[i].map { |_, share| share }
|
131
|
+
signing_shares[i] = FROST::DKG.compute_signing_share(secret_packages[i], shares)
|
131
132
|
end
|
132
133
|
|
133
134
|
# Participant 1 compute group public key.
|
134
|
-
group_pubkey = FROST::DKG.compute_group_pubkey(
|
135
|
+
group_pubkey = FROST::DKG.compute_group_pubkey(secret_packages[1], received_package[1])
|
135
136
|
|
136
137
|
# The subsequent signing phase is the same as above with signing_shares as the secret.
|
137
|
-
```
|
138
|
+
```
|
139
|
+
|
140
|
+
### Share repair
|
141
|
+
|
142
|
+
Using `FROST::Repairable` module, you can repair existing (or new) participant's share with the cooperation of T participants.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Dealer generate shares.
|
146
|
+
FROST::SigningKey.generate(ECDSA::Group::Secp256k1)
|
147
|
+
polynomial = dealer.gen_poly(min_signers - 1)
|
148
|
+
shares = 1.upto(max_signers).map {|identifier| polynomial.gen_share(identifier) }
|
149
|
+
|
150
|
+
# Signer 2 will lose their share
|
151
|
+
# Signers (helpers) 1, 4 and 5 will help signer 2 (participant) to recover their share
|
152
|
+
helper1 = shares[0]
|
153
|
+
helper4 = shares[3]
|
154
|
+
helper5 = shares[4]
|
155
|
+
helper_shares = [helper1, helper4, helper5]
|
156
|
+
helpers = helper_shares.map(&:identifier)
|
157
|
+
participant_share = shares[1]
|
158
|
+
|
159
|
+
# Each helper computes delta values.
|
160
|
+
received_values = {}
|
161
|
+
helper_shares.each do |helper_share|
|
162
|
+
delta_values = FROST::Repairable.step1(helpers, participant_share.identifier, helper_share)
|
163
|
+
delta_values.each do |target_id, value|
|
164
|
+
received_values[target_id] ||= []
|
165
|
+
received_values[target_id] << value
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Each helper send sum value to participant.
|
170
|
+
participant_received_values = []
|
171
|
+
received_values.each do |_, values|
|
172
|
+
participant_received_values << FROST::Repairable.step2(values, ECDSA::Group::Secp256k1)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Participant can obtain his share.
|
176
|
+
repair_share = FROST::Repairable.step3(2, participant_received_values, ECDSA::Group::Secp256k1)
|
177
|
+
```
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module FROST
|
2
|
+
module DKG
|
3
|
+
# Package to hold participants' secret share.
|
4
|
+
class SecretPackage
|
5
|
+
|
6
|
+
attr_reader :identifier
|
7
|
+
attr_reader :polynomial
|
8
|
+
attr_reader :public_package
|
9
|
+
attr_reader :min_signers
|
10
|
+
attr_reader :max_signers
|
11
|
+
|
12
|
+
# Constructor.
|
13
|
+
# @param [Integer] identifier The identifier of this owner.
|
14
|
+
# @param [Integer] min_signers Minimum number of signers.
|
15
|
+
# @param [Integer] max_signers Maximum number of signers.
|
16
|
+
# @param [FROST::Polynomial] polynomial Polynomial with secret share.
|
17
|
+
def initialize(identifier, min_signers, max_signers, polynomial)
|
18
|
+
raise ArgumentError, "identifier must be Integer." unless identifier.is_a?(Integer)
|
19
|
+
raise ArgumentError, "identifier must be greater than 0." if identifier < 1
|
20
|
+
raise ArgumentError, "min_signers must be Integer." unless min_signers.is_a?(Integer)
|
21
|
+
raise ArgumentError, "max_signers must be Integer." unless max_signers.is_a?(Integer)
|
22
|
+
raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
|
23
|
+
raise ArgumentError, "max_signers must be greater than or equal to min_signers." if max_signers < min_signers
|
24
|
+
raise ArgumentError, "Number of coefficients of polynomial and min_signers do not match." unless min_signers == polynomial.coefficients.length
|
25
|
+
|
26
|
+
@identifier = identifier
|
27
|
+
@min_signers = min_signers
|
28
|
+
@max_signers = max_signers
|
29
|
+
@polynomial = polynomial
|
30
|
+
@public_package = Package.new(identifier, polynomial.gen_commitments, polynomial.gen_proof_of_knowledge(identifier))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generate secret share for identifier.
|
34
|
+
# @param [Integer] identifier
|
35
|
+
# @return [FROST::SecretShare] Generate share.
|
36
|
+
def gen_share(identifier)
|
37
|
+
polynomial.gen_share(identifier)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get group.
|
41
|
+
# @return [ECDSA::Group]
|
42
|
+
def group
|
43
|
+
polynomial.group
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get verification point.
|
47
|
+
# @return [ECDSA::Point]
|
48
|
+
def verification_point
|
49
|
+
polynomial.verification_point
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/frost/dkg.rb
CHANGED
@@ -2,6 +2,7 @@ module FROST
|
|
2
2
|
# Distributed Key Generation feature.
|
3
3
|
module DKG
|
4
4
|
|
5
|
+
autoload :SecretPackage, "frost/dkg/secret_package"
|
5
6
|
autoload :Package, "frost/dkg/package"
|
6
7
|
|
7
8
|
module_function
|
@@ -10,17 +11,18 @@ module FROST
|
|
10
11
|
# Participant generate key and commitments, proof of knowledge for secret.
|
11
12
|
# @param [Integer] identifier
|
12
13
|
# @param [ECDSA::Group] group Group of elliptic curve.
|
13
|
-
# @return [
|
14
|
-
def
|
14
|
+
# @return [FROST::DKG::SecretPackage] Secret received_package for owner.
|
15
|
+
def generate_secret(identifier, min_signers, max_signers, group)
|
15
16
|
raise ArgumentError, "identifier must be Integer" unless identifier.is_a?(Integer)
|
16
17
|
raise ArgumentError, "identifier must be greater than 0." if identifier < 1
|
17
18
|
raise ArgumentError, "group must be ECDSA::Group." unless group.is_a?(ECDSA::Group)
|
19
|
+
raise ArgumentError, "min_signers must be Integer." unless min_signers.is_a?(Integer)
|
20
|
+
raise ArgumentError, "max_singers must be Integer." unless max_signers.is_a?(Integer)
|
18
21
|
raise ArgumentError, "max_signers must be greater than or equal to min_signers." if max_signers < min_signers
|
19
22
|
|
20
23
|
secret = FROST::SigningKey.generate(group)
|
21
|
-
# Every participant P_i samples t random values (a_{i0}, ..., a_{i(t−1)}) ← Z_q
|
22
24
|
polynomial = secret.gen_poly(min_signers - 1)
|
23
|
-
|
25
|
+
SecretPackage.new(identifier, min_signers, max_signers, polynomial)
|
24
26
|
end
|
25
27
|
|
26
28
|
# Generate proof of knowledge for secret.
|
@@ -43,38 +45,45 @@ module FROST
|
|
43
45
|
end
|
44
46
|
|
45
47
|
# Verify proof of knowledge for received commitment.
|
46
|
-
# @param [FROST::DKG::
|
48
|
+
# @param [FROST::DKG::SecretPackage] secret_package Verifier's secret package.
|
49
|
+
# @param [FROST::DKG::Package] received_package Received received_package.
|
47
50
|
# @return [Boolean]
|
48
|
-
def verify_proof_of_knowledge(
|
49
|
-
raise ArgumentError, "
|
51
|
+
def verify_proof_of_knowledge(secret_package, received_package)
|
52
|
+
raise ArgumentError, "secret_package must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
|
53
|
+
raise ArgumentError, "received_package must be FROST::DKG::Package." unless received_package.is_a?(FROST::DKG::Package)
|
54
|
+
raise FROST::Error, "Invalid number of commitments in package." unless secret_package.min_signers == received_package.commitments.length
|
50
55
|
|
51
|
-
verification_key =
|
52
|
-
msg = FROST.encode_identifier(
|
53
|
-
[verification_key.to_hex +
|
56
|
+
verification_key = received_package.verification_key
|
57
|
+
msg = FROST.encode_identifier(received_package.identifier, verification_key.group) +
|
58
|
+
[verification_key.to_hex + received_package.proof.r.to_hex].pack("H*")
|
54
59
|
challenge = Hash.hdkg(msg, verification_key.group)
|
55
|
-
|
60
|
+
received_package.proof.r == verification_key.group.generator * received_package.proof.s + (verification_key * challenge).negate
|
56
61
|
end
|
57
62
|
|
58
63
|
# Compute signing share using received shares from other participants
|
59
|
-
# @param [FROST::
|
64
|
+
# @param [FROST::DKG::SecretPackage] secret_package Own secret received_package.
|
60
65
|
# @param [Array] received_shares Array of FROST::SecretShare received by other participants.
|
61
66
|
# @return [FROST::SecretShare] Signing share.
|
62
|
-
def compute_signing_share(
|
63
|
-
raise ArgumentError, "polynomial must be FROST::
|
67
|
+
def compute_signing_share(secret_package, received_shares)
|
68
|
+
raise ArgumentError, "polynomial must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
|
69
|
+
raise FROST::Error, "Invalid number of received_shares." unless secret_package.max_signers - 1 == received_shares.length
|
70
|
+
|
64
71
|
identifier = received_shares.first.identifier
|
65
72
|
s_id = received_shares.sum {|share| share.share}
|
66
|
-
field = ECDSA::PrimeField.new(
|
73
|
+
field = ECDSA::PrimeField.new(secret_package.group.order)
|
67
74
|
FROST::SecretShare.new(
|
68
|
-
identifier, field.mod(s_id +
|
75
|
+
identifier, field.mod(s_id + secret_package.gen_share(identifier).share), secret_package.group)
|
69
76
|
end
|
70
77
|
|
71
78
|
# Compute Group public key.
|
72
|
-
# @param [FROST::
|
79
|
+
# @param [FROST::DKG::SecretPackage] secret_package Own secret received_package.
|
73
80
|
# @param [Array] received_packages Array of FROST::DKG::Package received by other participants.
|
74
81
|
# @return [ECDSA::Point] Group public key.
|
75
|
-
def compute_group_pubkey(
|
76
|
-
raise ArgumentError, "polynomial must be FROST::
|
77
|
-
|
82
|
+
def compute_group_pubkey(secret_package, received_packages)
|
83
|
+
raise ArgumentError, "polynomial must be FROST::DKG::SecretPackage." unless secret_package.is_a?(FROST::DKG::SecretPackage)
|
84
|
+
raise FROST::Error, "Invalid number of received_packages." unless secret_package.max_signers - 1 == received_packages.length
|
85
|
+
|
86
|
+
received_packages.inject(secret_package.verification_point) {|sum, package| sum + package.commitments.first }
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
data/lib/frost/polynomial.rb
CHANGED
@@ -75,22 +75,34 @@ module FROST
|
|
75
75
|
end
|
76
76
|
|
77
77
|
# Generates the lagrange coefficient for the i'th participant.
|
78
|
+
# The Lagrange polynomial for a set of points (xj, yj) for 0 <= j <= k is
|
79
|
+
# ∑_{i=0}^k yi.ℓi(x), where ℓi(x) is the Lagrange basis polynomial:
|
80
|
+
# ℓi(x) = ∏_{0≤j≤k; j≠i} (x - xj) / (xi - xj).
|
81
|
+
# This computes ℓj(x) for the set of points `xs` and for the j corresponding to the given xj.
|
78
82
|
# @param [Array] x_coordinates The list of x-coordinates.
|
79
83
|
# @param [Integer] xi an x-coordinate contained in x_coordinates.
|
80
84
|
# @param [ECDSA::Group] group Elliptic curve group.
|
85
|
+
# @param [Integer] x (Optional) if x is nil, it uses 0 for it (since Identifiers can't be 0).
|
81
86
|
# @return [Integer] The lagrange coefficient.
|
82
|
-
def self.derive_interpolating_value(x_coordinates, xi, group)
|
87
|
+
def self.derive_interpolating_value(x_coordinates, xi, group, x: nil)
|
83
88
|
raise ArgumentError, "xi is not included in x_coordinates." unless x_coordinates.include?(xi)
|
84
89
|
raise ArgumentError, "Duplicate values in x_coordinates." if (x_coordinates.length - x_coordinates.uniq.length) > 0
|
85
90
|
raise ArgumentError, "group must be ECDSA::Group." unless group.is_a?(ECDSA::Group)
|
91
|
+
raise ArgumentError, "x must be Integer." if x && !x.is_a?(Integer)
|
86
92
|
|
87
93
|
field = ECDSA::PrimeField.new(group.order)
|
88
94
|
numerator = 1
|
89
95
|
denominator = 1
|
96
|
+
|
90
97
|
x_coordinates.each do |xj|
|
91
98
|
next if xi == xj
|
92
|
-
|
93
|
-
|
99
|
+
if x
|
100
|
+
numerator *= (x - xj)
|
101
|
+
denominator *= (xi - xj)
|
102
|
+
else
|
103
|
+
numerator *= xj
|
104
|
+
denominator *= (xj - xi)
|
105
|
+
end
|
94
106
|
end
|
95
107
|
|
96
108
|
field.mod(numerator * field.inverse(denominator))
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module FROST
|
2
|
+
# Implements the Repairable Threshold Scheme (RTS) from <https://eprint.iacr.org/2017/1155>
|
3
|
+
module Repairable
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Step 1 for RTS.
|
7
|
+
# Each helper computes delta_i,j for other helpers.
|
8
|
+
# @param [Array] helpers Array of helper's identifier.
|
9
|
+
# @param [Integer] participant Identifier of the participant whose shares you want to restore.
|
10
|
+
# @param [FROST::SecretShare] share Share of participant running this process.
|
11
|
+
# @return [Hash] Hash with helper ID as key and value as delta value.
|
12
|
+
def step1(helpers, participant, share)
|
13
|
+
raise ArgumentError, "helpers must be greater than 1." if helpers.length < 2
|
14
|
+
raise ArgumentError, "participant must be greater than 1." if participant < 1
|
15
|
+
raise ArgumentError, "helpers has duplicate identifier." unless helpers.uniq.length == helpers.length
|
16
|
+
raise ArgumentError, "helpers contains same identifier with participant." if helpers.include?(participant)
|
17
|
+
|
18
|
+
field = ECDSA::PrimeField.new(share.group.order)
|
19
|
+
random_values = (helpers.length - 1).times.map { SecureRandom.random_number(share.group.order - 1) }
|
20
|
+
|
21
|
+
# compute last random value
|
22
|
+
## Calculate Lagrange Coefficient for helper_i
|
23
|
+
zeta_i = Polynomial.derive_interpolating_value(helpers, share.identifier, share.group, x: participant)
|
24
|
+
lhs = field.mod(zeta_i * share.share)
|
25
|
+
# last random value
|
26
|
+
last = field.mod(lhs - random_values.sum)
|
27
|
+
random_values << last
|
28
|
+
|
29
|
+
helpers.zip(random_values).to_h
|
30
|
+
end
|
31
|
+
|
32
|
+
# Step 2 for RTS.
|
33
|
+
# Each helper sum received delta values from other helpers.
|
34
|
+
# @param [Array] step1_values Array of delta values.
|
35
|
+
# @param [ECDSA::Group] group
|
36
|
+
# @return [Integer] Sum of delta values.
|
37
|
+
def step2(step1_values, group)
|
38
|
+
raise ArgumentError, "group must be ECDSA::Group" unless group.is_a?(ECDSA::Group)
|
39
|
+
|
40
|
+
field = ECDSA::PrimeField.new(group.order)
|
41
|
+
field.mod(step1_values.sum)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Participant compute own share with received sum of delta value.
|
45
|
+
# @param [Integer] identifier Identifier of the participant whose shares you want to restore.
|
46
|
+
# @param [Array] step2_results Array of Step 2 results received from other helpers.
|
47
|
+
# @param [ECDSA::Group] group
|
48
|
+
# @return
|
49
|
+
def step3(identifier, step2_results, group)
|
50
|
+
raise ArgumentError, "group must be ECDSA::Group" unless group.is_a?(ECDSA::Group)
|
51
|
+
|
52
|
+
field = ECDSA::PrimeField.new(group.order)
|
53
|
+
FROST::SecretShare.new(identifier, field.mod(step2_results.sum), group)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/frost/version.rb
CHANGED
data/lib/frost.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frostrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa_ext
|
@@ -61,9 +61,11 @@ files:
|
|
61
61
|
- lib/frost/commitments.rb
|
62
62
|
- lib/frost/dkg.rb
|
63
63
|
- lib/frost/dkg/package.rb
|
64
|
+
- lib/frost/dkg/secret_package.rb
|
64
65
|
- lib/frost/hash.rb
|
65
66
|
- lib/frost/nonce.rb
|
66
67
|
- lib/frost/polynomial.rb
|
68
|
+
- lib/frost/repairable.rb
|
67
69
|
- lib/frost/secret_share.rb
|
68
70
|
- lib/frost/signature.rb
|
69
71
|
- lib/frost/signing_key.rb
|