From 5c97417dfd417907ed73af881773ef1efb355c95 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 24 Sep 2021 21:48:01 +0200 Subject: [PATCH 1/5] :sparkles: Add PeerId interface --- packages/interfaces/package.json | 1 + packages/interfaces/src/peer-id/README.md | 44 +++++++ packages/interfaces/src/peer-id/types.d.ts | 137 +++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 packages/interfaces/src/peer-id/README.md create mode 100644 packages/interfaces/src/peer-id/types.d.ts diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index f14558647..f039f6786 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -59,6 +59,7 @@ "dependencies": { "abort-controller": "^3.0.0", "abortable-iterator": "^3.0.0", + "cids": "^1.1.9", "debug": "^4.3.1", "err-code": "^3.0.1", "it-length-prefixed": "^5.0.2", diff --git a/packages/interfaces/src/peer-id/README.md b/packages/interfaces/src/peer-id/README.md new file mode 100644 index 000000000..4f52694c4 --- /dev/null +++ b/packages/interfaces/src/peer-id/README.md @@ -0,0 +1,44 @@ +interface-peer-id +======================== + +> A test suite and interface you can use to implement a PeerId module for libp2p. + +The primary goal of this module is to enable developers to implement PeerId modules. This module and test suite was heavily inspired by earlier implementation of [PeerId](https://github.com/libp2p/js-peer-id). + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through different stacks. + +## Modules that implement the interface + +- [JavaScript libp2p-peer-id](https://github.com/libp2p/js-peer-id) + +Send a PR to add a new one if you happen to find or write one. + +## Badge + +Include this badge in your readme if you make a new module that uses interface-peer-id API. + +![](/img/badge.png) + +## Usage + +### Node.js + +Install `interface-peer-id` as one of the dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: + +```js +const tests = require('libp2p-interfaces-compliance-tests/src/peer-id') + +describe('your peer id', () => { + // use all of the test suits + tests({ + setup () { + return YourPeerId + }, + teardown () { + // Clean up any resources created by setup() + } + }) +}) +``` diff --git a/packages/interfaces/src/peer-id/types.d.ts b/packages/interfaces/src/peer-id/types.d.ts new file mode 100644 index 000000000..3dfa98509 --- /dev/null +++ b/packages/interfaces/src/peer-id/types.d.ts @@ -0,0 +1,137 @@ +/** + * @typedef {import('libp2p-crypto').PrivateKey} PrivateKey + * @typedef {import('libp2p-crypto').PublicKey} PublicKey + * @typedef {import('libp2p-crypto').KeyType} KeyType + * @typedef {import('cids').CIDVersion} CIDVersion + */ + +interface PeerIdJSON { + readonly id: string; + readonly pubKey?: string; + readonly privKey?: string; +} + +interface CreateOptions { + readonly bits?: number; + readonly keyType?: KeyType; +} + +export interface PeerId { + readonly id: Uint8Array; + privKey: PrivateKey | undefined; + pubKey: PublicKey | undefined; + + /** + * Return the protobuf version of the public key, matching go ipfs formatting + */ + marshalPubKey ():Uint8Array | undefined; + + /** + * Return the protobuf version of the private key, matching go ipfs formatting + */ + marshalPrivKey (): Uint8Array | undefined; + + /** + * Return the protobuf version of the peer-id + */ + marshal (excludePriv?: boolean): Uint8Array; + + /** + * String representation + */ + toPrint (): string; + + /** + * The jsonified version of the key, matching the formatting of go-ipfs for its config file + */ + toJSON (): PeerIdJSON; + + /** + * Encode to hex. + */ + toHexString ():string; + + /** + * Return raw id bytes + */ + toBytes () : Uint8Array; + + /** + * Encode to base58 string. + */ + toB58String (): string; + + /** + * Self-describing String representation + * in default format from RFC 0001: https://github.com/libp2p/specs/pull/209 + */ + toString ():string; + + /** + * Checks the equality of `this` peer against a given PeerId. + */ + equals (id: Uint8Array|PeerId): boolean | never; + + /** + * Check if this PeerId instance is valid (privKey -> pubKey -> Id) + */ + isValid (): boolean; + + /** + * Check if the PeerId has an inline public key. + */ + hasInlinePublicKey (): boolean; +} + +export interface PeerIdFactory { + new(id: Uint8Array, privKey?: PrivateKey, pubKey?: PublicKey): PeerId; + /** + * Create a new PeerId. + **/ + async create ({ bits = 2048, keyType = 'RSA' } = {}): Promise; + + /** + * Create PeerId from raw bytes. + */ + createFromBytes (buf: Uint8Array): PeerId; + + /** + * Create PeerId from base58-encoded string. + */ + createFromB58String (str: string): PeerId; + + /** + * Create PeerId from hex string. + */ + createFromHexString (str: string): PeerId; + + /** + * Create PeerId from CID. + */ + createFromCID (cid: CID | CIDVersion | Uint8Array | string): PeerId + + /** + * Create PeerId from public key. + */ + async createFromPubKey (key: Uint8Array | string): Promise; + + /** + * Create PeerId from private key. + */ + async createFromPrivKey (key: Uint8Array | string): Promise; + + /** + * Create PeerId from PeerId JSON formatted object. + */ + async createFromJSON (obj: PeerIdJSON): Promise; + + /** + * Create PeerId from Protobuf bytes. + */ + async createFromProtobuf (buf: Uint8Array | string): Promise; + + /** + * Checks if a value is an instance of PeerId. + */ + isPeerId (peerId:unknown): boolean; +} From f4f00702b6c7dbc1ce7c040821709db5536f8ac0 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 24 Sep 2021 21:48:41 +0200 Subject: [PATCH 2/5] :white_check_mark: Add compliance test for PeerId --- packages/compliance-tests/package.json | 2 + .../compliance-tests/src/peer-id/index.js | 392 ++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 packages/compliance-tests/src/peer-id/index.js diff --git a/packages/compliance-tests/package.json b/packages/compliance-tests/package.json index 05d516d29..1e6c653d3 100644 --- a/packages/compliance-tests/package.json +++ b/packages/compliance-tests/package.json @@ -45,6 +45,8 @@ "it-pipe": "^1.1.0", "libp2p-interfaces": "^1.1.0", "multiaddr": "^10.0.0", + "multihashes": "^4.0.3", + "cids": "^1.1.9", "p-defer": "^3.0.0", "p-limit": "^3.1.0", "p-wait-for": "^3.2.0", diff --git a/packages/compliance-tests/src/peer-id/index.js b/packages/compliance-tests/src/peer-id/index.js new file mode 100644 index 000000000..934738684 --- /dev/null +++ b/packages/compliance-tests/src/peer-id/index.js @@ -0,0 +1,392 @@ +// @ts-nocheck interface tests +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multihashes = require('multihashes') +const CID = require('cids') +const uint8ArrayFromString = require('uint8arrays/from-string') +const uint8ArrayToString = require('uint8arrays/to-string') +const util = require('util') + +const testOpts = { + bits: 512 +} + +const goId = { + id: 'QmRLoXS3E73psYaUsma1VSbboTa2J8Z9kso1tpiGLk9WQ4', + privKey: 'CAASpwkwggSjAgEAAoIBAQDWBEbO8kc6a5kEks09CKPQargY3p0DCmCczoCT52/RYFqxvH9dI+s+u4ZAvF9aLWOBvFomL7jHZODPxKDrbiNCmyEbViNgZYK+PNbwh0V3ZGbB27X3q8yZtLvYA8dhcNkz/2SHBarSoC4QLA5MXUuSWtVaYMY3MzMnzBF57Jc9Ase7NvHOIUI90M7aN5izP7hxPXpZ+shiN+TyjM8mFxYONG7ZSsY3IxUhtrU5MRzFX+tp1o/gb/aa51mHf7AL3N02j5ABiYbCK97Rbwr03hsBcwgMxoDPJmP3WZ+D5yyPcOIIF1Vd7+4/f7FQJnIw3xr9/jvaFbPyDCVbBOhr9oyxAgMBAAECggEALlrgx2Q8v0+c5hux7p1XdgYXd/OHyKfPw0cLHH4NfylCm6q7X34vLvhJHO5wLMUV/3y/ffPqLu4Pr5DkVfoWExAsvJIMuY1jIzdkStbR2glaJHUlVc7VUxmNcj1nSxi5QwT3TjORC2v8bi5Mroeqnbmk6p15cW1akC0oP+NZ4rG48+WFHRqsBaBusdSOVfA+IiZUqSd1ILysJ1w7aVN3EC7jLjDG43i+P/2BcEHy8TVClGOknJL341bHe3UPdEpmeu6k6aHGlDI4blUMXahCIUh0IdZuj+Vi/TxQME9+3bKIOjQb8RCNm3U3j/uz5gs9SyTjBuYIib9Scj/jDbLh0QKBgQDfLr3go3Q/AR0jb12QjGALJz1lc9ZRX2RQJkqqmYkZwOlHHyl+YJgqOZiO80fUkN0sJ29CmKecXU4gXuHir913Fdceei1ScBSsvZpWtBLhEZXKrRJYq8U0atKUFQADDMGutyB/uGCNeNwR6VcJezHPICvHxQfmWlWHA5VIOEtRPQKBgQD1fID76SkIpF/EaJMnN2alXWWnzKhUBUPGpQtbpwgSfaCBiZ4vr3NQwKBntOOB5QwHmifNZMoqaFQLzC4B/uyTNUcQMQQ6arYav7WQXqXTmW6poTsjUSuSOPx1swsHlYX09SmUwWDfd94XF9UOU0KUfA2/c85ixzNlV5ejkFA4hQKBgEvP3uQN4hD82d8Nl2TgqkdfnvV1cdnWY4buWvK0kOPUqelk5n1tZoMBaZc1gLLuOpMjGiIvJNByyXUpheWxA7POEXLi4b5dIEjFZ0YIiVk21gEw5UiFoMl7d+ihcY2Xqbslrb507SdhZLAY6V3pITRQo06K2XIgQWlJiE4uATepAoGBALZ2vEiBnYZW5vfN4tKbUyhGq3B1pggNgbr8odyV4mIcDlk6OOGov0WeZ5ut0AyUesSLyFnaOIoc0ZuTP/8rxBwG1bMrO8FP39sx83pDX25P9PkQZixyALjGsp+pXOFeOhtAvo9azO5M4j638Bydtjc3neBX62dwOLtyx7tDYN0hAoGAVLmr3w7XMVHTfEuCSzKHyRrOaN2PAuSX31QAji1PwlwVKMylVrb8rRvBOpTicA/wXPX9Q5O/yjegqhqLT/LXAm9ziFzy5b9/9SzXPukKebXXbvc0FOmcsrcxtijlPyUzf9fKM1ShiwqqsgM9eNyZ9GWUJw2GFATCWW7pl7rtnWk=' +} + +const testId = { + id: '122019318b6e5e0cf93a2314bf01269a2cc23cd3dcd452d742cdb9379d8646f6e4a9', + privKey: 'CAASpgkwggSiAgEAAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAECggEAZtju/bcKvKFPz0mkHiaJcpycy9STKphorpCT83srBVQi59CdFU6Mj+aL/xt0kCPMVigJw8P3/YCEJ9J+rS8BsoWE+xWUEsJvtXoT7vzPHaAtM3ci1HZd302Mz1+GgS8Epdx+7F5p80XAFLDUnELzOzKftvWGZmWfSeDnslwVONkL/1VAzwKy7Ce6hk4SxRE7l2NE2OklSHOzCGU1f78ZzVYKSnS5Ag9YrGjOAmTOXDbKNKN/qIorAQ1bovzGoCwx3iGIatQKFOxyVCyO1PsJYT7JO+kZbhBWRRE+L7l+ppPER9bdLFxs1t5CrKc078h+wuUr05S1P1JjXk68pk3+kQKBgQDeK8AR11373Mzib6uzpjGzgNRMzdYNuExWjxyxAzz53NAR7zrPHvXvfIqjDScLJ4NcRO2TddhXAfZoOPVH5k4PJHKLBPKuXZpWlookCAyENY7+Pd55S8r+a+MusrMagYNljb5WbVTgN8cgdpim9lbbIFlpN6SZaVjLQL3J8TWH6wKBgQDSChzItkqWX11CNstJ9zJyUE20I7LrpyBJNgG1gtvz3ZMUQCn3PxxHtQzN9n1P0mSSYs+jBKPuoSyYLt1wwe10/lpgL4rkKWU3/m1Myt0tveJ9WcqHh6tzcAbb/fXpUFT/o4SWDimWkPkuCb+8j//2yiXk0a/T2f36zKMuZvujqQKBgC6B7BAQDG2H2B/ijofp12ejJU36nL98gAZyqOfpLJ+FeMz4TlBDQ+phIMhnHXA5UkdDapQ+zA3SrFk+6yGk9Vw4Hf46B+82SvOrSbmnMa+PYqKYIvUzR4gg34rL/7AhwnbEyD5hXq4dHwMNsIDq+l2elPjwm/U9V0gdAl2+r50HAoGALtsKqMvhv8HucAMBPrLikhXP/8um8mMKFMrzfqZ+otxfHzlhI0L08Bo3jQrb0Z7ByNY6M8epOmbCKADsbWcVre/AAY0ZkuSZK/CaOXNX/AhMKmKJh8qAOPRY02LIJRBCpfS4czEdnfUhYV/TYiFNnKRj57PPYZdTzUsxa/yVTmECgYBr7slQEjb5Onn5mZnGDh+72BxLNdgwBkhO0OCdpdISqk0F0Pxby22DFOKXZEpiyI9XYP1C8wPiJsShGm2yEwBPWXnrrZNWczaVuCbXHrZkWQogBDG3HGXNdU4MAWCyiYlyinIBpPpoAJZSzpGLmWbMWh28+RJS6AQX6KHrK1o2uw==', + pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAE=', + marshaled: '0a22122019318b6e5e0cf93a2314bf01269a2cc23cd3dcd452d742cdb9379d8646f6e4a912ab02080012a60230820122300d06092a864886f70d01010105000382010f003082010a0282010100b648aa3f1cc1597819a5d401775e28f3af1adf417749ce378f05901b771a8a47531cea3b911d78a3e875d83e3940934d41845d52dcb9782f08b47001e18207f8e7bb0c839e545b278629e52fd2e720bc2a41c25479710d36d22d0c8338cf58e2d6ab5aedbd26cd7008b6644567ebe43611c1e8df052f591b4b78acfe0d94997f0d8f1030be0c63c93e5edff20ef3979e98ca69a6cc7f658992cdaf383faa2768914bf9bb5a5d1ab7292ee3cd79338393472a281f8e51bb8a8fd1928581020848dac9b24397ddbbea86a52fd82106d49e12fdb492e81ab53bd8cb9f74c05949924bf297e9cfc481f410460c28af5745696ef57627a127dba22c1cbfc3374a5b2302030100011aab09080012a609308204a20201000282010100b648aa3f1cc1597819a5d401775e28f3af1adf417749ce378f05901b771a8a47531cea3b911d78a3e875d83e3940934d41845d52dcb9782f08b47001e18207f8e7bb0c839e545b278629e52fd2e720bc2a41c25479710d36d22d0c8338cf58e2d6ab5aedbd26cd7008b6644567ebe43611c1e8df052f591b4b78acfe0d94997f0d8f1030be0c63c93e5edff20ef3979e98ca69a6cc7f658992cdaf383faa2768914bf9bb5a5d1ab7292ee3cd79338393472a281f8e51bb8a8fd1928581020848dac9b24397ddbbea86a52fd82106d49e12fdb492e81ab53bd8cb9f74c05949924bf297e9cfc481f410460c28af5745696ef57627a127dba22c1cbfc3374a5b2302030100010282010066d8eefdb70abca14fcf49a41e2689729c9ccbd4932a9868ae9093f37b2b055422e7d09d154e8c8fe68bff1b749023cc562809c3c3f7fd808427d27ead2f01b28584fb159412c26fb57a13eefccf1da02d337722d4765ddf4d8ccf5f86812f04a5dc7eec5e69f345c014b0d49c42f33b329fb6f58666659f49e0e7b25c1538d90bff5540cf02b2ec27ba864e12c5113b976344d8e9254873b30865357fbf19cd560a4a74b9020f58ac68ce0264ce5c36ca34a37fa88a2b010d5ba2fcc6a02c31de21886ad40a14ec72542c8ed4fb09613ec93be9196e105645113e2fb97ea693c447d6dd2c5c6cd6de42aca734efc87ec2e52bd394b53f52635e4ebca64dfe9102818100de2bc011d75dfbdccce26fabb3a631b380d44ccdd60db84c568f1cb1033cf9dcd011ef3acf1ef5ef7c8aa30d270b27835c44ed9375d85701f66838f547e64e0f24728b04f2ae5d9a56968a24080c84358efe3dde794bcafe6be32eb2b31a8183658dbe566d54e037c7207698a6f656db20596937a4996958cb40bdc9f13587eb02818100d20a1cc8b64a965f5d4236cb49f73272504db423b2eba720493601b582dbf3dd93144029f73f1c47b50ccdf67d4fd2649262cfa304a3eea12c982edd70c1ed74fe5a602f8ae4296537fe6d4ccadd2dbde27d59ca8787ab737006dbfdf5e95054ffa384960e299690f92e09bfbc8ffff6ca25e4d1afd3d9fdfacca32e66fba3a90281802e81ec10100c6d87d81fe28e87e9d767a3254dfa9cbf7c800672a8e7e92c9f8578ccf84e504343ea6120c8671d70395247436a943ecc0dd2ac593eeb21a4f55c381dfe3a07ef364af3ab49b9a731af8f62a29822f533478820df8acbffb021c276c4c83e615eae1d1f030db080eafa5d9e94f8f09bf53d57481d025dbeaf9d070281802edb0aa8cbe1bfc1ee7003013eb2e29215cfffcba6f2630a14caf37ea67ea2dc5f1f39612342f4f01a378d0adbd19ec1c8d63a33c7a93a66c22800ec6d6715adefc0018d1992e4992bf09a397357fc084c2a628987ca8038f458d362c8251042a5f4b873311d9df521615fd362214d9ca463e7b3cf619753cd4b316bfc954e610281806beec9501236f93a79f99999c60e1fbbd81c4b35d83006484ed0e09da5d212aa4d05d0fc5bcb6d8314e297644a62c88f5760fd42f303e226c4a11a6db213004f5979ebad9356733695b826d71eb664590a200431b71c65cd754e0c0160b28989728a7201a4fa68009652ce918b9966cc5a1dbcf91252e80417e8a1eb2b5a36bb' +} + +const testIdHex = testId.id +const testIdBytes = multihashes.fromHexString(testId.id) +const testIdB58String = multihashes.toB58String(testIdBytes) +const testIdCID = new CID(1, 'libp2p-key', testIdBytes) +const testIdCIDString = testIdCID.toBaseEncodedString('base32') + +module.exports = (common) => { + describe('interface-peer-id compliance tests', () => { + let peerId + let PeerId + + beforeEach(async () => { + peerId = await common.setup() + PeerId = peerId.constructor + }) + + afterEach(() => { + common.teardown && common.teardown() + }) + + it('create an id without \'new\'', () => { + expect(PeerId).to.throw(Error) + }) + + it('create a new id', async () => { + const id = await PeerId.create(testOpts) + expect(id.toB58String().length).to.equal(46) + }) + + it('can be created for a Secp256k1 key', async () => { + const id = await PeerId.create({ keyType: 'secp256k1', bits: 256 }) + const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + expect(id.toB58String()).to.equal(expB58) + }) + + it('isPeerId', async () => { + const id = await PeerId.create(testOpts) + expect(PeerId.isPeerId(id)).to.equal(true) + expect(PeerId.isPeerId('aaa')).to.equal(false) + expect(PeerId.isPeerId(uint8ArrayFromString('batatas'))).to.equal(false) + }) + + it('throws on changing the id', async () => { + const id = await PeerId.create(testOpts) + expect(id.toB58String().length).to.equal(46) + expect(() => { + // @ts-ignore + id.id = uint8ArrayFromString('hello') + }).to.throw(/immutable/) + }) + + it('recreate from Hex string', () => { + const id = PeerId.createFromHexString(testIdHex) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from a Uint8Array', () => { + const id = PeerId.createFromBytes(testIdBytes) + expect(testId.id).to.equal(id.toHexString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + 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 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 Uint8Array', () => { + const id = PeerId.createFromCID(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') + 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 () => { + const id = await PeerId.createFromPrivKey(testId.privKey) + expect(testIdB58String).to.equal(id.toB58String()) + const encoded = uint8ArrayFromString(testId.privKey, 'base64pad') + const id2 = await PeerId.createFromPrivKey(encoded) + expect(testIdB58String).to.equal(id2.toB58String()) + expect(id.marshalPubKey()).to.deep.equal(id2.marshalPubKey()) + }) + + it('recreate from Protobuf', async () => { + const id = await PeerId.createFromProtobuf(testId.marshaled) + expect(testIdB58String).to.equal(id.toB58String()) + const encoded = uint8ArrayFromString(testId.privKey, 'base64pad') + const id2 = await PeerId.createFromPrivKey(encoded) + expect(testIdB58String).to.equal(id2.toB58String()) + expect(id.marshalPubKey()).to.deep.equal(id2.marshalPubKey()) + expect(uint8ArrayToString(id.marshal(), 'base16')).to.deep.equal(testId.marshaled) + }) + + it('can be created from a Secp256k1 public key', async () => { + const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) + const id = await PeerId.createFromPubKey(privKey.public.bytes) + const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + expect(id.toB58String()).to.equal(expB58) + }) + + it('can be created from a Secp256k1 private key', async () => { + const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) + const id = await PeerId.createFromPrivKey(privKey.bytes) + const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + expect(id.toB58String()).to.equal(expB58) + }) + + it('Compare generated ID with one created from PubKey', async () => { + const id1 = await PeerId.create(testOpts) + const id2 = await PeerId.createFromPubKey(id1.marshalPubKey()) + expect(id1.id).to.be.eql(id2.id) + }) + + it('Works with default options', async function () { + const id = await PeerId.create() + expect(id.toB58String().length).to.equal(46) + }) + + it('Non-default # of bits', async function () { + this.timeout(1000 * 60) + const shortId = await PeerId.create(testOpts) + const longId = await PeerId.create({ bits: 1024 }) + expect(shortId.privKey.bytes.length).is.below(longId.privKey.bytes.length) + }) + + it('Pretty printing', async () => { + const id1 = await PeerId.create(testOpts) + const id2 = await PeerId.createFromPrivKey((id1.toJSON()).privKey) + expect(id1.toPrint()).to.be.eql(id2.toPrint()) + expect(id1.toPrint()).to.equal('') + }) + + it('toBytes', () => { + const id = PeerId.createFromHexString(testIdHex) + expect(uint8ArrayToString(id.toBytes(), 'base16')).to.equal(uint8ArrayToString(testIdBytes, 'base16')) + }) + + it('isEqual', async () => { + const ids = await Promise.all([ + PeerId.create(testOpts), + PeerId.create(testOpts) + ]) + + expect(ids[0].isEqual(ids[0])).to.equal(true) + expect(ids[0].isEqual(ids[1])).to.equal(false) + expect(ids[0].isEqual(ids[0].id)).to.equal(true) + expect(ids[0].isEqual(ids[1].id)).to.equal(false) + }) + + it('equals', async () => { + const ids = await Promise.all([ + PeerId.create(testOpts), + PeerId.create(testOpts) + ]) + + expect(ids[0].equals(ids[0])).to.equal(true) + expect(ids[0].equals(ids[1])).to.equal(false) + expect(ids[0].equals(ids[0].id)).to.equal(true) + expect(ids[0].equals(ids[1].id)).to.equal(false) + }) + + describe('hasInlinePublicKey', () => { + it('returns true if uses a key type with inline public key', async () => { + const peerId = await PeerId.create({ keyType: 'secp256k1' }) + expect(peerId.hasInlinePublicKey()).to.equal(true) + }) + + it('returns false if uses a key type with no inline public key', async () => { + const peerId = await PeerId.create({ keyType: 'RSA' }) + expect(peerId.hasInlinePublicKey()).to.equal(false) + }) + }) + + describe('fromJSON', () => { + it('full node', async () => { + const id = await PeerId.create(testOpts) + const other = await PeerId.createFromJSON(id.toJSON()) + expect(id.toB58String()).to.equal(other.toB58String()) + expect(id.privKey.bytes).to.eql(other.privKey.bytes) + expect(id.pubKey.bytes).to.eql(other.pubKey.bytes) + }) + + it('only id', async () => { + const key = await crypto.keys.generateKeyPair('RSA', 1024) + const digest = await key.public.hash() + const id = PeerId.createFromBytes(digest) + expect(id.privKey).to.not.exist() + expect(id.pubKey).to.not.exist() + const other = await PeerId.createFromJSON(id.toJSON()) + expect(id.toB58String()).to.equal(other.toB58String()) + }) + + it('go interop', async () => { + const id = await PeerId.createFromJSON(goId) + const digest = await id.privKey.public.hash() + expect(multihashes.toB58String(digest)).to.eql(goId.id) + }) + }) + + it('set privKey (valid)', async () => { + const peerId = await PeerId.create(testOpts) + // @ts-ignore + peerId.privKey = peerId._privKey + expect(peerId.isValid()).to.equal(true) + }) + + it('set pubKey (valid)', async () => { + const peerId = await PeerId.create(testOpts) + // @ts-ignore + peerId.pubKey = peerId._pubKey + expect(peerId.isValid()).to.equal(true) + }) + + it('set privKey (invalid)', async () => { + const peerId = await PeerId.create(testOpts) + // @ts-ignore + peerId.privKey = uint8ArrayFromString('bufff') + expect(peerId.isValid()).to.equal(false) + }) + + it('set pubKey (invalid)', async () => { + const peerId = await PeerId.create(testOpts) + // @ts-ignore + peerId.pubKey = uint8ArrayFromString('bufff') + expect(peerId.isValid()).to.equal(false) + }) + + it('keys are equal after one is stringified', async () => { + const peerId = await PeerId.create(testOpts) + const peerId1 = PeerId.createFromB58String(peerId.toB58String()) + const peerId2 = PeerId.createFromB58String(peerId.toB58String()) + + expect(peerId1).to.deep.equal(peerId2) + + peerId1.toString() + expect(peerId1).to.deep.equal(peerId2) + }) + + describe('returns error via cb instead of crashing', () => { + const garbage = [ + uint8ArrayFromString('00010203040506070809', 'base16'), + {}, null, false, undefined, true, 1, 0, + uint8ArrayFromString(''), 'aGVsbG93b3JsZA==', 'helloworld', '' + ] + + const fncs = ['createFromPubKey', 'createFromPrivKey', 'createFromJSON', 'createFromProtobuf'] + + for (const gb of garbage) { + for (const fn of fncs) { + // Need to loop over each test case + // eslint-disable-next-line no-loop-func + it(`${fn} (${util.inspect(gb)})`, async () => { + try { + await PeerId[fn](gb) + } catch (err) { + expect(err).to.exist() + } + }) + } + } + }) + + describe('throws on inconsistent data', () => { + let k1 + let k2 + let k3 + + before(async () => { + const keys = await Promise.all([ + crypto.keys.generateKeyPair('RSA', 512), + crypto.keys.generateKeyPair('RSA', 512), + crypto.keys.generateKeyPair('RSA', 512) + ]) + + k1 = keys[0] + k2 = keys[1] + k3 = keys[2] + }) + + it('missmatch private - public key', async () => { + const digest = await k1.public.hash() + expect(() => { + new PeerId(digest, k1, k2.public) // eslint-disable-line no-new + }).to.throw(/inconsistent arguments/) + }) + + it('missmatch id - private - public key', async () => { + const digest = await k1.public.hash() + expect(() => { + new PeerId(digest, k1, k3.public) // eslint-disable-line no-new + }).to.throw(/inconsistent arguments/) + }) + + it('invalid id', () => { + expect(() => new PeerId('hello world')).to.throw(/invalid id/) + }) + }) + }) +} From 06b9e3a94cbf8049c40f069d66a901bd598d5aca Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 3 Nov 2021 00:01:39 +0100 Subject: [PATCH 3/5] :art: Update usage of "cids" package with "multiformats/cid" --- packages/compliance-tests/package.json | 2 +- packages/compliance-tests/src/peer-id/index.js | 2 +- packages/interfaces/src/peer-id/types.d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/compliance-tests/package.json b/packages/compliance-tests/package.json index 1e6c653d3..00fd577cb 100644 --- a/packages/compliance-tests/package.json +++ b/packages/compliance-tests/package.json @@ -46,7 +46,7 @@ "libp2p-interfaces": "^1.1.0", "multiaddr": "^10.0.0", "multihashes": "^4.0.3", - "cids": "^1.1.9", + "multiformats": "^9.4.9", "p-defer": "^3.0.0", "p-limit": "^3.1.0", "p-wait-for": "^3.2.0", diff --git a/packages/compliance-tests/src/peer-id/index.js b/packages/compliance-tests/src/peer-id/index.js index 934738684..77c03386c 100644 --- a/packages/compliance-tests/src/peer-id/index.js +++ b/packages/compliance-tests/src/peer-id/index.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const multihashes = require('multihashes') -const CID = require('cids') +const { CID } = require('multiformats/cid') const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') const util = require('util') diff --git a/packages/interfaces/src/peer-id/types.d.ts b/packages/interfaces/src/peer-id/types.d.ts index 3dfa98509..438766cdd 100644 --- a/packages/interfaces/src/peer-id/types.d.ts +++ b/packages/interfaces/src/peer-id/types.d.ts @@ -2,7 +2,7 @@ * @typedef {import('libp2p-crypto').PrivateKey} PrivateKey * @typedef {import('libp2p-crypto').PublicKey} PublicKey * @typedef {import('libp2p-crypto').KeyType} KeyType - * @typedef {import('cids').CIDVersion} CIDVersion + * @typedef {import('multiformats/cid').CIDVersion} CIDVersion */ interface PeerIdJSON { From 2828eeb4301feadd83836f3873e9a54bf5513039 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 3 Nov 2021 14:58:26 +0000 Subject: [PATCH 4/5] chore: add missing code and tests from peer-id@0.15.x --- packages/compliance-tests/package.json | 2 +- .../compliance-tests/src/peer-id/index.js | 142 ++++++++++-------- packages/interfaces/src/crypto/types.d.ts | 34 +---- packages/interfaces/src/keys/README.md | 25 +++ packages/interfaces/src/keys/types.d.ts | 34 +++++ packages/interfaces/src/peer-id/types.d.ts | 30 ++-- .../interfaces/src/value-store/types.d.ts | 2 +- 7 files changed, 156 insertions(+), 113 deletions(-) create mode 100644 packages/interfaces/src/keys/README.md create mode 100644 packages/interfaces/src/keys/types.d.ts diff --git a/packages/compliance-tests/package.json b/packages/compliance-tests/package.json index 7af7a9da8..107b3834d 100644 --- a/packages/compliance-tests/package.json +++ b/packages/compliance-tests/package.json @@ -43,9 +43,9 @@ "it-goodbye": "^3.0.0", "it-pair": "^1.0.0", "it-pipe": "^1.1.0", + "libp2p-crypto": "^0.19.5", "libp2p-interfaces": "^1.2.0", "multiaddr": "^10.0.0", - "multihashes": "^4.0.3", "multiformats": "^9.4.9", "p-defer": "^3.0.0", "p-limit": "^3.1.0", diff --git a/packages/compliance-tests/src/peer-id/index.js b/packages/compliance-tests/src/peer-id/index.js index 77c03386c..7633a3549 100644 --- a/packages/compliance-tests/src/peer-id/index.js +++ b/packages/compliance-tests/src/peer-id/index.js @@ -3,11 +3,19 @@ 'use strict' const { expect } = require('aegir/utils/chai') -const multihashes = require('multihashes') +const crypto = require('libp2p-crypto') const { CID } = require('multiformats/cid') -const uint8ArrayFromString = require('uint8arrays/from-string') -const uint8ArrayToString = require('uint8arrays/to-string') -const util = require('util') +const Digest = require('multiformats/hashes/digest') +const { base16 } = require('multiformats/bases/base16') +const { base36 } = require('multiformats/bases/base36') +const { base58btc } = require('multiformats/bases/base58') +const { identity } = require('multiformats/hashes/identity') +const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') +const { toString: uint8ArrayToString } = require('uint8arrays/to-string') + +const DAG_PB_CODE = 0x70 +const LIBP2P_KEY_CODE = 0x72 +const RAW_CODE = 0x55 const testOpts = { bits: 512 @@ -26,10 +34,12 @@ const testId = { } const testIdHex = testId.id -const testIdBytes = multihashes.fromHexString(testId.id) -const testIdB58String = multihashes.toB58String(testIdBytes) -const testIdCID = new CID(1, 'libp2p-key', testIdBytes) -const testIdCIDString = testIdCID.toBaseEncodedString('base32') +const testIdBytes = base16.decode(`f${testId.id}`) +const testIdDigest = Digest.decode(testIdBytes) +const testIdB58String = base58btc.encode(testIdBytes).substring(1) +const testIdB36String = base36.encode(testIdBytes) +const testIdCID = CID.createV1(LIBP2P_KEY_CODE, testIdDigest) +const testIdCIDString = testIdCID.toString() module.exports = (common) => { describe('interface-peer-id compliance tests', () => { @@ -56,10 +66,16 @@ module.exports = (common) => { it('can be created for a Secp256k1 key', async () => { const id = await PeerId.create({ keyType: 'secp256k1', bits: 256 }) - const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) + it('can get the public key from a Secp256k1 key', async () => { + const original = await PeerId.create({ keyType: 'secp256k1', bits: 256 }) + const newId = PeerId.createFromB58String(original.toB58String()) + expect(original.pubKey.bytes).to.eql(newId.pubKey.bytes) + }) + it('isPeerId', async () => { const id = await PeerId.create(testOpts) expect(PeerId.isPeerId(id)).to.equal(true) @@ -99,64 +115,62 @@ module.exports = (common) => { expect(testIdBytes).to.deep.equal(id.toBytes()) }) - it('recreate from Base58 String (CIDv0))', () => { - const id = PeerId.createFromCID(testIdB58String) + it('recreate from Base58 String (CIDv0)', () => { + const id = PeerId.createFromCID(CID.parse(testIdB58String)) + expect(testIdCIDString).to.equal(id.toString()) + expect(testIdBytes).to.deep.equal(id.toBytes()) + }) + + it('recreate from Base36 String', () => { + const id = PeerId.parse(testIdB36String) 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' + const invalidMultihash = uint8ArrayToString(Uint8Array.from([0x50, 0x1, 0x0]), 'base58btc') 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/) + 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 () => { @@ -184,17 +198,39 @@ module.exports = (common) => { expect(uint8ArrayToString(id.marshal(), 'base16')).to.deep.equal(testId.marshaled) }) + it('recreate from embedded ed25519 key', async () => { + const key = '12D3KooWRm8J3iL796zPFi2EtGGtUJn58AG67gcqzMFHZnnsTzqD' + const id = await PeerId.parse(key) + expect(id.toB58String()).to.equal(key) + const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) + expect(id.toB58String()).to.equal(expB58) + }) + + it('recreate from embedded secp256k1 key', async () => { + const key = '16Uiu2HAm5qw8UyXP2RLxQUx5KvtSN8DsTKz8quRGqGNC3SYiaB8E' + const id = await PeerId.parse(key) + expect(id.toB58String()).to.equal(key) + const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) + expect(id.toB58String()).to.equal(expB58) + }) + + it('recreate from string key', async () => { + const key = 'QmRsooYQasV5f5r834NSpdUtmejdQcpxXkK6qsozZWEihC' + const id = await PeerId.parse(key) + expect(id.toB58String()).to.equal(key) + }) + it('can be created from a Secp256k1 public key', async () => { const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) const id = await PeerId.createFromPubKey(privKey.public.bytes) - const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) it('can be created from a Secp256k1 private key', async () => { const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) const id = await PeerId.createFromPrivKey(privKey.bytes) - const expB58 = multihashes.toB58String(multihashes.encode(id.pubKey.bytes, 'identity')) + const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) @@ -205,6 +241,7 @@ module.exports = (common) => { }) it('Works with default options', async function () { + this.timeout(10000) const id = await PeerId.create() expect(id.toB58String().length).to.equal(46) }) @@ -218,7 +255,8 @@ module.exports = (common) => { 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('') }) @@ -286,7 +324,7 @@ module.exports = (common) => { it('go interop', async () => { const id = await PeerId.createFromJSON(goId) const digest = await id.privKey.public.hash() - expect(multihashes.toB58String(digest)).to.eql(goId.id) + expect(base58btc.encode(digest).slice(1)).to.eql(goId.id) }) }) @@ -326,31 +364,8 @@ module.exports = (common) => { expect(peerId1).to.deep.equal(peerId2) peerId1.toString() - expect(peerId1).to.deep.equal(peerId2) - }) - describe('returns error via cb instead of crashing', () => { - const garbage = [ - uint8ArrayFromString('00010203040506070809', 'base16'), - {}, null, false, undefined, true, 1, 0, - uint8ArrayFromString(''), 'aGVsbG93b3JsZA==', 'helloworld', '' - ] - - const fncs = ['createFromPubKey', 'createFromPrivKey', 'createFromJSON', 'createFromProtobuf'] - - for (const gb of garbage) { - for (const fn of fncs) { - // Need to loop over each test case - // eslint-disable-next-line no-loop-func - it(`${fn} (${util.inspect(gb)})`, async () => { - try { - await PeerId[fn](gb) - } catch (err) { - expect(err).to.exist() - } - }) - } - } + expect(peerId1).to.deep.equal(peerId2) }) describe('throws on inconsistent data', () => { @@ -385,6 +400,7 @@ module.exports = (common) => { }) it('invalid id', () => { + // @ts-expect-error incorrect constructor arg type expect(() => new PeerId('hello world')).to.throw(/invalid id/) }) }) diff --git a/packages/interfaces/src/crypto/types.d.ts b/packages/interfaces/src/crypto/types.d.ts index c094edfd8..0815d0d04 100644 --- a/packages/interfaces/src/crypto/types.d.ts +++ b/packages/interfaces/src/crypto/types.d.ts @@ -1,4 +1,4 @@ -import PeerId from 'peer-id' +import { PeerId } from '../peer-id/types' import { MultiaddrConnection } from '../transport/types' /** @@ -22,35 +22,3 @@ export type SecureOutbound = { remoteEarlyData: Buffer; remotePeer: PeerId; } - -export interface PublicKey { - readonly bytes: Uint8Array - verify: (data: Uint8Array, sig: Uint8Array) => Promise - marshal: () => Uint8Array - equals: (key: PublicKey) => boolean - hash: () => Promise -} - -/** - * Generic private key interface - */ -export interface PrivateKey { - readonly public: PublicKey - readonly bytes: Uint8Array - sign: (data: Uint8Array) => Promise - marshal: () => Uint8Array - equals: (key: PrivateKey) => boolean - hash: () => Promise - /** - * Gets the ID of the key. - * - * The key id is the base58 encoding of the SHA-256 multihash of its public key. - * The public key is a protobuf encoding containing a type and the DER encoding - * of the PKCS SubjectPublicKeyInfo. - */ - id: () => Promise - /** - * Exports the password protected key in the format specified. - */ - export: (password: string, format?: 'pkcs-8' | string) => Promise -} diff --git a/packages/interfaces/src/keys/README.md b/packages/interfaces/src/keys/README.md new file mode 100644 index 000000000..c158729d7 --- /dev/null +++ b/packages/interfaces/src/keys/README.md @@ -0,0 +1,25 @@ +# interface-keys + +> Interfaces for libp2p keys + +## Table of Contents +- [Using the Test Suite](#using-the-test-suite) + +## Using the Test Suite + +You can also check out the [internal test suite](../../test/crypto/compliance.spec.js) to see the setup in action. + +```js +const tests = require('libp2p-interfaces-compliance-tests/src/keys') +const yourKeys = require('./your-keys') + +tests({ + setup () { + // Set up your keys if needed, then return it + return yourKeys + }, + teardown () { + // Clean up your keys if needed + } +}) +``` diff --git a/packages/interfaces/src/keys/types.d.ts b/packages/interfaces/src/keys/types.d.ts new file mode 100644 index 000000000..fbb9a3039 --- /dev/null +++ b/packages/interfaces/src/keys/types.d.ts @@ -0,0 +1,34 @@ + +export interface PublicKey { + readonly bytes: Uint8Array + verify: (data: Uint8Array, sig: Uint8Array) => Promise + marshal: () => Uint8Array + equals: (key: PublicKey) => boolean + hash: () => Promise +} + +/** + * Generic private key interface + */ +export interface PrivateKey { + readonly public: PublicKey + readonly bytes: Uint8Array + sign: (data: Uint8Array) => Promise + marshal: () => Uint8Array + equals: (key: PrivateKey) => boolean + hash: () => Promise + /** + * Gets the ID of the key. + * + * The key id is the base58 encoding of the SHA-256 multihash of its public key. + * The public key is a protobuf encoding containing a type and the DER encoding + * of the PKCS SubjectPublicKeyInfo. + */ + id: () => Promise + /** + * Exports the password protected key in the format specified. + */ + export: (password: string, format?: 'pkcs-8' | string) => Promise +} + +export type KeyType = 'Ed25519' | 'RSA' | 'secp256k1' diff --git a/packages/interfaces/src/peer-id/types.d.ts b/packages/interfaces/src/peer-id/types.d.ts index 438766cdd..d469c0eb4 100644 --- a/packages/interfaces/src/peer-id/types.d.ts +++ b/packages/interfaces/src/peer-id/types.d.ts @@ -1,9 +1,5 @@ -/** - * @typedef {import('libp2p-crypto').PrivateKey} PrivateKey - * @typedef {import('libp2p-crypto').PublicKey} PublicKey - * @typedef {import('libp2p-crypto').KeyType} KeyType - * @typedef {import('multiformats/cid').CIDVersion} CIDVersion - */ +import type { CID } from 'multiformats/cid' +import type { PublicKey, PrivateKey, KeyType } from '../keys/types' interface PeerIdJSON { readonly id: string; @@ -12,8 +8,8 @@ interface PeerIdJSON { } interface CreateOptions { - readonly bits?: number; - readonly keyType?: KeyType; + bits?: number; + keyType?: KeyType; } export interface PeerId { @@ -84,11 +80,10 @@ export interface PeerId { } export interface PeerIdFactory { - new(id: Uint8Array, privKey?: PrivateKey, pubKey?: PublicKey): PeerId; /** * Create a new PeerId. **/ - async create ({ bits = 2048, keyType = 'RSA' } = {}): Promise; + create (args: CreateOptions): Promise; /** * Create PeerId from raw bytes. @@ -108,27 +103,32 @@ export interface PeerIdFactory { /** * Create PeerId from CID. */ - createFromCID (cid: CID | CIDVersion | Uint8Array | string): PeerId + createFromCID (cid: CID | Uint8Array | string): PeerId /** * Create PeerId from public key. */ - async createFromPubKey (key: Uint8Array | string): Promise; + createFromPubKey (key: Uint8Array | string): Promise; /** * Create PeerId from private key. */ - async createFromPrivKey (key: Uint8Array | string): Promise; + createFromPrivKey (key: Uint8Array | string): Promise; /** * Create PeerId from PeerId JSON formatted object. */ - async createFromJSON (obj: PeerIdJSON): Promise; + createFromJSON (obj: PeerIdJSON): Promise; /** * Create PeerId from Protobuf bytes. */ - async createFromProtobuf (buf: Uint8Array | string): Promise; + createFromProtobuf (buf: Uint8Array | string): Promise; + + /** + * Parse PeerId from string, maybe base58btc encoded without multibase prefix + */ + parse (str: string): PeerId /** * Checks if a value is an instance of PeerId. diff --git a/packages/interfaces/src/value-store/types.d.ts b/packages/interfaces/src/value-store/types.d.ts index e3aa37aff..ed89ac6a1 100644 --- a/packages/interfaces/src/value-store/types.d.ts +++ b/packages/interfaces/src/value-store/types.d.ts @@ -1,4 +1,4 @@ -import type PeerId from 'peer-id' +import type { PeerId } from '../peer-id/types' export interface GetValueResult { from: PeerId, From fbc008d649b223d1aeddc121f21f0f94d0c6b2cc Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 3 Nov 2021 15:24:34 +0000 Subject: [PATCH 5/5] chore: return factory from test setup --- .../compliance-tests/src/peer-id/index.js | 139 +++++++++--------- packages/interfaces/src/peer-id/README.md | 4 +- 2 files changed, 73 insertions(+), 70 deletions(-) diff --git a/packages/compliance-tests/src/peer-id/index.js b/packages/compliance-tests/src/peer-id/index.js index 7633a3549..fa20d01ed 100644 --- a/packages/compliance-tests/src/peer-id/index.js +++ b/packages/compliance-tests/src/peer-id/index.js @@ -43,48 +43,43 @@ const testIdCIDString = testIdCID.toString() module.exports = (common) => { describe('interface-peer-id compliance tests', () => { - let peerId - let PeerId + /** @type {import('libp2p-interfaces/src/peer-id/types').PeerIdFactory} */ + let factory beforeEach(async () => { - peerId = await common.setup() - PeerId = peerId.constructor + factory = await common.setup() }) afterEach(() => { common.teardown && common.teardown() }) - it('create an id without \'new\'', () => { - expect(PeerId).to.throw(Error) - }) - it('create a new id', async () => { - const id = await PeerId.create(testOpts) + const id = await factory.create(testOpts) expect(id.toB58String().length).to.equal(46) }) it('can be created for a Secp256k1 key', async () => { - const id = await PeerId.create({ keyType: 'secp256k1', bits: 256 }) + const id = await factory.create({ keyType: 'secp256k1', bits: 256 }) const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) it('can get the public key from a Secp256k1 key', async () => { - const original = await PeerId.create({ keyType: 'secp256k1', bits: 256 }) - const newId = PeerId.createFromB58String(original.toB58String()) + const original = await factory.create({ keyType: 'secp256k1', bits: 256 }) + const newId = factory.createFromB58String(original.toB58String()) expect(original.pubKey.bytes).to.eql(newId.pubKey.bytes) }) it('isPeerId', async () => { - const id = await PeerId.create(testOpts) - expect(PeerId.isPeerId(id)).to.equal(true) - expect(PeerId.isPeerId('aaa')).to.equal(false) - expect(PeerId.isPeerId(uint8ArrayFromString('batatas'))).to.equal(false) + const id = await factory.create(testOpts) + expect(factory.isPeerId(id)).to.equal(true) + expect(factory.isPeerId('aaa')).to.equal(false) + expect(factory.isPeerId(uint8ArrayFromString('batatas'))).to.equal(false) }) it('throws on changing the id', async () => { - const id = await PeerId.create(testOpts) + const id = await factory.create(testOpts) expect(id.toB58String().length).to.equal(46) expect(() => { // @ts-ignore @@ -93,57 +88,57 @@ module.exports = (common) => { }) it('recreate from Hex string', () => { - const id = PeerId.createFromHexString(testIdHex) + const id = factory.createFromHexString(testIdHex) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from a Uint8Array', () => { - const id = PeerId.createFromBytes(testIdBytes) + const id = factory.createFromBytes(testIdBytes) expect(testId.id).to.equal(id.toHexString()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from a B58 String', () => { - const id = PeerId.createFromB58String(testIdB58String) + const id = factory.createFromB58String(testIdB58String) expect(testIdB58String).to.equal(id.toB58String()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from CID object', () => { - const id = PeerId.createFromCID(testIdCID) + const id = factory.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(CID.parse(testIdB58String)) + const id = factory.createFromCID(CID.parse(testIdB58String)) expect(testIdCIDString).to.equal(id.toString()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from Base36 String', () => { - const id = PeerId.parse(testIdB36String) + const id = factory.parse(testIdB36String) expect(testIdCIDString).to.equal(id.toString()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from CIDv1 Base32 (libp2p-key multicodec)', () => { const cid = CID.createV1(LIBP2P_KEY_CODE, testIdDigest) - const id = PeerId.createFromCID(cid) + const id = factory.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 = CID.createV1(DAG_PB_CODE, testIdDigest) - const id = PeerId.createFromCID(cid) + const id = factory.createFromCID(cid) // toString should return CID with multicodec set to 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.createFromBytes(testIdCID.bytes) + const id = factory.createFromBytes(testIdCID.bytes) expect(testIdCIDString).to.equal(id.toString()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) @@ -152,7 +147,7 @@ module.exports = (common) => { // only libp2p and dag-pb are supported const invalidCID = CID.createV1(RAW_CODE, testIdDigest) expect(() => { - PeerId.createFromCID(invalidCID) + factory.createFromCID(invalidCID) }).to.throw(/invalid/i) }) @@ -161,7 +156,7 @@ module.exports = (common) => { // https://github.com/multiformats/js-multihash/blob/b85999d5768bf06f1b0f16b926ef2cb6d9c14265/src/constants.js#L345 const invalidMultihash = uint8ArrayToString(Uint8Array.from([0x50, 0x1, 0x0]), 'base58btc') expect(() => { - PeerId.createFromB58String(invalidMultihash) + factory.createFromB58String(invalidMultihash) }).to.throw(/invalid/i) }) @@ -169,30 +164,30 @@ module.exports = (common) => { const invalidCID = {} expect(() => { // @ts-expect-error invalid cid is invalid type - PeerId.createFromCID(invalidCID) + factory.createFromCID(invalidCID) }).to.throw(/invalid/i) }) it('recreate from a Public Key', async () => { - const id = await PeerId.createFromPubKey(testId.pubKey) + const id = await factory.createFromPubKey(testId.pubKey) expect(testIdB58String).to.equal(id.toB58String()) expect(testIdBytes).to.deep.equal(id.toBytes()) }) it('recreate from a Private Key', async () => { - const id = await PeerId.createFromPrivKey(testId.privKey) + const id = await factory.createFromPrivKey(testId.privKey) expect(testIdB58String).to.equal(id.toB58String()) const encoded = uint8ArrayFromString(testId.privKey, 'base64pad') - const id2 = await PeerId.createFromPrivKey(encoded) + const id2 = await factory.createFromPrivKey(encoded) expect(testIdB58String).to.equal(id2.toB58String()) expect(id.marshalPubKey()).to.deep.equal(id2.marshalPubKey()) }) it('recreate from Protobuf', async () => { - const id = await PeerId.createFromProtobuf(testId.marshaled) + const id = await factory.createFromProtobuf(testId.marshaled) expect(testIdB58String).to.equal(id.toB58String()) const encoded = uint8ArrayFromString(testId.privKey, 'base64pad') - const id2 = await PeerId.createFromPrivKey(encoded) + const id2 = await factory.createFromPrivKey(encoded) expect(testIdB58String).to.equal(id2.toB58String()) expect(id.marshalPubKey()).to.deep.equal(id2.marshalPubKey()) expect(uint8ArrayToString(id.marshal(), 'base16')).to.deep.equal(testId.marshaled) @@ -200,7 +195,7 @@ module.exports = (common) => { it('recreate from embedded ed25519 key', async () => { const key = '12D3KooWRm8J3iL796zPFi2EtGGtUJn58AG67gcqzMFHZnnsTzqD' - const id = await PeerId.parse(key) + const id = await factory.parse(key) expect(id.toB58String()).to.equal(key) const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) @@ -208,7 +203,7 @@ module.exports = (common) => { it('recreate from embedded secp256k1 key', async () => { const key = '16Uiu2HAm5qw8UyXP2RLxQUx5KvtSN8DsTKz8quRGqGNC3SYiaB8E' - const id = await PeerId.parse(key) + const id = await factory.parse(key) expect(id.toB58String()).to.equal(key) const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) @@ -216,60 +211,60 @@ module.exports = (common) => { it('recreate from string key', async () => { const key = 'QmRsooYQasV5f5r834NSpdUtmejdQcpxXkK6qsozZWEihC' - const id = await PeerId.parse(key) + const id = await factory.parse(key) expect(id.toB58String()).to.equal(key) }) it('can be created from a Secp256k1 public key', async () => { const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) - const id = await PeerId.createFromPubKey(privKey.public.bytes) + const id = await factory.createFromPubKey(privKey.public.bytes) const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) it('can be created from a Secp256k1 private key', async () => { const privKey = await crypto.keys.generateKeyPair('secp256k1', 256) - const id = await PeerId.createFromPrivKey(privKey.bytes) + const id = await factory.createFromPrivKey(privKey.bytes) const expB58 = base58btc.encode((await identity.digest(id.pubKey.bytes)).bytes).slice(1) expect(id.toB58String()).to.equal(expB58) }) it('Compare generated ID with one created from PubKey', async () => { - const id1 = await PeerId.create(testOpts) - const id2 = await PeerId.createFromPubKey(id1.marshalPubKey()) + const id1 = await factory.create(testOpts) + const id2 = await factory.createFromPubKey(id1.marshalPubKey()) expect(id1.id).to.be.eql(id2.id) }) it('Works with default options', async function () { this.timeout(10000) - const id = await PeerId.create() + const id = await factory.create() expect(id.toB58String().length).to.equal(46) }) it('Non-default # of bits', async function () { this.timeout(1000 * 60) - const shortId = await PeerId.create(testOpts) - const longId = await PeerId.create({ bits: 1024 }) + const shortId = await factory.create(testOpts) + const longId = await factory.create({ bits: 1024 }) expect(shortId.privKey.bytes.length).is.below(longId.privKey.bytes.length) }) it('Pretty printing', async () => { - const id1 = await PeerId.create(testOpts) + const id1 = await factory.create(testOpts) const json = id1.toJSON() - const id2 = await PeerId.createFromPrivKey(json.privKey || 'invalid, should not happen') + const id2 = await factory.createFromPrivKey(json.privKey || 'invalid, should not happen') expect(id1.toPrint()).to.be.eql(id2.toPrint()) expect(id1.toPrint()).to.equal('') }) it('toBytes', () => { - const id = PeerId.createFromHexString(testIdHex) + const id = factory.createFromHexString(testIdHex) expect(uint8ArrayToString(id.toBytes(), 'base16')).to.equal(uint8ArrayToString(testIdBytes, 'base16')) }) it('isEqual', async () => { const ids = await Promise.all([ - PeerId.create(testOpts), - PeerId.create(testOpts) + factory.create(testOpts), + factory.create(testOpts) ]) expect(ids[0].isEqual(ids[0])).to.equal(true) @@ -280,8 +275,8 @@ module.exports = (common) => { it('equals', async () => { const ids = await Promise.all([ - PeerId.create(testOpts), - PeerId.create(testOpts) + factory.create(testOpts), + factory.create(testOpts) ]) expect(ids[0].equals(ids[0])).to.equal(true) @@ -292,20 +287,20 @@ module.exports = (common) => { describe('hasInlinePublicKey', () => { it('returns true if uses a key type with inline public key', async () => { - const peerId = await PeerId.create({ keyType: 'secp256k1' }) + const peerId = await factory.create({ keyType: 'secp256k1' }) expect(peerId.hasInlinePublicKey()).to.equal(true) }) it('returns false if uses a key type with no inline public key', async () => { - const peerId = await PeerId.create({ keyType: 'RSA' }) + const peerId = await factory.create({ keyType: 'RSA' }) expect(peerId.hasInlinePublicKey()).to.equal(false) }) }) describe('fromJSON', () => { it('full node', async () => { - const id = await PeerId.create(testOpts) - const other = await PeerId.createFromJSON(id.toJSON()) + const id = await factory.create(testOpts) + const other = await factory.createFromJSON(id.toJSON()) expect(id.toB58String()).to.equal(other.toB58String()) expect(id.privKey.bytes).to.eql(other.privKey.bytes) expect(id.pubKey.bytes).to.eql(other.pubKey.bytes) @@ -314,52 +309,52 @@ module.exports = (common) => { it('only id', async () => { const key = await crypto.keys.generateKeyPair('RSA', 1024) const digest = await key.public.hash() - const id = PeerId.createFromBytes(digest) + const id = factory.createFromBytes(digest) expect(id.privKey).to.not.exist() expect(id.pubKey).to.not.exist() - const other = await PeerId.createFromJSON(id.toJSON()) + const other = await factory.createFromJSON(id.toJSON()) expect(id.toB58String()).to.equal(other.toB58String()) }) it('go interop', async () => { - const id = await PeerId.createFromJSON(goId) + const id = await factory.createFromJSON(goId) const digest = await id.privKey.public.hash() expect(base58btc.encode(digest).slice(1)).to.eql(goId.id) }) }) it('set privKey (valid)', async () => { - const peerId = await PeerId.create(testOpts) + const peerId = await factory.create(testOpts) // @ts-ignore peerId.privKey = peerId._privKey expect(peerId.isValid()).to.equal(true) }) it('set pubKey (valid)', async () => { - const peerId = await PeerId.create(testOpts) + const peerId = await factory.create(testOpts) // @ts-ignore peerId.pubKey = peerId._pubKey expect(peerId.isValid()).to.equal(true) }) it('set privKey (invalid)', async () => { - const peerId = await PeerId.create(testOpts) + const peerId = await factory.create(testOpts) // @ts-ignore peerId.privKey = uint8ArrayFromString('bufff') expect(peerId.isValid()).to.equal(false) }) it('set pubKey (invalid)', async () => { - const peerId = await PeerId.create(testOpts) + const peerId = await factory.create(testOpts) // @ts-ignore peerId.pubKey = uint8ArrayFromString('bufff') expect(peerId.isValid()).to.equal(false) }) it('keys are equal after one is stringified', async () => { - const peerId = await PeerId.create(testOpts) - const peerId1 = PeerId.createFromB58String(peerId.toB58String()) - const peerId2 = PeerId.createFromB58String(peerId.toB58String()) + const peerId = await factory.create(testOpts) + const peerId1 = factory.createFromB58String(peerId.toB58String()) + const peerId2 = factory.createFromB58String(peerId.toB58String()) expect(peerId1).to.deep.equal(peerId2) @@ -388,20 +383,28 @@ module.exports = (common) => { it('missmatch private - public key', async () => { const digest = await k1.public.hash() expect(() => { - new PeerId(digest, k1, k2.public) // eslint-disable-line no-new + factory.createFromJSON({ + id: digest, + pubKey: k1, + privKey: k2.public + }) // eslint-disable-line no-new }).to.throw(/inconsistent arguments/) }) it('missmatch id - private - public key', async () => { const digest = await k1.public.hash() expect(() => { - new PeerId(digest, k1, k3.public) // eslint-disable-line no-new + factory.createFromJSON({ + id: digest, + pubKey: k1, + privKey: k3.public + }) // eslint-disable-line no-new }).to.throw(/inconsistent arguments/) }) it('invalid id', () => { // @ts-expect-error incorrect constructor arg type - expect(() => new PeerId('hello world')).to.throw(/invalid id/) + expect(() => factory.createFromJSON('hello world')).to.throw(/invalid id/) }) }) }) diff --git a/packages/interfaces/src/peer-id/README.md b/packages/interfaces/src/peer-id/README.md index 4f52694c4..f7a342387 100644 --- a/packages/interfaces/src/peer-id/README.md +++ b/packages/interfaces/src/peer-id/README.md @@ -25,7 +25,7 @@ Include this badge in your readme if you make a new module that uses interface-p ### Node.js -Install `interface-peer-id` as one of the dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: +Install `libp2p-interfaces-compliance-tests` as one of the development dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: ```js const tests = require('libp2p-interfaces-compliance-tests/src/peer-id') @@ -34,7 +34,7 @@ describe('your peer id', () => { // use all of the test suits tests({ setup () { - return YourPeerId + return YourPeerIdFactory }, teardown () { // Clean up any resources created by setup()