From 544ca7d74bb5354a77a1456498998a31a99fe486 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 4 Nov 2019 18:22:29 +0100 Subject: [PATCH] feat: support Peer ID represented as CID (#105) * feat: support Peer ID represented as CID This change adds two functions: - createFromCID accepts CID as String|CID|Buffer and creates PeerId from the multihash value inside of it - toCIDString serializes PeerId multihash into a CIDv1 in Base32, as agreed in https://github.com/libp2p/specs/pull/209 License: MIT Signed-off-by: Marcin Rataj * refactor: rename toCIDString to toString CIDv1 is self describing, and toString was not defined. Makes sense to use generic toString in this case. This change also: - remembers string with CID, so it is lazily generated only once - switches createFromB58String to createFromCID (b58 is CIDv0), making it easier to migrate existing codebases. License: MIT Signed-off-by: Marcin Rataj * docs: comment tests License: MIT Signed-off-by: Marcin Rataj * feat: validate CID multicodec - require CID with 'libp2p-key' (CIDv1) or 'dag-pb' (CIDv0 converted to CIDv1) - delegate CID validation to CID constructor License: MIT Signed-off-by: Marcin Rataj --- README.md | 23 ++++++++++-- package.json | 1 + src/index.js | 24 ++++++++++++- test/peer-id.spec.js | 86 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2520402..f93c3ad 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,16 @@ - [Import](#import) - [`createFromHexString(str)`](#createfromhexstringstr) - [`createFromBytes(buf)`](#createfrombytesbuf) + - [`createFromCID(cid)`](#createfromcidcid) - [`createFromB58String(str)`](#createfromb58stringstr) - [`createFromPubKey(pubKey)`](#createfrompubkeypubkey) - [`createFromPrivKey(privKey)`](#createfromprivkeyprivkey) - [`createFromJSON(obj)`](#createfromjsonobj) - [Export](#export) - - [`toHexString()`](#tohexstring) - [`toBytes()`](#tobytes) + - [`toString()`](#tostring) - [`toB58String()`](#tob58string) + - [`toHexString()`](#tohexstring) - [`toJSON()`](#tojson) - [`toPrint()`](#toprint) - [License](#license) @@ -145,6 +147,14 @@ Creates a Peer ID from a buffer representing the key's multihash. Returns `PeerId`. +### `createFromCID(cid)` + +- `cid: CID|String|Buffer` - The multihash encoded as [CID](https://github.com/ipld/js-cid) (object, `String` or `Buffer`) + +Creates a Peer ID from a CID representation of the key's multihash ([RFC 0001](https://github.com/libp2p/specs/blob/master/RFC/0001-text-peerid-cid.md)). + +Returns `PeerId`. + ### `createFromB58String(str)` Creates a Peer ID from a Base58 string representing the key's multihash. @@ -197,9 +207,18 @@ Returns the Peer ID's `id` as a buffer. ``` + +### `toString()` + +Returns the Peer ID's `id` as a self-describing CIDv1 in Base32 ([RFC 0001](https://github.com/libp2p/specs/blob/master/RFC/0001-text-peerid-cid.md)) + +``` +bafzbeigweq4zr4x4ky2dvv7nanbkw6egutvrrvzw6g3h2rftp7gidyhtt4 +``` + ### `toB58String()` -Returns the Peer ID's `id` as a base58 string. +Returns the Peer ID's `id` as a base58 string (multihash/CIDv0). ``` QmckZzdVd72h9QUFuJJpQqhsZqGLwjhh81qSvZ9BhB2FQi diff --git a/package.json b/package.json index ee71a55..3901c51 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dirty-chai": "^2.0.1" }, "dependencies": { + "cids": "~0.7.1", "class-is": "^1.1.0", "libp2p-crypto": "~0.17.0", "multihashes": "~0.4.15", diff --git a/src/index.js b/src/index.js index 94a1980..0081dd4 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ 'use strict' const mh = require('multihashes') +const CID = require('cids') const cryptoKeys = require('libp2p-crypto/src/keys') const assert = require('assert') const withIs = require('class-is') @@ -122,6 +123,16 @@ class PeerId { return this._idB58String } + // return self-describing String representation + // in default format from RFC 0001: https://github.com/libp2p/specs/pull/209 + toString () { + if (!this._idCIDString) { + const cid = new CID(1, 'libp2p-key', this.id, 'base32') + this._idCIDString = cid.toBaseEncodedString('base32') + } + return this._idCIDString + } + isEqual (id) { if (Buffer.isBuffer(id)) { return this.id.equals(id) @@ -184,7 +195,18 @@ exports.createFromBytes = (buf) => { } exports.createFromB58String = (str) => { - return new PeerIdWithIs(mh.fromB58String(str)) + return exports.createFromCID(str) // B58String is CIDv0 +} + +const validMulticodec = (cid) => { + // supported: 'libp2p-key' (CIDv1) and 'dag-pb' (CIDv0 converted to CIDv1) + return cid.codec === 'libp2p-key' || cid.codec === 'dag-pb' +} + +exports.createFromCID = (cid) => { + cid = CID.isCID(cid) ? cid : new CID(cid) + if (!validMulticodec(cid)) throw new Error('Supplied PeerID CID has invalid multicodec: ' + cid.codec) + return new PeerIdWithIs(cid.multihash) } // Public Key input will be a buffer diff --git a/test/peer-id.spec.js b/test/peer-id.spec.js index 685866c..2e85a5f 100644 --- a/test/peer-id.spec.js +++ b/test/peer-id.spec.js @@ -8,6 +8,7 @@ chai.use(dirtyChai) const expect = chai.expect const crypto = require('libp2p-crypto') const mh = require('multihashes') +const CID = require('cids') const PeerId = require('../src') @@ -17,6 +18,8 @@ const testId = require('./fixtures/sample-id') const testIdHex = testId.id const testIdBytes = mh.fromHexString(testId.id) const testIdB58String = mh.toB58String(testIdBytes) +const testIdCID = new CID(1, 'libp2p-key', testIdBytes) +const testIdCIDString = testIdCID.toBaseEncodedString('base32') const goId = require('./fixtures/go-private-key') @@ -63,27 +66,96 @@ describe('PeerId', () => { }).to.throw(/immutable/) }) - it('recreate an Id from Hex string', () => { + it('recreate from Hex string', () => { const id = PeerId.createFromHexString(testIdHex) - expect(testIdBytes).to.deep.equal(id.id) + expect(testIdBytes).to.deep.equal(id.toBytes()) }) - it('Recreate an Id from a Buffer', () => { + it('recreate from a Buffer', () => { const id = PeerId.createFromBytes(testIdBytes) expect(testId.id).to.equal(id.toHexString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) }) - it('Recreate a B58 String', () => { + it('recreate from a B58 String', () => { const id = PeerId.createFromB58String(testIdB58String) expect(testIdB58String).to.equal(id.toB58String()) + expect(testIdBytes).to.deep.equal(id.toBytes()) }) - it('Recreate from a Public Key', async () => { + it('recreate from CID object', () => { + const id = PeerId.createFromCID(testIdCID) + expect(testIdCIDString).to.equal(id.toString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from Base58 String (CIDv0))', () => { + const id = PeerId.createFromCID(testIdB58String) + expect(testIdCIDString).to.equal(id.toString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from CIDv1 Base32 (libp2p-key multicodec)', () => { + const cid = new CID(1, 'libp2p-key', testIdBytes) + const cidString = cid.toBaseEncodedString('base32') + const id = PeerId.createFromCID(cidString) + expect(cidString).to.equal(id.toString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from CIDv1 Base32 (dag-pb multicodec)', () => { + const cid = new CID(1, 'dag-pb', testIdBytes) + const cidString = cid.toBaseEncodedString('base32') + const id = PeerId.createFromCID(cidString) + // toString should return CID with multicodec set to libp2p-key + expect(new CID(id.toString()).codec).to.equal('libp2p-key') + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from CID Buffer', () => { + const id = PeerId.createFromCID(testIdCID.buffer) + expect(testIdCIDString).to.equal(id.toString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('throws on invalid CID multicodec', () => { + // only libp2p and dag-pb are supported + const invalidCID = new CID(1, 'raw', testIdBytes).toBaseEncodedString('base32') + expect(() => { + PeerId.createFromCID(invalidCID) + }).to.throw(/Supplied PeerID CID has invalid multicodec: raw/) + }) + + it('throws on invalid CID value', () => { + // using function code that does not represent valid hash function + // https://github.com/multiformats/js-multihash/blob/b85999d5768bf06f1b0f16b926ef2cb6d9c14265/src/constants.js#L345 + const invalidCID = 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L' + expect(() => { + PeerId.createFromCID(invalidCID) + }).to.throw(/multihash unknown function code: 0x50/) + }) + + it('throws on invalid CID object', () => { + const invalidCID = {} + expect(() => { + PeerId.createFromCID(invalidCID) + }).to.throw(/Invalid version, must be a number equal to 1 or 0/) + }) + + it('throws on invalid CID object', () => { + const invalidCID = {} + expect(() => { + PeerId.createFromCID(invalidCID) + }).to.throw(/Invalid version, must be a number equal to 1 or 0/) + }) + + it('recreate from a Public Key', async () => { const id = await PeerId.createFromPubKey(testId.pubKey) expect(testIdB58String).to.equal(id.toB58String()) + expect(testIdBytes).to.deep.equal(id.toBytes()) }) - it('Recreate from a Private Key', async () => { + it('recreate from a Private Key', async () => { const id = await PeerId.createFromPrivKey(testId.privKey) expect(testIdB58String).to.equal(id.toB58String()) const encoded = Buffer.from(testId.privKey, 'base64') @@ -92,7 +164,7 @@ describe('PeerId', () => { expect(id.marshalPubKey()).to.deep.equal(id2.marshalPubKey()) }) - it('Recreate from Protobuf', async () => { + it('recreate from Protobuf', async () => { const id = await PeerId.createFromProtobuf(testId.marshaled) expect(testIdB58String).to.equal(id.toB58String()) const encoded = Buffer.from(testId.privKey, 'base64')