Skip to content

tss: Adds RSA Threshold signatures #364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions tss/rsa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# RSA Threshold Signatures

This is an implementation of ["Practical Threshold Signatures" by Victor Shoup](https://www.iacr.org/archive/eurocrypt2000/1807/18070209-new.pdf).
Protocol 1 is implemented.

## Threshold Primer

Let *l* be the total number of players, *t* be the number of corrupted players, and *k* be the threshold.
The idea of threshold signatures is that at least *k* players need to participate to form a valid signature.

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.

During the signing phase, at least *k* players use their key share and the message to generate a signature share.
Finally, the *k* signature shares are combined to form a valid signature for the message.

## Modifications

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.
2. The paper requires p and q to be safe primes. We do not.
110 changes: 110 additions & 0 deletions tss/rsa/internal/pkcs1v15.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/pkcs1v15.go

// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal

import (
"crypto"
"crypto/rsa"
"errors"
"fmt"
)

var hashPrefixes = map[crypto.Hash][]byte{
crypto.MD5: {
0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05,
0x00, 0x04, 0x10,
},
crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
crypto.SHA224: {
0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
0x05, 0x00, 0x04, 0x1c,
},
crypto.SHA256: {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20,
},
crypto.SHA384: {
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
0x05, 0x00, 0x04, 0x30,
},
crypto.SHA512: {
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
0x05, 0x00, 0x04, 0x40,
},
crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
}

func PadPKCS1v15(pub *rsa.PublicKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
if err != nil {
return nil, err
}

tLen := len(prefix) + hashLen
k := pub.Size()
if k < tLen+11 {
return nil, fmt.Errorf("message too long")
}

// EM = 0x00 || 0x01 || PS || 0x00 || T
em := make([]byte, k)
em[1] = 1
for i := 2; i < k-tLen-1; i++ {
em[i] = 0xff
}
copy(em[k-tLen:k-hashLen], prefix)
copy(em[k-hashLen:k], hashed)

return em, nil
}

func pkcs1v15HashInfo(hash crypto.Hash, inLen int) (hashLen int, prefix []byte, err error) {
// Special case: crypto.Hash(0) is used to indicate that the data is
// signed directly.
if hash == 0 {
return inLen, nil, nil
}

hashLen = hash.Size()
if inLen != hashLen {
return 0, nil, errors.New("threshold_internal: crypto/rsa: input must be hashed message")
}
prefix, ok := hashPrefixes[hash]
if !ok {
return 0, nil, errors.New("threshold_internal: crypto/rsa: unsupported hash function")
}
return
}
169 changes: 169 additions & 0 deletions tss/rsa/internal/pss/pss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/pss.go

// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pss

// This file implements the RSASSA-PSS signature scheme according to RFC 8017.

import (
"crypto"
"crypto/rsa"
"errors"
"hash"
"io"
)

// Per RFC 8017, Section 9.1
//
// EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc
//
// where
//
// DB = PS || 0x01 || salt
//
// and PS can be empty so
//
// emLen = dbLen + hLen + 1 = psLen + sLen + hLen + 2
//

func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) {
// See RFC 8017, Section 9.1.1.

hLen := hash.Size()
sLen := len(salt)
emLen := (emBits + 7) / 8

// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "message too
// long" and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen.

if len(mHash) != hLen {
return nil, errors.New("crypto/rsa: input must be hashed with given hash")
}

// 3. If emLen < hLen + sLen + 2, output "encoding error" and stop.

if emLen < hLen+sLen+2 {
return nil, errors.New("crypto/rsa: key size too small for PSS signature")
}

em := make([]byte, emLen)
psLen := emLen - sLen - hLen - 2
db := em[:psLen+1+sLen]
h := em[psLen+1+sLen : emLen-1]

// 4. Generate a random octet string salt of length sLen; if sLen = 0,
// then salt is the empty string.
//
// 5. Let
// M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
//
// M' is an octet string of length 8 + hLen + sLen with eight
// initial zero octets.
//
// 6. Let H = Hash(M'), an octet string of length hLen.

var prefix [8]byte

hash.Write(prefix[:])
hash.Write(mHash)
hash.Write(salt)

h = hash.Sum(h[:0])
hash.Reset()

// 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
// zero octets. The length of PS may be 0.
//
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
// emLen - hLen - 1.

db[psLen] = 0x01
copy(db[psLen+1:], salt)

// 9. Let dbMask = MGF(H, emLen - hLen - 1).
//
// 10. Let maskedDB = DB \xor dbMask.

mgf1XOR(db, hash, h)

// 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in
// maskedDB to zero.

db[0] &= 0xff >> (8*emLen - emBits)

// 12. Let EM = maskedDB || H || 0xbc.
em[emLen-1] = 0xbc

// 13. Output EM.
return em, nil
}

func padPSSWithSalt(pub *rsa.PublicKey, hash crypto.Hash, hashed, salt []byte) ([]byte, error) {
emBits := pub.N.BitLen() - 1
em, err := emsaPSSEncode(hashed, emBits, salt, hash.New())
if err != nil {
return nil, err
}
return em, nil
}

func saltLength(opts *rsa.PSSOptions) int {
if opts == nil {
return rsa.PSSSaltLengthAuto
}
return opts.SaltLength
}

func PadPSS(rand io.Reader, pub *rsa.PublicKey, hash crypto.Hash, digest []byte, opts *rsa.PSSOptions) ([]byte, error) {
if opts != nil && opts.Hash != 0 {
hash = opts.Hash
}

saltLength := saltLength(opts)
switch saltLength {
case rsa.PSSSaltLengthAuto:
saltLength = (pub.N.BitLen()-1+7)/8 - 2 - hash.Size()
case rsa.PSSSaltLengthEqualsHash:
saltLength = hash.Size()
}

salt := make([]byte, saltLength)
if _, err := io.ReadFull(rand, salt); err != nil {
return nil, err
}
return padPSSWithSalt(pub, hash, digest, salt)
}
70 changes: 70 additions & 0 deletions tss/rsa/internal/pss/rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/rsa/rsa.go

// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package pss

import (
"hash"
)

// incCounter increments a four byte, big-endian counter.
func incCounter(c *[4]byte) {
if c[3]++; c[3] != 0 {
return
}
if c[2]++; c[2] != 0 {
return
}
if c[1]++; c[1] != 0 {
return
}
c[0]++
}

// mgf1XOR XORs the bytes in out with a mask generated using the MGF1 function
// specified in PKCS #1 v2.1.
func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
var counter [4]byte
var digest []byte

done := 0
for done < len(out) {
hash.Write(seed)
hash.Write(counter[0:4])
digest = hash.Sum(digest[:0])
hash.Reset()

for i := 0; i < len(digest) && done < len(out); i++ {
out[done] ^= digest[i]
done++
}
incCounter(&counter)
}
}
Loading