Skip to content

Commit fa1d557

Browse files
authored
Adds RSA Threshold signatures (#364)
Adds RSA Threshold signatures: This is an implementation of "Practical Threshold Signatures" by Victor Shoup. * Minor changes related to PR comments * Formatting, remove md5 test * adds players and threshold to SignShare * Marshal/Unmarshal bug fixes, adds tests * minor fixes to keyshare * signShare fixes and more tests * minor fix Authored-by: Josh Brown <[email protected]>
1 parent 03a6a6e commit fa1d557

13 files changed

+1729
-0
lines changed

tss/rsa/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# RSA Threshold Signatures
2+
3+
This is an implementation of ["Practical Threshold Signatures" by Victor Shoup](https://www.iacr.org/archive/eurocrypt2000/1807/18070209-new.pdf).
4+
Protocol 1 is implemented.
5+
6+
## Threshold Primer
7+
8+
Let *l* be the total number of players, *t* be the number of corrupted players, and *k* be the threshold.
9+
The idea of threshold signatures is that at least *k* players need to participate to form a valid signature.
10+
11+
Setup consists of a dealer generating *l* key shares from a key pair and "dealing" them to the players. In this implementation the dealer is trusted.
12+
13+
During the signing phase, at least *k* players use their key share and the message to generate a signature share.
14+
Finally, the *k* signature shares are combined to form a valid signature for the message.
15+
16+
## Modifications
17+
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.

tss/rsa/internal/pkcs1v15.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/pkcs1v15.go
2+
3+
// Copyright (c) 2009 The Go Authors. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
// * Redistributions in binary form must reproduce the above
12+
// copyright notice, this list of conditions and the following disclaimer
13+
// in the documentation and/or other materials provided with the
14+
// distribution.
15+
// * Neither the name of Google Inc. nor the names of its
16+
// contributors may be used to endorse or promote products derived from
17+
// this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
// Copyright 2009 The Go Authors. All rights reserved.
32+
// Use of this source code is governed by a BSD-style
33+
// license that can be found in the LICENSE file.
34+
35+
package internal
36+
37+
import (
38+
"crypto"
39+
"crypto/rsa"
40+
"errors"
41+
"fmt"
42+
)
43+
44+
var hashPrefixes = map[crypto.Hash][]byte{
45+
crypto.MD5: {
46+
0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05,
47+
0x00, 0x04, 0x10,
48+
},
49+
crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
50+
crypto.SHA224: {
51+
0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
52+
0x05, 0x00, 0x04, 0x1c,
53+
},
54+
crypto.SHA256: {
55+
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
56+
0x05, 0x00, 0x04, 0x20,
57+
},
58+
crypto.SHA384: {
59+
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
60+
0x05, 0x00, 0x04, 0x30,
61+
},
62+
crypto.SHA512: {
63+
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
64+
0x05, 0x00, 0x04, 0x40,
65+
},
66+
crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
67+
crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
68+
}
69+
70+
func PadPKCS1v15(pub *rsa.PublicKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
71+
hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
tLen := len(prefix) + hashLen
77+
k := pub.Size()
78+
if k < tLen+11 {
79+
return nil, fmt.Errorf("message too long")
80+
}
81+
82+
// EM = 0x00 || 0x01 || PS || 0x00 || T
83+
em := make([]byte, k)
84+
em[1] = 1
85+
for i := 2; i < k-tLen-1; i++ {
86+
em[i] = 0xff
87+
}
88+
copy(em[k-tLen:k-hashLen], prefix)
89+
copy(em[k-hashLen:k], hashed)
90+
91+
return em, nil
92+
}
93+
94+
func pkcs1v15HashInfo(hash crypto.Hash, inLen int) (hashLen int, prefix []byte, err error) {
95+
// Special case: crypto.Hash(0) is used to indicate that the data is
96+
// signed directly.
97+
if hash == 0 {
98+
return inLen, nil, nil
99+
}
100+
101+
hashLen = hash.Size()
102+
if inLen != hashLen {
103+
return 0, nil, errors.New("threshold_internal: crypto/rsa: input must be hashed message")
104+
}
105+
prefix, ok := hashPrefixes[hash]
106+
if !ok {
107+
return 0, nil, errors.New("threshold_internal: crypto/rsa: unsupported hash function")
108+
}
109+
return
110+
}

tss/rsa/internal/pss/pss.go

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/pss.go
2+
3+
// Copyright (c) 2009 The Go Authors. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
// * Redistributions in binary form must reproduce the above
12+
// copyright notice, this list of conditions and the following disclaimer
13+
// in the documentation and/or other materials provided with the
14+
// distribution.
15+
// * Neither the name of Google Inc. nor the names of its
16+
// contributors may be used to endorse or promote products derived from
17+
// this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
// Copyright 2013 The Go Authors. All rights reserved.
32+
// Use of this source code is governed by a BSD-style
33+
// license that can be found in the LICENSE file.
34+
35+
package pss
36+
37+
// This file implements the RSASSA-PSS signature scheme according to RFC 8017.
38+
39+
import (
40+
"crypto"
41+
"crypto/rsa"
42+
"errors"
43+
"hash"
44+
"io"
45+
)
46+
47+
// Per RFC 8017, Section 9.1
48+
//
49+
// EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc
50+
//
51+
// where
52+
//
53+
// DB = PS || 0x01 || salt
54+
//
55+
// and PS can be empty so
56+
//
57+
// emLen = dbLen + hLen + 1 = psLen + sLen + hLen + 2
58+
//
59+
60+
func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) {
61+
// See RFC 8017, Section 9.1.1.
62+
63+
hLen := hash.Size()
64+
sLen := len(salt)
65+
emLen := (emBits + 7) / 8
66+
67+
// 1. If the length of M is greater than the input limitation for the
68+
// hash function (2^61 - 1 octets for SHA-1), output "message too
69+
// long" and stop.
70+
//
71+
// 2. Let mHash = Hash(M), an octet string of length hLen.
72+
73+
if len(mHash) != hLen {
74+
return nil, errors.New("crypto/rsa: input must be hashed with given hash")
75+
}
76+
77+
// 3. If emLen < hLen + sLen + 2, output "encoding error" and stop.
78+
79+
if emLen < hLen+sLen+2 {
80+
return nil, errors.New("crypto/rsa: key size too small for PSS signature")
81+
}
82+
83+
em := make([]byte, emLen)
84+
psLen := emLen - sLen - hLen - 2
85+
db := em[:psLen+1+sLen]
86+
h := em[psLen+1+sLen : emLen-1]
87+
88+
// 4. Generate a random octet string salt of length sLen; if sLen = 0,
89+
// then salt is the empty string.
90+
//
91+
// 5. Let
92+
// M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
93+
//
94+
// M' is an octet string of length 8 + hLen + sLen with eight
95+
// initial zero octets.
96+
//
97+
// 6. Let H = Hash(M'), an octet string of length hLen.
98+
99+
var prefix [8]byte
100+
101+
hash.Write(prefix[:])
102+
hash.Write(mHash)
103+
hash.Write(salt)
104+
105+
h = hash.Sum(h[:0])
106+
hash.Reset()
107+
108+
// 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
109+
// zero octets. The length of PS may be 0.
110+
//
111+
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
112+
// emLen - hLen - 1.
113+
114+
db[psLen] = 0x01
115+
copy(db[psLen+1:], salt)
116+
117+
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
118+
//
119+
// 10. Let maskedDB = DB \xor dbMask.
120+
121+
mgf1XOR(db, hash, h)
122+
123+
// 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in
124+
// maskedDB to zero.
125+
126+
db[0] &= 0xff >> (8*emLen - emBits)
127+
128+
// 12. Let EM = maskedDB || H || 0xbc.
129+
em[emLen-1] = 0xbc
130+
131+
// 13. Output EM.
132+
return em, nil
133+
}
134+
135+
func padPSSWithSalt(pub *rsa.PublicKey, hash crypto.Hash, hashed, salt []byte) ([]byte, error) {
136+
emBits := pub.N.BitLen() - 1
137+
em, err := emsaPSSEncode(hashed, emBits, salt, hash.New())
138+
if err != nil {
139+
return nil, err
140+
}
141+
return em, nil
142+
}
143+
144+
func saltLength(opts *rsa.PSSOptions) int {
145+
if opts == nil {
146+
return rsa.PSSSaltLengthAuto
147+
}
148+
return opts.SaltLength
149+
}
150+
151+
func PadPSS(rand io.Reader, pub *rsa.PublicKey, hash crypto.Hash, digest []byte, opts *rsa.PSSOptions) ([]byte, error) {
152+
if opts != nil && opts.Hash != 0 {
153+
hash = opts.Hash
154+
}
155+
156+
saltLength := saltLength(opts)
157+
switch saltLength {
158+
case rsa.PSSSaltLengthAuto:
159+
saltLength = (pub.N.BitLen()-1+7)/8 - 2 - hash.Size()
160+
case rsa.PSSSaltLengthEqualsHash:
161+
saltLength = hash.Size()
162+
}
163+
164+
salt := make([]byte, saltLength)
165+
if _, err := io.ReadFull(rand, salt); err != nil {
166+
return nil, err
167+
}
168+
return padPSSWithSalt(pub, hash, digest, salt)
169+
}

tss/rsa/internal/pss/rsa.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/rsa.go
2+
3+
// Copyright (c) 2009 The Go Authors. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
// * Redistributions in binary form must reproduce the above
12+
// copyright notice, this list of conditions and the following disclaimer
13+
// in the documentation and/or other materials provided with the
14+
// distribution.
15+
// * Neither the name of Google Inc. nor the names of its
16+
// contributors may be used to endorse or promote products derived from
17+
// this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
package pss
32+
33+
import (
34+
"hash"
35+
)
36+
37+
// incCounter increments a four byte, big-endian counter.
38+
func incCounter(c *[4]byte) {
39+
if c[3]++; c[3] != 0 {
40+
return
41+
}
42+
if c[2]++; c[2] != 0 {
43+
return
44+
}
45+
if c[1]++; c[1] != 0 {
46+
return
47+
}
48+
c[0]++
49+
}
50+
51+
// mgf1XOR XORs the bytes in out with a mask generated using the MGF1 function
52+
// specified in PKCS #1 v2.1.
53+
func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
54+
var counter [4]byte
55+
var digest []byte
56+
57+
done := 0
58+
for done < len(out) {
59+
hash.Write(seed)
60+
hash.Write(counter[0:4])
61+
digest = hash.Sum(digest[:0])
62+
hash.Reset()
63+
64+
for i := 0; i < len(digest) && done < len(out); i++ {
65+
out[done] ^= digest[i]
66+
done++
67+
}
68+
incCounter(&counter)
69+
}
70+
}

0 commit comments

Comments
 (0)