From edf1052d869823468e50c605fb27cabd5a43eeb6 Mon Sep 17 00:00:00 2001 From: malatrax Date: Fri, 31 May 2024 17:22:10 +0200 Subject: [PATCH] feat: add Pedersen builtin --- cairo_programs/cairo_0/pedersen_builtin.cairo | 27 ++++++ src/builtins/builtin.ts | 2 + src/builtins/pedersen.test.ts | 94 +++++++++++++++++++ src/builtins/pedersen.ts | 38 ++++++++ src/runners/cairoRunner.test.ts | 38 ++++++++ 5 files changed, 199 insertions(+) create mode 100644 cairo_programs/cairo_0/pedersen_builtin.cairo create mode 100644 src/builtins/pedersen.test.ts create mode 100644 src/builtins/pedersen.ts diff --git a/cairo_programs/cairo_0/pedersen_builtin.cairo b/cairo_programs/cairo_0/pedersen_builtin.cairo new file mode 100644 index 00000000..d6daaed7 --- /dev/null +++ b/cairo_programs/cairo_0/pedersen_builtin.cairo @@ -0,0 +1,27 @@ +%builtins pedersen + +from starkware.cairo.common.cairo_builtins import HashBuiltin + +func main{pedersen_ptr: HashBuiltin*}() { + assert pedersen_ptr.x = 0; + assert pedersen_ptr.y = 0; + assert pedersen_ptr.result = 0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804; + let pedersen_ptr = pedersen_ptr + HashBuiltin.SIZE; + + assert pedersen_ptr.x = 0; + assert pedersen_ptr.y = 1; + assert pedersen_ptr.result = 0x46c9aeb066cc2f41c7124af30514f9e607137fbac950524f5fdace5788f9d43; + let pedersen_ptr = pedersen_ptr + HashBuiltin.SIZE; + + assert pedersen_ptr.x = 1; + assert pedersen_ptr.y = 0; + assert pedersen_ptr.result = 0x268a9d47dde48af4b6e2c33932ed1c13adec25555abaa837c376af4ea2f8a94; + let pedersen_ptr = pedersen_ptr + HashBuiltin.SIZE; + + assert pedersen_ptr.x = 54; + assert pedersen_ptr.y = 1249832432; + assert pedersen_ptr.result = 0x20120a7d08fd21654c72a9281841406543b16d00faaca1069332053c41c07b8; + let pedersen_ptr = pedersen_ptr + HashBuiltin.SIZE; + + return (); +} diff --git a/src/builtins/builtin.ts b/src/builtins/builtin.ts index 1c157483..8fc4f77e 100644 --- a/src/builtins/builtin.ts +++ b/src/builtins/builtin.ts @@ -1,6 +1,7 @@ import { SegmentValue } from 'primitives/segmentValue'; import { bitwiseHandler } from './bitwise'; import { ecOpHandler } from './ecop'; +import { pedersenHandler } from './pedersen'; /** Proxy handler to abstract validation & deduction rules off the VM */ export type BuiltinHandler = ProxyHandler>; @@ -22,6 +23,7 @@ const BUILTIN_HANDLER: { } = { bitwise: bitwiseHandler, ec_op: ecOpHandler, + pedersen: pedersenHandler, }; /** Getter of the object `BUILTIN_HANDLER` */ diff --git a/src/builtins/pedersen.test.ts b/src/builtins/pedersen.test.ts new file mode 100644 index 00000000..15a97e4f --- /dev/null +++ b/src/builtins/pedersen.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, test } from 'bun:test'; + +import { UndefinedValue } from 'errors/builtins'; + +import { Felt } from 'primitives/felt'; +import { Relocatable } from 'primitives/relocatable'; +import { Memory } from 'memory/memory'; +import { bitwiseHandler } from './bitwise'; +import { ExpectedFelt } from 'errors/virtualMachine'; +import { pedersenHandler } from './pedersen'; + +type PedersenInput = { + x: Felt; + y: Felt; + expected: Felt; +}; + +describe('Pedersen', () => { + const inputs: PedersenInput[] = [ + { + x: new Felt(0n), + y: new Felt(0n), + expected: new Felt( + 0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804n + ), + }, + { + x: new Felt(0n), + y: new Felt(1n), + expected: new Felt( + 0x46c9aeb066cc2f41c7124af30514f9e607137fbac950524f5fdace5788f9d43n + ), + }, + { + x: new Felt(1n), + y: new Felt(0n), + expected: new Felt( + 0x268a9d47dde48af4b6e2c33932ed1c13adec25555abaa837c376af4ea2f8a94n + ), + }, + { + x: new Felt(54n), + y: new Felt(1249832432n), + expected: new Felt( + 0x20120a7d08fd21654c72a9281841406543b16d00faaca1069332053c41c07b8n + ), + }, + ]; + + test.each(inputs)( + 'should properly compute pedersen hash', + ({ x, y, expected }: PedersenInput) => { + const memory = new Memory(); + const { segmentId } = memory.addSegment(pedersenHandler); + const addressHash = new Relocatable(segmentId, 2); + + memory.assertEq(new Relocatable(segmentId, 0), x); + memory.assertEq(new Relocatable(segmentId, 1), y); + + expect(memory.get(addressHash)).toEqual(expected); + } + ); + + test.each([0, 1])( + 'should throw UndefinedValue error when one of the two input is not constrained', + (offset: number) => { + const memory = new Memory(); + const { segmentId } = memory.addSegment(bitwiseHandler); + const address = new Relocatable(segmentId, offset); + const addressHash = new Relocatable(segmentId, 2); + + memory.assertEq(address, new Felt(0n)); + + expect(() => memory.get(addressHash)).toThrow( + new UndefinedValue((offset + 1) % 2) + ); + } + ); + + test('should throw ExpectedFelt error when trying to constrain an input cell to a Relocatable', () => { + const memory = new Memory(); + const { segmentId } = memory.addSegment(bitwiseHandler); + const addressHash = new Relocatable(segmentId, 2); + + const xAddr = new Relocatable(segmentId, 0); + const yAddr = new Relocatable(segmentId, 1); + const y = new Felt(1n); + + memory.assertEq(xAddr, xAddr); + memory.assertEq(yAddr, y); + + expect(() => memory.get(addressHash)).toThrow(new ExpectedFelt()); + }); +}); diff --git a/src/builtins/pedersen.ts b/src/builtins/pedersen.ts new file mode 100644 index 00000000..d3cdc77c --- /dev/null +++ b/src/builtins/pedersen.ts @@ -0,0 +1,38 @@ +import { UndefinedValue } from 'errors/builtins'; +import { BuiltinHandler } from './builtin'; +import { ExpectedFelt } from 'errors/virtualMachine'; +import { isFelt } from 'primitives/segmentValue'; +import { pedersen } from '@scure/starknet'; +import { Felt } from 'primitives/felt'; + +/** 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) { + return target[offset]; + } + + const xOffset = offset - pedersenIndex; + const xValue = target[xOffset]; + if (!xValue) throw new UndefinedValue(xOffset); + if (!isFelt(xValue)) throw new ExpectedFelt(); + const x = xValue.toBigInt(); + + const yOffset = xOffset + 1; + const yValue = target[yOffset]; + if (!yValue) throw new UndefinedValue(yOffset); + if (!isFelt(yValue)) throw new ExpectedFelt(); + const y = yValue.toBigInt(); + + return (target[offset] = new Felt(BigInt(pedersen(x, y)))); + }, +}; diff --git a/src/runners/cairoRunner.test.ts b/src/runners/cairoRunner.test.ts index fa4f55bb..bb7e8745 100644 --- a/src/runners/cairoRunner.test.ts +++ b/src/runners/cairoRunner.test.ts @@ -24,9 +24,15 @@ const EC_OP_PROGRAM_STRING = fs.readFileSync( 'utf8' ); +const PEDERSEN_PROGRAM_STRING = fs.readFileSync( + 'cairo_programs/cairo_0/pedersen_builtin.json', + 'utf8' +); + const FIBONACCI_PROGRAM = parseProgram(FIBONACCI_PROGRAM_STRING); const BITWISE_PROGRAM = parseProgram(BITWISE_PROGRAM_STRING); const EC_OP_PROGRAM = parseProgram(EC_OP_PROGRAM_STRING); +const PEDERSEN_PROGRAM = parseProgram(PEDERSEN_PROGRAM_STRING); describe('cairoRunner', () => { describe('constructor', () => { @@ -134,5 +140,37 @@ describe('cairoRunner', () => { expect(runner.vm.memory.get(executionEnd.sub(2))).toEqual(expectedRy); }); }); + + describe('pedersen', () => { + test('should properly compute Pedersen hashes of (0, 0), (0, 1), (1, 0) and (54, 1249832432) tuples', () => { + const runner = new CairoRunner(PEDERSEN_PROGRAM); + const config: RunOptions = { + relocate: true, + relocateOffset: 1, + }; + runner.run(config); + + const expectedHashes = [ + 0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804n, + 0x46c9aeb066cc2f41c7124af30514f9e607137fbac950524f5fdace5788f9d43n, + 0x268a9d47dde48af4b6e2c33932ed1c13adec25555abaa837c376af4ea2f8a94n, + 0x20120a7d08fd21654c72a9281841406543b16d00faaca1069332053c41c07b8n, + ].map((value) => new Felt(value)); + + const executionSize = runner.vm.memory.getSegmentSize(1); + const executionEnd = runner.executionBase.add(executionSize); + + const cellsPerPedersen = 3; + const start = expectedHashes.length * cellsPerPedersen - 1; + + expectedHashes.forEach((hash, index) => + expect( + runner.vm.memory.get( + executionEnd.sub(start - cellsPerPedersen * index) + ) + ).toEqual(hash) + ); + }); + }); }); });