Assessment reports>Brevis>Informational findings>Inconsistency in behavior of type conversions
Category: Coding Mistakes

Inconsistency in behavior of type conversions

Informational Severity
Informational Impact
N/A Likelihood

Description

The sdk provides a number of different types. To convert between them, sdk/circuit_api.go provides several conversion functions. Converting between types often involves choices; if the target type cannot represent parts of the range of the source type, should conversion fail for values outside of the target range, or should the source value be truncated and if so, how?

The following is the implementation of the ToUint248 function:

// ToUint248 casts the input to a Uint248 type. Supports Uint32, Uint64, Uint248, Int248,
// Bytes32, and Uint521
func (api *CircuitAPI) ToUint248(i interface{}) Uint248 {
	switch v := i.(type) {
	case Uint248:
		return v
	case Int248:
		return newU248(v.Val)
	case Uint32:
		return newU248(v.Val)
	case Uint64:
		return newU248(v.Val)
	case Bytes32:
		api.g.AssertIsEqual(v.Val[1], 0)
		return newU248(v.Val[0])
	case Uint521:
		max248 := ConstUint521(MaxUint248)
		api.Uint521.AssertIsLessOrEqual(v, max248)
		bits := api.Uint521.ToBinary(v, numBitsPerVar)
		return api.Uint248.FromBinary(bits[:numBitsPerVar]...)
	}
	panic(fmt.Errorf("unsupported casting from %T to Uint248", i))
}

As seen here, for the source type Bytes32, the choice is to enforce that the source value was in the representable range; the 248 bits of the Bytes32 that are stored in Val[0] are used for the Uint248, and it is asserted that the remaining eight bits in Val[1] are all zero. Similarly, in the case of source type Uint521, it is asserted that the value is in the range representable in 248 bits.

In contrast, for source type Int248, the underlying value is used directly. This results in a wraparound modulo , which changes negative values. An alternative, which would be more in line with how the previously mentioned two types are treated, would be to assert that the original Int248 was not negative.

The analogous behavior occurs in the ToInt248 function when converting the other way:

func (api *CircuitAPI) ToInt248(i interface{}) Int248 {
	switch v := i.(type) {
    // ...
    case Uint248:
		return newI248(v.Val)
    }
    // ...
}

Here, values that are or bigger will be reinterpreted by subtracting . An alternative behavior would be to range check the value to be smaller than .

Impact

The behavior of type-conversion functions when the source value is not representable in the target value is not documented, with the implementation making inconsistent choices depending on the type. This risks users making incorrect assumptions about the behavior of these functions.

Recommendations

We recommend to document the behavior of the various type conversions. It could also be considered whether the conversions pointed out above should assert the source value to be in the range representable by the target type.

Remediation

In , documentations comments were added to clarify the implemented behavior of conversion between Uint248 and Int248.

Zellic © 2025Back to top ↑