diff --git a/.nx/version-plans/version-plan-1765995084329.md b/.nx/version-plans/version-plan-1765995084329.md new file mode 100644 index 000000000..090317fbd --- /dev/null +++ b/.nx/version-plans/version-plan-1765995084329.md @@ -0,0 +1,5 @@ +--- +'@storacha/encrypt-upload-client': major +--- + +Add Lit v8 support and delegation revocation checks to Lit Actions diff --git a/packages/encrypt-upload-client/README.md b/packages/encrypt-upload-client/README.md index 2d72d0556..7fd9aad5e 100644 --- a/packages/encrypt-upload-client/README.md +++ b/packages/encrypt-upload-client/README.md @@ -1,13 +1,27 @@
Use Lit Protocol and Storacha Network to enable private decentralized hot storage.
## About -This library leverages `@storacha/cli` and `@lit-protocol/lit-node-client` to provide a simple interface for encrypting files with Lit Protocol and uploading them to the Storacha Network. It also enables anyone with a valid `space/content/decrypt` UCAN delegation to decrypt the file. With Lit Protocol, encryption keys are managed in a decentralized way, so you don't have to handle them yourself. +This library provides client-side encryption and key management solution integrations for files stored on Storacha. + +**The library is strategy-agnostic**, meaning it supports different key management solutions without changing your integration code: + +- **Lit Protocol** - Decentralized encryption with UCAN-friendly validation inside programmable Lit Actions +- **Google KMS** - Centralized key management for enterprise compliance and auditability + +Different apps have different needs: some prioritize decentralization and user sovereignty, while others need to satisfy enterprise compliance or data residency rules. The library unifies the flow so you can switch from a centralized to a decentralized key management solution (or vice versa) without rewriting your entire logic. + +**Key features:** + +- **Client-side streaming encryption** - memory-efficient for large files +- **Decentralized key management** via Lit Protocol's threshold cryptography network +- **UCAN-based access control** - grant and revoke decrypt permissions without re-encrypting +- **Pluggable crypto adapters** - use Lit Protocol, Google KMS, or implement your own +- **Composable architecture** - integrate seamlessly with existing Storacha workflows ## Install -You can add the `@storacha/encrypt-upload-client` package to your JavaScript or TypeScript project with `npm`: +You can add the `@storacha/encrypt-upload-client` package to your project with `npm`: ```sh npm install @storacha/encrypt-upload-client @@ -15,7 +29,7 @@ npm install @storacha/encrypt-upload-client ## Usage -To use this library, you'll need to install `@storacha/cli` and `@lit-protocol/lit-node-client`, as they are required for initialization—though the Lit client is optional. You must also provide a crypto adapter that implements the `CryptoAdapter` interface. A ready-to-use Node.js & Browser crypto adapters are available. +To use this library, you'll need to install `@storacha/client`. If using the Lit Protocol adapter, you'll also need to install `@lit-protocol/lit-client` and `@lit-protocol/auth`, as they are required for initialization. You must also provide a crypto adapter that implements the `CryptoAdapter` interface. ### CryptoAdapter Interface @@ -32,60 +46,65 @@ interface EncryptOutput { } ``` -### Node Usage - -```js -const encryptedClient = await EncryptClient.create({ - storachaClient, - cryptoAdapter: new NodeCryptoAdapter(), -}) -``` - -### Browser Usage - -For browser apps, use the `BrowserCryptoAdapter`: +### Usage ```js -import { BrowserCryptoAdapter } from '@storacha/encrypt-upload-client/crypto-adapters/browser-crypto-adapter.js' - -const encryptedClient = await EncryptClient.create({ +// Using the Lit adapter +const encryptedClient = await create({ storachaClient: client, - cryptoAdapter: new BrowserCryptoAdapter(), + cryptoAdapter: createGenericLitAdapter(litClient, authManager), }) ``` ### Encryption -The encryption process automatically generates a custom Access Control Condition (ACC) based on the current space setup in your Storacha client. It then creates a symmetric key to encrypt the file and uses Lit Protocol to encrypt that key, so you don't have to manage it yourself. Once encrypted, both the file and the generated encrypted metadata are uploaded to Storacha. +The encryption process with Lit Adapter automatically generates a custom Access Control Condition (ACC) based on the current space in your Storacha client. It then creates a symmetric key to encrypt the file and uses the Lit Protocol to encrypt that key, so you don't have to manage it yourself. Once encrypted, both the file and the generated encrypted metadata are uploaded to Storacha. #### Encryption Example ```js const fileContent = await fs.promises.readFile('./README.md') const blob = new Blob([fileContent]) -const cid = await encryptedClient.uploadEncryptedFile(blob) + +const encryptionConfig = { + issuer: principal, + spaceDID: space.did(), +} + +const cidLink = await encryptedClient.encryptAndUploadFile( + blob, + encryptionConfig +) ``` You can find a full example in `examples/encrypt-test.js`. ### Decryption -To decrypt a file, you'll need the CID returned from `uploadEncryptedFile`, a UCAN delegation CAR with the `space/content/decrypt` capability (proving that the file owner has granted you decryption access), and an Ethereum wallet with available Capacity Credits on the Lit Network to cover the decryption cost. - -For details on minting Capacity Credits, check out the [official documentation](https://developer.litprotocol.com/concepts/capacity-credits-concept). +To decrypt a file, you'll need the CID returned from `encryptAndUploadFile`, a UCAN delegation with the `space/content/decrypt` capability (proving that the file owner has granted you decryption access), and any other parameters specific to the selected adapter. #### Decryption Example ```js -const decryptedContent = await encryptedClient.retrieveAndDecryptFile( +// Lit adapter using an EOA wallet +const decryptionConfig = { wallet, + decryptDelegation, + spaceDID, +} + +const decryptedContent = await encryptedClient.retrieveAndDecryptFile( cid, - delegationCarBuffer + decryptionConfig ) ``` You can find a full example in `examples/decrypt-test.js`. +## Using PKP (Programmable Key Pairs) + +If you want to use the Lit Protocol adapter without requiring a wallet (EOA account) for decryption, you can use a PKP (Programmable Key Pair). Check out the [demo code using PKP](https://github.com/storacha/lit-pkp-demo). + ## Contributing Feel free to join in. All welcome. Please [open an issue](https://github.com/storacha/upload-service/issues)! diff --git a/packages/encrypt-upload-client/examples/decrypt-test.js b/packages/encrypt-upload-client/examples/decrypt-test.js index 559e4eb08..94d7e40e6 100644 --- a/packages/encrypt-upload-client/examples/decrypt-test.js +++ b/packages/encrypt-upload-client/examples/decrypt-test.js @@ -11,7 +11,7 @@ import { privateKeyToAccount } from 'viem/accounts' import { create } from '../src/index.js' import { serviceConf, receiptsEndpoint } from '../src/config/service.js' -import { createGenericLitAdapter } from '../src/crypto/factories.node.js' +import { createGenericLitAdapter } from '../src/crypto/factories.js' import { extract } from '@ucanto/core/delegation' import { createAuthManager, storagePlugins } from '@lit-protocol/auth' diff --git a/packages/encrypt-upload-client/examples/encrypt-test.js b/packages/encrypt-upload-client/examples/encrypt-test.js index e9c45a818..f1a696f20 100644 --- a/packages/encrypt-upload-client/examples/encrypt-test.js +++ b/packages/encrypt-upload-client/examples/encrypt-test.js @@ -12,7 +12,7 @@ import { createLitClient } from '@lit-protocol/lit-client' import * as EncryptClient from '../src/index.js' import { serviceConf, receiptsEndpoint } from '../src/config/service.js' -import { createGenericLitAdapter } from '../src/crypto/factories.node.js' +import { createGenericLitAdapter } from '../src/crypto/factories.js' // import { CID } from 'multiformats' import { createAuthManager, storagePlugins } from '@lit-protocol/auth' diff --git a/packages/encrypt-upload-client/package.json b/packages/encrypt-upload-client/package.json index dba349663..23d1f3f5e 100644 --- a/packages/encrypt-upload-client/package.json +++ b/packages/encrypt-upload-client/package.json @@ -37,21 +37,9 @@ "import": "./dist/index.js", "require": "./dist/index.js" }, - "./factories.node": { - "import": "./dist/crypto/factories.node.js", - "require": "./dist/crypto/factories.node.js" - }, - "./factories.browser": { - "import": "./dist/crypto/factories.browser.js", - "require": "./dist/crypto/factories.browser.js" - }, - "./node": { - "import": "./dist/crypto/symmetric/node-aes-cbc-crypto.js", - "require": "./dist/crypto/symmetric/node-aes-cbc-crypto.js" - }, - "./browser": { - "import": "./dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js", - "require": "./dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js" + "./factories": { + "import": "./dist/crypto/factories.js", + "require": "./dist/crypto/factories.js" }, "./types": "./dist/types.js" }, diff --git a/packages/encrypt-upload-client/src/config/constants.js b/packages/encrypt-upload-client/src/config/constants.js index a1426e34a..d4cbad319 100644 --- a/packages/encrypt-upload-client/src/config/constants.js +++ b/packages/encrypt-upload-client/src/config/constants.js @@ -1,4 +1,5 @@ export const STORACHA_LIT_ACTION_CID = 'QmbJJX7nBZafj4kUGPc9LPSdBvHACxhPWdZSVHcAHP9rik' +// TODO: update lit action to use Lit runOnce export const GATEWAY_URL = new URL('https://storacha.link') diff --git a/packages/encrypt-upload-client/src/crypto/factories.browser.js b/packages/encrypt-upload-client/src/crypto/factories.browser.js deleted file mode 100644 index 223635ef2..000000000 --- a/packages/encrypt-upload-client/src/crypto/factories.browser.js +++ /dev/null @@ -1,26 +0,0 @@ -import { GenericAesCtrStreamingCrypto } from './symmetric/generic-aes-ctr-streaming-crypto.js' -import { KMSCryptoAdapter } from './adapters/kms-crypto-adapter.js' - -/** - * Create a KMS crypto adapter for browser environments - * Uses the generic AES-CTR streaming crypto implementation - * Works in browser and Node.js environments - * - * @param {URL|string} keyManagerServiceURL - * @param {string} keyManagerServiceDID - * @param {object} [options] - Optional configuration - * @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production) - */ -export function createGenericKMSAdapter( - keyManagerServiceURL, - keyManagerServiceDID, - options = {} -) { - const symmetricCrypto = new GenericAesCtrStreamingCrypto() - return new KMSCryptoAdapter( - symmetricCrypto, - keyManagerServiceURL, - /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID), - options - ) -} diff --git a/packages/encrypt-upload-client/src/crypto/factories.node.js b/packages/encrypt-upload-client/src/crypto/factories.js similarity index 71% rename from packages/encrypt-upload-client/src/crypto/factories.node.js rename to packages/encrypt-upload-client/src/crypto/factories.js index 2729711cf..837d32e8f 100644 --- a/packages/encrypt-upload-client/src/crypto/factories.node.js +++ b/packages/encrypt-upload-client/src/crypto/factories.js @@ -4,26 +4,32 @@ import { LitCryptoAdapter } from './adapters/lit-crypto-adapter.js' import * as Type from '../types.js' /** - * Create a KMS crypto adapter for Node.js using the generic AES-CTR streaming crypto. - * Works in Node.js & browser environments. + * Create a KMS crypto adapter + * Uses the generic AES-CTR streaming crypto implementation + * Works in browser and Node.js environments * * @param {URL|string} keyManagerServiceURL * @param {string} keyManagerServiceDID + * @param {object} [options] - Optional configuration + * @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production) */ export function createGenericKMSAdapter( keyManagerServiceURL, - keyManagerServiceDID + keyManagerServiceDID, + options = {} ) { const symmetricCrypto = new GenericAesCtrStreamingCrypto() return new KMSCryptoAdapter( symmetricCrypto, keyManagerServiceURL, - /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID) + /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID), + options ) } /** - * Create a Lit crypto adapter for Node.js using the generic AES-CTR streaming crypto. + * Create a Lit crypto adapter + * Uses the generic AES-CTR streaming crypto. * Works in Node.js & browser environments. * * @param {import('@lit-protocol/lit-client').LitClientType} litClient diff --git a/packages/encrypt-upload-client/test/factories.spec.js b/packages/encrypt-upload-client/test/factories.spec.js index e4e3c2687..f97e32767 100644 --- a/packages/encrypt-upload-client/test/factories.spec.js +++ b/packages/encrypt-upload-client/test/factories.spec.js @@ -5,7 +5,7 @@ import assert from 'node:assert' import { createGenericKMSAdapter, createGenericLitAdapter, -} from '../src/crypto/factories.node.js' +} from '../src/crypto/factories.js' import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js' import { LitCryptoAdapter } from '../src/crypto/adapters/lit-crypto-adapter.js' import { KMSCryptoAdapter } from '../src/crypto/adapters/kms-crypto-adapter.js' diff --git a/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js b/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js index e6d8cbffc..ac8b84bbd 100644 --- a/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js +++ b/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js @@ -144,7 +144,7 @@ await describe('LitCryptoAdapter', async () => { await test('should verify factory function behavior', async () => { const { createGenericLitAdapter } = await import( - '../src/crypto/factories.node.js' + '../src/crypto/factories.js' ) const genericAdapter = createGenericLitAdapter( diff --git a/packages/ui/packages/react/src/hooks.ts b/packages/ui/packages/react/src/hooks.ts index 962747033..a962da32d 100644 --- a/packages/ui/packages/react/src/hooks.ts +++ b/packages/ui/packages/react/src/hooks.ts @@ -55,26 +55,33 @@ export function useKMSConfig(initialConfig?: KMSConfig): { } { // Merge initial config with environment variable fallbacks const defaultConfig = { - keyManagerServiceURL: process.env.NEXT_PUBLIC_UCAN_KMS_URL ?? 'https://kms.storacha.network', - keyManagerServiceDID: process.env.NEXT_PUBLIC_UCAN_KMS_DID ?? 'did:web:kms.storacha.network', + keyManagerServiceURL: + process.env.NEXT_PUBLIC_UCAN_KMS_URL ?? 'https://kms.storacha.network', + keyManagerServiceDID: + process.env.NEXT_PUBLIC_UCAN_KMS_DID ?? 'did:web:kms.storacha.network', location: process.env.NEXT_PUBLIC_UCAN_KMS_LOCATION, keyring: process.env.NEXT_PUBLIC_UCAN_KMS_KEYRING, - allowInsecureHttp: process.env.NEXT_PUBLIC_UCAN_KMS_ALLOW_INSECURE_HTTP === 'true', + allowInsecureHttp: + process.env.NEXT_PUBLIC_UCAN_KMS_ALLOW_INSECURE_HTTP === 'true', // Override with any provided initial config values - ...initialConfig + ...initialConfig, } - - const [kmsConfig, setKmsConfig] = useState