Behavior of bits2Bytes
and addOutput
for circuit variables
In the sdk repository, sdk/host_circuit.go includes the following function bits2Bytes
:
func bits2Bytes(data []frontend.Variable) []byte {
var bits []uint
for _, b := range data {
bits = append(bits, uint(fromInterface(b).Int64()))
}
bytes := make([]byte, len(bits)/8)
for i := 0; i < len(bits)/8; i++ {
for j := 0; j < 8; j++ {
bytes[i] += byte(bits[i*8+j] << j)
}
}
return bytes
}
Note that this function calls the fromInterface
function of Finding ref↗ with every component of the argument data
. Currently, this will return zero on components of data
that are of an unsupported type, such as circuit variables. For Finding ref↗, we recommend that fromInterface
panic on unsupported types instead of returning zero. Should this change be implemented, this will result in bits2Bytes
panicking if any of the components of data
is not of a type supported by fromInterface
. Taking into account only bits2Bytes
itself, this is reasonable behavior.
However, bits2Bytes
is called by the following function from sdk/circuit_api.go:
func (api *CircuitAPI) addOutput(bits []variable) {
// the decomposed v bits are little-endian bits. The way evm uses Keccak expects
// the input to be big-endian bytes, but the bits in each byte are little endian
b := flipByGroups(bits, 8)
api.output = append(api.output, b...)
dryRunOutput = append(dryRunOutput, bits2Bytes(b)...)
}
This function will be run as part of the application circuit's Define
for outputs of that circuit. This will both happen for dry runs with native types as well as with circuit variables during construction of the constraint system in order to, for example, generate the verification key.
In the former case, the relevant effect is that dryRunOutput
is updated using bits2Bytes
. This works in that case, as the types that ultimately reach fromInterface
should be native types that it supports.
However, in the latter case, the types will be circuit variables, which fromInterface
does not support. In this case, correct values for dryRunOutput
are not needed, so it is fine that so far bits2Bytes
will return incorrect values.
Should fromInterface
be changed, as recommended in Finding ref↗, then bits2Bytes
and thus addOutput
will panic when run with circuit variables. Because of this, the code would need to be structured differently so as to only call bits2Bytes
when actually in the case of a dry run — but not otherwise.