diff --git a/package.json b/package.json index 484bed6..143070f 100644 --- a/package.json +++ b/package.json @@ -33,20 +33,22 @@ "IPFS", "libp2p", "crypto", - "rsa" + "rsa", + "secp256k1" ], "license": "MIT", "dependencies": { "buffer": "^5.5.0", "err-code": "^2.0.0", + "is-typedarray": "^1.0.0", "iso-random-stream": "^1.1.0", "keypair": "^1.0.1", - "libp2p-crypto-secp256k1": "^0.4.2", "multibase": "^0.7.0", "multihashing-async": "^0.8.1", "node-forge": "~0.9.1", "pem-jwk": "^2.0.0", "protons": "^1.0.1", + "secp256k1": "^4.0.0", "ursa-optional": "~0.10.1" }, "devDependencies": { diff --git a/secp256k1/.gitignore b/secp256k1/.gitignore deleted file mode 100644 index f99b5ca..0000000 --- a/secp256k1/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -**/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 - -dist -docs -package-lock.json -yarn.lock -.vscode diff --git a/secp256k1/.npmignore b/secp256k1/.npmignore deleted file mode 100644 index 59335fd..0000000 --- a/secp256k1/.npmignore +++ /dev/null @@ -1,34 +0,0 @@ -**/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/secp256k1/.travis.yml b/secp256k1/.travis.yml deleted file mode 100644 index cb1d170..0000000 --- a/secp256k1/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ - -language: node_js - -cache: npm - -stages: - - check - - test - - cov - -node_js: - - '10' - - '12' - -os: - - linux - - osx - - windows - -script: npx nyc -s npm run test:node -- --bail -after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov - -jobs: - include: - - stage: check - script: - - npx aegir dep-check - - npm run lint - - - stage: test - name: chrome - addons: - chrome: stable - script: - - npx aegir test -t browser - - - stage: test - name: firefox - addons: - firefox: latest - script: - - npx aegir test -t browser -- --browsers FirefoxHeadless - -notifications: - email: false \ No newline at end of file diff --git a/secp256k1/CHANGELOG.md b/secp256k1/CHANGELOG.md deleted file mode 100644 index 4dd03a4..0000000 --- a/secp256k1/CHANGELOG.md +++ /dev/null @@ -1,134 +0,0 @@ - -## [0.4.3](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.4.2...v0.4.3) (2020-03-25) - - - - -## [0.4.2](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.4.1...v0.4.2) (2020-03-17) - - -### Bug Fixes - -* add buffer and update deps ([#25](https://github.com/libp2p/js-libp2p-crypto-secp256k1/issues/25)) ([35f196e](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/35f196e)) - - - - -## [0.4.1](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.4.0...v0.4.1) (2020-01-06) - - - - -# [0.4.0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.3.1...v0.4.0) (2019-07-10) - - -### Features - -* use async await ([#18](https://github.com/libp2p/js-libp2p-crypto-secp256k1/issues/18)) ([1974eb9](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/1974eb9)) - - -### BREAKING CHANGES - -* Callback support has been dropped in favor of async/await. - -* feat: use async/await - -This PR changes this module to remove callbacks and use async/await. The API is unchanged aside from the obvious removal of the `callback` parameter. - -refs https://github.com/ipfs/js-ipfs/issues/1670 - -* fix: use latest multihashing-async as it is all promises now - - - - -## [0.3.1](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.2.2...v0.3.1) (2019-07-10) - - -### Bug Fixes - -* update deps and repo setup ([cfdcbe0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/cfdcbe0)) -* **unmarshal:** provide only one arg to callback ([#17](https://github.com/libp2p/js-libp2p-crypto-secp256k1/issues/17)) ([3bb8451](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/3bb8451)) - - -### Features - -* add `id()` method to Secp256k1PrivateKey ([f4dbd62](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/f4dbd62)) - - - - -# [0.3.0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.2.3...v0.3.0) (2019-02-20) - - -### Features - -* add `id()` method to Secp256k1PrivateKey ([f4dbd62](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/f4dbd62)) - - - - -## [0.2.3](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.2.2...v0.2.3) (2019-01-08) - - -### Bug Fixes - -* update deps and repo setup ([cfdcbe0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/cfdcbe0)) - - - - -## [0.2.2](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.2.1...v0.2.2) (2017-07-22) - - -### Bug Fixes - -* circular circular dep -> DI ([0dcf1a6](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/0dcf1a6)) - - - - -## [0.2.1](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.2.0...v0.2.1) (2017-07-22) - - - - -# [0.2.0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.1.4...v0.2.0) (2017-07-22) - - -### Features - -* next libp2p-crypto ([#4](https://github.com/libp2p/js-libp2p-crypto-secp256k1/issues/4)) ([4ee48a7](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/4ee48a7)) - - - - -## [0.1.4](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.1.3...v0.1.4) (2017-02-12) - - - - -## [0.1.3](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.1.2...v0.1.3) (2017-02-11) - - - - -## [0.1.2](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.1.1...v0.1.2) (2017-02-09) - - - - -## [0.1.1](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/v0.1.0...v0.1.1) (2017-02-09) - - - - -# [0.1.0](https://github.com/libp2p/js-libp2p-crypto-secp256k1/compare/4c36aeb...v0.1.0) (2017-02-09) - - -### Features - -* initial implementation ([4c36aeb](https://github.com/libp2p/js-libp2p-crypto-secp256k1/commit/4c36aeb)) - - - diff --git a/secp256k1/LICENSE b/secp256k1/LICENSE deleted file mode 100644 index bbfffbf..0000000 --- a/secp256k1/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 libp2p - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/secp256k1/README.md b/secp256k1/README.md deleted file mode 100644 index 7063200..0000000 --- a/secp256k1/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# js-libp2p-crypto-secp256k1 - -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) -[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) -[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) -[![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1) -[![](https://img.shields.io/travis/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1) -[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1) -[![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. - -## Lead Captain - -[Friedel Ziegelmayer](https://github.com/dignifiedquire/) - -## Table of Contents - -- [Install](#install) -- [Usage](#usage) - - [Example](#example) -- [API](#api) - - [`generateKeyPair([bits])`](#generatekeypairbits) - - [`unmarshalSecp256k1PublicKey(bytes)`](#unmarshalsecp256k1publickeybytes) - - [`unmarshalSecp256k1PrivateKey(bytes)`](#unmarshalsecp256k1privatekeybytes) - - [`Secp256k1PublicKey`](#secp256k1publickey) - - [`.verify(data, sig)`](#verifydata-sig) - - [`Secp256k1PrivateKey`](#secp256k1privatekey) - - [`.public`](#public) - - [`.sign(data)`](#signdata) -- [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') - -const key = await crypto.generateKeyPair('secp256k1', 256) -// assuming no error, key will be an instance of Secp256k1PrivateKey -// the public key is available as key.public -const sig = await key.sign(msg) - -const valid = await key.public.verify(msg, sig) -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])` -- `bits: Number` - Optional, included for compatibility with js-libp2p-crypto. Ignored if present; private keys will always be 256 bits. - -Returns `Promise` - -### `unmarshalSecp256k1PublicKey(bytes)` -- `bytes: Buffer` - -Converts a serialized secp256k1 public key into an instance of `Secp256k1PublicKey` and returns it - -### `unmarshalSecp256k1PrivateKey(bytes)` -- `bytes: Buffer` - -Returns `Promise` - -Converts a serialized secp256k1 private key into an instance of `Secp256k1PrivateKey`. - -### `Secp256k1PublicKey` - -#### `.verify(data, sig)` -- `data: Buffer` -- `sig: Buffer` - -Returns `Promise` - -Calculates the SHA-256 hash of `data`, and verifies the DER-encoded signature in `sig`. - -### `Secp256k1PrivateKey` - -#### `.public` - -Accessor for the `Secp256k1PublicKey` associated with this private key. - -#### `.sign(data)` -- `data: Buffer` - -Returns `Promise` - -Calculates the SHA-256 hash of `data` and signs it, resolves with the DER-encoded signature. - -## Contribute - -Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-crypto-secp256k1/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/secp256k1/package.json b/secp256k1/package.json deleted file mode 100644 index 0a41bd6..0000000 --- a/secp256k1/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "libp2p-crypto-secp256k1", - "version": "0.4.3", - "description": "Support for secp256k1 keys in libp2p-crypto", - "leadMaintainer": "Friedel Ziegelmayer ", - "main": "src/index.js", - "scripts": { - "lint": "aegir lint", - "build": "aegir build", - "test": "aegir test -t node -t browser", - "test:node": "aegir test -t node", - "test:browser": "aegir test -t 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" - ], - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "is-typedarray": "^1.0.0", - "multibase": "^0.7.0", - "multihashing-async": "^0.8.1", - "secp256k1": "^4.0.0" - }, - "devDependencies": { - "aegir": "^21.0.2", - "benchmark": "^2.1.4", - "chai": "^4.2.0", - "dirty-chai": "^2.0.1", - "libp2p-crypto": "~0.17.2", - "protons": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0", - "npm": ">=3.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 ", - "Jacob Heun ", - "Friedel Ziegelmayer ", - "Hugo Dias ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Yusef Napora ", - "Alan Shaw ", - "Alberto Elias ", - "Alex Potsides ", - "Arve Knudsen ", - "Vasco Santos " - ] -} diff --git a/secp256k1/test/secp256k1.spec.js b/secp256k1/test/secp256k1.spec.js deleted file mode 100644 index 0863ca4..0000000 --- a/secp256k1/test/secp256k1.spec.js +++ /dev/null @@ -1,268 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const { Buffer } = require('buffer') -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const protobuf = require('protons') -const keysPBM = protobuf(require('libp2p-crypto/src/keys/keys.proto')) -const randomBytes = require('libp2p-crypto/src/random-bytes') -const crypto = require('../src/crypto')(randomBytes) - -describe('secp256k1 keys', () => { - let key - const secp256k1 = require('../src')(keysPBM, randomBytes) - - before(async () => { - key = await secp256k1.generateKeyPair() - }) - - it('generates a valid key', async () => { - expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) - expect(key.public).to.be.an.instanceof(secp256k1.Secp256k1PublicKey) - - const digest = await key.hash() - expect(digest).to.have.length(34) - - const publicDigest = await key.public.hash() - expect(publicDigest).to.have.length(34) - }) - - it('optionally accepts a `bits` argument when generating a key', async () => { - const _key = await secp256k1.generateKeyPair(256) - expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) - }) - - it('signs', async () => { - const text = randomBytes(512) - const sig = await key.sign(text) - const res = await key.public.verify(text, sig) - expect(res).to.equal(true) - }) - - it('encoding', async () => { - const keyMarshal = key.marshal() - const key2 = await secp256k1.unmarshalSecp256k1PrivateKey(keyMarshal) - const keyMarshal2 = key2.marshal() - - expect(keyMarshal).to.eql(keyMarshal2) - - const pk = key.public - const pkMarshal = pk.marshal() - const pk2 = secp256k1.unmarshalSecp256k1PublicKey(pkMarshal) - const pkMarshal2 = pk2.marshal() - - expect(pkMarshal).to.eql(pkMarshal2) - }) - - it('key id', async () => { - const id = await key.id() - expect(id).to.exist() - expect(id).to.be.a('string') - }) - - describe('key equals', () => { - it('equals itself', () => { - expect(key.equals(key)).to.eql(true) - - expect(key.public.equals(key.public)).to.eql(true) - }) - - it('not equals other key', async () => { - const key2 = await secp256k1.generateKeyPair(256) - expect(key.equals(key2)).to.eql(false) - expect(key2.equals(key)).to.eql(false) - expect(key.public.equals(key2.public)).to.eql(false) - expect(key2.public.equals(key.public)).to.eql(false) - }) - }) - - it('sign and verify', async () => { - const data = Buffer.from('hello world') - const sig = await key.sign(data) - const valid = await key.public.verify(data, sig) - expect(valid).to.eql(true) - }) - - it('fails to verify for different data', async () => { - const data = Buffer.from('hello world') - const sig = await key.sign(data) - const valid = await key.public.verify(Buffer.from('hello'), sig) - expect(valid).to.eql(false) - }) -}) - -describe('key generation error', () => { - let generateKey - let secp256k1 - - before(() => { - generateKey = crypto.generateKey - crypto.generateKey = () => { throw new Error('Error generating key') } - secp256k1 = require('../src')(keysPBM, randomBytes, crypto) - }) - - after(() => { - crypto.generateKey = generateKey - }) - - it('returns an error if key generation fails', async () => { - try { - await secp256k1.generateKeyPair() - } catch (err) { - return expect(err.message).to.equal('Error generating key') - } - throw new Error('Expected error to be thrown') - }) -}) - -describe('handles generation of invalid key', () => { - let generateKey - let secp256k1 - - before(() => { - generateKey = crypto.generateKey - crypto.generateKey = () => Buffer.from('not a real key') - secp256k1 = require('../src')(keysPBM, randomBytes, crypto) - }) - - after(() => { - crypto.generateKey = generateKey - }) - - it('returns an error if key generator returns an invalid key', async () => { - try { - await secp256k1.generateKeyPair() - } catch (err) { - return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') - } - throw new Error('Expected error to be thrown') - }) -}) - -describe('crypto functions', () => { - let privKey - let pubKey - - before(async () => { - privKey = await crypto.generateKey() - pubKey = crypto.computePublicKey(privKey) - }) - - it('generates valid keys', () => { - expect(() => { - crypto.validatePrivateKey(privKey) - crypto.validatePublicKey(pubKey) - }).to.not.throw() - }) - - it('does not validate an invalid key', () => { - expect(() => crypto.validatePublicKey(Buffer.from('42'))).to.throw() - expect(() => crypto.validatePrivateKey(Buffer.from('42'))).to.throw() - }) - - it('validates a correct signature', async () => { - const sig = await crypto.hashAndSign(privKey, Buffer.from('hello')) - const valid = await crypto.hashAndVerify(pubKey, sig, Buffer.from('hello')) - expect(valid).to.equal(true) - }) - - it('errors if given a null buffer to sign', async () => { - try { - await crypto.hashAndSign(privKey, null) - } catch (err) { - return // expected - } - throw new Error('Expected error to be thrown') - }) - - it('errors when signing with an invalid key', async () => { - try { - await crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello')) - } catch (err) { - return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') - } - throw new Error('Expected error to be thrown') - }) - - it('errors if given a null buffer to validate', async () => { - const sig = await crypto.hashAndSign(privKey, Buffer.from('hello')) - - try { - await crypto.hashAndVerify(privKey, sig, null) - } catch (err) { - return // expected - } - throw new Error('Expected error to be thrown') - }) - - it('errors when validating a message with an invalid signature', async () => { - try { - await crypto.hashAndVerify(pubKey, Buffer.from('invalid-sig'), Buffer.from('hello')) - } catch (err) { - return expect(err.message).to.equal('Signature could not be parsed') - } - throw new Error('Expected error to be thrown') - }) - - it('errors when signing with an invalid key', async () => { - try { - await crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello')) - } catch (err) { - return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') - } - throw new Error('Expected error to be thrown') - }) - - it('throws when compressing an invalid public key', () => { - expect(() => crypto.compressPublicKey(Buffer.from('42'))).to.throw() - }) - - it('throws when decompressing an invalid public key', () => { - expect(() => crypto.decompressPublicKey(Buffer.from('42'))).to.throw() - }) - - it('compresses/decompresses a valid public key', () => { - const decompressed = crypto.decompressPublicKey(pubKey) - expect(decompressed).to.exist() - expect(decompressed.length).to.be.eql(65) - const recompressed = crypto.compressPublicKey(decompressed) - expect(recompressed).to.eql(pubKey) - }) -}) - -describe('go interop', () => { - const secp256k1 = require('../src')(keysPBM, randomBytes) - const fixtures = require('./fixtures/go-interop') - - it('loads a private key marshaled by go-libp2p-crypto', async () => { - // we need to first extract the key data from the protobuf, which is - // normally handled by js-libp2p-crypto - const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey) - expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1) - - const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data) - expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) - expect(key.bytes).to.eql(fixtures.privateKey) - }) - - it('loads a public key marshaled by go-libp2p-crypto', () => { - const decoded = keysPBM.PublicKey.decode(fixtures.publicKey) - expect(decoded.Type).to.be.eql(keysPBM.KeyType.Secp256k1) - - const key = secp256k1.unmarshalSecp256k1PublicKey(decoded.Data) - expect(key).to.be.an.instanceof(secp256k1.Secp256k1PublicKey) - expect(key.bytes).to.eql(fixtures.publicKey) - }) - - it('generates the same signature as go-libp2p-crypto', async () => { - const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey) - expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1) - - const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data) - const sig = await key.sign(fixtures.message) - expect(sig).to.eql(fixtures.signature) - }) -}) diff --git a/src/keys/index.js b/src/keys/index.js index a05d8cc..ad92cd1 100644 --- a/src/keys/index.js +++ b/src/keys/index.js @@ -13,7 +13,7 @@ exports = module.exports const supportedKeys = { rsa: require('./rsa-class'), ed25519: require('./ed25519-class'), - secp256k1: require('libp2p-crypto-secp256k1')(keysPBM, require('../random-bytes')) + secp256k1: require('./secp256k1-class')(keysPBM, require('../random-bytes')) } exports.supportedKeys = supportedKeys diff --git a/secp256k1/src/index.js b/src/keys/secp256k1-class.js similarity index 97% rename from secp256k1/src/index.js rename to src/keys/secp256k1-class.js index 13ebfe8..e8c5390 100644 --- a/secp256k1/src/index.js +++ b/src/keys/secp256k1-class.js @@ -4,7 +4,7 @@ const multibase = require('multibase') const sha = require('multihashing-async/src/sha') module.exports = (keysProtobuf, randomBytes, crypto) => { - crypto = crypto || require('./crypto')(randomBytes) + crypto = crypto || require('./secp256k1')(randomBytes) class Secp256k1PublicKey { constructor (key) { diff --git a/secp256k1/src/crypto.js b/src/keys/secp256k1.js similarity index 100% rename from secp256k1/src/crypto.js rename to src/keys/secp256k1.js diff --git a/secp256k1/test/fixtures/go-interop.js b/test/fixtures/go-key-secp256k1.js similarity index 100% rename from secp256k1/test/fixtures/go-interop.js rename to test/fixtures/go-key-secp256k1.js diff --git a/test/keys/secp256k1.spec.js b/test/keys/secp256k1.spec.js index 2f17613..da245ab 100644 --- a/test/keys/secp256k1.spec.js +++ b/test/keys/secp256k1.spec.js @@ -1,81 +1,267 @@ /* eslint-env mocha */ 'use strict' +const { Buffer } = require('buffer') const chai = require('chai') const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const sinon = require('sinon') - -const fixtures = require('../fixtures/secp256k1') const crypto = require('../../src') +const secp256k1 = crypto.keys.supportedKeys.secp256k1 +const keysPBM = crypto.keys.keysPBM +const randomBytes = crypto.randomBytes +const secp256k1Crypto = require('../../src/keys/secp256k1')(randomBytes) -describe('without libp2p-crypto-secp256k1 module present', () => { - before(() => { - const empty = null - sinon.replace(crypto.keys.supportedKeys, 'secp256k1', empty) +describe('secp256k1 keys', () => { + let key + + before(async () => { + key = await secp256k1.generateKeyPair() }) - after(() => { - sinon.restore() - }) - - it('fails to generate a secp256k1 key', async () => { - try { - await crypto.keys.generateKeyPair('secp256k1', 256) - } catch (err) { - return // expected - } - throw new Error('Expected error to be thrown') - }) - - it('fails to unmarshal a secp256k1 private key', async () => { - try { - await crypto.keys.unmarshalPrivateKey(fixtures.pbmPrivateKey) - } catch (err) { - return // expected - } - throw new Error('Expected error to be thrown') - }) - - it('fails to unmarshal a secp256k1 public key', () => { - expect(() => { - crypto.keys.unmarshalPublicKey(fixtures.pbmPublicKey) - }).to.throw(Error) - }) -}) - -describe('with libp2p-crypto-secp256k1 module present', () => { it('generates a valid key', async () => { - const key = await crypto.keys.generateKeyPair('secp256k1', 256) - expect(key).to.exist() + expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) + expect(key.public).to.be.an.instanceof(secp256k1.Secp256k1PublicKey) + + const digest = await key.hash() + expect(digest).to.have.length(34) + + const publicDigest = await key.public.hash() + expect(publicDigest).to.have.length(34) }) - it('protobuf encoding', async () => { - const key = await crypto.keys.generateKeyPair('secp256k1', 256) - expect(key).to.exist() + it('optionally accepts a `bits` argument when generating a key', async () => { + const _key = await secp256k1.generateKeyPair(256) + expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) + }) - const keyMarshal = crypto.keys.marshalPrivateKey(key) - const key2 = await crypto.keys.unmarshalPrivateKey(keyMarshal) - const keyMarshal2 = crypto.keys.marshalPrivateKey(key2) + it('signs', async () => { + const text = randomBytes(512) + const sig = await key.sign(text) + const res = await key.public.verify(text, sig) + expect(res).to.equal(true) + }) + + it('encoding', async () => { + const keyMarshal = key.marshal() + const key2 = await secp256k1.unmarshalSecp256k1PrivateKey(keyMarshal) + const keyMarshal2 = key2.marshal() expect(keyMarshal).to.eql(keyMarshal2) const pk = key.public - const pkMarshal = crypto.keys.marshalPublicKey(pk) - const pk2 = crypto.keys.unmarshalPublicKey(pkMarshal) - const pkMarshal2 = crypto.keys.marshalPublicKey(pk2) + const pkMarshal = pk.marshal() + const pk2 = secp256k1.unmarshalSecp256k1PublicKey(pkMarshal) + const pkMarshal2 = pk2.marshal() expect(pkMarshal).to.eql(pkMarshal2) }) - it('unmarshals a secp256k1 private key', async () => { - const key = await crypto.keys.unmarshalPrivateKey(fixtures.pbmPrivateKey) - expect(key).to.exist() + it('key id', async () => { + const id = await key.id() + expect(id).to.exist() + expect(id).to.be.a('string') }) - it('unmarshals a secp256k1 public key', () => { - const key = crypto.keys.unmarshalPublicKey(fixtures.pbmPublicKey) - expect(key).to.exist() + describe('key equals', () => { + it('equals itself', () => { + expect(key.equals(key)).to.eql(true) + + expect(key.public.equals(key.public)).to.eql(true) + }) + + it('not equals other key', async () => { + const key2 = await secp256k1.generateKeyPair(256) + expect(key.equals(key2)).to.eql(false) + expect(key2.equals(key)).to.eql(false) + expect(key.public.equals(key2.public)).to.eql(false) + expect(key2.public.equals(key.public)).to.eql(false) + }) + }) + + it('sign and verify', async () => { + const data = Buffer.from('hello world') + const sig = await key.sign(data) + const valid = await key.public.verify(data, sig) + expect(valid).to.eql(true) + }) + + it('fails to verify for different data', async () => { + const data = Buffer.from('hello world') + const sig = await key.sign(data) + const valid = await key.public.verify(Buffer.from('hello'), sig) + expect(valid).to.eql(false) + }) +}) + +describe('key generation error', () => { + let generateKey + let secp256k1 + + before(() => { + generateKey = secp256k1Crypto.generateKey + secp256k1 = require('../../src/keys/secp256k1-class')(keysPBM, randomBytes, secp256k1Crypto) + secp256k1Crypto.generateKey = () => { throw new Error('Error generating key') } + }) + + after(() => { + secp256k1Crypto.generateKey = generateKey + }) + + it('returns an error if key generation fails', async () => { + try { + await secp256k1.generateKeyPair() + } catch (err) { + return expect(err.message).to.equal('Error generating key') + } + throw new Error('Expected error to be thrown') + }) +}) + +describe('handles generation of invalid key', () => { + let generateKey + let secp256k1 + + before(() => { + generateKey = secp256k1Crypto.generateKey + secp256k1 = require('../../src/keys/secp256k1-class')(keysPBM, randomBytes, secp256k1Crypto) + secp256k1Crypto.generateKey = () => Buffer.from('not a real key') + }) + + after(() => { + secp256k1Crypto.generateKey = generateKey + }) + + it('returns an error if key generator returns an invalid key', async () => { + try { + await secp256k1.generateKeyPair() + } catch (err) { + return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') + } + throw new Error('Expected error to be thrown') + }) +}) + +describe('crypto functions', () => { + let privKey + let pubKey + + before(async () => { + privKey = await secp256k1Crypto.generateKey() + pubKey = secp256k1Crypto.computePublicKey(privKey) + }) + + it('generates valid keys', () => { + expect(() => { + secp256k1Crypto.validatePrivateKey(privKey) + secp256k1Crypto.validatePublicKey(pubKey) + }).to.not.throw() + }) + + it('does not validate an invalid key', () => { + expect(() => secp256k1Crypto.validatePublicKey(Buffer.from('42'))).to.throw() + expect(() => secp256k1Crypto.validatePrivateKey(Buffer.from('42'))).to.throw() + }) + + it('validates a correct signature', async () => { + const sig = await secp256k1Crypto.hashAndSign(privKey, Buffer.from('hello')) + const valid = await secp256k1Crypto.hashAndVerify(pubKey, sig, Buffer.from('hello')) + expect(valid).to.equal(true) + }) + + it('errors if given a null buffer to sign', async () => { + try { + await secp256k1Crypto.hashAndSign(privKey, null) + } catch (err) { + return // expected + } + throw new Error('Expected error to be thrown') + }) + + it('errors when signing with an invalid key', async () => { + try { + await secp256k1Crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello')) + } catch (err) { + return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') + } + throw new Error('Expected error to be thrown') + }) + + it('errors if given a null buffer to validate', async () => { + const sig = await secp256k1Crypto.hashAndSign(privKey, Buffer.from('hello')) + + try { + await secp256k1Crypto.hashAndVerify(privKey, sig, null) + } catch (err) { + return // expected + } + throw new Error('Expected error to be thrown') + }) + + it('errors when validating a message with an invalid signature', async () => { + try { + await secp256k1Crypto.hashAndVerify(pubKey, Buffer.from('invalid-sig'), Buffer.from('hello')) + } catch (err) { + return expect(err.message).to.equal('Signature could not be parsed') + } + throw new Error('Expected error to be thrown') + }) + + it('errors when signing with an invalid key', async () => { + try { + await secp256k1Crypto.hashAndSign(Buffer.from('42'), Buffer.from('Hello')) + } catch (err) { + return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32') + } + throw new Error('Expected error to be thrown') + }) + + it('throws when compressing an invalid public key', () => { + expect(() => secp256k1Crypto.compressPublicKey(Buffer.from('42'))).to.throw() + }) + + it('throws when decompressing an invalid public key', () => { + expect(() => secp256k1Crypto.decompressPublicKey(Buffer.from('42'))).to.throw() + }) + + it('compresses/decompresses a valid public key', () => { + const decompressed = secp256k1Crypto.decompressPublicKey(pubKey) + expect(decompressed).to.exist() + expect(decompressed.length).to.be.eql(65) + const recompressed = secp256k1Crypto.compressPublicKey(decompressed) + expect(recompressed).to.eql(pubKey) + }) +}) + +describe('go interop', () => { + const fixtures = require('../fixtures/go-key-secp256k1') + + it('loads a private key marshaled by go-libp2p-crypto', async () => { + // we need to first extract the key data from the protobuf, which is + // normally handled by js-libp2p-crypto + const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey) + expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1) + + const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data) + expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey) + expect(key.bytes).to.eql(fixtures.privateKey) + }) + + it('loads a public key marshaled by go-libp2p-crypto', () => { + const decoded = keysPBM.PublicKey.decode(fixtures.publicKey) + expect(decoded.Type).to.be.eql(keysPBM.KeyType.Secp256k1) + + const key = secp256k1.unmarshalSecp256k1PublicKey(decoded.Data) + expect(key).to.be.an.instanceof(secp256k1.Secp256k1PublicKey) + expect(key.bytes).to.eql(fixtures.publicKey) + }) + + it('generates the same signature as go-libp2p-crypto', async () => { + const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey) + expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1) + + const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data) + const sig = await key.sign(fixtures.message) + expect(sig).to.eql(fixtures.signature) }) })