frostrb 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09af4e893de9e1260635431b1feef84a9eb6273534f0f9e7e2e4bcde82d8788e'
4
- data.tar.gz: 66f39e7bff887c34c65844ac42b94d8e77cdd7fb86caedb575d8ae0a7c0b12a1
3
+ metadata.gz: e3824b64152fafddb21b2189891156852ec6a3b079274d81cbe6e0d5732ee848
4
+ data.tar.gz: 8e28eeb4c71d9b4d9636e922fd71772c86b98819c4ed2f4ea7416dc085961a7b
5
5
  SHA512:
6
- metadata.gz: ba8c33e24ace6174176ec30bb5afaf0222e6c5046df34960ca28c8772286c236611bdb119c3184448fc308f491d1408e57a7fa1c715e3a7e0036e557a6788c46
7
- data.tar.gz: b738d2fe05fa151032e9ff877758f580d6d96153ce179b384a95d6e353ce3e5003638622afbdfd2a4c219825d45f77e78f3e5238484b4cb34de5f8e8de8b768a
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
- secrets = {}
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
- polynomial, package = FROST::DKG.part1(i, min_signer, max_signer, group)
89
- secrets[i] = polynomial
90
- round1_outputs[i] = package
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
- polynomial = secrets[i] # own secret
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, polynomial.gen_share(o)]
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(secrets[i], shares)
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(secrets[1], received_package[1])
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 [Array] The triple of polynomial and public package(FROST::DKG::Package)
14
- def part1(identifier, min_signers, max_signers, group)
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
- [polynomial, Package.new(identifier, polynomial.gen_commitments, polynomial.gen_proof_of_knowledge(identifier))]
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::Package] package Received package.
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(package)
49
- raise ArgumentError, "package must be FROST::DKG::Package." unless package.is_a?(FROST::DKG::Package)
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 = package.verification_key
52
- msg = FROST.encode_identifier(package.identifier, verification_key.group) +
53
- [verification_key.to_hex + package.proof.r.to_hex].pack("H*")
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
- package.proof.r == verification_key.group.generator * package.proof.s + (verification_key * challenge).negate
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::Polynomial] polynomial Own polynomial contains own secret.
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(polynomial, received_shares)
63
- raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
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(polynomial.group.order)
73
+ field = ECDSA::PrimeField.new(secret_package.group.order)
67
74
  FROST::SecretShare.new(
68
- identifier, field.mod(s_id + polynomial.gen_share(identifier).share), polynomial.group)
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::Polynomial] polynomial Own polynomial contains own secret.
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(polynomial, received_packages)
76
- raise ArgumentError, "polynomial must be FROST::Polynomial." unless polynomial.is_a?(FROST::Polynomial)
77
- received_packages.inject(polynomial.verification_point) {|sum, package| sum + package.commitments.first }
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
@@ -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
- numerator *= xj
93
- denominator *= (xj - xi)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FROST
4
- VERSION = "0.1.1"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/frost.rb CHANGED
@@ -17,6 +17,7 @@ module FROST
17
17
  autoload :Polynomial, "frost/polynomial"
18
18
  autoload :SigningKey, "frost/signing_key"
19
19
  autoload :DKG, "frost/dkg"
20
+ autoload :Repairable, "frost/repairable"
20
21
 
21
22
  module_function
22
23
 
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.1.1
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-16 00:00:00.000000000 Z
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