pragma language_version >= 0.23;
import CompactStandardLibrary;
// User keypair (witness-derived)
struct UserSecretKey { bytes: Bytes<32>; }
struct UserPublicKey { bytes: Bytes<32>; }
witness getUserSecret(): UserSecretKey;
// Admin keypair (witness-derived) — gates _mint and _burn
struct AdminSecretKey { bytes: Bytes<32>; }
struct AdminPublicKey { bytes: Bytes<32>; }
witness getAdminSecret(): AdminSecretKey;
export ledger _admin: AdminPublicKey;
// Balances: tokenId → (owner → amount)
export ledger _balances: Map<Uint<128>, Map<UserPublicKey, Uint<128>>>;
// Operator approvals (all-or-nothing across every token type)
export ledger _operatorApprovals: Map<UserPublicKey, Map<UserPublicKey, Boolean>>;
// Per-tokenId metadata URIs
export ledger _uris: Map<Uint<128>, Opaque<"string">>;
constructor(initialAdmin: AdminPublicKey) {
_admin = disclose(initialAdmin);
}
// Key derivation
export circuit deriveUserPublicKey(sk: UserSecretKey): UserPublicKey {
return UserPublicKey {
bytes: persistentHash<Vector<2, Bytes<32>>>([
pad(32, "erc1155:user:v1"),
sk.bytes
])
};
}
export circuit deriveAdminPublicKey(sk: AdminSecretKey): AdminPublicKey {
return AdminPublicKey {
bytes: persistentHash<Vector<2, Bytes<32>>>([
pad(32, "erc1155:admin:v1"),
sk.bytes
])
};
}
// Balance of `account` for token id `id`
export circuit balanceOf(account: UserPublicKey, id: Uint<128>): Uint<128> {
if (!_balances.member(disclose(id))) { return 0; }
if (!_balances.lookup(disclose(id)).member(disclose(account))) { return 0; }
return _balances.lookup(disclose(id)).lookup(disclose(account));
}
// Operator approval — caller authenticates as `owner`
export circuit setApprovalForAll(operator: UserPublicKey, approved: Boolean): [] {
const owner = disclose(deriveUserPublicKey(getUserSecret()));
_setApprovalForAll(owner, operator, approved);
}
export circuit isApprovedForAll(account: UserPublicKey, operator: UserPublicKey): Boolean {
if (_operatorApprovals.member(disclose(account)) &&
_operatorApprovals.lookup(disclose(account)).member(disclose(operator))) {
return _operatorApprovals.lookup(disclose(account)).lookup(disclose(operator));
}
return false;
}
// Transfer — caller must be `fromKey` itself, or an approved operator
export circuit transfer(
fromKey: UserPublicKey,
to: UserPublicKey,
id: Uint<128>,
value: Uint<128>
): [] {
// Disclose both sides of the comparison up front. `fromKey` will be
// disclosed anyway by `_debit` below, so this changes no privacy
// properties — but it lets the compiler see that the boolean branch
// is on public values.
const fromD = disclose(fromKey);
const callerD = disclose(deriveUserPublicKey(getUserSecret()));
assert(fromD == callerD || isApprovedForAll(fromKey, callerD), "Not authorized");
_update(fromKey, to, id, value);
}
// Read token URI
export circuit uri(tokenId: Uint<128>): Opaque<"string"> {
if (!_uris.member(disclose(tokenId))) { return default<Opaque<"string">>; }
return _uris.lookup(disclose(tokenId));
}
// Admin: set the URI for a token id
export circuit setURI(id: Uint<128>, newuri: Opaque<"string">): [] {
assertAdmin();
_uris.insert(disclose(id), disclose(newuri));
}
// Admin: mint
export circuit mint(to: UserPublicKey, id: Uint<128>, value: Uint<128>): [] {
assertAdmin();
_credit(to, id, value);
}
// Admin: burn (forceful — admin can burn any holder's tokens)
export circuit adminBurn(fromKey: UserPublicKey, id: Uint<128>, value: Uint<128>): [] {
assertAdmin();
_debit(fromKey, id, value);
}
// Self-burn — caller authenticates as the holder
export circuit burn(id: Uint<128>, value: Uint<128>): [] {
const account = disclose(deriveUserPublicKey(getUserSecret()));
_debit(account, id, value);
}
// Internals
circuit assertAdmin(): [] {
assert(_admin == deriveAdminPublicKey(getAdminSecret()), "Not admin");
}
circuit _setApprovalForAll(
owner: UserPublicKey,
operator: UserPublicKey,
approved: Boolean
): [] {
assert(owner != operator, "Cannot approve self");
if (!_operatorApprovals.member(disclose(owner))) {
_operatorApprovals.insert(disclose(owner), default<Map<UserPublicKey, Boolean>>);
}
_operatorApprovals.lookup(disclose(owner)).insert(disclose(operator), disclose(approved));
}
circuit _update(
fromKey: UserPublicKey,
to: UserPublicKey,
id: Uint<128>,
value: Uint<128>
): [] {
_debit(fromKey, id, value);
_credit(to, id, value);
}
circuit _credit(account: UserPublicKey, id: Uint<128>, value: Uint<128>): [] {
const MAX_UINT128: Uint<128> = 340282366920938463463374607431768211455 as Uint<128>;
const current = balanceOf(account, id);
assert(current <= MAX_UINT128 - value, "Balance overflow");
if (!_balances.member(disclose(id))) {
_balances.insert(disclose(id), default<Map<UserPublicKey, Uint<128>>>);
}
_balances.lookup(disclose(id)).insert(
disclose(account),
disclose((current + value) as Uint<128>)
);
}
circuit _debit(account: UserPublicKey, id: Uint<128>, value: Uint<128>): [] {
const current = balanceOf(account, id);
assert(current >= value, "Insufficient balance");
_balances.lookup(disclose(id)).insert(disclose(account), disclose(current - value));
}