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 all 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 @@ -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
Loading