Skip to content

Commit

Permalink
feat(eddsa): Added EDDSA crypto module
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hanish520 committed Feb 25, 2024
1 parent 3176a69 commit 0a66445
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 106 deletions.
1 change: 1 addition & 0 deletions .vscode/dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ coverpkg
coverprofile
Debugf
durationpb
eddsa
emptypb
Erevik
Fangyu
Expand Down
3 changes: 3 additions & 0 deletions crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)) })
}
Expand Down
92 changes: 10 additions & 82 deletions crypto/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand All @@ -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)
179 changes: 179 additions & 0 deletions crypto/eddsa/eddsa.go
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 0a66445

Please sign in to comment.