diff --git a/lib/crypto/crypto.go b/lib/crypto/crypto.go new file mode 100644 index 000000000..977e8ae9a --- /dev/null +++ b/lib/crypto/crypto.go @@ -0,0 +1,70 @@ +package crypto + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "io" + + secp256k1 "github.com/ipsn/go-secp256k1" +) + +// PrivateKeyBytes is the size of a serialized private key. +const PrivateKeyBytes = 32 + +// PublicKeyBytes is the size of a serialized public key. +const PublicKeyBytes = 65 + +// PublicKey returns the public key for this private key. +func PublicKey(sk []byte) []byte { + x, y := secp256k1.S256().ScalarBaseMult(sk) + return elliptic.Marshal(secp256k1.S256(), x, y) +} + +// Sign signs the given message, which must be 32 bytes long. +func Sign(sk, msg []byte) ([]byte, error) { + return secp256k1.Sign(msg, sk) +} + +// Equals compares two private key for equality and returns true if they are the same. +func Equals(sk, other []byte) bool { + return bytes.Equal(sk, other) +} + +// Verify checks the given signature and returns true if it is valid. +func Verify(pk, msg, signature []byte) bool { + if len(signature) == 65 { + // Drop the V (1byte) in [R | S | V] style signatures. + // The V (1byte) is the recovery bit and is not apart of the signature verification. + return secp256k1.VerifySignature(pk[:], msg, signature[:len(signature)-1]) + } + + return secp256k1.VerifySignature(pk[:], msg, signature) +} + +// GenerateKeyFromSeed generates a new key from the given reader. +func GenerateKeyFromSeed(seed io.Reader) ([]byte, error) { + key, err := ecdsa.GenerateKey(secp256k1.S256(), seed) + if err != nil { + return nil, err + } + + privkey := make([]byte, PrivateKeyBytes) + blob := key.D.Bytes() + + // the length is guaranteed to be fixed, given the serialization rules for secp2561k curve points. + copy(privkey[PrivateKeyBytes-len(blob):], blob) + + return privkey, nil +} + +// GenerateKey creates a new key using secure randomness from crypto.rand. +func GenerateKey() ([]byte, error) { + return GenerateKeyFromSeed(rand.Reader) +} + +// EcRecover recovers the public key from a message, signature pair. +func EcRecover(msg, signature []byte) ([]byte, error) { + return secp256k1.RecoverPubkey(msg, signature) +} diff --git a/lib/crypto/crypto_test.go b/lib/crypto/crypto_test.go new file mode 100644 index 000000000..c9c8db9e3 --- /dev/null +++ b/lib/crypto/crypto_test.go @@ -0,0 +1,64 @@ +package crypto_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/go-filecoin/crypto" + tf "github.com/filecoin-project/go-filecoin/testhelpers/testflags" +) + +func TestGenerateKey(t *testing.T) { + tf.UnitTest(t) + + rand.Seed(time.Now().UnixNano()) + + sk, err := crypto.GenerateKey() + assert.NoError(t, err) + + assert.Equal(t, len(sk), 32) + + msg := make([]byte, 32) + for i := 0; i < len(msg); i++ { + msg[i] = byte(i) + } + + digest, err := crypto.Sign(sk, msg) + assert.NoError(t, err) + assert.Equal(t, len(digest), 65) + pk := crypto.PublicKey(sk) + + // valid signature + assert.True(t, crypto.Verify(pk, msg, digest)) + + // invalid signature - different message (too short) + assert.False(t, crypto.Verify(pk, msg[3:], digest)) + + // invalid signature - different message + msg2 := make([]byte, 32) + copy(msg2, msg) + rand.Shuffle(len(msg2), func(i, j int) { msg2[i], msg2[j] = msg2[j], msg2[i] }) + assert.False(t, crypto.Verify(pk, msg2, digest)) + + // invalid signature - different digest + digest2 := make([]byte, 65) + copy(digest2, digest) + rand.Shuffle(len(digest2), func(i, j int) { digest2[i], digest2[j] = digest2[j], digest2[i] }) + assert.False(t, crypto.Verify(pk, msg, digest2)) + + // invalid signature - digest too short + assert.False(t, crypto.Verify(pk, msg, digest[3:])) + assert.False(t, crypto.Verify(pk, msg, digest[:29])) + + // invalid signature - digest too long + digest3 := make([]byte, 70) + copy(digest3, digest) + assert.False(t, crypto.Verify(pk, msg, digest3)) + + recovered, err := crypto.EcRecover(msg, digest) + assert.NoError(t, err) + assert.Equal(t, recovered, crypto.PublicKey(sk)) +}