Skip to content
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ run-all diff-test bench clean-diff-test clean-bench clean-tmp

# Clone & build the other VMs - Assume that related lang are installed
CAIRO_VM_RS_CLI:=cairo-vm/target/release/cairo-vm-cli
CAIRO_VM_RS_CAIRO_CLI:=cairo-vm/target/release/cairo1-run
CAIRO_VM_ZIG_CLI:=ziggy-starkdust/zig-out/bin/ziggy-starkdust

$(CAIRO_VM_RS_CLI):
@git submodule update --init cairo-vm; \
cd cairo-vm; cargo build --release --bin cairo-vm-cli

$(CAIRO_VM_RS_CAIRO_CLI):
@git submodule update --init cairo-vm; \
cd cairo-vm/cairo1-run; make deps; \
cd ..; cargo build --release --bin cairo1-run

$(CAIRO_VM_ZIG_CLI):
@git submodule update --init ziggy-starkdust \
cd ziggy-starkdust; zig build -Doptimize=ReleaseFast

build:
@bun install; bun link

build-cairo-vm-rs-cli: | $(CAIRO_VM_RS_CLI)
build-cairo-vm-rs-cli: | $(CAIRO_VM_RS_CLI) $(CAIRO_VM_RS_CAIRO_CLI)

build-cairo-vm-zig-cli: | $(CAIRO_VM_ZIG_CLI)

Expand Down
13 changes: 8 additions & 5 deletions src/builtins/bitwise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import { Felt } from 'primitives/felt';
import { isFelt } from 'primitives/segmentValue';
import { BuiltinHandler } from './builtin';

/** Total number of cells per bitwise operation */
export const CELLS_PER_BITWISE = 5;

/** Number of input cells for a bitwise operation */
export const INPUT_CELLS_PER_BITWISE = 2;

export const bitwiseHandler: BuiltinHandler = {
get(target, prop) {
if (isNaN(Number(prop))) {
return Reflect.get(target, prop);
}

const cellsPerBitwise = 5;
const inputCellsPerBitwise = 2;

const offset = Number(prop);
const bitwiseIndex = offset % cellsPerBitwise;
if (bitwiseIndex < inputCellsPerBitwise || target[offset]) {
const bitwiseIndex = offset % CELLS_PER_BITWISE;
if (bitwiseIndex < INPUT_CELLS_PER_BITWISE || target[offset]) {
return target[offset];
}

Expand Down
45 changes: 30 additions & 15 deletions src/builtins/builtin.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,58 @@
import { SegmentValue } from 'primitives/segmentValue';
import { bitwiseHandler } from './bitwise';
import { ecOpHandler } from './ecop';
import { ecdsaHandler } from './ecdsa';
import { pedersenHandler } from './pedersen';
import { poseidonHandler } from './poseidon';
import { keccakHandler } from './keccak';

import { outputHandler } from './output';
import { rangeCheckHandler } from './rangeCheck';
import { CELLS_PER_PEDERSEN, pedersenHandler } from './pedersen';
import { rangeCheckHandler, RC_BITS, RC_BITS_96 } from './rangeCheck';
import { CELLS_PER_ECDSA, ecdsaHandler } from './ecdsa';
import { bitwiseHandler, CELLS_PER_BITWISE } from './bitwise';
import { CELLS_PER_EC_OP, ecOpHandler } from './ecop';
import { CELLS_PER_KECCAK, keccakHandler } from './keccak';
import { CELLS_PER_POSEIDON, poseidonHandler } from './poseidon';
import { segmentArenaHandler } from './segmentArena';

/** Proxy handler to abstract validation & deduction rules off the VM */
export type BuiltinHandler = ProxyHandler<Array<SegmentValue>>;

/**
* Object to map builtin names to their ProxyHandler:
* - Bitwise: Builtin for bitwise operations
* - Output: Output builtin
* - Pedersen: Builtin for pedersen hash family
* - RangeCheck: Builtin for inequality operations
* - ECDSA: Builtin for Elliptic Curve Digital Signature Algorithm
* - Bitwise: Builtin for bitwise operations
* - EcOp: Builtin for Elliptic curve Operations
* - Keccak: Builtin for keccak hash family
* - Pedersen: Builtin for pedersen hash family
* - Poseidon: Builtin for poseidon hash family
* - Segment Arena: Builtin to manage the dictionaries
* - Output: Output builtin
*/
const BUILTIN_HANDLER: {
[key: string]: BuiltinHandler;
} = {
output: outputHandler,
pedersen: pedersenHandler,
range_check: rangeCheckHandler(RC_BITS),
ecdsa: ecdsaHandler,
bitwise: bitwiseHandler,
ec_op: ecOpHandler,
ecdsa: ecdsaHandler,
pedersen: pedersenHandler,
poseidon: poseidonHandler,
keccak: keccakHandler,
range_check: rangeCheckHandler(128n),
range_check96: rangeCheckHandler(96n),
poseidon: poseidonHandler,
range_check96: rangeCheckHandler(RC_BITS_96),
segment_arena: segmentArenaHandler,
};

/** Getter of the object `BUILTIN_HANDLER` */
export const getBuiltin = (name: string) => BUILTIN_HANDLER[name];

export const CELLS_PER_INSTANCE: {
[key: string]: number;
} = {
output: 0,
pedersen: CELLS_PER_PEDERSEN,
range_check: 1,
ecdsa: CELLS_PER_ECDSA,
bitwise: CELLS_PER_BITWISE,
ec_op: CELLS_PER_EC_OP,
keccak: CELLS_PER_KECCAK,
poseidon: CELLS_PER_POSEIDON,
range_check96: 1,
};
5 changes: 3 additions & 2 deletions src/builtins/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type EcdsaSignatureDict = { [key: number]: EcdsaSignature };
export type EcdsaSegment = SegmentValue[] & { signatures: EcdsaSignatureDict };
type EcdsaProxyHandler = ProxyHandler<EcdsaSegment>;

export const CELLS_PER_ECDSA = 2;

const signatureHandler: ProxyHandler<EcdsaSignatureDict> = {
set(target, prop, newValue): boolean {
if (isNaN(Number(prop))) throw new ExpectedOffset();
Expand Down Expand Up @@ -50,9 +52,8 @@ export const ecdsaHandler: EcdsaProxyHandler = {
if (!target.signatures) throw new UndefinedSignatureDict();
if (!isFelt(newValue)) throw new ExpectedFelt(newValue);

const cellsPerEcdsa = 2;
const offset = Number(prop);
const ecdsaIndex = offset % cellsPerEcdsa;
const ecdsaIndex = offset % CELLS_PER_ECDSA;

const pubKeyXOffset = ecdsaIndex ? offset - 1 : offset;
const msgOffset = ecdsaIndex ? offset : offset + 1;
Expand Down
17 changes: 10 additions & 7 deletions src/builtins/ecop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { LadderFailed } from 'errors/builtins';

const _1n = BigInt(1);

/** Total number of cells per ec_op operation */
export const CELLS_PER_EC_OP = 7;

/** Number of input cells for a ec_op operation */
export const INPUT_CELLS_PER_EC_OP = 5;

/**
* EcOp Builtin - Computes R = P + mQ
* P and Q are points on the STARK curve
Expand All @@ -18,17 +24,14 @@ export const ecOpHandler: BuiltinHandler = {
return Reflect.get(target, prop);
}

const cellsPerEcOp = 7;
const inputCellsPerEcOp = 5;

const offset = Number(prop);
const ecOpIndex = offset % cellsPerEcOp;
if (ecOpIndex < inputCellsPerEcOp || target[offset]) {
const ecOpIndex = offset % CELLS_PER_EC_OP;
if (ecOpIndex < INPUT_CELLS_PER_EC_OP || target[offset]) {
return target[offset];
}

const inputOffset = offset - ecOpIndex;
const outputOffset = inputOffset + inputCellsPerEcOp;
const outputOffset = inputOffset + INPUT_CELLS_PER_EC_OP;

const inputs = target.slice(inputOffset, outputOffset).map((value) => {
if (!isFelt(value)) throw new ExpectedFelt(value);
Expand All @@ -41,7 +44,7 @@ export const ecOpHandler: BuiltinHandler = {
const r = p.multiplyAndAddUnsafe(q, _1n, inputs[4]);
if (r === undefined) throw new LadderFailed();

switch (ecOpIndex - inputCellsPerEcOp) {
switch (ecOpIndex - INPUT_CELLS_PER_EC_OP) {
case 0:
return (target[outputOffset] = new Felt(r.x));
default:
Expand Down
34 changes: 27 additions & 7 deletions src/builtins/keccak.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ import { BuiltinHandler } from './builtin';
const KECCAK_BYTES = 25;
const KECCAK_BITS = 200n;

/** Total number of cells per keccak operation */
export const CELLS_PER_KECCAK = 16;

/** Number of input cells for a keccak operation */
export const INPUT_CELLS_PER_KECCAK = 8;

/**
* The diluted cells are:
* - state - 25 rounds times 1600 elements.
* - parity - 24 rounds times 1600/5 elements times 3 auxiliaries.
* - after_theta_rho_pi - 24 rounds times 1600 elements.
* - theta_aux - 24 rounds times 1600 elements.
* - chi_iota_aux - 24 rounds times 1600 elements times 2 auxiliaries.
*
* In total 25 * 1600 + 24 * 320 * 3 + 24 * 1600 + 24 * 1600 + 24 * 1600 * 2 = 216640.
*
* But we actually allocate 4 virtual columns, of dimensions 64 * 1024, in which we embed the
* real cells, and we don't free the unused ones.
*
* So the real number is 4 * 64 * 1024 = 262144.
*/
export const KECCAK_DILUTED_CELLS = 262144;

/**
* Compute the new state of the keccak-f1600 block permutation on 24 rounds
*
Expand All @@ -24,17 +47,14 @@ export const keccakHandler: BuiltinHandler = {
return Reflect.get(target, prop);
}

const cellsPerKeccak = 16;
const inputCellsPerKeccak = 8;

const offset = Number(prop);
const keccakIndex = offset % cellsPerKeccak;
if (keccakIndex < inputCellsPerKeccak || target[offset]) {
const keccakIndex = offset % CELLS_PER_KECCAK;
if (keccakIndex < INPUT_CELLS_PER_KECCAK || target[offset]) {
return target[offset];
}

const inputOffset = offset - keccakIndex;
const outputOffset = inputOffset + inputCellsPerKeccak;
const outputOffset = inputOffset + INPUT_CELLS_PER_KECCAK;

const input = concatBytes(
...target.slice(inputOffset, outputOffset).map((value) => {
Expand All @@ -53,7 +73,7 @@ export const keccakHandler: BuiltinHandler = {
).map(bytesToNumberLE);

return (target[offset] = new Felt(
outputs[keccakIndex - inputCellsPerKeccak]
outputs[keccakIndex - INPUT_CELLS_PER_KECCAK]
));
},
};
13 changes: 8 additions & 5 deletions src/builtins/pedersen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ import { isFelt } from 'primitives/segmentValue';
import { pedersen } from '@scure/starknet';
import { Felt } from 'primitives/felt';

/** Total number of cells per pedersen operation */
export const CELLS_PER_PEDERSEN = 3;

/** Number of input cells for a pedersen operation */
export const INPUT_CELLS_PER_PEDERSEN = 2;

/** Pedersen Builtin - Computes Pedersen(x, y) */
export const pedersenHandler: BuiltinHandler = {
get(target, prop) {
if (isNaN(Number(prop))) {
return Reflect.get(target, prop);
}

const cellsPerPedersen = 3;
const inputCellsPerPedersen = 2;

const offset = Number(prop);
const pedersenIndex = offset % cellsPerPedersen;
if (pedersenIndex < inputCellsPerPedersen || target[offset]) {
const pedersenIndex = offset % CELLS_PER_PEDERSEN;
if (pedersenIndex < INPUT_CELLS_PER_PEDERSEN || target[offset]) {
return target[offset];
}

Expand Down
15 changes: 9 additions & 6 deletions src/builtins/poseidon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { isFelt } from 'primitives/segmentValue';
import { poseidonSmall } from '@scure/starknet';
import { Felt } from 'primitives/felt';

/** Total number of cells per poseidon operation */
export const CELLS_PER_POSEIDON = 6;

/** Number of input cells for a poseidon operation */
export const INPUT_CELLS_PER_POSEIDON = 3;

const poseidon = (x: bigint, y: bigint, z: bigint) => poseidonSmall([x, y, z]);

/** Poseidon Builtin - Computes state of Poseidon(x, y)
Expand All @@ -17,12 +23,9 @@ export const poseidonHandler: BuiltinHandler = {
return Reflect.get(target, prop);
}

const cellsPerPoseidon = 6;
const inputCellsPerPoseidon = 3;

const offset = Number(prop);
const poseidonIndex = offset % cellsPerPoseidon;
if (poseidonIndex < inputCellsPerPoseidon || target[offset]) {
const poseidonIndex = offset % CELLS_PER_POSEIDON;
if (poseidonIndex < INPUT_CELLS_PER_POSEIDON || target[offset]) {
return target[offset];
}

Expand All @@ -47,7 +50,7 @@ export const poseidonHandler: BuiltinHandler = {
const state = poseidon(x, y, z);

return (target[offset] = new Felt(
state[poseidonIndex - inputCellsPerPoseidon]
state[poseidonIndex - INPUT_CELLS_PER_POSEIDON]
));
},
};
12 changes: 12 additions & 0 deletions src/builtins/rangeCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ import { BuiltinHandler } from './builtin';
import { isFelt } from 'primitives/segmentValue';
import { ExpectedFelt } from 'errors/primitives';

export const INNER_RC_BOUND_SHIFT = 16;
export const INNER_RC_BOUND_MASK = 0xffffn;

export const RC_N_PARTS = 8;
export const RC_N_PARTS_96 = 6;

/** Bound exponent of the range_check builtin, `128`. */
export const RC_BITS = BigInt(INNER_RC_BOUND_SHIFT * RC_N_PARTS);

/** Bound exponent of the range_check96 builtin, `96`. */
export const RC_BITS_96 = BigInt(INNER_RC_BOUND_SHIFT * RC_N_PARTS_96);

export const rangeCheckHandler = (boundExponent: bigint): BuiltinHandler => {
return {
set(target, prop, newValue): boolean {
Expand Down
1 change: 1 addition & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ program
})
)
.option('-s, --silent', 'silent all logs')
.option('--proof-mode', 'run in proof mode')
.addOption(
new Option('-l, --layout <LAYOUT>', 'Layout to be used').default('plain')
)
Expand Down
28 changes: 27 additions & 1 deletion src/errors/cairoRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class CairoZeroHintsNotSupported extends CairoRunnerError {
/** The given entrypoint is not in the program, it cannot be executed. */
export class UndefinedEntrypoint extends CairoRunnerError {
constructor(name: string) {
super(`The function to be executed doesn't exist: ${name}`);
super(`The entrypoint to be executed doesn't exist: ${name}`);
}
}

Expand All @@ -35,3 +35,29 @@ Layout builtins: ${layoutBuiltins.join(', ')}`
);
}
}

/**
* The label `__main__.__end__` must be in the compilation artifacts
* to run a Cairo Zero program in proof mode.
*/
export class MissingEndLabel extends CairoRunnerError {
constructor() {
super('Label __end__ not found in program.');
}
}

/** The requested builtin segment is undefined. */
export class UndefinedBuiltinSegment extends CairoRunnerError {
constructor(builtin: string) {
super(`The requested builtin segment '${builtin}' is undefined.`);
}
}

/** The program consumes too many steps for the chosen layout. */
export class InsufficientAllocatedCells extends CairoRunnerError {
constructor(layout: string, used: number, capacity: number) {
super(
`The chosen layout ${layout} only has a capacity of ${capacity} cells, but the program used ${used} cells.`
);
}
}
Loading