Hold, serve, incinerate
Custody holds what your app may one day have to forget: an ID document, a signed agreement, a private file. Each object has its own key; deleting it destroys the key, recorded on Sui our statement (Custody and deletion).
Source the Quickstart’s first block. No bucket needed.
# Custody holds a secret for you, encrypted, and answers with two hashes:# certificate_fingerprint is the salted fingerprint the deletion certificate# will name (only someone holding the salt can check it), stored_hash is the# hash of the bytes as stored. Keep both - they are used by different verifiers.
TEXT=${TEXT:-"hello, custody"}
curl -sS -m 60 -X POST -H "$AUTH" -H 'Content-Type: application/json' \ -d "$(python3 -c 'import json,sys; print(json.dumps({"text": sys.argv[1]}))' "$TEXT")" \ -o store.json -w 'store %{http_code}\n' "$HOST/v1/api/custody/store"
# jq does this just as well: CID=$(jq -r .id store.json)CID=$(python3 -c 'import json; print(json.load(open("store.json"))["id"])')CERT_FINGERPRINT=$(python3 -c 'import json; print(json.load(open("store.json"))["certificate_fingerprint"])')STORED_HASH=$(python3 -c 'import json; print(json.load(open("store.json"))["stored_hash"])')
echo "id $CID"echo "certificate_fingerprint $CERT_FINGERPRINT"echo "stored_hash $STORED_HASH"Keep the id and:
certificate_fingerprint: the salted fingerprint the certificate will name (certificate_fingerprint_kind: salted), checkable only with the salt.stored_hash: what the verify route answers for.
Check it and serve it
Section titled “Check it and serve it”# alive is true while the key still exists.
curl -sS -m 60 -H "$AUTH" "$HOST/v1/api/custody/status/$CID"echoalive is true while the key exists. Unknown and other tenants’ ids both answer 404.
# While the key lives, the plaintext comes back.
curl -sS -m 60 -H "$AUTH" "$HOST/v1/api/custody/plaintext/$CID"echoThe tenant gets plaintext while the key lives.
Incinerate
Section titled “Incinerate”Irreversible.
# Incinerate: deletion by key destruction, with a certificate as the receipt.# minted means the certificate landed on chain and the digest below resolves.
curl -sS -m 120 -X POST -H "$AUTH" \ -o incinerate.json -w 'incinerate %{http_code}\n' \ "$HOST/v1/api/custody/incinerate/$CID"
python3 - <<'PY'import jsond = json.load(open("incinerate.json"))for k in ("certificate_status", "certificate_object_id", "certificate_tx_digest"): print(k, "=", d.get(k))PYminted: the certificate is on Sui atcertificate_object_id.not_minted: no certificate landed. The key is destroyed either way.
Confirm
Section titled “Confirm”# The plaintext is gone for good: 410, and the status says so.
curl -sS -m 60 -H "$AUTH" \ -o after.json -w 'plaintext %{http_code}\n' "$HOST/v1/api/custody/plaintext/$CID"
curl -sS -m 60 -H "$AUTH" -o status.json "$HOST/v1/api/custody/status/$CID"
python3 - <<'PY'import jsond = json.load(open("status.json"))print("alive =", d.get("alive"))print("deletion_mode =", d.get("deletion_mode"))PYPlaintext answers 410 and status alive: false, for good. The ciphertext remains, unreadable.
What the certificate says
Section titled “What the certificate says”It names a salted fingerprint, not a tenant or user: whoever holds the file and salt can match it, and nobody learns who stored what.
Permafrost runs on Sui testnet and Walrus testnet. Everything here describes a shipped testnet instance, not a production service.