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
4 changes: 2 additions & 2 deletions docs/Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Seal consists of two main components:
- **Off-chain Key Servers:** Key servers are off-chain services, each holding a single IBE master secret key. Users can request a derived secret key for a specific identity. The key server returns the derived key only if the associated onchain access policy approves the request.

Consider the following basic example for realizing time-lock encryption:
```rust
```move
module patterns::tle;

use sui::bcs;
Expand All @@ -35,7 +35,7 @@ const ENoAccess : u64 = 1;
/// The following function accepts only the inner identity, i.e., [bcs::to_bytes(time)], and Seal extends it with the namespace.
entry fun seal_approve(id: vector<u8>, c: &clock::Clock) {
// Convert the identity to u64.
let mut prepared: BCS = bcs::new(id);
let mut prepared = bcs::new(id);
let t = prepared.peel_u64();
let leftovers = prepared.into_remainder_bytes();

Expand Down
2 changes: 1 addition & 1 deletion docs/ExamplePatterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Use this pattern to publish encrypted content that unlocks automatically at a sp

### Variation - Pre-signed URLs

Apply the same time-based logic to gate a specific Walrus blob behind a time-limited link that expires. Encrypt once (optionally bind the blob ID in the key ID), include an expiry parameter, and let the policy authorize decrypts only before the deadline, not after. To emulate cloud “signed URLs” more closely, combine the time check with an access rule (for example, a [allowlist](#allowlist) or [subscription](#subscription) check).
Apply the same time-based logic to gate a specific Walrus blob behind a time-limited link that expires. Encrypt once (optionally bind the blob ID in the key ID), include an expiry parameter, and let the policy authorize decrypts only before the deadline, not after. To emulate cloud “signed URLs” more closely, combine the time check with an access rule (for example, an [allowlist](#allowlist) or [subscription](#subscription) check).

This section covers access control, not link generation. You can generate and distribute the URL off-chain, or add a helper function in the same access policy package to produce/validate link parameters if you prefer to keep it on-chain. This enables limited-time downloads without re-encrypting content or managing per-user copies.

Expand Down
13 changes: 9 additions & 4 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ Use the SDK to encrypt data before storing it:
```typescript
const { encryptedObject: encryptedBytes, key: backupKey } = await client.encrypt({
threshold: 2,
packageId: fromHEX(packageId),
id: fromHEX(id),
// The contract corresponding to this `packageId` should be the original version.
packageId: originalPackageId,
// Use `objectId` as the requested identity.
// When decrypting this encryptedData, need to pass the same `objectId` as the first parameter.
id: objectId,
data,
});
```
Expand All @@ -62,13 +65,15 @@ When a user requests access, Seal checks your onchain policy. If approved, decry
// Create the Transaction for evaluating the seal_approve function.
const tx = new Transaction();
tx.moveCall({
// If the contract has been upgraded, can pass the latest packageId here.
target: `${packageId}::${moduleName}::seal_approve`,
arguments: [
tx.pure.vector("u8", fromHEX(id)),
// The first parameter is the requested identity without prefix.
tx.pure.vector("u8", fromHex(id)),
// other arguments
]
});
const txBytes = tx.build( { client: suiClient, onlyTransactionKind: true })
const txBytes = tx.build( { client: suiClient, onlyTransactionKind: true });
const decryptedBytes = await client.decrypt({
data: encryptedBytes,
sessionKey,
Expand Down
2 changes: 1 addition & 1 deletion docs/TermsOfService.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ WHEN YOU AGREE TO THESE TERMS, YOU ARE AGREEING (WITH LIMITED EXCEPTION) TO RESO
1. **Tax Records and Reporting**: You are solely responsible for all costs incurred by you in using the Services, and for determining, collecting, reporting, and paying all applicable Taxes that you may be required by law to collect and remit to any governmental or regulatory agencies. As used herein, “**Taxes**” means the taxes, duties, levies, tariffs, and other charges imposed by any federal, state, multinational or local governmental or regulatory authority. We reserve the right to report any activity occurring using the Services to relevant tax authorities as required under Applicable Law. You are solely responsible for maintaining all relevant Tax records and complying with any reporting requirements you may have as related to our Services. You are further solely responsible for independently maintaining the accuracy of any record submitted to any tax authority including any information derived from the Services.

8. Your Content
1. **User Content**: Our Services may allow you to access or enable access to stored or shared content such as text (in posts or communications with others), files, documents, graphics, images, music, software, code, audio and video. Anything (other than Feedback) that you post or otherwise make available through the Services, including Customer Data, is referred to as “User Content.” Mysten does not claim any ownership rights in any User Content and nothing in these Terms will be deemed to restrict any rights that you may have to your User Content. We do not store, retain, or control any User Content and shall have no responsibility for data loss, corruption, or inaccessibility resulting from third-party storage solutions or from any unauthorized access to or unavailability of any Key Server.
1. **User Content**: Our Services may allow you to access or enable access to store or share content such as text (in posts or communications with others), files, documents, graphics, images, music, software, code, audio and video. Anything (other than Feedback) that you post or otherwise make available through the Services, including Customer Data, is referred to as “User Content.” Mysten does not claim any ownership rights in any User Content and nothing in these Terms will be deemed to restrict any rights that you may have to your User Content. We do not store, retain, or control any User Content and shall have no responsibility for data loss, corruption, or inaccessibility resulting from third-party storage solutions or from any unauthorized access to or unavailability of any Key Server.
1. **Your Responsibility for User Content**: You are solely responsible for all your User Content. You represent and warrant that your User Content will not:
1. infringe, misappropriate or violate a third party’s intellectual property rights, or rights of publicity or privacy;
1. result in the violation of any Applicable Law or regulation;
Expand Down
107 changes: 56 additions & 51 deletions docs/UsingSeal.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Packages should define `seal_approve*` functions in their modules to control acc
See [Example patterns](./ExamplePatterns.md) for additional examples and high-level patterns.

As `seal_approve*` functions are standard Move functions, they can be tested locally using Move tests.
Building and publishing the code can be done using the [`Sui CLI`](https://docs.sui.io/references/cli), e.g.,:
Building and publishing the code can be done using the [`Sui CLI`](https://docs.sui.io/references/cli), e.g.:

```shell
$ cd examples/move
Expand All @@ -35,9 +35,8 @@ When using `seal_approve*` functions, keep the following in mind:
- `seal_approve*` functions are not evaluated atomically across all key servers. Avoid relying on frequently changing state to determine access, as different full nodes may observe different versions of the chain.
- Do not rely on invariants that depend on the relative order of transactions within a checkpoint. For example, the following code assumes a specific ordering of increment operations, but full nodes may observe different intermediate counter values due to interleaved execution.

```rust

struct Counter {
```move
public struct Counter has key {
id: UID,
count: u64,
}
Expand Down Expand Up @@ -79,12 +78,12 @@ const suiClient = new SuiClient({ url: getFullnodeUrl('testnet') });
const serverObjectIds = ["0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75", "0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8"];

const client = new SealClient({
suiClient,
serverConfigs: serverObjectIds.map((id) => ({
objectId: id,
weight: 1,
})),
verifyKeyServers: false,
suiClient,
serverConfigs: serverObjectIds.map((id) => ({
objectId: id,
weight: 1,
})),
verifyKeyServers: false,
});
```
The `serverConfigs` is a list of objects, where each object contains a key server object ID and its weight. Recall that the weight indicates how many times the key server can contribute towards reaching the decryption threshold. In this example, all key servers are given equal weight 1.
Expand Down Expand Up @@ -116,8 +115,11 @@ The `encrypt` function returns two values: the encrypted object, and the symmetr
```typescript
const { encryptedObject: encryptedBytes, key: backupKey } = await client.encrypt({
threshold: 2,
packageId: fromHEX(packageId),
id: fromHEX(id),
// The contract corresponding to this `packageId` should be the original version.
packageId: originalPackageId,
// Use `objectId` as the requested identity.
// When decrypting this encryptedData, need to pass the same `objectId` as the first parameter.
id: objectId,
data,
});
```
Expand All @@ -143,7 +145,8 @@ Once initialized, the session key can be used to retrieve multiple decryption ke
```typescript
const sessionKey = await SessionKey.create({
address: suiAddress,
packageId: fromHEX(packageId),
// The contract corresponding to this `packageId` should be the original version.
packageId: originalPackageId,
ttlMin: 10, // TTL of 10 minutes
suiClient: new SuiClient({ url: getFullnodeUrl('testnet') }),
});
Expand All @@ -168,13 +171,15 @@ The simplest way to perform decryption is to call the client’s `decrypt` funct
// Create the Transaction for evaluating the seal_approve function.
const tx = new Transaction();
tx.moveCall({
// If the contract has been upgraded, can pass the latest packageId here.
target: `${packageId}::${moduleName}::seal_approve`,
arguments: [
tx.pure.vector("u8", fromHEX(id)),
// The first parameter is the requested identity without prefix.
tx.pure.vector("u8", fromHex(id)),
// other arguments
]
});
const txBytes = tx.build( { client: suiClient, onlyTransactionKind: true })
const txBytes = tx.build( { client: suiClient, onlyTransactionKind: true });
const decryptedBytes = await client.decrypt({
data: encryptedBytes,
sessionKey,
Expand Down Expand Up @@ -249,68 +254,68 @@ const encryptedObject = EncryptedObject.parse(encryptedBytes);

// 2. Get derived keys from key servers for the encrypted object's ID.
const derivedKeys = await client.getDerivedKeys({
id: encryptedObject.id,
txBytes,
sessionKey,
threshold: encryptedObject.threshold,
id: encryptedObject.id,
txBytes,
sessionKey,
threshold: encryptedObject.threshold,
});

// 3. Get the public keys corresponding to the derived keys.
// In practice, this should should be done only during the app initialization.
const publicKeys = await client.getPublicKeys(encryptedObject.services.map(([service, _]) => service));
const correspondingPublicKeys = derivedKeys.keys().map((objectId) => {
const index = encryptedObject.services.findIndex(([s, _]) => s === objectId);
return tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::new_public_key`,
arguments: [
tx.pure.address(objectId),
tx.pure.vector("u8", publicKeys[index].toBytes())
],
});
const index = encryptedObject.services.findIndex(([s, _]) => s === objectId);
return tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::new_public_key`,
arguments: [
tx.pure.address(objectId),
tx.pure.vector("u8", publicKeys[index].toBytes())
],
});
}).toArray();

// 4. Convert the derived keys to G1 elements.
const derivedKeysAsG1Elements = Array.from(derivedKeys).map(([_, value]) =>
tx.moveCall({
target: `0x2::bls12381::g1_from_bytes`,
arguments: [ tx.pure.vector("u8", fromHex(value.toString())) ],
})
tx.moveCall({
target: `0x2::bls12381::g1_from_bytes`,
arguments: [ tx.pure.vector("u8", fromHex(value.toString())) ],
})
);

// 5. Call the Move function verify_derived_keys. This should be cached if decryption for the same ID is performed again.
const verifiedDerivedKeys = tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::verify_derived_keys`,
arguments: [
tx.makeMoveVec({ elements: derivedKeysAsG1Elements, type: '0x2::group_ops::Element<0x2::bls12381::G1>' }),
tx.pure.address(encryptedObject.packageId),
tx.pure.vector("u8", fromHex(encryptedObject.id)),
tx.makeMoveVec({ elements: correspondingPublicKeys, type: `${SEAL_PACKAGE_ID}::bf_hmac_encryption::PublicKey` }),
],
target: `${seal_package_id}::bf_hmac_encryption::verify_derived_keys`,
arguments: [
tx.makeMoveVec({ elements: derivedKeysAsG1Elements, type: '0x2::group_ops::Element<0x2::bls12381::G1>' }),
tx.pure.address(encryptedObject.packageId),
tx.pure.vector("u8", fromHex(encryptedObject.id)),
tx.makeMoveVec({ elements: correspondingPublicKeys, type: `${SEAL_PACKAGE_ID}::bf_hmac_encryption::PublicKey` }),
],
});

// 6. Construct the parsed encrypted object onchain.
const parsedEncryptedObject = tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::parse_encrypted_object`,
arguments: [tx.pure.vector("u8", encryptedBytes)],
target: `${seal_package_id}::bf_hmac_encryption::parse_encrypted_object`,
arguments: [tx.pure.vector("u8", encryptedBytes)],
});

// 7. Construct a list of public key objects.
const allPublicKeys = publicKeys.map((publicKey, i) => tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::new_public_key`,
arguments: [
tx.pure.address(encryptedObject.services[i][0]),
tx.pure.vector("u8", publicKey.toBytes())
],
target: `${seal_package_id}::bf_hmac_encryption::new_public_key`,
arguments: [
tx.pure.address(encryptedObject.services[i][0]),
tx.pure.vector("u8", publicKey.toBytes())
],
}));

// 7. Perform decryption with verified derived keys.
const decrypted = tx.moveCall({
target: `${seal_package_id}::bf_hmac_encryption::decrypt`,
arguments: [
parsedEncryptedObject,
verifiedDerivedKeys,
tx.makeMoveVec({ elements: allPublicKeys, type: `${SEAL_PACKAGE_ID}::bf_hmac_encryption::PublicKey` }),
],
target: `${seal_package_id}::bf_hmac_encryption::decrypt`,
arguments: [
parsedEncryptedObject,
verifiedDerivedKeys,
tx.makeMoveVec({ elements: allPublicKeys, type: `${SEAL_PACKAGE_ID}::bf_hmac_encryption::PublicKey` }),
],
});

// The decryption result is in an option to be consumed if successful, `none` otherwise.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Seal is a decentralized secrets management (DSM) service that relies on access c

- **Encryption and Decryption:** Seal supports encryption of sensitive data using a [secret sharing mechanism](https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing), where one can encrypt sensitive data using different public keys, and store those on Walrus or another storage. When needed, one can decrypt the encrypted parts using private keys generated by off-chain key servers (see below). To maximize privacy, Seal uses client-side encryption where the application / user is responsible to encrypt and decrypt the data.
- **Access control on Sui:** Seal leverages [Sui](https://docs.sui.io/concepts/components) for controlling access to the decryption keys, and thus allows access control for the sensitive data itself. A user can achieve this by implementing an application specific Move package which must follow some Seal conventions. The application specific logic in the Move package controls when to allow or disallow access to a key, and this flexibility provides a way to realize various authorization scenarios. Refer to [Seal design](Design.md) and [Using Seal](UsingSeal.md) for details.
- **Decentralized gatekeeping using off-chain servers:** Seal key server is a simple and lightweight service that is queried by an application or user to validate the access control on Sui and to generate a identity-based private key that can be further used to decrypt the encrypted data. Different parties can operate their own Seal key servers, thus allowing users to realize `t-out-of-n` threshold encryption. In such a scenario, `n` is the number of total Seal key servers, and `t` is the threshold number of key servers that a user chooses for their use case. Seal does not require any specific key server to be used, and applications or users can choose their preferred key servers based on their trust assumptions or regulatory requirements. Refer to [Seal design](Design.md) and [Using Seal](UsingSeal.md) for details.
- **Decentralized gatekeeping using off-chain servers:** Seal key server is a simple and lightweight service that is queried by an application or user to validate the access control on Sui and to generate an identity-based private key that can be further used to decrypt the encrypted data. Different parties can operate their own Seal key servers, thus allowing users to realize `t-out-of-n` threshold encryption. In such a scenario, `n` is the number of total Seal key servers, and `t` is the threshold number of key servers that a user chooses for their use case. Seal does not require any specific key server to be used, and applications or users can choose their preferred key servers based on their trust assumptions or regulatory requirements. Refer to [Seal design](Design.md) and [Using Seal](UsingSeal.md) for details.
- **Seamless access:** Applications can interact with Seal key servers using software development kits (SDKs). The [typescript SDK](https://www.npmjs.com/package/@mysten/seal) is available through npm.

!!! note
Expand Down