From 4c36aeba174ce7e1cf8ff8a7fbc7ee39820e0356 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Thu, 9 Feb 2017 06:35:39 -0500 Subject: [PATCH] feat: initial implementation --- .gitignore | 23 +-- .npmignore | 34 ++++ .travis.yml | 38 ++++ README.md | 118 ++++++++++- circle.yml | 12 ++ package.json | 65 ++++++ src/crypto/index.js | 85 ++++++++ src/index.js | 119 +++++++++++ test/fixtures/go-interop.js | 31 +++ test/secp256k1.spec.js | 391 ++++++++++++++++++++++++++++++++++++ 10 files changed, 902 insertions(+), 14 deletions(-) create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 circle.yml create mode 100644 package.json create mode 100644 src/crypto/index.js create mode 100644 src/index.js create mode 100644 test/fixtures/go-interop.js create mode 100644 test/secp256k1.spec.js diff --git a/.gitignore b/.gitignore index 5148e52..fb8d1c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,12 @@ +**/node_modules/ +**/*.log +test/repo-tests* + # Logs logs *.log -npm-debug.log* + +coverage # Runtime data pids @@ -14,24 +19,16 @@ lib-cov # Coverage directory used by tools like istanbul coverage -# nyc test coverage -.nyc_output - # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release +build -# Dependency directories +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules -jspm_packages -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..59335fd --- /dev/null +++ b/.npmignore @@ -0,0 +1,34 @@ +**/node_modules/ +**/*.log +test/repo-tests* + +# Logs +logs +*.log + +coverage + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +build + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c601607 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,38 @@ +sudo: false +language: node_js + +matrix: + include: + - node_js: 4 + env: CXX=g++-4.8 + - node_js: 6 + env: + - SAUCE=true + - CXX=g++-4.8 + - node_js: stable + env: CXX=g++-4.8 + +# Make sure we have new NPM. +before_install: + - npm install -g npm + +script: + - npm run lint + - npm test + - npm run coverage + + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +after_success: + - npm run coverage-publish + +addons: + firefox: 'latest' + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/README.md b/README.md index f05b59b..3670b05 100644 --- a/README.md +++ b/README.md @@ -1 +1,117 @@ -# js-libp2p-crypto-secp256k1 \ No newline at end of file +# js-libp2p-crypto + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) +![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square) +![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square) + +> Support for secp256k1 keys in js-libp2p-crypto + +This repo contains a [js-libp2p-crypto](https://github.com/libp2p/js-libp2p-crypto)-compatible +implementation of cryptographic signature generation and verification using the +[secp256k1 elliptic curve](https://en.bitcoin.it/wiki/Secp256k1) popularized by Bitcoin and other +crypto currencies. + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) + - [Example](#example) +- [API](#api) + - [`generateKeyPair([bits,] callback)`](#generatekeypairbits-callback) + - [`unmarshalSecp256k1PublicKey(bytes)`](#unmarshalsecp256k1publickeybytes) + - [`unmarshalSecp256k1PrivateKey(bytes, callback)`](#unmarshalsecp256k1privatekeybytes-callback) + - [`Secp256k1PublicKey`](#secp256k1publickey) + - [`.verify(data, sig, callback)`](#verifydata-sig-callback) + - [`Secp256k1PrivateKey`](#secp256k1privatekey) + - [`.public`](#public) + - [`.sign(data, callback)`](#signdata-callback) +- [Contribute](#contribute) +- [License](#license) + +## Install + +```sh +npm install --save libp2p-crypto-secp256k1 +``` + +## Usage + +This module is designed to work with [js-libp2p-crypto](https://github.com/libp2p/js-libp2p-crypto). +Installing `libp2p-crypto-secp256k1` will automatically add support for the `'secp256k1'` key type, which +can be used as an argument to the [libp2p-crypto API functions](https://github.com/libp2p/js-libp2p-crypto#api) +`generateKeyPair`, `unmarshalPublicKey`, and `marshalPrivateKey`. The keys returned from those functions will be +instances of the `Secp256k1PublicKey` or `Secp256k1PrivateKey` classes provided by this module. + +### Example + +```js +const crypto = require('libp2p-crypto') + +const msg = Buffer.from('Hello World') + +crypto.generateKeyPair('secp256k1', 256, (err, key) => { + // assuming no error, key will be an instance of Secp256k1PrivateKey + // the public key is available as key.public + key.sign(msg, (err, sig) => { + key.public.verify(msg, sig, (err, valid) => { + assert(valid, 'Something went horribly wrong') + }) + }) +}) +``` + +## API + +The functions below are the public API of this module. +For usage within libp2p-crypto, see the [libp2p-crypto API documentation](https://github.com/libp2p/js-libp2p-crypto#api). + +### `generateKeyPair([bits, ] callback)` +- `bits: Number` - Optional, included for compatibility with js-libp2p-crypto. Ignored if present; private keys will always be 256 bits. +- `callback: Function` + +### `unmarshalSecp256k1PublicKey(bytes)` +- `bytes: Buffer` + +Converts a serialized secp256k1 public key into an instance of `Secp256k1PublicKey` and returns it + +### `unmarshalSecp256k1PrivateKey(bytes, callback)` +- `bytes: Buffer` +- `callback: Function` + +Converts a serialized secp256k1 private key into an instance of `Secp256k1PrivateKey`, passing it to `callback` on success + +### `Secp256k1PublicKey` + +#### `.verify(data, sig, callback)` +- `data: Buffer` +- `sig: Buffer` +- `callback: Function` + +Calculates the SHA-256 hash of `data`, and verifies the DER-encoded signature in `sig`, passing the result to `callback` + +### `Secp256k1PrivateKey` + +#### `.public` + +Accessor for the `Secp256k1PublicKey` associated with this private key. + +#### `.sign(data, callback)` +- `data: Buffer` + +Calculates the SHA-256 hash of `data` and signs it, passing the DER-encoded signature to `callback` + +## Contribute + +Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-crypto/issues)! + +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) + +## License + +[MIT](LICENSE) diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..434211a --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +machine: + node: + version: stable + +dependencies: + pre: + - google-chrome --version + - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + - sudo apt-get update + - sudo apt-get --only-upgrade install google-chrome-stable + - google-chrome --version diff --git a/package.json b/package.json new file mode 100644 index 0000000..a3a0d1c --- /dev/null +++ b/package.json @@ -0,0 +1,65 @@ +{ + "name": "libp2p-crypto-secp256k1", + "version": "0.0.1", + "description": "Support for secp256k1 keys in libp2p-crypto", + "main": "src/index.js", + "browser": { + "secp256k1": "secp256k1/js" + }, + "scripts": { + "lint": "aegir-lint", + "build": "aegir-build", + "test": "npm run test:node && npm run test:browser", + "test:node": "aegir-test --env node", + "test:browser": "aegir-test --env browser", + "release": "aegir-release", + "release-minor": "aegir-release --type minor", + "release-major": "aegir-release --type major", + "coverage": "aegir-coverage", + "coverage-publish": "aegir-coverage publish" + }, + "keywords": [ + "IPFS", + "libp2p", + "crypto", + "secp256k1" + ], + "author": "Yusef Napora ", + "license": "MIT", + "dependencies": { + "libp2p-crypto": "^0.8.0", + "multihashing-async": "^0.3.0", + "nodeify": "^1.0.0", + "safe-buffer": "^5.0.1", + "secp256k1": "^3.2.5" + }, + "devDependencies": { + "aegir": "^9.2.1", + "benchmark": "^2.1.2", + "chai": "^3.5.0", + "pre-commit": "^1.1.3" + }, + "pre-commit": [ + "lint", + "test" + ], + "engines": { + "node": ">=4.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/libp2p/js-libp2p-crypto-secp256k1.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-crypto-secp256k1/issues" + }, + "homepage": "https://github.com/libp2p/js-libp2p-crypto-secp256k1", + "contributors": [ + "David Dias ", + "Friedel Ziegelmayer ", + "Greenkeeper ", + "Richard Littauer ", + "Yusef Napora ", + "nikuda " + ] +} diff --git a/src/crypto/index.js b/src/crypto/index.js new file mode 100644 index 0000000..ca09fbe --- /dev/null +++ b/src/crypto/index.js @@ -0,0 +1,85 @@ +'use strict' + +const secp256k1 = require('secp256k1') +const multihashing = require('multihashing-async') +const setImmediate = require('async/setImmediate') +const randomBytes = require('libp2p-crypto').randomBytes + +const HASH_ALGORITHM = 'sha2-256' + +exports.privateKeyLength = 32 + +exports.generateKey = function (callback) { + const done = (err, res) => setImmediate(() => { + callback(err, res) + }) + + let privateKey + do { + privateKey = randomBytes(32) + } while (!secp256k1.privateKeyVerify(privateKey)) + + done(null, privateKey) +} + +exports.hashAndSign = function (key, msg, callback) { + const done = (err, res) => setImmediate(() => { + callback(err, res) + }) + + multihashing.digest(msg, HASH_ALGORITHM, (err, digest) => { + if (err) return done(err) + try { + const sig = secp256k1.sign(digest, key) + const sigDER = secp256k1.signatureExport(sig.signature) + return done(null, sigDER) + } catch (err) { + done(err) + } + }) +} + +exports.hashAndVerify = function (key, sig, msg, callback) { + const done = (err, res) => setImmediate(() => { + callback(err, res) + }) + + multihashing.digest(msg, HASH_ALGORITHM, (err, digest) => { + if (err) return done(err) + try { + sig = secp256k1.signatureImport(sig) + const valid = secp256k1.verify(digest, sig, key) + return done(null, valid) + } catch (err) { + done(err) + } + }) +} + +exports.compressPublicKey = function compressPublicKey (key) { + if (!secp256k1.publicKeyVerify(key)) { + throw new Error('Invalid public key') + } + return secp256k1.publicKeyConvert(key, true) +} + +exports.decompressPublicKey = function decompressPublicKey (key) { + return secp256k1.publicKeyConvert(key, false) +} + +exports.validatePrivateKey = function validatePrivateKey (key) { + if (!secp256k1.privateKeyVerify(key)) { + throw new Error('Invalid private key') + } +} + +exports.validatePublicKey = function validatePublicKey (key) { + if (!secp256k1.publicKeyVerify(key)) { + throw new Error('Invalid public key') + } +} + +exports.computePublicKey = function computePublicKey (privateKey) { + exports.validatePrivateKey(privateKey) + return secp256k1.publicKeyCreate(privateKey) +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e5dafd0 --- /dev/null +++ b/src/index.js @@ -0,0 +1,119 @@ +'use strict' + +const multihashing = require('multihashing-async') +const crypto = require('./crypto') +const pbm = require('libp2p-crypto').protobuf + +class Secp256k1PublicKey { + constructor (key) { + crypto.validatePublicKey(key) + this._key = key + } + + verify (data, sig, callback) { + ensure(callback) + crypto.hashAndVerify(this._key, sig, data, callback) + } + + marshal () { + return crypto.compressPublicKey(this._key) + } + + get bytes () { + return pbm.PublicKey.encode({ + Type: pbm.KeyType.Secp256k1, + Data: this.marshal() + }) + } + + equals (key) { + return this.bytes.equals(key.bytes) + } + + hash (callback) { + ensure(callback) + multihashing(this.bytes, 'sha2-256', callback) + } +} + +class Secp256k1PrivateKey { + constructor (key, publicKey) { + this._key = key + this._publicKey = publicKey || crypto.computePublicKey(key) + crypto.validatePrivateKey(this._key) + crypto.validatePublicKey(this._publicKey) + } + + sign (message, callback) { + ensure(callback) + crypto.hashAndSign(this._key, message, callback) + } + + get public () { + return new Secp256k1PublicKey(this._publicKey) + } + + marshal () { + return this._key + } + + get bytes () { + return pbm.PrivateKey.encode({ + Type: pbm.KeyType.Secp256k1, + Data: this.marshal() + }) + } + + equals (key) { + return this.bytes.equals(key.bytes) + } + + hash (callback) { + ensure(callback) + multihashing(this.bytes, 'sha2-256', callback) + } +} + +function unmarshalSecp256k1PrivateKey (bytes, callback) { + callback(null, new Secp256k1PrivateKey(bytes), null) +} + +function unmarshalSecp256k1PublicKey (bytes) { + return new Secp256k1PublicKey(bytes) +} + +function generateKeyPair (_bits, cb) { + if (cb === undefined && typeof _bits === 'function') { + cb = _bits + } + ensure(cb) + + crypto.generateKey((err, privateKeyBytes) => { + if (err) { + return cb(err) + } + let privkey + try { + privkey = new Secp256k1PrivateKey(privateKeyBytes) + } catch (err) { + cb(err) + return + } + + cb(null, privkey) + }) +} + +function ensure (cb) { + if (typeof cb !== 'function') { + throw new Error('callback is required') + } +} + +module.exports = { + Secp256k1PublicKey, + Secp256k1PrivateKey, + unmarshalSecp256k1PrivateKey, + unmarshalSecp256k1PublicKey, + generateKeyPair +} diff --git a/test/fixtures/go-interop.js b/test/fixtures/go-interop.js new file mode 100644 index 0000000..8f18c1a --- /dev/null +++ b/test/fixtures/go-interop.js @@ -0,0 +1,31 @@ +'use strict' + +const Buffer = require('safe-buffer').Buffer + +// The keypair and signature below were generated in a gore repl session (https://github.com/motemen/gore) +// using the secp256k1 fork of go-libp2p-crypto by github user @vyzo +// +// gore> :import github.com/vyzo/go-libp2p-crypto +// gore> :import crypto/rand +// gore> :import io/ioutil +// gore> priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Secp256k1, 256, rand.Reader) +// gore> privBytes, err := priv.Bytes() +// gore> pubBytes, err := pub.Bytes() +// gore> msg := []byte("hello! and welcome to some awesome crypto primitives") +// gore> sig, err := priv.Sign(msg) +// gore> ioutil.WriteFile("/tmp/secp-go-priv.bin", privBytes, 0644) +// gore> ioutil.WriteFile("/tmp/secp-go-pub.bin", pubBytes, 0644) +// gore> ioutil.WriteFile("/tmp/secp-go-sig.bin", sig, 0644) +// +// The generated files were then read in a node repl with e.g.: +// > fs.readFileSync('/tmp/secp-go-pub.bin').toString('hex') +// '08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf' +// +// and the results copy/pasted in here + +module.exports = { + privateKey: Buffer.from('08021220358f15db8c2014d570e8e3a622454e2273975a3cca443ec0c45375b13d381d18', 'hex'), + publicKey: Buffer.from('08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf', 'hex'), + message: Buffer.from('hello! and welcome to some awesome crypto primitives', 'utf-8'), + signature: Buffer.from('304402200e4c629e9f5d99439115e60989cd40087f6978c36078b0b50cf3d30af5c38d4102204110342c8e7f0809897c1c7a66e49e1c6b7cb0a6ed6993640ec2fe742c1899a9', 'hex') +} diff --git a/test/secp256k1.spec.js b/test/secp256k1.spec.js new file mode 100644 index 0000000..65a95e8 --- /dev/null +++ b/test/secp256k1.spec.js @@ -0,0 +1,391 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const Buffer = require('safe-buffer').Buffer + +const secp256k1 = require('../src') +const crypto = require('../src/crypto') +const libp2pCrypto = require('libp2p-crypto') +const pbm = libp2pCrypto.protobuf +const randomBytes = libp2pCrypto.randomBytes + +describe('secp256k1 keys', () => { + let key + before((done) => { + secp256k1.generateKeyPair((err, _key) => { + if (err) return done(err) + key = _key + done() + }) + }) + + it('generates a valid key', (done) => { + expect( + key + ).to.be.an.instanceof( + secp256k1.Secp256k1PrivateKey + ) + expect( + key.public + ).to.be.an.instanceof( + secp256k1.Secp256k1PublicKey + ) + + key.hash((err, digest) => { + if (err) return done(err) + + expect(digest).to.have.length(34) + + key.public.hash((err, digest) => { + if (err) return done(err) + + expect(digest).to.have.length(34) + done() + }) + }) + }) + + it('optionally accepts a `bits` argument when generating a key', (done) => { + secp256k1.generateKeyPair(256, (err, _key) => { + expect(err).to.not.exist + expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) + done() + }) + }) + + it('requires a callback to generate a key', (done) => { + expect(() => + secp256k1.generateKeyPair() + ).to.throw() + done() + }) + + it('signs', (done) => { + const text = randomBytes(512) + + key.sign(text, (err, sig) => { + if (err) { + return done(err) + } + + key.public.verify(text, sig, (err, res) => { + if (err) { + return done(err) + } + + expect(res).to.be.eql(true) + done() + }) + }) + }) + + it('encoding', (done) => { + const keyMarshal = key.marshal() + secp256k1.unmarshalSecp256k1PrivateKey(keyMarshal, (err, key2) => { + if (err) { + return done(err) + } + const keyMarshal2 = key2.marshal() + + expect( + keyMarshal + ).to.be.eql( + keyMarshal2 + ) + + const pk = key.public + const pkMarshal = pk.marshal() + const pk2 = secp256k1.unmarshalSecp256k1PublicKey(pkMarshal) + const pkMarshal2 = pk2.marshal() + + expect( + pkMarshal + ).to.be.eql( + pkMarshal2 + ) + done() + }) + }) + + describe('key equals', () => { + it('equals itself', () => { + expect( + key.equals(key) + ).to.be.eql( + true + ) + + expect( + key.public.equals(key.public) + ).to.be.eql( + true + ) + }) + + it('not equals other key', (done) => { + secp256k1.generateKeyPair(256, (err, key2) => { + if (err) return done(err) + + expect( + key.equals(key2) + ).to.be.eql( + false + ) + + expect( + key2.equals(key) + ).to.be.eql( + false + ) + + expect( + key.public.equals(key2.public) + ).to.be.eql( + false + ) + + expect( + key2.public.equals(key.public) + ).to.be.eql( + false + ) + done() + }) + }) + }) + + it('sign and verify', (done) => { + const data = Buffer.from('hello world') + key.sign(data, (err, sig) => { + if (err) { + return done(err) + } + + key.public.verify(data, sig, (err, valid) => { + if (err) { + return done(err) + } + expect(valid).to.be.eql(true) + done() + }) + }) + }) + + it('fails to verify for different data', (done) => { + const data = Buffer.from('hello world') + key.sign(data, (err, sig) => { + if (err) { + return done(err) + } + + key.public.verify(Buffer.from('hello'), sig, (err, valid) => { + if (err) { + return done(err) + } + expect(valid).to.be.eql(false) + done() + }) + }) + }) +}) + +describe('key generation error', () => { + let generateKey + + before((done) => { + generateKey = crypto.generateKey + crypto.generateKey = (callback) => { callback(new Error('Error generating key')) } + done() + }) + + after((done) => { + crypto.generateKey = generateKey + done() + }) + + it('returns an error if key generation fails', (done) => { + secp256k1.generateKeyPair((err, key) => { + expect(err).to.exist + expect(key).to.not.exist + done() + }) + }) +}) + +describe('handles generation of invalid key', () => { + let generateKey + + before((done) => { + generateKey = crypto.generateKey + crypto.generateKey = (callback) => { callback(null, Buffer.from('not a real key')) } + done() + }) + + after((done) => { + crypto.generateKey = generateKey + done() + }) + + it('returns an error if key generator returns an invalid key', (done) => { + secp256k1.generateKeyPair((err, key) => { + expect(err).to.exist + expect(key).to.not.exist + done() + }) + }) +}) + +describe('crypto functions', () => { + let privKey, pubKey + + before((done) => { + crypto.generateKey((err, _key) => { + if (err) return done(err) + privKey = _key + pubKey = crypto.computePublicKey(privKey) + done() + }) + }) + + it('generates valid keys', (done) => { + expect(() => { + crypto.validatePrivateKey(privKey) + crypto.validatePublicKey(pubKey) + }).to.not.throw() + done() + }) + + it('does not validate an invalid key', (done) => { + expect(() => { + crypto.validatePublicKey(Buffer.from('42')) + }).to.throw() + + expect(() => { + crypto.validatePrivateKey(Buffer.from('42')) + }).to.throw() + done() + }) + + it('validates a correct signature', (done) => { + crypto.hashAndSign(privKey, Buffer.from('hello'), (err, sig) => { + if (err) return done(err) + crypto.hashAndVerify(pubKey, sig, Buffer.from('hello'), (err, valid) => { + if (err) return done(err) + expect(valid).to.be.eql(true) + done() + }) + }) + }) + + it('errors if given a null buffer to sign', (done) => { + crypto.hashAndSign(privKey, null, (err, sig) => { + expect(err).to.exist + expect(sig).to.not.exist + done() + }) + }) + + it('errors when signing with an invalid key', (done) => { + crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello'), (err, sig) => { + expect(err).to.exist + expect(sig).to.not.exist + done() + }) + }) + + it('errors if given a null buffer to validate', (done) => { + crypto.hashAndSign(privKey, Buffer.from('hello'), (err, sig) => { + if (err) return done(err) + + crypto.hashAndVerify(privKey, sig, null, (err, valid) => { + expect(err).to.exist + expect(valid).to.not.exist + done() + }) + }) + }) + + it('errors when validating a message with an invalid signature', (done) => { + crypto.hashAndVerify(pubKey, Buffer.from('invalid-sig'), Buffer.from('hello'), (err, valid) => { + expect(err).to.exist + expect(valid).to.not.exist + done() + }) + }) + + it('errors when signing with an invalid key', (done) => { + crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello'), (err, sig) => { + expect(err).to.exist + expect(sig).to.not.exist + done() + }) + }) + + it('throws when compressing an invalid public key', (done) => { + expect(() => { + crypto.compressPublicKey(Buffer.from('42')) + }).to.throw() + done() + }) + + it('throws when decompressing an invalid public key', (done) => { + expect(() => { + crypto.decompressPublicKey(Buffer.from('42')) + }).to.throw() + done() + }) + + it('compresses/decompresses a valid public key', (done) => { + const decompressed = crypto.decompressPublicKey(pubKey) + expect(decompressed).to.exist + expect(decompressed.length).to.be.eql(65) + const recompressed = crypto.compressPublicKey(decompressed) + expect(recompressed).to.be.eql(pubKey) + done() + }) +}) + +describe('go interop', () => { + const fixtures = require('./fixtures/go-interop') + + it('loads a private key marshaled by go-libp2p-crypto', (done) => { + // we need to first extract the key data from the protobuf, which is + // normally handled by js-libp2p-crypto + const decoded = pbm.PrivateKey.decode(fixtures.privateKey) + expect(decoded.Type).to.be.eql(pbm.KeyType.Secp256k1) + + secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data, (err, key) => { + if (err) return done(err) + + expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) + expect(key.bytes).to.be.eql(fixtures.privateKey) + done() + }) + }) + + it('loads a public key marshaled by go-libp2p-crypto', (done) => { + const decoded = pbm.PublicKey.decode(fixtures.publicKey) + expect(decoded.Type).to.be.eql(pbm.KeyType.Secp256k1) + + const key = secp256k1.unmarshalSecp256k1PublicKey(decoded.Data) + expect(key).to.be.an.instanceof(secp256k1.Secp256k1PublicKey) + expect(key.bytes).to.be.eql(fixtures.publicKey) + done() + }) + + it('generates the same signature as go-libp2p-crypto', (done) => { + const decoded = pbm.PrivateKey.decode(fixtures.privateKey) + expect(decoded.Type).to.be.eql(pbm.KeyType.Secp256k1) + + secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data, (err, key) => { + if (err) return done(err) + + key.sign(fixtures.message, (err, sig) => { + if (err) return done(err) + expect(sig).to.be.eql(fixtures.signature) + done() + }) + }) + }) +})