diff --git a/packages/nest/README.md b/packages/nest/README.md index 22f38b2..1e67d97 100644 --- a/packages/nest/README.md +++ b/packages/nest/README.md @@ -10,9 +10,10 @@ A layer around the `wnfs` package that provides a `FileSystem` class, a root tre - A root tree, holding references to all the needed individual parts (public fs, private forest, exchange, etc) - A mounting system for private nodes, mount specific paths. - A unix-fs compatibility layer for the public file system (allows for public files to be viewed through, for example, IPFS gateways) -- Private data sharing helpers +- Private data sharing + helpers - Provides a transaction system, rewinding the state if an error occurs. -- Creates a private forest automatically with a RSA modules using the Web Crypto API (supported on multiple platforms) +- Creates a private forest automatically with a RSA modulus using the Web Crypto API (supported on multiple platforms) +- In addition to the default symmetric key, use an RSA-OAEP asymmetric key to mount a private node (essentially sharing to self). Can be used to load a private directory, or file, using a passkey + the PRF extension. - Ability to verify commits to the file system. If a commit, aka. modification, is not verified, it will result in a no-op. - And more: typed paths, events, path helpers, data casting, … @@ -121,11 +122,11 @@ fs.rename fs.write ``` -## Identity +## Identifier ```ts -fs.identity() -fs.assignIdentity('did') +fs.identifier() +fs.assignIdentifier('did') ``` ## Private Data Sharing @@ -134,32 +135,44 @@ Flow: 1. The receiver of a share register their exchange key. An app could do this automatically when the app starts, or at some other time. 2. The data root of the receiver is passed to the sharer. Ideally this is done through some naming system. For example, you use DNS to map a username to the data root (eg. `TXT file-system.tokono.ma` could resolve to the data root, a CID). That said, this could also be done without a naming system, maybe by presenting a QR code. -3. The sharer creates the share. -4. This step is the reverse of step 2, where we pass the sharer's data root to the receiver. -5. Use the shared item. +3. Make sure the sharer's file system has an identity assigned. +4. The sharer creates the share. +5. This step is the reverse of step 2, where we pass the sharer's data root to the receiver. +6. Use the shared item. ```ts -// Step 1 & 2 +// Step 1 & 2 (Receiver) const { dataRoot } = await fs.registerExchangeKey('key-id', publicKey) const receiverDataRoot = dataRoot -// Step 3 & 4 + +// Step 3, 4 & 5 (Sharer) +await fs.assignIdentifier('did') + const { dataRoot } = await fs.share(pathToPrivateItem, receiverDataRoot) const sharerDataRoot = dataRoot -// Step 5 -const { share } = await fs.receive(sharerDataRoot, { publicKey, privateKey }) + +// Step 6 (Receiver) +const share = await fs.receive(sharerDataRoot, { publicKey, privateKey }) await share.read('utf8') ``` ## Manage private node using exchange key pair -Instead of keeping the symmetric capsule key around we can use an exchange key pair to mount a private node. This basically creates a share for ourselves. +Instead of keeping the (symmetric) capsule key around we can use an (asymmetric) exchange key pair to mount a private node. This basically creates a share for ourselves. ```ts +// 🚀 Create & mount await fs.createPrivateNode({ path: Path.root(), exchangeKeyPair: { publicKey, privateKey }, // 🔑 Pass in key pair here }) + +// 🧳 Load +await fs.mountPrivateNode({ + path: Path.root(), + exchangeKeyPair: { publicKey, privateKey }, +}) ``` ## Transactions diff --git a/packages/nest/src/class.ts b/packages/nest/src/class.ts index e664c72..b7476c1 100644 --- a/packages/nest/src/class.ts +++ b/packages/nest/src/class.ts @@ -262,6 +262,29 @@ export class FileSystem { * @param mutationOptions * @group Mounting */ + async createPrivateNode( + node: { + path: Path.Distinctive + exchangeKeyPair: { + publicKey: CryptoKey | Uint8Array + privateKey: CryptoKey + } + }, + mutationOptions?: MutationOptions + ): Promise<{ + path: Path.Distinctive> + capsuleKey: Uint8Array + shareId: string + }> + async createPrivateNode( + node: { + path: Path.Distinctive + }, + mutationOptions?: MutationOptions + ): Promise<{ + path: Path.Distinctive> + capsuleKey: Uint8Array + }> async createPrivateNode( node: { path: Path.Distinctive @@ -274,6 +297,7 @@ export class FileSystem { ): Promise<{ path: Path.Distinctive> capsuleKey: Uint8Array + shareId?: string }> { const { path } = node const absolutePosixPath = Path.toPosix(path, { absolute: true }) @@ -354,7 +378,15 @@ export class FileSystem { // Share to self, pt. 2 if (node.exchangeKeyPair !== undefined) { - await this.share(pathWithPartition, dataRoot, { mutationOptions }) + const { shareId } = await this.share(pathWithPartition, dataRoot, { + mutationOptions, + }) + + return { + path: pathWithPartition, + capsuleKey: accessKey.toBytes(), + shareId, + } } // Fin @@ -382,6 +414,7 @@ export class FileSystem { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey } + shareId?: string } ): Promise { await this.mountPrivateNodes([node]) @@ -409,6 +442,7 @@ export class FileSystem { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey } + shareId?: string } > ): Promise { @@ -432,6 +466,7 @@ export class FileSystem { await this.calculateDataRoot(), args.exchangeKeyPair, { + shareId: args.shareId, sharerBlockstore: this.#blockstore, } ).then((a) => a.sharedNode) @@ -780,8 +815,8 @@ export class FileSystem { ) } - // IDENTITY - // -------- + // IDENTIFIER + // ---------- async assignIdentifier( did: string, @@ -828,14 +863,14 @@ export class FileSystem { * NOTE: A share can only be received if the exchange key was registered * and the receiver is in possession of the associated private key. * - * @param itemName * @param sharerDataRoot The data root CID from the sharer * @param exchangeKeyPair A RSA-OAEP-256 key pair * @param exchangeKeyPair.publicKey A RSA-OAEP-256 public key in the form of a `CryptoKey` or its modulus bytes * @param exchangeKeyPair.privateKey A RSA-OAEP-256 private key in the form of a `CryptoKey` * @param opts Optional overrides - * @param opts.sharerBlockstore Specify what blockstore to use to load the sharer's file system - * @param opts.sharerRootTreeClass Specify what root tree class was used for the sharer's file system + * @param opts.shareId Specify what shareId to use, otherwise this'll load the last share that was made to the given exchange key. + * @param opts.sharerBlockstore Specify what blockstore to use to load the sharer's file system. + * @param opts.sharerRootTreeClass Specify what root tree class was used for the sharer's file system. * * @group Sharing */ @@ -846,21 +881,16 @@ export class FileSystem { privateKey: CryptoKey }, opts: { + shareId?: string sharerBlockstore?: Blockstore sharerRootTreeClass?: typeof RootTree } = {} - ): Promise<{ - share: Share - }> { - const share = await this.#transactionContext().receive( + ): Promise { + return await this.#transactionContext().receive( sharerDataRoot, exchangeKeyPair, opts ) - - return { - share, - } } /** @@ -914,14 +944,26 @@ export class FileSystem { receiverBlockstore?: Blockstore receiverRootTreeClass?: typeof RootTree } = {} - ): Promise> { - return await this.#infusedTransaction( + ): Promise<{ shareId: string } & MutationResult> { + let shareId: string | undefined + + const result = await this.#infusedTransaction( async (t) => { - await t.share(path, receiverDataRoot, opts) + const shareResult = await t.share(path, receiverDataRoot, opts) + shareId = shareResult.shareId }, path, opts.mutationOptions ) + + if (shareId === undefined) { + throw new Error('`shareId` was not set') + } + + return { + ...result, + shareId, + } } // TRANSACTIONS diff --git a/packages/nest/src/share.ts b/packages/nest/src/share.ts index c96d7d7..64e0274 100644 --- a/packages/nest/src/share.ts +++ b/packages/nest/src/share.ts @@ -1,4 +1,5 @@ import type { Blockstore } from 'interface-blockstore' +import type { PrivateNode } from 'wnfs' import * as Queries from './queries.js' import * as Path from './path.js' @@ -22,12 +23,15 @@ import { dataFromBytes } from './data.js' // CLASS export class Share { + readonly id: string + readonly #blockstore: Blockstore readonly #privateNodes: MountedPrivateNodes readonly #rootTree: RootTree readonly #rng: Rng /** + * @param id * @param blockstore * @param privateNodes * @param rng @@ -35,17 +39,28 @@ export class Share { * @internal */ constructor( + id: string, blockstore: Blockstore, privateNodes: MountedPrivateNodes, rng: Rng, rootTree: RootTree ) { + this.id = id this.#blockstore = blockstore this.#privateNodes = privateNodes this.#rng = rng this.#rootTree = rootTree } + // EXPORT + + export(): PrivateNode { + const node = this.#privateNodes['/'] + if (node === undefined) + throw new Error('Expected a node to be mounted at root') + return node.node + } + // QUERIES /** diff --git a/packages/nest/src/sharing.ts b/packages/nest/src/sharing.ts index 286e1f5..985af41 100644 --- a/packages/nest/src/sharing.ts +++ b/packages/nest/src/sharing.ts @@ -24,6 +24,7 @@ import { ExchangeKey } from './exchange-key.js' * @param exchangeKeyPair.publicKey * @param exchangeKeyPair.privateKey * @param opts + * @param opts.shareId * @param opts.sharerBlockstore * @param opts.sharerRootTreeClass */ @@ -34,10 +35,12 @@ export async function loadShare( privateKey: CryptoKey }, opts: { + shareId?: string sharerBlockstore: Blockstore sharerRootTreeClass?: typeof RootTree } ): Promise<{ + shareId: string sharedNode: PrivateNode sharerRootTree: RootTree }> { @@ -60,14 +63,20 @@ export async function loadShare( throw new Error("The sharer's file system is missing an identifier") // Find the share number - const shareNumber: bigint = await findLatestShareCounter( - 0, - sharerCounter < 1 ? 1 : sharerCounter, - publicKeyResult, - sharerIdentifier, - sharerForest, - Store.wnfs(sharerBlockstore) - ) + const shareNumber: undefined | number | bigint = + opts.shareId === undefined + ? await findLatestShareCounter( + 0, + sharerCounter < 1 ? 1 : sharerCounter, + publicKeyResult, + sharerIdentifier, + sharerForest, + Store.wnfs(sharerBlockstore) + ) + : Number.parseInt(opts.shareId) + + if (shareNumber === undefined) + throw new Error('Failed to determine share number') // Determine share name const shareLabel = createShareName( @@ -95,5 +104,9 @@ export async function loadShare( Store.wnfs(sharerBlockstore) ) - return { sharedNode, sharerRootTree } + return { + shareId: Number(shareNumber).toString(), + sharedNode, + sharerRootTree, + } } diff --git a/packages/nest/src/transaction.ts b/packages/nest/src/transaction.ts index dc14993..4aa39c3 100644 --- a/packages/nest/src/transaction.ts +++ b/packages/nest/src/transaction.ts @@ -561,7 +561,7 @@ export class TransactionContext { } } - // IDENTITY + // IDENTIFIER async assignIdentifier(did: string): Promise { this.#rootTree = await this.#rootTree.replaceDID(did) @@ -616,14 +616,14 @@ export class TransactionContext { * NOTE: A share can only be received if the exchange key was registered * and the receiver is in possession of the associated private key. * - * @param itemName * @param sharerDataRoot The data root CID from the sharer * @param exchangeKeyPair A RSA-OAEP-256 key pair * @param exchangeKeyPair.publicKey A RSA-OAEP-256 public key in the form of a `CryptoKey` or its modulus bytes * @param exchangeKeyPair.privateKey A RSA-OAEP-256 private key in the form of a `CryptoKey` * @param opts Optional overrides - * @param opts.sharerBlockstore Specify what blockstore to use to load the sharer's file system - * @param opts.sharerRootTreeClass Specify what root tree class was used for the sharer's file system + * @param opts.shareId Specify what shareId to use, otherwise this'll load the last share that was made to the given exchange key. + * @param opts.sharerBlockstore Specify what blockstore to use to load the sharer's file system. + * @param opts.sharerRootTreeClass Specify what root tree class was used for the sharer's file system. * * @group Sharing */ @@ -634,16 +634,18 @@ export class TransactionContext { privateKey: CryptoKey }, opts: { + shareId?: string sharerBlockstore?: Blockstore sharerRootTreeClass?: typeof RootTree } = {} ): Promise { const sharerBlockstore = opts.sharerBlockstore ?? this.#blockstore - const { sharedNode, sharerRootTree } = await loadShare( + const { shareId, sharedNode, sharerRootTree } = await loadShare( sharerDataRoot, exchangeKeyPair, { sharerBlockstore, + shareId: opts.shareId, sharerRootTreeClass: opts.sharerRootTreeClass, } ) @@ -654,6 +656,7 @@ export class TransactionContext { ) return new Share( + shareId, sharerBlockstore, { [Path.toPosix(path, { absolute: true })]: { path, node: sharedNode } }, this.#rng, @@ -715,7 +718,7 @@ export class TransactionContext { receiverBlockstore?: Blockstore receiverRootTreeClass?: typeof RootTree } = {} - ): Promise { + ): Promise<{ shareId: string }> { const did = this.identifier() if (did === undefined) @@ -738,9 +741,29 @@ export class TransactionContext { receiverDataRoot ) + const receiverWnfsBlockstore = Store.wnfs(receiverBlockstore) const exchangeRoot: Uint8Array = await receiverRootTree .exchangeRoot() - .store(Store.wnfs(receiverBlockstore)) + .store(receiverWnfsBlockstore) + + // Create a "merged" blockstore + const sharerBlockstore = Store.wnfs(this.#blockstore) + + const mergedBlockstore = { + async getBlock(cid: Uint8Array): Promise { + if (await sharerBlockstore.hasBlock(cid)) + return await sharerBlockstore.getBlock(cid) + return await receiverWnfsBlockstore.getBlock(cid) + }, + + async hasBlock(cid: Uint8Array): Promise { + if (await sharerBlockstore.hasBlock(cid)) return true + return await receiverWnfsBlockstore.hasBlock(cid) + }, + + putBlockKeyed: sharerBlockstore.putBlockKeyed.bind(sharerBlockstore), + putBlock: sharerBlockstore.putBlock?.bind(sharerBlockstore), + } // Create share const forest: PrivateForest = await share( @@ -749,11 +772,11 @@ export class TransactionContext { did, exchangeRoot, this.#rootTree.privateForest(), - Store.wnfs(this.#blockstore) + mergedBlockstore ) // Update counter - await this.#rootTree.increaseShareCounter() + this.#rootTree = await this.#rootTree.increaseShareCounter() // Modification const change = { @@ -765,6 +788,11 @@ export class TransactionContext { // Replace root tree this.#rootTree = await this.#rootTree.replacePrivateForest(forest, [change]) + + // Fin + return { + shareId: counter.toString(), + } } // ㊙️ ▒▒ QUERIES diff --git a/packages/nest/test/class.test.ts b/packages/nest/test/class.test.ts index 06df36a..13e5d35 100644 --- a/packages/nest/test/class.test.ts +++ b/packages/nest/test/class.test.ts @@ -1201,7 +1201,7 @@ describe('File System Class', () => { // Receive share const sharerDataRoot = b.dataRoot - const { share } = await receiverFs.receive(sharerDataRoot, keypair) + const share = await receiverFs.receive(sharerDataRoot, keypair) const content = await share.read( 'utf8', @@ -1360,4 +1360,142 @@ describe('File System Class', () => { assert.equal(content, '✌️') }) + + it('can share multiple items and still load the older ones', async () => { + const keypairA = await ExchangeKey.generate() + const keypairB = await ExchangeKey.generate() + + const receiverFs = await FileSystem.create({ + blockstore, + ...fsOpts, + }) + + // Register exchange key + await receiverFs.registerExchangeKey('device-a', keypairA.publicKey) + await receiverFs.registerExchangeKey('device-b', keypairB.publicKey) + const receiverDataRoot = await receiverFs.calculateDataRoot() + + // Assign sharer identifier & create share + await fs.assignIdentifier('did:test:5') + + await fs.write(Path.file('private', 'file 1'), 'utf8', '🔐 1') + await fs.write(Path.file('private', 'file 2'), 'utf8', '🔒 2') + + const { shareId } = await fs.share( + Path.file('private', 'file 1'), + receiverDataRoot + ) + + await fs.share(Path.file('private', 'file 2'), receiverDataRoot) + + // Receive shares + const sharerDataRoot = await fs.calculateDataRoot() + + const content1 = await receiverFs + .receive(sharerDataRoot, keypairA, { shareId }) + .then(async (share) => await share.read('utf8')) + + const content2 = await receiverFs + .receive(sharerDataRoot, keypairB) + .then(async (share) => await share.read('utf8')) + + // Assert + assert.equal(content1, '🔐 1') + assert.equal(content2, '🔒 2') + }) + + it('can create multiple private nodes with an exchange key and still mount the older one', async () => { + const keypair = await ExchangeKey.generate() + + fs = await FileSystem.create({ + blockstore, + ...fsOpts, + }) + + await fs.assignIdentifier('did:test:6') + await fs.registerExchangeKey('device', keypair.publicKey) + + const first = await fs.createPrivateNode({ + path: Path.directory('first'), + exchangeKeyPair: keypair, + }) + + const _second = await fs.createPrivateNode({ + path: Path.directory('second'), + exchangeKeyPair: keypair, + }) + + await fs.write( + Path.file('private', 'first', 'nested', 'file'), + 'utf8', + '🔑' + ) + + await fs.write(Path.file('private', 'second', 'file'), 'utf8', '🔒') + + const fsInstance = await FileSystem.fromCID(await fs.calculateDataRoot(), { + blockstore, + }) + + await fsInstance.mountPrivateNode({ + path: Path.directory('first'), + exchangeKeyPair: keypair, + shareId: first.shareId, + }) + + await fsInstance.mountPrivateNode({ + path: Path.directory('second'), + exchangeKeyPair: keypair, + }) + + const contents = await fsInstance.read( + Path.file('private', 'first', 'nested', 'file'), + 'utf8' + ) + + assert.equal(contents, '🔑') + + const contents2 = await fsInstance.read( + Path.file('private', 'second', 'file'), + 'utf8' + ) + + assert.equal(contents2, '🔒') + }) + + it('can create a private node using an exchange key and then later mount it using the capsule key', async () => { + const keypair = await ExchangeKey.generate() + + fs = await FileSystem.create({ + blockstore, + ...fsOpts, + }) + + await fs.assignIdentifier('did:test:7') + await fs.registerExchangeKey('device', keypair.publicKey) + + await fs.createPrivateNode({ + path: Path.root(), + exchangeKeyPair: keypair, + }) + + const { capsuleKey, dataRoot } = await fs.write( + Path.file('private', 'file'), + 'utf8', + '👀' + ) + + const fsInstance = await FileSystem.fromCID(dataRoot, { + blockstore, + }) + + await fsInstance.mountPrivateNode({ + path: Path.root(), + capsuleKey, + }) + + const contents = await fs.read(Path.file('private', 'file'), 'utf8') + + assert.equal(contents, '👀') + }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d22e1f2..cc58702 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,34 @@ importers: specifier: ^3.3.0 version: 3.3.0(vite@5.2.2) + examples/passkey: + dependencies: + '@wnfs-wg/nest': + specifier: '*' + version: link:../../packages/nest + idb-keyval: + specifier: ^6.2.1 + version: 6.2.1 + iso-base: + specifier: ^4.0.0 + version: 4.0.0 + iso-passkeys: + specifier: ^0.2.2 + version: 0.2.2 + devDependencies: + '@rsbuild/core': + specifier: ^0.5.2 + version: 0.5.2 + '@types/node': + specifier: ^20.11.30 + version: 20.11.30 + '@types/qrcode': + specifier: ^1.5.5 + version: 1.5.5 + typescript: + specifier: 5.4.3 + version: 5.4.3 + examples/web3storage: dependencies: '@picocss/pico': @@ -458,6 +486,54 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@cbor-extract/cbor-extract-darwin-arm64@2.2.0: + resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-darwin-x64@2.2.0: + resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-arm64@2.2.0: + resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-arm@2.2.0: + resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-x64@2.2.0: + resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-win32-x64@2.2.0: + resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@chainsafe/is-ip@2.0.2: resolution: {integrity: sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA==} @@ -1378,6 +1454,10 @@ packages: resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==} dev: false + /@noble/ed25519@2.1.0: + resolution: {integrity: sha512-KM4qTyXPinyCgMzeYJH/UudpdL+paJXtY3CHtHYZQtBkS8MZoPr4rOikZllIutJe0d06QDQKisyn02gxZ8TcQA==} + dev: false + /@noble/hashes@1.3.3: resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} engines: {node: '>= 16'} @@ -1409,6 +1489,33 @@ packages: fastq: 1.17.1 dev: true + /@peculiar/asn1-ecc@2.3.8: + resolution: {integrity: sha512-Ah/Q15y3A/CtxbPibiLM/LKcMbnLTdUdLHUgdpB5f60sSvGkXzxJCu5ezGTFHogZXWNX3KSmYqilCrfdmBc6pQ==} + dependencies: + '@peculiar/asn1-schema': 2.3.8 + '@peculiar/asn1-x509': 2.3.8 + asn1js: 3.0.5 + tslib: 2.6.2 + dev: false + + /@peculiar/asn1-schema@2.3.8: + resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} + dependencies: + asn1js: 3.0.5 + pvtsutils: 1.3.5 + tslib: 2.6.2 + dev: false + + /@peculiar/asn1-x509@2.3.8: + resolution: {integrity: sha512-voKxGfDU1c6r9mKiN5ZUsZWh3Dy1BABvTM3cimf0tztNwyMJPhiXY94eRTgsMQe6ViLfT6EoXxkWVzcm3mFAFw==} + dependencies: + '@peculiar/asn1-schema': 2.3.8 + asn1js: 3.0.5 + ipaddr.js: 2.1.0 + pvtsutils: 1.3.5 + tslib: 2.6.2 + dev: false + /@perma/map@1.0.3: resolution: {integrity: sha512-Bf5njk0fnJGTFE2ETntq0N1oJ6YdCPIpTDn3R3KYZJQdeYSOCNL7mBrFlGnbqav8YQhJA/p81pvHINX9vAtHkQ==} dependencies: @@ -1940,6 +2047,10 @@ packages: resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} dev: false + /@types/retry@0.12.2: + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + dev: false + /@types/semver@7.5.8: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true @@ -2621,6 +2732,15 @@ packages: engines: {node: '>=8'} dev: true + /asn1js@3.0.5: + resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + engines: {node: '>=12.0.0'} + dependencies: + pvtsutils: 1.3.5 + pvutils: 1.1.3 + tslib: 2.6.2 + dev: false + /assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} dependencies: @@ -2816,6 +2936,28 @@ packages: resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} dev: true + /cbor-extract@2.2.0: + resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.1.1 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 + '@cbor-extract/cbor-extract-linux-x64': 2.2.0 + '@cbor-extract/cbor-extract-win32-x64': 2.2.0 + dev: false + optional: true + + /cbor-x@1.5.9: + resolution: {integrity: sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==} + optionalDependencies: + cbor-extract: 2.2.0 + dev: false + /cborg@4.1.3: resolution: {integrity: sha512-I8sAcVtiarz0dZ4IYixNUaL2hIl9cMDjo1ytI57F5fUlekTEO5Im8aXbAvsuayeP76hHSPRMwos0AUuntHJjqQ==} hasBin: true @@ -3006,6 +3148,21 @@ packages: semver: 7.6.0 dev: false + /conf@12.0.0: + resolution: {integrity: sha512-fIWyWUXrJ45cHCIQX+Ck1hrZDIf/9DR0P0Zewn3uNht28hbt5OfGUq8rRWsxi96pZWPyBEd0eY9ama01JTaknA==} + engines: {node: '>=18'} + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + atomically: 2.0.2 + debounce-fn: 5.1.2 + dot-prop: 8.0.2 + env-paths: 3.0.0 + json-schema-typed: 8.0.1 + semver: 7.6.0 + uint8array-extras: 0.3.0 + dev: false + /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true @@ -3157,6 +3314,11 @@ packages: object-keys: 1.1.1 dev: true + /delay@6.0.0: + resolution: {integrity: sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==} + engines: {node: '>=16'} + dev: false + /depcheck@1.4.7: resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} engines: {node: '>=10'} @@ -3198,6 +3360,17 @@ packages: engines: {node: '>=0.10.0'} dev: true + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + requiresBuild: true + dev: false + optional: true + + /did-resolver@4.1.0: + resolution: {integrity: sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==} + dev: false + /diff@5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} @@ -3268,6 +3441,13 @@ packages: type-fest: 2.19.0 dev: false + /dot-prop@8.0.2: + resolution: {integrity: sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==} + engines: {node: '>=16'} + dependencies: + type-fest: 3.13.1 + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -3309,7 +3489,7 @@ packages: resolution: {integrity: sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==} engines: {node: '>=10.13.0'} dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 tapable: 2.2.1 dev: true @@ -4651,6 +4831,11 @@ packages: side-channel: 1.0.6 dev: true + /ipaddr.js@2.1.0: + resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} + engines: {node: '>= 10'} + dev: false + /ipfs-unixfs-exporter@13.5.0: resolution: {integrity: sha512-s1eWXzoyhQFNEAB1p+QE3adjhW+lBdgpORmmjiCLiruHs5z7T5zsAgRVcWpM8LWYhq2flRtJHObb7Hg73J+oLQ==} dependencies: @@ -4896,6 +5081,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-network-error@1.1.0: + resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} + engines: {node: '>=16'} + dev: false + /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -5012,11 +5202,54 @@ packages: bigint-mod-arith: 3.3.1 dev: false + /iso-base@4.0.0: + resolution: {integrity: sha512-Vf+6r7XlP+LQD0HSR0ZBLSj/mwuP+59ElbgMKT+iFSnkBW2RzRohQScgKfPCYbaGZAjEdbi40eYq28E/RAnrVA==} + dependencies: + base-x: 4.0.0 + bigint-mod-arith: 3.3.1 + dev: false + + /iso-did@1.6.0: + resolution: {integrity: sha512-DBq2MzTMGMJgYs/3jhj+h/EuDuQs7NWUYwNtXpBgbTRl6CVqsu0nAHqzxIzaZYJbb05msnq7KUwMCZmLCQ9asA==} + dependencies: + did-resolver: 4.1.0 + iso-base: 2.0.1 + iso-web: 1.0.5 + multiformats: 13.1.0 + dev: false + + /iso-kv@3.0.2: + resolution: {integrity: sha512-DL4TNf1SRVskOKRsEk2QjMHsLUWh1H+iV4LPc9dMDfi1wcb/HlkGl+9ETphk3FN5ToL6l6CUKdeHGYyOPdmMHg==} + dependencies: + conf: 12.0.0 + idb-keyval: 6.2.1 + kysely: 0.27.3 + dev: false + + /iso-passkeys@0.2.2: + resolution: {integrity: sha512-CsuztK2+FOmlq7LVNhwLYiDDeVzBqCL/iHFWzpY8YhiNgiBMVi8Vtxb5pev780QWgKKh3XmLPPH/rgaZ2XuIPQ==} + dependencies: + '@noble/ed25519': 2.1.0 + '@peculiar/asn1-ecc': 2.3.8 + '@peculiar/asn1-schema': 2.3.8 + cbor-x: 1.5.9 + iso-base: 2.0.1 + iso-did: 1.6.0 + dev: false + /iso-url@1.2.1: resolution: {integrity: sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==} engines: {node: '>=12'} dev: false + /iso-web@1.0.5: + resolution: {integrity: sha512-ZRZ5BgGAvwau+WtNLZXp1byawaMPFUFnTwVHLXKFXIKxz0CD9hkEkSe505kxcNEui0TqVEQBOwg4806au1djGg==} + dependencies: + delay: 6.0.0 + iso-kv: 3.0.2 + p-retry: 6.2.0 + dev: false + /iso-websocket@0.2.0: resolution: {integrity: sha512-imBalzmPSq0C9CfMouimB2kZ5X1qS4Yai8kGTQdluGRb0T0iu+BkPcakFelh4FIlTM8y6+BNuCEGog3lf8HC4A==} dependencies: @@ -5276,6 +5509,11 @@ packages: engines: {node: '>=6'} dev: true + /kysely@0.27.3: + resolution: {integrity: sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==} + engines: {node: '>=14.0.0'} + dev: false + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -5669,6 +5907,15 @@ packages: whatwg-url: 5.0.0 dev: false + /node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 2.0.3 + dev: false + optional: true + /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true @@ -5909,6 +6156,15 @@ packages: retry: 0.13.1 dev: false + /p-retry@6.2.0: + resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} + engines: {node: '>=16.17'} + dependencies: + '@types/retry': 0.12.2 + is-network-error: 1.1.0 + retry: 0.13.1 + dev: false + /p-timeout@6.1.2: resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} engines: {node: '>=14.16'} @@ -6173,6 +6429,17 @@ packages: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} dev: true + /pvtsutils@1.3.5: + resolution: {integrity: sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==} + dependencies: + tslib: 2.6.2 + dev: false + + /pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + dev: false + /qrcode@1.5.3: resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} engines: {node: '>=10.13.0'} @@ -6904,7 +7171,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /tsutils-etc@1.4.2(tsutils@3.21.0)(typescript@5.3.3): resolution: {integrity: sha512-2Dn5SxTDOu6YWDNKcx1xu2YUy6PUeKrWZB/x2cQ8vY2+iz3JRembKn/iZ0JLT1ZudGNwQQvtFX9AwvRHbXuPUg==} @@ -6974,7 +7240,6 @@ packages: /type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} - dev: true /type-fest@4.13.1: resolution: {integrity: sha512-ASMgM+Vf2cLwDMt1KXSkMUDSYCxtckDJs8zsaVF/mYteIsiARKCVtyXtcK38mIKbLTctZP8v6GMqdNaeI3fo7g==} @@ -7092,6 +7357,11 @@ packages: uint8arraylist: 2.4.8 uint8arrays: 5.0.3 + /uint8array-extras@0.3.0: + resolution: {integrity: sha512-erJsJwQ0tKdwuqI0359U8ijkFmfiTcq25JvvzRVc1VP+2son1NJRXhxcAKJmAW3ajM8JSGAfsAXye8g4s+znxA==} + engines: {node: '>=18'} + dev: false + /uint8arraylist@2.4.8: resolution: {integrity: sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==} dependencies: @@ -7253,7 +7523,7 @@ packages: engines: {node: '>=10.13.0'} dependencies: glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 dev: true /webidl-conversions@3.0.1: