Skip to content

Commit

Permalink
feat: add url peer id (#2598)
Browse files Browse the repository at this point in the history
To parse non-cryptographic peer ids from http servers, add the
url peer id type.

---------

Co-authored-by: Chad Nehemiah <chad.nehemiah94@gmail.com>
  • Loading branch information
achingbrain and maschad committed Jul 2, 2024
1 parent 6573cb8 commit b0b6cae
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 7 deletions.
4 changes: 4 additions & 0 deletions packages/interface/src/peer-id/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export interface Secp256k1PeerId extends PeerId {
readonly publicKey: Uint8Array
}

export interface URLPeerId extends PeerId {
readonly type: 'url'
}

export interface PeerId {
type: PeerIdType
multihash: MultihashDigest
Expand Down
10 changes: 9 additions & 1 deletion packages/peer-id-factory/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,13 @@ async function createFromParts (multihash: Uint8Array, privKey?: Uint8Array, pub
return createFromPubKey(key)
}

return peerIdFromBytes(multihash)
const peerId = peerIdFromBytes(multihash)

if (peerId.type !== 'Ed25519' && peerId.type !== 'secp256k1' && peerId.type !== 'RSA') {
// should not be possible since `multihash` is derived from keys and these
// are the cryptographic peer id types
throw new Error('Supplied PeerID is invalid')
}

return peerId
}
64 changes: 59 additions & 5 deletions packages/peer-id/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
*/

import { CodeError } from '@libp2p/interface'
import { type Ed25519PeerId, type PeerIdType, type RSAPeerId, type Secp256k1PeerId, peerIdSymbol, type PeerId } from '@libp2p/interface'
import { type Ed25519PeerId, type PeerIdType, type RSAPeerId, type URLPeerId, type Secp256k1PeerId, peerIdSymbol, type PeerId } from '@libp2p/interface'
import { base58btc } from 'multiformats/bases/base58'
import { bases } from 'multiformats/basics'
import { CID } from 'multiformats/cid'
import * as Digest from 'multiformats/hashes/digest'
import { identity } from 'multiformats/hashes/identity'
import { sha256 } from 'multiformats/hashes/sha2'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import type { MultibaseDecoder } from 'multiformats/bases/interface'
import type { MultihashDigest } from 'multiformats/hashes/interface'

Expand Down Expand Up @@ -181,6 +183,52 @@ class Secp256k1PeerIdImpl extends PeerIdImpl implements Secp256k1PeerId {
}
}

// these values are from https://github.com/multiformats/multicodec/blob/master/table.csv
const TRANSPORT_IPFS_GATEWAY_HTTP_CODE = 0x0920

class URLPeerIdImpl implements URLPeerId {
readonly type = 'url'
readonly multihash: MultihashDigest
readonly privateKey?: Uint8Array
readonly publicKey?: Uint8Array
readonly url: string

constructor (url: URL) {
this.url = url.toString()
this.multihash = identity.digest(uint8ArrayFromString(this.url))
}

[inspect] (): string {
return `PeerId(${this.url})`
}

readonly [peerIdSymbol] = true

toString (): string {
return this.toCID().toString()
}

toCID (): CID {
return CID.createV1(TRANSPORT_IPFS_GATEWAY_HTTP_CODE, this.multihash)
}

toBytes (): Uint8Array {
return this.toCID().bytes
}

equals (other?: PeerId | Uint8Array | string): boolean {
if (other == null) {
return false
}

if (other instanceof Uint8Array) {
other = uint8ArrayToString(other)
}

return other.toString() === this.toString()
}
}

export function createPeerId (init: PeerIdInit): Ed25519PeerId | Secp256k1PeerId | RSAPeerId {
if (init.type === 'RSA') {
return new RSAPeerIdImpl(init)
Expand Down Expand Up @@ -213,7 +261,7 @@ export function peerIdFromPeerId (other: any): Ed25519PeerId | Secp256k1PeerId |
throw new CodeError('Not a PeerId', 'ERR_INVALID_PARAMETERS')
}

export function peerIdFromString (str: string, decoder?: MultibaseDecoder<any>): Ed25519PeerId | Secp256k1PeerId | RSAPeerId {
export function peerIdFromString (str: string, decoder?: MultibaseDecoder<any>): Ed25519PeerId | Secp256k1PeerId | RSAPeerId | URLPeerId {
decoder = decoder ?? baseDecoder

if (str.charAt(0) === '1' || str.charAt(0) === 'Q') {
Expand All @@ -233,7 +281,7 @@ export function peerIdFromString (str: string, decoder?: MultibaseDecoder<any>):
return peerIdFromBytes(baseDecoder.decode(str))
}

export function peerIdFromBytes (buf: Uint8Array): Ed25519PeerId | Secp256k1PeerId | RSAPeerId {
export function peerIdFromBytes (buf: Uint8Array): Ed25519PeerId | Secp256k1PeerId | RSAPeerId | URLPeerId {
try {
const multihash = Digest.decode(buf)

Expand All @@ -255,11 +303,17 @@ export function peerIdFromBytes (buf: Uint8Array): Ed25519PeerId | Secp256k1Peer
throw new Error('Supplied PeerID CID is invalid')
}

export function peerIdFromCID (cid: CID): Ed25519PeerId | Secp256k1PeerId | RSAPeerId {
if (cid == null || cid.multihash == null || cid.version == null || (cid.version === 1 && cid.code !== LIBP2P_KEY_CODE)) {
export function peerIdFromCID (cid: CID): Ed25519PeerId | Secp256k1PeerId | RSAPeerId | URLPeerId {
if (cid == null || cid.multihash == null || cid.version == null || (cid.version === 1 && (cid.code !== LIBP2P_KEY_CODE) && cid.code !== TRANSPORT_IPFS_GATEWAY_HTTP_CODE)) {
throw new Error('Supplied PeerID CID is invalid')
}

if (cid.code === TRANSPORT_IPFS_GATEWAY_HTTP_CODE) {
const url = uint8ArrayToString(cid.multihash.digest)

return new URLPeerIdImpl(new URL(url))
}

const multihash = cid.multihash

if (multihash.code === sha256.code) {
Expand Down
31 changes: 30 additions & 1 deletion packages/peer-id/test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/* eslint-env mocha */
import { expect } from 'aegir/chai'
import { CID } from 'multiformats/cid'
import { identity } from 'multiformats/hashes/identity'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { createPeerId, peerIdFromBytes, peerIdFromString } from '../src/index.js'
import { createPeerId, peerIdFromBytes, peerIdFromCID, peerIdFromString } from '../src/index.js'

// these values are from https://github.com/multiformats/multicodec/blob/master/table.csv
const TRANSPORT_IPFS_GATEWAY_HTTP_CODE = 0x0920

describe('PeerId', () => {
it('create an id without \'new\'', () => {
Expand Down Expand Up @@ -111,4 +116,28 @@ describe('PeerId', () => {

expect(res).to.have.property('id', id.toString())
})

it('creates a url peer id from a CID string', async () => {
const url = 'http://example.com/'
const cid = CID.createV1(TRANSPORT_IPFS_GATEWAY_HTTP_CODE, identity.digest(uint8ArrayFromString(url)))
const id = peerIdFromString(cid.toString())
expect(id).to.have.property('type', 'url')
expect(id.toString()).to.equal(cid.toString())
})

it('creates a url peer id from a CID bytes', async () => {
const url = 'http://example.com/'
const cid = CID.createV1(TRANSPORT_IPFS_GATEWAY_HTTP_CODE, identity.digest(uint8ArrayFromString(url)))
const id = peerIdFromBytes(cid.bytes)
expect(id).to.have.property('type', 'url')
expect(id.toString()).to.equal(cid.toString())
})

it('creates a url peer id from a CID', async () => {
const url = 'http://example.com/'
const cid = CID.createV1(TRANSPORT_IPFS_GATEWAY_HTTP_CODE, identity.digest(uint8ArrayFromString(url)))
const id = peerIdFromCID(cid)
expect(id).to.have.property('type', 'url')
expect(id.toString()).to.equal(cid.toString())
})
})

0 comments on commit b0b6cae

Please sign in to comment.