Data Types for Circuits
In an Axiom client circuit, it is important to distinguish between values that should be constant in the circuit regardless of what inputs it is given, and values that are variable and depend on the inputs to the circuit. We use different data types to distinguish these notions. In particular we have the primitive types ConstantValue
, CircuitValue
, and CircuitValue256
. All circuit inputs must be built from CircuitValue
and CircuitValue256
types to delineate that they are variable and may change with each new proof.
Constant Data Types
export type ConstantValue = string | number | bigint;
Circuit functions can take in the ConstantValue
type. These values are inferred to be fixed and immutable in your circuit. You should use ConstantValue
types when the value should not change based on the inputs to your circuit.
In some editors, IntelliSense will show RawCircuitInput
in function signatures. This is an internal type alias that is the same as ConstantValue
.
Circuit Data Types
There are two protected classes to represent variable values inside a circuit: CircuitValue
and CircuitValue256
.
Due to some specifics of our ZK proving system, a CircuitValue
represents variable values that can be at most 253 bits. Since EVM slots are 256 bits, we've created a CircuitValue256
class that represents 256 bit values in hi
-lo
form (hi
is the most significant 128 bits, lo
is the 128 least significant bits).
CircuitValue
Here are the methods available on a CircuitValue
object:
class CircuitValue {
// converts the CircuitValue to a CircuitValue256 inside the circuit
toCircuitValue256(): CircuitValue256;
// returns the value as a bigint
value(): bigint;
// returns the value as a number
number(): number;
// returns the value as an address string
address(): string;
}
The toCircuitValue256()
function can be used to convert the object from CircuitValue
type to CircuitValue256
type:
toCircuitValue256()
-- constructs a newCircuitValue256
object where the lo field is the originalCircuitValue
and the hi field is loaded as a constant0
.
If necessary, you can cast from ConstantValue
to CircuitValue
by using the constant()
function.
The functions value(), number(), address()
should only be used for logging/debugging purposes and should not be used in any ZK primitives or Axiom subqueries.
value()
-- returns the internal value as a bigintnumber()
-- tries to cast the internal value toNumber
and returns it. Will throw an error if the value is too large to fit in aNumber
.address()
-- converts the internal value to a hex string starting with0x
representing 20 bytes exactly. Will left pad with zeros if the value is less than 20 bytes. Will truncate if the value is greater than 20 bytes.
CircuitValue256
Since EVM slots are 256 bits, we've created a CircuitValue256
class that represents 256 bit values in hi
-lo
form (hi
is the most significant 128 bits, lo
is the 128 least significant bits).
Here are the methods available on a CircuitValue256
object.
class CircuitValue256 {
// returns the `hi` CircuitValue
hi(): CircuitValue;
// returns the `lo` CircuitValue
lo(): CircuitValue;
// constrains that the CircuitValue256 can fit in 253 bits
// and constrains out = 2**128 * hi + lo
toCircuitValue(): CircuitValue;
// returns the value as a bigint
value(): bigint;
// returns the value as a hex string
hex(): string;
}
We have the following functions that return CircuitValue
:
hi()
-- returns thehi
field of theCircuitValue256
object, wherehi
refers to the most significant 128 bits of the value.lo()
-- returns thelo
field of theCircuitValue256
object, wherelo
refers to the least significant 128 bits of the value.toCircuitValue()
-- uses ZK primitives to constrain that theCircuitValue256
has at most 253 bits and then computes and returns2**128 * hi + lo
as aCircuitValue
.
By default all Axiom Subqueries return a CircuitValue256
. To use these results in other ZK primitives that take CircuitValue
s, you must call .toCircuitValue()
. If you know that the data you are querying is less than 253 bits (ie. block number, address, uint128, etc.), this is totally safe. For values which may overflow 253 bits (such as storage slots, bytes32), your proof will fail to verify if the value actually overflows (in particular, if the hi CircuitValue
exceeds 125 bits).
The functions value(), hex()
should only be used for logging/debugging purposes and should not be used in any ZK primitives or Axiom subqueries.
value()
-- returns the internal value as a biginthex()
-- returns the internal value as a hex string starting with0x
representing 32 bytes exactly. Will left pad with zeros if the value is less than 32 bytes.