o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
1,606 lines (1,478 loc) • 52.1 kB
text/typescript
import { Field, Bool } from './wrapped.js';
import { AnyConstructor, Struct } from './types/struct.js';
import { Types } from '../../bindings/mina-transaction/types.js';
import * as TypesBigint from '../../bindings/mina-transaction/transaction-leaves-bigint.js';
import { HashInput } from './crypto/poseidon.js';
import { Provable } from './provable.js';
import * as RangeCheck from './gadgets/range-check.js';
import * as Bitwise from './gadgets/bitwise.js';
import { addMod32 } from './gadgets/arithmetic.js';
import type { Gadgets } from './gadgets/gadgets.js';
import { withMessage } from './field.js';
import { FieldVar } from './core/fieldvar.js';
import { CircuitValue, prop } from './types/circuit-value.js';
import {
assertLessThanGeneric,
assertLessThanOrEqualGeneric,
lessThanGeneric,
lessThanOrEqualGeneric,
} from './gadgets/comparison.js';
import { assert } from '../util/assert.js';
// external API
export { UInt8, UInt32, UInt64, Int64, Sign };
/**
* A 64 bit unsigned integer with values ranging from 0 to 18,446,744,073,709,551,615.
*/
class UInt64 extends CircuitValue {
@prop value: Field;
static NUM_BITS = 64;
/**
* Create a {@link UInt64}.
* The max value of a {@link UInt64} is `2^64 - 1 = UInt64.MAXINT()`.
*
* **Warning**: Cannot overflow, an error is thrown if the result is greater than UInt64.MAXINT()
*/
constructor(x: UInt64 | UInt32 | FieldVar | number | string | bigint) {
if (x instanceof UInt64 || x instanceof UInt32) x = x.value.value;
let value = Field(x);
super(value);
// check the range if the argument is a constant
UInt64.checkConstant(value);
}
static Unsafe = {
/**
* Create a {@link UInt64} from a {@link Field} without constraining its range.
*
* **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 64 bits.\
* Only use this if you know what you are doing, otherwise use the safe {@link UInt64.from}.
*/
fromField(x: Field) {
return new UInt64(x.value);
},
};
/**
* Static method to create a {@link UInt64} with value `0`.
*/
static get zero() {
return new UInt64(0);
}
/**
* Static method to create a {@link UInt64} with value `1`.
*/
static get one() {
return new UInt64(1);
}
/**
* Turns the {@link UInt64} into a string.
* @returns
*/
toString() {
return this.value.toString();
}
/**
* Turns the {@link UInt64} into a {@link BigInt}.
* @returns
*/
toBigInt() {
return this.value.toBigInt();
}
/**
* Turns the {@link UInt64} into a {@link UInt32}, asserting that it fits in 32 bits.
*/
toUInt32() {
let uint32 = new UInt32(this.value.value);
UInt32.check(uint32);
return uint32;
}
/**
* Turns the {@link UInt64} into a {@link UInt32}, clamping to the 32 bits range if it's too large.
* ```ts
* UInt64.from(4294967296).toUInt32Clamped().toString(); // "4294967295"
* ```
*/
toUInt32Clamped() {
let max = (1n << 32n) - 1n;
let field = Provable.if(
this.greaterThan(UInt64.from(max)),
Field.from(max),
this.value
);
return UInt32.Unsafe.fromField(field);
}
static check(x: UInt64) {
RangeCheck.rangeCheckN(UInt64.NUM_BITS, x.value);
}
static toInput(x: UInt64): HashInput {
return { packed: [[x.value, 64]] };
}
/**
* Encodes this structure into a JSON-like object.
*/
static toJSON(x: UInt64) {
return x.value.toString();
}
/**
* Decodes a JSON-like object into this structure.
*/
static fromJSON<T extends AnyConstructor>(x: string): InstanceType<T> {
return this.from(x) as any;
}
private static checkConstant(x: Field) {
if (!x.isConstant()) return x;
let xBig = x.toBigInt();
if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) {
throw Error(
`UInt64: Expected number between 0 and 2^64 - 1, got ${xBig}`
);
}
return x;
}
/**
* Creates a new {@link UInt64}.
*/
static from(x: UInt64 | UInt32 | number | string | bigint) {
if (x instanceof UInt64) return x;
return new this(x);
}
/**
* Creates a {@link UInt64} with a value of 18,446,744,073,709,551,615.
*/
static MAXINT() {
return new UInt64((1n << 64n) - 1n);
}
/**
* Integer division with remainder.
*
* `x.divMod(y)` returns the quotient and the remainder.
*/
divMod(y: UInt64 | number | string) {
let x = this.value;
let y_ = UInt64.from(y).value;
if (this.value.isConstant() && y_.isConstant()) {
let xn = x.toBigInt();
let yn = y_.toBigInt();
let q = xn / yn;
let r = xn - q * yn;
return {
quotient: new UInt64(q),
rest: new UInt64(r),
};
}
y_ = y_.seal();
let q = Provable.witness(
Field,
() => new Field(x.toBigInt() / y_.toBigInt())
);
RangeCheck.rangeCheckN(UInt64.NUM_BITS, q);
// TODO: Could be a bit more efficient
let r = x.sub(q.mul(y_)).seal();
RangeCheck.rangeCheckN(UInt64.NUM_BITS, r);
let r_ = new UInt64(r.value);
let q_ = new UInt64(q.value);
r_.assertLessThan(new UInt64(y_.value));
return { quotient: q_, rest: r_ };
}
/**
* Integer division.
*
* `x.div(y)` returns the floor of `x / y`, that is, the greatest
* `z` such that `z * y <= x`.
*
*/
div(y: UInt64 | number) {
return this.divMod(y).quotient;
}
/**
* Integer remainder.
*
* `x.mod(y)` returns the value `z` such that `0 <= z < y` and
* `x - z` is divisible by `y`.
*/
mod(y: UInt64 | number) {
return this.divMod(y).rest;
}
/**
* Multiplication with overflow checking.
*/
mul(y: UInt64 | number) {
let z = this.value.mul(UInt64.from(y).value);
RangeCheck.rangeCheckN(UInt64.NUM_BITS, z);
return new UInt64(z.value);
}
/**
* Addition with overflow checking.
*/
add(y: UInt64 | number) {
let z = this.value.add(UInt64.from(y).value);
RangeCheck.rangeCheckN(UInt64.NUM_BITS, z);
return new UInt64(z.value);
}
/**
* Subtraction with underflow checking.
*/
sub(y: UInt64 | number) {
let z = this.value.sub(UInt64.from(y).value);
RangeCheck.rangeCheckN(UInt64.NUM_BITS, z);
return new UInt64(z.value);
}
/**
* Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR).
* A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal.
*
* This gadget builds a chain of XOR gates recursively.
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1)
*
* @param x {@link UInt64} element to XOR.
*
* @example
* ```ts
* let a = UInt64.from(0b0101);
* let b = UInt64.from(0b0011);
*
* let c = a.xor(b);
* c.assertEquals(0b0110);
* ```
*/
xor(x: UInt64) {
return new UInt64(Bitwise.xor(this.value, x.value, UInt64.NUM_BITS).value);
}
/**
* Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise
* NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/
* Web/JavaScript/Reference/Operators/Bitwise_NOT).
*
* **Note:** The NOT gate operates over 64 bit for UInt64 types.
*
* A NOT gate works by returning `1` in each bit position if the
* corresponding bit of the operand is `0`, and returning `0` if the
* corresponding bit of the operand is `1`.
*
* NOT is implemented as a subtraction of the input from the all one bitmask
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not)
*
* @example
* ```ts
* // NOTing 4 bits with the unchecked version
* let a = UInt64.from(0b0101);
* let b = a.not(false);
*
* console.log(b.toBigInt().toString(2));
* // 1111111111111111111111111111111111111111111111111111111111111010
*
* ```
*
* @param a - The value to apply NOT to.
*
*/
not() {
return new UInt64(Bitwise.not(this.value, UInt64.NUM_BITS, false).value);
}
/**
* A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript,
* with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded.
* For a left rotation, this means that bits shifted off the left end reappear at the right end.
* Conversely, for a right rotation, bits shifted off the right end reappear at the left end.
*
* It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number,
* where the most significant (64th) bit is on the left end and the least significant bit is on the right end.
* The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation.
*
* To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits;
* for example, using {@link Gadgets.rangeCheck64}.
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation)
*
* @param bits amount of bits to rotate this {@link UInt64} element with.
* @param direction left or right rotation direction.
*
*
* @example
* ```ts
* const x = UInt64.from(0b001100);
* const y = x.rotate(2, 'left');
* const z = x.rotate(2, 'right'); // right rotation by 2 bits
* y.assertEquals(0b110000);
* z.assertEquals(0b000011);
* ```
*/
rotate(bits: number, direction: 'left' | 'right' = 'left') {
return new UInt64(Bitwise.rotate64(this.value, bits, direction).value);
}
/**
* Performs a left shift operation on the provided {@link UInt64} element.
* This operation is similar to the `<<` shift operation in JavaScript,
* where bits are shifted to the left, and the overflowing bits are discarded.
*
* It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number,
* where the most significant (64th) bit is on the left end and the least significant bit is on the right end.
*
* @param bits Amount of bits to shift the {@link UInt64} element to the left. The amount should be between 0 and 64 (or else the shift will fail).
*
* @example
* ```ts
* const x = UInt64.from(0b001100); // 12 in binary
* const y = x.leftShift(2); // left shift by 2 bits
* y.assertEquals(0b110000); // 48 in binary
* ```
*/
leftShift(bits: number) {
return new UInt64(Bitwise.leftShift64(this.value, bits).value);
}
/**
* Performs a right shift operation on the provided {@link UInt64} element.
* This operation is similar to the `>>` shift operation in JavaScript,
* where bits are shifted to the right, and the overflowing bits are discarded.
*
* It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number,
* where the most significant (64th) bit is on the left end and the least significant bit is on the right end.
*
* @param bits Amount of bits to shift the {@link UInt64} element to the right. The amount should be between 0 and 64 (or else the shift will fail).
*
* @example
* ```ts
* const x = UInt64.from(0b001100); // 12 in binary
* const y = x.rightShift(2); // right shift by 2 bits
* y.assertEquals(0b000011); // 3 in binary
* ```
*/
rightShift(bits: number) {
return new UInt64(Bitwise.rightShift64(this.value, bits).value);
}
/**
* Bitwise AND gadget on {@link UInt64} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND).
* The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise.
*
* It can be checked by a double generic gate that verifies the following relationship between the values below.
*
* The generic gate verifies:\
* `a + b = sum` and the conjunction equation `2 * and = sum - xor`\
* Where:\
* `a + b = sum`\
* `a ^ b = xor`\
* `a & b = and`
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and)
*
*
* @example
* ```typescript
* let a = UInt64.from(3); // ... 000011
* let b = UInt64.from(5); // ... 000101
*
* let c = a.and(b); // ... 000001
* c.assertEquals(1);
* ```
*/
and(x: UInt64) {
return new UInt64(Bitwise.and(this.value, x.value, UInt64.NUM_BITS).value);
}
/**
* Checks if a {@link UInt64} is less than or equal to another one.
*/
lessThanOrEqual(y: UInt64) {
if (this.value.isConstant() && y.value.isConstant()) {
return Bool(this.value.toBigInt() <= y.value.toBigInt());
}
return lessThanOrEqualGeneric(this.value, y.value, 1n << 64n, (v) =>
RangeCheck.rangeCheckN(UInt64.NUM_BITS, v)
);
}
/**
* Asserts that a {@link UInt64} is less than or equal to another one.
*/
assertLessThanOrEqual(y: UInt64, message?: string) {
if (this.value.isConstant() && y.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()];
return assert(
x0 <= y0,
message ?? `UInt64.assertLessThanOrEqual: expected ${x0} <= ${y0}`
);
}
assertLessThanOrEqualGeneric(this.value, y.value, (v) =>
RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message)
);
}
/**
*
* Checks if a {@link UInt64} is less than another one.
*/
lessThan(y: UInt64) {
if (this.value.isConstant() && y.value.isConstant()) {
return Bool(this.value.toBigInt() < y.value.toBigInt());
}
return lessThanGeneric(this.value, y.value, 1n << 64n, (v) =>
RangeCheck.rangeCheckN(UInt64.NUM_BITS, v)
);
}
/**
* Asserts that a {@link UInt64} is less than another one.
*/
assertLessThan(y: UInt64, message?: string) {
if (this.value.isConstant() && y.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()];
return assert(
x0 < y0,
message ?? `UInt64.assertLessThan: expected ${x0} < ${y0}`
);
}
assertLessThanGeneric(this.value, y.value, (v) =>
RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message)
);
}
/**
* Checks if a {@link UInt64} is greater than another one.
*/
greaterThan(y: UInt64) {
return y.lessThan(this);
}
/**
* Asserts that a {@link UInt64} is greater than another one.
*/
assertGreaterThan(y: UInt64, message?: string) {
y.assertLessThan(this, message);
}
/**
* Checks if a {@link UInt64} is greater than or equal to another one.
*/
greaterThanOrEqual(y: UInt64) {
return y.lessThanOrEqual(this);
}
/**
* Asserts that a {@link UInt64} is greater than or equal to another one.
*/
assertGreaterThanOrEqual(y: UInt64, message?: string) {
y.assertLessThanOrEqual(this, message);
}
static toValue(x: UInt64) {
return x.value.toBigInt();
}
static fromValue<T extends AnyConstructor>(
x: bigint | UInt64
): InstanceType<T> {
return UInt64.from(x) as any;
}
}
/**
* A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295.
*/
class UInt32 extends CircuitValue {
@prop value: Field;
static NUM_BITS = 32;
/**
* Create a {@link UInt32}.
* The max value of a {@link UInt32} is `2^32 - 1 = UInt32.MAXINT()`.
*
* **Warning**: Cannot overflow, an error is thrown if the result is greater than UInt32.MAXINT()
*/
constructor(x: UInt32 | FieldVar | number | string | bigint) {
if (x instanceof UInt32) x = x.value.value;
let value = Field(x);
super(value);
// check the range if the argument is a constant
UInt32.checkConstant(value);
}
static Unsafe = {
/**
* Create a {@link UInt32} from a {@link Field} without constraining its range.
*
* **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 32 bits.\
* Only use this if you know what you are doing, otherwise use the safe {@link UInt32.from}.
*/
fromField(x: Field) {
return new UInt32(x.value);
},
};
/**
* Static method to create a {@link UInt32} with value `0`.
*/
static get zero(): UInt32 {
return new UInt32(0);
}
/**
* Static method to create a {@link UInt32} with value `0`.
*/
static get one(): UInt32 {
return new UInt32(1);
}
/**
* Turns the {@link UInt32} into a string.
*/
toString(): string {
return this.value.toString();
}
/**
* Turns the {@link UInt32} into a {@link BigInt}.
*/
toBigint() {
return this.value.toBigInt();
}
/**
* Turns the {@link UInt32} into a {@link UInt64}.
*/
toUInt64(): UInt64 {
// this is safe, because the UInt32 range is included in the UInt64 range
return new UInt64(this.value.value);
}
static check(x: UInt32) {
RangeCheck.rangeCheck32(x.value);
}
static toInput(x: UInt32): HashInput {
return { packed: [[x.value, 32]] };
}
/**
* Encodes this structure into a JSON-like object.
*/
static toJSON(x: UInt32) {
return x.value.toString();
}
/**
* Decodes a JSON-like object into this structure.
*/
static fromJSON<T extends AnyConstructor>(x: string): InstanceType<T> {
return this.from(x) as any;
}
private static checkConstant(x: Field) {
if (!x.isConstant()) return x;
let xBig = x.toBigInt();
if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) {
throw Error(
`UInt32: Expected number between 0 and 2^32 - 1, got ${xBig}`
);
}
return x;
}
// this checks the range if the argument is a constant
/**
* Creates a new {@link UInt32}.
*/
static from(x: UInt32 | number | string | bigint) {
if (x instanceof UInt32) return x;
return new this(x);
}
/**
* Creates a {@link UInt32} with a value of 4,294,967,295.
*/
static MAXINT() {
return new UInt32((1n << 32n) - 1n);
}
/**
* Addition modulo 2^32. Check {@link Gadgets.addMod32} for a detailed description.
*/
addMod32(y: UInt32) {
return new UInt32(addMod32(this.value, y.value).value);
}
/**
* Integer division with remainder.
*
* `x.divMod(y)` returns the quotient and the remainder.
*/
divMod(y: UInt32 | number | string) {
let x = this.value;
let y_ = UInt32.from(y).value;
if (x.isConstant() && y_.isConstant()) {
let xn = x.toBigInt();
let yn = y_.toBigInt();
let q = xn / yn;
let r = xn - q * yn;
return {
quotient: new UInt32(new Field(q.toString()).value),
rest: new UInt32(new Field(r.toString()).value),
};
}
y_ = y_.seal();
let q = Provable.witness(
Field,
() => new Field(x.toBigInt() / y_.toBigInt())
);
RangeCheck.rangeCheck32(q);
// TODO: Could be a bit more efficient
let r = x.sub(q.mul(y_)).seal();
RangeCheck.rangeCheck32(r);
let r_ = new UInt32(r.value);
let q_ = new UInt32(q.value);
r_.assertLessThan(new UInt32(y_.value));
return { quotient: q_, rest: r_ };
}
/**
* Integer division.
*
* `x.div(y)` returns the floor of `x / y`, that is, the greatest
* `z` such that `x * y <= x`.
*
*/
div(y: UInt32 | number) {
return this.divMod(y).quotient;
}
/**
* Integer remainder.
*
* `x.mod(y)` returns the value `z` such that `0 <= z < y` and
* `x - z` is divisible by `y`.
*/
mod(y: UInt32 | number) {
return this.divMod(y).rest;
}
/**
* Multiplication with overflow checking.
*/
mul(y: UInt32 | number) {
let z = this.value.mul(UInt32.from(y).value);
RangeCheck.rangeCheck32(z);
return new UInt32(z.value);
}
/**
* Addition with overflow checking.
*/
add(y: UInt32 | number) {
let z = this.value.add(UInt32.from(y).value);
RangeCheck.rangeCheck32(z);
return new UInt32(z.value);
}
/**
* Subtraction with underflow checking.
*/
sub(y: UInt32 | number) {
let z = this.value.sub(UInt32.from(y).value);
RangeCheck.rangeCheck32(z);
return new UInt32(z.value);
}
/**
* Bitwise XOR gadget on {@link UInt32} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR).
* A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal.
*
* This gadget builds a chain of XOR gates recursively.
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1)
*
* @param x {@link UInt32} element to compare.
*
* @example
* ```ts
* let a = UInt32.from(0b0101);
* let b = UInt32.from(0b0011);
*
* let c = a.xor(b);
* c.assertEquals(0b0110);
* ```
*/
xor(x: UInt32) {
return new UInt32(Bitwise.xor(this.value, x.value, UInt32.NUM_BITS).value);
}
/**
* Bitwise NOT gate on {@link UInt32} elements. Similar to the [bitwise
* NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/
* Web/JavaScript/Reference/Operators/Bitwise_NOT).
*
* **Note:** The NOT gate operates over 32 bit for UInt32 types.
*
* A NOT gate works by returning `1` in each bit position if the
* corresponding bit of the operand is `0`, and returning `0` if the
* corresponding bit of the operand is `1`.
*
* NOT is implemented as a subtraction of the input from the all one bitmask.
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not)
*
* @example
* ```ts
* // NOTing 4 bits with the unchecked version
* let a = UInt32.from(0b0101);
* let b = a.not();
*
* console.log(b.toBigInt().toString(2));
* // 11111111111111111111111111111010
* ```
*
* @param a - The value to apply NOT to.
*/
not() {
return new UInt32(Bitwise.not(this.value, UInt32.NUM_BITS, false).value);
}
/**
* A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript,
* with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded.
* For a left rotation, this means that bits shifted off the left end reappear at the right end.
* Conversely, for a right rotation, bits shifted off the right end reappear at the left end.
*
* It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number,
* where the most significant (64th) bit is on the left end and the least significant bit is on the right end.
* The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation.
*
* To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits;
* for example, using {@link Gadgets.rangeCheck64}.
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation)
*
* @param bits amount of bits to rotate this {@link UInt32} element with.
* @param direction left or right rotation direction.
*
*
* @example
* ```ts
* const x = UInt32.from(0b001100);
* const y = x.rotate(2, 'left');
* const z = x.rotate(2, 'right'); // right rotation by 2 bits
* y.assertEquals(0b110000);
* z.assertEquals(0b000011);
* ```
*/
rotate(bits: number, direction: 'left' | 'right' = 'left') {
return new UInt32(Bitwise.rotate32(this.value, bits, direction).value);
}
/**
* Performs a left shift operation on the provided {@link UInt32} element.
* This operation is similar to the `<<` shift operation in JavaScript,
* where bits are shifted to the left, and the overflowing bits are discarded.
*
* It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number,
* where the most significant (32th) bit is on the left end and the least significant bit is on the right end.
*
* The operation expects the input to be range checked to 32 bit.
*
* @param bits Amount of bits to shift the {@link UInt32} element to the left. The amount should be between 0 and 32 (or else the shift will fail).
*
* @example
* ```ts
* const x = UInt32.from(0b001100); // 12 in binary
* const y = x.leftShift(2); // left shift by 2 bits
* y.assertEquals(0b110000); // 48 in binary
* ```
*/
leftShift(bits: number) {
return new UInt32(Bitwise.leftShift32(this.value, bits).value);
}
/**
* Performs a left right operation on the provided {@link UInt32} element.
* This operation is similar to the `>>` shift operation in JavaScript,
* where bits are shifted to the right, and the overflowing bits are discarded.
*
* It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number,
* where the most significant (32th) bit is on the left end and the least significant bit is on the right end.
*
* @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 32 (or else the shift will fail).
*
* The operation expects the input to be range checked to 32 bit.
*
* @example
* ```ts
* const x = UInt32.from(0b001100); // 12 in binary
* const y = x.rightShift(2); // left shift by 2 bits
* y.assertEquals(0b000011); // 48 in binary
* ```
*/
rightShift(bits: number) {
return new UInt32(Bitwise.rightShift64(this.value, bits).value);
}
/**
* Bitwise AND gadget on {@link UInt32} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND).
* The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise.
*
* It can be checked by a double generic gate that verifies the following relationship between the values below.
*
* The generic gate verifies:\
* `a + b = sum` and the conjunction equation `2 * and = sum - xor`\
* Where:\
* `a + b = sum`\
* `a ^ b = xor`\
* `a & b = and`
*
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and)
*
*
* @example
* ```typescript
* let a = UInt32.from(3); // ... 000011
* let b = UInt32.from(5); // ... 000101
*
* let c = a.and(b, 2); // ... 000001
* c.assertEquals(1);
* ```
*/
and(x: UInt32) {
return new UInt32(Bitwise.and(this.value, x.value, UInt32.NUM_BITS).value);
}
/**
* Checks if a {@link UInt32} is less than or equal to another one.
*/
lessThanOrEqual(y: UInt32) {
if (this.value.isConstant() && y.value.isConstant()) {
return Bool(this.value.toBigInt() <= y.value.toBigInt());
}
return lessThanOrEqualGeneric(this.value, y.value, 1n << 32n, (v) =>
RangeCheck.rangeCheckN(UInt32.NUM_BITS, v)
);
}
/**
* Asserts that a {@link UInt32} is less than or equal to another one.
*/
assertLessThanOrEqual(y: UInt32, message?: string) {
if (this.value.isConstant() && y.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()];
return assert(
x0 <= y0,
message ?? `UInt32.assertLessThanOrEqual: expected ${x0} <= ${y0}`
);
}
assertLessThanOrEqualGeneric(this.value, y.value, (v) =>
RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message)
);
}
/**
* Checks if a {@link UInt32} is less than another one.
*/
lessThan(y: UInt32) {
if (this.value.isConstant() && y.value.isConstant()) {
return Bool(this.value.toBigInt() < y.value.toBigInt());
}
return lessThanGeneric(this.value, y.value, 1n << 64n, (v) =>
RangeCheck.rangeCheckN(UInt64.NUM_BITS, v)
);
}
/**
* Asserts that a {@link UInt32} is less than another one.
*/
assertLessThan(y: UInt32, message?: string) {
if (this.value.isConstant() && y.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()];
return assert(
x0 < y0,
message ?? `UInt32.assertLessThan: expected ${x0} < ${y0}`
);
}
assertLessThanGeneric(this.value, y.value, (v) =>
RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message)
);
}
/**
* Checks if a {@link UInt32} is greater than another one.
*/
greaterThan(y: UInt32) {
return y.lessThan(this);
}
/**
* Asserts that a {@link UInt32} is greater than another one.
*/
assertGreaterThan(y: UInt32, message?: string) {
y.assertLessThan(this, message);
}
/**
* Checks if a {@link UInt32} is greater than or equal to another one.
*/
greaterThanOrEqual(y: UInt32) {
return y.lessThanOrEqual(this);
}
/**
* Asserts that a {@link UInt32} is greater than or equal to another one.
*/
assertGreaterThanOrEqual(y: UInt32, message?: string) {
y.assertLessThanOrEqual(this, message);
}
static toValue(x: UInt32) {
return x.value.toBigInt();
}
static fromValue<T extends AnyConstructor>(
x: bigint | UInt32
): InstanceType<T> {
return UInt32.from(x) as any;
}
}
class Sign extends CircuitValue {
@prop value: Field; // +/- 1
static get one() {
return new Sign(Field(1));
}
static get minusOne() {
return new Sign(Field(-1));
}
static check(x: Sign) {
// x^2 === 1 <=> x === 1 or x === -1
x.value.square().assertEquals(1);
}
static empty<T extends AnyConstructor>(): InstanceType<T> {
return Sign.one as any;
}
static toInput(x: Sign): HashInput {
return { packed: [[x.isPositive().toField(), 1]] };
}
static toJSON(x: Sign) {
if (x.toString() === '1') return 'Positive';
if (x.neg().toString() === '1') return 'Negative';
throw Error(`Invalid Sign: ${x}`);
}
static fromJSON<T extends AnyConstructor>(
x: 'Positive' | 'Negative'
): InstanceType<T> {
return (x === 'Positive' ? new Sign(Field(1)) : new Sign(Field(-1))) as any;
}
neg() {
return new Sign(this.value.neg());
}
mul(y: Sign) {
return new Sign(this.value.mul(y.value));
}
isPositive() {
return this.value.equals(1);
}
isNegative() {
return this.value.equals(-1);
}
toString() {
return this.value.toString();
}
static toValue(x: Sign) {
return x.value.toBigInt() as TypesBigint.Sign;
}
static fromValue<T extends AnyConstructor>(
x: bigint | Sign
): InstanceType<T> {
if (x instanceof Sign) return x as any;
return new Sign(Field(x)) as any;
}
}
type BalanceChange = Types.AccountUpdate['body']['balanceChange'];
/**
* A 64 bit signed integer with values ranging from -18,446,744,073,709,551,615 to 18,446,744,073,709,551,615.
*/
class Int64 extends CircuitValue implements BalanceChange {
// * in the range [-2^64+1, 2^64-1], unlike a normal int64
// * under- and overflowing is disallowed, similar to UInt64, unlike a normal int64
@prop magnitude: UInt64; // absolute value
@prop sgn: Sign; // +/- 1
// Some thoughts regarding the representation as field elements:
// toFields returns the in-circuit representation, so the main objective is to minimize the number of constraints
// that result from this representation. Therefore, I think the only candidate for an efficient 1-field representation
// is the one where the Int64 is the field: toFields = Int64 => [Int64.magnitude.mul(Int64.sign)]. Anything else involving
// bit packing would just lead to very inefficient circuit operations.
//
// So, is magnitude * sign ("1-field") a more efficient representation than (magnitude, sign) ("2-field")?
// Several common operations like add, mul, etc, operate on 1-field so in 2-field they result in one additional multiplication
// constraint per operand. However, the check operation (constraining to 64 bits + a sign) which is called at the introduction
// of every witness, and also at the end of add, mul, etc, operates on 2-field. So here, the 1-field representation needs
// to add an additional magnitude * sign = Int64 multiplication constraint, which will typically cancel out most of the gains
// achieved by 1-field elsewhere.
// There are some notable operations for which 2-field is definitely better:
//
// * div and mod (which do integer division with rounding on the magnitude)
// * converting the Int64 to a Currency.Amount.Signed (for the zkapp balance), which has the exact same (magnitude, sign) representation we use here.
//
// The second point is one of the main things an Int64 is used for, and was the original motivation to use 2 fields.
// Overall, I think the existing implementation is the optimal one.
constructor(magnitude: UInt64, sgn = Sign.one) {
super(magnitude, sgn);
}
/**
* Creates a new {@link Int64} from a {@link Field}.
*
* Does check if the {@link Field} is within range.
*/
private static fromFieldUnchecked(x: Field) {
let TWO64 = 1n << 64n;
let xBigInt = x.toBigInt();
let isValidPositive = xBigInt < TWO64; // covers {0,...,2^64 - 1}
let isValidNegative = Field.ORDER - xBigInt < TWO64; // {-2^64 + 1,...,-1}
if (!isValidPositive && !isValidNegative)
throw Error(`Int64: Expected a value between (-2^64, 2^64), got ${x}`);
let magnitude = (isValidPositive ? x : x.neg()).toConstant();
let sign = isValidPositive ? Sign.one : Sign.minusOne;
return new Int64(UInt64.Unsafe.fromField(magnitude), sign);
}
// this doesn't check ranges because we assume they're already checked on UInts
/**
* Creates a new {@link Int64} from a {@link Field}.
*
* **Does not** check if the {@link Field} is within range.
*/
static fromUnsigned(x: UInt64 | UInt32) {
return new Int64(x instanceof UInt32 ? x.toUInt64() : x);
}
// this checks the range if the argument is a constant
/**
* Creates a new {@link Int64}.
*
* Check the range if the argument is a constant.
*/
static from(x: Int64 | UInt32 | UInt64 | Field | number | string | bigint) {
if (x instanceof Int64) return x;
if (x instanceof UInt64 || x instanceof UInt32) {
return Int64.fromUnsigned(x);
}
return Int64.fromFieldUnchecked(Field(x));
}
/**
* Turns the {@link Int64} into a string.
*/
toString() {
let abs = this.magnitude.toString();
let sgn = this.isPositive().toBoolean() || abs === '0' ? '' : '-';
return sgn + abs;
}
isConstant() {
return this.magnitude.value.isConstant() && this.sgn.isConstant();
}
// --- circuit-compatible operations below ---
// the assumption here is that all Int64 values that appear in a circuit are already checked as valid
// this is because Provable.witness calls .check, which calls .check on each prop, i.e. UInt64 and Sign
// so we only have to do additional checks if an operation on valid inputs can have an invalid outcome (example: overflow)
/**
* Static method to create a {@link Int64} with value `0`.
*/
static get zero() {
return new Int64(UInt64.zero);
}
/**
* Static method to create a {@link Int64} with value `1`.
*/
static get one() {
return new Int64(UInt64.one);
}
/**
* Static method to create a {@link Int64} with value `-1`.
*/
static get minusOne() {
return new Int64(UInt64.one).neg();
}
/**
* Returns the {@link Field} value.
*/
toField() {
return this.magnitude.value.mul(this.sgn.value);
}
/**
* Static method to create a {@link Int64} from a {@link Field}.
*/
static fromField(x: Field): Int64 {
// constant case - just return unchecked value
if (x.isConstant()) return Int64.fromFieldUnchecked(x);
// variable case - create a new checked witness and prove consistency with original field
let xInt = Provable.witness(Int64, () => Int64.fromFieldUnchecked(x));
xInt.toField().assertEquals(x); // sign(x) * |x| === x
return xInt;
}
/**
* @deprecated Use {@link negV2()} instead.
* The current implementation will not be backwards-compatible with v2.
*/
neg() {
// doesn't need further check if `this` is valid
return new Int64(this.magnitude, this.sgn.neg());
}
/**
* Negates the value.
*
* `Int64.from(5).neg()` will turn into `Int64.from(-5)`
*/
negV2() {
return Provable.if(
this.magnitude.value.equals(0),
Int64.zero,
new Int64(this.magnitude, this.sgn.neg())
);
}
/**
* Addition with overflow checking.
*/
add(y: Int64 | number | string | bigint | UInt64 | UInt32) {
let y_ = Int64.from(y);
return Int64.fromField(this.toField().add(y_.toField()));
}
/**
* Subtraction with underflow checking.
*/
sub(y: Int64 | number | string | bigint | UInt64 | UInt32) {
let y_ = Int64.from(y);
return Int64.fromField(this.toField().sub(y_.toField()));
}
/**
* Multiplication with overflow checking.
*/
mul(y: Int64 | number | string | bigint | UInt64 | UInt32) {
let y_ = Int64.from(y);
return Int64.fromField(this.toField().mul(y_.toField()));
}
/**
* Integer division.
*
* `x.div(y)` returns the floor of `x / y`, that is, the greatest
* `z` such that `z * y <= x`.
* On negative numbers, this rounds towards zero.
*/
div(y: Int64 | number | string | bigint | UInt64 | UInt32) {
let y_ = Int64.from(y);
let { quotient } = this.magnitude.divMod(y_.magnitude);
let sign = this.sgn.mul(y_.sgn);
return new Int64(quotient, sign);
}
/**
* @deprecated Use {@link modV2()} instead.
* This implementation is vulnerable whenever `this` is zero.
* It allows the prover to return `y` instead of 0 as the result.
*/
mod(y: UInt64 | number | string | bigint | UInt32) {
let y_ = UInt64.from(y);
let rest = this.magnitude.divMod(y_).rest.value;
rest = Provable.if(this.isPositive(), rest, y_.value.sub(rest));
return new Int64(new UInt64(rest.value));
}
/**
* Integer remainder.
*
* `x.mod(y)` returns the value `z` such that `0 <= z < y` and
* `x - z` is divisible by `y`.
*/
modV2(y: UInt64 | number | string | bigint | UInt32) {
let y_ = UInt64.from(y);
let rest = this.magnitude.divMod(y_).rest.value;
let isNonNegative = this.magnitude
.equals(UInt64.zero)
.or(this.sgn.isPositive());
rest = Provable.if(isNonNegative, rest, y_.value.sub(rest));
return new Int64(new UInt64(rest.value));
}
/**
* Checks if two values are equal.
*/
equals(y: Int64 | number | string | bigint | UInt64 | UInt32) {
let y_ = Int64.from(y);
return this.toField().equals(y_.toField());
}
/**
* Asserts that two values are equal.
*/
assertEquals(
y: Int64 | number | string | bigint | UInt64 | UInt32,
message?: string
) {
let y_ = Int64.from(y);
this.toField().assertEquals(y_.toField(), message);
}
/**
* @deprecated Use {@link isPositiveV2} instead.
* The current implementation actually tests for non-negativity, but is wrong for the negative representation of 0.
*/
isPositive() {
return this.sgn.isPositive();
}
/**
* Checks if the value is positive (x > 0).
*/
isPositiveV2() {
return this.magnitude.equals(UInt64.zero).not().and(this.sgn.isPositive());
}
// TODO add this when `checkV2` is enabled
// then it will be the correct logic; right now it would be misleading
/**
* Checks if the value is non-negative (x >= 0).
*/
// isNonNegativeV2() {
// return this.sgn.isPositive();
// }
// TODO add this when `checkV2` is enabled
// then it will be the correct logic; right now it would be misleading
/**
* Checks if the value is negative (x < 0).
*/
// isNegative() {
// return this.sgn.isNegative();
// }
// TODO enable this check method in v2, to force a unique representation of 0
static checkV2({ magnitude, sgn }: { magnitude: UInt64; sgn: Sign }) {
// check that the magnitude is in range
UInt64.check(magnitude);
// check that the sign is valid
Sign.check(sgn);
// check unique representation of 0: we can't have magnitude = 0 and sgn = -1
// magnitude + sign != -1 (this check works because magnitude >= 0)
magnitude.value
.add(sgn.value)
.assertNotEquals(-1, 'Int64: 0 must have positive sign');
}
}
/**
* A 8 bit unsigned integer with values ranging from 0 to 255.
*/
class UInt8 extends Struct({
value: Field,
}) {
static NUM_BITS = 8;
/**
* Create a {@link UInt8} from a bigint or number.
* The max value of a {@link UInt8} is `2^8 - 1 = 255`.
*
* **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255.
*/
constructor(x: number | bigint | FieldVar | UInt8) {
if (x instanceof UInt8) x = x.value.value;
super({ value: Field(x) });
UInt8.checkConstant(this.value);
}
static Unsafe = {
/**
* Create a {@link UInt8} from a {@link Field} without constraining its range.
*
* **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 8 bits.\
* Only use this if you know what you are doing, otherwise use the safe {@link UInt8.from}.
*/
fromField(x: Field) {
return new UInt8(x.value);
},
};
/**
* Add a {@link UInt8} to another {@link UInt8} without allowing overflow.
*
* @example
* ```ts
* const x = UInt8.from(3);
* const sum = x.add(5);
* sum.assertEquals(8);
* ```
*
* @throws if the result is greater than 255.
*/
add(y: UInt8 | bigint | number) {
let z = this.value.add(UInt8.from(y).value);
RangeCheck.rangeCheck8(z);
return UInt8.Unsafe.fromField(z);
}
/**
* Subtract a {@link UInt8} from another {@link UInt8} without allowing underflow.
*
* @example
* ```ts
* const x = UInt8.from(8);
* const difference = x.sub(5);
* difference.assertEquals(3);
* ```
*
* @throws if the result is less than 0.
*/
sub(y: UInt8 | bigint | number) {
let z = this.value.sub(UInt8.from(y).value);
RangeCheck.rangeCheck8(z);
return UInt8.Unsafe.fromField(z);
}
/**
* Multiply a {@link UInt8} by another {@link UInt8} without allowing overflow.
*
* @example
* ```ts
* const x = UInt8.from(3);
* const product = x.mul(5);
* product.assertEquals(15);
* ```
*
* @throws if the result is greater than 255.
*/
mul(y: UInt8 | bigint | number) {
let z = this.value.mul(UInt8.from(y).value);
RangeCheck.rangeCheck8(z);
return UInt8.Unsafe.fromField(z);
}
/**
* Divide a {@link UInt8} by another {@link UInt8}.
* This is integer division that rounds down.
*
* @example
* ```ts
* const x = UInt8.from(7);
* const quotient = x.div(2);
* quotient.assertEquals(3);
* ```
*/
div(y: UInt8 | bigint | number) {
return this.divMod(y).quotient;
}
/**
* Get the remainder a {@link UInt8} of division of another {@link UInt8}.
*
* @example
* ```ts
* const x = UInt8.from(50);
* const mod = x.mod(30);
* mod.assertEquals(20);
* ```
*/
mod(y: UInt8 | bigint | number) {
return this.divMod(y).remainder;
}
/**
* Get the quotient and remainder of a {@link UInt8} divided by another {@link UInt8}:
*
* `x == y * q + r`, where `0 <= r < y`.
*
* @param y - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}.
*
* @return The quotient `q` and remainder `r`.
*/
divMod(y: UInt8 | bigint | number) {
let x = this.value;
let y_ = UInt8.from(y).value.seal();
if (this.value.isConstant() && y_.isConstant()) {
let xn = x.toBigInt();
let yn = y_.toBigInt();
let q = xn / yn;
let r = xn - q * yn;
return { quotient: UInt8.from(q), remainder: UInt8.from(r) };
}
// prove that x === q * y + r, where 0 <= r < y
let q = Provable.witness(Field, () => Field(x.toBigInt() / y_.toBigInt()));
let r = x.sub(q.mul(y_)).seal();
// q, r being 16 bits is enough for them to be 8 bits,
// thanks to the === x check and the r < y check below
RangeCheck.rangeCheck16(q);
RangeCheck.rangeCheck16(r);
let remainder = UInt8.Unsafe.fromField(r);
let quotient = UInt8.Unsafe.fromField(q);
remainder.assertLessThan(y);
return { quotient, remainder };
}
/**
* Check if this {@link UInt8} is less than or equal to another {@link UInt8} value.
* Returns a {@link Bool}.
*
* @example
* ```ts
* UInt8.from(3).lessThanOrEqual(UInt8.from(5));
* ```
*/
lessThanOrEqual(y: UInt8 | bigint | number): Bool {
let y_ = UInt8.from(y);
if (this.value.isConstant() && y_.value.isConstant()) {
return Bool(this.toBigInt() <= y_.toBigInt());
}
return lessThanOrEqualGeneric(
this.value,
y_.value,
1n << 8n,
RangeCheck.rangeCheck8
);
}
/**
* Check if this {@link UInt8} is less than another {@link UInt8} value.
* Returns a {@link Bool}.
*
* @example
* ```ts
* UInt8.from(2).lessThan(UInt8.from(3));
* ```
*/
lessThan(y: UInt8 | bigint | number): Bool {
let y_ = UInt8.from(y);
if (this.value.isConstant() && y_.value.isConstant()) {
return Bool(this.toBigInt() < y_.toBigInt());
}
return lessThanGeneric(
this.value,
y_.value,
1n << 8n,
RangeCheck.rangeCheck8
);
}
/**
* Assert that this {@link UInt8} is less than another {@link UInt8} value.
*
* **Important**: If an assertion fails, the code throws an error.
*
* @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}.
* @param message? - a string error message to print if the assertion fails, optional.
*/
assertLessThan(y: UInt8 | bigint | number, message?: string) {
let y_ = UInt8.from(y);
if (this.value.isConstant() && y_.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()];
return assert(
x0 < y0,
message ?? `UInt8.assertLessThan: expected ${x0} < ${y0}`
);
}
try {
// 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanGeneric`
assertLessThanGeneric(this.value, y_.value, RangeCheck.rangeCheck16);
} catch (err) {
throw withMessage(err, message);
}
}
/**
* Assert that this {@link UInt8} is less than or equal to another {@link UInt8} value.
*
* **Important**: If an assertion fails, the code throws an error.
*
* @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}.
* @param message? - a string error message to print if the assertion fails, optional.
*/
assertLessThanOrEqual(y: UInt8 | bigint | number, message?: string) {
let y_ = UInt8.from(y);
if (this.value.isConstant() && y_.value.isConstant()) {
let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()];
return assert(
x0 <= y0,
message ?? `UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}`
);
}
try {
// 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanOrEqualGeneric`
assertLessThanOrEqualGeneric(
this.value,
y_.value,
RangeCheck.rangeCheck16
);
} catch (err) {
throw withMessage(err, message);
}
}
/**
* Check if this {@link UInt8} is greater than another {@link UInt8}.
* Returns a {@link Bool}.
*
* @example
* ```ts
* // 5 > 3
* UInt8.from(5).greaterThan(3);
* ```
*/
greaterThan(y: UInt8 | bigint | number) {
return UInt8.from(y).lessThan(this);
}
/**
* Check if this {@link UInt8} is greater than or equal another {@link UInt8} value.
* Returns a {@link Bool}.
*
* @example
* ```ts
* // 3 >= 3
* UInt8.from(3).greaterThanOrEqual(3);
* ```
*/
greaterThanOrEqual(y: UInt8 | bigint | number) {
return UInt8.from(y).lessThanOrEqual(this);
}
/**
* Assert that this {@link UInt8} is greater than another {@link UInt8} value.
*
* **Important**: If an assertion fails, the code throws an error.
*
* @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}.
* @param message? - a string error message to print if the assertion fails, optional.
*/
assertGreaterThan(y: UInt8 | bigint | number, message?: string) {
UInt8.from(y).assertLessThan(this, message);
}
/**
* Assert that this {@link UInt8} is greater than or equal to another {@link UInt8} value.
*
* **Important**: If an assertion fails, the code throws an error.
*
* @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}.
* @param message? - a string error message to print if the assertion fails, optional.
*/
assertGreaterThanOrEqual(y: UInt8, message?: string) {
UInt8.from(y).assertLessThanOrEqual(this, message);
}
/**
* Assert that this {@link UInt8} is equal another {@link UInt8} value.
*
* **Important**: If an assertion fails, the code throws an error.
*
* @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}.
* @param message? -