diff --git a/src/crypto/ed25519.js b/src/crypto/ed25519.js index c26bc9f..b46c1d2 100644 --- a/src/crypto/ed25519.js +++ b/src/crypto/ed25519.js @@ -22,6 +22,22 @@ exports.generateKey = function (callback) { done(null, keys) } +// seed should be a 32 byte uint8array +exports.generateKeyFromSeed = function (seed, callback) { + const done = (err, res) => setImmediate(() => { + callback(err, res) + }) + + let keys + try { + keys = nacl.sign.keyPair.fromSeed(seed) + } catch (err) { + done(err) + return + } + done(null, keys) +} + exports.hashAndSign = function (key, msg, callback) { setImmediate(() => { callback(null, Buffer.from(nacl.sign.detached(msg, key))) diff --git a/src/index.js b/src/index.js index 380ecdd..9bf59a2 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,19 @@ exports.generateKeyPair = (type, bits, cb) => { key.generateKeyPair(bits, cb) } +// Generates a keypair of the given type and bitsize +// seed is a 32 byte uint8array +exports.generateKeyPairFromSeed = (type, seed, bits, cb) => { + let key = keys[type.toLowerCase()] + if (!key) { + return cb(new Error('invalid or unsupported key type')) + } + if (type.toLowerCase() !== 'ed25519') { + return cb(new Error('Seed key derivation is unimplemented for RSA or secp256k1')) + } + key.generateKeyPairFromSeed(seed, bits, cb) +} + // Converts a protobuf serialized public key into its // representative object exports.unmarshalPublicKey = (buf) => { diff --git a/src/keys/ed25519.js b/src/keys/ed25519.js index d6b322d..19cafb3 100644 --- a/src/keys/ed25519.js +++ b/src/keys/ed25519.js @@ -117,6 +117,27 @@ function generateKeyPair (_bits, cb) { }) } +function generateKeyPairFromSeed (seed, _bits, cb) { + if (cb === undefined && typeof _bits === 'function') { + cb = _bits + } + + crypto.generateKeyFromSeed(seed, (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') @@ -138,5 +159,6 @@ module.exports = { Ed25519PrivateKey, unmarshalEd25519PrivateKey, unmarshalEd25519PublicKey, - generateKeyPair + generateKeyPair, + generateKeyPairFromSeed } diff --git a/test/ed25519.spec.js b/test/ed25519.spec.js index 289762d..91f6004 100644 --- a/test/ed25519.spec.js +++ b/test/ed25519.spec.js @@ -35,6 +35,70 @@ describe('ed25519', () => { }) }) + it('generates a valid key from seed', (done) => { + var seed = crypto.randomBytes(32) + crypto.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey) => { + if (err) return done(err) + expect( + seededkey + ).to.be.an.instanceof( + ed25519.Ed25519PrivateKey + ) + + seededkey.hash((err, digest) => { + if (err) { + return done(err) + } + + expect(digest).to.have.length(34) + done() + }) + }) + }) + + it('generates the same key from the same seed', (done) => { + var seed = crypto.randomBytes(32) + crypto.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey1) => { + if (err) return done(err) + crypto.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey2) => { + if (err) return done(err) + expect( + seededkey1.equals(seededkey2) + ).to.be.eql( + true + ) + expect( + seededkey1.public.equals(seededkey2.public) + ).to.be.eql( + true + ) + done() + }) + }) + }) + + it('generates different keys for different seeds', (done) => { + var seed1 = crypto.randomBytes(32) + crypto.generateKeyPairFromSeed('Ed25519', seed1, 512, (err, seededkey1) => { + if (err) return done(err) + var seed2 = crypto.randomBytes(32) + crypto.generateKeyPairFromSeed('Ed25519', seed2, 512, (err, seededkey2) => { + if (err) return done(err) + expect( + seededkey1.equals(seededkey2) + ).to.be.eql( + false + ) + expect( + seededkey1.public.equals(seededkey2.public) + ).to.be.eql( + false + ) + done() + }) + }) + }) + it('signs', (done) => { const text = crypto.randomBytes(512)