program card_v2

// PLUMBv2 full-Digest authority and atomic transition example.
// Normative schema: Trident reference/plumb-v2.md.
// External hook/controller registry proof requests require outer composition.
use os.neptune.standards.plumb

// --- Flag constants ---
// TRANSFERABLE = 1, BURNABLE = 2, UPDATABLE = 4, LOCKABLE = 8, MINTABLE = 16
// --- Versioned core and full authority/creator commitments ---
fn hash_leaf(
    asset_id: Field, owner_id: Field, nonce: Field, auth_hash: Digest,
    lock_until: Field, collection_id: Field, metadata_hash: Field,
    royalty_bps: Field, creator_id: Digest, flags: Field
) -> Digest {
    plumb.assert_non_negative(nonce)
    plumb.assert_non_negative(lock_until)
    plumb.assert_non_negative(royalty_bps)
    plumb.assert_non_negative(sub(10000,royalty_bps))
    assert_valid_flags(flags)
    let core = hash(asset_id,owner_id,nonce,lock_until,collection_id,metadata_hash,royalty_bps,flags,2,2)
    plumb.pair(core,plumb.pair(auth_hash,creator_id))
}



// A checked U32 bit test implements the five independent capability flags.
fn assert_flag(flags: Field, mask: U32) {
    let bits: U32 = as_u32(flags)
    assert(bits < as_u32(32))
    assert((bits & mask) == mask)
}
fn assert_transferable(flags: Field) { assert_flag(flags,as_u32(1)) }
fn assert_burnable(flags: Field) { assert_flag(flags,as_u32(2)) }
fn assert_updatable(flags: Field) { assert_flag(flags,as_u32(4)) }
fn assert_lockable(flags: Field) { assert_flag(flags,as_u32(8)) }
fn assert_mintable(flags: Field) { assert_flag(flags,as_u32(16)) }
fn assert_valid_flags(flags: Field) { assert(as_u32(flags) < as_u32(32)) }

// --- 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: Digest,
    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()
    plumb.assert_non_negative(asset_count)
    let asset_id: Field = pub_read()
    let current_time: Field = pub_read()
    plumb.assert_non_negative(current_time)
    let config: Digest = pub_read5()
    // --- Verify config ---
    let cfg_admin: Digest = divine5()
    let cfg_pay: Digest = divine5()
    let cfg_lock: Digest = divine5()
    let cfg_mint: Digest = divine5()
    let cfg_burn: Digest = divine5()
    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: Digest = divine5()
    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: Digest = divine5()
    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())
    plumb.check_index(leaf_asset_id,leaf_idx)
    // 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 plumb.is_zero(cfg_pay) {
    } 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: Digest = divine5()
    // New leaf: only owner_id, auth_hash, nonce change
    let new_nonce: Field = plumb.next_nonce(leaf_nonce)
    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
    assert_digest(plumb.update_leaf(old_leaf,new_leaf,old_root,leaf_idx),new_root)
    // 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()
    plumb.assert_non_negative(asset_count)
    let asset_id: Field = pub_read()
    let lock_until_time: Field = pub_read()
    plumb.assert_non_negative(lock_until_time)
    let config: Digest = pub_read5()
    // --- Verify config ---
    let cfg_admin: Digest = divine5()
    let cfg_pay: Digest = divine5()
    let cfg_lock: Digest = divine5()
    let cfg_mint: Digest = divine5()
    let cfg_burn: Digest = divine5()
    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: Digest = divine5()
    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: Digest = divine5()
    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())
    plumb.check_index(leaf_asset_id,leaf_idx)
    assert_eq(leaf_asset_id, asset_id)
    // Owner authorization
    plumb.verify_auth(leaf_auth_hash)
    // Dual auth if lock_auth โ‰  0
    if plumb.is_zero(cfg_lock) {
    } 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 = plumb.next_nonce(leaf_nonce)
    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
    assert_digest(plumb.update_leaf(old_leaf,new_leaf,old_root,leaf_idx),new_root)
    // Hook signal
    plumb.signal_hook(cfg_lock_hook)
    seal Nullifier { asset_id: leaf_asset_id, nonce: leaf_nonce }
    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()
    plumb.assert_non_negative(asset_count)
    let asset_id: Field = pub_read()
    let new_metadata_hash: Field = pub_read()
    let config: Digest = pub_read5()
    // --- Verify config ---
    let cfg_admin: Digest = divine5()
    let cfg_pay: Digest = divine5()
    let cfg_lock: Digest = divine5()
    let cfg_mint: Digest = divine5()
    let cfg_burn: Digest = divine5()
    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 ---
        assert_digest(old_root,new_root)
        let new_config: Digest = pub_read5()
        plumb.verify_auth(cfg_admin)
        read_and_verify_config(new_config)
    } else {
        // --- Metadata update ---
        let leaf_asset_id: Field = divine()
        let leaf_owner_id: Field = divine()
        let leaf_nonce: Field = divine()
        let leaf_auth_hash: Digest = divine5()
        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: Digest = divine5()
        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())
        plumb.check_index(leaf_asset_id,leaf_idx)
        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 = plumb.next_nonce(leaf_nonce)
        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
        assert_digest(plumb.update_leaf(old_leaf,new_leaf,old_root,leaf_idx),new_root)
        seal Nullifier { asset_id: leaf_asset_id, nonce: leaf_nonce }
        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()
    plumb.assert_non_negative(old_count)
    let new_count: Field = pub_read()
    plumb.assert_non_negative(new_count)
    let max_supply: Field = pub_read()
    plumb.assert_non_negative(max_supply)
    let asset_id: Field = pub_read()
    let metadata_hash: Field = pub_read()
    let collection_id: Field = pub_read()
    let config: Digest = pub_read5()
    let metadata_config: Digest = pub_read5()
    // --- Verify config ---
    let cfg_admin: Digest = divine5()
    let cfg_pay: Digest = divine5()
    let cfg_lock: Digest = divine5()
    let cfg_mint: Digest = divine5()
    let cfg_burn: Digest = divine5()
    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)
    verify_metadata_cap(metadata_config,max_supply)
    // 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: Digest = divine5()
    let creator_id: Digest = divine5()
    assert_digest(creator_id,cfg_mint)
    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())
    plumb.check_index(asset_id,leaf_idx)
    assert_digest(plumb.update_leaf(plumb.empty_leaf(),new_leaf,old_root,leaf_idx),new_root)
    // 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()
    plumb.assert_non_negative(old_count)
    let new_count: Field = pub_read()
    plumb.assert_non_negative(new_count)
    let asset_id: Field = pub_read()
    let current_time: Field = pub_read()
    plumb.assert_non_negative(current_time)
    let config: Digest = pub_read5()
    // --- Verify config ---
    let cfg_admin: Digest = divine5()
    let cfg_pay: Digest = divine5()
    let cfg_lock: Digest = divine5()
    let cfg_mint: Digest = divine5()
    let cfg_burn: Digest = divine5()
    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: Digest = divine5()
    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: Digest = divine5()
    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())
    plumb.check_index(leaf_asset_id,leaf_idx)
    assert_eq(leaf_asset_id, asset_id)
    // Owner authorization
    plumb.verify_auth(leaf_auth_hash)
    // Dual auth if burn_auth โ‰  0
    if plumb.is_zero(cfg_burn) {
    } 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)
    assert_digest(plumb.update_leaf(old_leaf,plumb.empty_leaf(),old_root,leaf_idx),new_root)
    // 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()
    } else { assert(false) }
}

fn read_and_verify_config(expected: Digest) {
    let admin: Digest = divine5()
    let pay: Digest = divine5()
    let lock: Digest = divine5()
    let mint: Digest = divine5()
    let burn: Digest = divine5()
    let h0 = divine() let h1 = divine() let h2 = divine() let h3 = divine() let h4 = divine()
    plumb.verify_config(admin,pay,lock,mint,burn,h0,h1,h2,h3,h4,expected)
}

fn verify_metadata_cap(expected: Digest, cap: Field) {
    let name = divine() let description = divine() let image = divine()
    let site = divine() let custom = divine() let declared_cap = divine()
    let royalty_receiver = divine() let version = divine() let r0 = divine() let r1 = divine()
    assert_eq(version,2) assert_eq(r0,0) assert_eq(r1,0)
    assert_eq(cap,declared_cap)
    assert_digest(hash(name,description,image,site,custom,declared_cap,royalty_receiver,version,r0,r1),expected)
}

Homonyms

warriors/trisha/examples/experimental/neptune/plumb-v1/card.tri

Graph