Skip to main content

Understanding the implementation of the f(ζ)=ZH(ζ)t(ζ)f(\zeta) = Z_H(\zeta) t(\zeta) check

Unlike the latest version of vanilla PlonK\plonk that implements the final check using a polynomial opening (via Maller's optimization), we implement it manually. (That is to say, Izaak implemented Maller's optimization for 5-wires.)

But the check is not exactly f(ζ)=ZH(ζ)t(ζ)f(\zeta) = Z_H(\zeta) t(\zeta). This note describes how and why the implementation deviates a little.

What's f and what's missing in the final equation?

If you look at how the evaluation of f(z)f(z) is computed on the prover side (or the commitment of ff is computed on the verifier side), you can see that f is missing two things:

  • the public input part
  • some terms in the permutation

What is it missing in the permutation part? Let's look more closely. This is what we have:

1z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)βσ[6](x)\begin{aligned} -1\, \cdot\, &z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ \cdot\, &(w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ \cdot\, &(w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ \cdot\, &(w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ \cdot\, &(w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ \cdot\, &(w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ \cdot\, &(w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ \cdot\, &\beta \cdot \sigma[6](x) \end{aligned}

In comparison, here is the list of the stuff we needed to have:

  1. the two sides of the coin: z(ζ)zkpm(ζ)αPERM0(w[0](ζ)+βshift[0](ζ)+γ)(w[1](ζ)+βshift[1](ζ)+γ)(w[2](ζ)+βshift[2](ζ)+γ)(w[3](ζ)+βshift[3](ζ)+γ)(w[4](ζ)+βshift[4](ζ)+γ)(w[5](ζ)+βshift[5](ζ)+γ)(w[6](ζ)+βshift[6](ζ)+γ)\begin{aligned} z(\zeta)\, \cdot\, &\mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ \cdot\, &(w[0](\zeta) + \beta \cdot \mathsf{shift}[0](\zeta) + \gamma) \\ \cdot\, &(w[1](\zeta) + \beta \cdot \mathsf{shift}[1](\zeta) + \gamma) \\ \cdot\, &(w[2](\zeta) + \beta \cdot \mathsf{shift}[2](\zeta) + \gamma) \\ \cdot\, &(w[3](\zeta) + \beta \cdot \mathsf{shift}[3](\zeta) + \gamma) \\ \cdot\, &(w[4](\zeta) + \beta \cdot \mathsf{shift}[4](\zeta) + \gamma) \\ \cdot\, &(w[5](\zeta) + \beta \cdot \mathsf{shift}[5](\zeta) + \gamma) \\ \cdot\, &(w[6](\zeta) + \beta \cdot \mathsf{shift}[6](\zeta) + \gamma) \end{aligned} with shift[0]=1\mathsf{shift}[0] = 1
  2. and 1z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+βσ[6](ζ)+γ)\begin{aligned} -1\, \cdot\, &z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ \cdot\, &(w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ \cdot\, &(w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ \cdot\, &(w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ \cdot\, &(w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ \cdot\, &(w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ \cdot\, &(w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ \cdot\, &(w[6](\zeta) + \beta \cdot \sigma[6](\zeta) + \gamma) \end{aligned}
  3. the initialization: (z(ζ)1)L1(ζ)αPERM1(z(\zeta) - 1) \cdot L_1(\zeta) \cdot \alpha^{\mathsf{PERM1}}
  4. and the end of the accumulator: (z(ζ)1)Lnk(ζ)αPERM2(z(\zeta) - 1) \cdot L_{n-k}(\zeta) \cdot \alpha^{\mathsf{PERM2}}

You can read more about why it looks like that in this post.

We can see clearly that the permutation stuff we have in f is clearly lacking. It's just the subtraction part (equation 2), and even that is missing some terms. It is missing exactly this:

1 z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+γ)\begin{aligned} -1\ \cdot\, &z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ \cdot\, &(w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ \cdot\, &(w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ \cdot\, &(w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ \cdot\, &(w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ \cdot\, &(w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ \cdot\, &(w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ \cdot\, &(w[6](\zeta) + \gamma) \end{aligned}

So at the end, when we have to check for the identity f(ζ)=ZH(ζ)t(ζ)f(\zeta) = Z_H(\zeta) t(\zeta) we'll actually have to check something like this (I colored the missing parts on the left-hand side of the equation):

f(ζ)+pub(ζ)+z(ζ)zkpm(ζ)αPERM0(w[0](ζ)+βζ+γ)(w[1](ζ)+βshift[0](ζ)+γ)(w[2](ζ)+βshift[1](ζ)+γ)(w[3](ζ)+βshift[2](ζ)+γ)(w[4](ζ)+βshift[3](ζ)+γ)(w[5](ζ)+βshift[4](ζ)+γ)(w[6](ζ)+βshift[5](ζ)+γ)z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+γ)+(z(ζ)1)L1(ζ)αPERM1+(z(ζ)1)Lnk(ζ)αPERM2=t(ζ)(ζn1)\begin{aligned} f(\zeta) &+ \color{darkgreen}{\mathsf{pub}(\zeta)} \\ & \color{darkred}{+ z(\zeta) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}}} \\ & \qquad \color{darkred}{\cdot (w[0](\zeta) + \beta \zeta + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[1](\zeta) + \beta \cdot \mathsf{shift}[0](\zeta) + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[2](\zeta) + \beta \cdot \mathsf{shift}[1](\zeta) + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[3](\zeta) + \beta \cdot \mathsf{shift}[2](\zeta) + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[4](\zeta) + \beta \cdot \mathsf{shift}[3](\zeta) + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[5](\zeta) + \beta \cdot \mathsf{shift}[4](\zeta) + \gamma)} \\ & \qquad \color{darkred}{\cdot (w[6](\zeta) + \beta \cdot \mathsf{shift}[5](\zeta) + \gamma)} \\ & \color{blue}{- z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \cdot} \\ & \qquad \color{blue}{\cdot (w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \cdot} \\ & \qquad \color{blue}{\cdot (w[6](\zeta) + \gamma)} \\ & \color{purple}{+ (z(\zeta) - 1) \cdot L_1(\zeta) \cdot \alpha^{\mathsf{PERM1}}} \\ & \color{darkblue}{+ (z(\zeta) - 1) \cdot L_{n-k}(\zeta) \cdot \alpha^{\mathsf{PERM2}}} \\ & = t(\zeta)(\zeta^n - 1) \end{aligned}

This is not exactly what happens in the code. But if we move things around a bit, we end up with what's implemented. I highlight what changes in each steps. First, we just move things around:

f(ζ)+pub(ζ)+z(ζ)zkpm(ζ)αPERM0(w[0](ζ)+βshift[0]ζ+γ)(w[1](ζ)+βshift[1]ζ+γ)(w[2](ζ)+βshift[2]ζ+γ)(w[3](ζ)+βshift[3]ζ+γ)(w[4](ζ)+βshift[4]ζ+γ)(w[5](ζ)+βshift[5]ζ+γ)(w[6](ζ)+βshift[6]ζ+γ)z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+γ)t(ζ)(ζn1)=(1z(ζ))L1(ζ)αPERM1+(1z(ζ))Lnk(ζ)αPERM2\begin{aligned} f(\zeta) &+ \mathsf{pub}(\zeta) \\ &+ z(\zeta) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ & \qquad \cdot (w[0](\zeta) + \beta \cdot \mathsf{shift}[0] \zeta + \gamma) \\ & \qquad \cdot (w[1](\zeta) + \beta \cdot \mathsf{shift}[1] \zeta + \gamma) \\ & \qquad \cdot (w[2](\zeta) + \beta \cdot \mathsf{shift}[2] \zeta + \gamma) \\ & \qquad \cdot (w[3](\zeta) + \beta \cdot \mathsf{shift}[3] \zeta + \gamma) \\ & \qquad \cdot (w[4](\zeta) + \beta \cdot \mathsf{shift}[4] \zeta + \gamma) \\ & \qquad \cdot (w[5](\zeta) + \beta \cdot \mathsf{shift}[5] \zeta + \gamma) \\ & \qquad \cdot (w[6](\zeta) + \beta \cdot \mathsf{shift}[6] \zeta + \gamma) \\ &- z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \cdot \\ & \qquad \cdot (w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ & \qquad \cdot (w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ & \qquad \cdot (w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ & \qquad \cdot (w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ & \qquad \cdot (w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ & \qquad \cdot (w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ & \qquad \cdot (w[6](\zeta) + \gamma) \\ &\color{darkred}{- t(\zeta)(\zeta^n - 1)} \\ &= \color{darkred}{(1 - z(\zeta)) L_1(\zeta) \alpha^{\mathsf{PERM1}}} \\ & \qquad \color{darkred}{+ (1 - z(\zeta)) L_{n-k}(\zeta) \alpha^{\mathsf{PERM2}}} \\ \end{aligned}

here are the actual lagrange basis calculated with the formula here, oh and we actually use L0L_0 in the code, not L1L_1, so let's change that as well:

f(ζ)+pub(ζ)+z(ζ)zkpm(ζ)αPERM0(w[0](ζ)+βshift[0]ζ+γ)(w[1](ζ)+βshift[1]ζ+γ)(w[2](ζ)+βshift[2]ζ+γ)(w[3](ζ)+βshift[3]ζ+γ)(w[4](ζ)+βshift[4]ζ+γ)(w[5](ζ)+βshift[5]ζ+γ)(w[6](ζ)+βshift[6]ζ+γ)+z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+γ)+t(ζ)(ζn1)=(1z(ζ))[(ζn1)n(ζ1)αPERM1+ωnk(ζn1)n(ζωnk)αPERM2]\begin{aligned} f(\zeta) + &\mathsf{pub}(\zeta) \\ + & z(\zeta) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ & \quad \cdot (w[0](\zeta) + \beta \cdot \mathsf{shift}[0] \zeta + \gamma) \\ & \quad \cdot (w[1](\zeta) + \beta \cdot \mathsf{shift}[1] \zeta + \gamma) \\ & \quad \cdot (w[2](\zeta) + \beta \cdot \mathsf{shift}[2] \zeta + \gamma) \\ & \quad \cdot (w[3](\zeta) + \beta \cdot \mathsf{shift}[3] \zeta + \gamma) \\ & \quad \cdot (w[4](\zeta) + \beta \cdot \mathsf{shift}[4] \zeta + \gamma) \\ & \quad \cdot (w[5](\zeta) + \beta \cdot \mathsf{shift}[5] \zeta + \gamma) \\ & \quad \cdot (w[6](\zeta) + \beta \cdot \mathsf{shift}[6] \zeta + \gamma) + \\ - & z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ & \quad \cdot (w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ & \quad \cdot (w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ & \quad \cdot (w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ & \quad \cdot (w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ & \quad \cdot (w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ & \quad \cdot (w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ & \quad \cdot (w[6](\zeta) + \gamma) + \\ - & t(\zeta)(\zeta^n - 1) \\ = & \color{darkred}{(1 - z(\zeta))[\frac{(\zeta^n - 1)}{n(\zeta - 1)} \alpha^{\mathsf{PERM1}} + \frac{\omega^{n-k}(\zeta^n - 1)}{n(\zeta - \omega^{n-k})} \alpha^{\mathsf{PERM2}}]} \end{aligned}

finally we extract some terms from the lagrange basis:

[f(ζ)+pub(ζ)+z(ζ)zkpm(ζ)αPERM0(w[0](ζ)+βshift[0]ζ+γ)(w[1](ζ)+βshift[1]ζ+γ)(w[2](ζ)+βshift[2]ζ+γ)(w[3](ζ)+βshift[3]ζ+γ)(w[4](ζ)+βshift[4]ζ+γ)(w[5](ζ)+βshift[5]ζ+γ)(w[6](ζ)+βshift[6]ζ+γ)+z(ζω)zkpm(ζ)αPERM0(w[0](ζ)+βσ[0](ζ)+γ)(w[1](ζ)+βσ[1](ζ)+γ)(w[2](ζ)+βσ[2](ζ)+γ)(w[3](ζ)+βσ[3](ζ)+γ)(w[4](ζ)+βσ[4](ζ)+γ)(w[5](ζ)+βσ[5](ζ)+γ)(w[6](ζ)+γ)t(ζ)(ζn1)](ζ1)(ζωnk)=(1z(ζ))[(ζn1)(ζωnk)nαPERM1+ωnk(ζn1)(ζ1)nαPERM2]\begin{aligned} & \color{darkred}{[} \\ & f(\zeta) + \mathsf{pub}(\zeta) \\ & + z(\zeta) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ & \qquad \cdot (w[0](\zeta) + \beta \cdot \mathsf{shift}[0] \zeta + \gamma) \\ & \qquad \cdot (w[1](\zeta) + \beta \cdot \mathsf{shift}[1] \zeta + \gamma) \\ & \qquad \cdot (w[2](\zeta) + \beta \cdot \mathsf{shift}[2] \zeta + \gamma) \\ & \qquad \cdot (w[3](\zeta) + \beta \cdot \mathsf{shift}[3] \zeta + \gamma) \\ & \qquad \cdot (w[4](\zeta) + \beta \cdot \mathsf{shift}[4] \zeta + \gamma) \\ & \qquad \cdot (w[5](\zeta) + \beta \cdot \mathsf{shift}[5] \zeta + \gamma) \\ & \qquad \cdot (w[6](\zeta) + \beta \cdot \mathsf{shift}[6] \zeta + \gamma) + \\ & - z(\zeta \omega) \cdot \mathsf{zkpm}(\zeta) \cdot \alpha^{\mathsf{PERM0}} \\ & \qquad \cdot (w[0](\zeta) + \beta \cdot \sigma[0](\zeta) + \gamma) \\ & \qquad \cdot (w[1](\zeta) + \beta \cdot \sigma[1](\zeta) + \gamma) \\ & \qquad \cdot (w[2](\zeta) + \beta \cdot \sigma[2](\zeta) + \gamma) \\ & \qquad \cdot (w[3](\zeta) + \beta \cdot \sigma[3](\zeta) + \gamma) \\ & \qquad \cdot (w[4](\zeta) + \beta \cdot \sigma[4](\zeta) + \gamma) \\ & \qquad \cdot (w[5](\zeta) + \beta \cdot \sigma[5](\zeta) + \gamma) \\ & \qquad \cdot (w[6](\zeta) + \gamma) \\ & - t(\zeta)(\zeta^n - 1) \\ & \color{darkred}{] \cdot (\zeta - 1)(\zeta - \omega^{n-k})} & \\ & = \color{darkred}{(1 - z(\zeta))\Bigg[\frac{(\zeta^n - 1)(\zeta - \omega^{n-k})}{n} \alpha^{\mathsf{PERM1}} + \frac{\omega^{n-k}(\zeta^n - 1)(\zeta - 1)}{n} \alpha^{\mathsf{PERM2}}\Bigg]} \end{aligned}

with αPERM0=α17,αPERM1=α18,αPERM2=α19\alpha^{\mathsf{PERM0}} = \alpha^{17}, \alpha^{\mathsf{PERM1}} = \alpha^{18}, \alpha^{\mathsf{PERM2}} = \alpha^{19}

Why do we do things this way? Most likely to reduce

Also, about the code:

  • the division by nn in the αPERM1\alpha^{\mathsf{PERM1}} and the αPERM2\alpha^{\mathsf{PERM2}} terms is missing (why?)
  • the multiplication by wnkw^{n-k} in the αPERM2\alpha^{\mathsf{PERM2}} term is missing (why?)

As these terms are constants, and do not prevent the division by ZHZ_H to form tt, tt also omits them. In other word, they cancel one another.

What about tt?

In verifier.rs you can see the following code (cleaned to remove anything not important)

// compute the witness polynomials $w_0, \cdots, w_14$ and the permutation polynomial $z$ in evaluation forms on different domains
let lagrange = index.cs.evaluate(&w, &z);

// compute parts of the permutation stuff that is included in t
let (perm, bnd) = index.cs.perm_quot(&lagrange, &oracles, &z, &alpha[range::PERM])?;

// divide contributions with vanishing polynomial
let (mut t, res) = (perm + some_other_stuff).divide_by_vanishing_poly()

// add the other permutation stuff
t += &bnd;

// t is evaluated at zeta and sent...

Notice that bnd is not divided by the vanishing polynomial. Also what's perm? Let's answer the latter TESTREMOVEME:

perm(x)= aPERM0zkpl(x)[z(x)(w[0](x)+γ+xβshift[0])(w[1](x)+γ+xβshift[1])z(xw)(w[0](x)+γ+σ[0]β)  (w[1](x)+γ+σ[1]β)]\begin{aligned} \mathsf{perm}(x) =\ & a^{\mathsf{PERM0}} \cdot \mathsf{zkpl}(x) \cdot [ \\ & z(x) \cdot (w[0](x) + \gamma + x \cdot \beta \cdot \mathsf{shift}[0]) \\ & \qquad \cdot (w[1](x) + \gamma + x \cdot \beta \cdot \mathsf{shift}[1]) \cdot \ldots \\ - &z(x \cdot w) \cdot (w[0](x) + \gamma + \sigma[0] \cdot \beta) \\ & \qquad \quad \ \ \cdot (w[1](x) + \gamma + \sigma[1] \cdot \beta) \cdot \ldots \\ &] \end{aligned}

and bnd is:

bnd(x)=aPERM1z(x)1x1+aPERM2z(x)1xsid[nk]\mathsf{bnd}(x) = a^{\mathsf{PERM1}} \cdot \frac{z(x) - 1}{x - 1} + a^{\mathsf{PERM2}} \cdot \frac{z(x) - 1}{x - \mathsf{sid}[n-k]}

you can see that some terms are missing in bnd, specifically the numerator xn1x^n - 1. Well, it would have been cancelled by the division by the vanishing polynomial, and this is the reason why we didn't divide that term by ZH(x)=xn1Z_H(x) = x^n - 1.

Also, note that the same constant terms that were missing in ff are missing in tt.