Mint authority is gated by the witness-derived admin keypair from
Access Control . The admin proves knowledge of a
witness secret whose hash matches the stored _admin public key.
The Contract
pragma language_version >= 0.23;
import CompactStandardLibrary;
// Admin keypair (witness-derived)
struct AdminSecretKey { bytes: Bytes<32>; }
struct AdminPublicKey { bytes: Bytes<32>; }
witness getAdminSecret(): AdminSecretKey;
// User keypair (witness-derived) — used as a balance index
struct UserSecretKey { bytes: Bytes<32>; }
struct UserPublicKey { bytes: Bytes<32>; }
witness getUserSecret(): UserSecretKey;
// Ledger state
export ledger _admin: AdminPublicKey;
export ledger _balances: Map<UserPublicKey, Uint<128>>;
export ledger _totalSupply: Uint<128>;
constructor(initialAdmin: AdminPublicKey) {
_admin = disclose(initialAdmin);
}
// Key derivation
export circuit deriveAdminPublicKey(sk: AdminSecretKey): AdminPublicKey {
return AdminPublicKey {
bytes: persistentHash<Vector<2, Bytes<32>>>([
pad(32, "myapp:admin:v1"),
sk.bytes
])
};
}
export circuit deriveUserPublicKey(sk: UserSecretKey): UserPublicKey {
return UserPublicKey {
bytes: persistentHash<Vector<2, Bytes<32>>>([
pad(32, "myapp:user:v1"),
sk.bytes
])
};
}
// Read total supply
export circuit getTotalSupply(): Uint<128> {
return _totalSupply;
}
// Mint — admin only
export circuit mint(account: UserPublicKey, amount: Uint<128>): [] {
assert(
_admin == deriveAdminPublicKey(getAdminSecret()),
"Only admin may mint"
);
_mint(account, amount);
}
// Internal mint logic
circuit _mint(account: UserPublicKey, amount: Uint<128>): [] {
const MAX_UINT128: Uint<128> = 340282366920938463463374607431768211455 as Uint<128>;
// Check total supply won't overflow
assert(_totalSupply <= MAX_UINT128 - amount, "Total supply overflow");
_totalSupply = disclose((_totalSupply + amount) as Uint<128>);
// Check recipient's balance won't overflow
const currentBalance = getBalance(account);
assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");
_balances.insert(
disclose(account),
disclose((currentBalance + amount) as Uint<128>)
);
}
// Helper
circuit getBalance(account: UserPublicKey): Uint<128> {
if (!_balances.member(disclose(account))) { return 0; }
return _balances.lookup(disclose(account));
}
How It Works
Admin authentication
assert(
_admin == deriveAdminPublicKey(getAdminSecret()),
"Only admin may mint"
);
The contract stores the admin’s public key. To mint, the caller must
produce a ZK proof that they know a witness secret whose hash equals that
public key. Reading _admin off chain gives an attacker the hash, not the
secret — preimage resistance means they cannot forge the proof.
Total supply invariant
assert(_totalSupply <= MAX_UINT128 - amount, "Total supply overflow");
_totalSupply = disclose((_totalSupply + amount) as Uint<128>);
sum(all balances) == _totalSupply is maintained by always updating both
sides together. Minting increases both; burning (Burning )
decreases both; transferring preserves both.
Cast back to Uint<128> on store
_totalSupply = disclose((_totalSupply + amount) as Uint<128>);
Arithmetic widens; after the overflow assertion we cast back to Uint<128>
before writing. See Overflow Protection .
Privacy Note
Each mint discloses account and amount on the public ledger. The full
participant set and amount of every mint is observable to anyone reading the
chain. This contract is suitable for transparent registries (game items,
public rewards) but not for private finance.
What’s Next
Burning Tokens Learn how to destroy tokens
ERC20 Token Complete token with minting