From 2f18a077b47ee84c450431f7431ecdfc913c8543 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Sat, 18 Jul 2020 12:44:07 +0200 Subject: [PATCH] fix: go ed25519 interop fixes https://github.com/libp2p/js-libp2p-crypto/issues/175 --- src/keys/ed25519-class.js | 20 +++++++++----- test/fixtures/go-key-ed25519.js | 46 +++++++++++++++++++++------------ test/keys/ed25519.spec.js | 25 ++++++++++-------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/keys/ed25519-class.js b/src/keys/ed25519-class.js index 1cba96f..7136cd7 100644 --- a/src/keys/ed25519-class.js +++ b/src/keys/ed25519-class.js @@ -89,9 +89,17 @@ class Ed25519PrivateKey { } function unmarshalEd25519PrivateKey (bytes) { - bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength) + // Try the old, redundant public key version + if (bytes.length > crypto.privateKeyLength) { + bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength) + const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength) + const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length) + return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes) + } + + bytes = ensureKey(bytes, crypto.privateKeyLength) const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength) - const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length) + const publicKeyBytes = bytes.slice(crypto.publicKeyLength) return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes) } @@ -111,11 +119,9 @@ async function generateKeyPairFromSeed (seed) { } function ensureKey (key, length) { - if (Buffer.isBuffer(key)) { - key = new Uint8Array(key) - } - if (!(key instanceof Uint8Array) || key.length !== length) { - throw errcode(new Error('Key must be a Uint8Array or Buffer of length ' + length), 'ERR_INVALID_KEY_TYPE') + key = Uint8Array.from(key || []) + if (key.length !== length) { + throw errcode(new Error(`Key must be a Uint8Array or Buffer of length ${length}, got ${key.length}`), 'ERR_INVALID_KEY_TYPE') } return key } diff --git a/test/fixtures/go-key-ed25519.js b/test/fixtures/go-key-ed25519.js index 2fc7830..921424d 100644 --- a/test/fixtures/go-key-ed25519.js +++ b/test/fixtures/go-key-ed25519.js @@ -2,28 +2,42 @@ const { Buffer } = require('buffer') module.exports = { - // These were generated in a gore (https://github.com/motemen/gore) repl session: + // Generation code from https://github.com/libp2p/js-libp2p-crypto/issues/175#issuecomment-634467463 // - // :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) + // package main // - // :import io/ioutil - // ioutil.WriteFile("/tmp/pubkey_go.bin", pubkeyBytes, 0644) - // // etc.. + // import ( + // "crypto/rand" + // "fmt" + // "strings" + + // "github.com/libp2p/go-libp2p-core/crypto" + // ) + + // func main() { + // priv, pub, _ := crypto.GenerateEd25519Key(rand.Reader) + // pubkeyBytes, _ := pub.Bytes() + // privkeyBytes, _ := priv.Bytes() + // data := []byte("hello! and welcome to some awesome crypto primitives") + // sig, _ := priv.Sign(data) + // fmt.Println("{\n publicKey: Buffer.from(", strings.Replace(fmt.Sprint(pubkeyBytes), " ", ",", -1), "),") + // fmt.Println(" privateKey: Buffer.from(", strings.Replace(fmt.Sprint(privkeyBytes), " ", ",", -1), "),") + // fmt.Println(" data: Buffer.from(", strings.Replace(fmt.Sprint(data), " ", ",", -1), "),") + // fmt.Println(" signature: Buffer.from(", strings.Replace(fmt.Sprint(sig), " ", ",", -1), ")\n}") + // } // - // 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: { + + // The legacy key unnecessarily appends the publickey. (It's already included) See https://github.com/libp2p/js-libp2p-crypto/issues/175 + redundantPubKey: { 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]) + }, + verify: { + publicKey: Buffer.from( [8,1,18,32,163,176,195,47,254,208,49,5,192,102,32,63,58,202,171,153,146,164,25,212,25,91,146,26,117,165,148,6,207,90,217,126] ), + privateKey: Buffer.from( [8,1,18,64,232,56,175,20,240,160,19,47,92,88,115,221,164,13,36,162,158,136,247,31,29,231,76,143,12,91,193,4,88,33,67,23,163,176,195,47,254,208,49,5,192,102,32,63,58,202,171,153,146,164,25,212,25,91,146,26,117,165,148,6,207,90,217,126] ), + 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( [160,125,30,62,213,189,239,92,87,76,205,169,251,149,187,57,96,85,175,213,22,132,229,60,196,18,117,194,12,174,135,31,39,168,174,103,78,55,37,222,37,172,222,239,153,63,197,152,67,167,191,215,161,212,216,163,81,77,45,228,151,79,101,1] ) } } diff --git a/test/keys/ed25519.spec.js b/test/keys/ed25519.spec.js index bfedbec..8a7d77c 100644 --- a/test/keys/ed25519.spec.js +++ b/test/keys/ed25519.spec.js @@ -131,25 +131,28 @@ describe('ed25519', function () { describe('go interop', () => { // @ts-check - /** - * @type {PrivateKey} - */ - let privateKey - - before(async () => { - const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey) - privateKey = key - }) - it('verifies with data from go', async () => { const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey) const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature) expect(ok).to.eql(true) }) + it('verifies with data from go with redundant public key', async () => { + const key = crypto.keys.unmarshalPublicKey(fixtures.redundantPubKey.publicKey) + const ok = await key.verify(fixtures.redundantPubKey.data, fixtures.redundantPubKey.signature) + expect(ok).to.eql(true) + }) + it('generates the same signature as go', async () => { - const sig = await privateKey.sign(fixtures.verify.data) + const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey) + const sig = await key.sign(fixtures.verify.data) expect(sig).to.eql(fixtures.verify.signature) }) + + it('generates the same signature as go with redundant public key', async () => { + const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey) + const sig = await key.sign(fixtures.redundantPubKey.data) + expect(sig).to.eql(fixtures.redundantPubKey.signature) + }) }) })