From d721a45097dacf95ed3648346910cbc78b921586 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Tue, 23 Apr 2024 07:02:51 +0100 Subject: [PATCH 1/5] feat: extensible peer ids 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. --- packages/crypto/src/keys/ed25519-class.ts | 5 ++-- packages/crypto/src/keys/index.ts | 13 ++++----- packages/crypto/src/keys/rsa-class.ts | 5 ++-- packages/crypto/src/keys/secp256k1-class.ts | 5 ++-- packages/interface/src/keys/index.ts | 10 +++---- packages/interface/src/peer-id/index.ts | 32 ++++++++++----------- packages/peer-id-factory/src/index.ts | 12 ++++---- packages/peer-id/src/index.ts | 12 ++++---- 8 files changed, 46 insertions(+), 48 deletions(-) diff --git a/packages/crypto/src/keys/ed25519-class.ts b/packages/crypto/src/keys/ed25519-class.ts index 3591f0d11d..c4449b7af2 100644 --- a/packages/crypto/src/keys/ed25519-class.ts +++ b/packages/crypto/src/keys/ed25519-class.ts @@ -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) { @@ -47,7 +48,7 @@ export class Ed25519PublicKey { } } -export class Ed25519PrivateKey { +export class Ed25519PrivateKey implements PrivateKey<'Ed25519'> { private readonly _key: Uint8Array private readonly _publicKey: Uint8Array diff --git a/packages/crypto/src/keys/index.ts b/packages/crypto/src/keys/index.ts index b49956ca67..fc1ee5f8b6 100644 --- a/packages/crypto/src/keys/index.ts +++ b/packages/crypto/src/keys/index.ts @@ -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 { +export async function generateKeyPair (type: T, bits?: number): Promise> { return typeToKey(type).generateKeyPair(bits ?? 2048) } @@ -68,7 +65,7 @@ export async function generateKeyPair (type: KeyTypes, bits?: number): Promise

{ +export async function generateKeyPairFromSeed (type: T, seed: Uint8Array, bits?: number): Promise> { if (type.toLowerCase() !== 'ed25519') { throw new CodeError('Seed key derivation is unimplemented for RSA or secp256k1', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE') } @@ -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 (buf: Uint8Array): PublicKey { const decoded = keysPBM.PublicKey.decode(buf) const data = decoded.Data ?? new Uint8Array() @@ -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 { +export async function unmarshalPrivateKey (buf: Uint8Array): Promise> { const decoded = keysPBM.PrivateKey.decode(buf) const data = decoded.Data ?? new Uint8Array() @@ -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 { +export async function importKey (encryptedKey: string, password: string): Promise> { try { const key = await importer(encryptedKey, password) return await unmarshalPrivateKey(key) diff --git a/packages/crypto/src/keys/rsa-class.ts b/packages/crypto/src/keys/rsa-class.ts index ec3c77fe59..eae38a95b4 100644 --- a/packages/crypto/src/keys/rsa-class.ts +++ b/packages/crypto/src/keys/rsa-class.ts @@ -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) { @@ -48,7 +49,7 @@ export class RsaPublicKey { } } -export class RsaPrivateKey { +export class RsaPrivateKey implements PrivateKey<'RSA'> { private readonly _key: JsonWebKey private readonly _publicKey: JsonWebKey diff --git a/packages/crypto/src/keys/secp256k1-class.ts b/packages/crypto/src/keys/secp256k1-class.ts index 2b05ee1cbf..98dcd0b935 100644 --- a/packages/crypto/src/keys/secp256k1-class.ts +++ b/packages/crypto/src/keys/secp256k1-class.ts @@ -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) { @@ -50,7 +51,7 @@ export class Secp256k1PublicKey { } } -export class Secp256k1PrivateKey { +export class Secp256k1PrivateKey implements PrivateKey<'secp256k1'> { private readonly _key: Uint8Array private readonly _publicKey: Uint8Array diff --git a/packages/interface/src/keys/index.ts b/packages/interface/src/keys/index.ts index b7ade54f57..648a1211b1 100644 --- a/packages/interface/src/keys/index.ts +++ b/packages/interface/src/keys/index.ts @@ -1,22 +1,22 @@ import type { Uint8ArrayList } from 'uint8arraylist' -export interface PublicKey { +export interface PublicKey { readonly bytes: Uint8Array verify(data: Uint8Array | Uint8ArrayList, sig: Uint8Array): boolean | Promise marshal(): Uint8Array - equals(key: PublicKey): boolean + equals(key: PublicKey): boolean hash(): Uint8Array | Promise } /** * Generic private key interface */ -export interface PrivateKey { - readonly public: PublicKey +export interface PrivateKey { + readonly public: PublicKey readonly bytes: Uint8Array sign(data: Uint8Array | Uint8ArrayList): Uint8Array | Promise marshal(): Uint8Array - equals(key: PrivateKey): boolean + equals(key: PrivateKey): boolean hash(): Uint8Array | Promise /** * Gets the ID of the key. diff --git a/packages/interface/src/peer-id/index.ts b/packages/interface/src/peer-id/index.ts index 13d0b77c82..d916996af4 100644 --- a/packages/interface/src/peer-id/index.ts +++ b/packages/interface/src/peer-id/index.ts @@ -1,36 +1,34 @@ import type { CID } from 'multiformats/cid' import type { MultihashDigest } from 'multiformats/hashes/interface' -export type PeerIdType = 'RSA' | 'Ed25519' | 'secp256k1' +export type PeerIdType = 'RSA' | 'Ed25519' | 'secp256k1' | 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') diff --git a/packages/peer-id-factory/src/index.ts b/packages/peer-id-factory/src/index.ts index b6183f83be..9905cfc790 100644 --- a/packages/peer-id-factory/src/index.ts +++ b/packages/peer-id-factory/src/index.ts @@ -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 => { const key = await generateKeyPair('Ed25519') @@ -60,11 +60,11 @@ export const createRSAPeerId = async (opts?: { bits: number }): Promise { +export async function createFromPubKey (publicKey: PublicKey): Promise { return peerIdFromKeys(marshalPublicKey(publicKey)) } -export async function createFromPrivKey (privateKey: PrivateKey): Promise { +export async function createFromPrivKey (privateKey: PrivateKey): Promise { return peerIdFromKeys(marshalPublicKey(privateKey.public), marshalPrivateKey(privateKey)) } @@ -76,7 +76,7 @@ export function exportToProtobuf (peerId: RSAPeerId | Ed25519PeerId | Secp256k1P }) } -export async function createFromProtobuf (buf: Uint8Array): Promise { +export async function createFromProtobuf (buf: Uint8Array): Promise { const { id, privKey, @@ -90,7 +90,7 @@ export async function createFromProtobuf (buf: Uint8Array): Promise { ) } -export async function createFromJSON (obj: { id: string, privKey?: string, pubKey?: string }): Promise { +export async function createFromJSON (obj: { id: string, privKey?: string, pubKey?: string }): Promise { return createFromParts( uint8ArrayFromString(obj.id, 'base58btc'), obj.privKey != null ? uint8ArrayFromString(obj.privKey, 'base64pad') : undefined, @@ -98,7 +98,7 @@ export async function createFromJSON (obj: { id: string, privKey?: string, pubKe ) } -async function createFromParts (multihash: Uint8Array, privKey?: Uint8Array, pubKey?: Uint8Array): Promise { +async function createFromParts (multihash: Uint8Array, privKey?: Uint8Array, pubKey?: Uint8Array): Promise { if (privKey != null) { const key = await unmarshalPrivateKey(privKey) diff --git a/packages/peer-id/src/index.ts b/packages/peer-id/src/index.ts index fcda52e02e..da9557a7f1 100644 --- a/packages/peer-id/src/index.ts +++ b/packages/peer-id/src/index.ts @@ -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) } @@ -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) } @@ -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): PeerId { +export function peerIdFromString (str: string, decoder?: MultibaseDecoder): Ed25519PeerId | Secp256k1PeerId | RSAPeerId { decoder = decoder ?? baseDecoder if (str.charAt(0) === '1' || str.charAt(0) === 'Q') { @@ -233,7 +233,7 @@ export function peerIdFromString (str: string, decoder?: MultibaseDecoder): 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) @@ -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') } @@ -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 { +export async function peerIdFromKeys (publicKey: Uint8Array, privateKey?: Uint8Array): Promise { if (publicKey.length === MARSHALLED_ED225519_PUBLIC_KEY_LENGTH) { return new Ed25519PeerIdImpl({ multihash: Digest.create(identity.code, publicKey), privateKey }) } From a2c4b0883ead751a80579e9ca6bc1cd7b599fa68 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 23 Apr 2024 17:54:44 +0100 Subject: [PATCH 2/5] chore: apply suggestions from code review Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> --- packages/crypto/src/keys/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/crypto/src/keys/index.ts b/packages/crypto/src/keys/index.ts index fc1ee5f8b6..a87d07c501 100644 --- a/packages/crypto/src/keys/index.ts +++ b/packages/crypto/src/keys/index.ts @@ -76,7 +76,7 @@ export async function generateKeyPairFromSeed (type: T, see /** * Converts a protobuf serialized public key into its representative object */ -export function unmarshalPublicKey (buf: Uint8Array): PublicKey { +export function unmarshalPublicKey (buf: Uint8Array): PublicKey { const decoded = keysPBM.PublicKey.decode(buf) const data = decoded.Data ?? new Uint8Array() @@ -104,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> { +export async function unmarshalPrivateKey (buf: Uint8Array): Promise> { const decoded = keysPBM.PrivateKey.decode(buf) const data = decoded.Data ?? new Uint8Array() @@ -134,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> { +export async function importKey (encryptedKey: string, password: string): Promise> { try { const key = await importer(encryptedKey, password) return await unmarshalPrivateKey(key) From 61beb410ec13dd038c1726620a28affd8ac8bbbc Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:42:00 -0700 Subject: [PATCH 3/5] chore: fix lint errors from pr comment suggestion --- packages/crypto/src/keys/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/crypto/src/keys/index.ts b/packages/crypto/src/keys/index.ts index a87d07c501..d11052a8f6 100644 --- a/packages/crypto/src/keys/index.ts +++ b/packages/crypto/src/keys/index.ts @@ -76,7 +76,7 @@ export async function generateKeyPairFromSeed (type: T, see /** * Converts a protobuf serialized public key into its representative object */ -export function unmarshalPublicKey (buf: Uint8Array): PublicKey { +export function unmarshalPublicKey (buf: Uint8Array): PublicKey { const decoded = keysPBM.PublicKey.decode(buf) const data = decoded.Data ?? new Uint8Array() @@ -104,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> { +export async function unmarshalPrivateKey (buf: Uint8Array): Promise> { const decoded = keysPBM.PrivateKey.decode(buf) const data = decoded.Data ?? new Uint8Array() From 1b5f062e0ad9772f7550cbb3ad1cc99245d4301d Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 24 Apr 2024 07:16:17 +0100 Subject: [PATCH 4/5] chore: reuse type from interfaces --- packages/crypto/src/keys/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/crypto/src/keys/index.ts b/packages/crypto/src/keys/index.ts index d11052a8f6..aaa5122a96 100644 --- a/packages/crypto/src/keys/index.ts +++ b/packages/crypto/src/keys/index.ts @@ -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' From ff2ce9efe8a8c68f83e12f1a090d44e5ebcac377 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 24 Apr 2024 07:20:19 +0100 Subject: [PATCH 5/5] chore: reuse type --- packages/interface/src/peer-id/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interface/src/peer-id/index.ts b/packages/interface/src/peer-id/index.ts index d916996af4..7841f199bb 100644 --- a/packages/interface/src/peer-id/index.ts +++ b/packages/interface/src/peer-id/index.ts @@ -1,7 +1,8 @@ +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' | string +export type PeerIdType = KeyType | string export interface RSAPeerId extends PeerId { readonly type: 'RSA'