(no commit message provided)

This commit is contained in:
Prad Nukala
2024-07-06 00:34:41 -04:00
committed by Prad Nukala (aider)
parent 5fd43dfd6b
commit 2f976209db
345 changed files with 409 additions and 72177 deletions
-288
View File
@@ -1,288 +0,0 @@
// Copyright 2016 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 ted25519 implements the Ed25519 signature algorithm. See https://ed25519.cr.yp.to/
//
// These functions are also compatible with the "Ed25519" function defined in
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
// representation includes a public key suffix to make multiple signing
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the "seed".
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
package ted25519
import (
"bytes"
"crypto"
cryptorand "crypto/rand"
"crypto/sha512"
"fmt"
"io"
"strconv"
"github.com/onsonr/hway/crypto/core/curves"
)
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
SignatureSize = 64
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
SeedSize = 32
)
// PublicKey is the type of Ed25519 public keys.
type PublicKey []byte
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
type PrivateKey []byte
// Bytes returns the publicKey in byte array
func (p PublicKey) Bytes() []byte {
return p
}
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[32:])
return PublicKey(publicKey)
}
// Seed returns the private key seed corresponding to priv. It is provided for
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
// in this package.
func (priv PrivateKey) Seed() []byte {
seed := make([]byte, SeedSize)
copy(seed, priv[:32])
return seed
}
// Sign signs the given message with priv.
// Ed25519 performs two passes over messages to be signed and therefore cannot
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
// indicate the message hasn't been hashed. This can be achieved by passing
// crypto.Hash(0) as the value for opts.
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
if opts.HashFunc() != crypto.Hash(0) {
return nil, fmt.Errorf("ed25519: cannot sign hashed message")
}
sig, err := Sign(priv, message)
if err != nil {
return nil, err
}
return sig, nil
}
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
if rand == nil {
rand = cryptorand.Reader
}
seed := make([]byte, SeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
privateKey, err := NewKeyFromSeed(seed)
if err != nil {
return nil, nil, err
}
publicKey := make([]byte, PublicKeySize)
copy(publicKey, privateKey[32:])
return publicKey, privateKey, nil
}
// NewKeyFromSeed calculates a private key from a seed. It will panic if
// len(seed) is not SeedSize. This function is provided for interoperability
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
// package.
func NewKeyFromSeed(seed []byte) (PrivateKey, error) {
// Outline the function body so that the returned key can be stack-allocated.
privateKey := make([]byte, PrivateKeySize)
err := newKeyFromSeed(privateKey, seed)
if err != nil {
return nil, err
}
return privateKey, nil
}
func newKeyFromSeed(privateKey, seed []byte) error {
if l := len(seed); l != SeedSize {
return fmt.Errorf("ed25519: bad seed length: " + strconv.Itoa(l))
}
digest := sha512.Sum512(seed)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
var hBytes [32]byte
copy(hBytes[:], digest[:])
h, err := new(curves.ScalarEd25519).SetBytesClamping(hBytes[:])
if err != nil {
return err
}
ed25519 := curves.ED25519()
A := ed25519.ScalarBaseMult(h)
publicKeyBytes := A.ToAffineCompressed()
copy(privateKey, seed)
copy(privateKey[32:], publicKeyBytes[:])
return nil
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) ([]byte, error) {
// Outline the function body so that the returned signature can be
// stack-allocated.
signature := make([]byte, SignatureSize)
err := sign(signature, privateKey, message)
if err != nil {
return nil, err
}
return signature, nil
}
func sign(signature, privateKey, message []byte) error {
if l := len(privateKey); l != PrivateKeySize {
return fmt.Errorf("ed25519: bad private key length: " + strconv.Itoa(l))
}
var err error
h := sha512.New()
_, err = h.Write(privateKey[:32])
if err != nil {
return err
}
var digest1, messageDigest, hramDigest [64]byte
var expandedSecretKey [32]byte
_ = h.Sum(digest1[:0])
copy(expandedSecretKey[:], digest1[:])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
h.Reset()
_, err = h.Write(digest1[32:])
if err != nil {
return err
}
_, err = h.Write(message)
if err != nil {
return err
}
_ = h.Sum(messageDigest[:0])
r, err := new(curves.ScalarEd25519).SetBytesWide(messageDigest[:])
if err != nil {
return err
}
// R = r * G
R := curves.ED25519().Point.Generator().Mul(r)
encodedR := R.ToAffineCompressed()
h.Reset()
_, err = h.Write(encodedR[:])
if err != nil {
return err
}
_, err = h.Write(privateKey[32:])
if err != nil {
return err
}
_, err = h.Write(message)
if err != nil {
return err
}
_ = h.Sum(hramDigest[:0])
// Set k and s
k, err := new(curves.ScalarEd25519).SetBytesWide(hramDigest[:])
if err != nil {
return err
}
s, err := new(curves.ScalarEd25519).SetBytesClamping(expandedSecretKey[:])
if err != nil {
return err
}
// S = k*s + r
S := k.MulAdd(s, r)
copy(signature[:], encodedR[:])
copy(signature[32:], S.Bytes()[:])
return nil
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
// Previously publicKey is of type PublicKey
func Verify(publicKey PublicKey, message, sig []byte) (bool, error) {
if l := len(publicKey); l != PublicKeySize {
return false, fmt.Errorf("ed25519: bad public key length: " + strconv.Itoa(l))
}
if len(sig) != SignatureSize || sig[63]&224 != 0 {
return false, fmt.Errorf("ed25519: bad signature size: " + strconv.Itoa(len(sig)))
}
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
A, err := new(curves.PointEd25519).FromAffineCompressed(publicKeyBytes[:])
if err != nil {
return false, err
}
// Negate sets A = -A, and returns A. It actually negates X and T but keep Y and Z
negA := A.Neg()
h := sha512.New()
_, err = h.Write(sig[:32])
if err != nil {
panic(err)
}
_, err = h.Write(publicKey[:])
if err != nil {
return false, err
}
_, err = h.Write(message)
if err != nil {
return false, err
}
var digest [64]byte
_ = h.Sum(digest[:0])
hReduced, err := new(curves.ScalarEd25519).SetBytesWide(digest[:])
if err != nil {
return false, err
}
var s [32]byte
copy(s[:], sig[32:])
sScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(s[:])
if err != nil {
return false, err
}
// R' = hash * A + s * BasePoint
R := new(curves.PointEd25519).VarTimeDoubleScalarBaseMult(hReduced, negA, sScalar)
// Check R == R'
return bytes.Equal(sig[:32], R.ToAffineCompressed()), nil
}
-165
View File
@@ -1,165 +0,0 @@
// Copyright 2016 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.
// Copyright 2016 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 ted25519
import (
"bytes"
"crypto"
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
)
// sign.input.gz is a selection of test cases from
// https://ed25519.cr.yp.to/python/sign.input
const testVectorPath = "../../../test/data/sign.input.gz"
type zeroReader struct{}
func (zeroReader) Read(buf []byte) (int, error) {
for i := range buf {
buf[i] = 0
}
return len(buf), nil
}
func TestUnmarshalMarshal(t *testing.T) {
pub, _, err := GenerateKey(rand.Reader)
require.NoError(t, err)
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], pub)
A, err := new(curves.PointEd25519).FromAffineCompressed(publicKeyBytes[:])
require.NoError(t, err)
var pub2 [32]byte
copy(pub2[:], A.ToAffineCompressed())
if publicKeyBytes != pub2 {
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", publicKeyBytes, pub2)
}
}
func TestSignVerify(t *testing.T) {
var zero zeroReader
public, private, err := GenerateKey(zero)
require.NoError(t, err)
message := []byte("test message")
sig, err := Sign(private, message)
require.NoError(t, err)
ok, _ := Verify(public, message, sig)
require.True(t, ok)
wrongMessage := []byte("wrong message")
ok, _ = Verify(public, wrongMessage, sig)
require.True(t, !ok)
}
func TestCryptoSigner(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
signer := crypto.Signer(private)
publicInterface := signer.Public()
public2, ok := publicInterface.(PublicKey)
if !ok {
t.Fatalf("expected PublicKey from Public() but got %T", publicInterface)
}
if !bytes.Equal(public, public2) {
t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2)
}
message := []byte("message")
var noHash crypto.Hash
signature, err := signer.Sign(zero, message, noHash)
if err != nil {
t.Fatalf("error from Sign(): %s", err)
}
ok, _ = Verify(public, message, signature)
if !ok {
t.Errorf("Verify failed on signature from Sign()")
}
}
func TestMalleability(t *testing.T) {
// https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test
// that s be in [0, order). This prevents someone from adding a multiple of
// order to s and obtaining a second valid signature for the same message.
msg := []byte{0x54, 0x65, 0x73, 0x74}
sig := []byte{
0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a,
0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b,
0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67,
0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d,
0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33,
0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d,
}
publicKey := []byte{
0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5,
0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34,
0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa,
}
ok, _ := Verify(publicKey, msg, sig)
if ok {
t.Fatal("non-canonical signature accepted")
}
}
func BenchmarkKeyGeneration(b *testing.B) {
var zero zeroReader
for i := 0; i < b.N; i++ {
if _, _, err := GenerateKey(zero); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkNewKeyFromSeed(b *testing.B) {
seed := make([]byte, SeedSize)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = NewKeyFromSeed(seed)
}
}
func BenchmarkSigning(b *testing.B) {
var zero zeroReader
_, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = Sign(priv, message)
}
}
func BenchmarkVerification(b *testing.B) {
var zero zeroReader
pub, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
signature, _ := Sign(priv, message)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = Verify(pub, message, signature)
}
}
-123
View File
@@ -1,123 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"crypto/sha512"
"strconv"
"github.com/onsonr/hway/crypto/core/curves"
)
// GeAdd returns the sum of two public keys, a and b.
func GeAdd(a PublicKey, b PublicKey) PublicKey {
aPoint, err := new(curves.PointEd25519).FromAffineCompressed(a)
if err != nil {
panic("attempted to add invalid point: a")
}
bPoint, err := new(curves.PointEd25519).FromAffineCompressed(b)
if err != nil {
panic("attempted to add invalid point: b")
}
sum := aPoint.Add(bPoint)
return sum.ToAffineCompressed()
}
// ExpandSeed applies the standard Ed25519 transform to the seed to turn it into the real private
// key that is used for signing. It returns the expanded seed.
func ExpandSeed(seed []byte) []byte {
digest := sha512.Sum512(seed)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
return digest[:32]
}
// reverseBytes returns a new slice of the input bytes reversed
func reverseBytes(inBytes []byte) []byte {
outBytes := make([]byte, len(inBytes))
for i, j := 0, len(inBytes)-1; j >= 0; i, j = i+1, j-1 {
outBytes[i] = inBytes[j]
}
return outBytes
}
// ThresholdSign is used for creating signatures for threshold protocols that replace the values of
// the private key and nonce with shamir shares instead. Because of this we must have a custom
// signing implementation that accepts arguments for values that cannot be derived anymore and
// removes the extended key generation since that should be done before the secret is shared.
//
// expandedSecretKeyShare and rShare must be little-endian.
func ThresholdSign(
expandedSecretKeyShare []byte, publicKey PublicKey,
message []byte,
rShare []byte, R PublicKey, // nolint:gocritic
) []byte {
// These length checks are are sanity checks where we panic if a provided value falls outside of the expected range.
// These should never fail in practice but serve to protect us from some bug that would cause us to produce
// signatures using a zero value or clipping off extra bytes unintentionally.
//
// We don't specifically check for 32 byte values as any value within the subgroup field could show up here. This is
// different than the upstream Ed25519 which does, but this seems to be a result of how they initialize their byte
// slices to constants and does not guarantee the value itself is 32 bytes without padding.
if l := len(expandedSecretKeyShare); l == 0 || l > 32 {
panic("ed25519: bad key share length: " + strconv.Itoa(l))
}
if l := len(rShare); l == 0 || l > 32 {
panic("ed25519: bad nonce share length: " + strconv.Itoa(l))
}
var expandedSecretKey, rBytes [32]byte
copy(expandedSecretKey[:], expandedSecretKeyShare)
copy(rBytes[:], rShare)
// c = H(R || A || m) mod q
var hramDigest [64]byte
var err error
h := sha512.New()
_, err = h.Write(R[:])
if err != nil {
panic(err)
}
_, err = h.Write(publicKey[:])
if err != nil {
panic(err)
}
_, err = h.Write(message)
if err != nil {
panic(err)
}
_ = h.Sum(hramDigest[:0])
// Set c, x and r
c, err := new(curves.ScalarEd25519).SetBytesWide(hramDigest[:])
if err != nil {
panic(err)
}
x, err := new(curves.ScalarEd25519).SetBytesCanonical(expandedSecretKey[:])
if err != nil {
panic(err)
}
r, err := new(curves.ScalarEd25519).SetBytesCanonical(rBytes[:])
if err != nil {
panic(err)
}
// s = cx+r
s := c.MulAdd(x, r)
signature := make([]byte, SignatureSize)
copy(signature, R[:])
copy(signature[32:], s.Bytes()[:])
return signature
}
-113
View File
@@ -1,113 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"crypto/rand"
"encoding/hex"
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
v1 "github.com/onsonr/hway/crypto/sharing/v1"
)
const (
expectedSeedHex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"
expectedPrivKeyHex = "307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f"
)
func TestExpandSeed(t *testing.T) {
seedBytes, err := hex.DecodeString(expectedSeedHex)
require.NoError(t, err)
privKeyHex := hex.EncodeToString(ExpandSeed(seedBytes))
require.Equal(t, expectedPrivKeyHex, privKeyHex)
}
func TestThresholdSign(t *testing.T) {
pub, priv, err := generateKey()
require.NoError(t, err)
field := curves.NewField(curves.Ed25519Order())
keyShare := v1.NewShamirShare(0, priv, field)
r := big.NewInt(123456789).Bytes()
nonceShare := v1.NewShamirShare(0, r, field)
r = reverseBytes(r)
var rInput [32]byte
copy(rInput[:], r)
scalar, err := new(curves.ScalarEd25519).SetBytesCanonical(rInput[:])
require.NoError(t, err)
noncePub := curves.ED25519().Point.Generator().Mul(scalar)
message := []byte("fnord!")
wrongMessage := []byte("23")
sig := ThresholdSign(reverseBytes(keyShare.Value.Bytes()), pub, message, reverseBytes(nonceShare.Value.Bytes()), noncePub.ToAffineCompressed())
ok, _ := Verify(pub, message, sig)
require.True(t, ok)
ok, _ = Verify(pub, wrongMessage, sig)
require.False(t, ok)
}
func TestThresholdSign_invalid_secrets(t *testing.T) {
message := []byte("fnord!")
secret := []byte{0x02}
secret = reverseBytes(secret)
var sInput [32]byte
copy(sInput[:], secret)
scalar, err := new(curves.ScalarEd25519).SetBytesCanonical(sInput[:])
require.NoError(t, err)
pub := curves.ED25519().Point.Generator().Mul(scalar)
nonce := []byte{0x03}
nonce = reverseBytes(nonce)
var nInput [32]byte
copy(nInput[:], nonce)
nScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(nInput[:])
require.NoError(t, err)
noncePub := curves.ED25519().Point.Generator().Mul(nScalar)
require.PanicsWithValue(t, "ed25519: bad key share length: 0",
func() {
ThresholdSign(make([]byte, 0), pub.ToAffineCompressed(), message, nonce, noncePub.ToAffineCompressed())
},
)
require.PanicsWithValue(t, "ed25519: bad key share length: 33",
func() {
ThresholdSign(make([]byte, 33), pub.ToAffineCompressed(), message, nonce, noncePub.ToAffineCompressed())
},
)
require.PanicsWithValue(t, "ed25519: bad nonce share length: 0",
func() {
ThresholdSign(secret, pub.ToAffineCompressed(), message, make([]byte, 0), noncePub.ToAffineCompressed())
},
)
require.PanicsWithValue(t, "ed25519: bad nonce share length: 33",
func() {
ThresholdSign(secret, pub.ToAffineCompressed(), message, make([]byte, 33), noncePub.ToAffineCompressed())
},
)
}
// generateKey is the same as generateSharableKey, but used only for testing
func generateKey() (PublicKey, []byte, error) {
pub, priv, err := GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
seed := priv.Seed()
expandedSeed := reverseBytes(ExpandSeed(seed))
field := &curves.Field{Int: curves.Ed25519Order()}
expandedSeedReduced := field.ReducedElementFromBytes(expandedSeed)
return pub, expandedSeedReduced.Bytes(), nil
}
-240
View File
@@ -1,240 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"encoding/binary"
"fmt"
"github.com/onsonr/hway/crypto/core/curves"
v1 "github.com/onsonr/hway/crypto/sharing/v1"
)
// PublicKeyFromBytes converts byte array into PublicKey byte array
func PublicKeyFromBytes(bytes []byte) ([]byte, error) {
if l := len(bytes); l != PublicKeySize {
return nil, fmt.Errorf("invalid public key size: %d", l)
}
return bytes, nil
}
// KeyShare represents a share of a generated key.
type KeyShare struct {
*v1.ShamirShare
}
// NewKeyShare is a KeyShare constructor.
func NewKeyShare(identifier byte, secret []byte) *KeyShare {
field := curves.NewField(curves.Ed25519Order())
return &KeyShare{v1.NewShamirShare(uint32(identifier), secret, field)}
}
// Commitments is a collection of public keys with each coefficient of a polynomial as the secret keys.
type Commitments []curves.Point
// CommitmentsToBytes converts commitments to bytes
func (commitments Commitments) CommitmentsToBytes() [][]byte {
bytes := make([][]byte, len(commitments))
for i, c := range commitments {
bytes[i] = c.ToAffineCompressed()
}
return bytes
}
// CommitmentsFromBytes converts bytes to commitments
func CommitmentsFromBytes(bytes [][]byte) (Commitments, error) {
comms := make([]curves.Point, len(bytes))
for i, pubKeyBytes := range bytes {
pubKey, err := PublicKeyFromBytes(pubKeyBytes)
if err != nil {
return nil, err
}
comms[i], err = new(curves.PointEd25519).FromAffineCompressed(pubKey)
if err != nil {
return nil, err
}
}
return comms, nil
}
// KeyShareFromBytes converts byte array into KeyShare type
func KeyShareFromBytes(bytes []byte) *KeyShare {
field := curves.NewField(curves.Ed25519Order())
element := field.ElementFromBytes(bytes[4:])
// We set first 4 bytes as identifier
identifier := binary.BigEndian.Uint32(bytes[:4])
return &KeyShare{&v1.ShamirShare{Identifier: identifier, Value: element}}
}
// ShareConfiguration sets threshold and limit for the protocol
type ShareConfiguration struct {
T int // threshold
N int // total shares
}
// generateSharableKey generates a random key and returns the public key and private key in
// big-endian encoding. It returns an error if it cannot acquire sufficient randomness.
func generateSharableKey() (PublicKey, []byte, error) {
pub, priv, err := GenerateKey(nil)
if err != nil {
return nil, nil, err
}
// Internally the PrivateKey type is represented as the seed || public key, but we want to pull
// out seed to share which is the actual private key.
seed := priv.Seed()
// We must apply the key expansion to the seed before splitting the key.
// Ed25519 signing by default will apply this during signature generation, but since it involves
// a hash function, it breaks the relationship between shares and breaks aggregating signatures.
// Our signature generation does not apply this mutation at signing time.
//
// As per anything that comes from the ed25519 library this value should be treated as
// little-endian so we reverse it before using it.
expandedSeed := reverseBytes(ExpandSeed(seed))
// Lastly we must reduce this value into the size of the field so we can share it. This diverges
// from how the standard implementation treats this because their scalar multiplication accepts
// values up to the curve order but we must constrain it to be able to split it and aggregate.
//
// If you read the documentation for the ReducedElementFromBytes function we call below, it
// includes a big warning about how it will return non-uniform outputs depending on the input.
// This is true, but not a concern for keygen specifically because the value we are providing it
// has been generated as the ed25519 spec requires, which has a slight bias by definition of how
// the ExpandSeed operation works.
field := &curves.Field{Int: curves.Ed25519Order()}
expandedSeedReduced := field.ReducedElementFromBytes(expandedSeed)
return pub, expandedSeedReduced.Bytes(), nil
}
// GenerateSharedKey generates a random key, splits it, and returns the public key, shares, and VSS commitments.
func GenerateSharedKey(config *ShareConfiguration) (PublicKey, []*KeyShare, Commitments, error) {
pub, priv, err := generateSharableKey()
// pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
return nil, nil, nil, err
}
keyShares, commitments, err := splitPrivateKey(config, priv)
if err != nil {
return nil, nil, nil, err
}
return pub, keyShares, commitments, nil
}
// splitPrivateKey splits the secret into a set of secret shares and creates a set of commitments of them.
func splitPrivateKey(config *ShareConfiguration, priv []byte) ([]*KeyShare, Commitments, error) {
commitments, shares, err := split(priv, config)
if err != nil {
return nil, nil, err
}
keyShares := make([]*KeyShare, len(shares))
for i, s := range shares {
keyShares[i] = &KeyShare{s}
}
return keyShares, commitments, nil
}
// split contains core operations to split the secret and generate commitments.
func split(secret []byte, config *ShareConfiguration) ([]curves.Point, []*v1.ShamirShare, error) {
field := curves.NewField(curves.Ed25519Order())
shamir, err := v1.NewShamir(config.T, config.N, field)
if err != nil {
return nil, nil, fmt.Errorf("error in NewShamir")
}
shares, poly, err := shamir.GetSharesAndPolynomial(secret)
if err != nil {
return nil, nil, fmt.Errorf("error in GetSharesAndPolynomial")
}
// Generate the verifiable commitments to the polynomial for the shares
verifiers := make([]curves.Point, len(poly.Coefficients))
// curve := sharing.Ed25519()
for i, c := range poly.Coefficients {
// We have to reverse each coefficient, which is different than the method sharing.Split
reverseC := reverseBytes(c.Bytes())
var reverseInput [32]byte
copy(reverseInput[:], reverseC)
cScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(reverseInput[:])
if err != nil {
return nil, nil, fmt.Errorf("error in SetBytesCanonical reverseC")
}
v := curves.ED25519().Point.Generator().Mul(cScalar)
verifiers[i] = v
}
return verifiers, shares, nil
}
// Reconstruct recovers the secret from a set of secret shares.
func Reconstruct(keyShares []*KeyShare, config *ShareConfiguration) ([]byte, error) {
curve := v1.Ed25519()
field := curves.NewField(curve.Params().N)
shamir, err := v1.NewShamir(config.T, config.N, field)
if err != nil {
return nil, err
}
shares := make([]*v1.ShamirShare, len(keyShares))
for i, s := range keyShares {
shares[i] = s.ShamirShare
}
return shamir.Combine(shares...)
}
// VerifyVSS validates that a Share represents a solution to a Shamir polynomial
// in which len(commitments) + 1 solutions are required to construct the private
// key for the public key at commitments[0].
func (share *KeyShare) VerifyVSS(commitments Commitments, config *ShareConfiguration) (bool, error) {
if len(commitments) < config.T {
return false, fmt.Errorf("not enough verifiers to check")
}
field := curves.NewField(curves.Ed25519Order())
xBytes := make([]byte, 4)
binary.BigEndian.PutUint32(xBytes, share.Identifier)
x := field.ElementFromBytes(xBytes)
i := share.Value.Modulus.One()
// c_0
rhs := commitments[0]
// Compute the sum of products
// c_0 * c_1^i * c_2^{i^2} *c_3^{i^3}
for j := 1; j < len(commitments); j++ {
// i *= x
i = i.Mul(x)
var iBytes [32]byte
copy(iBytes[:], i.Bytes()[:])
iScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(iBytes[:])
if err != nil {
return false, fmt.Errorf("error in SetBytesCanonical iBytes")
}
c := commitments[j].Mul(iScalar)
// ...* c_j^{i^j}
rhs = rhs.Add(c)
}
vValue := reverseBytes(share.Value.Bytes())
var vInput [32]byte
copy(vInput[:], vValue)
vScalar, err := new(curves.ScalarEd25519).SetBytes(vInput[:])
if err != nil {
return false, err
}
lhs := curves.ED25519().ScalarBaseMult(vScalar)
// Check if lhs == rhs
return lhs.Equal(rhs), nil
}
-125
View File
@@ -1,125 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
v1 "github.com/onsonr/hway/crypto/sharing/v1"
)
func TestGenerateEd25519Key(t *testing.T) {
config := ShareConfiguration{T: 2, N: 3}
// Generate and verify correct number of shares are produced
pub, shares, _, err := GenerateSharedKey(&config)
require.NoError(t, err)
require.Equal(t, config.N, len(shares))
// Verify reconstuction works for all permutations of shares
shareVec := make([]*KeyShare, 2)
shareVec[0] = shares[0]
shareVec[1] = shares[1]
secret1, err := Reconstruct(shareVec, &config)
require.Nil(t, err)
shareVec[0] = shares[1]
shareVec[1] = shares[2]
secret2, err := Reconstruct(shareVec, &config)
require.Nil(t, err)
shareVec[0] = shares[0]
shareVec[1] = shares[2]
secret3, err := Reconstruct(shareVec, &config)
require.Nil(t, err)
require.Equal(t, secret1, secret2)
require.Equal(t, secret2, secret3)
// Need to reverse secret1
secret1 = reverseBytes(secret1)
var secret1Bytes [32]byte
copy(secret1Bytes[:], secret1)
scalar1, err := new(curves.ScalarEd25519).SetBytesCanonical(secret1Bytes[:])
require.NoError(t, err)
ed25519 := curves.ED25519()
pubFromSeed := ed25519.Point.Generator().Mul(scalar1)
require.NoError(t, err)
require.Equal(t, pubFromSeed.ToAffineCompressed(), pub.Bytes())
}
func TestGenerateEd25519KeyInvalidConfig(t *testing.T) {
invalidConfig := ShareConfiguration{T: 1, N: 1}
_, _, _, err := GenerateSharedKey(&invalidConfig)
require.NotNil(t, err)
require.Error(t, err)
invalidConfig = ShareConfiguration{T: 2, N: 1}
_, _, _, err = GenerateSharedKey(&invalidConfig)
require.NotNil(t, err)
require.Error(t, err)
}
func TestVerifyVSSEd25519(t *testing.T) {
config := ShareConfiguration{T: 2, N: 3}
pubKey1, shares1, commitments1, err := GenerateSharedKey(&config)
require.NoError(t, err)
pubKey2, shares2, commitments2, err := GenerateSharedKey(&config)
require.NoError(t, err)
require.Equal(t, pubKey1.Bytes(), commitments1[0].ToAffineCompressed())
require.Equal(t, pubKey2.Bytes(), commitments2[0].ToAffineCompressed())
for _, s := range shares1 {
ok, err := s.VerifyVSS(commitments1, &config)
require.NoError(t, err)
require.True(t, ok)
ok, _ = s.VerifyVSS(commitments2, &config)
require.True(t, !ok)
}
for _, s := range shares2 {
ok, err := s.VerifyVSS(commitments2, &config)
require.NoError(t, err)
require.True(t, ok)
ok, _ = s.VerifyVSS(commitments1, &config)
require.True(t, !ok)
}
}
func TestCommitmentsFromBytes(t *testing.T) {
config := ShareConfiguration{T: 2, N: 3}
_, _, comms, err := GenerateSharedKey(&config)
require.NoError(t, err)
recoveredComms, err := CommitmentsFromBytes(comms.CommitmentsToBytes())
require.NoError(t, err)
require.Equal(t, len(comms), len(recoveredComms))
for i := range comms {
require.True(t, comms[i].Equal(recoveredComms[i]))
}
_, err = CommitmentsFromBytes([][]byte{{0x01}})
require.Error(t, err)
}
func TestPublicKeyFromBytes(t *testing.T) {
_, err := PublicKeyFromBytes([]byte{0x01})
require.EqualError(t, err, "invalid public key size: 1")
}
func TestKeyShareFromBytes(t *testing.T) {
field := curves.NewField(curves.Ed25519Order())
share := &v1.ShamirShare{
Identifier: 2,
Value: field.NewElement(big.NewInt(3)),
}
shareBytes := share.Bytes()
recoveredShare := KeyShareFromBytes(shareBytes)
require.Equal(t, recoveredShare.ShamirShare, share)
}
-123
View File
@@ -1,123 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"crypto/rand"
"crypto/sha256"
"io"
"golang.org/x/crypto/hkdf"
"github.com/onsonr/hway/crypto/core/curves"
)
// NonceShare represents a share of a generated nonce.
type NonceShare struct {
*KeyShare
}
// NewNonceShare is a NonceShare construction
func NewNonceShare(identifier byte, secret []byte) *NonceShare {
return &NonceShare{NewKeyShare(identifier, secret)}
}
// NonceShareFromBytes unmashals a NonceShare from its bytes representation
func NonceShareFromBytes(bytes []byte) *NonceShare {
return &NonceShare{KeyShareFromBytes(bytes)}
}
func generateSharableNonce(s *KeyShare, p PublicKey, m Message) (PublicKey, []byte, error) {
// Create an HKDF reader that produces random bytes that we will use to create a nonce
hkdf, err := generateRandomHkdf(s, p, m)
if err != nil {
return nil, nil, err
}
// Generate a random nonce that is within the field range so that we can share it.
//
// This diverges from how the standard implementation treats it because their scalar
// multiplication accepts values up to the curve order, but we must constrain it to be able to
// split it and aggregate.
//
// WARN: This operation is not constant time and we are dealing with a secret value
nonce, err := curves.NewField(curves.Ed25519Order()).RandomElement(hkdf)
if err != nil {
return nil, nil, err
}
nonceBytes := nonce.Bytes()
reverseBytes := reverseBytes(nonceBytes)
var reverseInput [32]byte
copy(reverseInput[:], reverseBytes)
scalar, err := new(curves.ScalarEd25519).SetBytesCanonical(reverseInput[:])
if err != nil {
return nil, nil, err
}
// Generate the nonce pubkey by multiplying it by the base point.
noncePubkey := curves.ED25519().Point.Generator().Mul(scalar)
return noncePubkey.ToAffineCompressed(), nonceBytes, nil
}
// GenerateSharedNonce generates a random nonce, splits it, and returns the nonce pubkey, nonce shares, and
// VSS commitments.
func GenerateSharedNonce(config *ShareConfiguration, s *KeyShare, p PublicKey, m Message) (
PublicKey,
[]*NonceShare,
Commitments,
error,
) {
noncePubkey, nonce, err := generateSharableNonce(s, p, m)
if err != nil {
return nil, nil, nil, err
}
keyShares, vssCommitments, err := splitPrivateKey(config, nonce)
if err != nil {
return nil, nil, nil, err
}
nonceShares := make([]*NonceShare, len(keyShares))
for i, k := range keyShares {
nonceShares[i] = &NonceShare{k}
}
return noncePubkey, nonceShares, vssCommitments, nil
}
// Add returns the sum of two NonceShares.
func (n NonceShare) Add(other *NonceShare) *NonceShare {
return &NonceShare{
&KeyShare{
// use Add method from the shamir.Share type to sum the shares
// WARN: This is not constant time and deals with secrets
n.ShamirShare.Add(other.ShamirShare),
},
}
}
// generateRandomHkdf returns an HMAC-based extract-and-expand Key Derivation Function (see RFC 5869).
func generateRandomHkdf(s *KeyShare, p PublicKey, m Message) (io.Reader, error) {
// We _must_ introduce randomness to the HKDF to make the output non-deterministic because deterministic nonces open
// up threshold schemes to potential nonce-reuse attacks. We continue to use the HKDF that takes in context about
// what is going to be signed as it adds some protection against bad local randomness.
randNonce := make([]byte, SeedSize)
if _, err := io.ReadFull(rand.Reader, randNonce); err != nil {
return nil, err
}
var secret []byte
secret = append(secret, s.Bytes()...)
secret = append(secret, randNonce...)
info := []byte("ted25519nonce")
// We use info for non-secret inputs to limit an attacker's ability to influence the key.
info = append(info, p.Bytes()...)
info = append(info, m...)
return hkdf.New(sha256.New, secret, nil, info), nil
}
-106
View File
@@ -1,106 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
v1 "github.com/onsonr/hway/crypto/sharing/v1"
)
func TestNonceShareFromBytes(t *testing.T) {
field := curves.NewField(curves.Ed25519Order())
share := &v1.ShamirShare{
Identifier: 2,
Value: field.NewElement(big.NewInt(3)),
}
shareBytes := share.Bytes()
recoveredShare := NonceShareFromBytes(shareBytes)
require.Equal(t, recoveredShare.ShamirShare, share)
require.Equal(t, share.Identifier, uint32(2))
}
func TestGenerateSharedNonce_congruence(t *testing.T) {
config := &ShareConfiguration{T: 2, N: 3}
message := []byte("fnord!")
pubKey, keyShares, _, err := GenerateSharedKey(config)
require.NoError(t, err)
nonceCommitment, nonceShares, _, err := GenerateSharedNonce(config, keyShares[0], pubKey, message)
require.NoError(t, err)
field := curves.NewField(curves.Ed25519Order())
shamir, err := v1.NewShamir(config.T, config.N, field)
require.NoError(t, err)
nonce, err := shamir.Combine(toShamirShare(nonceShares)...)
require.NoError(t, err)
nonce = reverseBytes(nonce)
var nonceBytes [32]byte
copy(nonceBytes[:], nonce)
nonceScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(nonceBytes[:])
require.NoError(t, err)
ed25519 := curves.ED25519()
recoveredCommitment := ed25519.Point.Generator().Mul(nonceScalar)
require.Equal(t, recoveredCommitment.ToAffineCompressed(), nonceCommitment.Bytes())
}
func TestGenerateNonce_non_determinism(t *testing.T) {
config := &ShareConfiguration{T: 2, N: 3}
message := []byte("fnord!")
pubKey, keyShares, _, err := GenerateSharedKey(config)
require.NoError(t, err)
_, nonceShares1, _, err := GenerateSharedNonce(config, keyShares[0], pubKey, message)
require.NoError(t, err)
field := curves.NewField(curves.Ed25519Order())
shamir, err := v1.NewShamir(config.T, config.N, field)
require.NoError(t, err)
nonce1, err := shamir.Combine(toShamirShare(nonceShares1)...)
require.NoError(t, err)
_, nonceShares2, _, err := GenerateSharedNonce(config, keyShares[1], pubKey, message)
require.NoError(t, err)
nonce2, err := shamir.Combine(toShamirShare(nonceShares2)...)
require.NoError(t, err)
_, nonceShares3, _, err := GenerateSharedNonce(config, keyShares[0], pubKey, message)
require.NoError(t, err)
nonce3, err := shamir.Combine(toShamirShare(nonceShares3)...)
require.NoError(t, err)
require.NotEqual(t, nonce1, nonce2)
require.NotEqual(t, nonce1, nonce3)
}
func TestNonceSharesAdd(t *testing.T) {
one := NewNonceShare(0, []byte{0x01})
two := NewNonceShare(0, []byte{0x02})
// basic addition
sum := one.Add(two)
require.Equal(t, uint32(0), sum.Identifier)
require.Equal(t, []byte{0x03}, sum.Value.Bytes())
}
func TestNonceSharesAdd_errors(t *testing.T) {
one := NewNonceShare(0, []byte{0x01})
two := NewNonceShare(1, []byte{0x02})
require.PanicsWithValue(t, "identifiers must match for valid addition", func() {
one.Add(two)
})
}
func toShamirShare(nonceShares []*NonceShare) []*v1.ShamirShare {
shamirShares := make([]*v1.ShamirShare, len(nonceShares))
for i, n := range nonceShares {
shamirShares[i] = n.ShamirShare
}
return shamirShares
}
-55
View File
@@ -1,55 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import "strconv"
type Message []byte
func (m Message) String() string {
return string(m)
}
const signatureLength = 64
type PartialSignature struct {
ShareIdentifier byte // x-coordinate of which signer produced signature
Sig []byte // 64-byte signature: R || s
}
// NewPartialSignature creates a new PartialSignature
func NewPartialSignature(identifier byte, sig []byte) *PartialSignature {
if l := len(sig); l != signatureLength {
panic("ted25519: invalid partial signature length: " + strconv.Itoa(l))
}
return &PartialSignature{ShareIdentifier: identifier, Sig: sig}
}
// R returns the R component of the signature
func (sig *PartialSignature) R() []byte {
return sig.Sig[:32]
}
// S returns the s component of the signature
func (sig *PartialSignature) S() []byte {
return sig.Sig[32:]
}
func (sig *PartialSignature) Bytes() []byte {
return sig.Sig
}
// TSign generates a signature that can later be aggregated with others to produce a signature valid
// under the provided public key and nonce pair.
func TSign(message Message, key *KeyShare, pub PublicKey, nonce *NonceShare, noncePub PublicKey) *PartialSignature {
sig := ThresholdSign(
reverseBytes(key.Value.Bytes()), pub,
message,
reverseBytes(nonce.Value.Bytes()), noncePub,
)
return NewPartialSignature(byte(key.ShamirShare.Identifier), sig)
}
@@ -1,56 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
)
func TestPartialSignNormalSignature(t *testing.T) {
pub, priv, err := generateSharableKey()
require.NoError(t, err)
keyShare := NewKeyShare(0, priv)
r := big.NewInt(123456789).Bytes()
nonceShare := NewNonceShare(0, r)
r = reverseBytes(r)
var rInput [32]byte
copy(rInput[:], r)
scalar, err := new(curves.ScalarEd25519).SetBytesCanonical(rInput[:])
require.NoError(t, err)
noncePub := curves.ED25519().Point.Generator().Mul(scalar)
message := []byte("test message")
wrongMessage := []byte("wrong message")
sig := TSign(message, keyShare, pub, nonceShare, noncePub.ToAffineCompressed())
ok, _ := Verify(pub, message, sig.Sig)
require.True(t, ok)
ok, _ = Verify(pub, wrongMessage, sig.Sig)
require.False(t, ok)
}
func TestNewPartialSignature(t *testing.T) {
s := []byte("11111111111111111111111111111111")
r := []byte("22222222222222222222222222222222")
sigBytes := []byte("2222222222222222222222222222222211111111111111111111111111111111")
sig := NewPartialSignature(1, sigBytes)
require.Equal(t, byte(1), sig.ShareIdentifier)
require.Equal(t, s, sig.S())
require.Equal(t, r, sig.R())
require.Equal(t, sigBytes, sig.Bytes())
require.PanicsWithValue(t, "ted25519: invalid partial signature length: 3", func() {
NewPartialSignature(1, []byte("sig"))
})
}
-62
View File
@@ -1,62 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"bytes"
"fmt"
"github.com/onsonr/hway/crypto/core/curves"
v1 "github.com/onsonr/hway/crypto/sharing/v1"
)
type Signature = []byte
func Aggregate(sigs []*PartialSignature, config *ShareConfiguration) (Signature, error) {
if len(sigs) == 0 {
return nil, fmt.Errorf("ted25519: sigs must be non-empty")
}
// Verify all nonce pubKeys are the same by checking they all match the first one.
noncePubkey := sigs[0].R()
for i := 1; i < len(sigs); i++ {
if !bytes.Equal(sigs[i].R(), noncePubkey) {
return nil, fmt.Errorf(
"ted25519: unexpected nonce pubkey. got: %x expected: %x",
sigs[i].R(),
noncePubkey,
)
}
}
// Convert signatures to a Shamir share representation so we can recombine them
sigShares := make([]*v1.ShamirShare, len(sigs))
field := curves.NewField(curves.Ed25519Order())
shamir, err := v1.NewShamir(config.T, config.N, field)
if err != nil {
return nil, err
}
for i, sig := range sigs {
sigShares[i] = v1.NewShamirShare(
uint32(sig.ShareIdentifier),
reverseBytes(sig.S()),
field,
)
}
sigS, err := shamir.Combine(sigShares...)
if err != nil {
return nil, err
}
sig := make([]byte, signatureLength)
copy(sig[:32], noncePubkey) // R is the same on all sigs
copy(sig[32:], reverseBytes(sigS)) // be-to-le
return sig, nil
}
-93
View File
@@ -1,93 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestSigAgg(t *testing.T) {
config := ShareConfiguration{T: 2, N: 3}
pub, secretShares, _, err := GenerateSharedKey(&config)
require.NoError(t, err)
message := []byte("test message")
// Each party generates a nonce and we combine them together into an aggregate one
noncePub1, nonceShares1, _, err := GenerateSharedNonce(&config, secretShares[0], pub, message)
require.NoError(t, err)
noncePub2, nonceShares2, _, err := GenerateSharedNonce(&config, secretShares[1], pub, message)
require.NoError(t, err)
noncePub3, nonceShares3, _, err := GenerateSharedNonce(&config, secretShares[2], pub, message)
require.NoError(t, err)
nonceShares := []*NonceShare{
nonceShares1[0].Add(nonceShares2[0]).Add(nonceShares3[0]),
nonceShares1[1].Add(nonceShares2[1]).Add(nonceShares3[1]),
nonceShares1[2].Add(nonceShares2[2]).Add(nonceShares3[2]),
}
noncePub := GeAdd(GeAdd(noncePub1, noncePub2), noncePub3)
sig1 := TSign(message, secretShares[0], pub, nonceShares[0], noncePub)
sig2 := TSign(message, secretShares[1], pub, nonceShares[1], noncePub)
sig3 := TSign(message, secretShares[2], pub, nonceShares[2], noncePub)
// Test signer 1&2 verification
sig, err := Aggregate([]*PartialSignature{sig1, sig2}, &config)
require.NoError(t, err)
assertSignatureVerifies(t, pub, message, sig)
// Test signer 2&3 verification
sig, err = Aggregate([]*PartialSignature{sig2, sig3}, &config)
require.NoError(t, err)
assertSignatureVerifies(t, pub, message, sig)
// Test signer 1&3 verification
sig, err = Aggregate([]*PartialSignature{sig1, sig3}, &config)
require.NoError(t, err)
assertSignatureVerifies(t, pub, message, sig)
}
func TestSigAgg_validations(t *testing.T) {
config := ShareConfiguration{T: 2, N: 3}
_, err := Aggregate([]*PartialSignature{}, &config)
require.EqualError(t, err, "ted25519: sigs must be non-empty")
sig1bytes, _ := hex.DecodeString(
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" +
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
)
sig2bytes, _ := hex.DecodeString(
"92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da" +
"085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
)
sig1 := NewPartialSignature(1, sig1bytes)
sig2 := NewPartialSignature(2, sig2bytes)
_, err = Aggregate([]*PartialSignature{sig1, sig2}, &config)
require.EqualError(
t,
err,
fmt.Sprintf("ted25519: unexpected nonce pubkey. got: %x expected: %x", sig2bytes[:32], sig1bytes[:32]),
)
}
func assertSignatureVerifies(t *testing.T, pub, message, sig []byte) {
ok, _ := Verify(pub, message, sig)
if !ok {
t.Errorf("valid signature rejected")
}
wrongMessage := []byte("wrong message")
ok, _ = Verify(pub, wrongMessage, sig)
if ok {
t.Errorf("signature of different message accepted")
}
}
-55
View File
@@ -1,55 +0,0 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
/*
* This is a simple example of a 2x2 signature scheme to prove out a simpler case than the threshold
* variants. We don't intend to use it and it is not modeled off of any specific known protocol.
*/
package ted25519
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/hway/crypto/core/curves"
)
func AggregateSignatures(sig1, sig2 *PartialSignature) []byte {
field := curves.NewField(curves.Ed25519Order())
sig1s := field.ElementFromBytes(reverseBytes(sig1.S()))
sig2s := field.ElementFromBytes(reverseBytes(sig2.S()))
sigS := sig1s.Add(sig2s)
// Create signature as R || s. The R is the same so we use the same one
sig := make([]byte, SignatureSize)
copy(sig, sig1.R())
copy(sig[32:], reverseBytes(sigS.Bytes()))
return sig
}
func TestTwoByTwoSigning(t *testing.T) {
// generate shared pubkey
pub1, priv1, _ := generateSharableKey()
pub2, priv2, _ := generateSharableKey()
pub := GeAdd(pub1, pub2)
// generate shared nonce
pubr1, r1, _ := generateSharableKey()
pubr2, r2, _ := generateSharableKey()
noncePub := GeAdd(pubr1, pubr2)
// generate partial sigs
msg := []byte("test message")
sig1 := TSign(msg, NewKeyShare(0, priv1), pub, NewNonceShare(0, r1), noncePub)
sig2 := TSign(msg, NewKeyShare(0, priv2), pub, NewNonceShare(0, r2), noncePub)
// add sigs (s+s)
sig := AggregateSignatures(sig1, sig2)
ok, _ := Verify(pub, msg, sig)
require.True(t, ok, "signature failed verification")
}