Access Control Contract
OpenTusk shared vaults use an on-chain Sui Move contract to enforce cryptographic access control. The contract manages Whitelists — shared objects on the Sui blockchain that list which Sui addresses can decrypt files in a given vault.
Package ID
Section titled “Package ID”The opentusk::shared_vault contract is deployed on Sui mainnet:
0xbb3f0933b07bb20a96129e05304905ba049db4a8f1c6005da1f5871c28367717View it on the Sui Explorer.
How it works
Section titled “How it works”When you create a shared vault, OpenTusk deploys two on-chain objects:
| Object | Type | Purpose |
|---|---|---|
| Whitelist | Shared object | Lists authorized Sui addresses. SEAL key servers read this to verify decryption access. |
| Cap | Owned object | Admin capability for adding/removing members. Held by the vault owner’s Sui wallet. |
Create shared vault └─ Vault owner calls create_whitelist_entry() ├─ Whitelist shared object created (stores member addresses) └─ Cap transferred to vault owner (admin control)
Add member └─ Vault owner calls add(whitelist, cap, member_address) └─ Member's Sui address added to on-chain Whitelist
Decrypt file └─ SEAL key server calls seal_approve(encryption_id, whitelist) ├─ Verifies encryption ID has whitelist ID as prefix └─ Verifies caller's address is in the Whitelist └─ If both pass → decryption key fragment issuedContract source
Section titled “Contract source”The full Move source code for the opentusk::shared_vault module:
// Copyright (c) OpenTusk/// Shared vault access control for the SEAL protocol.////// Each shared vault gets a Whitelist object (from the SEAL whitelist pattern)./// The vault owner holds a Cap that controls membership./// SEAL key servers call seal_approve to verify decryption access.////// Key format: [package_id][whitelist_id][nonce]/// - Any data encrypted to this key-id can be decrypted by whitelisted members./// - The nonce allows per-file key derivation within the same whitelist.
module opentusk::shared_vault;
use sui::table;
const ENoAccess: u64 = 1;const EInvalidCap: u64 = 2;const EDuplicate: u64 = 3;const ENotInWhitelist: u64 = 4;const EWrongVersion: u64 = 5;
const VERSION: u64 = 1;
/// Per-vault whitelist. Shared object on-chain./// Members listed here can decrypt files encrypted to this vault's key-id.public struct Whitelist has key { id: UID, version: u64, addresses: table::Table<address, bool>,}
/// Capability granting admin control over a Whitelist./// Held by the vault owner (transferred to them at creation).public struct Cap has key, store { id: UID, wl_id: ID,}
/// Create a new whitelist and return the admin cap./// The caller (platform wallet or vault owner) receives the Cap.public fun create_whitelist(ctx: &mut TxContext): (Cap, Whitelist) { let wl = Whitelist { id: object::new(ctx), version: VERSION, addresses: table::new(ctx), }; let cap = Cap { id: object::new(ctx), wl_id: object::id(&wl), }; (cap, wl)}
/// Share the whitelist so SEAL key servers can read it.public fun share_whitelist(wl: Whitelist) { transfer::share_object(wl);}
/// Entry function: create whitelist, share it, transfer cap to sender.entry fun create_whitelist_entry(ctx: &mut TxContext) { let (cap, wl) = create_whitelist(ctx); share_whitelist(wl); transfer::public_transfer(cap, ctx.sender());}
/// Add a member to the whitelist. Requires the matching Cap.public fun add(wl: &mut Whitelist, cap: &Cap, account: address) { assert!(cap.wl_id == object::id(wl), EInvalidCap); assert!(!wl.addresses.contains(account), EDuplicate); wl.addresses.add(account, true);}
/// Remove a member from the whitelist. Requires the matching Cap.public fun remove(wl: &mut Whitelist, cap: &Cap, account: address) { assert!(cap.wl_id == object::id(wl), EInvalidCap); assert!(wl.addresses.contains(account), ENotInWhitelist); wl.addresses.remove(account);}
/// Check if a caller is authorized to decrypt data encrypted to/// this whitelist's key-id./// Verifies: correct version, id prefix matches whitelist, caller/// is a member.fun check_policy( caller: address, id: vector<u8>, wl: &Whitelist,): bool { assert!(wl.version == VERSION, EWrongVersion);
// Verify the encryption id has the whitelist id as prefix let prefix = wl.id.to_bytes(); let mut i = 0; if (prefix.length() > id.length()) { return false }; while (i < prefix.length()) { if (prefix[i] != id[i]) { return false }; i = i + 1; };
// Check membership wl.addresses.contains(caller)}
/// SEAL entry point. Key servers call this to verify decryption access.entry fun seal_approve( id: vector<u8>, wl: &Whitelist, ctx: &TxContext,) { assert!(check_policy(ctx.sender(), id, wl), ENoAccess);}Entry points
Section titled “Entry points”| Function | Access | Description |
|---|---|---|
create_whitelist_entry | Entry | Creates a Whitelist + Cap, shares the Whitelist, transfers Cap to caller |
add | Public | Adds a Sui address to a Whitelist (requires matching Cap) |
remove | Public | Removes a Sui address from a Whitelist (requires matching Cap) |
seal_approve | Entry | Called by SEAL key servers to verify a caller can decrypt a given encryption ID |
Encryption key format
Section titled “Encryption key format”SEAL encryption IDs follow the format:
[whitelist_object_id][nonce]- whitelist_object_id — the 32-byte Sui object ID of the vault’s Whitelist
- nonce — a per-file nonce (typically derived from the file ID) enabling unique encryption keys per file within the same vault
The seal_approve function verifies that the encryption ID starts with the Whitelist’s object ID (prefix check) and that the caller is a member of that Whitelist.
SEAL key server
Section titled “SEAL key server”OpenTusk runs a self-hosted SEAL key server that validates decryption requests against the on-chain Whitelist:
| Parameter | Value |
|---|---|
| Key server object | 0x6ab907c5cdb1e3abe1f3c2c5ac4c853bf4a5932ae92886c40a22605540128803 |
| Threshold | 1-of-1 |
| URL | https://seal.opentusk.ai |
The key server calls seal_approve on-chain to verify membership before issuing decryption key fragments.
Error codes
Section titled “Error codes”| Code | Constant | Meaning |
|---|---|---|
| 1 | ENoAccess | Caller is not authorized (not in Whitelist or invalid ID prefix) |
| 2 | EInvalidCap | Cap does not match the Whitelist |
| 3 | EDuplicate | Address is already in the Whitelist |
| 4 | ENotInWhitelist | Address is not in the Whitelist (on removal) |
| 5 | EWrongVersion | Whitelist version mismatch |