feat: add support for secp256k1 keys through the libp2p-crypto-secp256k1 module

This commit is contained in:
Yusef Napora 2017-02-04 04:23:38 -05:00 committed by Friedel Ziegelmayer
parent 308ac7cd1a
commit 76eeb5aa18
8 changed files with 166 additions and 10 deletions

View File

@ -107,9 +107,21 @@ Depending on the environment this is either an instance of [node-webcrypto-ossl]
### `keys` ### `keys`
#### Supported Key Types
The [`generateKeyPair`](#generatekeypairtype-bits-callback), [`marshalPublicKey`](#marshalpublickeykey-type-callback), and
[`marshalPrivateKey`](#marshalprivatekeykey-type) functions accept a string `type` argument.
Currently the `'RSA'` and `'ed25519'` types are supported, although ed25519 keys support only signing and
verification of messages. For encryption / decryption support, RSA keys should be used.
Installing the [libp2p-crypto-secp256k1](https://github.com/libp2p/js-libp2p-crypto-secp256k1) module adds support for the
`'secp256k1'` type, which supports ECDSA signatures using the secp256k1 elliptic curve popularized by Bitcoin. This module
is not installed by default, and should be explicitly depended on if your project requires secp256k1 support.
### `generateKeyPair(type, bits, callback)` ### `generateKeyPair(type, bits, callback)`
- `type: String`, only `'RSA'` is currently supported - `type: String`, see [Supported Key Types](#supported-key-types) above.
- `bits: Number` Minimum of 1024 - `bits: Number` Minimum of 1024
- `callback: Function` - `callback: Function`
@ -160,8 +172,8 @@ Calls back with an object of the form
### `marshalPublicKey(key[, type], callback)` ### `marshalPublicKey(key[, type], callback)`
- `key: crypto.rsa.RsaPublicKey` - `key: keys.rsa.RsaPublicKey | keys.ed25519.Ed25519PublicKey | require('libp2p-crypto-secp256k1').Secp256k1PublicKey`
- `type: String`, only `'RSA'` is currently supported - `type: String`, see [Supported Key Types](#supported-key-types) above.
Converts a public key object into a protobuf serialized public key. Converts a public key object into a protobuf serialized public key.
@ -173,8 +185,8 @@ Converts a protobuf serialized public key into its representative object.
### `marshalPrivateKey(key[, type])` ### `marshalPrivateKey(key[, type])`
- `key: crypto.rsa.RsaPrivateKey` - `key: keys.rsa.RsaPrivateKey | keys.ed25519.Ed25519PrivateKey | require('libp2p-crypto-secp256k1').Secp256k1PrivateKey`
- `type: String`, only `'RSA'` is currently supported - `type: String`, see [Supported Key Types](#supported-key-types) above.
Converts a private key object into a protobuf serialized private key. Converts a private key object into a protobuf serialized private key.

View File

@ -80,4 +80,4 @@
"dryajov <dryajov@gmail.com>", "dryajov <dryajov@gmail.com>",
"nikuda <nikuda@gmail.com>" "nikuda <nikuda@gmail.com>"
] ]
} }

View File

@ -3,6 +3,7 @@
module.exports = `enum KeyType { module.exports = `enum KeyType {
RSA = 0; RSA = 0;
Ed25519 = 1; Ed25519 = 1;
Secp256k1 = 2;
} }
message PublicKey { message PublicKey {

View File

@ -10,7 +10,10 @@ exports.aes = c.aes
exports.webcrypto = c.webcrypto exports.webcrypto = c.webcrypto
const keys = exports.keys = require('./keys') const keys = exports.keys = require('./keys')
const KEY_TYPES = ['rsa', 'ed25519'] function isValidKeyType (keyType) {
const key = keys[keyType.toLowerCase()]
return key !== undefined
}
exports.keyStretcher = require('./key-stretcher') exports.keyStretcher = require('./key-stretcher')
exports.generateEphemeralKeyPair = require('./ephemeral-keys') exports.generateEphemeralKeyPair = require('./ephemeral-keys')
@ -35,6 +38,12 @@ exports.unmarshalPublicKey = (buf) => {
return keys.rsa.unmarshalRsaPublicKey(decoded.Data) return keys.rsa.unmarshalRsaPublicKey(decoded.Data)
case pbm.KeyType.Ed25519: case pbm.KeyType.Ed25519:
return keys.ed25519.unmarshalEd25519PublicKey(decoded.Data) return keys.ed25519.unmarshalEd25519PublicKey(decoded.Data)
case pbm.KeyType.Secp256k1:
if (keys.secp256k1) {
return keys.secp256k1.unmarshalSecp256k1PublicKey(decoded.Data)
} else {
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
}
default: default:
throw new Error('invalid or unsupported key type') throw new Error('invalid or unsupported key type')
} }
@ -43,7 +52,7 @@ exports.unmarshalPublicKey = (buf) => {
// Converts a public key object into a protobuf serialized public key // Converts a public key object into a protobuf serialized public key
exports.marshalPublicKey = (key, type) => { exports.marshalPublicKey = (key, type) => {
type = (type || 'rsa').toLowerCase() type = (type || 'rsa').toLowerCase()
if (KEY_TYPES.indexOf(type) < 0) { if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type') throw new Error('invalid or unsupported key type')
} }
@ -60,6 +69,12 @@ exports.unmarshalPrivateKey = (buf, callback) => {
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback) return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback)
case pbm.KeyType.Ed25519: case pbm.KeyType.Ed25519:
return keys.ed25519.unmarshalEd25519PrivateKey(decoded.Data, callback) return keys.ed25519.unmarshalEd25519PrivateKey(decoded.Data, callback)
case pbm.KeyType.Secp256k1:
if (keys.secp256k1) {
return keys.secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data, callback)
} else {
return callback(new Error('secp256k1 support requires libp2p-crypto-secp256k1 package'))
}
default: default:
callback(new Error('invalid or unsupported key type')) callback(new Error('invalid or unsupported key type'))
} }
@ -68,7 +83,7 @@ exports.unmarshalPrivateKey = (buf, callback) => {
// Converts a private key object into a protobuf serialized private key // Converts a private key object into a protobuf serialized private key
exports.marshalPrivateKey = (key, type) => { exports.marshalPrivateKey = (key, type) => {
type = (type || 'rsa').toLowerCase() type = (type || 'rsa').toLowerCase()
if (KEY_TYPES.indexOf(type) < 0) { if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type') throw new Error('invalid or unsupported key type')
} }

View File

@ -4,3 +4,8 @@ module.exports = {
rsa: require('./rsa'), rsa: require('./rsa'),
ed25519: require('./ed25519') ed25519: require('./ed25519')
} }
try {
module.exports.secp256k1 = require('libp2p-crypto-secp256k1')
} catch (err) {
}

11
test/fixtures/secp256k1.js vendored Normal file
View File

@ -0,0 +1,11 @@
'use strict'
const Buffer = require('safe-buffer').Buffer
module.exports = {
// protobuf marshaled key pair generated with libp2p-crypto-secp256k1
// and marshaled with libp2p-crypto.marshalPublicKey / marshalPrivateKey
pbmPrivateKey: Buffer.from('08021220e0600103010000000100000000000000be1dc82c2e000000e8d6030301000000', 'hex'),
pbmPublicKey: Buffer.from('0802122103a9a7272a726fa083abf31ba44037f8347fbc5e5d3113d62a7c6bc26752fd8ee1', 'hex')
}

View File

@ -23,9 +23,17 @@ describe('libp2p-crypto', () => {
const key2 = crypto.unmarshalPublicKey(crypto.marshalPublicKey(key.public)) const key2 = crypto.unmarshalPublicKey(crypto.marshalPublicKey(key.public))
expect(key2.equals(key.public)).to.be.eql(true) expect(key2.equals(key.public)).to.be.eql(true)
expect(() => {
crypto.marshalPublicKey(key.public, 'invalid-key-type')
}).to.throw()
}) })
it('marshalPrivateKey and unmarshalPrivateKey', (done) => { it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
expect(() => {
crypto.marshalPrivateKey(key, 'invalid-key-type')
}).to.throw()
crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key), (err, key2) => { crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key), (err, key2) => {
if (err) { if (err) {
return done(err) return done(err)
@ -103,7 +111,7 @@ describe('libp2p-crypto', () => {
it('throws with no number passed', () => { it('throws with no number passed', () => {
expect(() => { expect(() => {
crypto.randomBytes() crypto.randomBytes()
}).to.throw }).to.throw()
}) })
it('generates different random things', () => { it('generates different random things', () => {

104
test/secp256k1.spec.js Normal file
View File

@ -0,0 +1,104 @@
/* eslint-env mocha */
'use strict'
const expect = require('chai').expect
const fixtures = require('./fixtures/secp256k1')
const crypto = require('../src')
const mockPublicKey = {
bytes: fixtures.pbmPublicKey
}
const mockPrivateKey = {
bytes: fixtures.pbmPrivateKey,
public: mockPublicKey
}
const mockSecp256k1Module = {
generateKeyPair (bits, callback) {
callback(null, mockPrivateKey)
},
unmarshalSecp256k1PrivateKey (buf, callback) {
callback(null, mockPrivateKey)
},
unmarshalSecp256k1PublicKey (buf) {
return mockPublicKey
}
}
describe('with libp2p-crypto-secp256k1 module present', () => {
let key
before((done) => {
crypto.keys['secp256k1'] = mockSecp256k1Module
crypto.generateKeyPair('secp256k1', 256, (err, _key) => {
if (err) return done(err)
key = _key
done()
})
})
after((done) => {
delete crypto.keys['secp256k1']
done()
})
it('generates a valid key', (done) => {
expect(
key
).to.exist
done()
})
it('protobuf encoding', (done) => {
const keyMarshal = crypto.marshalPrivateKey(key)
crypto.unmarshalPrivateKey(keyMarshal, (err, key2) => {
if (err) return done(err)
const keyMarshal2 = crypto.marshalPrivateKey(key2)
expect(
keyMarshal
).to.be.eql(
keyMarshal2
)
const pk = key.public
const pkMarshal = crypto.marshalPublicKey(pk)
const pk2 = crypto.unmarshalPublicKey(pkMarshal)
const pkMarshal2 = crypto.marshalPublicKey(pk2)
expect(
pkMarshal
).to.be.eql(
pkMarshal2
)
done()
})
})
})
describe('without libp2p-crypto-secp256k1 module present', () => {
it('fails to generate a secp256k1 key', (done) => {
crypto.generateKeyPair('secp256k1', 256, (err, key) => {
expect(err).to.exist
expect(key).to.not.exist
done()
})
})
it('fails to unmarshal a secp256k1 private key', (done) => {
crypto.unmarshalPrivateKey(fixtures.pbmPrivateKey, (err, key) => {
expect(err).to.exist
expect(key).to.not.exist
done()
})
})
it('fails to unmarshal a secp256k1 public key', () => {
expect(() => {
crypto.unmarshalPublicKey(fixtures.pbmPublicKey)
}).to.throw(Error)
})
})