Skip to content

Commit ba95a38

Browse files
committed
Adding verification of signature shares.
1 parent 24eb204 commit ba95a38

7 files changed

+177
-53
lines changed

tss/rsa/README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This is an implementation of ["Practical Threshold Signatures" by Victor Shoup](https://www.iacr.org/archive/eurocrypt2000/1807/18070209-new.pdf).
44
Protocol 1 is implemented.
55

6-
## Threshold Primer
6+
## Threshold Cryptography Primer
77

88
Let *l* be the total number of players, *t* be the number of corrupted players, and *k* be the threshold.
99
The idea of threshold signatures is that at least *k* players need to participate to form a valid signature.
@@ -13,7 +13,9 @@ Setup consists of a dealer generating *l* key shares from a key pair and "dealin
1313
During the signing phase, at least *k* players use their key share and the message to generate a signature share.
1414
Finally, the *k* signature shares are combined to form a valid signature for the message.
1515

16-
## Modifications
16+
## Robustness
1717

18-
1. Our implementation is not robust. That is, the corrupted players can prevent a valid signature from being formed by the non-corrupted players. As such, we remove all verification.
19-
2. The paper requires p and q to be safe primes. We do not.
18+
The scheme requires p and q to be safe primes to provide robustness. That is, it is possible to validate (and reject) signature shares produced by malicious signers. RSA keys generated by the Go standard library are not guaranteed to be safe primes. In this case, the functions produces signature shares but they are not verifiable.
19+
To provide verifiability, use the GenerateKey function in this package, which produces a key pair composed of safe primes.
20+
21+
The Deal function opportunistically checks whether the RSA key is composed of safe primes, if so, the signature shares produced are verifiable.

tss/rsa/keyshare.go

+68-26
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,35 @@ import (
1010
"math"
1111
"math/big"
1212
"sync"
13+
14+
"github.com/cloudflare/circl/zk/qndleq"
1315
)
1416

17+
// VerifyKeys contains keys used to verify whether a signature share
18+
// was computed using the signer's key share.
19+
type VerifyKeys struct {
20+
// This key is common to the group of signers.
21+
GroupKey *big.Int
22+
// This key is the (public) key associated with the (private) key share.
23+
VerifyKey *big.Int
24+
}
25+
1526
// KeyShare represents a portion of the key. It can only be used to generate SignShare's. During the dealing phase (when Deal is called), one KeyShare is generated per player.
1627
type KeyShare struct {
1728
si *big.Int
1829

19-
twoDeltaSi *big.Int // optional cached value, this value is used to marginally speed up SignShare generation in Sign. If nil, it will be generated when needed and then cached.
30+
twoDeltaSi *big.Int // this value is used to marginally speed up SignShare generation in Sign.
2031
Index uint // When KeyShare's are generated they are each assigned an index sequentially
2132

2233
Players uint
2334
Threshold uint
35+
36+
// It stores keys to produce verifiable signature shares.
37+
// If it's nil, signature shares are still produced but
38+
// they are not verifiable.
39+
// This field is present only if the RSA private key is
40+
// composed of two safe primes.
41+
vk *VerifyKeys
2442
}
2543

2644
func (kshare KeyShare) String() string {
@@ -51,11 +69,7 @@ func (kshare *KeyShare) MarshalBinary() ([]byte, error) {
5169
threshold := uint16(kshare.Threshold)
5270
index := uint16(kshare.Index)
5371

54-
twoDeltaSiBytes := []byte(nil)
55-
if kshare.twoDeltaSi != nil {
56-
twoDeltaSiBytes = kshare.twoDeltaSi.Bytes()
57-
}
58-
72+
twoDeltaSiBytes := kshare.twoDeltaSi.Bytes()
5973
twoDeltaSiLen := len(twoDeltaSiBytes)
6074

6175
if twoDeltaSiLen > math.MaxInt16 {
@@ -86,15 +100,11 @@ func (kshare *KeyShare) MarshalBinary() ([]byte, error) {
86100

87101
copy(out[8:8+siLength], siBytes)
88102

89-
if twoDeltaSiBytes != nil {
90-
out[8+siLength] = 1 // twoDeltaSiNil
91-
}
103+
out[8+siLength] = 1 // twoDeltaSiNil
92104

93105
binary.BigEndian.PutUint16(out[8+siLength+1:8+siLength+3], uint16(twoDeltaSiLen))
94106

95-
if twoDeltaSiBytes != nil {
96-
copy(out[8+siLength+3:8+siLength+3+twoDeltaSiLen], twoDeltaSiBytes)
97-
}
107+
copy(out[8+siLength+3:8+siLength+3+twoDeltaSiLen], twoDeltaSiBytes)
98108

99109
return out, nil
100110
}
@@ -132,24 +142,18 @@ func (kshare *KeyShare) UnmarshalBinary(data []byte) error {
132142
return fmt.Errorf("rsa_threshold: keyshare unmarshalKeyShareTest failed: data length was too short for reading twoDeltaSiNil")
133143
}
134144

135-
isNil := data[8+siLen]
136-
137-
var twoDeltaSi *big.Int
138-
139-
if isNil != 0 {
140-
if len(data[8+siLen+1:]) < 2 {
141-
return fmt.Errorf("rsa_threshold: keyshare unmarshalKeyShareTest failed: data length was too short for reading twoDeltaSiLen length")
142-
}
143-
144-
twoDeltaSiLen := binary.BigEndian.Uint16(data[8+siLen+1 : 8+siLen+3])
145+
if len(data[8+siLen+1:]) < 2 {
146+
return fmt.Errorf("rsa_threshold: keyshare unmarshalKeyShareTest failed: data length was too short for reading twoDeltaSiLen length")
147+
}
145148

146-
if uint16(len(data[8+siLen+3:])) < twoDeltaSiLen {
147-
return fmt.Errorf("rsa_threshold: keyshare unmarshalKeyShareTest failed: data length was too short for reading twoDeltaSi, needed: %d found: %d", twoDeltaSiLen, len(data[8+siLen+2:]))
148-
}
149+
twoDeltaSiLen := binary.BigEndian.Uint16(data[8+siLen+1 : 8+siLen+3])
149150

150-
twoDeltaSi = new(big.Int).SetBytes(data[8+siLen+3 : 8+siLen+3+twoDeltaSiLen])
151+
if uint16(len(data[8+siLen+3:])) < twoDeltaSiLen {
152+
return fmt.Errorf("rsa_threshold: keyshare unmarshalKeyShareTest failed: data length was too short for reading twoDeltaSi, needed: %d found: %d", twoDeltaSiLen, len(data[8+siLen+2:]))
151153
}
152154

155+
twoDeltaSi := new(big.Int).SetBytes(data[8+siLen+3 : 8+siLen+3+twoDeltaSiLen])
156+
153157
kshare.Players = uint(players)
154158
kshare.Threshold = uint(threshold)
155159
kshare.Index = uint(index)
@@ -173,6 +177,24 @@ func (kshare *KeyShare) get2DeltaSi(players int64) *big.Int {
173177
return delta
174178
}
175179

180+
// IsVerifiable returns true if the key share can produce
181+
// verifiable signature shares.
182+
func (kshare *KeyShare) IsVerifiable() bool { return kshare.vk != nil }
183+
184+
// VerifyKeys returns a copy of the verification keys used to verify
185+
// signature shares. Returns nil if the key share cannot produce
186+
// verifiable signature shares.
187+
func (kshare *KeyShare) VerifyKeys() (vk *VerifyKeys) {
188+
if kshare.IsVerifiable() {
189+
vk = &VerifyKeys{
190+
GroupKey: new(big.Int).Set(kshare.vk.GroupKey),
191+
VerifyKey: new(big.Int).Set(kshare.vk.VerifyKey),
192+
}
193+
}
194+
195+
return
196+
}
197+
176198
// Sign msg using a KeyShare. msg MUST be padded and hashed. Call PadHash before this method.
177199
//
178200
// If rand is not nil then blinding will be used to avoid timing
@@ -248,5 +270,25 @@ func (kshare *KeyShare) Sign(randSource io.Reader, pub *rsa.PublicKey, digest []
248270
signShare.xi.Exp(x, exp, pub.N)
249271
}
250272

273+
// When verification keys are available, a DLEQ Proof is included.
274+
if kshare.vk != nil {
275+
const SecParam = 128
276+
fourDelta := calculateDelta(int64(kshare.Players))
277+
fourDelta.Lsh(fourDelta, 2)
278+
x4Delta := new(big.Int).Exp(x, fourDelta, pub.N)
279+
xiSqr := new(big.Int).Mul(signShare.xi, signShare.xi)
280+
xiSqr.Mod(xiSqr, pub.N)
281+
282+
proof, err := qndleq.Prove(randSource,
283+
kshare.si,
284+
kshare.vk.GroupKey, kshare.vk.VerifyKey,
285+
x4Delta, xiSqr,
286+
pub.N, SecParam)
287+
if err != nil {
288+
return SignShare{}, err
289+
}
290+
signShare.proof = proof
291+
}
292+
251293
return signShare, nil
252294
}

tss/rsa/keyshare_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func TestMarshallKeyShare(t *testing.T) {
117117

118118
marshalTestKeyShare(KeyShare{
119119
si: big.NewInt(10),
120-
twoDeltaSi: nil,
120+
twoDeltaSi: big.NewInt(20),
121121
Index: 30,
122122
Threshold: 0,
123123
Players: 200,
@@ -151,7 +151,7 @@ func TestMarshallKeyShareFull(t *testing.T) {
151151
if err != nil {
152152
t.Fatal(err)
153153
}
154-
keys, err := Deal(rand.Reader, players, threshold, key, false)
154+
keys, err := Deal(rand.Reader, players, threshold, key)
155155
if err != nil {
156156
t.Fatal(err)
157157
}

tss/rsa/rsa_threshold.go

+32-11
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ import (
1919
"math/big"
2020

2121
cmath "github.com/cloudflare/circl/math"
22+
"github.com/cloudflare/circl/zk/qndleq"
2223
)
2324

24-
// GenerateKey generates a RSA keypair for its use in RSA threshold signatures.
25-
// Internally, the modulus is the product of two safe primes. The time
26-
// consumed by this function is relatively longer than the regular
27-
// GenerateKey function from the crypto/rsa package.
25+
// GenerateKey generates an RSA keypair for its use in RSA threshold signatures.
26+
// Unlike crypto/rsa.GenerateKey, this function calculates the modulus as the
27+
// product of two safe primes. Note that the time consumed by this function is
28+
// relatively longer than the time of the crypto/rsa.GenerateKey function.
29+
//
30+
// Generate keys with this function to enable verifiability of signature shares.
2831
func GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) {
2932
p, err := cmath.SafePrime(random, bits/2)
3033
if err != nil {
@@ -85,9 +88,12 @@ func validateParams(players, threshold uint) error {
8588
return nil
8689
}
8790

88-
// Deal takes in an existing RSA private key generated elsewhere. If cache is true, cached values are stored in KeyShare taking up more memory by reducing Sign time.
89-
// See KeyShare documentation. Multi-prime RSA keys are unsupported.
90-
func Deal(randSource io.Reader, players, threshold uint, key *rsa.PrivateKey, cache bool) ([]KeyShare, error) {
91+
// Deal splits an RSA private key into key shares, so signing can be performed
92+
// from a threshold number of signatures shares.
93+
// When the modulus is the product of two safe primes, key shares include
94+
// keys for verification of signatures shares.
95+
// Note that multi-prime RSA keys are not supported.
96+
func Deal(randSource io.Reader, players, threshold uint, key *rsa.PrivateKey) ([]KeyShare, error) {
9197
err := validateParams(players, threshold)
9298

9399
ONE := big.NewInt(1)
@@ -103,6 +109,7 @@ func Deal(randSource io.Reader, players, threshold uint, key *rsa.PrivateKey, ca
103109
p := key.Primes[0]
104110
q := key.Primes[1]
105111
e := int64(key.E)
112+
hasSafePrimes := cmath.IsSafePrime(p) && cmath.IsSafePrime(q)
106113

107114
// p = 2p' + 1
108115
// q = 2q' + 1
@@ -143,18 +150,32 @@ func Deal(randSource io.Reader, players, threshold uint, key *rsa.PrivateKey, ca
143150
}
144151
}
145152

153+
var groupKey *big.Int
154+
if hasSafePrimes {
155+
groupKey, err = qndleq.SampleQn(randSource, key.N)
156+
if err != nil {
157+
return nil, err
158+
}
159+
}
160+
146161
shares := make([]KeyShare, players)
147162

148163
// 1 <= i <= l
149164
for i := uint(1); i <= players; i++ {
150165
shares[i-1].Players = players
151166
shares[i-1].Threshold = threshold
152167
// Σ^{k-1}_{i=0} | a_i * X^i (mod m)
153-
poly := computePolynomial(threshold, a, i, &m)
154-
shares[i-1].si = poly
168+
si := computePolynomial(threshold, a, i, &m)
169+
shares[i-1].si = si
155170
shares[i-1].Index = i
156-
if cache {
157-
shares[i-1].get2DeltaSi(int64(players))
171+
shares[i-1].get2DeltaSi(int64(players))
172+
173+
// If the modulus is composed by safe primes, verification keys are included.
174+
if hasSafePrimes {
175+
shares[i-1].vk = &VerifyKeys{
176+
GroupKey: groupKey,
177+
VerifyKey: new(big.Int).Exp(groupKey, si, key.N),
178+
}
158179
}
159180
}
160181

tss/rsa/rsa_threshold_test.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func createPrivateKey(p, q *big.Int, e int) *rsa.PrivateKey {
2929
return &rsa.PrivateKey{
3030
PublicKey: rsa.PublicKey{
3131
E: e,
32+
N: new(big.Int).Mul(p, q),
3233
},
3334
D: nil,
3435
Primes: []*big.Int{p, q},
@@ -141,7 +142,7 @@ func TestDeal(t *testing.T) {
141142
//
142143
//
143144
//
144-
r := bytes.NewReader([]byte{33, 17})
145+
r := io.MultiReader(bytes.NewReader([]byte{33, 17}), rand.Reader)
145146
players := uint(3)
146147
threshold := uint(2)
147148
p := int64(23)
@@ -150,7 +151,7 @@ func TestDeal(t *testing.T) {
150151

151152
key := createPrivateKey(big.NewInt(p), big.NewInt(q), e)
152153

153-
share, err := Deal(r, players, threshold, key, false)
154+
share, err := Deal(r, players, threshold, key)
154155
if err != nil {
155156
t.Fatal(err)
156157
}
@@ -198,6 +199,14 @@ func testIntegration(t *testing.T, algo crypto.Hash, priv *rsa.PrivateKey, thres
198199
if err != nil {
199200
t.Fatal(err)
200201
}
202+
203+
if signshares[i].IsVerifiable() {
204+
verifKeys := keys[i].VerifyKeys()
205+
err = signshares[i].Verify(pub, verifKeys, msgPH)
206+
if err != nil {
207+
t.Fatalf("sign share is verifiable, but didn't pass verification")
208+
}
209+
}
201210
}
202211

203212
sig, err := CombineSignShares(pub, signshares, msgPH)
@@ -236,14 +245,15 @@ func testIntegration(t *testing.T, algo crypto.Hash, priv *rsa.PrivateKey, thres
236245
func TestIntegrationStdRsaKeyGenerationPKS1v15(t *testing.T) {
237246
const players = 3
238247
const threshold = 2
239-
const bits = 2048
248+
// [warning] Bitlength used for tests only, use a bitlength above 2048 for security.
249+
const bits = 512
240250
const algo = crypto.SHA256
241251

242-
key, err := rsa.GenerateKey(rand.Reader, bits)
252+
key, err := GenerateKey(rand.Reader, bits)
243253
if err != nil {
244254
t.Fatal(err)
245255
}
246-
keys, err := Deal(rand.Reader, players, threshold, key, false)
256+
keys, err := Deal(rand.Reader, players, threshold, key)
247257
if err != nil {
248258
t.Fatal(err)
249259
}
@@ -253,14 +263,15 @@ func TestIntegrationStdRsaKeyGenerationPKS1v15(t *testing.T) {
253263
func TestIntegrationStdRsaKeyGenerationPSS(t *testing.T) {
254264
const players = 3
255265
const threshold = 2
256-
const bits = 2048
266+
// [warning] Bitlength used for tests only, use a bitlength above 2048 for security.
267+
const bits = 512
257268
const algo = crypto.SHA256
258269

259270
key, err := rsa.GenerateKey(rand.Reader, bits)
260271
if err != nil {
261272
t.Fatal(err)
262273
}
263-
keys, err := Deal(rand.Reader, players, threshold, key, false)
274+
keys, err := Deal(rand.Reader, players, threshold, key)
264275
if err != nil {
265276
t.Fatal(err)
266277
}
@@ -275,7 +286,7 @@ func benchmarkSignCombineHelper(randSource io.Reader, parallel bool, b *testing.
275286
panic(err)
276287
}
277288

278-
keys, err := Deal(rand.Reader, players, threshold, key, true)
289+
keys, err := Deal(rand.Reader, players, threshold, key)
279290
if err != nil {
280291
b.Fatal(err)
281292
}
@@ -428,7 +439,7 @@ func BenchmarkDealGeneration(b *testing.B) {
428439
}
429440
b.ResetTimer()
430441
for i := 0; i < b.N; i++ {
431-
_, err := Deal(rand.Reader, players, threshold, key, false)
442+
_, err := Deal(rand.Reader, players, threshold, key)
432443
if err != nil {
433444
b.Fatal(err)
434445
}

0 commit comments

Comments
 (0)