Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eddsa): Added EDDSA crypto module #112

Merged
merged 15 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
meling marked this conversation as resolved.
Show resolved Hide resolved

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
Loading