This standard defines a method for exporting a shielded note from one UTXO-based privacy protocol and importing it into another without publicly withdrawing and re-depositing the underlying asset. Similar to EIP-7503, sender creates a note in source pool bound to a destination-specific burn address. A destination protocol imports the note by verifying that the burn commitment is included in a recognized source pool root, that the source root is included in a trusted canonical tree, and that the imported output preserves the source note's asset context.
UTXO-based privacy protocols are usually isolated privacy sets. Moving between them requires a public withdrawal from one pool followed by a public deposit into another. This creates a linkable transition that may reveal the asset, amount, source pool, destination pool, timing, and recipient.
Shielded note teleportation changes the movement between pools from a public asset flow into a proof of prior membership. The source protocol does not need to transfer assets directly to the destination protocol. Instead, it creates a burn note that only the intended receiver can import. The destination protocol then verifies that the burned note existed in a recognized source pool root and creates the corresponding destination note or withdrawal.
The result is a positive-sum privacy primitive: existing privacy sets can become mutually composable, and a destination pool can accept private state transitions from multiple source pools without requiring all protocols to share the same note commitment format.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Source pool: A UTXO-based privacy protocol from which a note is exported.
Destination pool: A privacy protocol or withdrawal system that imports a source note by verifying a teleport proof.
Note: A private UTXO-like object containing data and secret material sufficient to derive a commitment and nullifier.
Burn address: A domain-separated value used as the owner or recipient of the source note commitment. A note committed to the burn address is exportable to a destination pool.
Canonical tree: An authenticated tree used by destination pools to prove that source pool commitment roots exist and are valid for teleportation.
Canonical root: The root of the canonical tree.
Teleport proof: A zero-knowledge proof that a source note was committed to the burn address, that the source note is included in the source pool root, that the source pool root is included in a canonical tree, and that the imported output is bound to the same note context.
Asset context: The set of public or private values that identify the imported value, including, as applicable, token address, token ID, denomination, amount, source pool address, source chain ID, and any protocol-specific asset domain.
Implementations MUST define the field, hash function, byte encoding, and tree hashers used by their circuits. The reference implementation uses BN254 field elements and Poseidon-style hashes for native pool commitments and custom canonical tree proofs. Protocol-specific adapters MAY use the source protocol's native tree hasher for source membership proofs.
All string domain separators used as field elements MUST have a canonical encoding. The domain separator for burn addresses is:
ZKTELEPORT
The burn address MUST be computed as:
burnAddress = H(
chainId,
dstPoolAddress,
receiver,
burnSecret,
"ZKTELEPORT"
)
where:
chainId is the EIP-155 chain identifier of the destination pool.dstPoolAddress is the destination pool address.receiver is the destination owner or receiver authorized to import the note.burnSecret is secret entropy chosen for this teleport."ZKTELEPORT" is the domain separator.Each input to H is interpreted inside the circuit as a Field. When an input is exposed as a public input, its Solidity verifier representation MAY be bytes32, uint256, or another 32-byte ABI type used by the verifier contract. In all cases, the public input MUST have a canonical 32-byte representation. Values shorter than 32 bytes MUST be left-padded with zero bytes. Values longer than 32 bytes MUST be reduced to one field element using a canonical encoding, hash, or commitment specified by the source pool adapter or destination pool.
The burn address MUST be bound to the destination chain ID. A note burned for one destination chain MUST NOT be replayable on another chain.
The burn address MUST be bound to the destination pool. A note burned for one destination pool MUST NOT be importable by another destination pool.
The burn address MUST be bound to the receiver. A note burned for one receiver MUST NOT be importable by another receiver.
The receiver value MUST be compatible with the address or public-key system used by the destination pool.
The burn address SHOULD include at least 128 bits of private entropy through burnSecret.
Destination pools MUST only accept source protocols whose adapters define how a burn note is made unusable for ordinary source-protocol spending. Depending on the source protocol, this MAY be accomplished by committing the note to an unspendable owner, proving a source nullifier has been consumed, or proving an equivalent protocol-specific lock or burn condition. A destination pool MUST NOT import value from a source note that can also be spent normally in the source pool unless the destination pool explicitly accounts for that double-spend risk.
A standard source pool note commitment SHOULD be expressible as:
blindedOwner = H(ownerAddress, blinding)
commitment = H(blindedOwner, data)
To export a note, the source commitment is recomputed with:
ownerAddress = burnAddress
The teleport proof MUST prove that this burn commitment is included in the source pool root:
assertMerkleMembership(
root = srcPoolRoot,
leaf = commitment,
index = sourceIndex,
siblings = sourceSiblings
)
Protocols with different note formats MAY define adapters. An adapter MUST specify how to compute the burn commitment, how to verify source tree membership, and how to bind the source note to the imported asset context.
Destination pools MUST verify that a source pool commitment root exists in, and is valid under, a canonical tree.
The canonical tree MAY be any authenticated tree trusted by the destination pool, including the chain's state tree or a custom Merkle tree. This standard is agnostic to which canonical tree the destination pool uses.
Destination pools MUST specify how source pool commitment roots are represented in the canonical tree and how canonical tree proofs are verified. The representation MUST include enough context for the destination pool to identify the source pool commitment root and determine that it is valid under the destination pool's trust policy.
For custom Merkle trees, the canonical tree proof can be represented as membership of canonicalLeaf in canonicalRoot:
assertMerkleMembership(
root = canonicalRoot,
leaf = canonicalLeaf,
index = canonicalIndex,
siblings = canonicalSiblings
)
Destination pools MUST reject teleport proofs for canonical roots that are not recognized by the destination pool or its configured canonical root registry.
Canonical root registries SHOULD expose a method equivalent to:
function isValidRoot(bytes32 root) external view returns (bool);
A destination pool importing a teleported note MUST verify a teleport proof with public inputs that include:
canonicalRootThe destination pool MUST mark each nullifier or equivalent nullification value as spent before or during import finalization. A nullifier or equivalent nullification value MUST NOT be accepted more than once by the same destination pool.
The destination pool MUST insert each imported destination commitment into its own commitment tree, execute each destination withdrawal, or both, according to its protocol rules.
Destination pools MUST define a backing model for imported value before accepting teleport proofs. The backing model MAY use escrowed liquidity, burn-and-mint accounting, protocol-owned inventory, a bridge settlement mechanism, or another explicitly specified policy. A destination pool MUST NOT create a destination note or execute a withdrawal unless the imported value is backed according to that policy.
The teleport proof MUST expose one or more nullifiers that are unique to the teleported note according to the destination pool's nullification rules. Each nullifier MUST be derived from private material or a nullifying key associated with the teleported note.
Destination pools MAY define their own nullifier mechanism, including a nullifying key, source-note nullifier material, burn-secret-derived value, or another protocol-specific value. The mechanism MUST ensure that the same teleported note cannot be imported more than once by the same destination pool.
Destination pools SHOULD domain-separate nullifiers by destination pool or only track them inside the destination pool where the proof is consumed. A proof generated for one destination pool MUST NOT be replayable against another destination pool.
For a destination note, the imported commitment SHOULD preserve the source note's asset context:
destinationCommitment = H(
destinationRecipient,
// ...data...
)
If the destination protocol uses a blinded recipient commitment, destinationRecipient MAY be a pre-blinded recipient value.
The teleport proof MUST bind the source asset context to the destination output. An importer MUST NOT be able to change note data without invalidating the proof.
Destination pools SHOULD bind each import to an explicit user authorization, such as an EIP-712 typed-data signature over the destination operation.
If typed-data authorization is used, the circuit MUST verify that the signer corresponds to the receiver bound into the burn address, and the destination contract MUST verify that the proof exposes the same typed-data digest accepted by the contract.
Adapters MAY be used for source protocols whose note commitments or tree hashers differ from the standard source note format.
An adapter MUST define:
For example, a Tornado-style adapter MAY compute the source leaf as:
sourceLeaf = H(burnAddress, noteSecret)
and MAY canonicalize the source root as:
canonicalLeaf = H(
srcChainId,
blockNumber,
srcPoolAddress,
H(token, denomination, srcPoolRoot)
)
For example, a Railgun-style adapter MAY use the source protocol's note public key and asset commitment format:
npk = H(burnAddress, blinding)
asset = token
sourceLeaf = H(npk, asset, amount)
Adapters MUST NOT weaken destination binding, receiver binding, source root membership, canonical tree membership, or asset-context preservation.
The burn address includes the destination chain ID, destination pool address and receiver to prevent replay on multiple destination pools and/or chains. Binding the source burn to these values gives the destination protocol a portable proof that the source note was intentionally exported for this destination and receiver.
Protocol-specific adapters allow existing privacy systems to participate without changing their historical commitment formats.
The canonical tree separates source-protocol trust policy from the import circuit. This allows destination pools to choose which source pools, source roots, and root publishers they trust without requiring every participating protocol to share the same commitment tree or note format.
The backing-model requirement is intentionally left policy-specific because different destination pools may settle imported value differently. The standard requires the policy to be explicit so that a valid teleport proof cannot, by itself, be treated as authority to inflate destination assets.
This standard is opt-in and does not change the behavior of existing privacy protocols. Existing source protocols can become teleport sources if their note commitment and root membership rules can be expressed in a circuit adapter and their roots can be verified in a canonical tree.
Existing privacy protocols with upgradability can add support for note teleportation as a new import method without changing their existing deposit, transfer, or withdrawal methods.
The following pseudocode illustrates a one-input, one-output teleport circuit:
fn verify_teleportation(
chain_id: u32,
block_number: u64,
receiver: Field,
burn_secret: Field,
dst_pool_address: Field,
src_chain_id: u32,
src_pool_address: Field,
src_pool_root: Field,
token: Field,
token_id: Field,
note: InputNote,
destination_operation_digest: Field,
destination_commitment: Field,
canonical_root: Field,
canonical_index: Field,
canonical_siblings: [Field],
) {
let burn_address = hash([
chain_id as Field,
dst_pool_address,
receiver,
burn_secret,
Field::from_be_bytes("ZKTELEPORT".as_bytes()),
]);
let burn_commitment = note.to_commitment(
burn_address,
token,
token_id,
);
assert_merkle_leaf_membership(
src_pool_root,
burn_commitment,
note.index,
note.siblings,
);
let asset_context = hash([
token,
token_id,
src_chain_id as Field,
src_pool_address,
]);
let canonical_leaf = hash([
src_chain_id as Field,
block_number as Field,
src_pool_address,
hash([asset_context, src_pool_root]),
]);
assert_merkle_leaf_membership(
canonical_root,
canonical_leaf,
canonical_index,
canonical_siblings,
);
let teleport_nullifier = note.to_destination_nullifier();
constrain_public_nullifier(teleport_nullifier);
constrain_destination_output(destination_commitment, asset_context, receiver);
constrain_operation_digest(destination_operation_digest, canonical_root, teleport_nullifier, destination_commitment);
}
Destination contracts can expose an import method equivalent to:
pragma solidity ^0.8.0;
function teleport(Joinsplit calldata joinsplit) external {
require(canonicalRootRegistry.isValidRoot(joinsplit.root), "invalid canonical root");
_verifyTeleportProof(joinsplit);
_spendNullifiers(joinsplit.nullifiers);
_enforceBackingPolicy(joinsplit);
_insertCommitments(joinsplit.commitments);
_executeWithdrawals(joinsplit.withdrawals);
}
Teleportation depends on the soundness of the source membership proof, canonical tree proof, and destination import proof. A failure in any of these checks can allow inflation, theft, or replay.
Destination pools MUST ensure that imported value is backed by a source note that is locked, burned, or otherwise made unusable according to the source protocol's rules. If the source protocol does not prevent subsequent spending of a burned note, the destination protocol MUST account for that risk before accepting the source as canonical.
The burn secret MUST be private until the teleport proof is generated. Reusing a burn secret across teleports is NOT RECOMMENDED.
Destination pools MUST prevent replay by tracking nullifiers. Cross-destination replay MUST be prevented by binding the burn address and authorization message to the destination pool.
Adapters MUST preserve the source protocol's exact commitment semantics. Incorrect hasher selection, field reduction, byte ordering, token encoding, or tree depth can make proofs unsound or make valid notes unimportable.
Typed-data authorization, if used, MUST be bound to the destination chain, destination contract, canonical root, nullifiers, outputs, and withdrawals. This prevents a proof or signature from being replayed for a different import operation.
Privacy can be weakened by timing, small anonymity sets, or canonical root update patterns. Implementations SHOULD batch root updates and user imports where practical.
Copyright and related rights waived via CC0.