Swapping Two Items in an Array in Circom

This chapter shows how to swap two signals in a list of signals. This is an important subroutine for a sorting algorithm. More generally, lists are a fundamental building block for more interesting functions like hash functions or modeling memory in a CPU, so we must learn how to update their values.

Swapping two items in a list is one of the first things programmers learn, a typical solution looks like the following:

# s and t are indexes of array arr
def swap(arr, s, t):
  temp = arr[s];
  arr[s] = arr[t];
  arr[t] = temp;
  return arr

However, in a ZK circuit, this operation can be surprisingly tricky.

  • First, we cannot directly index an array of signals. For that, we need to use a Quin selector.
  • Second, we cannot “write to” a signal in an array of signals because signals are immutable.

Instead, we need to create a new array and copy the old values to the new array, subject to the following conditions:

  • If we are at index s, write the value at arr[t]
  • If we are at index t, write the value at arr[s]
  • Otherwise, write the original value

Every write we make to the new array is a conditional operation.

The Quin selector was explained in a prior chapter — we won’t replicate the code here to save space.

Swap in Circom

The component below will swap the item at index s with the item at index t and return a new array. (The following code has a bug, try to find it! The answer is given later.)

template Swap(n) {
  signal input in[n];
  signal input s;
  signal input t;
  signal output out[n];

  // we do not check that
  // s < n or t < n
  // because the Quin selector
  // does that

  // get the value at s
  component qss = QuinSelector(n);
  qss.idx <== s;
  for (var i = 0; i < n; i++) {
    qss.in[i] <== in[i];
  }

  // get the value at t
  component qst = QuinSelector(n);
  qst.idx <== t;
  for (var i = 0; i < n; i++) {
    qst.in[i] <== in[i];
  }

  // qss.out holds in[s]
  // qst.out holds in[t]

  component IdxEqS[n];
  component IdxEqT[n];
  component IdxNorST[n];
  signal branchS[n];
  signal branchT[n];
  signal branchNorST[n];
  for (var i = 0; i < n; i++) {
    IdxEqS[i] = IsEqual();
    IdxEqS[i].in[0] <== i;
    IdxEqS[i].in[1] <== s;

    IdxEqT[i] = IsEqual();
    IdxEqT[i].in[0] <== i;
    IdxEqT[i].in[1] <== t;

    // if IdxEqS[i].out + IdxEqT[i].out
    // equals 0, then it is not i ≠ s and i ≠ t
    IdxNorST[i] = IsZero();
    IdxNorST[i].in <== IdxEqS[i].out + IdxEqT[i].out;

    // if we are at index s,
    // write in[t]
    // if we are at index t,
    // write in[s]
    // else write in[i]
    branchS[i] <== IdxEqS[i].out * qst.out;
    branchT[i] <== IdxEqT[i].out * qss.out;
    branchNorST[i] <== IdxNorST[i].out * in[i];
    out[i] <==  branchS[i] + branchT[i] + branchNorST[i];
  }
}

Note that the final conditional statement

branchS[i] <== IdxEqS[i].out * qst.out;
branchT[i] <== IdxEqT[i].out * qss.out;
branchNorST[i] <== IdxNorST[i].out * in[i];
out[i] <==  branchS[i] + branchT[i] + branchNorST[i];

cannot be written as

out[i] <==  IdxEqS[i].out * qst.out + IdxEqT[i].out * qss.out + IdxNorST[i].out * in[i]

because that would produce a non-quadratic constraints error (there is more than one multiplication in the constraint).

Catch the bug

There is a bug in the code above — can you catch it before scrolling down?

The bug in the code

The problem with the code above is that it doesn’t account for the fact that the value at s might equal the value at t (s == t). In that circumstance, the value written to the index will be the value at that index added to itself.

Fixing the problem

To prevent this, we need to explicitly detect if s == t and multiply one of either branchS or branchT by zero to avoid doubling the value. In other words, if the switches for s and t are both active, then the resulting value would be s + t. But we don’t want that, we want the value to effectively remain unchanged by selecting branchS or branchT arbitrarily (they will have the same value):

template Swap(n) {
  signal input in[n];
  signal input s;
  signal input t;
  signal output out[n];

  // NEW CODE to detect if s == t
  signal sEqT;
  sEqT <== IsEqual()([s, t]);

  // get the value at s
  component qss = QuinSelector(n);
  qss.idx <== s;
  for (var i = 0; i < n; i++) {
    qss.in[i] <== in[i];
  }

  // get the value at t
  component qst = QuinSelector(n);
  qst.idx <== t;
  for (var i = 0; i < n; i++) {
    qst.in[i] <== in[i];
  }

  component IdxEqS[n];
  component IdxEqT[n];
  component IdxNorST[n];
  signal branchS[n];
  signal branchT[n];
  signal branchNorST[n];
  for (var i = 0; i < n; i++) {
    IdxEqS[i] = IsEqual();
    IdxEqS[i].in[0] <== i;
    IdxEqS[i].in[1] <== s;

    IdxEqT[i] = IsEqual();
    IdxEqT[i].in[0] <== i;
    IdxEqT[i].in[1] <== t;

    // if IdxEqS[i].out + IdxEqT[i].out
    // equals 0, then it is not i ≠ s and i ≠ t
    IdxNorST[i] = IsZero();
    IdxNorST[i].in <== IdxEqS[i].out + IdxEqT[i].out;

    // if we are at index s, write in[t]
    // if we are at index t, write in[s]
    // else write in[i]
    branchS[i] <== IdxEqS[i].out * qst.out;
    branchT[i] <== IdxEqT[i].out * qss.out;
    branchNorST[i] <== IdxNorST[i].out * in[i];

    // multiply branchS by zero if s equals T
    out[i] <==  (1-sEqT) * (branchS[i]) + branchT[i] + branchNorST[i];
  }
}

Conclusion

Any array manipulation in Circom requires creating a new array and copying the old values to the new one, except where the update happens.

By using this pattern in a loop, we can do things like sort a list, model data structures like stacks and queues, and even change the state of a CPU or VM. We will see examples of those in the following chapters.

ZK Proof of Selection Sort

ZK Proof of Selection Sort Most computations of interest are generally “stateful” — that is, they need to go through a series of steps to produce the final result. Sometimes, we do not need to show we executed the computation but only show the result. For example, if A is a list, we can prove […]

How a ZKVM Works

How a ZKVM Works A Zero-Knowledge Virtual Machine (ZKVM) is a virtual machine that can create a ZK-proof that verifies it executed a set of machine instructions correctly. This allows us to take a program (a set of opcodes), a virtual machine specification (how the virtual machine behaves, what opcodes it uses, etc), and prove […]

The Permutation Argument

The Permutation Argument A permutation argument is a proof that two lists hold the same elements, but possibly in a different order. For example, [2,3,1] is a permutation of [1,2,3] and vice-versa. The permutation argument is useful for proving one list is a sorted version of another. That is, if list B has the same […]

ZK Friendly Hash Functions

ZK Friendly Hash Functions ZK-friendly hash functions are hash functions that require much fewer constraints to prove and verify than traditional cryptographic hash functions. Hash functions such as SHA256 or keccak256 make heavy use of bitwise operators such as XOR or bit rotation. Proving the correct execution of XOR or bit rotation requires representing the […]

Featured Jobs

RareSkills Researcher

As a RareSkills researcher, you will be contributing to the technical content we post on our website.

Apply Now
Rust/Solana Auditor

We’re looking for someone to design and implement security measures and defense-in-depth controls to prevent and limit vulnerabilities.

Apply Now
Full Stack Developer

We’re looking for a Senior Full-Stack Engineer to play a foundational role in working across the entire offchain stack of products.

Apply Now
Rust Developer

We are seeking a talented Rust Developer to build a robust, scalable blockchain indexers and analytic backend.

Apply Now