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 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