frostrb 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09af4e893de9e1260635431b1feef84a9eb6273534f0f9e7e2e4bcde82d8788e'
4
- data.tar.gz: 66f39e7bff887c34c65844ac42b94d8e77cdd7fb86caedb575d8ae0a7c0b12a1
3
+ metadata.gz: 0660ec6fbd998b152bdb359276284fd64e00d89ed19e783dbd72e04cac3fad67
4
+ data.tar.gz: c01bd20b7f10d10e0362d6ee4c3722c8849f748ed8899c06efd2cfcaee2dcefa
5
5
  SHA512:
6
- metadata.gz: ba8c33e24ace6174176ec30bb5afaf0222e6c5046df34960ca28c8772286c236611bdb119c3184448fc308f491d1408e57a7fa1c715e3a7e0036e557a6788c46
7
- data.tar.gz: b738d2fe05fa151032e9ff877758f580d6d96153ce179b384a95d6e353ce3e5003638622afbdfd2a4c219825d45f77e78f3e5238484b4cb34de5f8e8de8b768a
6
+ metadata.gz: 5cadf1d9b254ac672aad430dae0c27ec7f45878886bbf600cd250a8c4a405f5d89201de96b5bd145ff3fc59ae2384d227a5fe7e73c3abcf002b36a6fbd2d5098
7
+ data.tar.gz: 71c14b38913bf8471436979fb8d69a99ad7ad763a79d28f1f738435999ad5b485ba7e09d5e43083bbd2edec5ade02c189076e53678c44a4512a71c86e0883a42
data/README.md CHANGED
@@ -85,7 +85,7 @@ 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)
88
+ polynomial, package = FROST::DKG.generate_secret(i, min_signer, max_signer, group)
89
89
  secrets[i] = polynomial
90
90
  round1_outputs[i] = package
91
91
  end
@@ -93,7 +93,7 @@ end
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.
@@ -118,7 +118,7 @@ end
118
118
  # Each participant verify received shares.
119
119
  1.upto(max_signer) do |i|
120
120
  received_shares[i].each do |send_by, share|
121
- target_package = received_package[i].find{ |package| package.identifier == send_by }
121
+ target_package = received_package[i].find { |package| package.identifier == send_by }
122
122
  expect(target_package.verify_share(share)).to be true
123
123
  end
124
124
  end
@@ -126,7 +126,7 @@ end
126
126
  # Each participant compute signing share.
127
127
  signing_shares = {}
128
128
  1.upto(max_signer) do |i|
129
- shares = received_shares[i].map{|_, share| share}
129
+ shares = received_shares[i].map { |_, share| share }
130
130
  signing_shares[i] = FROST::DKG.compute_signing_share(secrets[i], shares)
131
131
  end
132
132
 
@@ -134,4 +134,43 @@ end
134
134
  group_pubkey = FROST::DKG.compute_group_pubkey(secrets[1], received_package[1])
135
135
 
136
136
  # The subsequent signing phase is the same as above with signing_shares as the secret.
137
- ```
137
+ ```
138
+
139
+ ### Share repair
140
+
141
+ Using `FROST::Repairable` module, you can repair existing (or new) participant's share with the cooperation of T participants.
142
+
143
+ ```ruby
144
+ # Dealer generate shares.
145
+ FROST::SigningKey.generate(ECDSA::Group::Secp256k1)
146
+ polynomial = dealer.gen_poly(min_signers - 1)
147
+ shares = 1.upto(max_signers).map {|identifier| polynomial.gen_share(identifier) }
148
+
149
+ # Signer 2 will lose their share
150
+ # Signers (helpers) 1, 4 and 5 will help signer 2 (participant) to recover their share
151
+ helper1 = shares[0]
152
+ helper4 = shares[3]
153
+ helper5 = shares[4]
154
+ helper_shares = [helper1, helper4, helper5]
155
+ helpers = helper_shares.map(&:identifier)
156
+ participant_share = shares[1]
157
+
158
+ # Each helper computes delta values.
159
+ received_values = {}
160
+ helper_shares.each do |helper_share|
161
+ delta_values = FROST::Repairable.step1(helpers, participant_share.identifier, helper_share)
162
+ delta_values.each do |target_id, value|
163
+ received_values[target_id] ||= []
164
+ received_values[target_id] << value
165
+ end
166
+ end
167
+
168
+ # Each helper send sum value to participant.
169
+ participant_received_values = []
170
+ received_values.each do |_, values|
171
+ participant_received_values << FROST::Repairable.step2(values, ECDSA::Group::Secp256k1)
172
+ end
173
+
174
+ # Participant can obtain his share.
175
+ repair_share = FROST::Repairable.step3(2, participant_received_values, ECDSA::Group::Secp256k1)
176
+ ```
data/lib/frost/dkg.rb CHANGED
@@ -11,7 +11,7 @@ module FROST
11
11
  # @param [Integer] identifier
12
12
  # @param [ECDSA::Group] group Group of elliptic curve.
13
13
  # @return [Array] The triple of polynomial and public package(FROST::DKG::Package)
14
- def part1(identifier, min_signers, max_signers, group)
14
+ def generate_secret(identifier, min_signers, max_signers, group)
15
15
  raise ArgumentError, "identifier must be Integer" unless identifier.is_a?(Integer)
16
16
  raise ArgumentError, "identifier must be greater than 0." if identifier < 1
17
17
  raise ArgumentError, "group must be ECDSA::Group." unless group.is_a?(ECDSA::Group)
@@ -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.2.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.2.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-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa_ext
@@ -64,6 +64,7 @@ files:
64
64
  - lib/frost/hash.rb
65
65
  - lib/frost/nonce.rb
66
66
  - lib/frost/polynomial.rb
67
+ - lib/frost/repairable.rb
67
68
  - lib/frost/secret_share.rb
68
69
  - lib/frost/signature.rb
69
70
  - lib/frost/signing_key.rb