Commit Flow
The commit operation destroys tokens and stores a cryptographic commitment in the Merkle tree.
Committing native GHOST
interface ICommitRevealVault {
function commitNative(
bytes32 commitment,
bytes32 quantumCommitment
) external payable;
}
Send GHOST as msg.value. The vault burns the tokens and inserts the commitment.
Example with Foundry
# Commit 1 GHOST
cast send 0x443434113980Ab9d5Eef0Ace7d1A29AB68Af6c70 \
"commitNative(bytes32,bytes32)" \
$COMMITMENT \
0x0000000000000000000000000000000000000000000000000000000000000000 \
--value 1ether \
--rpc-url https://testnet.specterchain.com \
--chain-id 5445 \
--private-key $PRIVATE_KEY
Example with ethers.js
const vault = new ethers.Contract(
'0x443434113980Ab9d5Eef0Ace7d1A29AB68Af6c70',
['function commitNative(bytes32 commitment, bytes32 quantumCommitment) payable'],
signer
);
const tx = await vault.commitNative(
commitment, // Poseidon hash of your secret inputs
ethers.ZeroHash, // quantum commitment (0x0 if not using quantum resistance)
{ value: ethers.parseEther('1'), gasPrice: 1_000_000_000n }
);
await tx.wait();
Committing ERC20 tokens
interface ICommitRevealVault {
function commit(
address token,
uint256 amount,
bytes32 commitment,
bytes32 quantumCommitment
) external;
}
First approve the vault to spend your tokens, then call commit():
// Approve
const token = new ethers.Contract(tokenAddress, ['function approve(address,uint256)'], signer);
await (await token.approve('0x443434113980Ab9d5Eef0Ace7d1A29AB68Af6c70', amount)).wait();
// Commit
const vault = new ethers.Contract(
'0x443434113980Ab9d5Eef0Ace7d1A29AB68Af6c70',
['function commit(address,uint256,bytes32,bytes32)'],
signer
);
await (await vault.commit(tokenAddress, amount, commitment, ethers.ZeroHash)).wait();
Committing with a policy
interface ICommitRevealVault {
function commitNativeWithPolicy(
bytes32 commitment,
bytes32 quantumCommitment,
address policy,
bytes32 policyParamsHash
) external payable;
function commitWithPolicy(
address token,
uint256 amount,
bytes32 commitment,
bytes32 quantumCommitment,
address policy,
bytes32 policyParamsHash
) external;
}
Policies bind enforceable conditions to the commitment. The policy contract's validate() function is called during reveal. See Policy System.
Computing the commitment
The commitment is a Poseidon hash of 5 or 7 fields:
commitment = Poseidon5(secret, nullifierSecret, tokenIdHash, amount, blinding)
Or with policy:
commitment = Poseidon7(secret, nullifierSecret, tokenIdHash, amount, blinding, policyId, policyParamsHash)
You can compute this client-side with snarkjs/circomlibjs, or via the commitment relayer:
curl -X POST https://relayer.specterchain.com/api/commitment/compute \
-H "Content-Type: application/json" \
-H "X-HMAC-Signature: $HMAC_SIG" \
-d '{
"secret": "0x...",
"nullifierSecret": "0x...",
"blinding": "0x...",
"tokenIdHash": "0x...",
"amount": "1000000000000000000"
}'
What happens on-chain
- The vault verifies the token is authorized via AssetGuard
- For ERC20: calls
token.burn(from, amount). For native: calls precompileburnNativeFrom() - Calls
commitmentTree.insert(commitment)to add the leaf - Records
totalCommitted[token] += amount - If policy is specified, stores
commitmentPolicies[commitment] = policy - Emits
Committed(commitment, token, amount)orCommittedWithPolicy(...)
Rate limiting
A cooldown period (default 5 seconds) prevents rapid sequential commits from the same address. This is enforced via lastCommitTime mapping and commitCooldown parameter.