mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 15:31:38 +00:00
No commit suggestions generated
This commit is contained in:
Executable
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
aliases: [README]
|
||||
tags: []
|
||||
title: README
|
||||
linter-yaml-title-alias: README
|
||||
date created: Wednesday, April 17th 2024, 4:11:40 pm
|
||||
date modified: Thursday, April 18th 2024, 8:19:25 am
|
||||
---
|
||||
|
||||
## FROST: Flexible Round-Optimized Schnorr Threshold Signatures
|
||||
|
||||
This package is an implementation of t-of-n threshold signature of
|
||||
[FROST: Flexible Round-Optimized Schnorr Threshold Signatures](https://eprint.iacr.org/2020/852.pdf)
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package frost
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
type ChallengeDerive interface {
|
||||
DeriveChallenge(msg []byte, pubKey curves.Point, r curves.Point) (curves.Scalar, error)
|
||||
}
|
||||
|
||||
type Ed25519ChallengeDeriver struct{}
|
||||
|
||||
func (ed Ed25519ChallengeDeriver) DeriveChallenge(
|
||||
msg []byte,
|
||||
pubKey curves.Point,
|
||||
r curves.Point,
|
||||
) (curves.Scalar, error) {
|
||||
h := sha512.New()
|
||||
_, _ = h.Write(r.ToAffineCompressed())
|
||||
_, _ = h.Write(pubKey.ToAffineCompressed())
|
||||
_, _ = h.Write(msg)
|
||||
return new(curves.ScalarEd25519).SetBytesWide(h.Sum(nil))
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package frost is an implementation of t-of-n threshold signature of https://eprint.iacr.org/2020/852.pdf
|
||||
package frost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/dkg/frost"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Signer is a tSchnorr player performing the signing operation.
|
||||
type Signer struct {
|
||||
skShare curves.Scalar // secret signing share for this signer
|
||||
vkShare curves.Point // store verification key share
|
||||
verificationKey curves.Point // verification key
|
||||
id uint32 // The ID assigned to this signer's shamir share
|
||||
threshold uint32
|
||||
curve *curves.Curve
|
||||
round uint
|
||||
lCoeffs map[uint32]curves.Scalar // lCoeffs are Lagrange coefficients of each cosigner.
|
||||
cosigners []uint32
|
||||
state *state // Accumulated intermediate values associated with signing
|
||||
challengeDeriver ChallengeDerive
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// Round 1
|
||||
capD, capE curves.Point // capD, capE are commitments this signer generates in signing round 1
|
||||
smallD, smallE curves.Scalar // smallD, smallE are scalars this signer generates in signing round 1
|
||||
|
||||
// Round 2
|
||||
commitments map[uint32]*Round1Bcast // Store commitments broadcast after signing round 1
|
||||
msg []byte
|
||||
c curves.Scalar
|
||||
capRs map[uint32]curves.Point
|
||||
sumR curves.Point
|
||||
}
|
||||
|
||||
// NewSigner create a signer from a dkg participant
|
||||
// Note that we can pre-assign Lagrange coefficients lcoeffs of each cosigner. This optimizes performance.
|
||||
// See paragraph 3 of section 3 in the draft - https://tools.ietf.org/pdf/draft-komlo-frost-00.pdf
|
||||
func NewSigner(
|
||||
info *frost.DkgParticipant,
|
||||
id, thresh uint32,
|
||||
lcoeffs map[uint32]curves.Scalar,
|
||||
cosigners []uint32,
|
||||
challengeDeriver ChallengeDerive,
|
||||
) (*Signer, error) {
|
||||
if info == nil || len(cosigners) == 0 || len(lcoeffs) == 0 {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
if thresh > uint32(len(cosigners)) {
|
||||
return nil, fmt.Errorf("threshold is higher than number of signers")
|
||||
}
|
||||
|
||||
if len(lcoeffs) != len(cosigners) {
|
||||
return nil, fmt.Errorf("expected coefficients to be equal to number of cosigners")
|
||||
}
|
||||
|
||||
// Check if cosigners and lcoeffs contain the same IDs
|
||||
for i := 0; i < len(cosigners); i++ {
|
||||
id := cosigners[i]
|
||||
if _, ok := lcoeffs[id]; !ok {
|
||||
return nil, fmt.Errorf("lcoeffs and cosigners have inconsistent ID")
|
||||
}
|
||||
}
|
||||
|
||||
return &Signer{
|
||||
skShare: info.SkShare,
|
||||
vkShare: info.VkShare,
|
||||
verificationKey: info.VerificationKey,
|
||||
id: id,
|
||||
threshold: thresh,
|
||||
curve: info.Curve,
|
||||
round: 1,
|
||||
lCoeffs: lcoeffs,
|
||||
cosigners: cosigners,
|
||||
state: &state{},
|
||||
challengeDeriver: challengeDeriver,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package frost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Round1Bcast contains values to be broadcast to all players after the completion of signing round 1.
|
||||
type Round1Bcast struct {
|
||||
Di, Ei curves.Point
|
||||
}
|
||||
|
||||
func (result *Round1Bcast) Encode() ([]byte, error) {
|
||||
gob.Register(result.Di) // just the point for now
|
||||
gob.Register(result.Ei)
|
||||
buf := &bytes.Buffer{}
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(result); err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't encode round 1 broadcast")
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (result *Round1Bcast) Decode(input []byte) error {
|
||||
buf := bytes.NewBuffer(input)
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err := dec.Decode(result); err != nil {
|
||||
return errors.Wrap(err, "couldn't encode round 1 broadcast")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (signer *Signer) SignRound1() (*Round1Bcast, error) {
|
||||
// Make sure signer is not empty
|
||||
if signer == nil || signer.curve == nil {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Make sure round number is correct
|
||||
if signer.round != 1 {
|
||||
return nil, internal.ErrInvalidRound
|
||||
}
|
||||
|
||||
// Step 1 - Sample di, ei
|
||||
di := signer.curve.Scalar.Random(crand.Reader)
|
||||
|
||||
ei := signer.curve.Scalar.Random(crand.Reader)
|
||||
|
||||
// Step 2 - Compute Di, Ei
|
||||
Di := signer.curve.ScalarBaseMult(di)
|
||||
|
||||
Ei := signer.curve.ScalarBaseMult(ei)
|
||||
|
||||
// Update round number
|
||||
signer.round = 2
|
||||
|
||||
// Store di, ei, Di, Ei locally and broadcast Di, Ei
|
||||
signer.state.capD = Di
|
||||
signer.state.capE = Ei
|
||||
signer.state.smallD = di
|
||||
signer.state.smallE = ei
|
||||
return &Round1Bcast{
|
||||
Di,
|
||||
Ei,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package frost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Round2Bcast contains values that will be broadcast to other signers after completion of round 2.
|
||||
type Round2Bcast struct {
|
||||
Zi curves.Scalar
|
||||
Vki curves.Point
|
||||
}
|
||||
|
||||
func (result *Round2Bcast) Encode() ([]byte, error) {
|
||||
gob.Register(result.Zi)
|
||||
gob.Register(result.Vki) // just the point for now
|
||||
buf := &bytes.Buffer{}
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(result); err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't encode round 1 broadcast")
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (result *Round2Bcast) Decode(input []byte) error {
|
||||
buf := bytes.NewBuffer(input)
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err := dec.Decode(result); err != nil {
|
||||
return errors.Wrap(err, "couldn't encode round 1 broadcast")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignRound2 implements FROST signing round 2.
|
||||
func (signer *Signer) SignRound2(
|
||||
msg []byte,
|
||||
round2Input map[uint32]*Round1Bcast,
|
||||
) (*Round2Bcast, error) {
|
||||
// Make sure necessary items of signer are not empty
|
||||
if signer == nil || signer.curve == nil || signer.state == nil {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Make sure those private d is not empty and not zero
|
||||
if signer.state.smallD == nil || signer.state.smallD.IsZero() {
|
||||
return nil, fmt.Errorf("empty d or d is zero")
|
||||
}
|
||||
|
||||
// Make sure those private e is not empty and not zero
|
||||
if signer.state.smallE == nil || signer.state.smallE.IsZero() {
|
||||
return nil, fmt.Errorf("empty e or e is zero")
|
||||
}
|
||||
|
||||
// Make sure msg is not empty
|
||||
if len(msg) == 0 {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Make sure the round number is correct
|
||||
if signer.round != 2 {
|
||||
return nil, internal.ErrInvalidRound
|
||||
}
|
||||
|
||||
// Check length of round2Input
|
||||
if uint32(len(round2Input)) != signer.threshold {
|
||||
return nil, fmt.Errorf("invalid length of round2Input")
|
||||
}
|
||||
|
||||
// Step 2 - Check Dj, Ej on the curve and Store round2Input
|
||||
for id, input := range round2Input {
|
||||
if input == nil || input.Di == nil || input.Ei == nil {
|
||||
return nil, fmt.Errorf("round2Input is nil from participant with id %d", id)
|
||||
}
|
||||
if !input.Di.IsOnCurve() || input.Di.IsIdentity() {
|
||||
return nil, fmt.Errorf("commitment Di is not on the curve with id %d", id)
|
||||
}
|
||||
if !input.Ei.IsOnCurve() || input.Ei.IsIdentity() {
|
||||
return nil, fmt.Errorf("commitment Ei is not on the curve with id %d", id)
|
||||
}
|
||||
}
|
||||
// Store Dj, Ej for further usage.
|
||||
signer.state.commitments = round2Input
|
||||
|
||||
// Step 3-6
|
||||
R := signer.curve.NewIdentityPoint()
|
||||
var ri curves.Scalar
|
||||
Rs := make(map[uint32]curves.Point, signer.threshold)
|
||||
for id, data := range round2Input {
|
||||
// Construct the blob (j, m, {Dj, Ej})
|
||||
blob := concatHashArray(id, msg, round2Input, signer.cosigners)
|
||||
|
||||
// Step 4 - rj = H(j,m,{Dj,Ej}_{j in [1...t]})
|
||||
rj := signer.curve.Scalar.Hash(blob)
|
||||
if signer.id == id {
|
||||
ri = rj
|
||||
}
|
||||
|
||||
// Step 5 - R_j = D_j + r_j*E_j
|
||||
rjEj := data.Ei.Mul(rj)
|
||||
Rj := rjEj.Add(data.Di)
|
||||
// assign Rj
|
||||
Rs[id] = Rj
|
||||
|
||||
// Step 6 - R = R+Rj
|
||||
R = R.Add(Rj)
|
||||
}
|
||||
|
||||
// Step 7 - c = H(m, R)
|
||||
c, err := signer.challengeDeriver.DeriveChallenge(msg, signer.verificationKey, R)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Step 8 - Record c, R, Rjs
|
||||
signer.state.c = c
|
||||
signer.state.capRs = Rs
|
||||
signer.state.sumR = R
|
||||
|
||||
// Step 9 - zi = di + ei*ri + Li*ski*c
|
||||
Li := signer.lCoeffs[signer.id]
|
||||
Liski := Li.Mul(signer.skShare)
|
||||
|
||||
Liskic := Liski.Mul(c)
|
||||
|
||||
if R.IsNegative() {
|
||||
signer.state.smallE = signer.state.smallE.Neg()
|
||||
signer.state.smallD = signer.state.smallD.Neg()
|
||||
}
|
||||
|
||||
eiri := signer.state.smallE.Mul(ri)
|
||||
|
||||
// Compute zi = di+ei*ri+Li*ski*c
|
||||
zi := Liskic.Add(eiri)
|
||||
zi = zi.Add(signer.state.smallD)
|
||||
|
||||
// Update round number and store message
|
||||
signer.round = 3
|
||||
signer.state.msg = msg
|
||||
|
||||
// set smallD and smallE to zero since they are one-time use
|
||||
signer.state.smallD = signer.curve.NewScalar()
|
||||
signer.state.smallE = signer.curve.NewScalar()
|
||||
|
||||
// Step 10 - Broadcast zi, vki to other participants
|
||||
return &Round2Bcast{
|
||||
zi,
|
||||
signer.vkShare,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// concatHashArray puts id, msg and (Dj,Ej), j=1...t into a byte array
|
||||
func concatHashArray(
|
||||
id uint32,
|
||||
msg []byte,
|
||||
round2Input map[uint32]*Round1Bcast,
|
||||
cosigners []uint32,
|
||||
) []byte {
|
||||
var blob []byte
|
||||
// Append identity id
|
||||
blob = append(blob, byte(id))
|
||||
|
||||
// Append message msg
|
||||
blob = append(blob, msg...)
|
||||
|
||||
// Append (Dj, Ej) for all j in [1...t]
|
||||
for i := 0; i < len(cosigners); i++ {
|
||||
id := cosigners[i]
|
||||
bytesDi := round2Input[id].Di.ToAffineCompressed()
|
||||
bytesEi := round2Input[id].Ei.ToAffineCompressed()
|
||||
|
||||
// Following the spec, we should add each party's identity.
|
||||
blob = append(blob, byte(id))
|
||||
blob = append(blob, bytesDi...)
|
||||
blob = append(blob, bytesEi...)
|
||||
}
|
||||
return blob
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package frost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Round3Bcast contains the output of FROST signature, i.e., it contains FROST signature (z,c) and the
|
||||
// corresponding message msg.
|
||||
type Round3Bcast struct {
|
||||
R curves.Point
|
||||
Z, C curves.Scalar
|
||||
msg []byte
|
||||
}
|
||||
|
||||
// Define frost signature type
|
||||
type Signature struct {
|
||||
Z curves.Scalar
|
||||
C curves.Scalar
|
||||
}
|
||||
|
||||
func (signer *Signer) SignRound3(round3Input map[uint32]*Round2Bcast) (*Round3Bcast, error) {
|
||||
// Make sure signer is not empty
|
||||
if signer == nil || signer.curve == nil {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Make sure signer's smallD and smallE are zero
|
||||
if !signer.state.smallD.IsZero() || !signer.state.smallE.IsZero() {
|
||||
return nil, fmt.Errorf(
|
||||
"signer's private smallD and smallE should be zero since one-time use",
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure the signer has had the msg
|
||||
if len(signer.state.msg) == 0 {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Validate Round3Input
|
||||
if round3Input == nil {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
for _, data := range round3Input {
|
||||
if data == nil {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the signer has commitments stored at the end of round 1.
|
||||
if signer.state.commitments == nil || len(signer.state.commitments) != len(round3Input) {
|
||||
return nil, internal.ErrNilArguments
|
||||
}
|
||||
|
||||
// Make sure the round number is correct
|
||||
if signer.round != 3 {
|
||||
return nil, internal.ErrInvalidRound
|
||||
}
|
||||
|
||||
// Round2 Input has different length of threshold
|
||||
if uint32(len(round3Input)) != signer.threshold {
|
||||
return nil, fmt.Errorf("invalid length of round3Input")
|
||||
}
|
||||
|
||||
// Step 1-3
|
||||
// Step 1: For j in [1...t]
|
||||
z := signer.curve.NewScalar()
|
||||
negate := signer.state.sumR.IsNegative()
|
||||
for id, data := range round3Input {
|
||||
zj := data.Zi
|
||||
vkj := data.Vki
|
||||
|
||||
// Step 2: Verify zj*G = Rj + c*Lj*vkj
|
||||
// zj*G
|
||||
zjG := signer.curve.ScalarBaseMult(zj)
|
||||
|
||||
// c*Lj
|
||||
cLj := signer.state.c.Mul(signer.lCoeffs[id])
|
||||
|
||||
// cLjvkj
|
||||
cLjvkj := vkj.Mul(cLj)
|
||||
|
||||
// Rj + c*Lj*vkj
|
||||
Rj := signer.state.capRs[id]
|
||||
if negate {
|
||||
Rj = Rj.Neg()
|
||||
}
|
||||
right := cLjvkj.Add(Rj)
|
||||
|
||||
// Check equation
|
||||
if !zjG.Equal(right) {
|
||||
return nil, fmt.Errorf("zjG != right with participant id %d", id)
|
||||
}
|
||||
|
||||
// Step 3 - z = z+zj
|
||||
z = z.Add(zj)
|
||||
}
|
||||
|
||||
// Step 4 - 7: Self verify the signature (z, c)
|
||||
// Step 5 - R' = z*G + (-c)*vk
|
||||
zG := signer.curve.ScalarBaseMult(z)
|
||||
cvk := signer.verificationKey.Mul(signer.state.c.Neg())
|
||||
tempR := zG.Add(cvk)
|
||||
// Step 6 - c' = H(m, R')
|
||||
tempC, err := signer.challengeDeriver.DeriveChallenge(
|
||||
signer.state.msg,
|
||||
signer.verificationKey,
|
||||
tempR,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Step 7 - Check c = c'
|
||||
if tempC.Cmp(signer.state.c) != 0 {
|
||||
return nil, fmt.Errorf("invalid signature: c != c'")
|
||||
}
|
||||
|
||||
// Updating round number
|
||||
signer.round = 4
|
||||
|
||||
// Step 8 - Broadcast signature and message
|
||||
return &Round3Bcast{
|
||||
signer.state.sumR,
|
||||
z,
|
||||
signer.state.c,
|
||||
signer.state.msg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Method to verify a frost signature.
|
||||
func Verify(
|
||||
curve *curves.Curve,
|
||||
challengeDeriver ChallengeDerive,
|
||||
vk curves.Point,
|
||||
msg []byte,
|
||||
signature *Signature,
|
||||
) (bool, error) {
|
||||
if vk == nil || msg == nil || len(msg) == 0 || signature.C == nil || signature.Z == nil {
|
||||
return false, fmt.Errorf("invalid input")
|
||||
}
|
||||
z := signature.Z
|
||||
c := signature.C
|
||||
|
||||
// R' = z*G - c*vk
|
||||
zG := curve.ScalarBaseMult(z)
|
||||
cvk := vk.Mul(c.Neg())
|
||||
tempR := zG.Add(cvk)
|
||||
|
||||
// c' = H(m, R')
|
||||
tempC, err := challengeDeriver.DeriveChallenge(msg, vk, tempR)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check c == c'
|
||||
if tempC.Cmp(c) != 0 {
|
||||
return false, fmt.Errorf("invalid signature: c != c'")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package frost
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
dkg "github.com/sonr-io/sonr/crypto/dkg/frost"
|
||||
"github.com/sonr-io/sonr/crypto/sharing"
|
||||
)
|
||||
|
||||
var (
|
||||
testCurve = curves.ED25519()
|
||||
ctx = "string to prevent replay attack"
|
||||
)
|
||||
|
||||
// Create two DKG participants.
|
||||
func PrepareDkgOutput(t *testing.T) (*dkg.DkgParticipant, *dkg.DkgParticipant) {
|
||||
// Initiate two participants and running DKG round 1
|
||||
p1, err := dkg.NewDkgParticipant(1, 2, ctx, testCurve, 2)
|
||||
require.NoError(t, err)
|
||||
p2, err := dkg.NewDkgParticipant(2, 2, ctx, testCurve, 1)
|
||||
require.NoError(t, err)
|
||||
bcast1, p2psend1, _ := p1.Round1(nil)
|
||||
bcast2, p2psend2, _ := p2.Round1(nil)
|
||||
bcast := make(map[uint32]*dkg.Round1Bcast)
|
||||
p2p1 := make(map[uint32]*sharing.ShamirShare)
|
||||
p2p2 := make(map[uint32]*sharing.ShamirShare)
|
||||
bcast[1] = bcast1
|
||||
bcast[2] = bcast2
|
||||
p2p1[2] = p2psend2[1]
|
||||
p2p2[1] = p2psend1[2]
|
||||
|
||||
// Running DKG round 2
|
||||
_, _ = p1.Round2(bcast, p2p1)
|
||||
_, _ = p2.Round2(bcast, p2p2)
|
||||
return p1, p2
|
||||
}
|
||||
|
||||
// Test FROST signing round 1
|
||||
func TestSignRound1Works(t *testing.T) {
|
||||
p1, p2 := PrepareDkgOutput(t)
|
||||
require.NotNil(t, p1)
|
||||
require.NotNil(t, p2)
|
||||
|
||||
scheme, _ := sharing.NewShamir(2, 2, testCurve)
|
||||
lCoeffs, err := scheme.LagrangeCoeffs([]uint32{p1.Id, p2.Id})
|
||||
require.NoError(t, err)
|
||||
signer1, err := NewSigner(p1, 1, 2, lCoeffs, []uint32{1, 2}, &Ed25519ChallengeDeriver{})
|
||||
require.NoError(t, err)
|
||||
round1Out, _ := signer1.SignRound1()
|
||||
require.NotNil(t, round1Out.Ei)
|
||||
require.NotNil(t, round1Out.Di)
|
||||
require.NotNil(t, signer1.state.smallE)
|
||||
require.NotNil(t, signer1.state.smallD)
|
||||
require.NotNil(t, signer1.state.capD)
|
||||
require.NotNil(t, signer1.state.capE)
|
||||
require.Equal(t, signer1.round, uint(2))
|
||||
require.Equal(t, signer1.cosigners, []uint32{1, 2})
|
||||
}
|
||||
|
||||
func TestSignRound1RepeatCall(t *testing.T) {
|
||||
p1, p2 := PrepareDkgOutput(t)
|
||||
scheme, _ := sharing.NewShamir(2, 2, testCurve)
|
||||
lCoeffs, err := scheme.LagrangeCoeffs([]uint32{p1.Id, p2.Id})
|
||||
require.NoError(t, err)
|
||||
signer1, _ := NewSigner(p1, 1, 2, lCoeffs, []uint32{1, 2}, &Ed25519ChallengeDeriver{})
|
||||
_, err = signer1.SignRound1()
|
||||
require.NoError(t, err)
|
||||
_, err = signer1.SignRound1()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func PrepareNewSigners(t *testing.T) (*Signer, *Signer) {
|
||||
threshold := uint32(2)
|
||||
limit := uint32(2)
|
||||
p1, p2 := PrepareDkgOutput(t)
|
||||
require.Equal(t, p1.VerificationKey, p2.VerificationKey)
|
||||
scheme, err := sharing.NewShamir(threshold, limit, testCurve)
|
||||
// field = sharing.NewField(p1.curve.Params().N)
|
||||
require.NotNil(t, scheme)
|
||||
require.NoError(t, err)
|
||||
lCoeffs, err := scheme.LagrangeCoeffs([]uint32{p1.Id, p2.Id})
|
||||
require.NotNil(t, lCoeffs[1])
|
||||
require.NotNil(t, lCoeffs[2])
|
||||
require.NoError(t, err)
|
||||
signer1, err := NewSigner(
|
||||
p1,
|
||||
p1.Id,
|
||||
threshold,
|
||||
lCoeffs,
|
||||
[]uint32{p1.Id, p2.Id},
|
||||
&Ed25519ChallengeDeriver{},
|
||||
)
|
||||
require.NotNil(t, signer1)
|
||||
require.NoError(t, err)
|
||||
signer2, err := NewSigner(
|
||||
p2,
|
||||
p2.Id,
|
||||
threshold,
|
||||
lCoeffs,
|
||||
[]uint32{p1.Id, p2.Id},
|
||||
&Ed25519ChallengeDeriver{},
|
||||
)
|
||||
require.NotNil(t, signer2)
|
||||
require.NoError(t, err)
|
||||
return signer1, signer2
|
||||
}
|
||||
|
||||
func TestSignRound2Works(t *testing.T) {
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 := PrepareNewSigners(t)
|
||||
require.Equal(t, signer1.verificationKey, signer2.verificationKey)
|
||||
require.NotNil(t, signer1)
|
||||
require.NotNil(t, signer2)
|
||||
round1Out1, _ := signer1.SignRound1()
|
||||
round1Out2, _ := signer2.SignRound1()
|
||||
round2Input := make(map[uint32]*Round1Bcast)
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
|
||||
// Actual Test
|
||||
msg := []byte("message")
|
||||
round2Out, err := signer1.SignRound2(msg, round2Input)
|
||||
require.NotNil(t, round2Out)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, signer1.round, uint(3))
|
||||
require.NotNil(t, signer1.state.commitments)
|
||||
require.Equal(t, signer1.state.msg, msg)
|
||||
require.True(t, signer1.state.smallD.IsZero())
|
||||
require.True(t, signer1.state.smallE.IsZero())
|
||||
}
|
||||
|
||||
func TestSignRound2RepeatCall(t *testing.T) {
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 := PrepareNewSigners(t)
|
||||
require.NotNil(t, signer1)
|
||||
require.NotNil(t, signer2)
|
||||
round1Out1, _ := signer1.SignRound1()
|
||||
round1Out2, _ := signer2.SignRound1()
|
||||
round2Input := make(map[uint32]*Round1Bcast)
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
|
||||
// Actual Test
|
||||
msg := []byte("message")
|
||||
_, err := signer1.SignRound2(msg, round2Input)
|
||||
require.NoError(t, err)
|
||||
_, err = signer1.SignRound2(msg, round2Input)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSignRound2BadInput(t *testing.T) {
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 := PrepareNewSigners(t)
|
||||
_, _ = signer1.SignRound1()
|
||||
round1Out2, _ := signer2.SignRound1()
|
||||
round2Input := make(map[uint32]*Round1Bcast)
|
||||
|
||||
// Actual Test: Set an input to nil
|
||||
round2Input[signer1.id] = nil
|
||||
round2Input[signer2.id] = round1Out2
|
||||
msg := []byte("message")
|
||||
_, err := signer1.SignRound2(msg, round2Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 = PrepareNewSigners(t)
|
||||
round1Out1, _ := signer1.SignRound1()
|
||||
round1Out2, _ = signer2.SignRound1()
|
||||
round2Input = make(map[uint32]*Round1Bcast)
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
// Actual Test: Nil message
|
||||
_, err = signer1.SignRound2(nil, round2Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 = PrepareNewSigners(t)
|
||||
round1Out1, _ = signer1.SignRound1()
|
||||
round1Out2, _ = signer2.SignRound1()
|
||||
round2Input = make(map[uint32]*Round1Bcast)
|
||||
|
||||
// Actual Test: Set invalid round2Input length
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
round2Input[3] = round1Out2
|
||||
_, err = signer1.SignRound2(msg, round2Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 = PrepareNewSigners(t)
|
||||
round1Out1, _ = signer1.SignRound1()
|
||||
round1Out2, _ = signer2.SignRound1()
|
||||
round2Input = make(map[uint32]*Round1Bcast)
|
||||
|
||||
// Actual Test: Set invalid round2Input length
|
||||
round1Out2.Ei = nil
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
_, err = signer1.SignRound2(msg, round2Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Preparing round 2 inputs
|
||||
signer1, signer2 = PrepareNewSigners(t)
|
||||
round1Out1, _ = signer1.SignRound1()
|
||||
round1Out2, _ = signer2.SignRound1()
|
||||
round2Input = make(map[uint32]*Round1Bcast)
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
|
||||
// Actual Test: Set nil smallD and smallE
|
||||
signer1.state.smallD = testCurve.NewScalar()
|
||||
signer1.state.smallE = nil
|
||||
_, err = signer1.SignRound2(msg, round2Input)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func PrepareRound3Input(t *testing.T) (*Signer, *Signer, map[uint32]*Round2Bcast) {
|
||||
// Running sign round 1
|
||||
threshold := uint32(2)
|
||||
limit := uint32(2)
|
||||
p1, p2 := PrepareDkgOutput(t)
|
||||
require.Equal(t, p1.VerificationKey, p2.VerificationKey)
|
||||
scheme, err := sharing.NewShamir(threshold, limit, testCurve)
|
||||
require.NotNil(t, scheme)
|
||||
require.NoError(t, err)
|
||||
lCoeffs, err := scheme.LagrangeCoeffs([]uint32{p1.Id, p2.Id})
|
||||
require.NotNil(t, lCoeffs[1])
|
||||
require.NotNil(t, lCoeffs[2])
|
||||
require.NoError(t, err)
|
||||
|
||||
signer1, err := NewSigner(
|
||||
p1,
|
||||
p1.Id,
|
||||
threshold,
|
||||
lCoeffs,
|
||||
[]uint32{p1.Id, p2.Id},
|
||||
&Ed25519ChallengeDeriver{},
|
||||
)
|
||||
require.NotNil(t, signer1)
|
||||
require.NoError(t, err)
|
||||
signer2, err := NewSigner(
|
||||
p2,
|
||||
p2.Id,
|
||||
threshold,
|
||||
lCoeffs,
|
||||
[]uint32{p1.Id, p2.Id},
|
||||
&Ed25519ChallengeDeriver{},
|
||||
)
|
||||
require.NotNil(t, signer2)
|
||||
require.NoError(t, err)
|
||||
|
||||
round1Out1, _ := signer1.SignRound1()
|
||||
round1Out2, _ := signer2.SignRound1()
|
||||
round2Input := make(map[uint32]*Round1Bcast, threshold)
|
||||
round2Input[signer1.id] = round1Out1
|
||||
round2Input[signer2.id] = round1Out2
|
||||
|
||||
// Running sign round 2
|
||||
msg := []byte("message")
|
||||
round2Out1, _ := signer1.SignRound2(msg, round2Input)
|
||||
round2Out2, _ := signer2.SignRound2(msg, round2Input)
|
||||
round3Input := make(map[uint32]*Round2Bcast, threshold)
|
||||
round3Input[signer1.id] = round2Out1
|
||||
round3Input[signer2.id] = round2Out2
|
||||
return signer1, signer2, round3Input
|
||||
}
|
||||
|
||||
func TestSignRound3Works(t *testing.T) {
|
||||
signer1, signer2, round3Input := PrepareRound3Input(t)
|
||||
round3Out1, err := signer1.SignRound3(round3Input)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, round3Out1)
|
||||
round3Out2, err := signer2.SignRound3(round3Input)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, round3Out2)
|
||||
// signer1 and signer2 outputs the same signature
|
||||
require.Equal(t, round3Out1.Z, round3Out2.Z)
|
||||
require.Equal(t, round3Out1.C, round3Out2.C)
|
||||
|
||||
// test verify method
|
||||
msg := []byte("message")
|
||||
signature := &Signature{
|
||||
round3Out1.Z,
|
||||
round3Out1.C,
|
||||
}
|
||||
vk := signer1.verificationKey
|
||||
ok, err := Verify(signer1.curve, signer1.challengeDeriver, vk, msg, signature)
|
||||
require.True(t, ok)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok, err = Verify(signer2.curve, signer2.challengeDeriver, vk, msg, signature)
|
||||
require.True(t, ok)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSignRound3RepeatCall(t *testing.T) {
|
||||
signer1, _, round3Input := PrepareRound3Input(t)
|
||||
_, err := signer1.SignRound3(round3Input)
|
||||
require.NoError(t, err)
|
||||
_, err = signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSignRound3BadInput(t *testing.T) {
|
||||
signer1, _, round3Input := PrepareRound3Input(t)
|
||||
|
||||
// Actual test: nil input
|
||||
round3Input[signer1.id] = nil
|
||||
_, err := signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
round3Input = nil
|
||||
_, err = signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Actual test: set invalid length of round3Input
|
||||
signer1, _, round3Input = PrepareRound3Input(t)
|
||||
round3Input[100] = round3Input[signer1.id]
|
||||
_, err = signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Actual test: maul the round3Input
|
||||
signer1, _, round3Input = PrepareRound3Input(t)
|
||||
round3Input[signer1.id].Zi = round3Input[signer1.id].Zi.Add(testCurve.Scalar.New(2))
|
||||
_, err = signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
|
||||
// Actual test: set non-zero smallD and smallE
|
||||
signer1, _, round3Input = PrepareRound3Input(t)
|
||||
signer1.state.smallD = testCurve.Scalar.New(1)
|
||||
_, err = signer1.SignRound3(round3Input)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFullRoundsWorks(t *testing.T) {
|
||||
// Give a full-round test (FROST DKG + FROST Signing) with threshold = 2 and limit = 3, same as the test of tECDSA
|
||||
threshold := 2
|
||||
limit := 3
|
||||
|
||||
// Prepare DKG participants
|
||||
participants := make(map[uint32]*dkg.DkgParticipant, limit)
|
||||
for i := 1; i <= limit; i++ {
|
||||
otherIds := make([]uint32, limit-1)
|
||||
idx := 0
|
||||
for j := 1; j <= limit; j++ {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
otherIds[idx] = uint32(j)
|
||||
idx++
|
||||
}
|
||||
p, err := dkg.NewDkgParticipant(uint32(i), uint32(threshold), ctx, testCurve, otherIds...)
|
||||
require.NoError(t, err)
|
||||
participants[uint32(i)] = p
|
||||
}
|
||||
|
||||
// FROST DKG round 1
|
||||
rnd1Bcast := make(map[uint32]*dkg.Round1Bcast, len(participants))
|
||||
rnd1P2p := make(map[uint32]dkg.Round1P2PSend, len(participants))
|
||||
for id, p := range participants {
|
||||
bcast, p2psend, err := p.Round1(nil)
|
||||
require.NoError(t, err)
|
||||
rnd1Bcast[id] = bcast
|
||||
rnd1P2p[id] = p2psend
|
||||
}
|
||||
|
||||
// FROST DKG round 2
|
||||
for id := range rnd1Bcast {
|
||||
rnd1P2pForP := make(map[uint32]*sharing.ShamirShare)
|
||||
for jid := range rnd1P2p {
|
||||
if jid == id {
|
||||
continue
|
||||
}
|
||||
rnd1P2pForP[jid] = rnd1P2p[jid][id]
|
||||
}
|
||||
_, err := participants[id].Round2(rnd1Bcast, rnd1P2pForP)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Prepare Lagrange coefficients
|
||||
scheme, _ := sharing.NewShamir(uint32(threshold), uint32(limit), testCurve)
|
||||
|
||||
// Here we use {1, 3} as 2 of 3 cosigners, we can also set cosigners as {1, 2}, {2, 3}
|
||||
signerIds := []uint32{1, 3}
|
||||
lCoeffs, err := scheme.LagrangeCoeffs(signerIds)
|
||||
require.NoError(t, err)
|
||||
signers := make(map[uint32]*Signer, threshold)
|
||||
for _, id := range signerIds {
|
||||
signers[id], err = NewSigner(
|
||||
participants[id],
|
||||
id,
|
||||
uint32(threshold),
|
||||
lCoeffs,
|
||||
signerIds,
|
||||
&Ed25519ChallengeDeriver{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, signers[id].skShare)
|
||||
}
|
||||
|
||||
// Running sign round 1
|
||||
round2Input := make(map[uint32]*Round1Bcast, threshold)
|
||||
for id := range signers {
|
||||
round1Out, err := signers[id].SignRound1()
|
||||
require.NoError(t, err)
|
||||
round2Input[signers[id].id] = round1Out
|
||||
}
|
||||
|
||||
// Running sign round 2
|
||||
msg := []byte("message")
|
||||
round3Input := make(map[uint32]*Round2Bcast, threshold)
|
||||
for id := range signers {
|
||||
round2Out, err := signers[id].SignRound2(msg, round2Input)
|
||||
require.NoError(t, err)
|
||||
round3Input[signers[id].id] = round2Out
|
||||
}
|
||||
|
||||
// Running sign round 3
|
||||
result := make(map[uint32]*Round3Bcast, threshold)
|
||||
for id := range signers {
|
||||
round3Out, err := signers[id].SignRound3(round3Input)
|
||||
require.NoError(t, err)
|
||||
result[signers[id].id] = round3Out
|
||||
}
|
||||
|
||||
// Every signer has the same output Schnorr signature
|
||||
require.Equal(t, result[1].Z, result[3].Z)
|
||||
// require.Equal(t, z, result[3].Z)
|
||||
require.Equal(t, result[1].C, result[3].C)
|
||||
// require.Equal(t, c, result[3].C)
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// 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"
|
||||
|
||||
"github.com/sonr-io/sonr/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: %d", 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: %d", 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: %d", l)
|
||||
}
|
||||
|
||||
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||
return false, fmt.Errorf("ed25519: bad signature size: %d", 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
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// sign.input.gz is a selection of test cases from
|
||||
// https://ed25519.cr.yp.to/python/sign.input
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package ted25519
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"strconv"
|
||||
|
||||
"github.com/sonr-io/sonr/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
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/crypto/core/curves"
|
||||
v1 "github.com/sonr-io/sonr/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
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package ted25519
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
v1 "github.com/sonr-io/sonr/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
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/crypto/core/curves"
|
||||
v1 "github.com/sonr-io/sonr/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)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/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
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/crypto/core/curves"
|
||||
v1 "github.com/sonr-io/sonr/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
|
||||
}
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/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"))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package ted25519
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
v1 "github.com/sonr-io/sonr/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
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/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