program card
// ======================================================
// ZK-Native Card โ TSP-2 (PLUMB)
// Ops: Pay (0), Lock (1), Update (2), Mint (3), Burn (4)
//
// PLUMB = Pay, Lock, Update, Mint, Burn
//
// State: Merkle tree of asset leaves + config commitment
//
// Leaf (10 fields):
// hash(asset_id, owner_id, nonce, auth_hash, lock_until,
// collection_id, metadata_hash, royalty_bps, creator_id, flags)
//
// Config (10 fields = 5 authorities + 5 hooks):
// hash(admin_auth, pay_auth, lock_auth, mint_auth, burn_auth,
// pay_hook, lock_hook, update_hook, mint_hook, burn_hook)
//
// Flags bitfield (5 bits):
// bit 0 = TRANSFERABLE (Pay allowed)
// bit 1 = BURNABLE (Burn allowed)
// bit 2 = UPDATABLE (Metadata update allowed)
// bit 3 = LOCKABLE (Lock allowed)
// bit 4 = MINTABLE (Collection accepts new mints)
// Flags are immutable after mint.
//
// Authorization:
// Account ops (Pay, Lock, Burn): owner auth always required.
// If config auth โ 0, dual auth (owner + config) required.
// Config ops (Mint): config auth required. 0 = disabled.
// Config ops (Update): admin auth required. 0 = renounced.
//
// Leaves are Merkle-authenticated against the state root.
// Hook program IDs are output for external proof composition.
// ======================================================
use std.crypto.merkle
use os.neptune.standards.plumb
// --- Flag constants ---
// TRANSFERABLE = 1, BURNABLE = 2, UPDATABLE = 4, LOCKABLE = 8, MINTABLE = 16
// --- Asset leaf hashing (10 fields) ---
fn hash_leaf(
asset_id: Field,
owner_id: Field,
nonce: Field,
auth_hash: Field,
lock_until: Field,
collection_id: Field,
metadata_hash: Field,
royalty_bps: Field,
creator_id: Field,
flags: Field
) -> Digest {
hash(
asset_id,
owner_id,
nonce,
auth_hash,
lock_until,
collection_id,
metadata_hash,
royalty_bps,
creator_id,
flags
)
}
// --- Flag checks ---
fn assert_transferable(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
let f1: Field = sub(flags, 1) * sub(flags, 3) * sub(flags, 5) * sub(flags, 7)
let f9: Field = sub(flags, 9) * sub(flags, 11) * sub(flags, 13) * sub(flags, 15)
let f17: Field = sub(flags, 17) * sub(flags, 19) * sub(flags, 21) * sub(flags, 23)
let f25: Field = sub(flags, 25) * sub(flags, 27) * sub(flags, 29) * sub(flags, 31)
let valid: Field = f1 * f9 * f17 * f25
assert_eq(valid, 0)
}
fn assert_burnable(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
let f2: Field = sub(flags, 2) * sub(flags, 3) * sub(flags, 6) * sub(flags, 7)
let f10: Field = sub(flags, 10) * sub(flags, 11) * sub(flags, 14) * sub(flags, 15)
let f18: Field = sub(flags, 18) * sub(flags, 19) * sub(flags, 22) * sub(flags, 23)
let f26: Field = sub(flags, 26) * sub(flags, 27) * sub(flags, 30) * sub(flags, 31)
let valid: Field = f2 * f10 * f18 * f26
assert_eq(valid, 0)
}
fn assert_updatable(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
let f4: Field = sub(flags, 4) * sub(flags, 5) * sub(flags, 6) * sub(flags, 7)
let f12: Field = sub(flags, 12) * sub(flags, 13) * sub(flags, 14) * sub(flags, 15)
let f20: Field = sub(flags, 20) * sub(flags, 21) * sub(flags, 22) * sub(flags, 23)
let f28: Field = sub(flags, 28) * sub(flags, 29) * sub(flags, 30) * sub(flags, 31)
let valid: Field = f4 * f12 * f20 * f28
assert_eq(valid, 0)
}
fn assert_lockable(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
let f8: Field = sub(flags, 8) * sub(flags, 9) * sub(flags, 10) * sub(flags, 11)
let f12: Field = sub(flags, 12) * sub(flags, 13) * sub(flags, 14) * sub(flags, 15)
let f24: Field = sub(flags, 24) * sub(flags, 25) * sub(flags, 26) * sub(flags, 27)
let f28: Field = sub(flags, 28) * sub(flags, 29) * sub(flags, 30) * sub(flags, 31)
let valid: Field = f8 * f12 * f24 * f28
assert_eq(valid, 0)
}
fn assert_mintable(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
let low: Field = sub(flags, 16)
plumb.assert_non_negative(low)
}
fn assert_valid_flags(flags: Field) {
let _: U32 = as_u32(flags)
let headroom: Field = sub(31, flags)
let _: U32 = as_u32(headroom)
}
// --- Events ---
event Pay {
asset_id: Field,
from_owner: Field,
to_owner: Field,
royalty_bps: Field,
}
event Lock {
asset_id: Field,
lock_until: Field,
}
event MetadataUpdate {
asset_id: Field,
old_metadata: Field,
new_metadata: Field,
}
event Mint {
asset_id: Field,
creator_id: Field,
collection_id: Field,
metadata_hash: Field,
}
event Burn {
asset_id: Field,
owner_id: Field,
}
event Nullifier {
asset_id: Field,
nonce: Field,
}
event SupplyChange {
old_count: Field,
new_count: Field,
}
// ============================================================
// Op 0: PAY โ transfer asset ownership
// ============================================================
fn pay() {
let old_root: Digest = pub_read5()
let new_root: Digest = pub_read5()
let asset_count: Field = pub_read()
let asset_id: Field = pub_read()
let current_time: Field = pub_read()
let config: Digest = pub_read5()
// --- Verify config ---
let cfg_admin: Field = divine()
let cfg_pay: Field = divine()
let cfg_lock: Field = divine()
let cfg_mint: Field = divine()
let cfg_burn: Field = divine()
let cfg_pay_hook: Field = divine()
let cfg_lock_hook: Field = divine()
let cfg_update_hook: Field = divine()
let cfg_mint_hook: Field = divine()
let cfg_burn_hook: Field = divine()
plumb.verify_config(
cfg_admin,
cfg_pay,
cfg_lock,
cfg_mint,
cfg_burn,
cfg_pay_hook,
cfg_lock_hook,
cfg_update_hook,
cfg_mint_hook,
cfg_burn_hook,
config
)
// --- Current asset leaf ---
let leaf_asset_id: Field = divine()
let leaf_owner_id: Field = divine()
let leaf_nonce: Field = divine()
let leaf_auth_hash: Field = divine()
let leaf_lock_until: Field = divine()
let leaf_collection_id: Field = divine()
let leaf_metadata_hash: Field = divine()
let leaf_royalty_bps: Field = divine()
let leaf_creator_id: Field = divine()
let leaf_flags: Field = divine()
// Authenticate leaf against old state root
let old_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
leaf_nonce,
leaf_auth_hash,
leaf_lock_until,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
let leaf_idx: U32 = as_u32(divine())
merkle.verify(old_leaf, old_root, leaf_idx, plumb.tree_depth())
// Asset ID must match public input
assert_eq(leaf_asset_id, asset_id)
// Owner authorization
plumb.verify_auth(leaf_auth_hash)
// Dual auth if pay_auth โ 0
if cfg_pay == 0 {
} else {
plumb.verify_auth(cfg_pay)
}
// Time-lock check
let lock_headroom: Field = sub(current_time, leaf_lock_until)
plumb.assert_non_negative(lock_headroom)
// Flag check: must be transferable
assert_transferable(leaf_flags)
// --- New owner ---
let new_owner_id: Field = divine()
let new_auth_hash: Field = divine()
// New leaf: only owner_id, auth_hash, nonce change
let new_nonce: Field = leaf_nonce + 1
let new_leaf: Digest = hash_leaf(
leaf_asset_id,
new_owner_id,
new_nonce,
new_auth_hash,
leaf_lock_until,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
// Authenticate new leaf against new state root
merkle.verify(new_leaf, new_root, leaf_idx, plumb.tree_depth())
// Hook signal
plumb.signal_hook(cfg_pay_hook)
// Nullifier (sealed โ prevents replay)
seal Nullifier { asset_id: leaf_asset_id, nonce: leaf_nonce }
// Events
reveal
Pay { asset_id: leaf_asset_id, from_owner: leaf_owner_id, to_owner: new_owner_id, royalty_bps: leaf_royalty_bps }
}
// ============================================================
// Op 1: LOCK โ time-lock an asset
// ============================================================
fn lock() {
let old_root: Digest = pub_read5()
let new_root: Digest = pub_read5()
let asset_count: Field = pub_read()
let asset_id: Field = pub_read()
let lock_until_time: Field = pub_read()
let config: Digest = pub_read5()
// --- Verify config ---
let cfg_admin: Field = divine()
let cfg_pay: Field = divine()
let cfg_lock: Field = divine()
let cfg_mint: Field = divine()
let cfg_burn: Field = divine()
let cfg_pay_hook: Field = divine()
let cfg_lock_hook: Field = divine()
let cfg_update_hook: Field = divine()
let cfg_mint_hook: Field = divine()
let cfg_burn_hook: Field = divine()
plumb.verify_config(
cfg_admin,
cfg_pay,
cfg_lock,
cfg_mint,
cfg_burn,
cfg_pay_hook,
cfg_lock_hook,
cfg_update_hook,
cfg_mint_hook,
cfg_burn_hook,
config
)
// --- Current asset leaf ---
let leaf_asset_id: Field = divine()
let leaf_owner_id: Field = divine()
let leaf_nonce: Field = divine()
let leaf_auth_hash: Field = divine()
let leaf_lock_until: Field = divine()
let leaf_collection_id: Field = divine()
let leaf_metadata_hash: Field = divine()
let leaf_royalty_bps: Field = divine()
let leaf_creator_id: Field = divine()
let leaf_flags: Field = divine()
// Authenticate leaf against old state root
let old_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
leaf_nonce,
leaf_auth_hash,
leaf_lock_until,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
let leaf_idx: U32 = as_u32(divine())
merkle.verify(old_leaf, old_root, leaf_idx, plumb.tree_depth())
assert_eq(leaf_asset_id, asset_id)
// Owner authorization
plumb.verify_auth(leaf_auth_hash)
// Dual auth if lock_auth โ 0
if cfg_lock == 0 {
} else {
plumb.verify_auth(cfg_lock)
}
// Flag check: must be lockable
assert_lockable(leaf_flags)
// Lock can only extend, not shorten
let extension: Field = sub(lock_until_time, leaf_lock_until)
plumb.assert_non_negative(extension)
// New leaf: only lock_until and nonce change
let new_nonce: Field = leaf_nonce + 1
let new_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
new_nonce,
leaf_auth_hash,
lock_until_time,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
// Authenticate new leaf against new state root
merkle.verify(new_leaf, new_root, leaf_idx, plumb.tree_depth())
// Hook signal
plumb.signal_hook(cfg_lock_hook)
reveal
Lock { asset_id: leaf_asset_id, lock_until: lock_until_time }
}
// ============================================================
// Op 2: UPDATE โ update config or asset metadata
// ============================================================
// Two modes:
// Config update: old_root == new_root, admin auth required
// Metadata update: owner auth + UPDATABLE flag required
fn update() {
let old_root: Digest = pub_read5()
let new_root: Digest = pub_read5()
let asset_count: Field = pub_read()
let asset_id: Field = pub_read()
let new_metadata_hash: Field = pub_read()
let config: Digest = pub_read5()
// --- Verify config ---
let cfg_admin: Field = divine()
let cfg_pay: Field = divine()
let cfg_lock: Field = divine()
let cfg_mint: Field = divine()
let cfg_burn: Field = divine()
let cfg_pay_hook: Field = divine()
let cfg_lock_hook: Field = divine()
let cfg_update_hook: Field = divine()
let cfg_mint_hook: Field = divine()
let cfg_burn_hook: Field = divine()
plumb.verify_config(
cfg_admin,
cfg_pay,
cfg_lock,
cfg_mint,
cfg_burn,
cfg_pay_hook,
cfg_lock_hook,
cfg_update_hook,
cfg_mint_hook,
cfg_burn_hook,
config
)
if asset_id == 0 {
// --- Config update ---
plumb.verify_auth(cfg_admin)
} else {
// --- Metadata update ---
let leaf_asset_id: Field = divine()
let leaf_owner_id: Field = divine()
let leaf_nonce: Field = divine()
let leaf_auth_hash: Field = divine()
let leaf_lock_until: Field = divine()
let leaf_collection_id: Field = divine()
let leaf_metadata_hash: Field = divine()
let leaf_royalty_bps: Field = divine()
let leaf_creator_id: Field = divine()
let leaf_flags: Field = divine()
// Authenticate leaf against old state root
let old_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
leaf_nonce,
leaf_auth_hash,
leaf_lock_until,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
let leaf_idx: U32 = as_u32(divine())
merkle.verify(old_leaf, old_root, leaf_idx, plumb.tree_depth())
assert_eq(leaf_asset_id, asset_id)
// Owner authorization
plumb.verify_auth(leaf_auth_hash)
// Flag check: must be updatable
assert_updatable(leaf_flags)
// New leaf: only metadata_hash and nonce change
let new_nonce: Field = leaf_nonce + 1
let new_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
new_nonce,
leaf_auth_hash,
leaf_lock_until,
leaf_collection_id,
new_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
// Authenticate new leaf against new state root
merkle.verify(new_leaf, new_root, leaf_idx, plumb.tree_depth())
reveal
MetadataUpdate { asset_id: leaf_asset_id, old_metadata: leaf_metadata_hash, new_metadata: new_metadata_hash }
}
// Hook signal
plumb.signal_hook(cfg_update_hook)
}
// ============================================================
// Op 3: MINT โ originate a new unique asset
// ============================================================
fn mint() {
let old_root: Digest = pub_read5()
let new_root: Digest = pub_read5()
let old_count: Field = pub_read()
let new_count: Field = pub_read()
let max_supply: Field = pub_read()
let asset_id: Field = pub_read()
let metadata_hash: Field = pub_read()
let collection_id: Field = pub_read()
let config: Digest = pub_read5()
// --- Verify config ---
let cfg_admin: Field = divine()
let cfg_pay: Field = divine()
let cfg_lock: Field = divine()
let cfg_mint: Field = divine()
let cfg_burn: Field = divine()
let cfg_pay_hook: Field = divine()
let cfg_lock_hook: Field = divine()
let cfg_update_hook: Field = divine()
let cfg_mint_hook: Field = divine()
let cfg_burn_hook: Field = divine()
plumb.verify_config(
cfg_admin,
cfg_pay,
cfg_lock,
cfg_mint,
cfg_burn,
cfg_pay_hook,
cfg_lock_hook,
cfg_update_hook,
cfg_mint_hook,
cfg_burn_hook,
config
)
// Mint authorization (always required, 0 = minting disabled)
plumb.verify_auth(cfg_mint)
// Supply accounting
let expected_count: Field = old_count + 1
assert_eq(new_count, expected_count)
// Max supply enforcement (0 = unlimited)
if max_supply == 0 {
} else {
let headroom: Field = sub(max_supply, new_count)
plumb.assert_non_negative(headroom)
}
// Asset fields from prover
let owner_id: Field = divine()
let auth_hash: Field = divine()
let creator_id: Field = divine()
let royalty_bps: Field = divine()
let flags: Field = divine()
// Validate fields
plumb.assert_non_negative(royalty_bps)
let royalty_headroom: Field = sub(10000, royalty_bps)
plumb.assert_non_negative(royalty_headroom)
assert_valid_flags(flags)
// Flag check: must be mintable (collection open for mints)
assert_mintable(flags)
// New leaf: nonce = 0, lock_until = 0
let new_leaf: Digest = hash_leaf(
asset_id,
owner_id,
0,
auth_hash,
0,
collection_id,
metadata_hash,
royalty_bps,
creator_id,
flags
)
// Authenticate new leaf against new state root
let leaf_idx: U32 = as_u32(divine())
merkle.verify(new_leaf, new_root, leaf_idx, plumb.tree_depth())
// Hook signal
plumb.signal_hook(cfg_mint_hook)
// Events
reveal
Mint { asset_id: asset_id, creator_id: creator_id, collection_id: collection_id, metadata_hash: metadata_hash }
reveal
SupplyChange { old_count: old_count, new_count: new_count }
}
// ============================================================
// Op 4: BURN โ permanently destroy an asset
// ============================================================
fn burn() {
let old_root: Digest = pub_read5()
let new_root: Digest = pub_read5()
let old_count: Field = pub_read()
let new_count: Field = pub_read()
let asset_id: Field = pub_read()
let current_time: Field = pub_read()
let config: Digest = pub_read5()
// --- Verify config ---
let cfg_admin: Field = divine()
let cfg_pay: Field = divine()
let cfg_lock: Field = divine()
let cfg_mint: Field = divine()
let cfg_burn: Field = divine()
let cfg_pay_hook: Field = divine()
let cfg_lock_hook: Field = divine()
let cfg_update_hook: Field = divine()
let cfg_mint_hook: Field = divine()
let cfg_burn_hook: Field = divine()
plumb.verify_config(
cfg_admin,
cfg_pay,
cfg_lock,
cfg_mint,
cfg_burn,
cfg_pay_hook,
cfg_lock_hook,
cfg_update_hook,
cfg_mint_hook,
cfg_burn_hook,
config
)
// --- Asset leaf ---
let leaf_asset_id: Field = divine()
let leaf_owner_id: Field = divine()
let leaf_nonce: Field = divine()
let leaf_auth_hash: Field = divine()
let leaf_lock_until: Field = divine()
let leaf_collection_id: Field = divine()
let leaf_metadata_hash: Field = divine()
let leaf_royalty_bps: Field = divine()
let leaf_creator_id: Field = divine()
let leaf_flags: Field = divine()
// Authenticate leaf against old state root
let old_leaf: Digest = hash_leaf(
leaf_asset_id,
leaf_owner_id,
leaf_nonce,
leaf_auth_hash,
leaf_lock_until,
leaf_collection_id,
leaf_metadata_hash,
leaf_royalty_bps,
leaf_creator_id,
leaf_flags
)
let leaf_idx: U32 = as_u32(divine())
merkle.verify(old_leaf, old_root, leaf_idx, plumb.tree_depth())
assert_eq(leaf_asset_id, asset_id)
// Owner authorization
plumb.verify_auth(leaf_auth_hash)
// Dual auth if burn_auth โ 0
if cfg_burn == 0 {
} else {
plumb.verify_auth(cfg_burn)
}
// Time-lock check
let lock_headroom: Field = sub(current_time, leaf_lock_until)
plumb.assert_non_negative(lock_headroom)
// Flag check: must be burnable
assert_burnable(leaf_flags)
// Supply accounting
let expected_count: Field = sub(old_count, 1)
assert_eq(new_count, expected_count)
// Hook signal
plumb.signal_hook(cfg_burn_hook)
// Nullifier (sealed โ prevents double-burn)
seal Nullifier { asset_id: leaf_asset_id, nonce: leaf_nonce }
// Events
reveal
Burn { asset_id: leaf_asset_id, owner_id: leaf_owner_id }
reveal
SupplyChange { old_count: old_count, new_count: new_count }
}
// ============================================================
// Entry point โ TSP-2 dispatch by PLUMB operation code
// ============================================================
fn main() {
let op: Field = pub_read()
if op == 0 {
pay()
} else if op == 1 {
lock()
} else if op == 2 {
update()
} else if op == 3 {
mint()
} else if op == 4 {
burn()
}
}
trident/os/neptune/standards/card.tri
ฯ 0.0%