From d88884cd221910aadfde10f34328004e06a8e6a4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 6 Aug 2018 21:49:13 +0100 Subject: [PATCH 1/4] feat: add public key support --- README.md | 58 +++++++++++++++++++++++++----- package.json | 1 + src/errors.js | 2 ++ src/index.js | 90 +++++++++++++++++++++++++++++++++++++++------- test/index.spec.js | 40 +++++++++++++++++++-- 5 files changed, 167 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a85d036..cd7df0e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ const ipns = require('ipns') ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => { // your code goes here -}); +}) ``` #### Validate record @@ -51,23 +51,35 @@ const ipns = require('ipns') ipns.validate(publicKey, ipnsEntry, (err) => { // your code goes here // if no error, the record is valid -}); +}) ``` #### Embed public key to record -> Not available yet +```js +const ipns = require('ipns') + +ipns.embedPublicKey(publicKey, ipnsEntry, (err, ipnsEntryWithEmbedPublicKey) => { + // your code goes here +}) +``` #### Extract public key from record -> Not available yet +```js +const ipns = require('ipns') + +ipns.extractPublicKey(peerId, ipnsEntry, (err, publicKey) => { + // your code goes here +}) +``` #### Datastore key ```js const ipns = require('ipns') -ipns.getLocalKey(peerId); +ipns.getLocalKey(peerId) ``` Returns a key to be used for storing the ipns entry locally, that is: @@ -85,7 +97,7 @@ ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => { // ... const marshalledData = ipns.marshal(entryData) // ... -}); +}) ``` Returns the entry data serialized. @@ -106,7 +118,7 @@ Returns the entry data structure after being serialized. ```js -ipns.create(privateKey, value, sequenceNumber, lifetime, [callback]); +ipns.create(privateKey, value, sequenceNumber, lifetime, [callback]) ``` Create an IPNS record for being stored in a protocol buffer. @@ -133,7 +145,7 @@ Create an IPNS record for being stored in a protocol buffer. ```js -ipns.validate(publicKey, ipnsEntry, [callback]); +ipns.validate(publicKey, ipnsEntry, [callback]) ``` Validate an IPNS record previously stored in a protocol buffer. @@ -147,7 +159,7 @@ Validate an IPNS record previously stored in a protocol buffer. #### Datastore key ```js -ipns.getDatastoreKey(peerId); +ipns.getDatastoreKey(peerId) ``` Get a key for storing the ipns entry in the datastore. @@ -174,6 +186,34 @@ Returns the entry data structure after being serialized. - `storedData` (Buffer): ipns entry record serialized. +#### Embed public key to record + +```js +ipns.embedPublicKey(publicKey, ipnsEntry, [callback]) +``` + +Embed a public key in an IPNS entry. If it is possible to extract the public key from the `peer-id`, there is no need to embed. + +- `publicKey` (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): key to be used for cryptographic operations. +- `ipnsEntry` (Object): ipns entry record (obtained using the create function). +- `callback` (function): operation result. + +`callback` must follow `function (err, resultEntry) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the operation was successful. If the `resultEntry` is also null, the `peer-id` allows to extract the public key from the `peer-id` and there is no need in extracting it. + +#### Extract public key from record + +```js +ipns.extractPublicKey(peerId, ipnsEntry, [callback]) +``` + +Extract a public key from an IPNS entry. + +- `peerId` (`PeerId` [Instance](https://github.com/libp2p/js-peer-id)): peer identifier object. +- `ipnsEntry` (Object): ipns entry record (obtained using the create function). +- `callback` (function): operation result. + +`callback` must follow `function (err, publicKey) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the validation was successful. The public key (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): may be used for cryptographic operations. + ## Contribute Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-ipns/issues)! diff --git a/package.json b/package.json index 3a6d9eb..9c856d3 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "interface-datastore": "^0.4.2", "left-pad": "^1.3.0", "nano-date": "^2.1.0", + "peer-id": "^0.11.0", "protons": "^1.0.1" }, "devDependencies": { diff --git a/src/errors.js b/src/errors.js index ac8e3b1..4b8bac2 100644 --- a/src/errors.js +++ b/src/errors.js @@ -5,3 +5,5 @@ exports.ERR_UNRECOGNIZED_VALIDITY = 'ERR_UNRECOGNIZED_VALIDITY' exports.ERR_SIGNATURE_CREATION = 'ERR_SIGNATURE_CREATION' exports.ERR_SIGNATURE_VERIFICATION = 'ERR_SIGNATURE_VERIFICATION' exports.ERR_UNRECOGNIZED_FORMAT = 'ERR_UNRECOGNIZED_FORMAT' +exports.ERR_PEER_ID_FROM_PUBLIC_KEY = 'ERR_PEER_ID_FROM_PUBLIC_KEY' +exports.ERR_PUBLICK_KEY_FROM_ID = 'ERR_PUBLICK_KEY_FROM_ID' diff --git a/src/index.js b/src/index.js index 8f4558c..f50e491 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,9 @@ const base32Encode = require('base32-encode') const Big = require('big.js') const NanoDate = require('nano-date').default const { Key } = require('interface-datastore') +const crypto = require('libp2p-crypto') +const peerId = require('peer-id') +const multihash = require('multihashes') const debug = require('debug') const log = debug('jsipns') @@ -13,6 +16,7 @@ const ipnsEntryProto = require('./pb/ipns.proto') const { parseRFC3339 } = require('./utils') const ERRORS = require('./errors') +const ID_MULTIHASH_CODE = 0 /** * Creates a new ipns entry and signs it with the given private key. * The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. @@ -100,15 +104,48 @@ const validate = (publicKey, entry, callback) => { } /** - * Validates the given ipns entry against the given public key. + * Embed the given public key in the given entry. While not strictly required, + * some nodes (eg. DHT servers) may reject IPNS entries that don't embed their + * public keys as they may not be able to validate them efficiently. + * As a consequence of nodes needing to validade a record upon receipt, they need + * the public key associated with it. For olde RSA keys, it is easier if we just + * send this as part of the record itself. For newer ed25519 keys, the public key + * can be embedded in the peerId. * - * @param {Object} publicKey public key for validating the record. + * @param {Object} publicKey public key for embed. * @param {Object} entry ipns entry record. * @param {function(Error)} [callback] * @return {Void} */ const embedPublicKey = (publicKey, entry, callback) => { - callback(new Error('not implemented yet')) + // Create a peer id from the public key. + peerId.createFromPubKey(publicKey.bytes, (err, peerIdResult) => { + if (err) { + const error = 'cannot create peer id from the public key' + + log.error(error) + return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) + } + + // Try to extract the public key from the ID. If we can, no need to embed it + let extractedPublicKey + try { + extractedPublicKey = extractPublicKeyFromId(peerIdResult) + } catch (err) { + const error = 'cannot get public key from the peer id' + + log.error(error) + return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PUBLICK_KEY_FROM_ID })) + } + + if (extractedPublicKey) { + return callback(null, null) + } + + // If we vailed to extract the public key from the peer ID, embed it in the record. + entry.pubKey = crypto.keys.marshalPublicKey(publicKey) + return callback(null, entry) + }) } /** @@ -120,7 +157,13 @@ const embedPublicKey = (publicKey, entry, callback) => { * @return {Void} */ const extractPublicKey = (peerId, entry, callback) => { - callback(new Error('not implemented yet')) + if (entry.pubKey) { + const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) + + return callback(null, pubKey) + } else { + return callback(null, peerId.pubKey) + } } // rawStdEncoding with RFC4648 @@ -139,16 +182,16 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`) * Get key for sharing the record in the routing mechanism. * Format: ${base32(/ipns/)}, ${base32(/pk/)} * - * @param {Buffer} key peer identifier object. + * @param {Buffer} pid peer identifier object. * @returns {Object} containing the `nameKey` and the `ipnsKey`. */ -const getIdKeys = (key) => { +const getIdKeys = (pid) => { const pkBuffer = Buffer.from('/pk/') const ipnsBuffer = Buffer.from('/ipns/') return { - nameKey: rawStdEncoding(Buffer.concat([pkBuffer, key])), - ipnsKey: rawStdEncoding(Buffer.concat([ipnsBuffer, key])) + pkKey: new Key(rawStdEncoding(Buffer.concat([pkBuffer, pid]))), + ipnsKey: new Key(rawStdEncoding(Buffer.concat([ipnsBuffer, pid]))) } } @@ -164,13 +207,34 @@ const sign = (privateKey, value, validityType, validity, callback) => { }) } -// Create record data for being signed -const ipnsEntryDataForSig = (value, validityType, eol) => { +// Utility for getting the validity type code name of a validity +const getValidityType = (validityType) => { + if (validityType.toString() === '0') { + return 'EOL' + } else { + log.error('unrecognized validity type') + throw Object.assign(new Error('unrecognized validity type'), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) + } +} + +// Utility for creating the record data for being signed +const ipnsEntryDataForSig = (value, validityType, validity) => { const valueBuffer = Buffer.from(value) - const validityTypeBuffer = Buffer.from(validityType.toString()) - const eolBuffer = Buffer.from(eol) + const validityTypeBuffer = Buffer.from(getValidityType(validityType)) + const validityBuffer = Buffer.from(validity) + + return Buffer.concat([valueBuffer, validityBuffer, validityTypeBuffer]) +} + +// Utility for extracting the public key from a peer-id +const extractPublicKeyFromId = (peerIdResult) => { + const decodedId = multihash.decode(peerIdResult.id) + + if (decodedId.code !== ID_MULTIHASH_CODE) { + return null + } - return Buffer.concat([valueBuffer, validityTypeBuffer, eolBuffer]) + return crypto.keys.unmarshalPublicKey(decodedId.digest) } module.exports = { diff --git a/test/index.spec.js b/test/index.spec.js index eb7ef25..057a590 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -146,9 +146,45 @@ describe('ipns', function () { const idKeys = ipns.getIdKeys(fromB58String(ipfsId.id)) expect(idKeys).to.exist() - expect(idKeys).to.have.a.property('nameKey') + expect(idKeys).to.have.a.property('pkKey') expect(idKeys).to.have.a.property('ipnsKey') - expect(idKeys.nameKey).to.not.startsWith('/pk/') + expect(idKeys.pkKey).to.not.startsWith('/pk/') expect(idKeys.ipnsKey).to.not.startsWith('/ipns/') }) + + it('should be able to embed a public key in an ipns record', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(rsa.public, entry, (err, entry) => { + expect(err).to.not.exist() + expect(entry).to.deep.include({ + pubKey: rsa.public.bytes + }) + done() + }) + }) + }) + + it('should be able to export a previously embed public key from an ipns record', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(rsa.public, entry, (err, entry) => { + expect(err).to.not.exist() + + ipns.extractPublicKey(ipfsId, entry, (err, publicKey) => { + expect(err).to.not.exist() + expect(publicKey.bytes).to.equalBytes(rsa.public.bytes) + done() + }) + }) + }) + }) }) From f76b391836583183c1d8946fe298b8e5cbbb03c9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 8 Aug 2018 18:50:13 +0100 Subject: [PATCH 2/4] fix: code review --- package.json | 4 ++-- src/errors.js | 2 +- src/index.js | 49 +++++++++++++++++++++++----------------------- test/index.spec.js | 23 ++++++++++++++++++++++ 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 9c856d3..264d903 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "debug": "^3.1.0", "interface-datastore": "^0.4.2", "left-pad": "^1.3.0", + "multihashes": "^0.4.14", "nano-date": "^2.1.0", "peer-id": "^0.11.0", "protons": "^1.0.1" @@ -50,8 +51,7 @@ "dirty-chai": "^2.0.1", "ipfs": "^0.29.3", "ipfsd-ctl": "^0.36.0", - "libp2p-crypto": "^0.13.0", - "multihashes": "^0.4.13" + "libp2p-crypto": "^0.13.0" }, "contributors": [ "Vasco Santos " diff --git a/src/errors.js b/src/errors.js index 4b8bac2..2b35005 100644 --- a/src/errors.js +++ b/src/errors.js @@ -6,4 +6,4 @@ exports.ERR_SIGNATURE_CREATION = 'ERR_SIGNATURE_CREATION' exports.ERR_SIGNATURE_VERIFICATION = 'ERR_SIGNATURE_VERIFICATION' exports.ERR_UNRECOGNIZED_FORMAT = 'ERR_UNRECOGNIZED_FORMAT' exports.ERR_PEER_ID_FROM_PUBLIC_KEY = 'ERR_PEER_ID_FROM_PUBLIC_KEY' -exports.ERR_PUBLICK_KEY_FROM_ID = 'ERR_PUBLICK_KEY_FROM_ID' +exports.ERR_PUBLIC_KEY_FROM_ID = 'ERR_PUBLIC_KEY_FROM_ID' diff --git a/src/index.js b/src/index.js index f50e491..ae082cd 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const Big = require('big.js') const NanoDate = require('nano-date').default const { Key } = require('interface-datastore') const crypto = require('libp2p-crypto') -const peerId = require('peer-id') +const PeerId = require('peer-id') const multihash = require('multihashes') const debug = require('debug') @@ -16,7 +16,7 @@ const ipnsEntryProto = require('./pb/ipns.proto') const { parseRFC3339 } = require('./utils') const ERRORS = require('./errors') -const ID_MULTIHASH_CODE = 0 +const ID_MULTIHASH_CODE = multihash.names.id /** * Creates a new ipns entry and signs it with the given private key. * The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. @@ -72,7 +72,7 @@ const validate = (publicKey, entry, callback) => { const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) // Validate Signature - publicKey.verify(dataForSignature, entry.signature, (err, result) => { + publicKey.verify(dataForSignature, entry.signature, (err) => { if (err) { log.error('record signature verification failed') return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_VERIFICATION })) @@ -119,32 +119,28 @@ const validate = (publicKey, entry, callback) => { */ const embedPublicKey = (publicKey, entry, callback) => { // Create a peer id from the public key. - peerId.createFromPubKey(publicKey.bytes, (err, peerIdResult) => { + PeerId.createFromPubKey(publicKey.bytes, (err, peerId) => { if (err) { - const error = 'cannot create peer id from the public key' - - log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) + log.error(err) + return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) } // Try to extract the public key from the ID. If we can, no need to embed it let extractedPublicKey try { - extractedPublicKey = extractPublicKeyFromId(peerIdResult) + extractedPublicKey = extractPublicKeyFromId(peerId) } catch (err) { - const error = 'cannot get public key from the peer id' - - log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PUBLICK_KEY_FROM_ID })) + log.error(err) + return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PUBLIC_KEY_FROM_ID })) } if (extractedPublicKey) { return callback(null, null) } - // If we vailed to extract the public key from the peer ID, embed it in the record. + // If we failed to extract the public key from the peer ID, embed it in the record. entry.pubKey = crypto.keys.marshalPublicKey(publicKey) - return callback(null, entry) + callback(null, entry) }) } @@ -158,12 +154,16 @@ const embedPublicKey = (publicKey, entry, callback) => { */ const extractPublicKey = (peerId, entry, callback) => { if (entry.pubKey) { - const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) + try { + const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) - return callback(null, pubKey) - } else { - return callback(null, peerId.pubKey) + return callback(null, pubKey) + } catch (err) { + log.error(err) + return callback(err) + } } + callback(null, peerId.pubKey) } // rawStdEncoding with RFC4648 @@ -182,7 +182,7 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`) * Get key for sharing the record in the routing mechanism. * Format: ${base32(/ipns/)}, ${base32(/pk/)} * - * @param {Buffer} pid peer identifier object. + * @param {Buffer} pid peer identifier represented by the multihash of the public key as Buffer. * @returns {Object} containing the `nameKey` and the `ipnsKey`. */ const getIdKeys = (pid) => { @@ -212,8 +212,9 @@ const getValidityType = (validityType) => { if (validityType.toString() === '0') { return 'EOL' } else { - log.error('unrecognized validity type') - throw Object.assign(new Error('unrecognized validity type'), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) + const error = `unrecognized validity type ${validityType.toString()}` + log.error(error) + throw Object.assign(new Error(error), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) } } @@ -227,8 +228,8 @@ const ipnsEntryDataForSig = (value, validityType, validity) => { } // Utility for extracting the public key from a peer-id -const extractPublicKeyFromId = (peerIdResult) => { - const decodedId = multihash.decode(peerIdResult.id) +const extractPublicKeyFromId = (peerId) => { + const decodedId = multihash.decode(peerId.id) if (decodedId.code !== ID_MULTIHASH_CODE) { return null diff --git a/test/index.spec.js b/test/index.spec.js index 057a590..23731f7 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -169,6 +169,29 @@ describe('ipns', function () { }) }) + // It should have a public key embeded for newer ed25519 keys + // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns.go#L81 + // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns_test.go + // https://github.com/libp2p/go-libp2p-peer/blob/7f219a1e70011a258c5d3e502aef6896c60d03ce/peer.go#L80 + it.skip('should be able to extract a public key directly from the peer', (done) => { + const sequence = 0 + const validity = 1000000 + + crypto.keys.generateKeyPair('ed25519', 2048, (err, ed25519) => { + expect(err).to.not.exist() + + ipns.create(ed25519, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(ed25519.public, entry, (err, entry) => { + expect(err).to.not.exist() + expect(entry).to.not.exist() // Should be null + done() + }) + }) + }) + }) + it('should be able to export a previously embed public key from an ipns record', (done) => { const sequence = 0 const validity = 1000000 From 7812af8f601cd7f18740287df0fe24ae871d5d9c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 8 Aug 2018 18:50:13 +0100 Subject: [PATCH 3/4] fix: code review --- package.json | 4 ++-- src/errors.js | 2 +- src/index.js | 51 +++++++++++++++++++++++----------------------- test/index.spec.js | 23 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 9c856d3..264d903 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "debug": "^3.1.0", "interface-datastore": "^0.4.2", "left-pad": "^1.3.0", + "multihashes": "^0.4.14", "nano-date": "^2.1.0", "peer-id": "^0.11.0", "protons": "^1.0.1" @@ -50,8 +51,7 @@ "dirty-chai": "^2.0.1", "ipfs": "^0.29.3", "ipfsd-ctl": "^0.36.0", - "libp2p-crypto": "^0.13.0", - "multihashes": "^0.4.13" + "libp2p-crypto": "^0.13.0" }, "contributors": [ "Vasco Santos " diff --git a/src/errors.js b/src/errors.js index 4b8bac2..2b35005 100644 --- a/src/errors.js +++ b/src/errors.js @@ -6,4 +6,4 @@ exports.ERR_SIGNATURE_CREATION = 'ERR_SIGNATURE_CREATION' exports.ERR_SIGNATURE_VERIFICATION = 'ERR_SIGNATURE_VERIFICATION' exports.ERR_UNRECOGNIZED_FORMAT = 'ERR_UNRECOGNIZED_FORMAT' exports.ERR_PEER_ID_FROM_PUBLIC_KEY = 'ERR_PEER_ID_FROM_PUBLIC_KEY' -exports.ERR_PUBLICK_KEY_FROM_ID = 'ERR_PUBLICK_KEY_FROM_ID' +exports.ERR_PUBLIC_KEY_FROM_ID = 'ERR_PUBLIC_KEY_FROM_ID' diff --git a/src/index.js b/src/index.js index f50e491..3173962 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const Big = require('big.js') const NanoDate = require('nano-date').default const { Key } = require('interface-datastore') const crypto = require('libp2p-crypto') -const peerId = require('peer-id') +const PeerId = require('peer-id') const multihash = require('multihashes') const debug = require('debug') @@ -16,7 +16,7 @@ const ipnsEntryProto = require('./pb/ipns.proto') const { parseRFC3339 } = require('./utils') const ERRORS = require('./errors') -const ID_MULTIHASH_CODE = 0 +const ID_MULTIHASH_CODE = multihash.names.id /** * Creates a new ipns entry and signs it with the given private key. * The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. @@ -72,7 +72,7 @@ const validate = (publicKey, entry, callback) => { const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) // Validate Signature - publicKey.verify(dataForSignature, entry.signature, (err, result) => { + publicKey.verify(dataForSignature, entry.signature, (err) => { if (err) { log.error('record signature verification failed') return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_VERIFICATION })) @@ -112,39 +112,35 @@ const validate = (publicKey, entry, callback) => { * send this as part of the record itself. For newer ed25519 keys, the public key * can be embedded in the peerId. * - * @param {Object} publicKey public key for embed. + * @param {Object} publicKey public key to embed. * @param {Object} entry ipns entry record. * @param {function(Error)} [callback] * @return {Void} */ const embedPublicKey = (publicKey, entry, callback) => { // Create a peer id from the public key. - peerId.createFromPubKey(publicKey.bytes, (err, peerIdResult) => { + PeerId.createFromPubKey(publicKey.bytes, (err, peerId) => { if (err) { - const error = 'cannot create peer id from the public key' - - log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) + log.error(err) + return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) } // Try to extract the public key from the ID. If we can, no need to embed it let extractedPublicKey try { - extractedPublicKey = extractPublicKeyFromId(peerIdResult) + extractedPublicKey = extractPublicKeyFromId(peerId) } catch (err) { - const error = 'cannot get public key from the peer id' - - log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_PUBLICK_KEY_FROM_ID })) + log.error(err) + return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PUBLIC_KEY_FROM_ID })) } if (extractedPublicKey) { return callback(null, null) } - // If we vailed to extract the public key from the peer ID, embed it in the record. + // If we failed to extract the public key from the peer ID, embed it in the record. entry.pubKey = crypto.keys.marshalPublicKey(publicKey) - return callback(null, entry) + callback(null, entry) }) } @@ -158,12 +154,16 @@ const embedPublicKey = (publicKey, entry, callback) => { */ const extractPublicKey = (peerId, entry, callback) => { if (entry.pubKey) { - const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) + try { + const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) - return callback(null, pubKey) - } else { - return callback(null, peerId.pubKey) + return callback(null, pubKey) + } catch (err) { + log.error(err) + return callback(err) + } } + callback(null, peerId.pubKey) } // rawStdEncoding with RFC4648 @@ -182,7 +182,7 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`) * Get key for sharing the record in the routing mechanism. * Format: ${base32(/ipns/)}, ${base32(/pk/)} * - * @param {Buffer} pid peer identifier object. + * @param {Buffer} pid peer identifier represented by the multihash of the public key as Buffer. * @returns {Object} containing the `nameKey` and the `ipnsKey`. */ const getIdKeys = (pid) => { @@ -212,8 +212,9 @@ const getValidityType = (validityType) => { if (validityType.toString() === '0') { return 'EOL' } else { - log.error('unrecognized validity type') - throw Object.assign(new Error('unrecognized validity type'), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) + const error = `unrecognized validity type ${validityType.toString()}` + log.error(error) + throw Object.assign(new Error(error), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) } } @@ -227,8 +228,8 @@ const ipnsEntryDataForSig = (value, validityType, validity) => { } // Utility for extracting the public key from a peer-id -const extractPublicKeyFromId = (peerIdResult) => { - const decodedId = multihash.decode(peerIdResult.id) +const extractPublicKeyFromId = (peerId) => { + const decodedId = multihash.decode(peerId.id) if (decodedId.code !== ID_MULTIHASH_CODE) { return null diff --git a/test/index.spec.js b/test/index.spec.js index 057a590..23731f7 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -169,6 +169,29 @@ describe('ipns', function () { }) }) + // It should have a public key embeded for newer ed25519 keys + // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns.go#L81 + // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns_test.go + // https://github.com/libp2p/go-libp2p-peer/blob/7f219a1e70011a258c5d3e502aef6896c60d03ce/peer.go#L80 + it.skip('should be able to extract a public key directly from the peer', (done) => { + const sequence = 0 + const validity = 1000000 + + crypto.keys.generateKeyPair('ed25519', 2048, (err, ed25519) => { + expect(err).to.not.exist() + + ipns.create(ed25519, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(ed25519.public, entry, (err, entry) => { + expect(err).to.not.exist() + expect(entry).to.not.exist() // Should be null + done() + }) + }) + }) + }) + it('should be able to export a previously embed public key from an ipns record', (done) => { const sequence = 0 const validity = 1000000 From 1c023a6b50ac810eb731bfa3eb349ff363348d44 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 9 Aug 2018 12:05:38 +0100 Subject: [PATCH 4/4] fix: code review --- package.json | 4 ++-- src/errors.js | 1 + src/index.js | 29 ++++++++++++++++++++++++----- test/index.spec.js | 2 ++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 264d903..60d6320 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "debug": "^3.1.0", "interface-datastore": "^0.4.2", "left-pad": "^1.3.0", + "libp2p-crypto": "^0.13.0", "multihashes": "^0.4.14", "nano-date": "^2.1.0", "peer-id": "^0.11.0", @@ -50,8 +51,7 @@ "chai-string": "^1.4.0", "dirty-chai": "^2.0.1", "ipfs": "^0.29.3", - "ipfsd-ctl": "^0.36.0", - "libp2p-crypto": "^0.13.0" + "ipfsd-ctl": "^0.36.0" }, "contributors": [ "Vasco Santos " diff --git a/src/errors.js b/src/errors.js index 2b35005..3b6853c 100644 --- a/src/errors.js +++ b/src/errors.js @@ -7,3 +7,4 @@ exports.ERR_SIGNATURE_VERIFICATION = 'ERR_SIGNATURE_VERIFICATION' exports.ERR_UNRECOGNIZED_FORMAT = 'ERR_UNRECOGNIZED_FORMAT' exports.ERR_PEER_ID_FROM_PUBLIC_KEY = 'ERR_PEER_ID_FROM_PUBLIC_KEY' exports.ERR_PUBLIC_KEY_FROM_ID = 'ERR_PUBLIC_KEY_FROM_ID' +exports.ERR_UNDEFINED_PARAMETER = 'ERR_UNDEFINED_PARAMETER' diff --git a/src/index.js b/src/index.js index 3173962..419ef12 100644 --- a/src/index.js +++ b/src/index.js @@ -48,7 +48,7 @@ const create = (privateKey, value, seq, lifetime, callback) => { const entry = { value: value, - signature: signature, // TODO confirm format compliance with go-ipfs + signature: signature, validityType: validityType, validity: isoValidity, sequence: seq @@ -118,6 +118,13 @@ const validate = (publicKey, entry, callback) => { * @return {Void} */ const embedPublicKey = (publicKey, entry, callback) => { + if (!publicKey || !publicKey.bytes || !entry) { + const error = 'one or more of the provided parameters are not defined' + + log.error(error) + return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER })) + } + // Create a peer id from the public key. PeerId.createFromPubKey(publicKey.bytes, (err, peerId) => { if (err) { @@ -139,7 +146,12 @@ const embedPublicKey = (publicKey, entry, callback) => { } // If we failed to extract the public key from the peer ID, embed it in the record. - entry.pubKey = crypto.keys.marshalPublicKey(publicKey) + try { + entry.pubKey = crypto.keys.marshalPublicKey(publicKey) + } catch (err) { + log.error(err) + return callback(err) + } callback(null, entry) }) } @@ -153,15 +165,22 @@ const embedPublicKey = (publicKey, entry, callback) => { * @return {Void} */ const extractPublicKey = (peerId, entry, callback) => { + if (!entry || !peerId) { + const error = 'one or more of the provided parameters are not defined' + + log.error(error) + return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER })) + } + if (entry.pubKey) { + let pubKey try { - const pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) - - return callback(null, pubKey) + pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) } catch (err) { log.error(err) return callback(err) } + return callback(null, pubKey) } callback(null, peerId.pubKey) } diff --git a/test/index.spec.js b/test/index.spec.js index 23731f7..761df81 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -173,6 +173,8 @@ describe('ipns', function () { // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns.go#L81 // https://github.com/ipfs/go-ipns/blob/d51115b4b14ed7fcca5472aadff0fee6772aca8c/ipns_test.go // https://github.com/libp2p/go-libp2p-peer/blob/7f219a1e70011a258c5d3e502aef6896c60d03ce/peer.go#L80 + // IDFromEd25519PublicKey is not currently implement on js-libp2p-peer + // https://github.com/libp2p/go-libp2p-peer/pull/30 it.skip('should be able to extract a public key directly from the peer', (done) => { const sequence = 0 const validity = 1000000