Assessment reports>Brevis>High findings>Function ,ConstInt248, allows invalid ranges for arguments, returning incorrect values
Category: Coding Mistakes

Function ConstInt248 allows invalid ranges for arguments, returning incorrect values

High Severity
High Impact
Medium Likelihood

Description

The sdk provides a type Int248 in sdk/api_int248.go for 248-bit signed integers with which the integer range from to can be represented. To represent negative values, two's complement is used.

The function ConstInt248 can be used to instantiate an Int248 from a native big.Int value, and it is implemented as follows:

// ConstInt248 initializes a constant Int248. This function does not generate
// circuit wires and should only be used outside of circuit. The input big int
// can be negative
func ConstInt248(v *big.Int) Int248 {
	if v.BitLen() > 248 {
		panic("cannot initialize Int248 with bit length > 248")
	}

	abs := new(big.Int).Abs(v)
	absBits := decomposeBitsExact(abs)

	if v.Sign() < 0 {
		bits := twosComplement(absBits, 248)
		a := recompose(bits, 1)
		return newI248(a, 1)
	}

	return newI248(abs, 0)
}

Note that the function checks the bit length of v and rejects values with a bit length larger than 248. This check does not account for the sign bit required in two's complement representation and is thereby insufficiently strict.

If v is nonnegative, then the value v is used directly as the returned Int248. Should the value of v be in the range , then the bit length check passes, yet the returned Int248 will be incorrect, as the sign bit will be set, so that the returned value will be interpreted as a negative integer.

An analogous issue is present in the negative case. For negative v, the function decomposes the absolute value of v into bits (in little endian) and passes them to the function twosComplement, and then it recomposes the bits returned by that function.

This function twosComplement is implemented in sdk/utils.go. It takes as arguments bits and n. From its usage in ConstInt248, we can conclude that bits is intended to be interpreted as representing an unsigned integer in little-endian presentation, and the return value should be the bitwise representation of the negation of that integer, represented using two's complement with n bits in total. The function is implemented as follows:

func twosComplement(bits []uint, n int) []uint {
	padded := padBitsRight(bits, n, 0)
	flipped := flipBits(padded)
	a := recompose(flipped, 1)
	a.Add(a, big.NewInt(1))
	d := decomposeAndSlice(a, 1, 248)
	ret := make([]uint, len(d))
	for i, b := range d {
		ret[i] = uint(b.Uint64())
	}
	return ret
	}

The function pads bits to n components using padBitsRight. If bits already has more than n components, then this will panic; see the discussion in section ref. But if bits has length exactly n, then no panic will happen. However, as one bit is needed for the sign, in two's complement with n bits, actually only values of at most n-1 bits width, and the edge case -2^(n-1), can be represented. Except that one edge case, the results will be incorrect.

For example, if bits is of length n and consists of 1, 0, ..., 0, 1, then the bits will be flipped to give 0, 1, ..., 1, 0, and then 1 is added, giving 1, 1, ..., 1, 0. This represents the positive value 2^(n-1) - 1, however, not the intended negative value -2^(n-1) - 1.

There is a single value with n bits for which twosComplement will return the correct two's complement representation of the negation, namely 2^(n-1), or in bits 0, ..., 0, 1. The bits returned by twosComplement will in this case be 0, ..., 0, 1 again, and this does represent the signed integer -2^(n-1) in n bit two's complement representation. On all other inputs with bit length n, the return value will be incorrect, however.

This in turn implies that ConstInt248 will return incorrect values for arguments v in the range -2^n < v < -2^(n-1).

Impact

The function ConstInt248 will return an incorrect Int248 value when it is called with a *big.Int value in one of the following ranges.

  • to

  • to

Recommendations

We recommend changing both ConstInt248 and twosComplement to more strictly check the inputs. Concretely, ConstInt248 should disallow every 248-bit length value as an input, except possibly for , and twosComplement should analogously disallow every length n input except possibly for [0,0,...,0,1].

Remediation

This issue has been acknowledged by Brevis, and fixes were implemented in the following commits:

Zellic © 2025Back to top ↑