mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 02:11:40 +00:00
(no commit message provided)
This commit is contained in:
committed by
Prad Nukala (aider)
parent
5fd43dfd6b
commit
2f976209db
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user