Skip to content

Commit

Permalink
feat: support Peer ID represented as CID (#105)
Browse files Browse the repository at this point in the history
* 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 libp2p/specs#209

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>

* 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 <lidel@lidel.org>

* docs: comment tests

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>

* 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 <lidel@lidel.org>
  • Loading branch information
lidel authored and vasco-santos committed Nov 4, 2019
1 parent 11d4ec1 commit 544ca7d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 10 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -197,9 +207,18 @@ Returns the Peer ID's `id` as a buffer.
<Buffer 12 20 d6 24 39 98 f2 fc 56 34 3a d7 ed 03 42 ab 78 86 a4 eb 18 d7 36 f1 b6 7d 44 b3 7f cc 81 e0 f3 9f>
```


### `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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 23 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
86 changes: 79 additions & 7 deletions test/peer-id.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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')

Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand Down

0 comments on commit 544ca7d

Please sign in to comment.