From 0a66445c834882fa636540cfb7c5bf1cb89e5a48 Mon Sep 17 00:00:00 2001 From: hanish gogada Date: Sun, 25 Feb 2024 01:20:32 +0100 Subject: [PATCH] feat(eddsa): Added EDDSA crypto module Added EDDSA(25519 curve) crypto module to the framework. Current design for eddsa module is messy, this PR is a starting point to take this activity. Need to discuss with others. --- .vscode/dict.txt | 1 + crypto/crypto_test.go | 3 + crypto/ecdsa/ecdsa.go | 92 ++-------- crypto/eddsa/eddsa.go | 179 +++++++++++++++++++ crypto/keygen/keygen.go | 40 ++++- crypto/multi_signature.go | 94 ++++++++++ internal/orchestration/orchestration_test.go | 3 + internal/orchestration/worker.go | 1 + internal/proto/hotstuffpb/convert.go | 53 ++++-- internal/proto/hotstuffpb/hotstuff.proto | 19 +- internal/testutil/testutil.go | 10 ++ 11 files changed, 389 insertions(+), 106 deletions(-) create mode 100644 crypto/eddsa/eddsa.go create mode 100644 crypto/multi_signature.go diff --git a/.vscode/dict.txt b/.vscode/dict.txt index e8ec8629..64194972 100644 --- a/.vscode/dict.txt +++ b/.vscode/dict.txt @@ -14,6 +14,7 @@ coverpkg coverprofile Debugf durationpb +eddsa emptypb Erevik Fangyu diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 56814a4f..af216e79 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -10,6 +10,7 @@ import ( "github.com/relab/hotstuff/crypto" "github.com/relab/hotstuff/crypto/bls12" "github.com/relab/hotstuff/crypto/ecdsa" + "github.com/relab/hotstuff/crypto/eddsa" "github.com/relab/hotstuff/internal/testutil" ) @@ -167,6 +168,8 @@ func runAll(t *testing.T, run func(*testing.T, setupFunc)) { t.Helper() t.Run("Ecdsa", func(t *testing.T) { run(t, setup(NewBase(ecdsa.New), testutil.GenerateECDSAKey)) }) t.Run("Cache+Ecdsa", func(t *testing.T) { run(t, setup(NewCache(ecdsa.New), testutil.GenerateECDSAKey)) }) + t.Run("Eddsa", func(t *testing.T) { run(t, setup(NewBase(eddsa.New), testutil.GenerateEDDSAKey)) }) + t.Run("Cache+Eddsa", func(t *testing.T) { run(t, setup(NewCache(eddsa.New), testutil.GenerateEDDSAKey)) }) t.Run("BLS12-381", func(t *testing.T) { run(t, setup(NewBase(bls12.New), testutil.GenerateBLS12Key)) }) t.Run("Cache+BLS12-381", func(t *testing.T) { run(t, setup(NewCache(bls12.New), testutil.GenerateBLS12Key)) }) } diff --git a/crypto/ecdsa/ecdsa.go b/crypto/ecdsa/ecdsa.go index a67facc3..ff91b788 100644 --- a/crypto/ecdsa/ecdsa.go +++ b/crypto/ecdsa/ecdsa.go @@ -1,4 +1,4 @@ -// Package ecdsa provides a crypto implementation for HotStuff using Go's 'crypto/ecdsa' package. +// Package ecdsa supports spec-k256 curve signature package ecdsa import ( @@ -12,7 +12,6 @@ import ( "github.com/relab/hotstuff/crypto" "github.com/relab/hotstuff/logging" "github.com/relab/hotstuff/modules" - "golang.org/x/exp/slices" ) func init() { @@ -61,79 +60,6 @@ func (sig Signature) ToBytes() []byte { return b } -// MultiSignature is a set of (partial) signatures. -type MultiSignature map[hotstuff.ID]*Signature - -// RestoreMultiSignature should only be used to restore an existing threshold signature from a set of signatures. -func RestoreMultiSignature(signatures []*Signature) MultiSignature { - sig := make(MultiSignature, len(signatures)) - for _, s := range signatures { - sig[s.signer] = s - } - return sig -} - -// ToBytes returns the object as bytes. -func (sig MultiSignature) ToBytes() []byte { - var b []byte - // sort by ID to make it deterministic - order := make([]hotstuff.ID, 0, len(sig)) - for _, signature := range sig { - order = append(order, signature.signer) - } - slices.Sort(order) - for _, id := range order { - b = append(b, sig[id].ToBytes()...) - } - return b -} - -// Participants returns the IDs of replicas who participated in the threshold signature. -func (sig MultiSignature) Participants() hotstuff.IDSet { - return sig -} - -// Add adds an ID to the set. -func (sig MultiSignature) Add(_ hotstuff.ID) { - panic("not implemented") -} - -// Contains returns true if the set contains the ID. -func (sig MultiSignature) Contains(id hotstuff.ID) bool { - _, ok := sig[id] - return ok -} - -// ForEach calls f for each ID in the set. -func (sig MultiSignature) ForEach(f func(hotstuff.ID)) { - for id := range sig { - f(id) - } -} - -// RangeWhile calls f for each ID in the set until f returns false. -func (sig MultiSignature) RangeWhile(f func(hotstuff.ID) bool) { - for id := range sig { - if !f(id) { - break - } - } -} - -// Len returns the number of entries in the set. -func (sig MultiSignature) Len() int { - return len(sig) -} - -func (sig MultiSignature) String() string { - return hotstuff.IDSetToString(sig) -} - -var ( - _ hotstuff.QuorumSignature = (*MultiSignature)(nil) - _ hotstuff.IDSet = (*MultiSignature)(nil) -) - type ecdsaBase struct { configuration modules.Configuration logger logging.Logger @@ -166,7 +92,7 @@ func (ec *ecdsaBase) Sign(message []byte) (signature hotstuff.QuorumSignature, e if err != nil { return nil, fmt.Errorf("ecdsa: sign failed: %w", err) } - return MultiSignature{ec.opts.ID(): &Signature{ + return crypto.MultiSignature{ec.opts.ID(): &Signature{ r: r, s: s, signer: ec.opts.ID(), @@ -179,10 +105,10 @@ func (ec *ecdsaBase) Combine(signatures ...hotstuff.QuorumSignature) (hotstuff.Q return nil, crypto.ErrCombineMultiple } - ts := make(MultiSignature) + ts := make(crypto.MultiSignature) for _, sig1 := range signatures { - if sig2, ok := sig1.(MultiSignature); ok { + if sig2, ok := sig1.(crypto.MultiSignature); ok { for id, s := range sig2 { if _, ok := ts[id]; ok { return nil, crypto.ErrCombineOverlap @@ -199,7 +125,7 @@ func (ec *ecdsaBase) Combine(signatures ...hotstuff.QuorumSignature) (hotstuff.Q // Verify verifies the given quorum signature against the message. func (ec *ecdsaBase) Verify(signature hotstuff.QuorumSignature, message []byte) bool { - s, ok := signature.(MultiSignature) + s, ok := signature.(crypto.MultiSignature) if !ok { ec.logger.Panicf("cannot verify signature of incompatible type %T (expected %T)", signature, s) } @@ -215,7 +141,7 @@ func (ec *ecdsaBase) Verify(signature hotstuff.QuorumSignature, message []byte) for _, sig := range s { go func(sig *Signature, hash hotstuff.Hash) { results <- ec.verifySingle(sig, hash) - }(sig, hash) + }(sig.(*Signature), hash) } valid := true @@ -230,7 +156,7 @@ func (ec *ecdsaBase) Verify(signature hotstuff.QuorumSignature, message []byte) // BatchVerify verifies the given quorum signature against the batch of messages. func (ec *ecdsaBase) BatchVerify(signature hotstuff.QuorumSignature, batch map[hotstuff.ID][]byte) bool { - s, ok := signature.(MultiSignature) + s, ok := signature.(crypto.MultiSignature) if !ok { ec.logger.Panicf("cannot verify signature of incompatible type %T (expected %T)", signature, s) } @@ -251,7 +177,7 @@ func (ec *ecdsaBase) BatchVerify(signature hotstuff.QuorumSignature, batch map[h set[hash] = struct{}{} go func(sig *Signature, hash hotstuff.Hash) { results <- ec.verifySingle(sig, hash) - }(sig, hash) + }(sig.(*Signature), hash) } valid := true @@ -274,3 +200,5 @@ func (ec *ecdsaBase) verifySingle(sig *Signature, hash hotstuff.Hash) bool { pk := replica.PublicKey().(*ecdsa.PublicKey) return ecdsa.Verify(pk, hash[:], sig.R(), sig.S()) } + +var _ crypto.Signature = (*Signature)(nil) diff --git a/crypto/eddsa/eddsa.go b/crypto/eddsa/eddsa.go new file mode 100644 index 00000000..5c59fb3c --- /dev/null +++ b/crypto/eddsa/eddsa.go @@ -0,0 +1,179 @@ +// Package eddsa implements 25519 curve signature +package eddsa + +import ( + "crypto/ed25519" + "crypto/sha256" + + "github.com/relab/hotstuff" + "github.com/relab/hotstuff/crypto" + "github.com/relab/hotstuff/logging" + "github.com/relab/hotstuff/modules" +) + +func init() { + modules.RegisterModule("eddsa", New) +} + +const ( + // PrivateKeyFileType is the PEM type for a private key. + PrivateKeyFileType = "EDDSA PRIVATE KEY" + + // PublicKeyFileType is the PEM type for a public key. + PublicKeyFileType = "EDDSA PUBLIC KEY" +) + +// Signature is an ECDSA signature +type Signature struct { + signer hotstuff.ID + sign []byte +} + +// RestoreSignature restores an existing signature. It should not be used to create new signatures, use Sign instead. +func RestoreSignature(sign []byte, signer hotstuff.ID) *Signature { + return &Signature{signer, sign} +} + +// Signer returns the ID of the replica that generated the signature. +func (sig Signature) Signer() hotstuff.ID { + return sig.signer +} + +// ToBytes returns a raw byte string representation of the signature +func (sig Signature) ToBytes() []byte { + var b []byte + b = append(b, sig.sign...) + return b +} + +type eddsaBase struct { + configuration modules.Configuration + logger logging.Logger + opts *modules.Options +} + +// New returns a new instance of the ECDSA CryptoBase implementation. +func New() modules.CryptoBase { + return &eddsaBase{} +} + +// InitModule gives the module a reference to the Core object. +// It also allows the module to set module options using the OptionsBuilder. +func (ed *eddsaBase) InitModule(mods *modules.Core) { + mods.Get( + &ed.configuration, + &ed.logger, + &ed.opts, + ) +} + +func (ed *eddsaBase) privateKey() ed25519.PrivateKey { + return ed.opts.PrivateKey().(ed25519.PrivateKey) +} + +func (ed *eddsaBase) Sign(message []byte) (signature hotstuff.QuorumSignature, err error) { + sign := ed25519.Sign(ed.privateKey(), message) + eddsaSign := &Signature{signer: ed.opts.ID(), sign: sign} + return crypto.MultiSignature{ed.opts.ID(): eddsaSign}, nil +} + +func (ed *eddsaBase) Combine(signatures ...hotstuff.QuorumSignature) (hotstuff.QuorumSignature, error) { + if len(signatures) < 2 { + return nil, crypto.ErrCombineMultiple + } + + ts := make(crypto.MultiSignature) + + for _, sig1 := range signatures { + if sig2, ok := sig1.(crypto.MultiSignature); ok { + for id, s := range sig2 { + if _, ok := ts[id]; ok { + return nil, crypto.ErrCombineOverlap + } + ts[id] = s + } + } else { + ed.logger.Panicf("cannot combine signature of incompatible type %T (expected %T)", sig1, sig2) + } + } + return ts, nil +} + +func (ed *eddsaBase) Verify(signature hotstuff.QuorumSignature, message []byte) bool { + s, ok := signature.(crypto.MultiSignature) + if !ok { + ed.logger.Panicf("cannot verify signature of incompatible type %T (expected %T)", signature, s) + } + n := signature.Participants().Len() + if n == 0 { + return false + } + + results := make(chan bool, n) + + for _, sig := range s { + go func(sig *Signature, msg []byte) { + results <- ed.verifySingle(sig, msg) + }(sig.(*Signature), message) + } + + valid := true + for range s { + if !<-results { + valid = false + } + } + + return valid + +} +func (ed *eddsaBase) BatchVerify(signature hotstuff.QuorumSignature, batch map[hotstuff.ID][]byte) bool { + s, ok := signature.(crypto.MultiSignature) + if !ok { + ed.logger.Panicf("cannot verify signature of incompatible type %T (expected %T)", signature, s) + } + + n := signature.Participants().Len() + if n == 0 { + return false + } + + results := make(chan bool, n) + set := make(map[hotstuff.Hash]struct{}) + for id, sig := range s { + message, ok := batch[id] + if !ok { + return false + } + hash := sha256.Sum256(message) + set[hash] = struct{}{} + go func(sig *Signature, msg []byte) { + results <- ed.verifySingle(sig, msg) + }(sig.(*Signature), message) + } + + valid := true + for range s { + if !<-results { + valid = false + } + } + + // valid if all partial signatures are valid and there are no duplicate messages + return valid && len(set) == len(batch) +} +func (ed *eddsaBase) verifySingle(sig *Signature, message []byte) bool { + replica, ok := ed.configuration.Replica(sig.Signer()) + if !ok { + ed.logger.Warnf("ecdsaBase: got signature from replica whose ID (%d) was not in the config.", sig.Signer()) + return false + } + pk, ok := replica.PublicKey().([]byte) + if !ok { + ed.logger.Infof("ecdsaBase: got public key from replica that was not of type []byte.") + pk = replica.PublicKey().(ed25519.PublicKey) + } + return ed25519.Verify(pk, message, sig.sign) +} + +var _ crypto.Signature = (*Signature)(nil) diff --git a/crypto/keygen/keygen.go b/crypto/keygen/keygen.go index 8ae4480c..211337e7 100644 --- a/crypto/keygen/keygen.go +++ b/crypto/keygen/keygen.go @@ -4,6 +4,7 @@ package keygen import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/x509" @@ -18,6 +19,7 @@ import ( "github.com/relab/hotstuff" "github.com/relab/hotstuff/crypto/bls12" ecdsacrypto "github.com/relab/hotstuff/crypto/ecdsa" + "github.com/relab/hotstuff/crypto/eddsa" ) // GenerateECDSAPrivateKey returns a new ECDSA private key. @@ -29,6 +31,15 @@ func GenerateECDSAPrivateKey() (pk *ecdsa.PrivateKey, err error) { return pk, nil } +// GenerateED25519Key generates 25519 key +func GenerateED25519Key() (pub ed25519.PublicKey, pk ed25519.PrivateKey, err error) { + pub, pk, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + return pub, pk, nil +} + // GenerateRootCert generates a self-signed TLS certificate to act as a CA. func GenerateRootCert(privateKey *ecdsa.PrivateKey) (cert *x509.Certificate, err error) { sn, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) @@ -104,6 +115,10 @@ func PrivateKeyToPEM(key hotstuff.PrivateKey) ([]byte, error) { case *bls12.PrivateKey: marshalled = k.ToBytes() keyType = bls12.PrivateKeyFileType + case ed25519.PrivateKey: + marshalled = make([]byte, ed25519.PrivateKeySize) + copy(marshalled, k) + keyType = eddsa.PrivateKeyFileType } b := &pem.Block{ Type: keyType, @@ -150,6 +165,10 @@ func PublicKeyToPEM(key hotstuff.PublicKey) ([]byte, error) { case *bls12.PublicKey: marshalled = k.ToBytes() keyType = bls12.PublicKeyFileType + case ed25519.PublicKey: + marshalled = make([]byte, ed25519.PublicKeySize) + copy(marshalled, k) + keyType = eddsa.PublicKeyFileType } b := &pem.Block{ @@ -213,8 +232,11 @@ func ParsePrivateKey(buf []byte) (key hotstuff.PrivateKey, err error) { k := &bls12.PrivateKey{} k.FromBytes(b.Bytes) key = k + case eddsa.PrivateKeyFileType: + k := ed25519.NewKeyFromSeed(b.Bytes[:32]) + key = k default: - return nil, fmt.Errorf("file type did not match any known types") + return nil, fmt.Errorf("file type did not match any known types %v", b.Type) } if err != nil { return nil, fmt.Errorf("failed to parse key: %w", err) @@ -247,8 +269,12 @@ func ParsePublicKey(buf []byte) (key hotstuff.PublicKey, err error) { return nil, err } key = k + case eddsa.PublicKeyFileType: + k := make([]byte, ed25519.PublicKeySize) + copy(k, b.Bytes) + key = k default: - return nil, fmt.Errorf("file type did not match any known types") + return nil, fmt.Errorf("file type did not match any known types %v", b.Type) } if err != nil { return nil, fmt.Errorf("failed to parse key: %w", err) @@ -316,14 +342,22 @@ func GenerateKeyChain(id hotstuff.ID, validFor []string, crypto string, ca *x509 certPEM := CertToPEM(cert) var privateKey hotstuff.PrivateKey + var publicKey hotstuff.PublicKey switch crypto { case "ecdsa": privateKey = ecdsaKey + publicKey = privateKey.Public() case "bls12": privateKey, err = bls12.GeneratePrivateKey() if err != nil { return KeyChain{}, fmt.Errorf("failed to generate bls12-381 private key: %w", err) } + publicKey = privateKey.Public() + case "eddsa": + publicKey, privateKey, err = GenerateED25519Key() + if err != nil { + return KeyChain{}, fmt.Errorf("failed to generate ed25519 key: %w", err) + } default: return KeyChain{}, fmt.Errorf("unknown crypto implementation: %s", crypto) } @@ -333,7 +367,7 @@ func GenerateKeyChain(id hotstuff.ID, validFor []string, crypto string, ca *x509 return KeyChain{}, err } - publicKeyPEM, err := PublicKeyToPEM(privateKey.Public()) + publicKeyPEM, err := PublicKeyToPEM(publicKey) if err != nil { return KeyChain{}, err } diff --git a/crypto/multi_signature.go b/crypto/multi_signature.go new file mode 100644 index 00000000..c6c92069 --- /dev/null +++ b/crypto/multi_signature.go @@ -0,0 +1,94 @@ +package crypto + +import ( + "reflect" + + "github.com/relab/hotstuff" + "golang.org/x/exp/slices" +) + +// Signature is the individual component in MultiSignature +type Signature interface { + Signer() hotstuff.ID + ToBytes() []byte +} + +// MultiSignature is a set of (partial) signatures. +type MultiSignature map[hotstuff.ID]Signature + +// RestoreMultiSignature should only be used to restore an existing threshold signature from a set of signatures. +func RestoreMultiSignature(signatures []Signature) MultiSignature { + sig := make(MultiSignature, len(signatures)) + for _, s := range signatures { + sig[s.Signer()] = s + } + return sig +} + +// ToBytes returns the object as bytes. +func (sig MultiSignature) ToBytes() []byte { + var b []byte + // sort by ID to make it deterministic + order := make([]hotstuff.ID, 0, len(sig)) + for _, signature := range sig { + order = append(order, signature.Signer()) + } + slices.Sort(order) + for _, id := range order { + b = append(b, sig[id].ToBytes()...) + } + return b +} + +// Participants returns the IDs of replicas who participated in the threshold signature. +func (sig MultiSignature) Participants() hotstuff.IDSet { + return sig +} + +// Add adds an ID to the set. +func (sig MultiSignature) Add(_ hotstuff.ID) { + panic("not implemented") +} + +// Contains returns true if the set contains the ID. +func (sig MultiSignature) Contains(id hotstuff.ID) bool { + _, ok := sig[id] + return ok +} + +// ForEach calls f for each ID in the set. +func (sig MultiSignature) ForEach(f func(hotstuff.ID)) { + for id := range sig { + f(id) + } +} + +// RangeWhile calls f for each ID in the set until f returns false. +func (sig MultiSignature) RangeWhile(f func(hotstuff.ID) bool) { + for id := range sig { + if !f(id) { + break + } + } +} + +// Len returns the number of entries in the set. +func (sig MultiSignature) Len() int { + return len(sig) +} + +func (sig MultiSignature) String() string { + return hotstuff.IDSetToString(sig) +} + +func (sig MultiSignature) Type() reflect.Type { + for _, s := range sig { + return reflect.TypeOf(s) + } + return nil +} + +var ( + _ hotstuff.QuorumSignature = (*MultiSignature)(nil) + _ hotstuff.IDSet = (*MultiSignature)(nil) +) diff --git a/internal/orchestration/orchestration_test.go b/internal/orchestration/orchestration_test.go index 14ec5cae..2e17eb07 100644 --- a/internal/orchestration/orchestration_test.go +++ b/internal/orchestration/orchestration_test.go @@ -78,6 +78,9 @@ func TestOrchestration(t *testing.T) { t.Run("Fast-HotStuff+BLS12", func(t *testing.T) { run("fasthotstuff", "bls12", nil, "") }) t.Run("Simple-HotStuff+ECDSA", func(t *testing.T) { run("simplehotstuff", "ecdsa", nil, "") }) t.Run("Simple-HotStuff+BLS12", func(t *testing.T) { run("simplehotstuff", "bls12", nil, "") }) + t.Run("ChainedHotStuff+EDDSA", func(t *testing.T) { run("chainedhotstuff", "eddsa", nil, "") }) + t.Run("Fast-HotStuff+EDDSA", func(t *testing.T) { run("fasthotstuff", "eddsa", nil, "") }) + t.Run("Simple-HotStuff+EDDSA", func(t *testing.T) { run("simplehotstuff", "eddsa", nil, "") }) // handel mods := []string{"handel"} diff --git a/internal/orchestration/worker.go b/internal/orchestration/worker.go index 04183561..290d66d6 100644 --- a/internal/orchestration/worker.go +++ b/internal/orchestration/worker.go @@ -39,6 +39,7 @@ import ( _ "github.com/relab/hotstuff/consensus/simplehotstuff" _ "github.com/relab/hotstuff/crypto/bls12" _ "github.com/relab/hotstuff/crypto/ecdsa" + _ "github.com/relab/hotstuff/crypto/eddsa" _ "github.com/relab/hotstuff/handel" _ "github.com/relab/hotstuff/leaderrotation" ) diff --git a/internal/proto/hotstuffpb/convert.go b/internal/proto/hotstuffpb/convert.go index 3f6124c7..3256dd79 100644 --- a/internal/proto/hotstuffpb/convert.go +++ b/internal/proto/hotstuffpb/convert.go @@ -8,28 +8,42 @@ import ( "github.com/relab/hotstuff/crypto" "github.com/relab/hotstuff/crypto/bls12" "github.com/relab/hotstuff/crypto/ecdsa" + "github.com/relab/hotstuff/crypto/eddsa" ) // QuorumSignatureToProto converts a threshold signature to a protocol buffers message. func QuorumSignatureToProto(sig hotstuff.QuorumSignature) *QuorumSignature { signature := &QuorumSignature{} - switch s := sig.(type) { - case ecdsa.MultiSignature: - sigs := make([]*ECDSASignature, 0, len(s)) - for _, p := range s { - sigs = append(sigs, &ECDSASignature{ - Signer: uint32(p.Signer()), - R: p.R().Bytes(), - S: p.S().Bytes(), - }) + switch ms := sig.(type) { + case crypto.MultiSignature: + ECDSASigs := make([]*ECDSASignature, 0, sig.Participants().Len()) + EDDSASigs := make([]*EDDSASignature, 0, sig.Participants().Len()) + for _, p := range ms { + switch s := p.(type) { + case *ecdsa.Signature: + ECDSASigs = append(ECDSASigs, &ECDSASignature{ + Signer: uint32(s.Signer()), + R: s.R().Bytes(), + S: s.S().Bytes(), + }) + case *eddsa.Signature: + EDDSASigs = append(EDDSASigs, &EDDSASignature{Signer: uint32(s.Signer()), Sig: s.ToBytes()}) + } } - signature.Sig = &QuorumSignature_ECDSASigs{ECDSASigs: &ECDSAMultiSignature{ - Sigs: sigs, - }} + if len(ECDSASigs) > 0 { + signature.Sig = &QuorumSignature_ECDSASigs{ECDSASigs: &ECDSAMultiSignature{ + Sigs: ECDSASigs, + }} + } else { + signature.Sig = &QuorumSignature_EDDSASigs{EDDSASigs: &EDDSAMultiSignature{ + Sigs: EDDSASigs, + }} + } + case *bls12.AggregateSignature: signature.Sig = &QuorumSignature_BLS12Sig{BLS12Sig: &BLS12AggregateSignature{ - Sig: s.ToBytes(), - Participants: s.Bitfield().Bytes(), + Sig: ms.ToBytes(), + Participants: ms.Bitfield().Bytes(), }} } return signature @@ -38,7 +52,7 @@ func QuorumSignatureToProto(sig hotstuff.QuorumSignature) *QuorumSignature { // QuorumSignatureFromProto converts a protocol buffers message to a threshold signature. func QuorumSignatureFromProto(sig *QuorumSignature) hotstuff.QuorumSignature { if signature := sig.GetECDSASigs(); signature != nil { - sigs := make([]*ecdsa.Signature, len(signature.GetSigs())) + sigs := make([]crypto.Signature, len(signature.GetSigs())) for i, sig := range signature.GetSigs() { r := new(big.Int) r.SetBytes(sig.GetR()) @@ -46,7 +60,14 @@ func QuorumSignatureFromProto(sig *QuorumSignature) hotstuff.QuorumSignature { s.SetBytes(sig.GetS()) sigs[i] = ecdsa.RestoreSignature(r, s, hotstuff.ID(sig.GetSigner())) } - return ecdsa.RestoreMultiSignature(sigs) + return crypto.RestoreMultiSignature(sigs) + } + if signature := sig.GetEDDSASigs(); signature != nil { + sigs := make([]crypto.Signature, len(signature.GetSigs())) + for i, sig := range signature.GetSigs() { + sigs[i] = eddsa.RestoreSignature(sig.Sig, hotstuff.ID(sig.GetSigner())) + } + return crypto.RestoreMultiSignature(sigs) } if signature := sig.GetBLS12Sig(); signature != nil { aggSig, err := bls12.RestoreAggregateSignature(signature.GetSig(), crypto.BitfieldFromBytes(signature.GetParticipants())) diff --git a/internal/proto/hotstuffpb/hotstuff.proto b/internal/proto/hotstuffpb/hotstuff.proto index 4d605c0b..27cc8739 100644 --- a/internal/proto/hotstuffpb/hotstuff.proto +++ b/internal/proto/hotstuffpb/hotstuff.proto @@ -30,7 +30,7 @@ service Hotstuff { message Proposal { Block Block = 1; - optional AggQC AggQC = 2; + AggQC AggQC = 2; } message BlockHash { bytes Hash = 1; } @@ -51,10 +51,16 @@ message ECDSASignature { message BLS12Signature { bytes Sig = 1; } +message EDDSASignature { + uint32 Signer = 1; + bytes Sig = 2; + } + message Signature { oneof Sig { ECDSASignature ECDSASig = 1; BLS12Signature BLS12Sig = 2; + EDDSASignature EDDSASig = 3; } } @@ -65,6 +71,8 @@ message PartialCert { message ECDSAMultiSignature { repeated ECDSASignature Sigs = 1; } +message EDDSAMultiSignature {repeated EDDSASignature Sigs = 1;} + message BLS12AggregateSignature { bytes Sig = 1; bytes participants = 2; @@ -74,6 +82,7 @@ message QuorumSignature { oneof Sig { ECDSAMultiSignature ECDSASigs = 1; BLS12AggregateSignature BLS12Sig = 2; + EDDSAMultiSignature EDDSASigs =3; } } @@ -92,13 +101,13 @@ message TimeoutMsg { uint64 View = 1; SyncInfo SyncInfo = 2; QuorumSignature ViewSig = 3; - optional QuorumSignature MsgSig = 4; + QuorumSignature MsgSig = 4; } message SyncInfo { - optional QuorumCert QC = 1; - optional TimeoutCert TC = 2; - optional AggQC AggQC = 3; + QuorumCert QC = 1; + TimeoutCert TC = 2; + AggQC AggQC = 3; } message AggQC { diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index ed0ecaf8..c42ea030 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -250,6 +250,16 @@ func GenerateECDSAKey(t *testing.T) hotstuff.PrivateKey { return key } +// GenerateEDDSAKey generates an ECDSA private key for use in tests. +func GenerateEDDSAKey(t *testing.T) hotstuff.PrivateKey { + t.Helper() + _, key, err := keygen.GenerateED25519Key() + if err != nil { + t.Fatalf("Failed to generate private key: %v", err) + } + return key +} + // GenerateBLS12Key generates a BLS12-381 private key for use in tests. func GenerateBLS12Key(t *testing.T) hotstuff.PrivateKey { t.Helper()