Skip to content

Commit

Permalink
feat: extensible peer ids (#2496)
Browse files Browse the repository at this point in the history
Sometimes a peer will have a non-cryptographic identifier such as a URL.

Update the PeerId type to be an interface so we can use arbitrary data to identify other peers, not just cryptographic keys.

---------

Co-authored-by: Russell Dempsey <[email protected]>
  • Loading branch information
achingbrain and SgtPooki authored Apr 24, 2024
1 parent fd1f834 commit 0d5d966
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 50 deletions.
5 changes: 3 additions & 2 deletions packages/crypto/src/keys/ed25519-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { isPromise } from '../util.js'
import * as crypto from './ed25519.js'
import { exporter } from './exporter.js'
import * as pbm from './keys.js'
import type { PublicKey, PrivateKey } from '@libp2p/interface'
import type { Multibase } from 'multiformats'
import type { Uint8ArrayList } from 'uint8arraylist'

export class Ed25519PublicKey {
export class Ed25519PublicKey implements PublicKey<'Ed25519'> {
private readonly _key: Uint8Array

constructor (key: Uint8Array) {
Expand Down Expand Up @@ -47,7 +48,7 @@ export class Ed25519PublicKey {
}
}

export class Ed25519PrivateKey {
export class Ed25519PrivateKey implements PrivateKey<'Ed25519'> {
private readonly _key: Uint8Array
private readonly _publicKey: Uint8Array

Expand Down
17 changes: 7 additions & 10 deletions packages/crypto/src/keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import * as keysPBM from './keys.js'
import * as RSA from './rsa-class.js'
import { importFromPem } from './rsa-utils.js'
import * as Secp256k1 from './secp256k1-class.js'
import type { PrivateKey, PublicKey } from '@libp2p/interface'
import type { PrivateKey, PublicKey, KeyType as KeyTypes } from '@libp2p/interface'

export { keyStretcher }
export { generateEphemeralKeyPair }
export { keysPBM }

export type KeyTypes = 'RSA' | 'Ed25519' | 'secp256k1'
export type { KeyTypes }

export { RsaPrivateKey, RsaPublicKey, MAX_RSA_KEY_SIZE } from './rsa-class.js'
export { Ed25519PrivateKey, Ed25519PublicKey } from './ed25519-class.js'
Expand Down Expand Up @@ -55,11 +55,8 @@ function typeToKey (type: string): typeof RSA | typeof Ed25519 | typeof Secp256k

/**
* Generates a keypair of the given type and bitsize
*
* @param type
* @param bits - Minimum of 1024
*/
export async function generateKeyPair (type: KeyTypes, bits?: number): Promise<PrivateKey> {
export async function generateKeyPair <T extends KeyTypes> (type: T, bits?: number): Promise<PrivateKey<T>> {
return typeToKey(type).generateKeyPair(bits ?? 2048)
}

Expand All @@ -68,7 +65,7 @@ export async function generateKeyPair (type: KeyTypes, bits?: number): Promise<P
*
* Seed is a 32 byte uint8array
*/
export async function generateKeyPairFromSeed (type: KeyTypes, seed: Uint8Array, bits?: number): Promise<PrivateKey> {
export async function generateKeyPairFromSeed <T extends KeyTypes> (type: T, seed: Uint8Array, bits?: number): Promise<PrivateKey<T>> {
if (type.toLowerCase() !== 'ed25519') {
throw new CodeError('Seed key derivation is unimplemented for RSA or secp256k1', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
}
Expand All @@ -79,7 +76,7 @@ export async function generateKeyPairFromSeed (type: KeyTypes, seed: Uint8Array,
/**
* Converts a protobuf serialized public key into its representative object
*/
export function unmarshalPublicKey (buf: Uint8Array): PublicKey {
export function unmarshalPublicKey <T extends KeyTypes> (buf: Uint8Array): PublicKey<T> {
const decoded = keysPBM.PublicKey.decode(buf)
const data = decoded.Data ?? new Uint8Array()

Expand Down Expand Up @@ -107,7 +104,7 @@ export function marshalPublicKey (key: { bytes: Uint8Array }, type?: string): Ui
/**
* Converts a protobuf serialized private key into its representative object
*/
export async function unmarshalPrivateKey (buf: Uint8Array): Promise<PrivateKey> {
export async function unmarshalPrivateKey <T extends KeyTypes> (buf: Uint8Array): Promise<PrivateKey<T>> {
const decoded = keysPBM.PrivateKey.decode(buf)
const data = decoded.Data ?? new Uint8Array()

Expand Down Expand Up @@ -137,7 +134,7 @@ export function marshalPrivateKey (key: { bytes: Uint8Array }, type?: string): U
*
* Supported formats are 'pem' (RSA only) and 'libp2p-key'.
*/
export async function importKey (encryptedKey: string, password: string): Promise<PrivateKey> {
export async function importKey <T extends KeyTypes> (encryptedKey: string, password: string): Promise<PrivateKey<T>> {
try {
const key = await importer(encryptedKey, password)
return await unmarshalPrivateKey(key)
Expand Down
5 changes: 3 additions & 2 deletions packages/crypto/src/keys/rsa-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { isPromise } from '../util.js'
import { exporter } from './exporter.js'
import * as pbm from './keys.js'
import * as crypto from './rsa.js'
import type { PublicKey, PrivateKey } from '@libp2p/interface'
import type { Multibase } from 'multiformats'
import type { Uint8ArrayList } from 'uint8arraylist'

export const MAX_RSA_KEY_SIZE = 8192

export class RsaPublicKey {
export class RsaPublicKey implements PublicKey<'RSA'> {
private readonly _key: JsonWebKey

constructor (key: JsonWebKey) {
Expand Down Expand Up @@ -48,7 +49,7 @@ export class RsaPublicKey {
}
}

export class RsaPrivateKey {
export class RsaPrivateKey implements PrivateKey<'RSA'> {
private readonly _key: JsonWebKey
private readonly _publicKey: JsonWebKey

Expand Down
5 changes: 3 additions & 2 deletions packages/crypto/src/keys/secp256k1-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { isPromise } from '../util.js'
import { exporter } from './exporter.js'
import * as keysProtobuf from './keys.js'
import * as crypto from './secp256k1.js'
import type { PublicKey, PrivateKey } from '@libp2p/interface'
import type { Multibase } from 'multiformats'
import type { Uint8ArrayList } from 'uint8arraylist'

export class Secp256k1PublicKey {
export class Secp256k1PublicKey implements PublicKey<'secp256k1'> {
private readonly _key: Uint8Array

constructor (key: Uint8Array) {
Expand Down Expand Up @@ -50,7 +51,7 @@ export class Secp256k1PublicKey {
}
}

export class Secp256k1PrivateKey {
export class Secp256k1PrivateKey implements PrivateKey<'secp256k1'> {
private readonly _key: Uint8Array
private readonly _publicKey: Uint8Array

Expand Down
10 changes: 5 additions & 5 deletions packages/interface/src/keys/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { Uint8ArrayList } from 'uint8arraylist'

export interface PublicKey {
export interface PublicKey<Type extends KeyType = 'Ed25519'> {
readonly bytes: Uint8Array
verify(data: Uint8Array | Uint8ArrayList, sig: Uint8Array): boolean | Promise<boolean>
marshal(): Uint8Array
equals(key: PublicKey): boolean
equals(key: PublicKey<Type>): boolean
hash(): Uint8Array | Promise<Uint8Array>
}

/**
* Generic private key interface
*/
export interface PrivateKey {
readonly public: PublicKey
export interface PrivateKey<Type extends KeyType = 'Ed25519'> {
readonly public: PublicKey<Type>
readonly bytes: Uint8Array
sign(data: Uint8Array | Uint8ArrayList): Uint8Array | Promise<Uint8Array>
marshal(): Uint8Array
equals(key: PrivateKey): boolean
equals(key: PrivateKey<Type>): boolean
hash(): Uint8Array | Promise<Uint8Array>
/**
* Gets the ID of the key.
Expand Down
33 changes: 16 additions & 17 deletions packages/interface/src/peer-id/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
import type { KeyType } from '../keys/index.js'
import type { CID } from 'multiformats/cid'
import type { MultihashDigest } from 'multiformats/hashes/interface'

export type PeerIdType = 'RSA' | 'Ed25519' | 'secp256k1'
export type PeerIdType = KeyType | string

interface BasePeerId {
readonly type: PeerIdType
readonly multihash: MultihashDigest
readonly privateKey?: Uint8Array
readonly publicKey?: Uint8Array

toString(): string
toCID(): CID
toBytes(): Uint8Array
equals(other?: PeerId | Uint8Array | string): boolean
}

export interface RSAPeerId extends BasePeerId {
export interface RSAPeerId extends PeerId {
readonly type: 'RSA'
readonly publicKey?: Uint8Array
}

export interface Ed25519PeerId extends BasePeerId {
export interface Ed25519PeerId extends PeerId {
readonly type: 'Ed25519'
readonly publicKey: Uint8Array
}

export interface Secp256k1PeerId extends BasePeerId {
export interface Secp256k1PeerId extends PeerId {
readonly type: 'secp256k1'
readonly publicKey: Uint8Array
}

export type PeerId = RSAPeerId | Ed25519PeerId | Secp256k1PeerId
export interface PeerId {
type: PeerIdType
multihash: MultihashDigest
privateKey?: Uint8Array
publicKey?: Uint8Array

toString(): string
toCID(): CID
toBytes(): Uint8Array
equals(other?: PeerId | Uint8Array | string): boolean
}

export const peerIdSymbol = Symbol.for('@libp2p/peer-id')

Expand Down
12 changes: 6 additions & 6 deletions packages/peer-id-factory/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { generateKeyPair, marshalPrivateKey, unmarshalPrivateKey, marshalPublicK
import { peerIdFromKeys, peerIdFromBytes } from '@libp2p/peer-id'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { PeerIdProto } from './proto.js'
import type { PublicKey, PrivateKey, RSAPeerId, Ed25519PeerId, Secp256k1PeerId, PeerId } from '@libp2p/interface'
import type { PublicKey, PrivateKey, RSAPeerId, Ed25519PeerId, Secp256k1PeerId, KeyType } from '@libp2p/interface'

export const createEd25519PeerId = async (): Promise<Ed25519PeerId> => {
const key = await generateKeyPair('Ed25519')
Expand Down Expand Up @@ -60,11 +60,11 @@ export const createRSAPeerId = async (opts?: { bits: number }): Promise<RSAPeerI
throw new Error(`Generated unexpected PeerId type "${id.type}"`)
}

export async function createFromPubKey (publicKey: PublicKey): Promise<PeerId> {
export async function createFromPubKey <T extends KeyType > (publicKey: PublicKey<T>): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
return peerIdFromKeys(marshalPublicKey(publicKey))
}

export async function createFromPrivKey (privateKey: PrivateKey): Promise<PeerId> {
export async function createFromPrivKey <T extends KeyType > (privateKey: PrivateKey<T>): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
return peerIdFromKeys(marshalPublicKey(privateKey.public), marshalPrivateKey(privateKey))
}

Expand All @@ -76,7 +76,7 @@ export function exportToProtobuf (peerId: RSAPeerId | Ed25519PeerId | Secp256k1P
})
}

export async function createFromProtobuf (buf: Uint8Array): Promise<PeerId> {
export async function createFromProtobuf (buf: Uint8Array): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
const {
id,
privKey,
Expand All @@ -90,15 +90,15 @@ export async function createFromProtobuf (buf: Uint8Array): Promise<PeerId> {
)
}

export async function createFromJSON (obj: { id: string, privKey?: string, pubKey?: string }): Promise<PeerId> {
export async function createFromJSON (obj: { id: string, privKey?: string, pubKey?: string }): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
return createFromParts(
uint8ArrayFromString(obj.id, 'base58btc'),
obj.privKey != null ? uint8ArrayFromString(obj.privKey, 'base64pad') : undefined,
obj.pubKey != null ? uint8ArrayFromString(obj.pubKey, 'base64pad') : undefined
)
}

async function createFromParts (multihash: Uint8Array, privKey?: Uint8Array, pubKey?: Uint8Array): Promise<PeerId> {
async function createFromParts (multihash: Uint8Array, privKey?: Uint8Array, pubKey?: Uint8Array): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
if (privKey != null) {
const key = await unmarshalPrivateKey(privKey)

Expand Down
12 changes: 6 additions & 6 deletions packages/peer-id/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class Secp256k1PeerIdImpl extends PeerIdImpl implements Secp256k1PeerId {
}
}

export function createPeerId (init: PeerIdInit): PeerId {
export function createPeerId (init: PeerIdInit): Ed25519PeerId | Secp256k1PeerId | RSAPeerId {
if (init.type === 'RSA') {
return new RSAPeerIdImpl(init)
}
Expand All @@ -197,7 +197,7 @@ export function createPeerId (init: PeerIdInit): PeerId {
throw new CodeError('Type must be "RSA", "Ed25519" or "secp256k1"', 'ERR_INVALID_PARAMETERS')
}

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

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

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

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

Expand All @@ -255,7 +255,7 @@ export function peerIdFromBytes (buf: Uint8Array): PeerId {
throw new Error('Supplied PeerID CID is invalid')
}

export function peerIdFromCID (cid: CID): PeerId {
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)) {
throw new Error('Supplied PeerID CID is invalid')
}
Expand All @@ -279,7 +279,7 @@ export function peerIdFromCID (cid: CID): PeerId {
* @param publicKey - A marshalled public key
* @param privateKey - A marshalled private key
*/
export async function peerIdFromKeys (publicKey: Uint8Array, privateKey?: Uint8Array): Promise<PeerId> {
export async function peerIdFromKeys (publicKey: Uint8Array, privateKey?: Uint8Array): Promise<Ed25519PeerId | Secp256k1PeerId | RSAPeerId> {
if (publicKey.length === MARSHALLED_ED225519_PUBLIC_KEY_LENGTH) {
return new Ed25519PeerIdImpl({ multihash: Digest.create(identity.code, publicKey), privateKey })
}
Expand Down

0 comments on commit 0d5d966

Please sign in to comment.