Skip to content
Open
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: 5 additions & 0 deletions .nx/version-plans/version-plan-1765995084329.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@storacha/encrypt-upload-client': major
---

Add Lit v8 support and delegation revocation checks to Lit Actions
73 changes: 46 additions & 27 deletions packages/encrypt-upload-client/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
<h1 align="center">🐔 + 🔑<br/>Encrypt Upload Client</h1>
<p align="center">Use Lit Protocol and Storacha Network to enable private decentralized hot storage.</a></p>

## 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
```

## 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

Expand All @@ -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)!
Expand Down
2 changes: 1 addition & 1 deletion packages/encrypt-upload-client/examples/decrypt-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
2 changes: 1 addition & 1 deletion packages/encrypt-upload-client/examples/encrypt-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
18 changes: 3 additions & 15 deletions packages/encrypt-upload-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you will need to verify if the Console App (encryption/decryption) still works. Mainly because these factories were put into different files to be able to build and run the Console App (decryption/encryption using browser lib). If I remember correctly, the Lit lib doesn't support that and was causing several issues.

"import": "./dist/crypto/factories.js",
"require": "./dist/crypto/factories.js"
},
"./types": "./dist/types.js"
},
Expand Down
1 change: 1 addition & 0 deletions packages/encrypt-upload-client/src/config/constants.js
Original file line number Diff line number Diff line change
@@ -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')
26 changes: 0 additions & 26 deletions packages/encrypt-upload-client/src/crypto/factories.browser.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/encrypt-upload-client/test/factories.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
37 changes: 23 additions & 14 deletions packages/ui/packages/react/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<KMSConfig | undefined>(defaultConfig)


const [kmsConfig, setKmsConfig] = useState<KMSConfig | undefined>(
defaultConfig
)

// Helper function to create KMS adapter with fallbacks
const createKMSAdapter = useCallback(async (): Promise<any | null> => {
if (!kmsConfig?.keyManagerServiceURL || !kmsConfig?.keyManagerServiceDID) {
return null
}

try {
const { createGenericKMSAdapter } = await import('@storacha/encrypt-upload-client/factories.browser')

const { createGenericKMSAdapter } = await import(
'@storacha/encrypt-upload-client/factories'
)

return createGenericKMSAdapter(
kmsConfig.keyManagerServiceURL,
kmsConfig.keyManagerServiceDID,
Expand All @@ -87,12 +94,14 @@ export function useKMSConfig(initialConfig?: KMSConfig): {
return null
}
}, [kmsConfig])

return {
kmsConfig,
setKmsConfig,
createKMSAdapter,
isConfigured: Boolean(kmsConfig?.keyManagerServiceURL && kmsConfig?.keyManagerServiceDID)
isConfigured: Boolean(
kmsConfig?.keyManagerServiceURL && kmsConfig?.keyManagerServiceDID
),
}
}

Expand All @@ -114,14 +123,14 @@ export function useDatamodel({
connection,
receiptsEndpoint,
})

startTransition(() => {
setClient(client)
setEvents(events)
setAccounts(Object.values(client.accounts()))
setSpaces(client.spaces())
})

if (!skipInitialClaim) {
await client.capability.access.claim()
}
Expand Down
Loading
Loading