Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,5 @@
"type": "module",
"workspaces": [
"packages/*"
],
"overrides": {
"playwright-core": "1.55.1"
}
]
}
1 change: 1 addition & 0 deletions packages/interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@libp2p/interface": "^3.2.0",
"@multiformats/dns": "^1.0.13",
"@multiformats/multiaddr": "^13.0.1",
"abort-error": "^1.0.2",
"interface-blockstore": "^7.0.1",
"interface-datastore": "^10.0.1",
"multiformats": "^14.0.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/interface/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ export class InvalidCodecError extends Error {
this.name = 'InvalidCodecError'
}
}

export class UnknownCryptoError extends Error {
static name = 'UnknownCryptoError'
name = 'UnknownCryptoError'
}
154 changes: 150 additions & 4 deletions packages/interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,156 @@
*/

import type { Blocks } from './blocks.ts'
import type { Keychain } from './keychain.ts'
import type { Pins } from './pins.ts'
import type { Routing } from './routing.ts'
import type { AbortOptions, ComponentLogger, Libp2p, Metrics, TypedEventEmitter } from '@libp2p/interface'
import type { ComponentLogger, Libp2p, Metrics, TypedEventEmitter } from '@libp2p/interface'
import type { DNS } from '@multiformats/dns'
import type { AbortOptions } from 'abort-error'
import type { Datastore } from 'interface-datastore'
import type { BlockCodec, MultihashHasher } from 'multiformats'
import type { CID } from 'multiformats/cid'
import type { CID, MultihashDigest } from 'multiformats/cid'
import type { ProgressEvent, ProgressOptions } from 'progress-events'

export interface CodecLoader {
<T = any, Code extends number = any>(code: Code): BlockCodec<Code, T> | Promise<BlockCodec<Code, T>>
<T = any, Code extends number = any>(code: Code, options?: AbortOptions): BlockCodec<Code, T> | Promise<BlockCodec<Code, T>>
}

export interface HasherLoader {
(code: number): MultihashHasher | Promise<MultihashHasher>
(code: number, options?: AbortOptions): MultihashHasher | Promise<MultihashHasher>
}

export interface CryptoKeyLoader {
(codeOrName: number | string, options?: AbortOptions): CryptoKeyImplementation | Promise<CryptoKeyImplementation>
}

export interface PublicKey {
/**
* The type of the crypto implementation, e.g. `Ed15519`
*/
type: string

/**
* The code that is used as the `Type` field in the protobuf representation of
* the public/private keys
*/
code: number

/**
* The raw public key
*/
raw: ArrayBuffer

/**
* Return a MultihashDigest that represents this key
*/
toMultihash (): MultihashDigest

/**
* Return the libp2p-key CID that represents this key
*/
toCID (): CID<unknown, 0x72>

/**
* Verify the passed message against it's signature
*/
verify(message: Uint8Array, signature: Uint8Array, options?: AbortOptions): boolean | Promise<boolean>
}

export function isPublicKey (obj?: any): obj is PublicKey {
if (obj == null) {
return false
}

return typeof obj.type === 'string' && typeof obj.code === 'number' && typeof obj.verify === 'function'
}

export interface PrivateKey {
/**
* The type of the crypto implementation, e.g. `Ed15519`
*/
type: string

/**
* The code that is used as the `Type` field in the protobuf representation of
* the public/private keys
*/
code: number

/**
* The raw private key
*/
raw: ArrayBuffer

/**
* The public key that corresponds to this private key
*/
publicKey: PublicKey

/**
* Sign the passed message and return a signature
*/
sign(message: Uint8Array, options?: AbortOptions): Uint8Array<ArrayBuffer> | Promise<Uint8Array<ArrayBuffer>>
}

export function isPrivateKey (obj?: any): obj is PrivateKey {
if (obj == null) {
return false
}

return typeof obj.type === 'string' && typeof obj.code === 'number' && typeof obj.sign === 'function' && isPublicKey(obj.publicKey)
}

export interface CipherOptions extends AbortOptions {
iterations?: number
hash?: string
keyLength?: number
algorithm?: string
}

export interface EncryptionResult {
salt: Uint8Array<ArrayBuffer>
iv: Uint8Array<ArrayBuffer>
cipherText: Uint8Array<ArrayBuffer>
}

export interface Cipher {
encrypt(data: Uint8Array, options?: AbortOptions): Promise<EncryptionResult>
decrypt(salt: Uint8Array, iv: Uint8Array, cipherText: Uint8Array, options?: CipherOptions): Promise<Uint8Array<ArrayBuffer>>
}

export interface CryptoKeyImplementation {
/**
* The type of the crypto implementation, e.g. `Ed15519`
*/
type: string

/**
* The code that is used as the `Type` field in the protobuf representation of
* the public/private keys
*/
code: number

/**
* Create a new private key
*/
createPrivateKey(options?: AbortOptions & Record<string, any>): Promise<PrivateKey>

/**
* Convert the passed bytes into a public key. The bytes come from the `.Data`
* field of a `PublicKey` protobuf message.
*/
publicKeyFromArray(key: ArrayBuffer | Uint8Array, options?: AbortOptions): PublicKey | Promise<PublicKey>

/**
* Convert a private key into a string suitable for storing in a datastore
*/
serialize (key: PrivateKey, cipher: Cipher, options?: AbortOptions): Promise<string>

/**
* Convert a string from a datastore into a private key
*/
deserialize (pem: string, cipher: Cipher, options?: AbortOptions): Promise<PrivateKey>
}

/**
Expand All @@ -56,6 +191,11 @@ export interface Helia<T extends Libp2p = Libp2p> {
*/
events: TypedEventEmitter<HeliaEvents<T>>

/**
* Secure storage for private keys
*/
keychain: Keychain

/**
* Pinning operations for blocks in the blockstore
*/
Expand Down Expand Up @@ -111,6 +251,11 @@ export interface Helia<T extends Libp2p = Libp2p> {
* the hasher is being fetched from the network.
*/
getHasher: HasherLoader

/**
* Cryptography implementations securely sign and verify data
*/
getCryptoKey: CryptoKeyLoader
}

export type GcEvents =
Expand Down Expand Up @@ -147,5 +292,6 @@ export interface HeliaEvents<T extends Libp2p = Libp2p> {

export * from './blocks.ts'
export * from './errors.ts'
export * from './keychain.ts'
export * from './pins.ts'
export * from './routing.ts'
107 changes: 107 additions & 0 deletions packages/interface/src/keychain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { PrivateKey } from './index.ts'
import type { AbortOptions } from 'abort-error'

export interface KeyInfo {
/**
* The hash of the key
*/
id: string

/**
* The key name
*/
name: string

/**
* The key type
*/
type?: 'Ed25519' | 'RSA' | string
}

export interface Keychain {
/**
* Create a key of the passed type and store it under the specified name. A
* cryptography implementation must be configured for the key type.
*/
createKey (name: string, type: 'Ed25519' | 'RSA' | string, options?: AbortOptions & Record<string, any>): Promise<PrivateKey>

/**
* Import a new private key.
*
* The `type` parameter must match a supported cryptography implementation.
*
* The default supported key types are `Ed25519` and `RSA`, others may be
* added through configuration.
*
* @example
*
* ```TypeScript
* const key = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify'])
* const raw = await crypto.subtle.exportKey('raw', key)
* await helia.keychain.importKey('my-key', 'Ed25519', raw)
* ```
*/
importKey(name: string, key: PrivateKey, options?: AbortOptions): Promise<PrivateKey>

/**
* Export an existing private key.
*
* @example
*
* ```TypeScript
* const raw = await helia.exportKey('my-key')
* const key = await crypto.subtle.importKey('raw', raw, {
* name: 'Ed25519'
* }, true, ['sign', 'verify'])
* ```
*/
exportKey(name: string, options?: AbortOptions): Promise<PrivateKey>

/**
* Removes a key from the keychain.
*
* @example
*
* ```TypeScript
* await helia.keychain.removeKey('keyTest')
* ```
*/
removeKey(name: string, options?: AbortOptions): Promise<void>

/**
* Rename a key in the keychain. This is done in a batch commit with rollback
* so errors thrown during the operation will not cause key loss.
*
* @example
*
* ```TypeScript
* await helia.keychain.renameKey('oldName', 'newName')
* ```
*/
renameKey(oldName: string, newName: string, options?: AbortOptions): Promise<void>

/**
* List all the keys.
*
* @example
*
* ```TypeScript
* for await (const name of helia.keychain.listKeys()) {
* // ...
* }
* ```
*/
listKeys(options?: AbortOptions): AsyncGenerator<KeyInfo>

/**
* Re-encrypt all keys in the keychain using a crypto graphic key derived
* from the password
*
* @example
*
* ```TypeScript
* await helia.keychain.rotateKeychainPass('newPassword')
* ```
*/
rotateKeychainPass(password: string, options?: AbortOptions): Promise<void>
}
7 changes: 0 additions & 7 deletions packages/interop/src/fixtures/create-helia.browser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { bitswap } from '@helia/block-brokers'
import { ipnsValidator, ipnsSelector } from '@helia/ipns'
import { kadDHT, removePublicAddressesMapper } from '@libp2p/kad-dht'
import { webSockets } from '@libp2p/websockets'
import { sha3512 } from '@multiformats/sha3'
Expand Down Expand Up @@ -30,12 +29,6 @@ export async function createHeliaNode (libp2pOptions?: Libp2pOptions): Promise<H
...(defaults.services ?? {}),
...(libp2pOptions?.services ?? {}),
dht: kadDHT({
validators: {
ipns: ipnsValidator
},
selectors: {
ipns: ipnsSelector
},
// skips waiting for the initial self-query to find peers
allowQueryWithZeroPeers: true,

Expand Down
7 changes: 0 additions & 7 deletions packages/interop/src/fixtures/create-helia.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { bitswap } from '@helia/block-brokers'
import { ipnsValidator, ipnsSelector } from '@helia/ipns'
import { kadDHT, removePublicAddressesMapper } from '@libp2p/kad-dht'
import { sha3512 } from '@multiformats/sha3'
import { createHelia, libp2pDefaults } from 'helia'
Expand All @@ -20,12 +19,6 @@ export async function createHeliaNode (libp2pOptions?: Libp2pOptions): Promise<H
...(defaults.services ?? {}),
...(libp2pOptions?.services ?? {}),
dht: kadDHT({
validators: {
ipns: ipnsValidator
},
selectors: {
ipns: ipnsSelector
},
protocol: '/ipfs/lan/kad/1.0.0',
peerInfoMapper: removePublicAddressesMapper,
clientMode: false
Expand Down
1 change: 0 additions & 1 deletion packages/interop/src/fixtures/key-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ import type { KeyType } from '@libp2p/interface'

export const keyTypes: KeyType[] = [
'Ed25519',
'secp256k1',
'RSA'
]
13 changes: 10 additions & 3 deletions packages/interop/src/ipns-http.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { ipns } from '@helia/ipns'
import { delegatedHTTPRouting } from '@helia/routers'
import { peerIdFromCID } from '@libp2p/peer-id'
import { expect } from 'aegir/chai'
import last from 'it-last'
import { CID } from 'multiformats/cid'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { isNode } from 'wherearewe'
import { createKuboNode } from './fixtures/create-kubo.ts'
import type { Helia } from '@helia/interface'
Expand Down Expand Up @@ -62,8 +64,13 @@ describe('@helia/ipns - http', () => {
})

const key = peerIdFromCID(CID.parse(res.name))
// @ts-expect-error @libp2p/peer-id needs dep updates
const { cid: resolvedCid } = await name.resolve(key.toMultihash())
expect(resolvedCid.toString()).to.equal(cid.toString())
// @ts-expect-error @libp2p/peer-id needs new multiformats
const result = await last(name.resolve(key.toMultihash()))

if (result == null) {
throw new Error('No results found')
}

expect(uint8ArrayToString(result.record.value)).to.equal(`/ipfs/${cid}`)
})
})
Loading
Loading