diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a300a714f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/bls-signatures/bls-signatures"] + path = lib/bls-signatures/bls-signatures + url = https://github.com/filecoin-project/bls-signatures.git diff --git a/go.mod b/go.mod index f0813cf75..9cf2fc07d 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/ipfs/go-datastore v0.0.5 github.com/ipfs/go-ipfs-routing v0.1.0 github.com/ipfs/go-log v0.0.2-0.20190703113630-0c3cfb1eccc4 + github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 github.com/libp2p/go-libp2p v0.2.0 github.com/libp2p/go-libp2p-circuit v0.1.0 github.com/libp2p/go-libp2p-connmgr v0.1.0 diff --git a/go.sum b/go.sum index 795f01f84..1eaf7caeb 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/ipfs/go-log v0.0.2-0.20190703113630-0c3cfb1eccc4 h1:4GUopYwyu/8kX0UxY github.com/ipfs/go-log v0.0.2-0.20190703113630-0c3cfb1eccc4/go.mod h1:YTiqro5xwLoGra88hB8tMBlN+7ByaT3Kdaa0UqwCmI0= github.com/ipfs/go-todocounter v0.0.1 h1:kITWA5ZcQZfrUnDNkRn04Xzh0YFaDFXsoO2A81Eb6Lw= github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4= +github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 h1:QG4CGBqCeuBo6aZlGAamSkxWdgWfZGeE49eUOWJPA4c= +github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52/go.mod h1:fdg+/X9Gg4AsAIzWpEHwnqd+QY3b7lajxyjE1m4hkq4= github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA= diff --git a/lib/bls-signatures/bls-signatures b/lib/bls-signatures/bls-signatures new file mode 160000 index 000000000..c2f288190 --- /dev/null +++ b/lib/bls-signatures/bls-signatures @@ -0,0 +1 @@ +Subproject commit c2f28819039ce50f9a25429598a6d179fdaabe20 diff --git a/lib/bls-signatures/bls.go b/lib/bls-signatures/bls.go new file mode 100644 index 000000000..5fc674e32 --- /dev/null +++ b/lib/bls-signatures/bls.go @@ -0,0 +1,148 @@ +package bls + +import ( + "unsafe" +) + +// #cgo LDFLAGS: -L${SRCDIR}/lib -lbls_signatures +// #cgo pkg-config: ${SRCDIR}/lib/pkgconfig/libbls_signatures.pc +// #include "./include/libbls_signatures.h" +import "C" + +// Hash computes the digest of a message +func Hash(message Message) Digest { + // prep request + cMessage := C.CBytes(message) + defer C.free(cMessage) + cMessagePtr := (*C.uchar)(cMessage) + cMessageLen := C.size_t(len(message)) + + // call method + resPtr := (*C.HashResponse)(unsafe.Pointer(C.hash(cMessagePtr, cMessageLen))) + defer C.destroy_hash_response(resPtr) + + // prep response + var digest Digest + digestSlice := C.GoBytes(unsafe.Pointer(&resPtr.digest), DigestBytes) // nolint: staticcheck + copy(digest[:], digestSlice) + + return digest +} + +// Verify verifies that a signature is the aggregated signature of digests - pubkeys +func Verify(signature Signature, digests []Digest, publicKeys []PublicKey) bool { + // prep data + flattenedDigests := make([]byte, DigestBytes*len(digests)) + for idx, digest := range digests { + copy(flattenedDigests[(DigestBytes*idx):(DigestBytes*(1+idx))], digest[:]) + } + + flattenedPublicKeys := make([]byte, PublicKeyBytes*len(publicKeys)) + for idx, publicKey := range publicKeys { + copy(flattenedPublicKeys[(PublicKeyBytes*idx):(PublicKeyBytes*(1+idx))], publicKey[:]) + } + + // prep request + cSignature := C.CBytes(signature[:]) + defer C.free(cSignature) + cSignaturePtr := (*C.uchar)(cSignature) + + cFlattenedDigests := C.CBytes(flattenedDigests) + defer C.free(cFlattenedDigests) + cFlattenedDigestsPtr := (*C.uint8_t)(cFlattenedDigests) + cFlattenedDigestsLen := C.size_t(len(flattenedDigests)) + + cFlattenedPublicKeys := C.CBytes(flattenedPublicKeys) + defer C.free(cFlattenedPublicKeys) + cFlattenedPublicKeysPtr := (*C.uint8_t)(cFlattenedPublicKeys) + cFlattenedPublicKeysLen := C.size_t(len(flattenedPublicKeys)) + + // call method + resPtr := (*C.VerifyResponse)(unsafe.Pointer(C.verify(cSignaturePtr, cFlattenedDigestsPtr, cFlattenedDigestsLen, cFlattenedPublicKeysPtr, cFlattenedPublicKeysLen))) + defer C.destroy_verify_response(resPtr) + + return resPtr.result > 0 +} + +// Aggregate aggregates signatures together into a new signature +func Aggregate(signatures []Signature) Signature { + // prep data + flattenedSignatures := make([]byte, SignatureBytes*len(signatures)) + for idx, sig := range signatures { + copy(flattenedSignatures[(SignatureBytes*idx):(SignatureBytes*(1+idx))], sig[:]) + } + + // prep request + cFlattenedSignatures := C.CBytes(flattenedSignatures) + defer C.free(cFlattenedSignatures) + cFlattenedSignaturesPtr := (*C.uint8_t)(cFlattenedSignatures) + cFlattenedSignaturesLen := C.size_t(len(flattenedSignatures)) + + // call method + resPtr := (*C.AggregateResponse)(unsafe.Pointer(C.aggregate(cFlattenedSignaturesPtr, cFlattenedSignaturesLen))) + defer C.destroy_aggregate_response(resPtr) + + // prep response + var signature Signature + signatureSlice := C.GoBytes(unsafe.Pointer(&resPtr.signature), SignatureBytes) // nolint: staticcheck + copy(signature[:], signatureSlice) + + return signature +} + +// PrivateKeyGenerate generates a private key +func PrivateKeyGenerate() PrivateKey { + // call method + resPtr := (*C.PrivateKeyGenerateResponse)(unsafe.Pointer(C.private_key_generate())) + defer C.destroy_private_key_generate_response(resPtr) + + // prep response + var privateKey PrivateKey + privateKeySlice := C.GoBytes(unsafe.Pointer(&resPtr.private_key), PrivateKeyBytes) // nolint: staticcheck + copy(privateKey[:], privateKeySlice) + + return privateKey +} + +// PrivateKeySign signs a message +func PrivateKeySign(privateKey PrivateKey, message Message) Signature { + // prep request + cPrivateKey := C.CBytes(privateKey[:]) + defer C.free(cPrivateKey) + cPrivateKeyPtr := (*C.uchar)(cPrivateKey) + + cMessage := C.CBytes(message) + defer C.free(cMessage) + cMessagePtr := (*C.uchar)(cMessage) + cMessageLen := C.size_t(len(message)) + + // call method + resPtr := (*C.PrivateKeySignResponse)(unsafe.Pointer(C.private_key_sign(cPrivateKeyPtr, cMessagePtr, cMessageLen))) + defer C.destroy_private_key_sign_response(resPtr) + + // prep response + var signature Signature + signatureSlice := C.GoBytes(unsafe.Pointer(&resPtr.signature), SignatureBytes) // nolint: staticcheck + copy(signature[:], signatureSlice) + + return signature +} + +// PrivateKeyPublicKey gets the public key for a private key +func PrivateKeyPublicKey(privateKey PrivateKey) PublicKey { + // prep request + cPrivateKey := C.CBytes(privateKey[:]) + defer C.free(cPrivateKey) + cPrivateKeyPtr := (*C.uchar)(cPrivateKey) + + // call method + resPtr := (*C.PrivateKeyPublicKeyResponse)(unsafe.Pointer(C.private_key_public_key(cPrivateKeyPtr))) // nolint: staticcheck + defer C.destroy_private_key_public_key_response(resPtr) + + // prep response + var publicKey PublicKey + publicKeySlice := C.GoBytes(unsafe.Pointer(&resPtr.public_key), PublicKeyBytes) // nolint: staticcheck + copy(publicKey[:], publicKeySlice) + + return publicKey +} diff --git a/lib/bls-signatures/bls_test.go b/lib/bls-signatures/bls_test.go new file mode 100644 index 000000000..153e0b565 --- /dev/null +++ b/lib/bls-signatures/bls_test.go @@ -0,0 +1,43 @@ +package bls + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBLSSigningAndVerification(t *testing.T) { + // generate private keys + fooPrivateKey := PrivateKeyGenerate() + barPrivateKey := PrivateKeyGenerate() + + // get the public keys for the private keys + fooPublicKey := PrivateKeyPublicKey(fooPrivateKey) + barPublicKey := PrivateKeyPublicKey(barPrivateKey) + + // make messages to sign with the keys + fooMessage := Message("hello foo") + barMessage := Message("hello bar!") + + // calculate the digests of the messages + fooDigest := Hash(fooMessage) + barDigest := Hash(barMessage) + + // get the signature when signing the messages with the private keys + fooSignature := PrivateKeySign(fooPrivateKey, fooMessage) + barSignature := PrivateKeySign(barPrivateKey, barMessage) + + // assert the foo message was signed with the foo key + assert.True(t, Verify(fooSignature, []Digest{fooDigest}, []PublicKey{fooPublicKey})) + + // assert the bar message was signed with the bar key + assert.True(t, Verify(barSignature, []Digest{barDigest}, []PublicKey{barPublicKey})) + + // assert the foo message was not signed by the bar key + assert.False(t, Verify(fooSignature, []Digest{fooDigest}, []PublicKey{barPublicKey})) + + // assert the bar/foo message was not signed by the foo/bar key + assert.False(t, Verify(barSignature, []Digest{barDigest}, []PublicKey{fooPublicKey})) + assert.False(t, Verify(barSignature, []Digest{fooDigest}, []PublicKey{barPublicKey})) + assert.False(t, Verify(fooSignature, []Digest{barDigest}, []PublicKey{fooPublicKey})) +} diff --git a/lib/bls-signatures/types.go b/lib/bls-signatures/types.go new file mode 100644 index 000000000..1d9cd5d6d --- /dev/null +++ b/lib/bls-signatures/types.go @@ -0,0 +1,28 @@ +package bls + +// SignatureBytes is the length of a BLS signature +const SignatureBytes = 96 + +// PrivateKeyBytes is the length of a BLS private key +const PrivateKeyBytes = 32 + +// PublicKeyBytes is the length of a BLS public key +const PublicKeyBytes = 48 + +// DigestBytes is the length of a BLS message hash/digest +const DigestBytes = 96 + +// Signature is a compressed affine +type Signature [SignatureBytes]byte + +// PrivateKey is a compressed affine +type PrivateKey [PrivateKeyBytes]byte + +// PublicKey is a compressed affine +type PublicKey [PublicKeyBytes]byte + +// Message is a byte slice +type Message []byte + +// Digest is a compressed affine +type Digest [DigestBytes]byte 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..bc86e1be2 --- /dev/null +++ b/lib/crypto/crypto_test.go @@ -0,0 +1,61 @@ +package crypto_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/go-lotus/lib/crypto" +) + +func TestGenerateKey(t *testing.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)) +}