Security & Privacy
This section describes the platform’s security posture and privacy guarantees: what we protect, how we protect it, and how we prove it in the absence of a traditional backend. Controls are designed to be verifiable by clients and anchored by on‑chain state, while sensitive materials stay encrypted off‑chain.
Security model at a glance
Core assets
- Economic state and attribution on Sui (entitlements, bounties, reputation, receipts)
- Published content and application assets on Walrus, delivered via CDN
- Threshold-encrypted submissions using MystenLabs Seal with blockchain-based access control
- Distributed key management through Seal's t-of-n key server network
- Confidential oracle computation via Nautilus framework with AWS Nitro Enclaves
- Hardware-attested results through Platform Configuration Register (PCR) verification
- Oracle receipts anchoring Nitro attestation documents and computation proofs
Trust boundaries
- Clients trust only verifiable data: on‑chain objects and events, content hashes, enclave attestations or zk proofs, and locally computed checks.
- Seal key servers: Threshold trust model where no single server can decrypt; blockchain policies provide additional verification layer.
- Identity providers (zkLogin) and sponsors: Facilitate UX but cannot grant access; all authorization flows through on-chain verification.
- Decentralization assumption: Multiple independent key servers, Walrus nodes, and Nautilus enclave instances prevent single points of failure.
- Confidential computation: Nautilus enclaves provide hardware-attested privacy for sensitive oracle computations and AI agent execution.
- Reproducible builds: Enclave PCR measurements ensure consistent, verifiable computation environments across deployments.
Threats & mitigations (selected)
- Tampered content: Clients verify Walrus blob hashes before render; mismatches trigger fallback and user prompts.
- Unauthorized access: Clients enforce entitlement checks against on‑chain state before any fetch; no backend grants access.
- Submission exfiltration: Seal threshold encryption protects source packages; only encrypted hashes and policy IDs are on‑chain. Multiple key servers must approve decryption.
- Key server compromise: t-of-n threshold prevents single point of failure; blockchain policies provide additional access control layer.
- Seal policy manipulation: Access policies are immutable objects on Sui; only authorized rotation capabilities can modify access lists.
- Source identification: Submission metadata is encrypted; even compromised key servers cannot reveal source identity without blockchain authorization.
- Phishing & token hijack (zkLogin): Device‑scoped salts and short‑lived session keys limit blast radius; logout clears key material; re‑auth required when salts/epochs change.
- Sponsored‑tx abuse: Rate limits, simple proof‑of‑person signals, and on‑chain deposits for high‑risk actions reduce spam; sponsorship policies cap exposure.
- Oracle forgery: Nautilus enclave measurements (PCRs) and AWS certificate chains verified on‑chain before state transitions; attestation documents provide cryptographic proof of execution environment integrity.
- Enclave compromise: Multiple independent Nautilus instances can provide redundant computation; threshold requirements prevent single-enclave attacks.
- Replay attacks: Attestation documents include timestamps and nonces; enclave configurations have expiration dates requiring re-registration.
- Dead man's switch abuse: Emergency timeouts require long delays (weeks/months) and emit public events for transparency.
Authentication and authorization
Authorization is purely data‑driven: if a reader owns a valid entitlement for a content object, access is granted. There is no session token that can be stolen to bypass checks.
- Entitlements: owned, time‑bounded objects; readable by anyone, effective only for the owner.
- Role gating: higher‑risk actions (e.g., posting bounties) can require deposits or zkLogin‑backed KYC where jurisdictionally necessary.
- Moderation: actions are recorded as events and linked to policies for auditability; appeals are handled off‑chain with evidence anchored on‑chain.
End‑to‑end encryption for submissions
Submissions from sources to journalists use MystenLabs Seal for threshold encryption with blockchain-based access control. Seal provides decentralized secrets management where sensitive data is encrypted client-side and access is controlled via Sui blockchain policies.
Seal Threshold Encryption Architecture
module pml::seal_integration {
/// Seal encryption policy for source submissions
struct SealPolicy has store, copy, drop {
key_servers: vector<vector<u8>>, // URLs of key servers
threshold: u8, // t-of-n threshold (e.g., 2 of 3)
access_policy_id: ID, // On-chain access policy
time_lock: Option<u64>, // Optional time-based access
dead_mans_switch: bool // Emergency access after timeout
}
/// On-chain access policy for decryption
struct AccessPolicy has key {
id: UID,
authorized_addresses: vector<address>,
conditions: AccessConditions,
emergency_timeout: u64,
revoked: bool
}
struct AccessConditions has store {
require_journalist_cap: bool,
min_reputation_score: u64,
require_2fa: bool,
bounty_id: Option<ID>
}
/// Create Seal policy for whistleblower submission
public entry fun create_submission_policy(
authorized_journalists: vector<address>,
key_servers: vector<vector<u8>>,
threshold: u8,
bounty_id: ID,
emergency_timeout_days: u64,
clock: &Clock,
ctx: &mut TxContext
) {
let conditions = AccessConditions {
require_journalist_cap: true,
min_reputation_score: 1000,
require_2fa: true,
bounty_id: option::some(bounty_id)
};
let policy = AccessPolicy {
id: object::new(ctx),
authorized_addresses: authorized_journalists,
conditions,
emergency_timeout: clock::timestamp_ms(clock) +
(emergency_timeout_days * 86400 * 1000),
revoked: false
};
transfer::share_object(policy);
}
/// Verify access for Seal decryption
public fun verify_decryption_access(
policy: &AccessPolicy,
requester: address,
journalist_cap: &JournalistCap,
reputation: &Reputation,
clock: &Clock
): bool {
// Check if policy is revoked
if (policy.revoked) return false;
// Check emergency timeout
if (clock::timestamp_ms(clock) > policy.emergency_timeout) {
return true; // Dead man's switch activated
}
// Check if requester is authorized
if (!vector::contains(&policy.authorized_addresses, &requester)) {
return false;
}
// Verify conditions
if (policy.conditions.require_journalist_cap) {
// Journalist cap validation would happen here
};
if (reputation.score < policy.conditions.min_reputation_score) {
return false;
}
true
}
}
Seal Integration Scheme
- Threshold Encryption: t-of-n key servers (e.g., 2-of-3) must approve decryption
- Blockchain Access Control: Sui Move contracts define who can decrypt
- Client-Side Encryption: Sources encrypt locally before upload to Walrus
- Key Server Network: Distributed servers validate access policies
- Emergency Access: Dead man's switch for time-locked revelations
Flow with Seal Integration
Oracle receipt verification with Nautilus
For decisions that require private compute or web evidence, the system implements Nautilus framework for secure, verifiable off-chain computation using AWS Nitro Enclaves. Nautilus enables trusted oracles and AI agents that can access external Web2 systems while providing cryptographic proof of execution integrity.
Nautilus Oracle Architecture
module pml::nautilus_oracle {
use sui::clock::{Self, Clock};
use sui::event;
/// Nautilus enclave configuration
struct EnclaveConfig has key, store {
id: UID,
pcr_0: vector<u8>, // Platform Configuration Register 0
pcr_1: vector<u8>, // Platform Configuration Register 1
pcr_2: vector<u8>, // Platform Configuration Register 2
public_key: vector<u8>, // Enclave's ephemeral public key
allowed_domains: vector<String>, // Whitelisted external domains
registered_at: u64,
valid_until: u64
}
/// Attestation document from Nitro Enclave
struct AttestationDocument has copy, drop {
module_id: vector<u8>, // Hash of enclave image
digest: vector<u8>, // SHA-384 digest
timestamp: u64, // Unix timestamp from enclave
pcrs: vector<vector<u8>>, // All PCR values
certificate: vector<u8>, // AWS certificate chain
cabundle: vector<vector<u8>>, // Certificate bundle
public_key: vector<u8>, // Ephemeral key for this session
user_data: Option<vector<u8>>, // Optional enclave-specific data
nonce: Option<vector<u8>> // Anti-replay nonce
}
/// Oracle result with attestation proof
struct OracleResult has copy, drop {
enclave_id: ID,
computation_hash: vector<u8>, // Hash of input + output
result_data: vector<u8>, // Actual computation result
attestation: AttestationDocument,
signature: vector<u8>, // Signed by enclave's ephemeral key
external_proofs: vector<vector<u8>> // Optional zkTLS or web proofs
}
/// Register a new Nautilus enclave instance
public entry fun register_enclave(
pcr_0: vector<u8>,
pcr_1: vector<u8>,
pcr_2: vector<u8>,
public_key: vector<u8>,
allowed_domains: vector<String>,
validity_days: u64,
clock: &Clock,
ctx: &mut TxContext
) {
let config = EnclaveConfig {
id: object::new(ctx),
pcr_0,
pcr_1,
pcr_2,
public_key,
allowed_domains,
registered_at: clock::timestamp_ms(clock),
valid_until: clock::timestamp_ms(clock) + (validity_days * 86400 * 1000)
};
transfer::share_object(config);
event::emit(EnclaveRegistered {
enclave_id: object::id(&config),
pcr_hash: hash_pcrs(&[pcr_0, pcr_1, pcr_2]),
public_key
});
}
/// Verify oracle result with Nitro attestation
public fun verify_oracle_result(
config: &EnclaveConfig,
result: &OracleResult,
clock: &Clock
): bool {
// Check enclave is still valid
if (clock::timestamp_ms(clock) > config.valid_until) {
return false
};
// Verify PCR measurements match registered values
let attestation = &result.attestation;
if (*vector::borrow(&attestation.pcrs, 0) != config.pcr_0 ||
*vector::borrow(&attestation.pcrs, 1) != config.pcr_1 ||
*vector::borrow(&attestation.pcrs, 2) != config.pcr_2) {
return false
};
// Verify ephemeral public key matches
if (attestation.public_key != config.public_key) {
return false
};
// Verify AWS certificate chain (simplified)
verify_aws_certificate_chain(&attestation.certificate, &attestation.cabundle);
// Verify signature over computation hash
verify_signature(
&result.signature,
&result.computation_hash,
&attestation.public_key
)
}
/// Submit oracle result for market resolution
public entry fun submit_oracle_resolution(
config: &EnclaveConfig,
market: &mut Market,
oracle_result: OracleResult,
clock: &Clock,
ctx: &mut TxContext
) {
// Verify the oracle result
assert!(
verify_oracle_result(config, &oracle_result, clock),
E_INVALID_ATTESTATION
);
// Extract resolution from oracle result
let resolution = parse_resolution(&oracle_result.result_data);
// Create resolution receipt
let receipt = ResolutionReceipt {
market_id: object::id(market),
outcome: resolution,
oracle_attestation: encode_attestation(&oracle_result.attestation),
proof_hash: hash(&oracle_result.computation_hash),
timestamp: clock::timestamp_ms(clock)
};
// Update market state
market.resolution = option::some(receipt);
event::emit(MarketResolvedByOracle {
market_id: object::id(market),
enclave_id: object::id(config),
outcome: resolution,
attestation_hash: hash(&oracle_result.attestation.digest)
});
}
}
Nautilus Integration Flow
Key handling and storage
Multi-Layer Key Architecture
module pml::key_management {
/// Key rotation capability for Seal policies
struct KeyRotationCap has key {
id: UID,
policy_id: ID,
rotation_count: u64
}
/// Emergency key escrow for dead man's switch
struct EmergencyEscrow has key {
id: UID,
sealed_key: vector<u8>, // Seal-encrypted emergency key
timeout_epoch: u64,
activated: bool
}
/// Rotate Seal access policy keys
public entry fun rotate_seal_policy(
cap: &KeyRotationCap,
policy: &mut AccessPolicy,
new_authorized: vector<address>,
ctx: &mut TxContext
) {
assert!(policy.id == cap.policy_id, E_WRONG_POLICY);
policy.authorized_addresses = new_authorized;
event::emit(PolicyRotated {
policy_id: object::id(policy),
rotation_count: cap.rotation_count + 1
});
}
}
- Wallet Keys: Users hold keys in standard Sui wallets; sensitive operations prompt explicit signatures.
- zkLogin Materials: Salts and ephemeral keys stored client‑side per Sui guidance (see zkLogin in Identity & Auth). Clearing local storage requires re‑establishing salts.
- Seal Key Servers: Distributed key servers validate blockchain policies; no single server can decrypt alone.
- Emergency Keys: Dead man's switch mechanisms for time-locked access to critical information.
- No Server Secrets: No server‑side session store in hot path; sponsorship services hold only minimum gas keys with strict policy controls.
Privacy principles
Privacy-by-Design Architecture
// Client-side Seal integration example
interface SealSubmission {
encrypt(documents: File[], policy: AccessPolicy): Promise<EncryptedBlob>;
createPolicy(journalists: Address[], conditions: AccessConditions): AccessPolicy;
submitAnonymously(bountyId: ID, encryptedBlob: EncryptedBlob): Promise<SubmissionReceipt>;
}
// Zero-knowledge submission flow
async function submitWhistleblowerDocs(documents: File[], bountyId: ID) {
// Create access policy with threshold requirements
const policy = await seal.createPolicy({
authorizedJournalists: [journalistAddress],
keyServers: ['ks1.protocol.io', 'ks2.protocol.io', 'ks3.protocol.io'],
threshold: 2, // 2 of 3 servers must approve
conditions: {
requireJournalistCap: true,
minReputationScore: 1000,
deadMansSwitch: 30 * 24 * 60 * 60 * 1000 // 30 days
}
});
// Encrypt documents locally
const encrypted = await seal.encrypt(documents, policy);
// Submit only hash and policy reference
return await submitAnonymously(bountyId, encrypted);
}
Nautilus Confidential Computing
// Example Nautilus enclave computation (Rust)
use nautilus_enclave::prelude::*;
#[derive(Serialize, Deserialize)]
struct StoryVerificationRequest {
story_claim: String,
sources_to_check: Vec<String>,
verification_threshold: u8,
}
#[derive(Serialize, Deserialize)]
struct VerificationResult {
verified: bool,
confidence_score: f64,
sources_confirming: u8,
evidence_hashes: Vec<String>,
computation_proof: Vec<u8>,
}
#[nautilus_handler]
async fn verify_story_outcome(
request: StoryVerificationRequest
) -> Result<VerificationResult, EnclaveError> {
let mut confirming_sources = 0;
let mut evidence_hashes = Vec::new();
// Fetch from whitelisted news sources
for source_url in &request.sources_to_check {
if let Ok(content) = fetch_external_url(source_url).await {
// Analyze content for claim verification
if verify_claim_in_content(&request.story_claim, &content) {
confirming_sources += 1;
evidence_hashes.push(hash_content(&content));
}
}
}
let verified = confirming_sources >= request.verification_threshold;
let confidence = (confirming_sources as f64) / (request.sources_to_check.len() as f64);
// Generate computation proof for on-chain verification
let computation_proof = generate_computation_proof(&request, confirming_sources);
Ok(VerificationResult {
verified,
confidence_score: confidence,
sources_confirming: confirming_sources,
evidence_hashes,
computation_proof,
})
}
Core Privacy Principles:
- Collect the minimum: No doxable PII written on‑chain; off‑chain logs avoid sensitive contents.
- Threshold encryption: Seal's t-of-n architecture prevents single points of decryption.
- Blockchain access control: Decryption policies enforced by smart contracts, not trusted parties.
- Metadata protection: File names, sizes, timestamps encrypted within Seal packages.
- Separate roles: Moderation and editorial actions traceable without exposing reporter/source identities.
- Emergency disclosure: Dead man's switch enables time-locked revelations for public interest.
Abuse prevention and rate limiting
Multi-Layer Abuse Prevention
module pml::abuse_prevention {
/// Rate limiting for Seal operations
struct SealRateLimit has key {
id: UID,
user: address,
daily_encryptions: u64,
daily_limit: u64,
last_reset: u64
}
/// Prevent Seal policy abuse
public entry fun create_rate_limited_policy(
requester_reputation: &Reputation,
rate_limit: &mut SealRateLimit,
clock: &Clock,
ctx: &mut TxContext
) {
// Reset daily limits
let today = clock::timestamp_ms(clock) / 86400000;
if (rate_limit.last_reset < today) {
rate_limit.daily_encryptions = 0;
rate_limit.last_reset = today;
}
// Check reputation-based limits
let user_limit = if (requester_reputation.score >= 5000) {
50 // High reputation users
} else if (requester_reputation.score >= 1000) {
10 // Medium reputation users
} else {
3 // Low reputation users
};
assert!(
rate_limit.daily_encryptions < user_limit,
E_DAILY_LIMIT_EXCEEDED
);
rate_limit.daily_encryptions = rate_limit.daily_encryptions + 1;
}
}
Prevention Mechanisms:
- Economic friction: Deposits and slashing for spam‑prone actions; per‑account quotas on sponsored transactions.
- Reputation-based limits: Higher reputation enables more Seal operations; new users have strict quotas.
- Key server coordination: Seal servers can blacklist abusive policies across the network.
- Policy validation: Smart contracts enforce minimum timeouts and reasonable access conditions.
- Proof of person‑ness: zkLogin signals or equivalent heuristics gate sponsorship eligibility.
- Client checks: Cooldowns and exponential backoff for repeated failures reduce abuse amplitude.
Vulnerability management and supply chain
- Dependency hygiene: automated alerts and upgrades; cryptographic verification of extension/wallet sources.
- Content security: strict CSP for the single‑page apps; subresource integrity for CDN‑served assets.
- Reporting: a security.txt and coordinated disclosure policy are maintained.
Incident response
Emergency Response Procedures
module pml::incident_response {
/// Emergency controls for security incidents
struct EmergencyControls has key {
id: UID,
admin_cap: AdminCap,
seal_policies: Table<ID, bool>, // Policy ID -> revoked status
emergency_active: bool
}
/// Revoke compromised Seal policies
public entry fun emergency_revoke_policy(
controls: &mut EmergencyControls,
policy_id: ID,
_admin_cap: &AdminCap,
ctx: &mut TxContext
) {
table::add(&mut controls.seal_policies, policy_id, true);
event::emit(PolicyRevoked {
policy_id,
revoked_by: tx_context::sender(ctx),
timestamp: tx_context::epoch(ctx)
});
}
/// Circuit breaker for platform-wide issues
public entry fun activate_emergency_mode(
controls: &mut EmergencyControls,
_admin_cap: &AdminCap
) {
controls.emergency_active = true;
event::emit(EmergencyActivated {
activated_at: tx_context::epoch(ctx)
});
}
}
Response Capabilities:
- Seal policy revocation: Instantly revoke compromised access policies across key server network.
- Nautilus enclave quarantine: Revoke enclave configurations and blacklist compromised PCR measurements.
- Key server coordination: Emergency blacklisting of malicious policies or addresses.
- Oracle result invalidation: Mark suspicious oracle results as disputed pending manual review.
- Sponsorship key rotation: Revoke sponsorship keys on abuse detection; rotate DNS to safe banner if necessary.
- Expedited takedowns: Content references withdrawn from catalogs while preserving on‑chain integrity and evidence trails.
- Emergency mode: Circuit breaker for platform-wide security incidents.
- Transparent logging: All emergency actions logged on-chain for audit and accountability.