Assessment reports>Brevis>Discussion>Behavior of bits2Bytes and addOutput for circuit variables

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.

Zellic © 2025Back to top ↑