Subtree Update Circuit
Details of subtree update circuit
Last updated
Details of subtree update circuit
Last updated
Almost all inputs (listed below) are as defined in the JoinSplit Circuit.
The only special encoding we need for this circuit is to deal with the fact that accumulatorHash
is a 256-bit value, which is not guaranteed to fit in an element.
Another public input for the circuit is the path to the root of the subtree spanning our batch update. The way this is encoded requires a bit more detail about the commitment tree's structure.
Our note commitment tree is quaternary - each branch node has four children, not two. That means the "path" is not a sequence of booleans (left vs right), but a sequence of numbers between 0 and 4. For binary trees, usually people use the fact that the big-endian binary representation of the index of a given leaf is the path - starting at the root, each bit when read left-to-right tells us which way to go. This would be a D - log2(B)
bit unsigned integer, where D
is the depth of the tree and B
is the batch size.
For our quaternary tree, this is almost identical - the only difference is that we read the big-endian bit representation of the index two bits at a time, giving us a sequence of two-bit numbers 0-4 which tell us which child to traverse instead of a sequence of bits. If our tree is depth D
then this will be a 2*D - log2(B)
bit number. In our case, D = 16
and B = 16
, which means our path will take up 28 bits.
This gives us plenty of room so we can pack the 3 most-significant bits of accumulatorHash
into the path's field element. The resulting encoding is as follows:
accumulatorHash
is the element represented by the 253 least-significant bits of the "actual" 256-bit accumulatorHash
encodedPathAndHash
is the element represented by the following bits, from most-significant to least-significant:
254 - (2*D - log2(B)) - 3
0
bits
the 3 most-significant bits of the actual accumulatorHash
the 2*D - log2(B)
bits of the path from the root of the commitment tree to the root of the subtree spanning the batch insertion
The Subtree Update circuit has the following public inputs:
accumulatorHash
- the 253 least-significant bits of the hash at the tip of the accumulatorQueue
on-chain
encodedPathAndHash
- a bit-packed field element containing the path from the root of the commitment tree to the root of the subtree spanning the batch and the 3 most-significant bits of accumulatorHash
.
For notation's sake, I will use encodedPathAndHash.hashBits
to refer to the 3 hash bits, and encodedPathAndHash.path
to refer to the 2*D - log2(B)
path bits
oldRoot
- the old root of the commitment tree
newRoot
- the claimed new root of the commitment tree
We also define the constant EMPTY_SUBTREE_ROOT
, which is the root of the depth-log2(B)
subtree containing B
zeros.
And requires the following private data:
SubtreeMembershipProof
- a depth 2*D - log2(B)
-bit Merkle membership proof against newRoot
whose claimed leaf
is the root of the subtree
EmptySubtreeMembershipProof
- a depth 2*D - log2(B)
-bit Merkle membership proof against oldRoot
whose claimed leaf
is EMPTY_SUBTREE_ROOT.
notes
- a size B
array EncodedNote
for each leaf in the tree. For note commitment insertions, the corresponding entry is ignored. These are encoded as defined in the encodings section.
leaves
- a size B
array containing the leaves of the subtree - that is, the note commitments of every note to be inserted, no matter which insertion case it is.
bitmap
- B
bits indicating which insertions are note insertions and which insertions are note commitment insertions. If bitmap[i] == 1
, then the i
th insertion should be treated as a note
insertion, not a note commitment insertion.
preimage
- a size B*256
bit array containing every insertion digest concatenated together.
We prove that the following conditions hold true, converting to/from big-endian bit representation as needed:
for the i
th bit
of bitmap
:
bit
is 0
or 1
if bit == 1
, then
preimage[i*256..(i+1)*256] = sha256(notes[i])
leaves[i] = noteCommit(note)
, where noteCommit
computes the note commitment of a note.
otherwise, preimage[i*256 + 2..(i+1)*256] = leaves[i]
encodedPathAndHash.hashBits || accumulatorHash = sha256(preimage)
where ||
denotes concatenation
SubtreeMembershipProof.leaf
is the root of the depth-log4(B)
subtree whose leaves are leaves
.
EmptySubtreeMembershipProof.leaf
is the root of an empty depth-log4(B)
subtree.
EmptySubtreeMembershipProof.path
represents the same path as encodedPathAndHash.path
SubtreeMembershipProof.path
represents the same path as encodedPathAndHash.path
EmptySubtreeMembershipProof
is a valid Merkle membership proof against oldRoot
. This asserts that the subtree was empty before the insertion.
SubtreeMembershipProof
is a valid Merkle membership proof against newRoot
. This asserts that newRoot
is the correct root resulting from the subtree update.