No commit suggestions generated

This commit is contained in:
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions

13
tecdsa/dklsv1/README.md Executable file
View File

@@ -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
---
## Secure Two-party Threshold ECDSA from ECDSA Assumptions
Package dkls implements the 2-of-2 threshold ECDSA signing algorithm of
[Secure Two-party Threshold ECDSA from ECDSA Assumptions](https://eprint.iacr.org/2018/499).

View File

@@ -0,0 +1,436 @@
package dklsv1
import (
"hash"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/refresh"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/sign"
)
// AliceDkg DKLS DKG implementation that satisfies the protocol iterator interface.
type AliceDkg struct {
protoStepper
*dkg.Alice
}
// BobDkg DKLS DKG implementation that satisfies the protocol iterator interface.
type BobDkg struct {
protoStepper
*dkg.Bob
}
// AliceSign DKLS sign implementation that satisfies the protocol iterator interface.
type AliceSign struct {
protoStepper
*sign.Alice
}
// BobSign DKLS sign implementation that satisfies the protocol iterator interface.
type BobSign struct {
protoStepper
*sign.Bob
}
// AliceRefresh DKLS refresh implementation that satisfies the protocol iterator interface.
type AliceRefresh struct {
protoStepper
*refresh.Alice
}
// BobRefresh DKLS refresh implementation that satisfies the protocol iterator interface.
type BobRefresh struct {
protoStepper
*refresh.Bob
}
var (
// Static type assertions
_ protocol.Iterator = &AliceDkg{}
_ protocol.Iterator = &BobDkg{}
_ protocol.Iterator = &AliceSign{}
_ protocol.Iterator = &BobSign{}
_ protocol.Iterator = &AliceRefresh{}
_ protocol.Iterator = &BobRefresh{}
)
// NewAliceDkg creates a new protocol that can compute a DKG as Alice
func NewAliceDkg(curve *curves.Curve, version uint) *AliceDkg {
a := &AliceDkg{Alice: dkg.NewAlice(curve)}
a.steps = []func(*protocol.Message) (*protocol.Message, error){
func(input *protocol.Message) (*protocol.Message, error) {
bobSeed, err := decodeDkgRound2Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
roundOutput, err := a.Round2CommitToProof(bobSeed)
if err != nil {
return nil, err
}
return encodeDkgRound2Output(roundOutput, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
proof, err := decodeDkgRound4Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
aliceProof, err := a.Round4VerifyAndReveal(proof)
if err != nil {
return nil, err
}
return encodeDkgRound4Output(aliceProof, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
proof, err := decodeDkgRound6Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
choices, err := a.Round6DkgRound2Ot(proof)
if err != nil {
return nil, err
}
return encodeDkgRound6Output(choices, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
challenge, err := decodeDkgRound8Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
responses, err := a.Round8DkgRound4Ot(challenge)
if err != nil {
return nil, err
}
return encodeDkgRound8Output(responses, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
opening, err := decodeDkgRound10Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
if err := a.Round10DkgRound6Ot(opening); err != nil {
return nil, err
}
return nil, nil
},
}
return a
}
// Result Returns an encoded version of Alice as sequence of bytes that can be used to initialize an AliceSign protocol.
func (a *AliceDkg) Result(version uint) (*protocol.Message, error) {
// Sanity check
if !a.complete() {
return nil, nil
}
if a.Alice == nil {
return nil, protocol.ErrNotInitialized
}
result := a.Output()
return EncodeAliceDkgOutput(result, version)
}
// NewBobDkg Creates a new protocol that can compute a DKG as Bob.
func NewBobDkg(curve *curves.Curve, version uint) *BobDkg {
b := &BobDkg{Bob: dkg.NewBob(curve)}
b.steps = []func(message *protocol.Message) (*protocol.Message, error){
func(*protocol.Message) (*protocol.Message, error) {
commitment, err := b.Round1GenerateRandomSeed()
if err != nil {
return nil, err
}
return encodeDkgRound1Output(commitment, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round3Input, err := decodeDkgRound3Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
bobProof, err := b.Round3SchnorrProve(round3Input)
if err != nil {
return nil, err
}
return encodeDkgRound3Output(bobProof, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
proof, err := decodeDkgRound5Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
bobProof, err := b.Round5DecommitmentAndStartOt(proof)
if err != nil {
return nil, err
}
return encodeDkgRound5Output(bobProof, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
choices, err := decodeDkgRound7Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
challenge, err := b.Round7DkgRound3Ot(choices)
if err != nil {
return nil, err
}
return encodeDkgRound7Output(challenge, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
responses, err := decodeDkgRound9Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
opening, err := b.Round9DkgRound5Ot(responses)
if err != nil {
return nil, err
}
return encodeDkgRound9Output(opening, version)
},
}
return b
}
// Result returns an encoded version of Bob as sequence of bytes that can be used to initialize an BobSign protocol.
func (b *BobDkg) Result(version uint) (*protocol.Message, error) {
// Sanity check
if !b.complete() {
return nil, nil
}
if b.Bob == nil {
return nil, protocol.ErrNotInitialized
}
result := b.Output()
return EncodeBobDkgOutput(result, version)
}
// NewAliceSign creates a new protocol that can compute a signature as Alice.
// Requires dkg state that was produced at the end of DKG.Output().
func NewAliceSign(
curve *curves.Curve,
hash hash.Hash,
message []byte,
dkgResultMessage *protocol.Message,
version uint,
) (*AliceSign, error) {
dkgResult, err := DecodeAliceDkgResult(dkgResultMessage)
if err != nil {
return nil, errors.WithStack(err)
}
a := &AliceSign{Alice: sign.NewAlice(curve, hash, dkgResult)}
a.steps = []func(message *protocol.Message) (*protocol.Message, error){
func(*protocol.Message) (*protocol.Message, error) {
aliceCommitment, err := a.Round1GenerateRandomSeed()
if err != nil {
return nil, err
}
return encodeSignRound1Output(aliceCommitment, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round2Output, err := decodeSignRound3Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round3Output, err := a.Round3Sign(message, round2Output)
if err != nil {
return nil, errors.WithStack(err)
}
return encodeSignRound3Output(round3Output, version)
},
}
return a, nil
}
// NewBobSign creates a new protocol that can compute a signature as Bob.
// Requires dkg state that was produced at the end of DKG.Output().
func NewBobSign(
curve *curves.Curve,
hash hash.Hash,
message []byte,
dkgResultMessage *protocol.Message,
version uint,
) (*BobSign, error) {
dkgResult, err := DecodeBobDkgResult(dkgResultMessage)
if err != nil {
return nil, errors.WithStack(err)
}
b := &BobSign{Bob: sign.NewBob(curve, hash, dkgResult)}
b.steps = []func(message *protocol.Message) (*protocol.Message, error){
func(input *protocol.Message) (*protocol.Message, error) {
commitment, err := decodeSignRound2Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round2Output, err := b.Round2Initialize(commitment)
if err != nil {
return nil, errors.WithStack(err)
}
return encodeSignRound2Output(round2Output, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round4Input, err := decodeSignRound4Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
if err = b.Round4Final(message, round4Input); err != nil {
return nil, errors.WithStack(err)
}
return nil, nil
},
}
return b, nil
}
// Result always returns an error.
// Alice does not compute a signature in the DKLS protocol; only Bob computes the signature.
func (a *AliceSign) Result(_ uint) (*protocol.Message, error) {
return nil, errors.New("dkls.Alice does not produce a signature")
}
// Result returns the signature that Bob computed as a *core.EcdsaSignature if the signing protocol completed successfully.
func (b *BobSign) Result(version uint) (*protocol.Message, error) {
// We can't produce a signature until the protocol completes
if !b.complete() {
return nil, nil
}
if b.Bob == nil {
// Object wasn't created with NewXSign()
return nil, protocol.ErrNotInitialized
}
return encodeSignature(b.Bob.Signature, version)
}
// NewAliceRefresh creates a new protocol that can compute a key refresh as Alice
func NewAliceRefresh(
curve *curves.Curve,
dkgResultMessage *protocol.Message,
version uint,
) (*AliceRefresh, error) {
dkgResult, err := DecodeAliceDkgResult(dkgResultMessage)
if err != nil {
return nil, errors.WithStack(err)
}
a := &AliceRefresh{Alice: refresh.NewAlice(curve, dkgResult)}
a.steps = []func(*protocol.Message) (*protocol.Message, error){
func(_ *protocol.Message) (*protocol.Message, error) {
aliceSeed := a.Round1RefreshGenerateSeed()
return encodeRefreshRound1Output(aliceSeed, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round3Input, err := decodeRefreshRound3Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round3Output, err := a.Round3RefreshMultiplyRound2Ot(round3Input)
if err != nil {
return nil, err
}
return encodeRefreshRound3Output(round3Output, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round5Input, err := decodeRefreshRound5Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round5Output, err := a.Round5RefreshRound4Ot(round5Input)
if err != nil {
return nil, err
}
return encodeRefreshRound5Output(round5Output, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round7Input, err := decodeRefreshRound7Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
if err := a.Round7DkgRound6Ot(round7Input); err != nil {
return nil, err
}
return nil, nil
},
}
return a, nil
}
// Result Returns an encoded version of Alice as sequence of bytes that can be used to initialize an AliceSign protocol.
func (a *AliceRefresh) Result(version uint) (*protocol.Message, error) {
// Sanity check
if !a.complete() {
return nil, nil
}
if a.Alice == nil {
return nil, protocol.ErrNotInitialized
}
result := a.Output()
return EncodeAliceDkgOutput(result, version)
}
// NewBobRefresh Creates a new protocol that can compute a refresh as Bob.
func NewBobRefresh(
curve *curves.Curve,
dkgResultMessage *protocol.Message,
version uint,
) (*BobRefresh, error) {
dkgResult, err := DecodeBobDkgResult(dkgResultMessage)
if err != nil {
return nil, errors.WithStack(err)
}
b := &BobRefresh{Bob: refresh.NewBob(curve, dkgResult)}
b.steps = []func(message *protocol.Message) (*protocol.Message, error){
func(input *protocol.Message) (*protocol.Message, error) {
round2Input, err := decodeRefreshRound2Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round2Output, err := b.Round2RefreshProduceSeedAndMultiplyAndStartOT(round2Input)
if err != nil {
return nil, err
}
return encodeRefreshRound2Output(round2Output, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round4Input, err := decodeRefreshRound4Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round4Output, err := b.Round4RefreshRound3Ot(round4Input)
if err != nil {
return nil, err
}
return encodeRefreshRound4Output(round4Output, version)
},
func(input *protocol.Message) (*protocol.Message, error) {
round6Input, err := decodeRefreshRound6Input(input)
if err != nil {
return nil, errors.WithStack(err)
}
round6Output, err := b.Round6RefreshRound5Ot(round6Input)
if err != nil {
return nil, err
}
return encodeRefreshRound6Output(round6Output, version)
},
}
return b, nil
}
// Result returns an encoded version of Bob as sequence of bytes that can be used to initialize an BobSign protocol.
func (b *BobRefresh) Result(version uint) (*protocol.Message, error) {
// Sanity check
if !b.complete() {
return nil, nil
}
if b.Bob == nil {
return nil, protocol.ErrNotInitialized
}
result := b.Output()
return EncodeBobDkgOutput(result, version)
}

View File

@@ -0,0 +1,87 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package dealer implements key generation via a trusted dealer for the protocol [DKLs18](https://eprint.iacr.org/2018/499.pdf).
// The trusted dealer produces the same output as the corresponding DKG protocol and can be used for signing without
// additional modifications.
// Note that running actual DKG is ALWAYS recommended over a trusted dealer.
package dealer
import (
"crypto/rand"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
)
// GenerateAndDeal produces private key material for alice and bob which they can later use in signing.
// Running actual DKG is ALWAYS recommended over using this function, as this function breaks the security guarantees of DKG.
// only use this function if you have a very good reason to.
func GenerateAndDeal(curve *curves.Curve) (*dkg.AliceOutput, *dkg.BobOutput, error) {
aliceSecretShare, bobSecretShare, publicKey := produceKeyShares(curve)
aliceOTOutput, bobOTOutput, err := produceOTResults(curve)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't produce OT results")
}
alice := &dkg.AliceOutput{
PublicKey: publicKey,
SecretKeyShare: aliceSecretShare,
SeedOtResult: aliceOTOutput,
}
bob := &dkg.BobOutput{
PublicKey: publicKey,
SecretKeyShare: bobSecretShare,
SeedOtResult: bobOTOutput,
}
return alice, bob, nil
}
func produceKeyShares(
curve *curves.Curve,
) (aliceSecretShare curves.Scalar, bobSecretShare curves.Scalar, publicKey curves.Point) {
aliceSecretShare = curve.Scalar.Random(rand.Reader)
bobSecretShare = curve.Scalar.Random(rand.Reader)
publicKey = curve.ScalarBaseMult(aliceSecretShare.Mul(bobSecretShare))
return aliceSecretShare, bobSecretShare, publicKey
}
func produceOTResults(
curve *curves.Curve,
) (*simplest.ReceiverOutput, *simplest.SenderOutput, error) {
oneTimePadEncryptionKeys := make([]simplest.OneTimePadEncryptionKeys, kos.Kappa)
oneTimePadDecryptionKey := make([]simplest.OneTimePadDecryptionKey, kos.Kappa)
// we'll need a receiver because in its constructor random bits will be selected.
receiver, err := simplest.NewReceiver(curve, kos.Kappa, [simplest.DigestSize]byte{})
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't initialize a receiver")
}
packedRandomChoiceBits, randomChoiceBits := receiver.Output.PackedRandomChoiceBits, receiver.Output.RandomChoiceBits
for i := 0; i < kos.Kappa; i++ {
if _, err := rand.Read(oneTimePadEncryptionKeys[i][0][:]); err != nil {
return nil, nil, errors.WithStack(err)
}
if _, err := rand.Read(oneTimePadEncryptionKeys[i][1][:]); err != nil {
return nil, nil, errors.WithStack(err)
}
oneTimePadDecryptionKey[i] = oneTimePadEncryptionKeys[i][randomChoiceBits[i]]
}
senderOutput := &simplest.SenderOutput{
OneTimePadEncryptionKeys: oneTimePadEncryptionKeys,
}
receiverOutput := &simplest.ReceiverOutput{
PackedRandomChoiceBits: packedRandomChoiceBits,
RandomChoiceBits: randomChoiceBits,
OneTimePadDecryptionKey: oneTimePadDecryptionKey,
}
return receiverOutput, senderOutput, nil
}

View File

@@ -0,0 +1,57 @@
package dealer_test
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dealer"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/sign"
)
func Test_DealerCanGenerateKeysThatSign(t *testing.T) {
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
aliceOutput, bobOutput, err := dealer.GenerateAndDeal(curve)
require.NoError(t, err)
alice := sign.NewAlice(curve, sha3.New256(), aliceOutput)
bob := sign.NewBob(curve, sha3.New256(), bobOutput)
message := []byte("A message.")
seed, err := alice.Round1GenerateRandomSeed()
require.NoError(t, err)
round3Output, err := bob.Round2Initialize(seed)
require.NoError(t, err)
round4Output, err := alice.Round3Sign(message, round3Output)
require.NoError(t, err)
err = bob.Round4Final(message, round4Output)
require.NoError(t, err, "curve: %s", curve.Name)
}
}
func Test_DealerGeneratesDifferentResultsEachTime(t *testing.T) {
curve := curves.K256()
aliceOutput1, bobOutput1, err := dealer.GenerateAndDeal(curve)
require.NoError(t, err)
aliceOutput2, bobOutput2, err := dealer.GenerateAndDeal(curve)
require.NoError(t, err)
require.NotEqual(t, aliceOutput1.SecretKeyShare, aliceOutput2.SecretKeyShare)
require.NotEqual(t, bobOutput1.SecretKeyShare, bobOutput2.SecretKeyShare)
require.NotEqualValues(
t,
aliceOutput1.SeedOtResult.RandomChoiceBits,
aliceOutput2.SeedOtResult.RandomChoiceBits,
)
require.NotEqualValues(
t,
bobOutput1.SeedOtResult.OneTimePadEncryptionKeys,
bobOutput2.SeedOtResult.OneTimePadEncryptionKeys,
)
}

309
tecdsa/dklsv1/dkg/dkg.go Normal file
View File

@@ -0,0 +1,309 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package dkg implements the Distributed Key Generation (DKG) protocol of [DKLs18](https://eprint.iacr.org/2018/499.pdf).
// The DKG protocol is defined in "Protocol 2" page 7, of the paper. The Zero Knowledge Proof ideal functionalities are
// realized using schnorr proofs. Moreover, the seed OT is realized using the Verified Simplest OT protocol.
package dkg
import (
"crypto/rand"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/zkp/schnorr"
)
// AliceOutput is the result of running DKG for Alice. It contains both the public and secret values that are needed
// for signing.
type AliceOutput struct {
// PublicKey is the joint public key of Alice and Bob.
// This value is public.
PublicKey curves.Point
// SecretKeyShare is Alice's secret key for the joint public key.
// This output must be kept secret. If it is lost, the users will lose access and cannot create signatures.
SecretKeyShare curves.Scalar
// SeedOtResult are the outputs that the receiver will obtain as a result of running the "random" OT protocol.
// This output must be kept secret. Although, if it is lost the users can run another OT protocol and obtain
// new values to replace it.
SeedOtResult *simplest.ReceiverOutput
}
// BobOutput is the result of running DKG for Bob. It contains both the public and secret values that are needed
// for signing.
type BobOutput struct {
// PublicKey is the joint public key of Alice and Bob.
// This value is public.
PublicKey curves.Point
// SecretKeyShare is Bob's secret key for the joint public key.
// This output must be kept secret. If it is lost, the users will lose access and cannot create signatures.
SecretKeyShare curves.Scalar
// SeedOtResult are the outputs that the sender will obtain as a result of running the "random" OT protocol.
// This output must be kept secret. Although, if it is lost the users can run another OT protocol and obtain
// new values to replace it.
SeedOtResult *simplest.SenderOutput
}
// Alice struct encoding Alice's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Alice will NOT obtain the signature.
type Alice struct {
// prover is a schnorr prover for Alice's portion of public key.
prover *schnorr.Prover
// proof is alice's proof to her portion of the public key. It is stored as an intermediate value, during commitment phase.
proof *schnorr.Proof
// receiver is the base OT receiver.
receiver *simplest.Receiver
// secretKeyShare is Alice's secret key for the joint public key.
secretKeyShare curves.Scalar
// publicKey is the joint public key of Alice and Bob.
publicKey curves.Point
curve *curves.Curve
transcript *merlin.Transcript
}
// Bob struct encoding Bob's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Bob will obtain the signature.
type Bob struct {
// prover is a schnorr prover for Bob's portion of public key.
prover *schnorr.Prover // this is a "schnorr statement" for pkB.
// sender is the base OT sender.
sender *simplest.Sender
// secretKeyShare is Bob's secret key for the joint public key.
secretKeyShare curves.Scalar
// publicKey is the joint public key of Alice and Bob.
publicKey curves.Point
// schnorr proof commitment to Alice's schnorr proof.
aliceCommitment schnorr.Commitment
// 32-byte transcript salt which will be used for Alice's schnorr proof
aliceSalt [simplest.DigestSize]byte
curve *curves.Curve
transcript *merlin.Transcript
}
// Round2Output contains the output of the 2nd round of DKG.
type Round2Output struct {
// Seed is the random value used to derive the joint unique session id.
Seed [simplest.DigestSize]byte
// Commitment is the commitment to the ZKP to Alice's secret key share.
Commitment schnorr.Commitment
}
// NewAlice creates a party that can participate in 2-of-2 DKG and threshold signature.
func NewAlice(curve *curves.Curve) *Alice {
return &Alice{
curve: curve,
transcript: merlin.NewTranscript("Coinbase_DKLs_DKG"),
}
}
// NewBob creates a party that can participate in 2-of-2 DKG and threshold signature. This party
// is the receiver of the signature at the end.
func NewBob(curve *curves.Curve) *Bob {
return &Bob{
curve: curve,
transcript: merlin.NewTranscript("Coinbase_DKLs_DKG"),
}
}
// Round1GenerateRandomSeed Bob flips random coins, and sends these to Alice
// in this round, Bob flips 32 random bytes and sends them to Alice.
// note that this is not _explicitly_ given as part of the protocol in https://eprint.iacr.org/2018/499.pdf, Protocol 1).
// rather, it is part of our generation of a unique session identifier, for use in subsequent schnorr proofs / seed OT / etc.
// we do it by having each party sample 32 bytes, then by appending _both_ as salts. secure if either party is honest
func (bob *Bob) Round1GenerateRandomSeed() ([simplest.DigestSize]byte, error) {
bobSeed := [simplest.DigestSize]byte{}
if _, err := rand.Read(bobSeed[:]); err != nil {
return [simplest.DigestSize]byte{}, errors.Wrap(
err,
"generating random bytes in bob DKG round 1 generate",
)
}
bob.transcript.AppendMessage(
[]byte("session_id_bob"),
bobSeed[:],
) // note: bob appends first here
return bobSeed, nil
}
// Round2CommitToProof steps 1) and 2) of protocol 2 on page 7.
func (alice *Alice) Round2CommitToProof(bobSeed [simplest.DigestSize]byte) (*Round2Output, error) {
aliceSeed := [simplest.DigestSize]byte{}
if _, err := rand.Read(aliceSeed[:]); err != nil {
return nil, errors.Wrap(err, "generating random bytes in bob DKG round 1 generate")
}
alice.transcript.AppendMessage([]byte("session_id_bob"), bobSeed[:])
alice.transcript.AppendMessage([]byte("session_id_alice"), aliceSeed[:])
var err error
uniqueSessionId := [simplest.DigestSize]byte{} // note: will use and re-use this below for sub-session IDs.
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("salt for simplest OT"), simplest.DigestSize),
)
alice.receiver, err = simplest.NewReceiver(alice.curve, kos.Kappa, uniqueSessionId)
if err != nil {
return nil, errors.Wrap(err, "alice constructing new seed OT receiver in Alice DKG round 1")
}
alice.secretKeyShare = alice.curve.Scalar.Random(rand.Reader)
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("salt for alice schnorr"), simplest.DigestSize),
)
alice.prover = schnorr.NewProver(alice.curve, nil, uniqueSessionId[:])
var commitment schnorr.Commitment
alice.proof, commitment, err = alice.prover.ProveCommit(
alice.secretKeyShare,
) // will mutate `pkA`
if err != nil {
return nil, errors.Wrap(err, "prove + commit in alice DKG Commit round 1")
}
return &Round2Output{
Commitment: commitment,
Seed: aliceSeed,
}, nil
}
// Round3SchnorrProve receives Bob's Commitment and returns schnorr statment + proof.
// Steps 1 and 3 of protocol 2 on page 7.
func (bob *Bob) Round3SchnorrProve(round2Output *Round2Output) (*schnorr.Proof, error) {
bob.transcript.AppendMessage([]byte("session_id_alice"), round2Output.Seed[:])
bob.aliceCommitment = round2Output.Commitment // store it, so that we can check when alice decommits
var err error
uniqueSessionId := [simplest.DigestSize]byte{} // note: will use and re-use this below for sub-session IDs.
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("salt for simplest OT"), simplest.DigestSize),
)
bob.sender, err = simplest.NewSender(bob.curve, kos.Kappa, uniqueSessionId)
if err != nil {
return nil, errors.Wrap(err, "bob constructing new OT sender in DKG round 2")
}
// extract alice's salt in the right order; we won't use this until she reveals her proof and we verify it below
copy(
bob.aliceSalt[:],
bob.transcript.ExtractBytes([]byte("salt for alice schnorr"), simplest.DigestSize),
)
bob.secretKeyShare = bob.curve.Scalar.Random(rand.Reader)
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("salt for bob schnorr"), simplest.DigestSize),
)
bob.prover = schnorr.NewProver(bob.curve, nil, uniqueSessionId[:])
proof, err := bob.prover.Prove(bob.secretKeyShare)
if err != nil {
return nil, errors.Wrap(err, "bob schnorr proving in DKG round 2")
}
return proof, err
}
// Round4VerifyAndReveal step 4 of protocol 2 on page 7.
func (alice *Alice) Round4VerifyAndReveal(proof *schnorr.Proof) (*schnorr.Proof, error) {
var err error
uniqueSessionId := [simplest.DigestSize]byte{}
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("salt for bob schnorr"), simplest.DigestSize),
)
if err = schnorr.Verify(proof, alice.curve, nil, uniqueSessionId[:]); err != nil {
return nil, errors.Wrap(
err,
"alice's verification of Bob's schnorr proof failed in DKG round 3",
)
}
alice.publicKey = proof.Statement.Mul(alice.secretKeyShare)
return alice.proof, nil
}
// Round5DecommitmentAndStartOt step 5 of protocol 2 on page 7.
func (bob *Bob) Round5DecommitmentAndStartOt(proof *schnorr.Proof) (*schnorr.Proof, error) {
var err error
if err = schnorr.DecommitVerify(proof, bob.aliceCommitment, bob.curve, nil, bob.aliceSalt[:]); err != nil {
return nil, errors.Wrap(err, "decommit + verify failed in bob's DKG round 4")
}
bob.publicKey = proof.Statement.Mul(bob.secretKeyShare)
seedOTRound1Output, err := bob.sender.Round1ComputeAndZkpToPublicKey()
if err != nil {
return nil, errors.Wrap(err, "bob computing round 1 of seed OT within DKG round 4")
}
return seedOTRound1Output, nil
}
// Round6DkgRound2Ot is a thin wrapper around the 2nd round of seed OT protocol.
func (alice *Alice) Round6DkgRound2Ot(
proof *schnorr.Proof,
) ([]simplest.ReceiversMaskedChoices, error) {
return alice.receiver.Round2VerifySchnorrAndPadTransfer(proof)
}
// Round7DkgRound3Ot is a thin wrapper around the 3rd round of seed OT protocol.
func (bob *Bob) Round7DkgRound3Ot(
compressedReceiversMaskedChoice []simplest.ReceiversMaskedChoices,
) ([]simplest.OtChallenge, error) {
return bob.sender.Round3PadTransfer(compressedReceiversMaskedChoice)
}
// Round8DkgRound4Ot is a thin wrapper around the 4th round of seed OT protocol.
func (alice *Alice) Round8DkgRound4Ot(
challenge []simplest.OtChallenge,
) ([]simplest.OtChallengeResponse, error) {
return alice.receiver.Round4RespondToChallenge(challenge)
}
// Round9DkgRound5Ot is a thin wrapper around the 5th round of seed OT protocol.
func (bob *Bob) Round9DkgRound5Ot(
challengeResponses []simplest.OtChallengeResponse,
) ([]simplest.ChallengeOpening, error) {
return bob.sender.Round5Verify(challengeResponses)
}
// Round10DkgRound6Ot is a thin wrapper around the 6th round of seed OT protocol.
func (alice *Alice) Round10DkgRound6Ot(challengeOpenings []simplest.ChallengeOpening) error {
return alice.receiver.Round6Verify(challengeOpenings)
}
// Output returns the output of the DKG operation. Must be called after step 9. Calling it before that step
// has undefined behaviour.
func (alice *Alice) Output() *AliceOutput {
return &AliceOutput{
PublicKey: alice.publicKey,
SecretKeyShare: alice.secretKeyShare,
SeedOtResult: alice.receiver.Output,
}
}
// Output returns the output of the DKG operation. Must be called after step 9. Calling it before that step
// has undefined behaviour.
func (bob *Bob) Output() *BobOutput {
return &BobOutput{
PublicKey: bob.publicKey,
SecretKeyShare: bob.secretKeyShare,
SeedOtResult: bob.sender.Output,
}
}

View File

@@ -0,0 +1,104 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package dkg
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
)
func TestDkg(t *testing.T) {
t.Parallel()
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
boundCurve := curve
t.Run(fmt.Sprintf("testing dkg for curve %s", boundCurve.Name), func(tt *testing.T) {
tt.Parallel()
alice := NewAlice(boundCurve)
bob := NewBob(boundCurve)
seed, err := bob.Round1GenerateRandomSeed()
require.NoError(tt, err)
round3Output, err := alice.Round2CommitToProof(seed)
require.NoError(tt, err)
proof, err := bob.Round3SchnorrProve(round3Output)
require.NoError(tt, err)
proof, err = alice.Round4VerifyAndReveal(proof)
require.NoError(tt, err)
proof, err = bob.Round5DecommitmentAndStartOt(proof)
require.NoError(tt, err)
compressedReceiversMaskedChoice, err := alice.Round6DkgRound2Ot(proof)
require.NoError(tt, err)
challenge, err := bob.Round7DkgRound3Ot(compressedReceiversMaskedChoice)
require.NoError(tt, err)
challengeResponse, err := alice.Round8DkgRound4Ot(challenge)
require.NoError(tt, err)
challengeOpenings, err := bob.Round9DkgRound5Ot(challengeResponse)
require.NoError(tt, err)
err = alice.Round10DkgRound6Ot(challengeOpenings)
require.NoError(tt, err)
// Verify correctness of the OT subprotocol after has completed
for i := 0; i < kos.Kappa; i++ {
if alice.receiver.Output.OneTimePadDecryptionKey[i] != bob.sender.Output.OneTimePadEncryptionKeys[i][alice.receiver.Output.RandomChoiceBits[i]] {
tt.Errorf("oblivious transfer is incorrect at index i=%v", i)
}
}
pkA := boundCurve.ScalarBaseMult(alice.Output().SecretKeyShare)
pkB := boundCurve.ScalarBaseMult(bob.Output().SecretKeyShare)
computedPublicKeyA := pkA.Mul(bob.Output().SecretKeyShare)
require.True(tt, computedPublicKeyA.Equal(alice.Output().PublicKey))
require.True(tt, computedPublicKeyA.Equal(bob.Output().PublicKey))
computedPublicKeyB := pkB.Mul(alice.Output().SecretKeyShare)
require.True(tt, computedPublicKeyB.Equal(alice.Output().PublicKey))
require.True(tt, computedPublicKeyB.Equal(bob.Output().PublicKey))
})
}
}
func BenchmarkDkg(b *testing.B) {
if testing.Short() {
b.SkipNow()
}
curve := curves.K256()
for n := 0; n < b.N; n++ {
alice := NewAlice(curve)
bob := NewBob(curve)
seed, err := bob.Round1GenerateRandomSeed()
require.NoError(b, err)
round3Output, err := alice.Round2CommitToProof(seed)
require.NoError(b, err)
proof, err := bob.Round3SchnorrProve(round3Output)
require.NoError(b, err)
proof, err = alice.Round4VerifyAndReveal(proof)
require.NoError(b, err)
proof, err = bob.Round5DecommitmentAndStartOt(proof)
require.NoError(b, err)
compressedReceiversMaskedChoice, err := alice.Round6DkgRound2Ot(proof)
require.NoError(b, err)
challenge, err := bob.Round7DkgRound3Ot(compressedReceiversMaskedChoice)
require.NoError(b, err)
challengeResponse, err := alice.Round8DkgRound4Ot(challenge)
require.NoError(b, err)
challengeOpenings, err := bob.Round9DkgRound5Ot(challengeResponse)
require.NoError(b, err)
err = alice.Round10DkgRound6Ot(challengeOpenings)
require.NoError(b, err)
}
}

View File

@@ -0,0 +1,327 @@
package dklsv1
import (
"bytes"
"encoding/gob"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/zkp/schnorr"
)
const payloadKey = "direct"
func newDkgProtocolMessage(payload []byte, round string, version uint) *protocol.Message {
return &protocol.Message{
Protocol: protocol.Dkls18Dkg,
Version: version,
Payloads: map[string][]byte{payloadKey: payload},
Metadata: map[string]string{"round": round},
}
}
func registerTypes() {
gob.Register(&curves.ScalarK256{})
gob.Register(&curves.PointK256{})
gob.Register(&curves.ScalarP256{})
gob.Register(&curves.PointP256{})
}
func encodeDkgRound1Output(commitment [32]byte, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(&commitment); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "1", version), nil
}
func decodeDkgRound2Input(m *protocol.Message) ([32]byte, error) {
if m.Version != protocol.Version1 {
return [32]byte{}, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := [32]byte{}
if err := dec.Decode(&decoded); err != nil {
return [32]byte{}, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound2Output(output *dkg.Round2Output, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(output); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "2", version), nil
}
func decodeDkgRound3Input(m *protocol.Message) (*dkg.Round2Output, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(dkg.Round2Output)
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound3Output(proof *schnorr.Proof, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(proof); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "3", version), nil
}
func decodeDkgRound4Input(m *protocol.Message) (*schnorr.Proof, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(schnorr.Proof)
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound4Output(proof *schnorr.Proof, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(proof); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "4", version), nil
}
func decodeDkgRound5Input(m *protocol.Message) (*schnorr.Proof, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(schnorr.Proof)
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound5Output(proof *schnorr.Proof, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(proof); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "5", version), nil
}
func decodeDkgRound6Input(m *protocol.Message) (*schnorr.Proof, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(schnorr.Proof)
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound6Output(
choices []simplest.ReceiversMaskedChoices,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(choices); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "6", version), nil
}
func decodeDkgRound7Input(m *protocol.Message) ([]simplest.ReceiversMaskedChoices, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.ReceiversMaskedChoices{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound7Output(
challenge []simplest.OtChallenge,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(challenge); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "7", version), nil
}
func decodeDkgRound8Input(m *protocol.Message) ([]simplest.OtChallenge, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.OtChallenge{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound8Output(
responses []simplest.OtChallengeResponse,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(responses); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "8", version), nil
}
func decodeDkgRound9Input(m *protocol.Message) ([]simplest.OtChallengeResponse, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.OtChallengeResponse{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeDkgRound9Output(
opening []simplest.ChallengeOpening,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(opening); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "9", version), nil
}
func decodeDkgRound10Input(m *protocol.Message) ([]simplest.ChallengeOpening, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.ChallengeOpening{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
// EncodeAliceDkgOutput serializes Alice DKG output based on the protocol version.
func EncodeAliceDkgOutput(result *dkg.AliceOutput, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(result); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "alice-output", version), nil
}
// DecodeAliceDkgResult deserializes Alice DKG output.
func DecodeAliceDkgResult(m *protocol.Message) (*dkg.AliceOutput, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
registerTypes()
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(dkg.AliceOutput)
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
// EncodeBobDkgOutput serializes Bob DKG output based on the protocol version.
func EncodeBobDkgOutput(result *dkg.BobOutput, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(result); err != nil {
return nil, errors.WithStack(err)
}
return newDkgProtocolMessage(buf.Bytes(), "bob-output", version), nil
}
// DecodeBobDkgResult deserializes Bob DKG output.
func DecodeBobDkgResult(m *protocol.Message) (*dkg.BobOutput, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(dkg.BobOutput)
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}

33
tecdsa/dklsv1/protocol.go Normal file
View File

@@ -0,0 +1,33 @@
// package dklsv1 provides a wrapper around the [DKLs18](https://eprint.iacr.org/2018/499.pdf) sign and dkg and provides
// serialization, serialization, and versioning for the serialized data.
package dklsv1
import (
"github.com/sonr-io/sonr/crypto/core/protocol"
)
// Basic protocol interface implementation that calls the next step func in a pre-defined list
type protoStepper struct {
steps []func(input *protocol.Message) (*protocol.Message, error)
step int
}
// Next runs the next step in the protocol and reports errors or increments the step index
func (p *protoStepper) Next(input *protocol.Message) (*protocol.Message, error) {
if p.complete() {
return nil, protocol.ErrProtocolFinished
}
// Run the current protocol step and report any errors
output, err := p.steps[p.step](input)
if err != nil {
return nil, err
}
// Increment the step index and report success
p.step++
return output, nil
}
// Reports true if the step index exceeds the number of steps
func (p *protoStepper) complete() bool { return p.step >= len(p.steps) /**/ }

View File

@@ -0,0 +1,428 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package dklsv1
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
)
// For DKG bob starts first. For refresh and sign, Alice starts first.
func runIteratedProtocol(
firstParty protocol.Iterator,
secondParty protocol.Iterator,
) (error, error) {
var (
message *protocol.Message
aErr error
bErr error
)
for aErr != protocol.ErrProtocolFinished || bErr != protocol.ErrProtocolFinished {
// Crank each protocol forward one iteration
message, bErr = firstParty.Next(message)
if bErr != nil && bErr != protocol.ErrProtocolFinished {
return nil, bErr
}
message, aErr = secondParty.Next(message)
if aErr != nil && aErr != protocol.ErrProtocolFinished {
return aErr, nil
}
}
return aErr, bErr
}
// Running steps in sequence ensures that no hidden read/write dependency exist in the read/write interfaces.
func TestDkgProto(t *testing.T) {
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
alice := NewAliceDkg(curve, protocol.Version1)
bob := NewBobDkg(curve, protocol.Version1)
aErr, bErr := runIteratedProtocol(bob, alice)
t.Run("both alice/bob complete simultaneously", func(t *testing.T) {
require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
})
for i := 0; i < kos.Kappa; i++ {
if alice.Alice.Output().SeedOtResult.OneTimePadDecryptionKey[i] != bob.Bob.Output().SeedOtResult.OneTimePadEncryptionKeys[i][alice.Alice.Output().SeedOtResult.RandomChoiceBits[i]] {
t.Errorf("oblivious transfer is incorrect at index i=%v", i)
}
}
t.Run("Both parties produces identical composite pubkey", func(t *testing.T) {
require.True(t, alice.Alice.Output().PublicKey.Equal(bob.Bob.Output().PublicKey))
})
var aliceResult *dkg.AliceOutput
var bobResult *dkg.BobOutput
t.Run("alice produces valid result", func(t *testing.T) {
// Get the result
r, err := alice.Result(protocol.Version1)
// Test
require.NoError(t, err)
require.NotNil(t, r)
aliceResult, err = DecodeAliceDkgResult(r)
require.NoError(t, err)
})
t.Run("bob produces valid result", func(t *testing.T) {
// Get the result
r, err := bob.Result(protocol.Version1)
// Test
require.NoError(t, err)
require.NotNil(t, r)
bobResult, err = DecodeBobDkgResult(r)
require.NoError(t, err)
})
t.Run("alice/bob agree on pubkey", func(t *testing.T) {
require.Equal(t, aliceResult.PublicKey, bobResult.PublicKey)
})
}
}
//
// // DKG > Refresh > Sign
// func TestRefreshProto(t *testing.T) {
// t.Parallel()
// curveInstances := []*curves.Curve{
// curves.K256(),
// curves.P256(),
// }
// for _, curve := range curveInstances {
// boundCurve := curve
// t.Run(fmt.Sprintf("testing refresh for curve %s", boundCurve.Name), func(tt *testing.T) {
// tt.Parallel()
// // DKG
// aliceDkg := NewAliceDkg(boundCurve, protocol.Version1)
// bobDkg := NewBobDkg(boundCurve, protocol.Version1)
//
// aDkgErr, bDkgErr := runIteratedProtocol(bobDkg, aliceDkg)
// require.ErrorIs(tt, aDkgErr, protocol.ErrProtocolFinished)
// require.ErrorIs(tt, bDkgErr, protocol.ErrProtocolFinished)
//
// aliceDkgResultMessage, err := aliceDkg.Result(protocol.Version1)
// require.NoError(tt, err)
// bobDkgResultMessage, err := bobDkg.Result(protocol.Version1)
// require.NoError(tt, err)
//
// // Refresh
// aliceRefreshResultMessage, bobRefreshResultMessage := refreshV1(t, boundCurve, aliceDkgResultMessage, bobDkgResultMessage)
//
// // sign
// signV1(t, boundCurve, aliceRefreshResultMessage, bobRefreshResultMessage)
// })
// }
// }
// DKG > Output > NewDklsSign > Sign > Output
func TestDkgSignProto(t *testing.T) {
// Setup
curve := curves.K256()
aliceDkg := NewAliceDkg(curve, protocol.Version1)
bobDkg := NewBobDkg(curve, protocol.Version1)
// DKG
aErr, bErr := runIteratedProtocol(bobDkg, aliceDkg)
require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
// Output
aliceDkgResultMessage, err := aliceDkg.Result(protocol.Version1)
require.NoError(t, err)
bobDkgResultMessage, err := bobDkg.Result(protocol.Version1)
require.NoError(t, err)
// New DklsSign
msg := []byte("As soon as you trust yourself, you will know how to live.")
aliceSign, err := NewAliceSign(
curve,
sha3.New256(),
msg,
aliceDkgResultMessage,
protocol.Version1,
)
require.NoError(t, err)
bobSign, err := NewBobSign(curve, sha3.New256(), msg, bobDkgResultMessage, protocol.Version1)
require.NoError(t, err)
// Sign
t.Run("sign", func(t *testing.T) {
aErr, bErr = runIteratedProtocol(aliceSign, bobSign)
require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
})
// Don't continue to verifying results if sign didn't run to completion.
require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
// Output
var result *curves.EcdsaSignature
t.Run("bob produces result of correct type", func(t *testing.T) {
resultMessage, err := bobSign.Result(protocol.Version1)
require.NoError(t, err)
result, err = DecodeSignature(resultMessage)
require.NoError(t, err)
})
require.NotNil(t, result)
t.Run("valid signature", func(t *testing.T) {
hash := sha3.New256()
_, err = hash.Write(msg)
require.NoError(t, err)
digest := hash.Sum(nil)
unCompressedAffinePublicKey := aliceDkg.Output().PublicKey.ToAffineUncompressed()
require.Equal(t, 65, len(unCompressedAffinePublicKey))
x := new(big.Int).SetBytes(unCompressedAffinePublicKey[1:33])
y := new(big.Int).SetBytes(unCompressedAffinePublicKey[33:])
ecCurve, err := curve.ToEllipticCurve()
require.NoError(t, err)
publicKey := &curves.EcPoint{
Curve: ecCurve,
X: x,
Y: y,
}
require.True(t,
curves.VerifyEcdsa(publicKey,
digest[:],
result,
),
"signature failed verification",
)
})
}
//
// // Decode > NewDklsSign > Sign > Output
// // NOTE: this cold-start test ensures backwards compatibility with durable,
// // encoding DKG state that may exist within production systems like test
// // Breaking changes must consider downstream production impacts.
// func TestSignColdStart(t *testing.T) {
// // Decode alice/bob state from file
// aliceDkg, err := os.ReadFile("testdata/alice-dkls-v1-dkg.bin")
// require.NoError(t, err)
// bobDkg, err := os.ReadFile("testdata/bob-dkls-v1-dkg.bin")
// require.NoError(t, err)
//
// // The choice of json marshaling is arbitrary, the binary could have been marshaled in other forms as well
// // The purpose here is to obtain an instance of `protocol.Message`
//
// aliceDkgMessage := &protocol.Message{}
// err = json.Unmarshal(aliceDkg, aliceDkgMessage)
// require.NoError(t, err)
//
// bobDkgMessage := &protocol.Message{}
// err = json.Unmarshal(bobDkg, bobDkgMessage)
// require.NoError(t, err)
//
// signV1(t, curves.K256(), aliceDkgMessage, bobDkgMessage)
// }
//
// func TestEncodeDecode(t *testing.T) {
// curve := curves.K256()
//
// alice := NewAliceDkg(curve, protocol.Version1)
// bob := NewBobDkg(curve, protocol.Version1)
// _, _ = runIteratedProtocol(bob, alice)
//
// var aliceBytes []byte
// var bobBytes []byte
// t.Run("Encode Alice/Bob", func(t *testing.T) {
// aliceDkgMessage, err := EncodeAliceDkgOutput(alice.Alice.Output(), protocol.Version1)
// require.NoError(t, err)
// aliceBytes, err = json.Marshal(aliceDkgMessage)
// require.NoError(t, err)
//
// bobDkgMessage, err := EncodeBobDkgOutput(bob.Bob.Output(), protocol.Version1)
// require.NoError(t, err)
// bobBytes, err = json.Marshal(bobDkgMessage)
// require.NoError(t, err)
// })
// require.NotEmpty(t, aliceBytes)
// require.NotEmpty(t, bobBytes)
//
// t.Run("Decode Alice", func(t *testing.T) {
// decodedAliceMessage := &protocol.Message{}
// err := json.Unmarshal(aliceBytes, decodedAliceMessage)
// require.NoError(t, err)
// require.NotNil(t, decodedAliceMessage)
// decodedAlice, err := DecodeAliceDkgResult(decodedAliceMessage)
// require.NoError(t, err)
//
// require.True(t, alice.Output().PublicKey.Equal(decodedAlice.PublicKey))
// require.Equal(t, alice.Output().SecretKeyShare, decodedAlice.SecretKeyShare)
// })
//
// t.Run("Decode Bob", func(t *testing.T) {
// decodedBobMessage := &protocol.Message{}
// err := json.Unmarshal(bobBytes, decodedBobMessage)
// require.NoError(t, err)
// require.NotNil(t, decodedBobMessage)
// decodedBob, err := DecodeBobDkgResult(decodedBobMessage)
// require.NoError(t, err)
//
// require.True(t, bob.Output().PublicKey.Equal(decodedBob.PublicKey))
// require.Equal(t, bob.Output().SecretKeyShare, decodedBob.SecretKeyShare)
// })
// }
//
// func signV1(t *testing.T, curve *curves.Curve, aliceDkgResultMessage *protocol.Message, bobDkgResultMessage *protocol.Message) {
// t.Helper()
// // New DklsSign
// msg := []byte("As soon as you trust yourself, you will know how to live.")
// aliceSign, err := NewAliceSign(curve, sha3.New256(), msg, aliceDkgResultMessage, protocol.Version1)
// require.NoError(t, err)
// bobSign, err := NewBobSign(curve, sha3.New256(), msg, bobDkgResultMessage, protocol.Version1)
// require.NoError(t, err)
//
// // Sign
// var aErr error
// var bErr error
// t.Run("sign", func(t *testing.T) {
// aErr, bErr = runIteratedProtocol(aliceSign, bobSign)
// require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
// require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
// })
// // Don't continue to verifying results if sign didn't run to completion.
// require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
// require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
//
// // Output
// var result *curves.EcdsaSignature
// t.Run("bob produces result of correct type", func(t *testing.T) {
// resultMessage, err := bobSign.Result(protocol.Version1)
// require.NoError(t, err)
// result, err = DecodeSignature(resultMessage)
// require.NoError(t, err)
// })
// require.NotNil(t, result)
//
// aliceDkg, err := DecodeAliceDkgResult(aliceDkgResultMessage)
// require.NoError(t, err)
//
// t.Run("valid signature", func(t *testing.T) {
// hash := sha3.New256()
// _, err = hash.Write(msg)
// require.NoError(t, err)
// digest := hash.Sum(nil)
// unCompressedAffinePublicKey := aliceDkg.PublicKey.ToAffineUncompressed()
// require.Equal(t, 65, len(unCompressedAffinePublicKey))
// x := new(big.Int).SetBytes(unCompressedAffinePublicKey[1:33])
// y := new(big.Int).SetBytes(unCompressedAffinePublicKey[33:])
// ecCurve, err := curve.ToEllipticCurve()
// require.NoError(t, err)
// publicKey := &curves.EcPoint{
// Curve: ecCurve,
// X: x,
// Y: y,
// }
// require.True(t,
// curves.VerifyEcdsa(publicKey,
// digest[:],
// result,
// ),
// "signature failed verification",
// )
// })
// }
//
// func refreshV1(t *testing.T, curve *curves.Curve, aliceDkgResultMessage, bobDkgResultMessage *protocol.Message) (aliceRefreshResultMessage, bobRefreshResultMessage *protocol.Message) {
// t.Helper()
// aliceRefresh, err := NewAliceRefresh(curve, aliceDkgResultMessage, protocol.Version1)
// require.NoError(t, err)
// bobRefresh, err := NewBobRefresh(curve, bobDkgResultMessage, protocol.Version1)
// require.NoError(t, err)
//
// aErr, bErr := runIteratedProtocol(aliceRefresh, bobRefresh)
// require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
// require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
//
// aliceRefreshResultMessage, err = aliceRefresh.Result(protocol.Version1)
// require.NoError(t, err)
// require.NotNil(t, aliceRefreshResultMessage)
// _, err = DecodeAliceRefreshResult(aliceRefreshResultMessage)
// require.NoError(t, err)
//
// bobRefreshResultMessage, err = bobRefresh.Result(protocol.Version1)
// require.NoError(t, err)
// require.NotNil(t, bobRefreshResultMessage)
// _, err = DecodeBobRefreshResult(bobRefreshResultMessage)
// require.NoError(t, err)
//
// return aliceRefreshResultMessage, bobRefreshResultMessage
// }
//
// func BenchmarkDKGProto(b *testing.B) {
// curveInstances := []*curves.Curve{
// curves.K256(),
// curves.P256(),
// }
// for _, curve := range curveInstances {
// alice := NewAliceDkg(curve, protocol.Version1)
// bob := NewBobDkg(curve, protocol.Version1)
// aErr, bErr := runIteratedProtocol(bob, alice)
//
// b.Run("both alice/bob complete simultaneously", func(t *testing.B) {
// require.ErrorIs(t, aErr, protocol.ErrProtocolFinished)
// require.ErrorIs(t, bErr, protocol.ErrProtocolFinished)
// })
//
// for i := 0; i < kos.Kappa; i++ {
// if alice.Alice.Output().SeedOtResult.OneTimePadDecryptionKey[i] != bob.Bob.Output().SeedOtResult.OneTimePadEncryptionKeys[i][alice.Alice.Output().SeedOtResult.RandomChoiceBits[i]] {
// b.Errorf("oblivious transfer is incorrect at index i=%v", i)
// }
// }
//
// b.Run("Both parties produces identical composite pubkey", func(t *testing.B) {
// require.True(t, alice.Alice.Output().PublicKey.Equal(bob.Bob.Output().PublicKey))
// })
//
// var aliceResult *dkg.AliceOutput
// var bobResult *dkg.BobOutput
// b.Run("alice produces valid result", func(t *testing.B) {
// // Get the result
// r, err := alice.Result(protocol.Version1)
//
// // Test
// require.NoError(t, err)
// require.NotNil(t, r)
// aliceResult, err = DecodeAliceDkgResult(r)
// require.NoError(t, err)
// })
// b.Run("bob produces valid result", func(t *testing.B) {
// // Get the result
// r, err := bob.Result(protocol.Version1)
//
// // Test
// require.NoError(t, err)
// require.NotNil(t, r)
// bobResult, err = DecodeBobDkgResult(r)
// require.NoError(t, err)
// })
//
// b.Run("alice/bob agree on pubkey", func(t *testing.B) {
// require.Equal(t, aliceResult.PublicKey, bobResult.PublicKey)
// })
// }
// }

View File

@@ -0,0 +1,193 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// This file implements the key refresh protocol of [DKLs18](https://eprint.iacr.org/2018/499.pdf).
// The key refresh protocol is defined as follows:
// 1. Key Share:
// 1.1. alice generates k_A <-- F_q; writes it to merlin transcript. sends it to bob.
// 1.2. bob receives k_A and writes it to merlin transcript. generates k_B <-- F_q and writes it to merlin transcript. reads k out of merlin transcript. overwrites sk_B *= k. sends k_B to Alice.
// 1.3. alice writes k_B to merlin transcript. reads k from it. overwrites sk_A *= k^{-1}.
// 2. OT: Redo OT (as it is done in the DKG)
package refresh
import (
"crypto/rand"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/zkp/schnorr"
)
// Alice struct encoding Alice's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Alice will NOT obtain the signature.
type Alice struct {
// receiver is the base OT receiver.
receiver *simplest.Receiver
// secretKeyShare is Alice's secret key for the joint public key.
secretKeyShare curves.Scalar
// publicKey is the joint public key of Alice and Bob.
publicKey curves.Point
curve *curves.Curve
transcript *merlin.Transcript
}
// Bob struct encoding Bob's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Bob will obtain the signature.
type Bob struct {
// sender is the base OT sender.
sender *simplest.Sender
// secretKeyShare is Bob's secret key for the joint public key.
secretKeyShare curves.Scalar
// publicKey is the joint public key of Alice and Bob.
publicKey curves.Point
curve *curves.Curve
transcript *merlin.Transcript
}
type RefreshRound2Output struct {
SeedOTRound1Output *schnorr.Proof
BobMultiplier curves.Scalar
}
// NewAliceRefresh creates a party that can participate in 2-of-2 key refresh.
func NewAlice(curve *curves.Curve, dkgOutput *dkg.AliceOutput) *Alice {
return &Alice{
curve: curve,
secretKeyShare: dkgOutput.SecretKeyShare,
publicKey: dkgOutput.PublicKey,
transcript: merlin.NewTranscript("Coinbase_DKLs_Refresh"),
}
}
// NewBobRefresh creates a party that can participate in 2-of-2 key refresh.
func NewBob(curve *curves.Curve, dkgOutput *dkg.BobOutput) *Bob {
return &Bob{
curve: curve,
secretKeyShare: dkgOutput.SecretKeyShare,
publicKey: dkgOutput.PublicKey,
transcript: merlin.NewTranscript("Coinbase_DKLs_Refresh"),
}
}
func (alice *Alice) Round1RefreshGenerateSeed() curves.Scalar {
refreshSeed := alice.curve.Scalar.Random(rand.Reader)
alice.transcript.AppendMessage([]byte("alice refresh seed"), refreshSeed.Bytes())
return refreshSeed
}
func (bob *Bob) Round2RefreshProduceSeedAndMultiplyAndStartOT(
aliceSeed curves.Scalar,
) (*RefreshRound2Output, error) {
bob.transcript.AppendMessage([]byte("alice refresh seed"), aliceSeed.Bytes())
bobSeed := bob.curve.Scalar.Random(rand.Reader)
bob.transcript.AppendMessage([]byte("bob refresh seed"), bobSeed.Bytes())
k, err := bob.curve.NewScalar().SetBytes(
bob.transcript.ExtractBytes([]byte("secret key share multiplier"), simplest.DigestSize),
)
if err != nil {
return nil, errors.Wrap(err, "couldn't produce bob's secret key share multiplier")
}
bob.secretKeyShare = bob.secretKeyShare.Mul(k)
uniqueSessionId := [simplest.DigestSize]byte{} // note: will use and re-use this below for sub-session IDs.
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("salt for simplest OT"), simplest.DigestSize),
)
bob.sender, err = simplest.NewSender(bob.curve, kos.Kappa, uniqueSessionId)
if err != nil {
return nil, errors.Wrap(err, "bob constructing new OT sender in refresh round 2")
}
seedOTRound1Output, err := bob.sender.Round1ComputeAndZkpToPublicKey()
if err != nil {
return nil, errors.Wrap(err, "bob computing round 1 of seed OT within refresh round 2")
}
return &RefreshRound2Output{
SeedOTRound1Output: seedOTRound1Output,
BobMultiplier: bobSeed,
}, nil
}
func (alice *Alice) Round3RefreshMultiplyRound2Ot(
input *RefreshRound2Output,
) ([]simplest.ReceiversMaskedChoices, error) {
alice.transcript.AppendMessage([]byte("bob refresh seed"), input.BobMultiplier.Bytes())
k, err := alice.curve.NewScalar().SetBytes(
alice.transcript.ExtractBytes([]byte("secret key share multiplier"), simplest.DigestSize),
)
if err != nil {
return nil, errors.Wrap(err, "couldn't produce bob's secret key share multiplier")
}
kInverse, err := k.Invert()
if err != nil {
return nil, errors.Wrap(err, "couldn't produce k inverse (alice's secret key share)")
}
alice.secretKeyShare = alice.secretKeyShare.Mul(kInverse)
uniqueSessionId := [simplest.DigestSize]byte{} // note: will use and re-use this below for sub-session IDs.
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("salt for simplest OT"), simplest.DigestSize),
)
alice.receiver, err = simplest.NewReceiver(alice.curve, kos.Kappa, uniqueSessionId)
if err != nil {
return nil, errors.Wrap(err, "couldn't construct OT receiver")
}
return alice.receiver.Round2VerifySchnorrAndPadTransfer(input.SeedOTRound1Output)
}
func (bob *Bob) Round4RefreshRound3Ot(
compressedReceiversMaskedChoice []simplest.ReceiversMaskedChoices,
) ([]simplest.OtChallenge, error) {
return bob.sender.Round3PadTransfer(compressedReceiversMaskedChoice)
}
func (alice *Alice) Round5RefreshRound4Ot(
challenge []simplest.OtChallenge,
) ([]simplest.OtChallengeResponse, error) {
return alice.receiver.Round4RespondToChallenge(challenge)
}
func (bob *Bob) Round6RefreshRound5Ot(
challengeResponses []simplest.OtChallengeResponse,
) ([]simplest.ChallengeOpening, error) {
return bob.sender.Round5Verify(challengeResponses)
}
func (alice *Alice) Round7DkgRound6Ot(challengeOpenings []simplest.ChallengeOpening) error {
return alice.receiver.Round6Verify(challengeOpenings)
}
func (alice *Alice) Output() *dkg.AliceOutput {
return &dkg.AliceOutput{
PublicKey: alice.publicKey,
SecretKeyShare: alice.secretKeyShare,
SeedOtResult: alice.receiver.Output,
}
}
func (bob *Bob) Output() *dkg.BobOutput {
return &dkg.BobOutput{
PublicKey: bob.publicKey,
SecretKeyShare: bob.secretKeyShare,
SeedOtResult: bob.sender.Output,
}
}

View File

@@ -0,0 +1,215 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package refresh_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/refresh"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/sign"
)
func performDKG(t *testing.T, curve *curves.Curve) (*dkg.Alice, *dkg.Bob) {
t.Helper()
alice := dkg.NewAlice(curve)
bob := dkg.NewBob(curve)
seed, err := bob.Round1GenerateRandomSeed()
require.NoError(t, err)
round3Output, err := alice.Round2CommitToProof(seed)
require.NoError(t, err)
proof, err := bob.Round3SchnorrProve(round3Output)
require.NoError(t, err)
proof, err = alice.Round4VerifyAndReveal(proof)
require.NoError(t, err)
proof, err = bob.Round5DecommitmentAndStartOt(proof)
require.NoError(t, err)
compressedReceiversMaskedChoice, err := alice.Round6DkgRound2Ot(proof)
require.NoError(t, err)
challenge, err := bob.Round7DkgRound3Ot(compressedReceiversMaskedChoice)
require.NoError(t, err)
challengeResponse, err := alice.Round8DkgRound4Ot(challenge)
require.NoError(t, err)
challengeOpenings, err := bob.Round9DkgRound5Ot(challengeResponse)
require.NoError(t, err)
err = alice.Round10DkgRound6Ot(challengeOpenings)
require.NoError(t, err)
return alice, bob
}
func performRefresh(
t *testing.T,
curve *curves.Curve,
aliceSecretKeyShare, bobSecretKeyShare curves.Scalar,
) (*refresh.Alice, *refresh.Bob) {
t.Helper()
alice := refresh.NewAlice(curve, &dkg.AliceOutput{SecretKeyShare: aliceSecretKeyShare})
bob := refresh.NewBob(curve, &dkg.BobOutput{SecretKeyShare: bobSecretKeyShare})
round1Output := alice.Round1RefreshGenerateSeed()
require.False(t, round1Output.IsZero())
round2Output, err := bob.Round2RefreshProduceSeedAndMultiplyAndStartOT(round1Output)
require.NoError(t, err)
round3Output, err := alice.Round3RefreshMultiplyRound2Ot(round2Output)
require.NoError(t, err)
round4Output, err := bob.Round4RefreshRound3Ot(round3Output)
require.NoError(t, err)
round5Output, err := alice.Round5RefreshRound4Ot(round4Output)
require.NoError(t, err)
round6Output, err := bob.Round6RefreshRound5Ot(round5Output)
require.NoError(t, err)
err = alice.Round7DkgRound6Ot(round6Output)
require.NoError(t, err)
return alice, bob
}
func Test_RefreshLeadsToTheSamePublicKeyButDifferentPrivateMaterial(t *testing.T) {
t.Parallel()
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
boundCurve := curve
t.Run(fmt.Sprintf("testing refresh for curve %s", boundCurve.Name), func(tt *testing.T) {
tt.Parallel()
alice, bob := performDKG(tt, boundCurve)
publicKey := alice.Output().PublicKey
require.True(tt, publicKey.Equal(bob.Output().PublicKey))
aliceRefreshed, bobRefreshed := performRefresh(
tt,
boundCurve,
alice.Output().SecretKeyShare,
bob.Output().SecretKeyShare,
)
require.NotEqual(
tt,
aliceRefreshed.Output().SecretKeyShare,
alice.Output().SecretKeyShare,
)
require.NotEqual(tt, bobRefreshed.Output().SeedOtResult, bob.Output().SecretKeyShare)
require.NotEqualValues(
tt,
aliceRefreshed.Output().SeedOtResult.OneTimePadDecryptionKey,
alice.Output().SeedOtResult.OneTimePadDecryptionKey,
)
require.NotEqualValues(
tt,
aliceRefreshed.Output().SeedOtResult.PackedRandomChoiceBits,
alice.Output().SeedOtResult.PackedRandomChoiceBits,
)
require.NotEqualValues(
tt,
aliceRefreshed.Output().SeedOtResult.RandomChoiceBits,
alice.Output().SeedOtResult.RandomChoiceBits,
)
require.NotEqualValues(
tt,
bobRefreshed.Output().SeedOtResult.OneTimePadEncryptionKeys,
bob.Output().SeedOtResult.OneTimePadEncryptionKeys,
)
pkA := boundCurve.ScalarBaseMult(aliceRefreshed.Output().SecretKeyShare)
computedPublicKeyA := pkA.Mul(bobRefreshed.Output().SecretKeyShare)
require.True(tt, computedPublicKeyA.Equal(publicKey))
pkB := boundCurve.ScalarBaseMult(bobRefreshed.Output().SecretKeyShare)
computedPublicKeyB := pkB.Mul(aliceRefreshed.Output().SecretKeyShare)
require.True(tt, computedPublicKeyB.Equal(publicKey))
})
}
}
func Test_RefreshOTIsCorrect(t *testing.T) {
t.Parallel()
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
boundCurve := curve
t.Run(
fmt.Sprintf("testing OT correctness of key refresh for curve %s", boundCurve.Name),
func(tt *testing.T) {
tt.Parallel()
alice, bob := performDKG(tt, boundCurve)
aliceRefreshed, bobRefreshed := performRefresh(
tt,
boundCurve,
alice.Output().SecretKeyShare,
bob.Output().SecretKeyShare,
)
for i := 0; i < kos.Kappa; i++ {
if aliceRefreshed.Output().SeedOtResult.OneTimePadDecryptionKey[i] != bobRefreshed.Output().SeedOtResult.OneTimePadEncryptionKeys[i][aliceRefreshed.Output().SeedOtResult.RandomChoiceBits[i]] {
tt.Errorf("oblivious transfer is incorrect at index i=%v", i)
}
}
},
)
}
}
func Test_CanSignAfterRefresh(t *testing.T) {
t.Parallel()
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
boundCurve := curve
t.Run(
fmt.Sprintf("testing sign after refresh for curve %s", boundCurve.Name),
func(tt *testing.T) {
tt.Parallel()
aliceDKG, bobDKG := performDKG(tt, boundCurve)
publicKey := aliceDKG.Output().PublicKey
require.True(tt, publicKey.Equal(bobDKG.Output().PublicKey))
aliceRefreshed, bobRefreshed := performRefresh(
tt,
boundCurve,
aliceDKG.Output().SecretKeyShare,
bobDKG.Output().SecretKeyShare,
)
alice := sign.NewAlice(boundCurve, sha3.New256(), &dkg.AliceOutput{
SeedOtResult: aliceRefreshed.Output().SeedOtResult,
SecretKeyShare: aliceRefreshed.Output().SecretKeyShare,
PublicKey: publicKey,
})
bob := sign.NewBob(boundCurve, sha3.New256(), &dkg.BobOutput{
SeedOtResult: bobRefreshed.Output().SeedOtResult,
SecretKeyShare: bobRefreshed.Output().SecretKeyShare,
PublicKey: publicKey,
})
message := []byte("A message.")
seed, err := alice.Round1GenerateRandomSeed()
require.NoError(tt, err)
round3Output, err := bob.Round2Initialize(seed)
require.NoError(tt, err)
round4Output, err := alice.Round3Sign(message, round3Output)
require.NoError(tt, err)
err = bob.Round4Final(message, round4Output)
require.NoError(tt, err, "curve: %s", boundCurve.Name)
},
)
}
}

View File

@@ -0,0 +1,253 @@
package dklsv1
import (
"bytes"
"encoding/gob"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/refresh"
)
func newRefreshProtocolMessage(payload []byte, round string, version uint) *protocol.Message {
return &protocol.Message{
Protocol: protocol.Dkls18Refresh,
Version: version,
Payloads: map[string][]byte{payloadKey: payload},
Metadata: map[string]string{"round": round},
}
}
func versionIsSupported(messageVersion uint) error {
if messageVersion < uint(protocol.Version1) {
return errors.New("only version 1 is supported.")
}
return nil
}
func encodeRefreshRound1Output(seed curves.Scalar, version uint) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(&seed); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "1", version), nil
}
func decodeRefreshRound2Input(m *protocol.Message) (curves.Scalar, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(curves.Scalar)
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return *decoded, nil
}
func encodeRefreshRound2Output(
output *refresh.RefreshRound2Output,
version uint,
) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(output); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "2", version), nil
}
func decodeRefreshRound3Input(m *protocol.Message) (*refresh.RefreshRound2Output, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(refresh.RefreshRound2Output)
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeRefreshRound3Output(
choices []simplest.ReceiversMaskedChoices,
version uint,
) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(choices); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "3", version), nil
}
func decodeRefreshRound4Input(m *protocol.Message) ([]simplest.ReceiversMaskedChoices, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.ReceiversMaskedChoices{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeRefreshRound4Output(
challenge []simplest.OtChallenge,
version uint,
) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(challenge); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "4", version), nil
}
func decodeRefreshRound5Input(m *protocol.Message) ([]simplest.OtChallenge, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.OtChallenge{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeRefreshRound5Output(
responses []simplest.OtChallengeResponse,
version uint,
) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(responses); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "5", version), nil
}
func decodeRefreshRound6Input(m *protocol.Message) ([]simplest.OtChallengeResponse, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.OtChallengeResponse{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeRefreshRound6Output(
opening []simplest.ChallengeOpening,
version uint,
) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(opening); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "6", version), nil
}
func decodeRefreshRound7Input(m *protocol.Message) ([]simplest.ChallengeOpening, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := []simplest.ChallengeOpening{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
// EncodeAliceRefreshOutput serializes Alice Refresh output based on the protocol version.
func EncodeAliceRefreshOutput(result *dkg.AliceOutput, version uint) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(result); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "alice-output", version), nil
}
// DecodeAliceRefreshResult deserializes Alice refresh output.
func DecodeAliceRefreshResult(m *protocol.Message) (*dkg.AliceOutput, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
registerTypes()
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(dkg.AliceOutput)
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
// EncodeBobRefreshOutput serializes Bob refresh output based on the protocol version.
func EncodeBobRefreshOutput(result *dkg.BobOutput, version uint) (*protocol.Message, error) {
if err := versionIsSupported(version); err != nil {
return nil, errors.Wrap(err, "version error")
}
registerTypes()
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(result); err != nil {
return nil, errors.WithStack(err)
}
return newRefreshProtocolMessage(buf.Bytes(), "bob-output", version), nil
}
// DecodeBobRefreshResult deserializes Bob refhresh output.
func DecodeBobRefreshResult(m *protocol.Message) (*dkg.BobOutput, error) {
if err := versionIsSupported(m.Version); err != nil {
return nil, errors.Wrap(err, "version error")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := new(dkg.BobOutput)
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}

View File

@@ -0,0 +1,299 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package sign
import (
"crypto/rand"
"crypto/subtle"
"fmt"
"math/big"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/internal"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
)
// This implements the Multiplication protocol of DKLs, protocol 5. https://eprint.iacr.org/2018/499.pdf
// two parties---the "sender" and "receiver", let's say---each input a scalar modulo q.
// the functionality multiplies their two scalars modulo q, and then randomly additively shares the product mod q.
// it then returns the two respective additive shares to the two parties.
// MultiplySender is the party that plays the role of Sender in the multiplication protocol (protocol 5 of the paper).
type MultiplySender struct {
cOtSender *kos.Sender // underlying cOT sender struct, used by mult.
outputAdditiveShare curves.Scalar // ultimate output share of mult.
gadget [kos.L]curves.Scalar
curve *curves.Curve
transcript *merlin.Transcript
uniqueSessionId [simplest.DigestSize]byte
}
// MultiplyReceiver is the party that plays the role of Sender in the multiplication protocol (protocol 5 of the paper).
type MultiplyReceiver struct {
cOtReceiver *kos.Receiver // underlying cOT receiver struct, used by mult.
outputAdditiveShare curves.Scalar // ultimate output share of mult.
omega [kos.COtBlockSizeBytes]byte // this is used as an intermediate result during the course of mult.
gadget [kos.L]curves.Scalar
curve *curves.Curve
transcript *merlin.Transcript
uniqueSessionId [simplest.DigestSize]byte
}
func generateGadgetVector(curve *curves.Curve) ([kos.L]curves.Scalar, error) {
var err error
gadget := [kos.L]curves.Scalar{}
for i := 0; i < kos.Kappa; i++ {
gadget[i], err = curve.Scalar.SetBigInt(new(big.Int).Lsh(big.NewInt(1), uint(i)))
if err != nil {
return gadget, errors.Wrap(err, "creating gadget scalar from big int")
}
}
shake := sha3.NewCShake256(nil, []byte("Coinbase DKLs gadget vector"))
for i := kos.Kappa; i < kos.L; i++ {
var err error
bytes := [simplest.DigestSize]byte{}
if _, err = shake.Read(bytes[:]); err != nil {
return gadget, err
}
gadget[i], err = curve.Scalar.SetBytes(bytes[:])
if err != nil {
return gadget, errors.Wrap(err, "creating gadget scalar from bytes")
}
}
return gadget, nil
}
// NewMultiplySender generates a `MultiplySender` instance, ready to take part in multiplication as the "sender".
// You must supply it the _output_ of a seed OT, from the receiver's point of view, as well as params and a unique ID.
// That is, the mult sender must run the base OT as the receiver; note the (apparent) reversal of roles.
func NewMultiplySender(
seedOtResults *simplest.ReceiverOutput,
curve *curves.Curve,
uniqueSessionId [simplest.DigestSize]byte,
) (*MultiplySender, error) {
sender := kos.NewCOtSender(seedOtResults, curve)
gadget, err := generateGadgetVector(curve)
if err != nil {
return nil, errors.Wrap(err, "error generating gadget vector in new multiply sender")
}
transcript := merlin.NewTranscript("Coinbase_DKLs_Multiply")
transcript.AppendMessage([]byte("session_id"), uniqueSessionId[:])
return &MultiplySender{
cOtSender: sender,
curve: curve,
transcript: transcript,
uniqueSessionId: uniqueSessionId,
gadget: gadget,
}, nil
}
// NewMultiplyReceiver generates a `MultiplyReceiver` instance, ready to take part in multiplication as the "receiver".
// You must supply it the _output_ of a seed OT, from the sender's point of view, as well as params and a unique ID.
// That is, the mult sender must run the base OT as the sender; note the (apparent) reversal of roles.
func NewMultiplyReceiver(
seedOtResults *simplest.SenderOutput,
curve *curves.Curve,
uniqueSessionId [simplest.DigestSize]byte,
) (*MultiplyReceiver, error) {
receiver := kos.NewCOtReceiver(seedOtResults, curve)
gadget, err := generateGadgetVector(curve)
if err != nil {
return nil, errors.Wrap(err, "error generating gadget vector in new multiply receiver")
}
transcript := merlin.NewTranscript("Coinbase_DKLs_Multiply")
transcript.AppendMessage([]byte("session_id"), uniqueSessionId[:])
return &MultiplyReceiver{
cOtReceiver: receiver,
curve: curve,
transcript: transcript,
uniqueSessionId: uniqueSessionId,
gadget: gadget,
}, nil
}
// MultiplyRound2Output is the output of the second round of the multiplication protocol.
type MultiplyRound2Output struct {
COTRound2Output *kos.Round2Output
R [kos.L]curves.Scalar
U curves.Scalar
}
// Algorithm 5. in DKLs. "Encodes" Bob's secret input scalars `beta` in the right way, using the opts.
// The idea is that if Bob were to just put beta's as the choice vector, then Alice could learn a few of Bob's bits.
// using selective failure attacks. so you subtract random components of a public random vector. see paper for details.
func (receiver *MultiplyReceiver) encode(beta curves.Scalar) ([kos.COtBlockSizeBytes]byte, error) {
// passing beta by value, so that we can mutate it locally. check that this does what i want.
encoding := [kos.COtBlockSizeBytes]byte{}
bytesOfBetaMinusDotProduct := beta.Bytes()
if _, err := rand.Read(encoding[kos.KappaBytes:]); err != nil {
return encoding, errors.Wrap(
err,
"sampling `gamma` random bytes in multiply receiver encode",
)
}
for j := kos.Kappa; j < kos.L; j++ {
jthBitOfGamma := simplest.ExtractBitFromByteVector(encoding[:], j)
// constant-time computation of the dot product beta - < gR, gamma >.
// we can only `ConstantTimeCopy` byte slices (as opposed to big ints). so keep them as bytes.
option0, err := receiver.curve.Scalar.SetBytes(bytesOfBetaMinusDotProduct[:])
if err != nil {
return encoding, errors.Wrap(err, "setting masking bits scalar from bytes")
}
option0Bytes := option0.Bytes()
option1 := option0.Sub(receiver.gadget[j])
option1Bytes := option1.Bytes()
bytesOfBetaMinusDotProduct = option0Bytes
subtle.ConstantTimeCopy(int(jthBitOfGamma), bytesOfBetaMinusDotProduct[:], option1Bytes)
}
copy(encoding[0:kos.KappaBytes], internal.ReverseScalarBytes(bytesOfBetaMinusDotProduct[:]))
return encoding, nil
}
// Round1Initialize Protocol 5., Multiplication, 3). Bob (receiver) encodes beta and initiates the cOT extension
func (receiver *MultiplyReceiver) Round1Initialize(beta curves.Scalar) (*kos.Round1Output, error) {
var err error
if receiver.omega, err = receiver.encode(beta); err != nil {
return nil, errors.Wrap(err, "encoding input beta in receiver round 1 initialize")
}
cOtRound1Output, err := receiver.cOtReceiver.Round1Initialize(
receiver.uniqueSessionId,
receiver.omega,
)
if err != nil {
return nil, errors.Wrap(
err,
"error in cOT round 1 initialize within multiply round 1 initialize",
)
}
// write the output of the first round to the transcript
for i := 0; i < kos.Kappa; i++ {
label := []byte(fmt.Sprintf("row %d of U", i))
receiver.transcript.AppendMessage(label, cOtRound1Output.U[i][:])
}
receiver.transcript.AppendMessage([]byte("wPrime"), cOtRound1Output.WPrime[:])
receiver.transcript.AppendMessage([]byte("vPrime"), cOtRound1Output.VPrime[:])
return cOtRound1Output, nil
}
// Round2Multiply Protocol 5., steps 3) 5), 7). Alice _responds_ to Bob's initial cOT message, using alpha as input.
// Doesn't actually send the message yet, only stashes it and moves onto the next steps of the multiplication protocol
// specifically, Alice can then do step 5) (compute the outputs of the multiplication protocol), also stashes this.
// Finishes by taking care of 7), after that, Alice is totally done with multiplication and has stashed the outputs.
func (sender *MultiplySender) Round2Multiply(
alpha curves.Scalar,
round1Output *kos.Round1Output,
) (*MultiplyRound2Output, error) {
var err error
alphaHat := sender.curve.Scalar.Random(rand.Reader)
input := [kos.L][2]curves.Scalar{} // sender's input, namely integer "sums" in case w_j == 1.
for j := 0; j < kos.L; j++ {
input[j][0] = alpha
input[j][1] = alphaHat
}
round2Output := &MultiplyRound2Output{}
round2Output.COTRound2Output, err = sender.cOtSender.Round2Transfer(
sender.uniqueSessionId,
input,
round1Output,
)
if err != nil {
return nil, errors.Wrap(err, "error in cOT within round 2 multiply")
}
// write the output of the first round to the transcript
for i := 0; i < kos.Kappa; i++ {
label := []byte(fmt.Sprintf("row %d of U", i))
sender.transcript.AppendMessage(label, round1Output.U[i][:])
}
sender.transcript.AppendMessage([]byte("wPrime"), round1Output.WPrime[:])
sender.transcript.AppendMessage([]byte("vPrime"), round1Output.VPrime[:])
// write our own output of the second round to the transcript
chiWidth := 2
for i := 0; i < kos.Kappa; i++ {
for k := 0; k < chiWidth; k++ {
label := []byte(fmt.Sprintf("row %d of Tau", i))
sender.transcript.AppendMessage(label, round2Output.COTRound2Output.Tau[i][k].Bytes())
}
}
chi := make([]curves.Scalar, chiWidth)
for k := 0; k < 2; k++ {
label := []byte(fmt.Sprintf("draw challenge chi %d", k))
randomBytes := sender.transcript.ExtractBytes(label, kos.KappaBytes)
chi[k], err = sender.curve.Scalar.SetBytes(randomBytes)
if err != nil {
return nil, errors.Wrap(err, "setting chi scalar from bytes")
}
}
sender.outputAdditiveShare = sender.curve.Scalar.Zero()
for j := 0; j < kos.L; j++ {
round2Output.R[j] = sender.curve.Scalar.Zero()
for k := 0; k < chiWidth; k++ {
round2Output.R[j] = round2Output.R[j].Add(
chi[k].Mul(sender.cOtSender.OutputAdditiveShares[j][k]),
)
}
sender.outputAdditiveShare = sender.outputAdditiveShare.Add(
sender.gadget[j].Mul(sender.cOtSender.OutputAdditiveShares[j][0]),
)
}
round2Output.U = chi[0].Mul(alpha).Add(chi[1].Mul(alphaHat))
return round2Output, nil
}
// Round3Multiply Protocol 5., Multiplication, 3) and 6). Bob finalizes the cOT extension.
// using that and Alice's multiplication message, Bob completes the multiplication protocol, including checks.
// At the end, Bob's values tB_j are populated.
func (receiver *MultiplyReceiver) Round3Multiply(round2Output *MultiplyRound2Output) error {
chiWidth := 2
// write the output of the second round to the transcript
for i := 0; i < kos.Kappa; i++ {
for k := 0; k < chiWidth; k++ {
label := []byte(fmt.Sprintf("row %d of Tau", i))
receiver.transcript.AppendMessage(label, round2Output.COTRound2Output.Tau[i][k].Bytes())
}
}
if err := receiver.cOtReceiver.Round3Transfer(round2Output.COTRound2Output); err != nil {
return errors.Wrap(err, "error within cOT round 3 transfer within round 3 multiply")
}
var err error
chi := make([]curves.Scalar, chiWidth)
for k := 0; k < chiWidth; k++ {
label := []byte(fmt.Sprintf("draw challenge chi %d", k))
randomBytes := receiver.transcript.ExtractBytes(label, kos.KappaBytes)
chi[k], err = receiver.curve.Scalar.SetBytes(randomBytes)
if err != nil {
return errors.Wrap(err, "setting chi scalar from bytes")
}
}
receiver.outputAdditiveShare = receiver.curve.Scalar.Zero()
for j := 0; j < kos.L; j++ {
// compute the LHS of bob's step 6) for j. note that we're "adding r_j" to both sides"; so this LHS includes r_j.
// the reason to do this is so that the constant-time (i.e., independent of w_j) calculation of w_j * u can proceed more cleanly.
leftHandSideOfCheck := round2Output.R[j]
for k := 0; k < chiWidth; k++ {
leftHandSideOfCheck = leftHandSideOfCheck.Add(
chi[k].Mul(receiver.cOtReceiver.OutputAdditiveShares[j][k]),
)
}
rightHandSideOfCheck := [simplest.DigestSize]byte{}
jthBitOfOmega := simplest.ExtractBitFromByteVector(receiver.omega[:], j)
subtle.ConstantTimeCopy(int(jthBitOfOmega), rightHandSideOfCheck[:], round2Output.U.Bytes())
if subtle.ConstantTimeCompare(rightHandSideOfCheck[:], leftHandSideOfCheck.Bytes()) != 1 {
return fmt.Errorf("alice's values R and U failed to check in round 3 multiply")
}
receiver.outputAdditiveShare = receiver.outputAdditiveShare.Add(
receiver.gadget[j].Mul(receiver.cOtReceiver.OutputAdditiveShares[j][0]),
)
}
return nil
}

View File

@@ -0,0 +1,52 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package sign
import (
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/ot/ottest"
)
func TestMultiply(t *testing.T) {
curve := curves.K256()
hashKeySeed := [simplest.DigestSize]byte{}
_, err := rand.Read(hashKeySeed[:])
require.NoError(t, err)
baseOtSenderOutput, baseOtReceiverOutput, err := ottest.RunSimplestOT(
curve,
kos.Kappa,
hashKeySeed,
)
require.NoError(t, err)
sender, err := NewMultiplySender(baseOtReceiverOutput, curve, hashKeySeed)
require.NoError(t, err)
receiver, err := NewMultiplyReceiver(baseOtSenderOutput, curve, hashKeySeed)
require.NoError(t, err)
alpha := curve.Scalar.Random(rand.Reader)
beta := curve.Scalar.Random(rand.Reader)
round1Output, err := receiver.Round1Initialize(beta)
require.Nil(t, err)
round2Output, err := sender.Round2Multiply(alpha, round1Output)
require.Nil(t, err)
err = receiver.Round3Multiply(round2Output)
require.Nil(t, err)
product := alpha.Mul(beta)
sum := sender.outputAdditiveShare.Add(receiver.outputAdditiveShare)
require.Equal(t, product, sum)
}

409
tecdsa/dklsv1/sign/sign.go Normal file
View File

@@ -0,0 +1,409 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package sign implements the 2-2 threshold signature protocol of [DKLs18](https://eprint.iacr.org/2018/499.pdf).
// The signing protocol is defined in "Protocol 4" page 9, of the paper. The Zero Knowledge Proof ideal functionalities are
// realized using schnorr proofs.
package sign
import (
"crypto/ecdsa"
"crypto/rand"
"fmt"
"hash"
"math/big"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
"github.com/sonr-io/sonr/crypto/zkp/schnorr"
)
const multiplicationCount = 2
// Alice struct encoding Alice's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Alice will not possess the signature.
type Alice struct {
hash hash.Hash // which hash function should we use to compute message (i.e, teh digest)
seedOtResults *simplest.ReceiverOutput
secretKeyShare curves.Scalar // the witness
publicKey curves.Point
curve *curves.Curve
transcript *merlin.Transcript
}
// Bob struct encoding Bob's state during one execution of the overall signing algorithm.
// At the end of the joint computation, Bob will obtain the signature.
type Bob struct {
// Signature is the resulting digital signature and is the output of this protocol.
Signature *curves.EcdsaSignature
hash hash.Hash // which hash function should we use to compute message
seedOtResults *simplest.SenderOutput
secretKeyShare curves.Scalar
publicKey curves.Point
transcript *merlin.Transcript
// multiplyReceivers are 2 receivers that are used to perform the two multiplications needed:
// 1. (phi + 1/kA) * (1/kB)
// 2. skA/KA * skB/kB
multiplyReceivers [multiplicationCount]*MultiplyReceiver
kB curves.Scalar
dB curves.Point
curve *curves.Curve
}
// NewAlice creates a party that can participate in protocol runs of DKLs sign, in the role of Alice.
func NewAlice(curve *curves.Curve, hash hash.Hash, dkgOutput *dkg.AliceOutput) *Alice {
return &Alice{
hash: hash,
seedOtResults: dkgOutput.SeedOtResult,
curve: curve,
secretKeyShare: dkgOutput.SecretKeyShare,
publicKey: dkgOutput.PublicKey,
transcript: merlin.NewTranscript("Coinbase_DKLs_Sign"),
}
}
// NewBob creates a party that can participate in protocol runs of DKLs sign, in the role of Bob.
// This party receives the signature at the end.
func NewBob(curve *curves.Curve, hash hash.Hash, dkgOutput *dkg.BobOutput) *Bob {
return &Bob{
hash: hash,
seedOtResults: dkgOutput.SeedOtResult,
curve: curve,
secretKeyShare: dkgOutput.SecretKeyShare,
publicKey: dkgOutput.PublicKey,
transcript: merlin.NewTranscript("Coinbase_DKLs_Sign"),
}
}
// SignRound2Output is the output of the 3rd round of the protocol.
type SignRound2Output struct {
// KosRound1Outputs is the output of the first round of OT Extension, stored for future rounds.
KosRound1Outputs [multiplicationCount]*kos.Round1Output
// DB is D_{B} = k_{B} . G from the paper.
DB curves.Point
// Seed is the random value used to derive the joint unique session id.
Seed [simplest.DigestSize]byte
}
// SignRound3Output is the output of the 3rd round of the protocol.
type SignRound3Output struct {
// MultiplyRound2Outputs is the output of the second round of multiply sub-protocol. Stored to use in future rounds.
MultiplyRound2Outputs [multiplicationCount]*MultiplyRound2Output
// RSchnorrProof is ZKP for the value R = k_{A} . D_{B} from the paper.
RSchnorrProof *schnorr.Proof
// RPrime is R' = k'_{A} . D_{B} from the paper.
RPrime curves.Point
// EtaPhi is the Eta_{Phi} from the paper.
EtaPhi curves.Scalar
// EtaSig is the Eta_{Sig} from the paper.
EtaSig curves.Scalar
}
// Round1GenerateRandomSeed first step of the generation of the shared random salt `idExt`
// in this round, Alice flips 32 random bytes and sends them to Bob.
// Note that this is not _explicitly_ given as part of the protocol in https://eprint.iacr.org/2018/499.pdf, Protocol 1).
// Rather, it is part of our generation of `idExt`, the shared random salt which both parties must use in cOT.
// This value introduced in Protocol 9), very top of page 16. it is not indicated how it should be derived.
// We do it by having each party sample 32 bytes, then by appending _both_ as salts. Secure if either party is honest
func (alice *Alice) Round1GenerateRandomSeed() ([simplest.DigestSize]byte, error) {
aliceSeed := [simplest.DigestSize]byte{}
if _, err := rand.Read(aliceSeed[:]); err != nil {
return [simplest.DigestSize]byte{}, errors.Wrap(
err,
"generating random bytes in alice round 1 generate",
)
}
alice.transcript.AppendMessage([]byte("session_id_alice"), aliceSeed[:])
return aliceSeed, nil
}
// Round2Initialize Bob's initial message, which kicks off the signature process. Protocol 1, Bob's steps 1) - 3).
// Bob's work here entails beginning the DiffieHellman-like construction of the instance key / nonce,
// as well as preparing the inputs which he will feed into the multiplication protocol,
// and moreover actually initiating the (first respective messages of) the multiplication protocol using these inputs.
// This latter step in turn amounts to sending the initial message in a new cOT extension.
// All the resulting data gets packaged and sent to Alice.
func (bob *Bob) Round2Initialize(aliceSeed [simplest.DigestSize]byte) (*SignRound2Output, error) {
bobSeed := [simplest.DigestSize]byte{}
if _, err := rand.Read(bobSeed[:]); err != nil {
return nil, errors.Wrap(err, "flipping random coins in bob round 2 initialize")
}
bob.transcript.AppendMessage([]byte("session_id_alice"), aliceSeed[:])
bob.transcript.AppendMessage([]byte("session_id_bob"), bobSeed[:])
var err error
uniqueSessionId := [simplest.DigestSize]byte{} // will use and _re-use_ this throughout, for sub-session IDs
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("multiply receiver id 0"), simplest.DigestSize),
)
bob.multiplyReceivers[0], err = NewMultiplyReceiver(
bob.seedOtResults,
bob.curve,
uniqueSessionId,
)
if err != nil {
return nil, errors.Wrap(err, "error creating multiply receiver 0 in Bob sign round 3")
}
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("multiply receiver id 1"), simplest.DigestSize),
)
bob.multiplyReceivers[1], err = NewMultiplyReceiver(
bob.seedOtResults,
bob.curve,
uniqueSessionId,
)
if err != nil {
return nil, errors.Wrap(err, "error creating multiply receiver 1 in Bob sign round 3")
}
round2Output := &SignRound2Output{
Seed: bobSeed,
}
bob.kB = bob.curve.Scalar.Random(rand.Reader)
bob.dB = bob.curve.ScalarBaseMult(bob.kB)
round2Output.DB = bob.dB
kBInv := bob.curve.Scalar.One().Div(bob.kB)
round2Output.KosRound1Outputs[0], err = bob.multiplyReceivers[0].Round1Initialize(kBInv)
if err != nil {
return nil, errors.Wrap(
err,
"error in multiply round 1 initialize 0 within Bob sign round 3 initialize",
)
}
round2Output.KosRound1Outputs[1], err = bob.multiplyReceivers[1].Round1Initialize(
bob.secretKeyShare.Mul(kBInv),
)
if err != nil {
return nil, errors.Wrap(
err,
"error in multiply round 1 initialize 1 within Bob sign round 3 initialize",
)
}
return round2Output, nil
}
// Round3Sign Alice's first message. Alice is the _responder_; she is responding to Bob's initial message.
// This is Protocol 1 (p. 6), and contains Alice's steps 3) -- 8). these can all be combined into one message.
// Alice's job here is to finish computing the shared instance key / nonce, as well as multiplication input values;
// then to invoke the multiplication on these two input values (stashing the outputs in her running result struct),
// then to use the _output_ of the multiplication (which she already possesses as of the end of her computation),
// and use that to compute some final values which will help Bob compute the final signature.
func (alice *Alice) Round3Sign(
message []byte,
round2Output *SignRound2Output,
) (*SignRound3Output, error) {
alice.transcript.AppendMessage([]byte("session_id_bob"), round2Output.Seed[:])
multiplySenders := [multiplicationCount]*MultiplySender{}
var err error
uniqueSessionId := [simplest.DigestSize]byte{} // will use and _re-use_ this throughout, for sub-session IDs
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("multiply receiver id 0"), simplest.DigestSize),
)
if multiplySenders[0], err = NewMultiplySender(alice.seedOtResults, alice.curve, uniqueSessionId); err != nil {
return nil, errors.Wrap(err, "creating multiply sender 0 in Alice round 4 sign")
}
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("multiply receiver id 1"), simplest.DigestSize),
)
if multiplySenders[1], err = NewMultiplySender(alice.seedOtResults, alice.curve, uniqueSessionId); err != nil {
return nil, errors.Wrap(err, "creating multiply sender 1 in Alice round 4 sign")
}
round3Output := &SignRound3Output{}
kPrimeA := alice.curve.Scalar.Random(rand.Reader)
round3Output.RPrime = round2Output.DB.Mul(kPrimeA)
hashRPrimeBytes := sha3.Sum256(round3Output.RPrime.ToAffineCompressed())
hashRPrime, err := alice.curve.Scalar.SetBytes(hashRPrimeBytes[:])
if err != nil {
return nil, errors.Wrap(err, "setting hashRPrime scalar from bytes")
}
kA := hashRPrime.Add(kPrimeA)
copy(
uniqueSessionId[:],
alice.transcript.ExtractBytes([]byte("schnorr proof for R"), simplest.DigestSize),
)
rSchnorrProver := schnorr.NewProver(alice.curve, round2Output.DB, uniqueSessionId[:])
round3Output.RSchnorrProof, err = rSchnorrProver.Prove(kA)
if err != nil {
return nil, errors.Wrap(
err,
"generating schnorr proof for R = kA * DB in alice round 4 sign",
)
}
// reassign / stash the below value here just for notational clarity.
// this is _the_ key public point R in the ECDSA signature. we'll use its coordinate X in various places.
r := round3Output.RSchnorrProof.Statement
phi := alice.curve.Scalar.Random(rand.Reader)
kAInv := alice.curve.Scalar.One().Div(kA)
if round3Output.MultiplyRound2Outputs[0], err = multiplySenders[0].Round2Multiply(phi.Add(kAInv), round2Output.KosRound1Outputs[0]); err != nil {
return nil, errors.Wrap(err, "error in round 2 multiply 0 within alice round 4 sign")
}
if round3Output.MultiplyRound2Outputs[1], err = multiplySenders[1].Round2Multiply(alice.secretKeyShare.Mul(kAInv), round2Output.KosRound1Outputs[1]); err != nil {
return nil, errors.Wrap(err, "error in round 2 multiply 1 within alice round 4 sign")
}
one := alice.curve.Scalar.One()
gamma1 := alice.curve.ScalarBaseMult(kA.Mul(phi).Add(one))
other := r.Mul(multiplySenders[0].outputAdditiveShare.Neg())
gamma1 = gamma1.Add(other)
hashGamma1Bytes := sha3.Sum256(gamma1.ToAffineCompressed())
hashGamma1, err := alice.curve.Scalar.SetBytes(hashGamma1Bytes[:])
if err != nil {
return nil, errors.Wrap(err, "setting hashGamma1 scalar from bytes")
}
round3Output.EtaPhi = hashGamma1.Add(phi)
if _, err = alice.hash.Write(message); err != nil {
return nil, errors.Wrap(err, "writing message to hash in alice round 4 sign")
}
digest := alice.hash.Sum(nil)
hOfMAsInteger, err := alice.curve.Scalar.SetBytes(digest)
if err != nil {
return nil, errors.Wrap(err, "setting hOfMAsInteger scalar from bytes")
}
affineCompressedForm := r.ToAffineCompressed()
if len(affineCompressedForm) != 33 {
return nil, errors.New("the compressed form must be exactly 33 bytes")
}
// Discard the leading byte and parse the rest as the X coordinate.
rX, err := alice.curve.Scalar.SetBytes(affineCompressedForm[1:])
if err != nil {
return nil, errors.Wrap(err, "setting rX scalar from bytes")
}
sigA := hOfMAsInteger.Mul(multiplySenders[0].outputAdditiveShare).
Add(rX.Mul(multiplySenders[1].outputAdditiveShare))
gamma2 := alice.publicKey.Mul(multiplySenders[0].outputAdditiveShare)
other = alice.curve.ScalarBaseMult(multiplySenders[1].outputAdditiveShare.Neg())
gamma2 = gamma2.Add(other)
hashGamma2Bytes := sha3.Sum256(gamma2.ToAffineCompressed())
hashGamma2, err := alice.curve.Scalar.SetBytes(hashGamma2Bytes[:])
if err != nil {
return nil, errors.Wrap(err, "setting hashGamma2 scalar from bytes")
}
round3Output.EtaSig = hashGamma2.Add(sigA)
return round3Output, nil
}
// Round4Final this is Bob's last portion of the signature computation, and ultimately results in the complete signature
// corresponds to Protocol 1, Bob's steps 3) -- 10).
// Bob begins by _finishing_ the OT-based multiplication, using Alice's one and only message to him re: the mult.
// Bob then move's onto the remainder of Alice's message, which contains extraneous data used to finish the signature.
// Using this data, Bob completes the signature, which gets stored in `Bob.Sig`. Bob also verifies it.
func (bob *Bob) Round4Final(message []byte, round3Output *SignRound3Output) error {
if err := bob.multiplyReceivers[0].Round3Multiply(round3Output.MultiplyRound2Outputs[0]); err != nil {
return errors.Wrap(err, "error in round 3 multiply 0 within sign round 5")
}
if err := bob.multiplyReceivers[1].Round3Multiply(round3Output.MultiplyRound2Outputs[1]); err != nil {
return errors.Wrap(err, "error in round 3 multiply 1 within sign round 5")
}
rPrimeHashedBytes := sha3.Sum256(round3Output.RPrime.ToAffineCompressed())
rPrimeHashed, err := bob.curve.Scalar.SetBytes(rPrimeHashedBytes[:])
if err != nil {
return errors.Wrap(err, "setting rPrimeHashed scalar from bytes")
}
r := bob.dB.Mul(rPrimeHashed)
r = r.Add(round3Output.RPrime)
// To ensure that the correct public statement is used, we use the public statement that we have calculated
// instead of the open Alice sent us.
round3Output.RSchnorrProof.Statement = r
uniqueSessionId := [simplest.DigestSize]byte{}
copy(
uniqueSessionId[:],
bob.transcript.ExtractBytes([]byte("schnorr proof for R"), simplest.DigestSize),
)
if err = schnorr.Verify(round3Output.RSchnorrProof, bob.curve, bob.dB, uniqueSessionId[:]); err != nil {
return errors.Wrap(err, "bob's verification of alice's schnorr proof re: r failed")
}
zero := bob.curve.Scalar.Zero()
affineCompressedForm := r.ToAffineCompressed()
if len(affineCompressedForm) != 33 {
return errors.New("the compressed form must be exactly 33 bytes")
}
rY := affineCompressedForm[0] & 0x1 // this is bit(0) of Y coordinate
rX, err := bob.curve.Scalar.SetBytes(affineCompressedForm[1:])
if err != nil {
return errors.Wrap(err, "setting rX scalar from bytes")
}
bob.Signature = &curves.EcdsaSignature{
R: rX.Add(zero).
BigInt(),
// slight trick here; add it to 0 just to mod it by q (now it's mod p!)
V: int(rY),
}
gamma1 := r.Mul(bob.multiplyReceivers[0].outputAdditiveShare)
gamma1HashedBytes := sha3.Sum256(gamma1.ToAffineCompressed())
gamma1Hashed, err := bob.curve.Scalar.SetBytes(gamma1HashedBytes[:])
if err != nil {
return errors.Wrap(err, "setting gamma1Hashed scalar from bytes")
}
phi := round3Output.EtaPhi.Sub(gamma1Hashed)
theta := bob.multiplyReceivers[0].outputAdditiveShare.Sub(phi.Div(bob.kB))
if _, err = bob.hash.Write(message); err != nil {
return errors.Wrap(err, "writing message to hash in Bob sign round 5 final")
}
digestBytes := bob.hash.Sum(nil)
digest, err := bob.curve.Scalar.SetBytes(digestBytes)
if err != nil {
return errors.Wrap(err, "setting digest scalar from bytes")
}
capitalR, err := bob.curve.Scalar.SetBigInt(bob.Signature.R)
if err != nil {
return errors.Wrap(err, "setting capitalR scalar from big int")
}
sigB := digest.Mul(theta).Add(capitalR.Mul(bob.multiplyReceivers[1].outputAdditiveShare))
gamma2 := bob.curve.ScalarBaseMult(bob.multiplyReceivers[1].outputAdditiveShare)
other := bob.publicKey.Mul(theta.Neg())
gamma2 = gamma2.Add(other)
gamma2HashedBytes := sha3.Sum256(gamma2.ToAffineCompressed())
gamma2Hashed, err := bob.curve.Scalar.SetBytes(gamma2HashedBytes[:])
if err != nil {
return errors.Wrap(err, "setting gamma2Hashed scalar from bytes")
}
scalarS := sigB.Add(round3Output.EtaSig.Sub(gamma2Hashed))
bob.Signature.S = scalarS.BigInt()
if bob.Signature.S.Bit(255) == 1 {
bob.Signature.S = scalarS.Neg().BigInt()
bob.Signature.V ^= 1
}
// now verify the signature
unCompressedAffinePublicKey := bob.publicKey.ToAffineUncompressed()
if len(unCompressedAffinePublicKey) != 65 {
return errors.New("the uncompressed form must have exactly 65 bytes")
}
x := new(big.Int).SetBytes(unCompressedAffinePublicKey[1:33])
y := new(big.Int).SetBytes(unCompressedAffinePublicKey[33:])
ellipticCurve, err := bob.curve.ToEllipticCurve()
if err != nil {
return errors.Wrap(err, "invalid curve")
}
if !ecdsa.Verify(
&ecdsa.PublicKey{Curve: ellipticCurve, X: x, Y: y},
digestBytes,
bob.Signature.R,
bob.Signature.S,
) {
return fmt.Errorf("final signature failed to verify")
}
return nil
}

View File

@@ -0,0 +1,121 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package sign
import (
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
"github.com/sonr-io/sonr/crypto/ot/extension/kos"
"github.com/sonr-io/sonr/crypto/ot/ottest"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/dkg"
)
func TestSign(t *testing.T) {
curveInstances := []*curves.Curve{
curves.K256(),
curves.P256(),
}
for _, curve := range curveInstances {
hashKeySeed := [simplest.DigestSize]byte{}
_, err := rand.Read(hashKeySeed[:])
require.NoError(t, err)
baseOtSenderOutput, baseOtReceiverOutput, err := ottest.RunSimplestOT(
curve,
kos.Kappa,
hashKeySeed,
)
require.NoError(t, err)
secretKeyShareA := curve.Scalar.Random(rand.Reader)
secretKeyShareB := curve.Scalar.Random(rand.Reader)
require.NoError(t, err)
publicKey := curve.ScalarBaseMult(secretKeyShareA.Mul(secretKeyShareB))
alice := NewAlice(
curve,
sha3.New256(),
&dkg.AliceOutput{
SeedOtResult: baseOtReceiverOutput,
SecretKeyShare: secretKeyShareA,
PublicKey: publicKey,
},
)
bob := NewBob(
curve,
sha3.New256(),
&dkg.BobOutput{
SeedOtResult: baseOtSenderOutput,
SecretKeyShare: secretKeyShareB,
PublicKey: publicKey,
},
)
message := []byte("A message.")
seed, err := alice.Round1GenerateRandomSeed()
require.NoError(t, err)
round3Output, err := bob.Round2Initialize(seed)
require.NoError(t, err)
round4Output, err := alice.Round3Sign(message, round3Output)
require.NoError(t, err)
err = bob.Round4Final(message, round4Output)
require.NoError(t, err, "curve: %s", curve.Name)
}
}
func BenchmarkSign(b *testing.B) {
curve := curves.K256()
hashKeySeed := [simplest.DigestSize]byte{}
_, err := rand.Read(hashKeySeed[:])
require.NoError(b, err)
baseOtSenderOutput, baseOtReceiverOutput, err := ottest.RunSimplestOT(
curve,
kos.Kappa,
hashKeySeed,
)
require.NoError(b, err)
secretKeyShareA := curve.Scalar.Random(rand.Reader)
secretKeyShareB := curve.Scalar.Random(rand.Reader)
publicKey := curve.ScalarBaseMult(secretKeyShareA.Mul(secretKeyShareB))
alice := NewAlice(
curve,
sha3.New256(),
&dkg.AliceOutput{
SeedOtResult: baseOtReceiverOutput,
SecretKeyShare: secretKeyShareA,
PublicKey: publicKey,
},
)
bob := NewBob(
curve,
sha3.New256(),
&dkg.BobOutput{
SeedOtResult: baseOtSenderOutput,
SecretKeyShare: secretKeyShareB,
PublicKey: publicKey,
},
)
message := []byte("A message.")
for n := 0; n < b.N; n++ {
seed, err := alice.Round1GenerateRandomSeed()
require.NoError(b, err)
round3Output, err := bob.Round2Initialize(seed)
require.NoError(b, err)
round4Output, err := alice.Round3Sign(message, round3Output)
require.NoError(b, err)
err = bob.Round4Final(message, round4Output)
require.NoError(b, err)
}
}

View File

@@ -0,0 +1,128 @@
package dklsv1
import (
"bytes"
"encoding/gob"
"github.com/pkg/errors"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/crypto/tecdsa/dklsv1/sign"
)
func newSignProtocolMessage(payload []byte, round string, version uint) *protocol.Message {
return &protocol.Message{
Protocol: protocol.Dkls18Sign,
Version: version,
Payloads: map[string][]byte{payloadKey: payload},
Metadata: map[string]string{"round": round},
}
}
func encodeSignRound1Output(commitment [32]byte, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(&commitment); err != nil {
return nil, errors.WithStack(err)
}
return newSignProtocolMessage(buf.Bytes(), "1", version), nil
}
func decodeSignRound2Input(m *protocol.Message) ([32]byte, error) {
if m.Version != protocol.Version1 {
return [32]byte{}, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := [32]byte{}
if err := dec.Decode(&decoded); err != nil {
return [32]byte{}, errors.WithStack(err)
}
return decoded, nil
}
func encodeSignRound2Output(
output *sign.SignRound2Output,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(output); err != nil {
return nil, errors.WithStack(err)
}
return newSignProtocolMessage(buf.Bytes(), "2", version), nil
}
func decodeSignRound3Input(m *protocol.Message) (*sign.SignRound2Output, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := &sign.SignRound2Output{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeSignRound3Output(
output *sign.SignRound3Output,
version uint,
) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(output); err != nil {
return nil, errors.WithStack(err)
}
return newSignProtocolMessage(buf.Bytes(), "3", version), nil
}
func decodeSignRound4Input(m *protocol.Message) (*sign.SignRound3Output, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := &sign.SignRound3Output{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}
func encodeSignature(signature *curves.EcdsaSignature, version uint) (*protocol.Message, error) {
if version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
if err := enc.Encode(signature); err != nil {
return nil, errors.WithStack(err)
}
return newSignProtocolMessage(buf.Bytes(), "signature", version), nil
}
// DecodeSignature serializes the signature.
func DecodeSignature(m *protocol.Message) (*curves.EcdsaSignature, error) {
if m.Version != protocol.Version1 {
return nil, errors.New("only version 1 is supported")
}
buf := bytes.NewBuffer(m.Payloads[payloadKey])
dec := gob.NewDecoder(buf)
decoded := &curves.EcdsaSignature{}
if err := dec.Decode(decoded); err != nil {
return nil, errors.WithStack(err)
}
return decoded, nil
}