mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 19:50:58 +00:00
feat: initial implementation
This commit is contained in:
parent
e57e4ffa0d
commit
4c36aeba17
23
.gitignore
vendored
23
.gitignore
vendored
@ -1,7 +1,12 @@
|
|||||||
|
**/node_modules/
|
||||||
|
**/*.log
|
||||||
|
test/repo-tests*
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
|
||||||
|
coverage
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
@ -14,24 +19,16 @@ lib-cov
|
|||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
|
|
||||||
# node-waf configuration
|
# node-waf configuration
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
build
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
node_modules
|
node_modules
|
||||||
jspm_packages
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
dist
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
34
.npmignore
Normal file
34
.npmignore
Normal file
@ -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
|
38
.travis.yml
Normal file
38
.travis.yml
Normal file
@ -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
|
118
README.md
118
README.md
@ -1 +1,117 @@
|
|||||||
# js-libp2p-crypto-secp256k1
|
# js-libp2p-crypto
|
||||||
|
|
||||||
|
[](http://ipn.io)
|
||||||
|
[](http://ipfs.io/)
|
||||||
|
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||||
|
[](https://github.com/RichardLitt/standard-readme)
|
||||||
|
[](https://github.com/feross/standard)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
> 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://github.com/ipfs/community/blob/master/contributing.md)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
12
circle.yml
Normal file
12
circle.yml
Normal file
@ -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
|
65
package.json
Normal file
65
package.json
Normal file
@ -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 <yusef@napora.org>",
|
||||||
|
"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 <daviddias.p@gmail.com>",
|
||||||
|
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||||
|
"Greenkeeper <support@greenkeeper.io>",
|
||||||
|
"Richard Littauer <richard.littauer@gmail.com>",
|
||||||
|
"Yusef Napora <yusef@napora.org>",
|
||||||
|
"nikuda <nikuda@gmail.com>"
|
||||||
|
]
|
||||||
|
}
|
85
src/crypto/index.js
Normal file
85
src/crypto/index.js
Normal file
@ -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)
|
||||||
|
}
|
119
src/index.js
Normal file
119
src/index.js
Normal file
@ -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
|
||||||
|
}
|
31
test/fixtures/go-interop.js
vendored
Normal file
31
test/fixtures/go-interop.js
vendored
Normal file
@ -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')
|
||||||
|
}
|
391
test/secp256k1.spec.js
Normal file
391
test/secp256k1.spec.js
Normal file
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user