Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update to new multiformats #149

Merged
merged 10 commits into from
Jul 6, 2021
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
"@types/dirty-chai": "^2.0.2",
"@types/mocha": "^8.2.0",
"aegir": "^33.0.0",
"multihashes": "^4.0.2",
"util": "^0.12.3"
},
"dependencies": {
"cids": "^1.1.5",
"class-is": "^1.1.0",
"libp2p-crypto": "^0.19.0",
"minimist": "^1.2.5",
"multihashes": "^4.0.2",
"multiformats": "^9.0.0",
"protobufjs": "^6.10.2",
"uint8arrays": "^2.0.5"
},
Expand Down
4 changes: 2 additions & 2 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PrivateKey, PublicKey, KeyType } from "libp2p-crypto";
import CID from 'cids'
import { CID } from 'multiformats/cid'

declare namespace PeerId {
/**
Expand Down Expand Up @@ -68,7 +68,7 @@ declare namespace PeerId {
* Create PeerId from CID.
* @param cid The CID.
*/
function createFromCID(cid: CID | Uint8Array | string | object): PeerId;
function createFromCID(cid: CID): PeerId;

/**
* Create PeerId from public key.
Expand Down
66 changes: 47 additions & 19 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@

'use strict'

const mh = require('multihashes')
const CID = require('cids')
const { CID } = require('multiformats/cid')
const { base58btc } = require('multiformats/bases/base58')
const { base16 } = require('multiformats/bases/base16')
const Digest = require('multiformats/hashes/digest')
const cryptoKeys = require('libp2p-crypto/src/keys')
const withIs = require('class-is')
const { PeerIdProto } = require('./proto')
const uint8ArrayEquals = require('uint8arrays/equals')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')

// these values are from https://github.com/multiformats/multicodec/blob/master/table.csv
const IDENTITY_CODE = 0x00
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for not using multicodec here? I would like to just have these codes in one place as the source of truth

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one of the design goals behind the new multiformats module was to not have the big lookup tables that multicodec is the guardian of.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Probably worth adding a reference to the table just to reference where they come from? Probably to https://github.com/multiformats/multicodec/blob/master/table.csv

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my journey through the codebase doing the multiformats stuff it seems that we only really use four or five of them. I think the only weirdness is when bitswap has to mess around with CID prefixes in incoming bitswap 1.1.0 messages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just adding a comment above with the multicodec table is enough here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

const DAG_PB_CODE = 0x70
const LIBP2P_KEY_CODE = 0x72

class PeerId {
constructor (id, privKey, pubKey) {
if (!(id instanceof Uint8Array)) {
Expand All @@ -24,7 +31,7 @@ class PeerId {
}

this._id = id
this._idB58String = mh.toB58String(this.id)
this._idB58String = base58btc.encode(this.id).substring(1)
this._privKey = privKey
this._pubKey = pubKey
}
Expand Down Expand Up @@ -55,9 +62,9 @@ class PeerId {
}

try {
const decoded = mh.decode(this.id)
const decoded = Digest.decode(this.id)

if (decoded.name === 'identity') {
if (decoded.code === IDENTITY_CODE) {
this._pubKey = cryptoKeys.unmarshalPublicKey(decoded.digest)
}
} catch (_) {
Expand Down Expand Up @@ -121,7 +128,7 @@ class PeerId {

// encode/decode functions
toHexString () {
return mh.toHexString(this.id)
return base16.encode(this.id).substring(1)
}

toBytes () {
Expand All @@ -136,10 +143,10 @@ class PeerId {
// 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')
const cid = CID.createV1(LIBP2P_KEY_CODE, Digest.decode(this.id))

Object.defineProperty(this, '_idCIDString', {
value: cid.toBaseEncodedString('base32'),
value: cid.toString(),
enumerable: false
})
}
Expand Down Expand Up @@ -192,8 +199,9 @@ class PeerId {
*/
hasInlinePublicKey () {
try {
const decoded = mh.decode(this.id)
if (decoded.name === 'identity') {
const decoded = Digest.decode(this.id)

if (decoded.code === IDENTITY_CODE) {
return true
}
} catch (_) {
Expand All @@ -213,7 +221,7 @@ exports = module.exports = PeerIdWithIs

const computeDigest = (pubKey) => {
if (pubKey.bytes.length <= 42) {
return mh.encode(pubKey.bytes, 'identity')
return Digest.create(IDENTITY_CODE, pubKey.bytes).bytes
} else {
return pubKey.hash()
}
Expand All @@ -235,26 +243,46 @@ exports.create = async (opts) => {
}

exports.createFromHexString = (str) => {
return new PeerIdWithIs(mh.fromHexString(str))
return new PeerIdWithIs(base16.decode('f' + str))
}

exports.createFromBytes = (buf) => {
return new PeerIdWithIs(buf)
try {
const cid = CID.decode(buf)

if (!validMulticodec(cid)) {
throw new Error('Supplied PeerID CID is invalid')
}

return exports.createFromCID(cid)
} catch {
const digest = Digest.decode(buf)

if (digest.code !== IDENTITY_CODE) {
throw new Error('Supplied PeerID CID is invalid')
}

return new PeerIdWithIs(buf)
}
}

exports.createFromB58String = (str) => {
return exports.createFromCID(str) // B58String is CIDv0
return exports.createFromBytes(base58btc.decode('z' + str))
}

const validMulticodec = (cid) => {
// supported: 'libp2p-key' (CIDv1) and 'dag-pb' (CIDv0 converted to CIDv1)
return cid.codec === 'libp2p-key' || cid.codec === 'dag-pb'
return cid.code === LIBP2P_KEY_CODE || cid.code === DAG_PB_CODE
}

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)
cid = CID.asCID(cid)

if (!cid || !validMulticodec(cid)) {
throw new Error('Supplied PeerID CID is invalid')
}

return new PeerIdWithIs(cid.multihash.bytes)
}

// Public Key input will be a Uint8Array
Expand Down Expand Up @@ -288,7 +316,7 @@ exports.createFromPrivKey = async (key) => {
}

exports.createFromJSON = async (obj) => {
const id = mh.fromB58String(obj.id)
const id = base58btc.decode('z' + obj.id)
const rawPrivKey = obj.privKey && uint8ArrayFromString(obj.privKey, 'base64pad')
const rawPubKey = obj.pubKey && uint8ArrayFromString(obj.pubKey, 'base64pad')
const pub = rawPubKey && await cryptoKeys.unmarshalPublicKey(rawPubKey)
Expand Down
58 changes: 29 additions & 29 deletions test/peer-id.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@
const { expect } = require('aegir/utils/chai')
const crypto = require('libp2p-crypto')
const mh = require('multihashes')
const CID = require('cids')
const { CID } = require('multiformats/cid')
const Digest = require('multiformats/hashes/digest')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')

const PeerId = require('../src')

const util = require('util')

const DAG_PB_CODE = 0x70
const LIBP2P_KEY_CODE = 0x72
const RAW_CODE = 0x55

const testId = require('./fixtures/sample-id')
const testIdHex = testId.id
const testIdBytes = mh.fromHexString(testId.id)
const testIdDigest = Digest.decode(testIdBytes)
const testIdB58String = mh.toB58String(testIdBytes)
const testIdCID = new CID(1, 'libp2p-key', testIdBytes)
const testIdCIDString = testIdCID.toBaseEncodedString('base32')
const testIdCID = CID.createV1(LIBP2P_KEY_CODE, testIdDigest)
const testIdCIDString = testIdCID.toString()

const goId = require('./fixtures/go-private-key')

Expand Down Expand Up @@ -90,63 +96,55 @@ describe('PeerId', () => {
})

it('recreate from Base58 String (CIDv0))', () => {
const id = PeerId.createFromCID(testIdB58String)
const id = PeerId.createFromCID(CID.parse(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())
const cid = CID.createV1(LIBP2P_KEY_CODE, testIdDigest)
const id = PeerId.createFromCID(cid)
expect(cid.toString()).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)
const cid = CID.createV1(DAG_PB_CODE, testIdDigest)
const id = PeerId.createFromCID(cid)
// toString should return CID with multicodec set to libp2p-key
expect(new CID(id.toString()).codec).to.equal('libp2p-key')
expect(CID.parse(id.toString()).code).to.equal(LIBP2P_KEY_CODE)
expect(testIdBytes).to.deep.equal(id.toBytes())
})

it('recreate from CID Uint8Array', () => {
const id = PeerId.createFromCID(testIdCID.bytes)
const id = PeerId.createFromBytes(testIdCID.bytes)
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')
const invalidCID = CID.createV1(RAW_CODE, testIdDigest)
expect(() => {
PeerId.createFromCID(invalidCID)
}).to.throw(/Supplied PeerID CID has invalid multicodec: raw/)
}).to.throw(/invalid/i)
})

it('throws on invalid CID value', () => {
// using function code that does not represent valid hash function
it('throws on invalid multihash value', () => {
// using function code 0x50 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 = {}
const invalidMultihash = uint8ArrayToString(Uint8Array.from([0x50, 0x1, 0x0]), 'base58btc')
expect(() => {
PeerId.createFromCID(invalidCID)
}).to.throw(/Invalid version, must be a number equal to 1 or 0/)
PeerId.createFromB58String(invalidMultihash)
}).to.throw(/invalid/i)
})

it('throws on invalid CID object', () => {
const invalidCID = {}
expect(() => {
// @ts-expect-error invalid cid is invalid type
PeerId.createFromCID(invalidCID)
}).to.throw(/Invalid version, must be a number equal to 1 or 0/)
}).to.throw(/invalid/i)
})

it('recreate from a Public Key', async () => {
Expand Down Expand Up @@ -209,7 +207,8 @@ describe('PeerId', () => {

it('Pretty printing', async () => {
const id1 = await PeerId.create(testOpts)
const id2 = await PeerId.createFromPrivKey((id1.toJSON()).privKey)
const json = id1.toJSON()
const id2 = await PeerId.createFromPrivKey(json.privKey || 'invalid, should not happen')
expect(id1.toPrint()).to.be.eql(id2.toPrint())
expect(id1.toPrint()).to.equal('<peer.ID ' + id1.toB58String().substr(2, 6) + '>')
})
Expand Down Expand Up @@ -375,6 +374,7 @@ describe('PeerId', () => {
})

it('invalid id', () => {
// @ts-expect-error incorrect constructor arg type
expect(() => new PeerId('hello world')).to.throw(/invalid id/)
})
})
Expand Down