From b4af4a0fb70382bdb13889d792f53cde31c53e95 Mon Sep 17 00:00:00 2001 From: Kenta Iwasaki Date: Fri, 22 Feb 2019 15:46:06 +0800 Subject: [PATCH 1/3] identity, signature, eddsa: separate out sign/verify interfaces from keypairs into a `signature` package skademlia: allow for a signature scheme to optionally be set --- identity/ed25519/mod.go | 20 ----------- identity/ed25519/mod_test.go | 70 ------------------------------------ identity/keypair.go | 3 -- signature/eddsa/mod.go | 43 ++++++++++++++++++++++ signature/scheme.go | 6 ++++ skademlia/keys.go | 20 ----------- skademlia/keys_test.go | 24 ------------- skademlia/mod.go | 25 ++++++------- skademlia/mod_test.go | 8 ++++- 9 files changed, 69 insertions(+), 150 deletions(-) create mode 100644 signature/eddsa/mod.go create mode 100644 signature/scheme.go diff --git a/identity/ed25519/mod.go b/identity/ed25519/mod.go index d5f8d368..d6fcd49f 100644 --- a/identity/ed25519/mod.go +++ b/identity/ed25519/mod.go @@ -50,26 +50,6 @@ func (p *Keypair) PublicKey() []byte { return p.publicKey } -func (p *Keypair) Sign(buf []byte) ([]byte, error) { - if len(p.privateKey) != edwards25519.PrivateKeySize { - return nil, errors.Errorf("edwards25519: private key expected to be %d bytes, but is %d bytes", edwards25519.PrivateKeySize, len(p.privateKey)) - } - - return edwards25519.Sign(p.privateKey, buf), nil -} - -func (p *Keypair) Verify(publicKeyBuf []byte, buf []byte, signature []byte) error { - if len(publicKeyBuf) != edwards25519.PublicKeySize { - return errors.Errorf("edwards25519: public key expected to be %d bytes, but is %d bytes", edwards25519.PublicKeySize, len(publicKeyBuf)) - } - - if edwards25519.Verify(publicKeyBuf, buf, signature) { - return nil - } else { - return errors.New("unable to verify signature") - } -} - func (p *Keypair) String() string { return fmt.Sprintf("Ed25519(public: %s, private: %s)", hex.EncodeToString(p.PublicKey()), hex.EncodeToString(p.PrivateKey())) } diff --git a/identity/ed25519/mod_test.go b/identity/ed25519/mod_test.go index f365f89d..1a0c0046 100644 --- a/identity/ed25519/mod_test.go +++ b/identity/ed25519/mod_test.go @@ -1,54 +1,11 @@ package ed25519_test import ( - "crypto/rand" "github.com/perlin-network/noise/identity/ed25519" "github.com/stretchr/testify/assert" "testing" ) -func BenchmarkSign(b *testing.B) { - p := ed25519.RandomKeys() - - message := make([]byte, 32) - if _, err := rand.Read(message); err != nil { - panic(err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - sig, err := p.Sign(message) - if err != nil || len(sig) == 0 { - panic("signing failed") - } - } -} - -func BenchmarkVerify(b *testing.B) { - p := ed25519.RandomKeys() - - message := make([]byte, 32) - if _, err := rand.Read(message); err != nil { - panic(err) - } - - publicKey := p.PublicKey() - - b.ResetTimer() - - sig, err := p.Sign(message) - if err != nil { - panic(err) - } - - for i := 0; i < b.N; i++ { - if err := p.Verify(publicKey, message, sig); err != nil { - panic("verification failed") - } - } -} - func TestEd25519(t *testing.T) { t.Parallel() p := ed25519.RandomKeys() @@ -58,35 +15,8 @@ func TestEd25519(t *testing.T) { assert.True(t, len(p.PublicKey()) > 0) assert.True(t, len(p.String()) > 0) - message := []byte("test message") - // sign with a bad key should have yield signature with 0 length - - // length of signature should not be 0 - sig, err := p.Sign(message) - assert.Nil(t, err) - assert.True(t, len(sig) > 0) - - // correct message should pass verify check - err = p.Verify(publicKey, message, sig) - assert.Nil(t, err) - - // wrong public key should fail verify check - err = p.Verify([]byte("bad key"), message, sig) - assert.NotNil(t, err) - - // wrong message should fail verify check - wrongMessage := []byte("wrong message") - err = p.Verify(publicKey, wrongMessage, sig) - assert.NotNil(t, err) - // try reloading the private key, should make the same object mgr := ed25519.LoadKeys(privateKey) assert.NotNil(t, mgr) assert.EqualValues(t, mgr.PublicKey(), publicKey) - - // make sure signing is different - badMgr := ed25519.LoadKeys(ed25519.RandomKeys().PrivateKey()) - badSig, err := badMgr.Sign(message) - assert.Nil(t, err) - assert.NotEqual(t, sig, badSig) } diff --git a/identity/keypair.go b/identity/keypair.go index b66f341e..3122d1c7 100644 --- a/identity/keypair.go +++ b/identity/keypair.go @@ -8,7 +8,4 @@ type Keypair interface { ID() []byte PublicKey() []byte PrivateKey() []byte - - Sign(buf []byte) ([]byte, error) - Verify(publicKeyBuf []byte, buf []byte, signature []byte) error } diff --git a/signature/eddsa/mod.go b/signature/eddsa/mod.go new file mode 100644 index 00000000..112f853b --- /dev/null +++ b/signature/eddsa/mod.go @@ -0,0 +1,43 @@ +package eddsa + +import ( + "github.com/perlin-network/noise/internal/edwards25519" + "github.com/perlin-network/noise/signature" + "github.com/pkg/errors" +) + +var _ signature.Scheme = (*policy)(nil) + +type policy struct{} + +func (p policy) Sign(privateKey, messageBuf []byte) ([]byte, error) { + return Sign(privateKey, messageBuf) +} + +func (p policy) Verify(publicKeyBuf, messageBuf, signatureBuf []byte) error { + return Verify(publicKeyBuf, messageBuf, signatureBuf) +} + +func New() *policy { + return new(policy) +} + +func Sign(privateKeyBuf, messageBuf []byte) ([]byte, error) { + if len(privateKeyBuf) != edwards25519.PrivateKeySize { + return nil, errors.Errorf("edwards25519: private key expected to be %d bytes, but is %d bytes", edwards25519.PrivateKeySize, len(privateKeyBuf)) + } + + return edwards25519.Sign(privateKeyBuf, messageBuf), nil +} + +func Verify(publicKeyBuf, messageBuf, signature []byte) error { + if len(publicKeyBuf) != edwards25519.PublicKeySize { + return errors.Errorf("edwards25519: public key expected to be %d bytes, but is %d bytes", edwards25519.PublicKeySize, len(publicKeyBuf)) + } + + if edwards25519.Verify(publicKeyBuf, messageBuf, signature) { + return nil + } else { + return errors.New("unable to verify signature") + } +} diff --git a/signature/scheme.go b/signature/scheme.go new file mode 100644 index 00000000..ae4723c2 --- /dev/null +++ b/signature/scheme.go @@ -0,0 +1,6 @@ +package signature + +type Scheme interface { + Sign(privateKey, messageBuf []byte) ([]byte, error) + Verify(publicKeyBuf, messageBuf, signatureBuf []byte) error +} diff --git a/skademlia/keys.go b/skademlia/keys.go index 20f59148..9f4dd648 100644 --- a/skademlia/keys.go +++ b/skademlia/keys.go @@ -112,26 +112,6 @@ func (p *Keypair) PublicKey() []byte { return p.publicKey } -func (p *Keypair) Sign(buf []byte) ([]byte, error) { - if len(p.privateKey) != edwards25519.PrivateKeySize { - return nil, errors.Errorf("ed25519: private key expected to be %d bytes, but is %d bytes", edwards25519.PrivateKeySize, len(p.privateKey)) - } - - return edwards25519.Sign(p.privateKey, buf), nil -} - -func (p *Keypair) Verify(publicKeyBuf []byte, buf []byte, signature []byte) error { - if len(publicKeyBuf) != edwards25519.PublicKeySize { - return errors.Errorf("ed25519: public key expected to be %d bytes, but is %d bytes", edwards25519.PublicKeySize, len(publicKeyBuf)) - } - - if edwards25519.Verify(publicKeyBuf, buf, signature) { - return nil - } else { - return errors.New("unable to verify signature") - } -} - func (p *Keypair) String() string { return fmt.Sprintf("S/Kademlia(public: %s, private: %s)", hex.EncodeToString(p.PublicKey()), hex.EncodeToString(p.PrivateKey())) } diff --git a/skademlia/keys_test.go b/skademlia/keys_test.go index bb224cf7..4beb428f 100644 --- a/skademlia/keys_test.go +++ b/skademlia/keys_test.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "fmt" "github.com/perlin-network/noise/identity/ed25519" - "github.com/perlin-network/noise/internal/edwards25519" "github.com/stretchr/testify/assert" "golang.org/x/crypto/blake2b" "testing" @@ -50,29 +49,6 @@ func TestNewSKademliaIdentityFromPrivateKey(t *testing.T) { } } -func TestSignAndVerify(t *testing.T) { - t.Parallel() - - data, err := randomBytes(1024) - assert.NoError(t, err) - - privateKeyHex := "1946e455ca6072bcdfd3182799c2ceb1557c2a56c5f810478ac0eb279ad4c93e8e8b6a97551342fd70ec03bea8bae5b05bc5dc0f54b2721dff76f06fab909263" - - privateKey, err := hex.DecodeString(privateKeyHex) - assert.NoError(t, err) - - keys, err := LoadKeys(privateKey, DefaultC1, DefaultC2) - assert.NoError(t, err) - assert.NotNil(t, keys) - - signature, err := keys.Sign([]byte(data)) - - assert.NoError(t, err) - assert.Len(t, signature, edwards25519.SignatureSize) - - assert.Nil(t, keys.Verify(keys.publicKey, data, signature)) -} - func TestGenerateKeyPairAndID(t *testing.T) { t.Parallel() diff --git a/skademlia/mod.go b/skademlia/mod.go index 826f60e9..e6eac943 100644 --- a/skademlia/mod.go +++ b/skademlia/mod.go @@ -5,6 +5,7 @@ import ( "github.com/perlin-network/noise/log" "github.com/perlin-network/noise/payload" "github.com/perlin-network/noise/protocol" + "github.com/perlin-network/noise/signature" "github.com/pkg/errors" "time" ) @@ -27,7 +28,7 @@ type block struct { opcodeLookupRequest noise.Opcode opcodeLookupResponse noise.Opcode - enforceSignatures bool + scheme signature.Scheme c1, c2 int @@ -35,12 +36,7 @@ type block struct { } func New() *block { - return &block{enforceSignatures: false, c1: DefaultC1, c2: DefaultC2, prefixDiffLen: DefaultPrefixDiffLen, prefixDiffMin: DefaultPrefixDiffMin} -} - -func (b *block) EnforceSignatures() *block { - b.enforceSignatures = true - return b + return &block{c1: DefaultC1, c2: DefaultC2, prefixDiffLen: DefaultPrefixDiffLen, prefixDiffMin: DefaultPrefixDiffMin} } func (b *block) WithC1(c1 int) *block { @@ -63,6 +59,11 @@ func (b *block) WithPrefixDiffMin(prefixDiffMin int) *block { return b } +func (b *block) WithSignatureScheme(scheme signature.Scheme) *block { + b.scheme = scheme + return b +} + func (b *block) OnRegister(p *protocol.Protocol, node *noise.Node) { b.opcodePing = noise.RegisterMessage(noise.NextAvailableOpcode(), (*Ping)(nil)) b.opcodeEvict = noise.RegisterMessage(noise.NextAvailableOpcode(), (*Evict)(nil)) @@ -103,7 +104,7 @@ func (b *block) OnBegin(p *protocol.Protocol, peer *noise.Peer) error { // Register peer. protocol.SetPeerID(peer, id.ID) - enforceSignatures(peer, b.enforceSignatures) + enforceSignatures(peer, b.scheme) // Log peer into S/Kademlia table, and have all messages update the S/Kademlia table. _ = b.logPeerActivity(peer) @@ -140,11 +141,11 @@ func (b *block) logPeerActivity(peer *noise.Peer) error { return nil } -func enforceSignatures(peer *noise.Peer, enforce bool) { - if enforce { +func enforceSignatures(peer *noise.Peer, scheme signature.Scheme) { + if scheme != nil { // Place signature at the footer of every single message. peer.OnEncodeFooter(func(node *noise.Node, peer *noise.Peer, header, msg []byte) (i []byte, e error) { - signature, err := node.Keys.Sign(msg) + signature, err := scheme.Sign(node.Keys.PrivateKey(), msg) if err != nil { panic(errors.Wrap(err, "signature: failed to sign message")) @@ -162,7 +163,7 @@ func enforceSignatures(peer *noise.Peer, enforce bool) { return errors.Wrap(err, "signature: failed to read message signature") } - if err = node.Keys.Verify(protocol.PeerID(peer).PublicKey(), msg, signature); err != nil { + if err = scheme.Verify(protocol.PeerID(peer).PublicKey(), msg, signature); err != nil { peer.Disconnect() return errors.Wrap(err, "signature: peer sent an invalid signature") } diff --git a/skademlia/mod_test.go b/skademlia/mod_test.go index 373affcb..019eeeea 100644 --- a/skademlia/mod_test.go +++ b/skademlia/mod_test.go @@ -1,6 +1,7 @@ package skademlia import ( + "github.com/perlin-network/noise/signature/eddsa" "github.com/stretchr/testify/assert" "testing" "testing/quick" @@ -9,7 +10,12 @@ import ( func TestNew(t *testing.T) { block := New() - assert.Equal(t, false, block.enforceSignatures) + scheme := eddsa.New() + + assert.Equal(t, nil, block.scheme) + block.WithSignatureScheme(scheme) + assert.Equal(t, scheme, block.scheme) + assert.Equal(t, DefaultC1, block.c1) assert.Equal(t, DefaultC2, block.c2) assert.Equal(t, DefaultPrefixDiffLen, block.prefixDiffLen) From ad33d6a2ee5fcd6c0e22c09452b1ae40dcd9c487 Mon Sep 17 00:00:00 2001 From: Kenta Iwasaki Date: Fri, 22 Feb 2019 15:55:39 +0800 Subject: [PATCH 2/3] eddsa: make test for 100% coverage --- signature/eddsa/mod_test.go | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 signature/eddsa/mod_test.go diff --git a/signature/eddsa/mod_test.go b/signature/eddsa/mod_test.go new file mode 100644 index 00000000..3d3d55f0 --- /dev/null +++ b/signature/eddsa/mod_test.go @@ -0,0 +1,71 @@ +package eddsa + +import ( + "bytes" + "github.com/perlin-network/noise/internal/edwards25519" + "github.com/stretchr/testify/assert" + "testing" + "testing/quick" +) + +func TestBadKey(t *testing.T) { + badKey := []byte("this is a bad key") + message := []byte("this is a message") + + scheme := New() + _, err := scheme.Sign(badKey, message) + assert.Error(t, err) + + _, err = Sign(badKey, message) + assert.Error(t, err) + + err = scheme.Verify(badKey, message, []byte("random signature")) + assert.Error(t, err) + + err = Verify(badKey, message, []byte("random signature")) + assert.Error(t, err) +} + +func TestSignAndVerify(t *testing.T) { + scheme := New() + + quick.Check(func(message []byte, bad []byte) bool { + publicKey, privateKey, err := edwards25519.GenerateKey(nil) + if err != nil { + return false + } + + sign1, err := scheme.Sign(privateKey, message) + if err != nil { + return false + } + + sign2, err := Sign(privateKey, message) + if err != nil { + return false + } + + if !bytes.Equal(sign1, sign2) { + return false + } + + if scheme.Verify(publicKey, message, sign2) != nil { + return false + } + + if Verify(publicKey, message, sign1) != nil { + return false + } + + // Check invalid signature + if scheme.Verify(publicKey, message, append(sign2, bad...)) != nil { + return false + } + + if Verify(publicKey, message, append(sign1, bad...)) != nil { + return false + } + + return true + }, nil) +} From 0854cdcc7b86d31ea86120e5830c40d9644b1a56 Mon Sep 17 00:00:00 2001 From: Kenta Iwasaki Date: Fri, 22 Feb 2019 16:01:13 +0800 Subject: [PATCH 3/3] docs, eddsa: update documentation --- docs/src/identities.md | 36 +++++++++++++++++++++++++----------- docs/src/skademlia.md | 5 +++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/src/identities.md b/docs/src/identities.md index c69c9b3f..7cab2853 100644 --- a/docs/src/identities.md +++ b/docs/src/identities.md @@ -8,8 +8,8 @@ Most likely, you would want a cryptographic ID for your node. As of right now, there exists a few identity schemes which Noise provides built-in support for that you may use on the get-go for your p2p application: -1. Ed25519 identities w/ EdDSA signatures -2. S/Kademlia-compatible Ed25519 identities w/ EdDSA signatures +1. Ed25519 identities +2. S/Kademlia-compatible Ed25519 identities You may additionally create/implement your own identity schemes that any node/peer may support in Noise by simply having your scheme implement the following interface: @@ -24,13 +24,10 @@ type Keypair interface { ID() []byte PublicKey() []byte PrivateKey() []byte - - Sign(buf []byte) ([]byte, error) - Verify(publicKeyBuf []byte, buf []byte, signature []byte) error } ``` -Should the identity scheme you wish to implement not have any associated signature scheme to it, or any sort of `PrivateKey()` or `PublicKey()` or `ID()` associated to it, you may simply stub out those functions and ignore their implementation. +Should the identity scheme you wish to implement not have any sort of `PrivateKey()` or `PublicKey()` or `ID()` associated to it, you may simply stub out those functions and ignore their implementation. After picking/implementing an identity scheme of your choice for your p2p application, it is simple to have your node adopt it as follows: @@ -61,16 +58,33 @@ params.Keys = skademlia.LoadKeys([]byte{...}, skademlia.DefaultC1, skademlia.Def ## Signing/Verifying Messages -Given a node instance instantiated with an identity scheme paired with a signature scheme, -you may sign/verify raw arrays of bytes to see whether or not a signature was generated -by a specified public key like so: +Signature schemes are stubbed out into an interface which you could implement to integrate +cryptographic signatures into supported protocol building blocks Noise provides. + +All signature schemes implement the following interface: ```go +type Scheme interface { + Sign(privateKey, messageBuf []byte) ([]byte, error) + Verify(publicKeyBuf, messageBuf, signatureBuf []byte) error +} +``` + +As of right now, Noise provides off-the-shelf the following signature schemes: +- EdDSA Signature Scheme (assembly-optimized) + +You may use a signature scheme to sign/verify raw arrays of bytes to see +whether or not a signature was generated by a specified public key like so: + +```go +import "github.com/perlin-network/noise/signature/eddsa" + var node *noise.Node message := "We're going to sign this message with our node!" -signature, err := node.Keys.Sign(message) +// Sign a message using the EdDSA signature scheme. +signature, err := eddsa.Sign(node.Keys.PrivateKey(), []byte(message)) if err != nil { panic("failed to sign the message") } @@ -80,5 +94,5 @@ fmt.Println("Signature:", signature) // Now let's verify that the signature is under our own nodes identity! fmt.Println("Is the signature valid?", - node.Keys.Verify(node.Keys.PublicKey(), message, signature)) + eddsa.Verify(node.Keys.PublicKey(), []byte(message), signature)) ``` \ No newline at end of file diff --git a/docs/src/skademlia.md b/docs/src/skademlia.md index 4f482982..a5ef9ada 100644 --- a/docs/src/skademlia.md +++ b/docs/src/skademlia.md @@ -149,6 +149,11 @@ block.WithC2(int(16)) block.WithPrefixDiffLen(int(128)) block.WithPrefixDiffMaxLen(int(32)) +// Additionally, have all S/Kademlia messages and future +// messages be appended with EdDSA signatures that are +// validated for every incoming message. +block.WithSignatureScheme(eddsa.New()) + // Register the protocol block and enforce it on our node. protocol.New().Register(block).Enforce(node) ```