How It Works (Basically Explained)
Skyhook rests on a single observation about Zcash: a transaction ID is the root of a tree of hashes, and a root can be checked without seeing every leaf. Since ZIP 244, Zcash computes the identifier of a transaction by hashing its parts into branches, and the branches into a single root.1 An Ethereum contract can therefore recompute that root from the few values it cares about, such as the amount and the recipient, while every other part of the transaction arrives as an already-hashed branch. If the recomputed root matches a transaction that Zcash miners included in a block, the payment happened, and the contract has learned nothing about the sender.
This page explains the idea with a four-leaf example, then shows how it applies to a real v6 transaction.
Hash trees
A hash function turns any input into a fixed-length output that looks random. Zcash uses BLAKE2b, designed by Aumasson, Neves, Wilcox-O’Hearn and Winnerlein, with 256-bit outputs.2 Two properties of such a function matter here. It is preimage-resistant: given an output, nobody can find an input that produces it. It is also collision-resistant: nobody can find two different inputs with the same output. With 256-bit outputs, an attacker would need on the order of quintillions of years of computation to break either property by brute force.
A hash tree (or Merkle tree) applies the function repeatedly. Pairs of leaves are hashed into branches, pairs of branches into higher branches, and so on until one root remains. Because each level depends on everything below it, the root acts as a fingerprint of all the leaves: change any leaf, and the root changes.
Checking a root with folded branches
The property Skyhook depends on is less obvious. In the example above, fold branch 2. The verifier no longer sees leaves 7 and 11, only their branch hash, yet it can still recompute the root: it hashes leaves 13 and 5 into branch 1, takes branch 2 as given, and hashes the pair.
A root can be verified from a mix of revealed leaves and opaque branch hashes, and the two halves of that check behave differently:
- A revealed leaf is checked exactly. If the prover changes leaf 13 to 14, branch 1 changes, the root changes, and the check fails.
- A folded branch reveals nothing. Preimage resistance means the verifier cannot recover leaves 7 and 11 from their hash, and collision resistance means the prover cannot substitute different leaves that produce the same hash.
Skyhook’s on-chain verification is this one operation, applied to the real transaction tree.
The v6 transaction tree
A real Zcash transaction has many more parts than four numbers. The diagram below shows a simplified version of the v6 transaction tree for a fully Ironwood (shielded) transaction. The transaction ID, txid_digest_v6, is computed from digests of the header, the transparent part, the Sapling and Orchard parts, and the Ironwood part. The Ironwood digest is itself built from further digests, including a compact digest over each action’s nullifier, cmx (note commitment), ephemeralKey, and the first 52 bytes of its encrypted ciphertext.
To prove a payment, the prover reveals the leaves the contract has to check, such as the note commitment that encodes the amount and the recipient, and hands over everything else as opaque digests: the non-compact action data, the memos, the other parts of the transaction. The contract does not need to know what the opaque digests contain. The Zcash network has already validated the full transaction before including it in a block, and if the prover had altered any value, the digests would not combine to the identifier in that block.
What the contract learns
An Ethereum contract can therefore confirm that a specific Zcash payment happened while seeing only hashes and the few values it checks. It never sees the sender’s note, address, or keys. What remains is to make sure the root belongs to a block on the real Zcash chain and that the block is final, which is the subject of Chain Finality.