Skip to content

Commit

Permalink
Merge pull request #112 from relab/eddsa
Browse files Browse the repository at this point in the history
feat(eddsa): Added EDDSA crypto module
  • Loading branch information
meling authored Mar 10, 2024
2 parents d23946a + 8928cbc commit 301532c
Show file tree
Hide file tree
Showing 14 changed files with 813 additions and 354 deletions.
1 change: 1 addition & 0 deletions .vscode/dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ coverprofile
cpuprofile
Debugf
durationpb
eddsa
emptypb
Erevik
eventloop
Expand Down
30 changes: 30 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 @@ -89,6 +90,33 @@ func TestCreateTimeoutCert(t *testing.T) {
runAll(t, run)
}

func TestCreateQCWithOneSig(t *testing.T) {
run := func(t *testing.T, setup setupFunc) {
ctrl := gomock.NewController(t)
td := setup(t, ctrl, 4)
pcs := testutil.CreatePCs(t, td.block, td.signers)
_, err := td.signers[0].CreateQuorumCert(td.block, pcs[:1])
if err == nil {
t.Fatal("Expected error when creating QC with only one signature")
}
}
runAll(t, run)
}

func TestCreateQCWithOverlappingSigs(t *testing.T) {
run := func(t *testing.T, setup setupFunc) {
ctrl := gomock.NewController(t)
td := setup(t, ctrl, 4)
pcs := testutil.CreatePCs(t, td.block, td.signers)
pcs = append(pcs, pcs[0])
_, err := td.signers[0].CreateQuorumCert(td.block, pcs)
if err == nil {
t.Fatal("Expected error when creating QC with overlapping signatures")
}
}
runAll(t, run)
}

func TestVerifyGenesisQC(t *testing.T) {
run := func(t *testing.T, setup setupFunc) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -167,6 +195,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
124 changes: 24 additions & 100 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 implements the spec-k256 curve signature.
package ecdsa

import (
Expand All @@ -7,7 +7,6 @@ import (
"crypto/sha256"
"fmt"
"math/big"
"slices"

"github.com/relab/hotstuff"
"github.com/relab/hotstuff/crypto"
Expand All @@ -22,18 +21,24 @@ func init() {
const (
// PrivateKeyFileType is the PEM type for a private key.
PrivateKeyFileType = "ECDSA PRIVATE KEY"

// PublicKeyFileType is the PEM type for a public key.
PublicKeyFileType = "ECDSA PUBLIC KEY"
)

// Signature is an ECDSA signature
var (
_ hotstuff.QuorumSignature = (*crypto.Multi[*Signature])(nil)
_ hotstuff.IDSet = (*crypto.Multi[*Signature])(nil)
_ crypto.Signature = (*Signature)(nil)
)

// Signature is an ECDSA signature.
type Signature struct {
r, s *big.Int
signer hotstuff.ID
}

// RestoreSignature restores an existing signature. It should not be used to create new signatures, use Sign instead.
// RestoreSignature restores an existing signature.
// It should not be used to create new signatures, use Sign instead.
func RestoreSignature(r, s *big.Int, signer hotstuff.ID) *Signature {
return &Signature{r, s, signer}
}
Expand All @@ -43,97 +48,24 @@ func (sig Signature) Signer() hotstuff.ID {
return sig.signer
}

// R returns the r value of the signature
// R returns the r value of the signature.
func (sig Signature) R() *big.Int {
return sig.r
}

// S returns the s value of the signature
// S returns the s value of the signature.
func (sig Signature) S() *big.Int {
return sig.s
}

// ToBytes returns a raw byte string representation of the signature
// ToBytes returns a raw byte string representation of the signature.
func (sig Signature) ToBytes() []byte {
var b []byte
b = append(b, sig.r.Bytes()...)
b = append(b, sig.s.Bytes()...)
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 All @@ -145,10 +77,6 @@ func New() modules.CryptoBase {
return &ecdsaBase{}
}

func (ec *ecdsaBase) getPrivateKey() *ecdsa.PrivateKey {
return ec.opts.PrivateKey().(*ecdsa.PrivateKey)
}

// InitModule gives the module a reference to the Core object.
// It also allows the module to set module options using the OptionsBuilder.
func (ec *ecdsaBase) InitModule(mods *modules.Core) {
Expand All @@ -159,14 +87,18 @@ func (ec *ecdsaBase) InitModule(mods *modules.Core) {
)
}

func (ec *ecdsaBase) privateKey() *ecdsa.PrivateKey {
return ec.opts.PrivateKey().(*ecdsa.PrivateKey)
}

// Sign creates a cryptographic signature of the given message.
func (ec *ecdsaBase) Sign(message []byte) (signature hotstuff.QuorumSignature, err error) {
hash := sha256.Sum256(message)
r, s, err := ecdsa.Sign(rand.Reader, ec.getPrivateKey(), hash[:])
r, s, err := ecdsa.Sign(rand.Reader, ec.privateKey(), hash[:])
if err != nil {
return nil, fmt.Errorf("ecdsa: sign failed: %w", err)
}
return MultiSignature{ec.opts.ID(): &Signature{
return crypto.Multi[*Signature]{ec.opts.ID(): &Signature{
r: r,
s: s,
signer: ec.opts.ID(),
Expand All @@ -179,12 +111,11 @@ func (ec *ecdsaBase) Combine(signatures ...hotstuff.QuorumSignature) (hotstuff.Q
return nil, crypto.ErrCombineMultiple
}

ts := make(MultiSignature)

ts := make(crypto.Multi[*Signature])
for _, sig1 := range signatures {
if sig2, ok := sig1.(MultiSignature); ok {
if sig2, ok := sig1.(crypto.Multi[*Signature]); ok {
for id, s := range sig2 {
if _, ok := ts[id]; ok {
if _, duplicate := ts[id]; duplicate {
return nil, crypto.ErrCombineOverlap
}
ts[id] = s
Expand All @@ -193,48 +124,42 @@ func (ec *ecdsaBase) Combine(signatures ...hotstuff.QuorumSignature) (hotstuff.Q
ec.logger.Panicf("cannot combine signature of incompatible type %T (expected %T)", sig1, sig2)
}
}

return ts, nil
}

// 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.Multi[*Signature])
if !ok {
ec.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)
hash := sha256.Sum256(message)

for _, sig := range s {
go func(sig *Signature, hash hotstuff.Hash) {
results <- ec.verifySingle(sig, hash)
}(sig, hash)
}

valid := true
for range s {
if !<-results {
valid = false
}
}

return valid
}

// 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.Multi[*Signature])
if !ok {
ec.logger.Panicf("cannot verify signature of incompatible type %T (expected %T)", signature, s)
}

n := signature.Participants().Len()
if n == 0 {
return false
Expand All @@ -253,7 +178,6 @@ func (ec *ecdsaBase) BatchVerify(signature hotstuff.QuorumSignature, batch map[h
results <- ec.verifySingle(sig, hash)
}(sig, hash)
}

valid := true
for range s {
if !<-results {
Expand Down
Loading

0 comments on commit 301532c

Please sign in to comment.