mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 08:41:05 +00:00
parent
e92bab1736
commit
c45bdf602e
@ -42,6 +42,7 @@
|
||||
"pem-jwk": "^1.5.1",
|
||||
"protocol-buffers": "^3.2.1",
|
||||
"rsa-pem-to-jwk": "^1.1.3",
|
||||
"tweetnacl": "^0.14.5",
|
||||
"webcrypto-shim": "github:dignifiedquire/webcrypto-shim#master"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -75,4 +76,4 @@
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"nikuda <nikuda@gmail.com>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -5,3 +5,4 @@ exports.hmac = require('./crypto/hmac')
|
||||
exports.ecdh = require('./crypto/ecdh')
|
||||
exports.aes = require('./crypto/aes')
|
||||
exports.rsa = require('./crypto/rsa')
|
||||
exports.ed25519 = require('./crypto/ed25519')
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
module.exports = `enum KeyType {
|
||||
RSA = 0;
|
||||
Ed25519 = 1;
|
||||
}
|
||||
|
||||
message PublicKey {
|
||||
|
34
src/crypto/ed25519.js
Normal file
34
src/crypto/ed25519.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const nacl = require('tweetnacl')
|
||||
const setImmediate = require('async/setImmediate')
|
||||
|
||||
exports.publicKeyLength = nacl.sign.publicKeyLength
|
||||
exports.privateKeyLength = nacl.sign.secretKeyLength
|
||||
|
||||
exports.generateKey = function (callback) {
|
||||
const done = (err, res) => setImmediate(() => {
|
||||
callback(err, res)
|
||||
})
|
||||
|
||||
let keys
|
||||
try {
|
||||
keys = nacl.sign.keyPair()
|
||||
} catch (err) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
done(null, keys)
|
||||
}
|
||||
|
||||
exports.hashAndSign = function (key, msg, callback) {
|
||||
setImmediate(() => {
|
||||
callback(null, Buffer.from(nacl.sign.detached(msg, key)))
|
||||
})
|
||||
}
|
||||
|
||||
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||
setImmediate(() => {
|
||||
callback(null, nacl.sign.detached.verify(msg, sig, key))
|
||||
})
|
||||
}
|
14
src/index.js
14
src/index.js
@ -9,6 +9,8 @@ exports.aes = c.aes
|
||||
exports.webcrypto = c.webcrypto
|
||||
|
||||
const keys = exports.keys = require('./keys')
|
||||
const KEY_TYPES = ['rsa', 'ed25519']
|
||||
|
||||
exports.keyStretcher = require('./key-stretcher')
|
||||
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
||||
|
||||
@ -30,6 +32,8 @@ exports.unmarshalPublicKey = (buf) => {
|
||||
switch (decoded.Type) {
|
||||
case pbm.KeyType.RSA:
|
||||
return keys.rsa.unmarshalRsaPublicKey(decoded.Data)
|
||||
case pbm.KeyType.Ed25519:
|
||||
return keys.ed25519.unmarshalEd25519PublicKey(decoded.Data)
|
||||
default:
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
@ -38,9 +42,7 @@ exports.unmarshalPublicKey = (buf) => {
|
||||
// Converts a public key object into a protobuf serialized public key
|
||||
exports.marshalPublicKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
|
||||
// for now only rsa is supported
|
||||
if (type !== 'rsa') {
|
||||
if (KEY_TYPES.indexOf(type) < 0) {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
@ -55,6 +57,8 @@ exports.unmarshalPrivateKey = (buf, callback) => {
|
||||
switch (decoded.Type) {
|
||||
case pbm.KeyType.RSA:
|
||||
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback)
|
||||
case pbm.KeyType.Ed25519:
|
||||
return keys.ed25519.unmarshalEd25519PrivateKey(decoded.Data, callback)
|
||||
default:
|
||||
callback(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
@ -63,9 +67,7 @@ exports.unmarshalPrivateKey = (buf, callback) => {
|
||||
// Converts a private key object into a protobuf serialized private key
|
||||
exports.marshalPrivateKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
|
||||
// for now only rsa is supported
|
||||
if (type !== 'rsa') {
|
||||
if (KEY_TYPES.indexOf(type) < 0) {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
|
141
src/keys/ed25519.js
Normal file
141
src/keys/ed25519.js
Normal file
@ -0,0 +1,141 @@
|
||||
'use strict'
|
||||
|
||||
const multihashing = require('multihashing-async')
|
||||
const protobuf = require('protocol-buffers')
|
||||
|
||||
const crypto = require('../crypto').ed25519
|
||||
const pbm = protobuf(require('../crypto.proto'))
|
||||
|
||||
class Ed25519PublicKey {
|
||||
constructor (key) {
|
||||
this._key = ensureKey(key, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
verify (data, sig, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndVerify(this._key, sig, data, callback)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return Buffer.from(this._key)
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PublicKey.encode({
|
||||
Type: pbm.KeyType.Ed25519,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
}
|
||||
|
||||
class Ed25519PrivateKey {
|
||||
// key - 64 byte Uint8Array or Buffer containing private key
|
||||
// publicKey - 32 byte Uint8Array or Buffer containing public key
|
||||
constructor (key, publicKey) {
|
||||
this._key = ensureKey(key, crypto.privateKeyLength)
|
||||
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
sign (message, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndSign(this._key, message, callback)
|
||||
}
|
||||
|
||||
get public () {
|
||||
if (!this._publicKey) {
|
||||
throw new Error('public key not provided')
|
||||
}
|
||||
|
||||
return new Ed25519PublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return Buffer.concat([Buffer.from(this._key), Buffer.from(this._publicKey)])
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PrivateKey.encode({
|
||||
Type: pbm.KeyType.Ed25519,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
}
|
||||
|
||||
function unmarshalEd25519PrivateKey (bytes, callback) {
|
||||
try {
|
||||
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
|
||||
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
|
||||
callback(null, new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes))
|
||||
}
|
||||
|
||||
function unmarshalEd25519PublicKey (bytes) {
|
||||
bytes = ensureKey(bytes, crypto.publicKeyLength)
|
||||
return new Ed25519PublicKey(bytes)
|
||||
}
|
||||
|
||||
function generateKeyPair (_bits, cb) {
|
||||
if (cb === undefined && typeof _bits === 'function') {
|
||||
cb = _bits
|
||||
}
|
||||
|
||||
crypto.generateKey((err, keys) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
let privkey
|
||||
try {
|
||||
privkey = new Ed25519PrivateKey(keys.secretKey, keys.publicKey)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
return
|
||||
}
|
||||
|
||||
cb(null, privkey)
|
||||
})
|
||||
}
|
||||
|
||||
function ensure (cb) {
|
||||
if (typeof cb !== 'function') {
|
||||
throw new Error('callback is required')
|
||||
}
|
||||
}
|
||||
|
||||
function ensureKey (key, length) {
|
||||
if (Buffer.isBuffer(key)) {
|
||||
key = new Uint8Array(key)
|
||||
}
|
||||
if (!(key instanceof Uint8Array) || key.length !== length) {
|
||||
throw new Error('Key must be a Uint8Array or Buffer of length ' + length)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Ed25519PublicKey,
|
||||
Ed25519PrivateKey,
|
||||
unmarshalEd25519PrivateKey,
|
||||
unmarshalEd25519PublicKey,
|
||||
generateKeyPair
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
rsa: require('./rsa')
|
||||
rsa: require('./rsa'),
|
||||
ed25519: require('./ed25519')
|
||||
}
|
||||
|
194
test/ed25519.spec.js
Normal file
194
test/ed25519.spec.js
Normal file
@ -0,0 +1,194 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
|
||||
const crypto = require('../src')
|
||||
const ed25519 = crypto.keys.ed25519
|
||||
const fixtures = require('./fixtures/go-key-ed25519')
|
||||
|
||||
describe('ed25519', () => {
|
||||
let key
|
||||
before((done) => {
|
||||
crypto.generateKeyPair('Ed25519', 512, (err, _key) => {
|
||||
if (err) return done(err)
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a valid key', (done) => {
|
||||
expect(
|
||||
key
|
||||
).to.be.an.instanceof(
|
||||
ed25519.Ed25519PrivateKey
|
||||
)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('signs', (done) => {
|
||||
const text = crypto.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()
|
||||
ed25519.unmarshalEd25519PrivateKey(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 = ed25519.unmarshalEd25519PublicKey(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) => {
|
||||
crypto.generateKeyPair('Ed25519', 512, (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 = new Buffer('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 = new Buffer('hello world')
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(new Buffer('hello'), sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
let privateKey
|
||||
before((done) => {
|
||||
crypto.unmarshalPrivateKey(fixtures.verify.privateKey, (err, key) => {
|
||||
expect(err).to.not.exist
|
||||
privateKey = key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('verifies with data from go', (done) => {
|
||||
const key = crypto.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
|
||||
key.verify(fixtures.verify.data, fixtures.verify.signature, (err, ok) => {
|
||||
if (err) throw err
|
||||
expect(err).to.not.exist
|
||||
expect(ok).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates the same signature as go', (done) => {
|
||||
privateKey.sign(fixtures.verify.data, (err, sig) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sig).to.deep.equal(fixtures.verify.signature)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
28
test/fixtures/go-key-ed25519.js
vendored
Normal file
28
test/fixtures/go-key-ed25519.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
// These were generated in a gore (https://github.com/motemen/gore) repl session:
|
||||
//
|
||||
// :import github.com/libp2p/go-libp2p-crypto
|
||||
// :import crypto/rand
|
||||
// priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
// pubkeyBytes, err := pub.Bytes()
|
||||
// privkeyBytes, err := priv.Bytes()
|
||||
// data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
// sig, err := priv.Sign(data)
|
||||
//
|
||||
// :import io/ioutil
|
||||
// ioutil.WriteFile("/tmp/pubkey_go.bin", pubkeyBytes, 0644)
|
||||
// // etc..
|
||||
//
|
||||
// Then loaded into a node repl and dumped to arrays with:
|
||||
//
|
||||
// var pubkey = Array.from(fs.readFileSync('/tmp/pubkey_go.bin'))
|
||||
// console.log(JSON.stringify(pubkey))
|
||||
verify: {
|
||||
privateKey: Buffer.from([8, 1, 18, 96, 201, 208, 1, 110, 176, 16, 230, 37, 66, 184, 149, 252, 78, 56, 206, 136, 2, 38, 118, 152, 226, 197, 117, 200, 54, 189, 156, 218, 184, 7, 118, 57, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
|
||||
publicKey: Buffer.from([8, 1, 18, 32, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
|
||||
data: Buffer.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
|
||||
signature: Buffer.from([7, 230, 175, 164, 228, 58, 78, 208, 62, 243, 73, 142, 83, 195, 176, 217, 166, 62, 41, 165, 168, 164, 75, 179, 163, 86, 102, 32, 18, 84, 150, 237, 39, 207, 213, 20, 134, 237, 50, 41, 176, 183, 229, 133, 38, 255, 42, 228, 68, 186, 100, 14, 175, 156, 243, 118, 125, 125, 120, 212, 124, 103, 252, 12])
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user