diff --git a/.github/workflows/verifier-ray.yml b/.github/workflows/verifier-ray.yml new file mode 100644 index 00000000000..a45696cfe89 --- /dev/null +++ b/.github/workflows/verifier-ray.yml @@ -0,0 +1,85 @@ +name: verifier-ray + +on: + pull_request: + branches: + - main + paths: + - 'verifier-ray/**' + - 'prover-ray/**' + - '.github/workflows/verifier-ray.yml' + push: + branches: + - main + paths: + - 'verifier-ray/**' + - 'prover-ray/**' + - '.github/workflows/verifier-ray.yml' + workflow_call: + workflow_dispatch: + +permissions: + contents: read + actions: read + +env: + GO_VERSION: '1.25.7' + GO_CORSET_VERSION: 'v1.2.14' + GO_CORSET_REF_FOR_ZKC: 'v1.2.14' + ZIG_VERSION: '0.17.0-dev.251+0db721ec2' + +concurrency: + group: verifier-ray-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + test: + runs-on: gha-runner-scale-set-ubuntu-24-amd64-small + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: false + + # Keep this aligned with .github/actions/setup-arithmetization-riscv, but use + # newer Go because verifier-ray/prover-ray modules declare go 1.25.7. + - name: Install Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: "**/*.sum" + + - name: Install Zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + with: + version: ${{ env.ZIG_VERSION }} + + - name: Install riscv64-unknown-elf-as + run: sudo apt-get update && sudo apt-get install -y binutils-riscv64-unknown-elf + + - name: Install go-corset and zkc + run: | + go install github.com/consensys/go-corset/cmd/go-corset@${GO_CORSET_VERSION} + go install github.com/consensys/go-corset/cmd/zkc@${GO_CORSET_REF_FOR_ZKC} + go-corset --version + go version -m "$(which zkc)" + + - name: Verifier-ray format check + working-directory: verifier-ray + run: make fmt + + - name: Verifier-ray generated testdata + working-directory: verifier-ray + run: make verify-testdata + + - name: Verifier-ray build + working-directory: verifier-ray + run: make build + + - name: Verifier-ray tests + working-directory: verifier-ray + run: make test + + - name: ZKC verifier smoke test + working-directory: verifier-ray + run: make zkc-verify diff --git a/.gitignore b/.gitignore index 02cd69da1d6..e14b79bf390 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,10 @@ tmp/ typechain/ typechain-types/ generated/ +!/verifier-ray/src/generated/ +!/verifier-ray/src/generated/** +!/verifier-ray/testdata/generated/ +!/verifier-ray/testdata/generated/** __pycache__/ /linea-besu/plugins/linea-sequencer/**/site diff --git a/verifier-ray/.gitignore b/verifier-ray/.gitignore new file mode 100644 index 00000000000..482621a5d76 --- /dev/null +++ b/verifier-ray/.gitignore @@ -0,0 +1,3 @@ +.zig-cache/ +zig-out/ + diff --git a/verifier-ray/Makefile b/verifier-ray/Makefile new file mode 100644 index 00000000000..5e58fb207c4 --- /dev/null +++ b/verifier-ray/Makefile @@ -0,0 +1,54 @@ +SHELL := /bin/bash + +.PHONY: ci fmt fmt-zig fmt-codegen fmt-testdata-generate build build-zig build-release test test-zig test-generated-zig test-codegen generate-testdata verify-testdata zkc-verify clean + +ci: fmt build test zkc-verify + +fmt: fmt-zig fmt-codegen fmt-testdata-generate + +fmt-zig: + zig fmt --check build.zig src test testdata/generated + +fmt-codegen: + @if [[ -n "$$(gofmt -l codegen)" ]]; then \ + echo "please run gofmt"; \ + gofmt -l codegen; \ + exit 1; \ + fi + +fmt-testdata-generate: + @if [[ -n "$$(gofmt -l testdata/generate)" ]]; then \ + echo "please run gofmt"; \ + gofmt -l testdata/generate; \ + exit 1; \ + fi + +build: build-zig + +build-zig: + zig build + +build-release: + zig build -Doptimize=ReleaseSmall -Dstrip=true + +test: test-zig test-codegen + +test-zig: verify-testdata + zig build test + +test-generated-zig: test-zig + +test-codegen: + cd codegen && go test ./... + +generate-testdata: + cd testdata/generate && go run . + +verify-testdata: generate-testdata + git diff --exit-code -- testdata/generated/vectors.zig + +zkc-verify: + @echo "zkc verifier smoke test is not wired yet; skipping." + +clean: + rm -rf .zig-cache zig-out diff --git a/verifier-ray/README.md b/verifier-ray/README.md new file mode 100644 index 00000000000..8daf0becbeb --- /dev/null +++ b/verifier-ray/README.md @@ -0,0 +1,31 @@ +# verifier-ray + +`verifier-ray` is the initial Zig verifier package for Ray proofs. + +The package is intentionally independent from `prover-ray` at the directory +level. The fixed verifier runtime, field arithmetic, cryptographic primitives, +and zkVM precompile interface live here. Code generation can also live here and +import `prover-ray` structures when needed. + +## Layout + +- `src/` contains the Zig verifier library and executable entry point. +- `src/generated/` contains generated verifier stubs. +- `codegen/` contains the Go code generation tool skeleton. +- `test/` contains Zig unit tests. +- `testdata/` holds fixtures exported from `prover-ray`. + +## Local Checks + +```bash +make fmt +make verify-testdata +make build +make build-release +make test +make zkc-verify +``` + +The current implementation covers Milestone 1 static field, extension, +polynomial, Poseidon2, and Fiat-Shamir primitives with prover-ray golden tests. +Vortex verification and zkVM `zkc` execution are still placeholders. diff --git a/verifier-ray/build.zig b/verifier-ray/build.zig new file mode 100644 index 00000000000..ab2d22af0e1 --- /dev/null +++ b/verifier-ray/build.zig @@ -0,0 +1,49 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const strip = b.option(bool, "strip", "Omit debug symbols") orelse (optimize == .ReleaseSmall); + + const verifier_mod = b.addModule("verifier_ray", .{ + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + .strip = strip, + }); + const test_vectors_mod = b.addModule("test_vectors", .{ + .root_source_file = b.path("testdata/generated/vectors.zig"), + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = "verifier-ray", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .strip = strip, + .imports = &.{ + .{ .name = "verifier_ray", .module = verifier_mod }, + }, + }), + }); + b.installArtifact(exe); + + const unit_tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("test/all.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "verifier_ray", .module = verifier_mod }, + .{ .name = "test_vectors", .module = test_vectors_mod }, + }, + }), + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run verifier-ray unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/verifier-ray/build.zig.zon b/verifier-ray/build.zig.zon new file mode 100644 index 00000000000..42e8d2e6ab3 --- /dev/null +++ b/verifier-ray/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .verifier_ray, + .fingerprint = 0xd1f807a3b6cefe12, + .version = "0.1.0", + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{ + "README.md", + "build.zig", + "build.zig.zon", + "src", + "test", + "testdata", + }, +} diff --git a/verifier-ray/codegen/cmd/ray-zig-codegen/main.go b/verifier-ray/codegen/cmd/ray-zig-codegen/main.go new file mode 100644 index 00000000000..22ab6f4224a --- /dev/null +++ b/verifier-ray/codegen/cmd/ray-zig-codegen/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/consensys/linea-monorepo/verifier-ray/codegen/internal/generator" +) + +func main() { + entryPoint := flag.String("entry", "verifyGenerated", "name of the generated Zig verifier entry point") + flag.Parse() + + system := generator.System{ + Rounds: []generator.Round{ + {ID: 0, VerifierActions: []string{"stub"}}, + }, + } + + if err := generator.Generate(system, generator.Options{EntryPoint: *entryPoint}, os.Stdout); err != nil { + fmt.Fprintf(os.Stderr, "ray-zig-codegen: %v\n", err) + os.Exit(1) + } +} diff --git a/verifier-ray/codegen/go.mod b/verifier-ray/codegen/go.mod new file mode 100644 index 00000000000..25e48fd5958 --- /dev/null +++ b/verifier-ray/codegen/go.mod @@ -0,0 +1,4 @@ +module github.com/consensys/linea-monorepo/verifier-ray/codegen + +go 1.25.7 + diff --git a/verifier-ray/codegen/internal/generator/emitters.go b/verifier-ray/codegen/internal/generator/emitters.go new file mode 100644 index 00000000000..9423e2a3583 --- /dev/null +++ b/verifier-ray/codegen/internal/generator/emitters.go @@ -0,0 +1,4 @@ +package generator + +// This file is reserved for action-specific emitters once prover-ray verifier +// actions are connected to verifier-ray code generation. diff --git a/verifier-ray/codegen/internal/generator/generator.go b/verifier-ray/codegen/internal/generator/generator.go new file mode 100644 index 00000000000..ccfb64b3a16 --- /dev/null +++ b/verifier-ray/codegen/internal/generator/generator.go @@ -0,0 +1,57 @@ +package generator + +import ( + "fmt" + "io" +) + +// Options controls Zig verifier source generation. +type Options struct { + // EntryPoint is the generated verifier function name. + EntryPoint string +} + +// System is the minimal codegen input used by the initial stub generator. +// It will be replaced with, or adapted from, prover-ray compiled IOP objects. +type System struct { + Rounds []Round +} + +// Round describes one verifier round in generated output. +type Round struct { + ID int + VerifierActions []string +} + +// Generate emits a compilable Zig verifier stub. +func Generate(system System, opts Options, dst io.Writer) error { + if opts.EntryPoint == "" { + opts.EntryPoint = defaultEntryPoint + } + + w := &Writer{} + w.Line("// Code generated by verifier-ray/codegen. DO NOT EDIT.") + w.Line("const proof_mod = @import(\"../proof.zig\");") + w.Line("const runtime_mod = @import(\"../runtime.zig\");") + w.Line("const verifier = @import(\"../verifier.zig\");") + w.Blank() + w.Line("pub fn %s(rt: *runtime_mod.Runtime, p: proof_mod.Proof) verifier.VerifyError!void {", opts.EntryPoint) + w.In() + w.Line("_ = p;") + for _, round := range system.Rounds { + w.Line("// Round %d", round.ID) + for _, action := range round.VerifierActions { + w.Line("// verifier action: %s", action) + } + w.Line("rt.advanceRound();") + } + w.Line("return verifier.VerifyError.Unsupported;") + w.Out() + w.Line("}") + + _, err := io.WriteString(dst, w.String()) + if err != nil { + return fmt.Errorf("write generated Zig: %w", err) + } + return nil +} diff --git a/verifier-ray/codegen/internal/generator/generator_test.go b/verifier-ray/codegen/internal/generator/generator_test.go new file mode 100644 index 00000000000..b734db1d3eb --- /dev/null +++ b/verifier-ray/codegen/internal/generator/generator_test.go @@ -0,0 +1,42 @@ +package generator_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/consensys/linea-monorepo/verifier-ray/codegen/internal/generator" +) + +func TestGenerate_DefaultEntryPoint(t *testing.T) { + var buf bytes.Buffer + err := generator.Generate(generator.System{ + Rounds: []generator.Round{ + {ID: 0, VerifierActions: []string{"commitment"}}, + {ID: 1, VerifierActions: []string{"quotient"}}, + }, + }, generator.Options{}, &buf) + if err != nil { + t.Fatalf("Generate returned error: %v", err) + } + + src := buf.String() + if !strings.Contains(src, "pub fn verifyGenerated") { + t.Fatalf("generated source is missing default entry point:\n%s", src) + } + if got := strings.Count(src, "rt.advanceRound();"); got != 2 { + t.Fatalf("expected one advance per round, got %d:\n%s", got, src) + } +} + +func TestGenerate_CustomEntryPoint(t *testing.T) { + var buf bytes.Buffer + err := generator.Generate(generator.System{}, generator.Options{EntryPoint: "verifyFib"}, &buf) + if err != nil { + t.Fatalf("Generate returned error: %v", err) + } + + if !strings.Contains(buf.String(), "pub fn verifyFib") { + t.Fatalf("generated source is missing custom entry point:\n%s", buf.String()) + } +} diff --git a/verifier-ray/codegen/internal/generator/options.go b/verifier-ray/codegen/internal/generator/options.go new file mode 100644 index 00000000000..f3870dd107e --- /dev/null +++ b/verifier-ray/codegen/internal/generator/options.go @@ -0,0 +1,3 @@ +package generator + +const defaultEntryPoint = "verifyGenerated" diff --git a/verifier-ray/codegen/internal/generator/writer.go b/verifier-ray/codegen/internal/generator/writer.go new file mode 100644 index 00000000000..d46fddb1745 --- /dev/null +++ b/verifier-ray/codegen/internal/generator/writer.go @@ -0,0 +1,35 @@ +package generator + +import ( + "fmt" + "strings" +) + +// Writer accumulates generated Zig source with spaces for indentation. +type Writer struct { + buf strings.Builder + indent int +} + +func (w *Writer) Line(format string, args ...any) { + fmt.Fprintf(&w.buf, "%s%s\n", strings.Repeat(" ", w.indent), fmt.Sprintf(format, args...)) +} + +func (w *Writer) Blank() { + w.buf.WriteByte('\n') +} + +func (w *Writer) In() { + w.indent++ +} + +func (w *Writer) Out() { + if w.indent == 0 { + panic("generator.Writer.Out called with zero indentation") + } + w.indent-- +} + +func (w *Writer) String() string { + return w.buf.String() +} diff --git a/verifier-ray/src/crypto/fiat_shamir.zig b/verifier-ray/src/crypto/fiat_shamir.zig new file mode 100644 index 00000000000..0e789628a43 --- /dev/null +++ b/verifier-ray/src/crypto/fiat_shamir.zig @@ -0,0 +1,51 @@ +const field = @import("../field/koalabear.zig"); +const ext = @import("../field/koalabear_ext.zig"); +const poseidon2 = @import("poseidon2.zig"); + +pub const Transcript = struct { + hasher: poseidon2.MDHasher, + + pub fn init() Transcript { + return .{ .hasher = poseidon2.MDHasher.init() }; + } + + pub fn updateElement(self: *Transcript, value: field.Element) void { + self.hasher.writeElement(value); + } + + pub fn updateElements(self: *Transcript, values: []const field.Element) void { + self.hasher.writeElements(values); + } + + pub fn updateExt(self: *Transcript, values: []const ext.Ext) void { + for (values) |value| { + self.hasher.writeElements(&value.limbs); + } + } + + pub fn randomField(self: *Transcript) poseidon2.Digest { + const challenge = self.hasher.sumElement(); + self.updateElement(field.Element.zero()); + return challenge; + } + + pub fn randomExt(self: *Transcript) ext.Ext { + const challenge = self.randomField(); + return .{ .limbs = .{ + challenge[0], + challenge[1], + challenge[2], + challenge[3], + } }; + } + + pub fn state(self: Transcript) poseidon2.Digest { + return self.hasher.getState(); + } + + pub fn setState(self: *Transcript, digest: poseidon2.Digest) void { + self.hasher.setState(digest); + } + + pub const challengeExt = randomExt; +}; diff --git a/verifier-ray/src/crypto/poseidon2.zig b/verifier-ray/src/crypto/poseidon2.zig new file mode 100644 index 00000000000..98f60305055 --- /dev/null +++ b/verifier-ray/src/crypto/poseidon2.zig @@ -0,0 +1,249 @@ +const field = @import("../field/koalabear.zig"); +const constants = @import("poseidon2_constants.zig"); + +pub const Error = field.Error || error{InvalidInputLength}; +pub const Digest = [8]field.Element; +pub const block_size = 8; +pub const digest_bytes = block_size * field.bytes; + +const full_rounds = 6; +const partial_rounds = 21; +const total_rounds = full_rounds + partial_rounds; + +pub fn zeroDigest() Digest { + return zeroArray(block_size); +} + +pub fn compress(left: Digest, right: Digest) Digest { + var state: [16]field.Element = undefined; + @memcpy(state[0..block_size], &left); + @memcpy(state[block_size..], &right); + + var out = right; + permutation(16, &state); + for (&out, state[block_size..]) |*dst, state_limb| { + dst.* = dst.add(state_limb); + } + return out; +} + +pub fn compressSlices(left: []const field.Element, right: []const field.Element) Error!Digest { + if (left.len != block_size or right.len != block_size) return Error.InvalidInputLength; + return compress(left[0..block_size].*, right[0..block_size].*); +} + +pub fn digestToBytes(digest: Digest) [digest_bytes]u8 { + var out: [digest_bytes]u8 = undefined; + for (digest, 0..) |limb, i| { + const encoded = limb.toBytes(); + @memcpy(out[i * field.bytes .. (i + 1) * field.bytes], &encoded); + } + return out; +} + +pub const MDHasher = struct { + state: Digest, + buffer: [block_size]field.Element, + buffer_len: usize, + + pub fn init() MDHasher { + return .{ + .state = zeroDigest(), + .buffer = zeroArray(block_size), + .buffer_len = 0, + }; + } + + pub fn writeElement(self: *MDHasher, value: field.Element) void { + self.buffer[self.buffer_len] = value; + self.buffer_len += 1; + if (self.buffer_len == block_size) { + self.state = compress(self.state, self.buffer); + self.buffer_len = 0; + } + } + + pub fn writeElements(self: *MDHasher, values: []const field.Element) void { + for (values) |value| { + self.writeElement(value); + } + } + + pub fn writeBytes(self: *MDHasher, encoded: []const u8) Error!void { + if (encoded.len % field.bytes != 0) return Error.InvalidInputLength; + var offset: usize = 0; + while (offset < encoded.len) : (offset += field.bytes) { + self.writeElement(try field.Element.fromBytesCanonicalSlice(encoded[offset .. offset + field.bytes])); + } + } + + pub fn sumElement(self: *MDHasher) Digest { + if (self.buffer_len != 0) { + var block: Digest = zeroArray(block_size); + // Match prover-ray MDHasher: partial blocks are zero-left-padded. + @memcpy(block[block_size - self.buffer_len ..], self.buffer[0..self.buffer_len]); + self.state = compress(self.state, block); + self.buffer_len = 0; + } + return self.state; + } + + pub fn sumBytes(self: *MDHasher) [digest_bytes]u8 { + return digestToBytes(self.sumElement()); + } + + pub fn getState(self: MDHasher) Digest { + var copy = self; + return copy.sumElement(); + } + + pub fn setState(self: *MDHasher, state: Digest) void { + self.state = state; + self.buffer_len = 0; + } +}; + +pub fn hashElements(values: []const field.Element) Digest { + var h = MDHasher.init(); + h.writeElements(values); + return h.sumElement(); +} + +fn permutation(comptime width: usize, state: *[width]field.Element) void { + if (width != constants.width) @compileError("Poseidon2 Koalabear verifier constants support width 16"); + const round_keys = &constants.round_keys; + + matMulExternalInPlace(width, state); + + const half_full = full_rounds / 2; + for (0..half_full) |round| { + addRoundKey(width, state, round_keys, round, width); + sBoxAll(width, state); + matMulExternalInPlace(width, state); + } + + for (half_full..half_full + partial_rounds) |round| { + addRoundKey(width, state, round_keys, round, 1); + state[0] = cube(state[0]); + matMulInternalInPlace(width, state); + } + + for (half_full + partial_rounds..total_rounds) |round| { + addRoundKey(width, state, round_keys, round, width); + sBoxAll(width, state); + matMulExternalInPlace(width, state); + } +} + +fn addRoundKey( + comptime width: usize, + state: *[width]field.Element, + round_keys: *const [total_rounds][width]field.Element, + round: usize, + key_len: usize, +) void { + for (0..key_len) |i| { + state[i] = state[i].add(round_keys.*[round][i]); + } +} + +fn sBoxAll(comptime width: usize, state: *[width]field.Element) void { + for (&state.*) |*limb| { + limb.* = cube(limb.*); + } +} + +fn cube(value: field.Element) field.Element { + return value.square().mul(value); +} + +fn matMulM4InPlace(comptime width: usize, state: *[width]field.Element) void { + for (0..width / 4) |chunk| { + const offset = 4 * chunk; + const t01 = state[offset].add(state[offset + 1]); + const t23 = state[offset + 2].add(state[offset + 3]); + const t0123 = t01.add(t23); + const t01123 = t0123.add(state[offset + 1]); + const t01233 = t0123.add(state[offset + 3]); + + state[offset + 3] = state[offset].double().add(t01233); + state[offset + 1] = state[offset + 2].double().add(t01123); + state[offset] = t01.add(t01123); + state[offset + 2] = t23.add(t01233); + } +} + +fn matMulExternalInPlace(comptime width: usize, state: *[width]field.Element) void { + matMulM4InPlace(width, state); + + var sums: [4]field.Element = zeroArray(4); + for (0..width / 4) |chunk| { + const offset = 4 * chunk; + sums[0] = sums[0].add(state[offset]); + sums[1] = sums[1].add(state[offset + 1]); + sums[2] = sums[2].add(state[offset + 2]); + sums[3] = sums[3].add(state[offset + 3]); + } + + for (0..width / 4) |chunk| { + const offset = 4 * chunk; + state[offset] = state[offset].add(sums[0]); + state[offset + 1] = state[offset + 1].add(sums[1]); + state[offset + 2] = state[offset + 2].add(sums[2]); + state[offset + 3] = state[offset + 3].add(sums[3]); + } +} + +fn zeroArray(comptime len: usize) [len]field.Element { + var out: [len]field.Element = undefined; + for (&out) |*limb| { + limb.* = field.Element.zero(); + } + return out; +} + +fn matMulInternalInPlace(comptime width: usize, state: *[width]field.Element) void { + var sum = state[0]; + for (state[1..]) |limb| { + sum = sum.add(limb); + } + + state[0] = sum.sub(state[0].double()); + state[1] = sum.add(state[1]); + state[2] = sum.add(state[2].double()); + state[3] = sum.add(state[3].halve()); + state[4] = sum.add(state[4].mul(field.Element.init(3))); + state[5] = sum.add(state[5].double().double()); + state[6] = sum.sub(state[6].halve()); + state[7] = sum.sub(state[7].mul(field.Element.init(3))); + state[8] = sum.sub(state[8].double().double()); + state[9] = sum.add(state[9].mul2ExpNegN(8)); + + switch (width) { + 16 => { + state[10] = sum.add(state[10].mul2ExpNegN(3)); + state[11] = sum.add(state[11].mul2ExpNegN(24)); + state[12] = sum.sub(state[12].mul2ExpNegN(8)); + state[13] = sum.sub(state[13].mul2ExpNegN(3)); + state[14] = sum.sub(state[14].mul2ExpNegN(4)); + state[15] = sum.sub(state[15].mul2ExpNegN(24)); + }, + 24 => { + state[10] = sum.add(state[10].mul2ExpNegN(2)); + state[11] = sum.add(state[11].mul2ExpNegN(3)); + state[12] = sum.add(state[12].mul2ExpNegN(4)); + state[13] = sum.add(state[13].mul2ExpNegN(5)); + state[14] = sum.add(state[14].mul2ExpNegN(6)); + state[15] = sum.add(state[15].mul2ExpNegN(24)); + state[16] = sum.sub(state[16].mul2ExpNegN(8)); + state[17] = sum.sub(state[17].mul2ExpNegN(3)); + state[18] = sum.sub(state[18].mul2ExpNegN(4)); + state[19] = sum.sub(state[19].mul2ExpNegN(5)); + state[20] = sum.sub(state[20].mul2ExpNegN(6)); + state[21] = sum.sub(state[21].mul2ExpNegN(7)); + state[22] = sum.sub(state[22].mul2ExpNegN(9)); + state[23] = sum.sub(state[23].mul2ExpNegN(24)); + }, + else => unreachable, + } +} diff --git a/verifier-ray/src/crypto/poseidon2_constants.zig b/verifier-ray/src/crypto/poseidon2_constants.zig new file mode 100644 index 00000000000..1dfdd9dca82 --- /dev/null +++ b/verifier-ray/src/crypto/poseidon2_constants.zig @@ -0,0 +1,35 @@ +// Constants generated from gnark-crypto Koalabear Poseidon2 parameters. +// Width = 16, full rounds = 6, partial rounds = 21, S-box degree = 3. +const field = @import("../field/koalabear.zig"); + +pub const width = 16; + +pub const round_keys = [_][width]field.Element{ + .{ field.Element.init(1954447561), field.Element.init(2103440337), field.Element.init(2034503157), field.Element.init(267205), field.Element.init(1104192591), field.Element.init(169624589), field.Element.init(1292298316), field.Element.init(1544927266), field.Element.init(1194641531), field.Element.init(1295682190), field.Element.init(126819829), field.Element.init(438096056), field.Element.init(2042625117), field.Element.init(1622257744), field.Element.init(1480232785), field.Element.init(2053436108) }, + .{ field.Element.init(822100724), field.Element.init(475203915), field.Element.init(475839081), field.Element.init(1836055519), field.Element.init(151122588), field.Element.init(845034931), field.Element.init(257347197), field.Element.init(23729110), field.Element.init(1935347690), field.Element.init(1665180051), field.Element.init(1503082005), field.Element.init(1159038489), field.Element.init(991271251), field.Element.init(402285205), field.Element.init(765189988), field.Element.init(1686686292) }, + .{ field.Element.init(743982662), field.Element.init(603553284), field.Element.init(1737633918), field.Element.init(430192715), field.Element.init(383616518), field.Element.init(638298469), field.Element.init(1107446107), field.Element.init(2124515806), field.Element.init(674287349), field.Element.init(232857957), field.Element.init(1458811973), field.Element.init(46165051), field.Element.init(1663439075), field.Element.init(1833093609), field.Element.init(1888775269), field.Element.init(1410190341) }, + .{ field.Element.init(271263440), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(648183298), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1662653166), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1984135584), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(594964655), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(108023522), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(849546096), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1961993938), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1546336947), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1036613726), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(452088758), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(275827416), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(763236035), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1068717067), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1580958419), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1376393748), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(892777736), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1345121022), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(908739241), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(908871000), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(1053550888), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0), field.Element.init(0) }, + .{ field.Element.init(287411648), field.Element.init(376444094), field.Element.init(2089726690), field.Element.init(980171675), field.Element.init(2047031047), field.Element.init(302932709), field.Element.init(238775784), field.Element.init(1868082076), field.Element.init(528049195), field.Element.init(692949401), field.Element.init(914145439), field.Element.init(454206937), field.Element.init(1875391778), field.Element.init(1039709554), field.Element.init(1699415150), field.Element.init(1757414906) }, + .{ field.Element.init(162854383), field.Element.init(1623226071), field.Element.init(589191500), field.Element.init(2074255977), field.Element.init(747734520), field.Element.init(975716605), field.Element.init(1904309685), field.Element.init(783242357), field.Element.init(589615138), field.Element.init(20876678), field.Element.init(1881026572), field.Element.init(584164528), field.Element.init(657928521), field.Element.init(1326166727), field.Element.init(1883117132), field.Element.init(1555583597) }, + .{ field.Element.init(1386260816), field.Element.init(1546305176), field.Element.init(872240208), field.Element.init(332293333), field.Element.init(1672038440), field.Element.init(1194373414), field.Element.init(1310038338), field.Element.init(1200824268), field.Element.init(1706624771), field.Element.init(868814277), field.Element.init(2118495825), field.Element.init(697283482), field.Element.init(1322377074), field.Element.init(1337672358), field.Element.init(1026801894), field.Element.init(1566697951) }, +}; diff --git a/verifier-ray/src/field/koalabear.zig b/verifier-ray/src/field/koalabear.zig new file mode 100644 index 00000000000..b54c475bc27 --- /dev/null +++ b/verifier-ray/src/field/koalabear.zig @@ -0,0 +1,179 @@ +pub const bytes: usize = 4; +pub const modulus: u32 = 2_130_706_433; +pub const multiplicative_gen: u32 = 3; +pub const max_order_root: usize = 24; +pub const root_of_unity: u32 = 1_791_270_792; +pub const mont_constant: u32 = 33_554_430; +pub const mont_constant_inv: u32 = 1_057_030_144; + +pub const Error = error{NonCanonicalEncoding}; + +pub const Element = struct { + value: u32, + + pub fn init(raw: u64) Element { + return .{ .value = @as(u32, @intCast(raw % modulus)) }; + } + + pub fn zero() Element { + return .{ .value = 0 }; + } + + pub fn one() Element { + return .{ .value = 1 }; + } + + pub fn fromCanonical(value: u32) Error!Element { + if (value >= modulus) return Error.NonCanonicalEncoding; + return .{ .value = value }; + } + + pub fn fromBytesCanonical(encoded: [bytes]u8) Error!Element { + return fromCanonical(readU32BigEndian(encoded)); + } + + pub fn fromBytesCanonicalSlice(encoded: []const u8) Error!Element { + if (encoded.len != bytes) return Error.NonCanonicalEncoding; + return fromBytesCanonical(.{ encoded[0], encoded[1], encoded[2], encoded[3] }); + } + + pub fn fromBytesWide(encoded: []const u8) Element { + var acc: u64 = 0; + for (encoded) |byte| { + acc = ((acc << 8) + byte) % modulus; + } + return init(acc); + } + + pub fn toBytes(self: Element) [bytes]u8 { + return writeU32BigEndian(self.value); + } + + pub fn eql(self: Element, rhs: Element) bool { + return self.value == rhs.value; + } + + pub fn isZero(self: Element) bool { + return self.value == 0; + } + + pub fn add(self: Element, rhs: Element) Element { + const sum = @as(u64, self.value) + @as(u64, rhs.value); + if (sum >= modulus) return .{ .value = @as(u32, @intCast(sum - modulus)) }; + return .{ .value = @as(u32, @intCast(sum)) }; + } + + pub fn sub(self: Element, rhs: Element) Element { + if (self.value >= rhs.value) { + return .{ .value = self.value - rhs.value }; + } + return .{ .value = modulus - (rhs.value - self.value) }; + } + + pub fn neg(self: Element) Element { + if (self.isZero()) return self; + return .{ .value = modulus - self.value }; + } + + pub fn double(self: Element) Element { + return self.add(self); + } + + pub fn halve(self: Element) Element { + if ((self.value & 1) == 0) return .{ .value = self.value >> 1 }; + return .{ .value = @as(u32, @intCast((@as(u64, self.value) + modulus) >> 1)) }; + } + + pub fn mul(self: Element, rhs: Element) Element { + return init(@as(u64, self.value) * @as(u64, rhs.value)); + } + + pub fn square(self: Element) Element { + return self.mul(self); + } + + pub fn pow(self: Element, exponent: u64) Element { + var result = Element.one(); + var base = self; + var exp = exponent; + while (exp != 0) : (exp >>= 1) { + if ((exp & 1) == 1) { + result = result.mul(base); + } + base = base.square(); + } + return result; + } + + pub fn inverse(self: Element) Element { + if (self.isZero()) unreachable; + return self.pow(modulus - 2); + } + + pub fn div(self: Element, rhs: Element) Element { + return self.mul(rhs.inverse()); + } + + pub fn mul2ExpNegN(self: Element, n: u32) Element { + if (n > 32) unreachable; + var result = self; + var i: u32 = 0; + while (i < n) : (i += 1) { + result = result.halve(); + } + return result; + } +}; + +pub fn zero() Element { + return Element.zero(); +} + +pub fn one() Element { + return Element.one(); +} + +pub fn rootOfUnityBy(cardinality: usize) Error!Element { + if (!isPowerOfTwo(cardinality)) { + return Error.NonCanonicalEncoding; + } + const log_n = log2PowerOfTwo(cardinality); + if (log_n > max_order_root) return Error.NonCanonicalEncoding; + + var result = Element.init(root_of_unity); + var i: usize = log_n; + while (i < max_order_root) : (i += 1) { + result = result.square(); + } + return result; +} + +pub fn isPowerOfTwo(value: usize) bool { + return value != 0 and (value & (value - 1)) == 0; +} + +pub fn log2PowerOfTwo(value: usize) usize { + if (!isPowerOfTwo(value)) unreachable; + var n = value; + var result: usize = 0; + while (n > 1) : (n >>= 1) { + result += 1; + } + return result; +} + +fn readU32BigEndian(encoded: [bytes]u8) u32 { + return @as(u32, encoded[3]) | + (@as(u32, encoded[2]) << 8) | + (@as(u32, encoded[1]) << 16) | + (@as(u32, encoded[0]) << 24); +} + +fn writeU32BigEndian(value: u32) [bytes]u8 { + return .{ + @as(u8, @intCast((value >> 24) & 0xff)), + @as(u8, @intCast((value >> 16) & 0xff)), + @as(u8, @intCast((value >> 8) & 0xff)), + @as(u8, @intCast(value & 0xff)), + }; +} diff --git a/verifier-ray/src/field/koalabear_ext.zig b/verifier-ray/src/field/koalabear_ext.zig new file mode 100644 index 00000000000..894c19ea5be --- /dev/null +++ b/verifier-ray/src/field/koalabear_ext.zig @@ -0,0 +1,174 @@ +const base = @import("koalabear.zig"); + +pub const degree = 4; +pub const bytes = degree * base.bytes; + +pub const Ext = struct { + /// Limb order matches prover-ray and gnark-crypto: + /// B0.A0, B0.A1, B1.A0, B1.A1 for B0 + B1*v with v^2 = u and u^2 = 3. + limbs: [degree]base.Element, + + pub fn zero() Ext { + return .{ .limbs = .{ + base.Element.zero(), + base.Element.zero(), + base.Element.zero(), + base.Element.zero(), + } }; + } + + pub fn one() Ext { + return lift(base.Element.one()); + } + + pub fn lift(value: base.Element) Ext { + return .{ .limbs = .{ + value, + base.Element.zero(), + base.Element.zero(), + base.Element.zero(), + } }; + } + + pub fn isZero(self: Ext) bool { + for (self.limbs) |limb| { + if (!limb.isZero()) return false; + } + return true; + } + + pub fn isBase(self: Ext) bool { + return self.limbs[1].isZero() and self.limbs[2].isZero() and self.limbs[3].isZero(); + } + + pub fn eql(self: Ext, rhs: Ext) bool { + for (self.limbs, rhs.limbs) |lhs_limb, rhs_limb| { + if (!lhs_limb.eql(rhs_limb)) return false; + } + return true; + } + + pub fn add(self: Ext, rhs: Ext) Ext { + var out = Ext.zero(); + for (&out.limbs, self.limbs, rhs.limbs) |*dst, lhs_limb, rhs_limb| { + dst.* = lhs_limb.add(rhs_limb); + } + return out; + } + + pub fn sub(self: Ext, rhs: Ext) Ext { + var out = Ext.zero(); + for (&out.limbs, self.limbs, rhs.limbs) |*dst, lhs_limb, rhs_limb| { + dst.* = lhs_limb.sub(rhs_limb); + } + return out; + } + + pub fn neg(self: Ext) Ext { + var out = Ext.zero(); + for (&out.limbs, self.limbs) |*dst, limb| { + dst.* = limb.neg(); + } + return out; + } + + pub fn mulByBase(self: Ext, rhs: base.Element) Ext { + var out = Ext.zero(); + for (&out.limbs, self.limbs) |*dst, limb| { + dst.* = limb.mul(rhs); + } + return out; + } + + pub fn divByBase(self: Ext, rhs: base.Element) Ext { + return self.mulByBase(rhs.inverse()); + } + + pub fn mul(self: Ext, rhs: Ext) Ext { + const a0 = E2{ .a0 = self.limbs[0], .a1 = self.limbs[1] }; + const a1 = E2{ .a0 = self.limbs[2], .a1 = self.limbs[3] }; + const b0 = E2{ .a0 = rhs.limbs[0], .a1 = rhs.limbs[1] }; + const b1 = E2{ .a0 = rhs.limbs[2], .a1 = rhs.limbs[3] }; + + const a0b0 = a0.mul(b0); + const a1b1 = a1.mul(b1); + const c0 = a0b0.add(a1b1.mulByU()); + const c1 = a0.mul(b1).add(a1.mul(b0)); + + return .{ .limbs = .{ c0.a0, c0.a1, c1.a0, c1.a1 } }; + } + + pub fn square(self: Ext) Ext { + return self.mul(self); + } + + pub fn pow(self: Ext, exponent: u128) Ext { + var result = Ext.one(); + var power = self; + var exp = exponent; + while (exp != 0) : (exp >>= 1) { + if ((exp & 1) == 1) { + result = result.mul(power); + } + power = power.square(); + } + return result; + } + + pub fn inverse(self: Ext) Ext { + if (self.isZero()) unreachable; + return self.pow(field_order_four_minus_two); + } + + pub fn div(self: Ext, rhs: Ext) Ext { + return self.mul(rhs.inverse()); + } + + pub fn toBytes(self: Ext) [bytes]u8 { + var out: [bytes]u8 = undefined; + for (self.limbs, 0..) |limb, i| { + const encoded = limb.toBytes(); + @memcpy(out[i * base.bytes .. (i + 1) * base.bytes], &encoded); + } + return out; + } + + pub fn fromBytesCanonical(encoded: [bytes]u8) base.Error!Ext { + return .{ .limbs = .{ + try base.Element.fromBytesCanonical(encoded[0..4].*), + try base.Element.fromBytesCanonical(encoded[4..8].*), + try base.Element.fromBytesCanonical(encoded[8..12].*), + try base.Element.fromBytesCanonical(encoded[12..16].*), + } }; + } +}; + +const field_order = @as(u128, base.modulus); +const field_order_four_minus_two = field_order * field_order * field_order * field_order - 2; + +const E2 = struct { + a0: base.Element, + a1: base.Element, + + fn add(self: E2, rhs: E2) E2 { + return .{ .a0 = self.a0.add(rhs.a0), .a1 = self.a1.add(rhs.a1) }; + } + + fn mul(self: E2, rhs: E2) E2 { + const c0 = self.a0.mul(rhs.a0).add(self.a1.mul(rhs.a1).mul(base.Element.init(3))); + const c1 = self.a0.mul(rhs.a1).add(self.a1.mul(rhs.a0)); + return .{ .a0 = c0, .a1 = c1 }; + } + + fn mulByU(self: E2) E2 { + return .{ .a0 = self.a1.mul(base.Element.init(3)), .a1 = self.a0 }; + } +}; + +pub fn zero() Ext { + return Ext.zero(); +} + +pub fn one() Ext { + return Ext.one(); +} diff --git a/verifier-ray/src/field/vec.zig b/verifier-ray/src/field/vec.zig new file mode 100644 index 00000000000..6cccca4232e --- /dev/null +++ b/verifier-ray/src/field/vec.zig @@ -0,0 +1,41 @@ +const base = @import("koalabear.zig"); +const ext = @import("koalabear_ext.zig"); + +pub const ElementSlice = []const base.Element; +pub const ExtSlice = []const ext.Ext; + +pub fn ElementArray(comptime len: usize) type { + return [len]base.Element; +} + +pub fn ExtArray(comptime len: usize) type { + return [len]ext.Ext; +} + +pub fn allZero(values: ElementSlice) bool { + for (values) |value| { + if (!value.isZero()) return false; + } + return true; +} + +pub fn allZeroExt(values: ExtSlice) bool { + for (values) |value| { + if (!value.isZero()) return false; + } + return true; +} + +pub fn batchInvertBase(out: []base.Element, values: []const base.Element) error{LengthMismatch}!void { + if (out.len != values.len) return error.LengthMismatch; + for (values, out) |value, *dst| { + dst.* = if (value.isZero()) base.Element.zero() else value.inverse(); + } +} + +pub fn batchInvertExt(out: []ext.Ext, values: []const ext.Ext) error{LengthMismatch}!void { + if (out.len != values.len) return error.LengthMismatch; + for (values, out) |value, *dst| { + dst.* = if (value.isZero()) ext.Ext.zero() else value.inverse(); + } +} diff --git a/verifier-ray/src/generated/stub.zig b/verifier-ray/src/generated/stub.zig new file mode 100644 index 00000000000..faaeaef4cc7 --- /dev/null +++ b/verifier-ray/src/generated/stub.zig @@ -0,0 +1,9 @@ +const proof_mod = @import("../proof.zig"); +const runtime_mod = @import("../runtime.zig"); +const verifier = @import("../verifier.zig"); + +pub fn verifyGenerated(rt: *runtime_mod.Runtime, p: proof_mod.Proof) verifier.VerifyError!void { + _ = p; + rt.advanceRound(); + return verifier.VerifyError.Unsupported; +} diff --git a/verifier-ray/src/lib.zig b/verifier-ray/src/lib.zig new file mode 100644 index 00000000000..f2a73296009 --- /dev/null +++ b/verifier-ray/src/lib.zig @@ -0,0 +1,43 @@ +pub const proof = @import("proof.zig"); +pub const runtime = @import("runtime.zig"); +pub const verifier = @import("verifier.zig"); + +pub const field = struct { + pub const koalabear = @import("field/koalabear.zig"); + pub const koalabear_ext = @import("field/koalabear_ext.zig"); + pub const vec = @import("field/vec.zig"); +}; + +pub const crypto = struct { + pub const fiat_shamir = @import("crypto/fiat_shamir.zig"); + pub const poseidon2 = @import("crypto/poseidon2.zig"); +}; + +pub const pcs = struct { + pub const lagrange = @import("pcs/lagrange.zig"); + pub const polynomial = @import("pcs/polynomial.zig"); +}; + +pub const precompiles = struct { + pub const interface = @import("precompiles/interface.zig"); + pub const native = @import("precompiles/native.zig"); + pub const riscv = @import("precompiles/riscv.zig"); +}; + +pub const vortex = struct { + pub const reed_solomon = @import("vortex/reed_solomon.zig"); + pub const ringsis = @import("vortex/ringsis.zig"); + pub const smt = @import("vortex/smt.zig"); + pub const verifier = @import("vortex/verifier.zig"); +}; + +pub const generated = struct { + pub const stub = @import("generated/stub.zig"); +}; + +pub const Proof = proof.Proof; +pub const VerifyError = verifier.VerifyError; + +pub fn verify(p: Proof) VerifyError!void { + return verifier.verify(p); +} diff --git a/verifier-ray/src/main.zig b/verifier-ray/src/main.zig new file mode 100644 index 00000000000..b8a126e153a --- /dev/null +++ b/verifier-ray/src/main.zig @@ -0,0 +1,5 @@ +const verifier_ray = @import("verifier_ray"); + +pub fn main() void { + _ = verifier_ray.verify(verifier_ray.Proof.empty()) catch {}; +} diff --git a/verifier-ray/src/pcs/lagrange.zig b/verifier-ray/src/pcs/lagrange.zig new file mode 100644 index 00000000000..86775a906ea --- /dev/null +++ b/verifier-ray/src/pcs/lagrange.zig @@ -0,0 +1,150 @@ +const field = @import("../field/koalabear.zig"); +const ext = @import("../field/koalabear_ext.zig"); + +pub const Error = field.Error || error{InvalidCardinality}; + +pub fn evaluateBaseAtBase(values: []const field.Element, point: field.Element) Error!field.Element { + if (values.len == 0) return field.Element.zero(); + const n = try checkedCardinality(values.len); + + const omega = try field.rootOfUnityBy(values.len); + const inv_n = field.Element.init(n).inverse(); + const vanishing = point.pow(n).sub(field.Element.one()); + + var omega_i = field.Element.one(); + var sum = field.Element.zero(); + for (values) |value| { + if (point.eql(omega_i)) return value; + const weighted = omega_i.mul(inv_n).mul(value); + const inv_denom = point.sub(omega_i).inverse(); + sum = sum.add(weighted.mul(inv_denom)); + omega_i = omega_i.mul(omega); + } + + return vanishing.mul(sum); +} + +pub fn evaluateBaseAtExt(values: []const field.Element, point: ext.Ext) Error!ext.Ext { + if (values.len == 0) return ext.Ext.zero(); + const n = try checkedCardinality(values.len); + + const omega = try field.rootOfUnityBy(values.len); + const inv_n = field.Element.init(n).inverse(); + const vanishing = point.pow(n).sub(ext.Ext.one()); + + var omega_i = field.Element.one(); + var sum = ext.Ext.zero(); + for (values) |value| { + const domain_point = ext.Ext.lift(omega_i); + if (point.eql(domain_point)) return ext.Ext.lift(value); + const weighted = omega_i.mul(inv_n).mul(value); + const denom = point.sub(domain_point); + sum = sum.add(denom.inverse().mulByBase(weighted)); + omega_i = omega_i.mul(omega); + } + + return vanishing.mul(sum); +} + +pub fn evaluateExtAtBase(values: []const ext.Ext, point: field.Element) Error!ext.Ext { + if (values.len == 0) return ext.Ext.zero(); + const n = try checkedCardinality(values.len); + + const omega = try field.rootOfUnityBy(values.len); + const inv_n = field.Element.init(n).inverse(); + const vanishing = point.pow(n).sub(field.Element.one()); + + var omega_i = field.Element.one(); + var sum = ext.Ext.zero(); + for (values) |value| { + if (point.eql(omega_i)) return value; + const weighted = value.mulByBase(omega_i.mul(inv_n)); + const inv_denom = point.sub(omega_i).inverse(); + sum = sum.add(weighted.mulByBase(inv_denom)); + omega_i = omega_i.mul(omega); + } + + return sum.mulByBase(vanishing); +} + +pub fn evaluateExtAtExt(values: []const ext.Ext, point: ext.Ext) Error!ext.Ext { + if (values.len == 0) return ext.Ext.zero(); + const n = try checkedCardinality(values.len); + + const omega = try field.rootOfUnityBy(values.len); + const inv_n = field.Element.init(n).inverse(); + const vanishing = point.pow(n).sub(ext.Ext.one()); + + var omega_i = field.Element.one(); + var sum = ext.Ext.zero(); + for (values) |value| { + const domain_point = ext.Ext.lift(omega_i); + if (point.eql(domain_point)) return value; + const weighted = value.mulByBase(omega_i.mul(inv_n)); + const denom = point.sub(domain_point); + sum = sum.add(weighted.mul(denom.inverse())); + omega_i = omega_i.mul(omega); + } + + return vanishing.mul(sum); +} + +pub fn evaluateBaseBatchAtBase( + out: []field.Element, + values: []const field.Element, + points: []const field.Element, +) Error!void { + if (out.len != points.len) return error.InvalidCardinality; + for (points, out) |point, *dst| { + dst.* = try evaluateBaseAtBase(values, point); + } +} + +pub fn evaluateBaseBatchAtExt( + out: []ext.Ext, + values: []const field.Element, + points: []const ext.Ext, +) Error!void { + if (out.len != points.len) return error.InvalidCardinality; + for (points, out) |point, *dst| { + dst.* = try evaluateBaseAtExt(values, point); + } +} + +pub fn evaluateExtBatchAtBase( + out: []ext.Ext, + values: []const ext.Ext, + points: []const field.Element, +) Error!void { + if (out.len != points.len) return error.InvalidCardinality; + for (points, out) |point, *dst| { + dst.* = try evaluateExtAtBase(values, point); + } +} + +pub fn evaluateExtBatchAtExt( + out: []ext.Ext, + values: []const ext.Ext, + points: []const ext.Ext, +) Error!void { + if (out.len != points.len) return error.InvalidCardinality; + for (points, out) |point, *dst| { + dst.* = try evaluateExtAtExt(values, point); + } +} + +pub fn evaluate(values: []const field.Element, point: field.Element) Error!field.Element { + return evaluateBaseAtBase(values, point); +} + +fn checkedCardinality(cardinality: usize) Error!u32 { + if (!field.isPowerOfTwo(cardinality)) { + return error.InvalidCardinality; + } + if (field.log2PowerOfTwo(cardinality) > field.max_order_root) { + return error.InvalidCardinality; + } + // Koalabear domains are bounded by max_order_root, so the validated length + // always fits in u32; truncate keeps the field-sized conversion explicit. + return @truncate(cardinality); +} diff --git a/verifier-ray/src/pcs/polynomial.zig b/verifier-ray/src/pcs/polynomial.zig new file mode 100644 index 00000000000..4ad2929afb0 --- /dev/null +++ b/verifier-ray/src/pcs/polynomial.zig @@ -0,0 +1,90 @@ +const field = @import("../field/koalabear.zig"); +const ext = @import("../field/koalabear_ext.zig"); + +pub const Error = error{LengthMismatch}; + +pub fn evaluateBaseCanonical(coefficients: []const field.Element, point: field.Element) field.Element { + var acc = field.Element.zero(); + var i = coefficients.len; + while (i != 0) { + i -= 1; + acc = acc.mul(point).add(coefficients[i]); + } + return acc; +} + +pub fn evaluateBaseCanonicalAtExt(coefficients: []const field.Element, point: ext.Ext) ext.Ext { + var acc = ext.Ext.zero(); + var i = coefficients.len; + while (i != 0) { + i -= 1; + acc = acc.mul(point).add(ext.Ext.lift(coefficients[i])); + } + return acc; +} + +pub fn evaluateExtCanonicalAtBase(coefficients: []const ext.Ext, point: field.Element) ext.Ext { + var acc = ext.Ext.zero(); + var i = coefficients.len; + while (i != 0) { + i -= 1; + acc = acc.mulByBase(point).add(coefficients[i]); + } + return acc; +} + +pub fn evaluateExtCanonical(coefficients: []const ext.Ext, point: ext.Ext) ext.Ext { + var acc = ext.Ext.zero(); + var i = coefficients.len; + while (i != 0) { + i -= 1; + acc = acc.mul(point).add(coefficients[i]); + } + return acc; +} + +pub fn evaluateBaseCanonicalBatch( + out: []field.Element, + polys: []const []const field.Element, + point: field.Element, +) Error!void { + if (out.len != polys.len) return Error.LengthMismatch; + for (polys, out) |poly, *dst| { + dst.* = evaluateBaseCanonical(poly, point); + } +} + +pub fn evaluateBaseCanonicalBatchAtExt( + out: []ext.Ext, + polys: []const []const field.Element, + point: ext.Ext, +) Error!void { + if (out.len != polys.len) return Error.LengthMismatch; + for (polys, out) |poly, *dst| { + dst.* = evaluateBaseCanonicalAtExt(poly, point); + } +} + +pub fn evaluateExtCanonicalBatchAtBase( + out: []ext.Ext, + polys: []const []const ext.Ext, + point: field.Element, +) Error!void { + if (out.len != polys.len) return Error.LengthMismatch; + for (polys, out) |poly, *dst| { + dst.* = evaluateExtCanonicalAtBase(poly, point); + } +} + +pub fn evaluateExtCanonicalBatch( + out: []ext.Ext, + polys: []const []const ext.Ext, + point: ext.Ext, +) Error!void { + if (out.len != polys.len) return Error.LengthMismatch; + for (polys, out) |poly, *dst| { + dst.* = evaluateExtCanonical(poly, point); + } +} + +pub const evaluateHorner = evaluateBaseCanonical; diff --git a/verifier-ray/src/precompiles/interface.zig b/verifier-ray/src/precompiles/interface.zig new file mode 100644 index 00000000000..daab7f3efb7 --- /dev/null +++ b/verifier-ray/src/precompiles/interface.zig @@ -0,0 +1,7 @@ +const poseidon2 = @import("../crypto/poseidon2.zig"); + +pub const Poseidon2CompressFn = *const fn (poseidon2.Digest, poseidon2.Digest) poseidon2.Digest; + +pub const Backend = struct { + poseidon2_compress: Poseidon2CompressFn, +}; diff --git a/verifier-ray/src/precompiles/native.zig b/verifier-ray/src/precompiles/native.zig new file mode 100644 index 00000000000..33574834641 --- /dev/null +++ b/verifier-ray/src/precompiles/native.zig @@ -0,0 +1,6 @@ +const interface = @import("interface.zig"); +const poseidon2 = @import("../crypto/poseidon2.zig"); + +pub const backend = interface.Backend{ + .poseidon2_compress = poseidon2.compress, +}; diff --git a/verifier-ray/src/precompiles/riscv.zig b/verifier-ray/src/precompiles/riscv.zig new file mode 100644 index 00000000000..d4c3b7b9d42 --- /dev/null +++ b/verifier-ray/src/precompiles/riscv.zig @@ -0,0 +1,4 @@ +const interface = @import("interface.zig"); +const native = @import("native.zig"); + +pub const backend: interface.Backend = native.backend; diff --git a/verifier-ray/src/proof.zig b/verifier-ray/src/proof.zig new file mode 100644 index 00000000000..81993b179a3 --- /dev/null +++ b/verifier-ray/src/proof.zig @@ -0,0 +1,17 @@ +const field = @import("field/koalabear.zig"); + +pub const Commitment = [8]field.Element; + +pub const Proof = struct { + commitments: []const Commitment, + public_inputs: []const field.Element, + proof_bytes: []const u8, + + pub fn empty() Proof { + return .{ + .commitments = &.{}, + .public_inputs = &.{}, + .proof_bytes = &.{}, + }; + } +}; diff --git a/verifier-ray/src/runtime.zig b/verifier-ray/src/runtime.zig new file mode 100644 index 00000000000..46921175553 --- /dev/null +++ b/verifier-ray/src/runtime.zig @@ -0,0 +1,17 @@ +const fiat_shamir = @import("crypto/fiat_shamir.zig"); + +pub const Runtime = struct { + transcript: fiat_shamir.Transcript, + current_round: usize, + + pub fn init() Runtime { + return .{ + .transcript = fiat_shamir.Transcript.init(), + .current_round = 0, + }; + } + + pub fn advanceRound(self: *Runtime) void { + self.current_round += 1; + } +}; diff --git a/verifier-ray/src/verifier.zig b/verifier-ray/src/verifier.zig new file mode 100644 index 00000000000..ab2c0e23c4c --- /dev/null +++ b/verifier-ray/src/verifier.zig @@ -0,0 +1,18 @@ +const proof_mod = @import("proof.zig"); +const runtime_mod = @import("runtime.zig"); +const generated = @import("generated/stub.zig"); + +pub const VerifyError = error{ + EmptyProof, + Unsupported, + InvalidProof, +}; + +pub fn verify(p: proof_mod.Proof) VerifyError!void { + if (p.proof_bytes.len == 0 and p.commitments.len == 0 and p.public_inputs.len == 0) { + return VerifyError.EmptyProof; + } + + var rt = runtime_mod.Runtime.init(); + return generated.verifyGenerated(&rt, p); +} diff --git a/verifier-ray/src/vortex/reed_solomon.zig b/verifier-ray/src/vortex/reed_solomon.zig new file mode 100644 index 00000000000..8b92c26a61e --- /dev/null +++ b/verifier-ray/src/vortex/reed_solomon.zig @@ -0,0 +1,7 @@ +const field = @import("../field/koalabear.zig"); + +pub const Error = error{Unsupported}; + +pub fn checkCodeword(_: []const field.Element) Error!void { + return Error.Unsupported; +} diff --git a/verifier-ray/src/vortex/ringsis.zig b/verifier-ray/src/vortex/ringsis.zig new file mode 100644 index 00000000000..faa4562eca4 --- /dev/null +++ b/verifier-ray/src/vortex/ringsis.zig @@ -0,0 +1,7 @@ +const field = @import("../field/koalabear.zig"); + +pub const Error = error{Unsupported}; + +pub fn hash(_: []const field.Element) Error![8]field.Element { + return Error.Unsupported; +} diff --git a/verifier-ray/src/vortex/smt.zig b/verifier-ray/src/vortex/smt.zig new file mode 100644 index 00000000000..4c53d932480 --- /dev/null +++ b/verifier-ray/src/vortex/smt.zig @@ -0,0 +1,11 @@ +const proof_mod = @import("../proof.zig"); + +pub const Proof = struct { + siblings: []const proof_mod.Commitment, +}; + +pub const Error = error{Unsupported}; + +pub fn verify(_: Proof, _: proof_mod.Commitment, _: proof_mod.Commitment) Error!void { + return Error.Unsupported; +} diff --git a/verifier-ray/src/vortex/verifier.zig b/verifier-ray/src/vortex/verifier.zig new file mode 100644 index 00000000000..8e4f77af380 --- /dev/null +++ b/verifier-ray/src/vortex/verifier.zig @@ -0,0 +1,7 @@ +const proof_mod = @import("../proof.zig"); + +pub const Error = error{Unsupported}; + +pub fn verify(_: proof_mod.Proof) Error!void { + return Error.Unsupported; +} diff --git a/verifier-ray/test/all.zig b/verifier-ray/test/all.zig new file mode 100644 index 00000000000..5f6462fad65 --- /dev/null +++ b/verifier-ray/test/all.zig @@ -0,0 +1,7 @@ +comptime { + _ = @import("field_test.zig"); + _ = @import("golden_test.zig"); + _ = @import("transcript_test.zig"); + _ = @import("vortex_test.zig"); + _ = @import("generated_stub_test.zig"); +} diff --git a/verifier-ray/test/field_test.zig b/verifier-ray/test/field_test.zig new file mode 100644 index 00000000000..195ed41b921 --- /dev/null +++ b/verifier-ray/test/field_test.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const verifier_ray = @import("verifier_ray"); + +const field = verifier_ray.field.koalabear; +const ext = verifier_ray.field.koalabear_ext; + +test "koalabear element reduces and adds modulo the field" { + const a = field.Element.init(field.modulus - 1); + const b = field.Element.init(2); + try std.testing.expect(a.add(b).eql(field.Element.one())); +} + +test "koalabear element multiplication uses the field modulus" { + const a = field.Element.init(field.modulus - 1); + try std.testing.expect(a.mul(a).eql(field.Element.one())); +} + +test "extension lift stores base element in the first limb" { + const lifted = ext.Ext.lift(field.Element.init(17)); + try std.testing.expect(lifted.limbs[0].eql(field.Element.init(17))); + try std.testing.expect(lifted.limbs[1].isZero()); + try std.testing.expect(lifted.limbs[2].isZero()); + try std.testing.expect(lifted.limbs[3].isZero()); +} diff --git a/verifier-ray/test/generated_stub_test.zig b/verifier-ray/test/generated_stub_test.zig new file mode 100644 index 00000000000..fbf37783437 --- /dev/null +++ b/verifier-ray/test/generated_stub_test.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +const verifier_ray = @import("verifier_ray"); + +test "generated verifier stub advances runtime and reports unsupported" { + var rt = verifier_ray.runtime.Runtime.init(); + try std.testing.expectError( + verifier_ray.VerifyError.Unsupported, + verifier_ray.generated.stub.verifyGenerated(&rt, verifier_ray.Proof.empty()), + ); + try std.testing.expectEqual(@as(usize, 1), rt.current_round); +} diff --git a/verifier-ray/test/golden_test.zig b/verifier-ray/test/golden_test.zig new file mode 100644 index 00000000000..45460f260fb --- /dev/null +++ b/verifier-ray/test/golden_test.zig @@ -0,0 +1,240 @@ +const std = @import("std"); +const verifier_ray = @import("verifier_ray"); +const vectors = @import("test_vectors"); + +const field = verifier_ray.field.koalabear; +const ext = verifier_ray.field.koalabear_ext; +const fiat_shamir = verifier_ray.crypto.fiat_shamir; +const poseidon2 = verifier_ray.crypto.poseidon2; +const lagrange = verifier_ray.pcs.lagrange; +const polynomial = verifier_ray.pcs.polynomial; + +test "koalabear base field matches prover-ray golden cases" { + for (vectors.field_cases) |case| { + const a = elem(case.a); + const b = elem(case.b); + + try expectElem(a.add(b), case.add); + try expectElem(a.sub(b), case.sub); + try expectElem(a.mul(b), case.mul); + try expectElem(a.square(), case.square_a); + try expectElem(a.neg(), case.neg_a); + try std.testing.expectEqualSlices(u8, &case.bytes_a, &a.toBytes()); + + if (case.has_inv_a) { + try expectElem(a.inverse(), case.inv_a); + } + if (case.has_div_ab) { + try expectElem(a.div(b), case.div_ab); + } + } +} + +test "koalabear extension field matches prover-ray golden cases" { + for (vectors.ext_cases) |case| { + const a = extElem(case.a); + const b = extElem(case.b); + const scalar = elem(case.scalar); + + try expectExt(a.add(b), case.add); + try expectExt(a.sub(b), case.sub); + try expectExt(a.mul(b), case.mul); + try expectExt(a.square(), case.square_a); + try expectExt(a.neg(), case.neg_a); + try expectExt(a.mulByBase(scalar), case.mul_by_base); + try std.testing.expectEqualSlices(u8, &case.bytes_a, &a.toBytes()); + + if (case.has_inv_a) { + try expectExt(a.inverse(), case.inv_a); + } + } +} + +test "canonical polynomial evaluation matches prover-ray golden cases" { + for (vectors.canonical_base_cases) |case| { + var coeffs: [16]field.Element = undefined; + fillElems(&coeffs, case.coeffs); + try expectElem(polynomial.evaluateBaseCanonical(coeffs[0..case.coeffs.len], elem(case.point)), case.expected); + } + + for (vectors.canonical_base_at_ext_cases) |case| { + var coeffs: [16]field.Element = undefined; + fillElems(&coeffs, case.coeffs); + try expectExt( + polynomial.evaluateBaseCanonicalAtExt(coeffs[0..case.coeffs.len], extElem(case.point)), + case.expected, + ); + } + + for (vectors.canonical_ext_at_base_cases) |case| { + var coeffs: [16]ext.Ext = undefined; + fillExts(&coeffs, case.coeffs); + try expectExt( + polynomial.evaluateExtCanonicalAtBase(coeffs[0..case.coeffs.len], elem(case.point)), + case.expected, + ); + } + + for (vectors.canonical_ext_cases) |case| { + var coeffs: [16]ext.Ext = undefined; + fillExts(&coeffs, case.coeffs); + try expectExt( + polynomial.evaluateExtCanonical(coeffs[0..case.coeffs.len], extElem(case.point)), + case.expected, + ); + } +} + +test "lagrange polynomial evaluation matches prover-ray golden cases" { + for (vectors.lagrange_base_cases) |case| { + var coeffs: [16]field.Element = undefined; + fillElems(&coeffs, case.coeffs); + try expectElem(try lagrange.evaluateBaseAtBase(coeffs[0..case.coeffs.len], elem(case.point)), case.expected); + } + + for (vectors.lagrange_base_at_ext_cases) |case| { + var coeffs: [16]field.Element = undefined; + fillElems(&coeffs, case.coeffs); + try expectExt( + try lagrange.evaluateBaseAtExt(coeffs[0..case.coeffs.len], extElem(case.point)), + case.expected, + ); + } + + for (vectors.lagrange_ext_at_base_cases) |case| { + var coeffs: [16]ext.Ext = undefined; + fillExts(&coeffs, case.coeffs); + try expectExt( + try lagrange.evaluateExtAtBase(coeffs[0..case.coeffs.len], elem(case.point)), + case.expected, + ); + } + + for (vectors.lagrange_ext_cases) |case| { + var coeffs: [16]ext.Ext = undefined; + fillExts(&coeffs, case.coeffs); + try expectExt( + try lagrange.evaluateExtAtExt(coeffs[0..case.coeffs.len], extElem(case.point)), + case.expected, + ); + } +} + +test "lagrange evaluation returns domain value at roots of unity" { + const base_values = [_]field.Element{ + elem(3), + elem(1), + elem(4), + elem(1), + }; + const ext_values = [_]ext.Ext{ + extElem(.{ 3, 1, 4, 1 }), + extElem(.{ 5, 9, 2, 6 }), + extElem(.{ 5, 3, 5, 8 }), + extElem(.{ 9, 7, 9, 3 }), + }; + + const omega = try field.rootOfUnityBy(base_values.len); + var domain_point = field.Element.one(); + for (base_values, ext_values) |base_value, ext_value| { + try expectElem(try lagrange.evaluateBaseAtBase(&base_values, domain_point), base_value.value); + try expectExt(try lagrange.evaluateBaseAtExt(&base_values, ext.Ext.lift(domain_point)), .{ + base_value.value, + 0, + 0, + 0, + }); + try expectExt(try lagrange.evaluateExtAtBase(&ext_values, domain_point), extValues(ext_value)); + try expectExt(try lagrange.evaluateExtAtExt(&ext_values, ext.Ext.lift(domain_point)), extValues(ext_value)); + domain_point = domain_point.mul(omega); + } +} + +test "poseidon2 compression and merkle-damgard match prover-ray golden cases" { + for (vectors.poseidon_compress_cases) |case| { + try expectDigest(poseidon2.compress(digest(case.left), digest(case.right)), case.expected); + } + + for (vectors.poseidon_md_cases) |case| { + var h = poseidon2.MDHasher.init(); + var message: [32]field.Element = undefined; + fillElems(&message, case.message); + h.writeElements(message[0..case.message.len]); + try expectDigest(h.sumElement(), case.expected); + } +} + +test "fiat-shamir transcript matches prover-ray golden cases" { + for (vectors.fiat_shamir_cases) |case| { + var transcript = fiat_shamir.Transcript.init(); + + var base_updates: [32]field.Element = undefined; + fillElems(&base_updates, case.base_updates); + transcript.updateElements(base_updates[0..case.base_updates.len]); + + var ext_updates: [16]ext.Ext = undefined; + fillExts(&ext_updates, case.ext_updates); + transcript.updateExt(ext_updates[0..case.ext_updates.len]); + + try expectDigest(transcript.randomField(), case.random_field); + try expectExt(transcript.randomExt(), case.random_ext); + } +} + +fn elem(value: u32) field.Element { + return field.Element.init(value); +} + +fn extElem(limbs: [4]u32) ext.Ext { + return .{ .limbs = .{ + elem(limbs[0]), + elem(limbs[1]), + elem(limbs[2]), + elem(limbs[3]), + } }; +} + +fn digest(values: [8]u32) poseidon2.Digest { + var out: poseidon2.Digest = undefined; + for (&out, values) |*dst, value| { + dst.* = elem(value); + } + return out; +} + +fn fillElems(out: []field.Element, values: []const u32) void { + for (values, 0..) |value, i| { + out[i] = elem(value); + } +} + +fn fillExts(out: []ext.Ext, values: []const [4]u32) void { + for (values, 0..) |value, i| { + out[i] = extElem(value); + } +} + +fn expectElem(actual: field.Element, expected: u32) !void { + try std.testing.expectEqual(expected, actual.value); +} + +fn expectExt(actual: ext.Ext, expected: [4]u32) !void { + for (actual.limbs, expected) |actual_limb, expected_limb| { + try expectElem(actual_limb, expected_limb); + } +} + +fn extValues(value: ext.Ext) [4]u32 { + return .{ + value.limbs[0].value, + value.limbs[1].value, + value.limbs[2].value, + value.limbs[3].value, + }; +} + +fn expectDigest(actual: poseidon2.Digest, expected: [8]u32) !void { + for (actual, expected) |actual_limb, expected_limb| { + try expectElem(actual_limb, expected_limb); + } +} diff --git a/verifier-ray/test/transcript_test.zig b/verifier-ray/test/transcript_test.zig new file mode 100644 index 00000000000..d9ae75e0e24 --- /dev/null +++ b/verifier-ray/test/transcript_test.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const verifier_ray = @import("verifier_ray"); + +const field = verifier_ray.field.koalabear; +const fiat_shamir = verifier_ray.crypto.fiat_shamir; + +test "transcript absorbs elements deterministically" { + var transcript = fiat_shamir.Transcript.init(); + transcript.updateElements(&.{ + field.Element.init(3), + field.Element.init(4), + }); + + const challenge = transcript.challengeExt(); + try std.testing.expect(!challenge.isZero()); +} diff --git a/verifier-ray/test/vortex_test.zig b/verifier-ray/test/vortex_test.zig new file mode 100644 index 00000000000..7c1bc597707 --- /dev/null +++ b/verifier-ray/test/vortex_test.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const verifier_ray = @import("verifier_ray"); + +test "vortex verifier reports unsupported until implementation is wired" { + try std.testing.expectError( + verifier_ray.vortex.verifier.Error.Unsupported, + verifier_ray.vortex.verifier.verify(verifier_ray.Proof.empty()), + ); +} diff --git a/verifier-ray/testdata/README.md b/verifier-ray/testdata/README.md new file mode 100644 index 00000000000..232cb3ec557 --- /dev/null +++ b/verifier-ray/testdata/README.md @@ -0,0 +1,15 @@ +# verifier-ray testdata + +Fixtures in this directory should be exported from `prover-ray` and consumed by +the Zig verifier tests. Keep files small and deterministic. + +Suggested groups: + +- `field/` for Koalabear and extension arithmetic vectors. +- `transcript/` for Fiat-Shamir transcript vectors. +- `vortex/` for commitment opening vectors. +- `generated/` for generated verifier golden files. + +Run `make generate-testdata` from `verifier-ray/` to refresh the generated Zig +vectors from local `prover-ray` references. `make verify-testdata` refreshes the +vectors and fails if the checked-in output is stale. diff --git a/verifier-ray/testdata/field/.gitkeep b/verifier-ray/testdata/field/.gitkeep new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/verifier-ray/testdata/field/.gitkeep @@ -0,0 +1 @@ + diff --git a/verifier-ray/testdata/generate/go.mod b/verifier-ray/testdata/generate/go.mod new file mode 100644 index 00000000000..776aca315f0 --- /dev/null +++ b/verifier-ray/testdata/generate/go.mod @@ -0,0 +1,25 @@ +module github.com/consensys/linea-monorepo/verifier-ray/testdata/generate + +go 1.25.7 + +require github.com/consensys/linea-monorepo/prover-ray v0.0.0 + +require ( + github.com/bits-and-blooms/bitset v1.24.4 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/consensys/gnark v0.14.1-0.20260219004710-bbfb2f70a565 // indirect + github.com/consensys/gnark-crypto v0.20.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect + github.com/ronanh/intcomp v1.1.1 // indirect + github.com/rs/zerolog v1.35.1 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect +) + +replace github.com/consensys/linea-monorepo/prover-ray => ../../../prover-ray diff --git a/verifier-ray/testdata/generate/go.sum b/verifier-ray/testdata/generate/go.sum new file mode 100644 index 00000000000..2d96220449b --- /dev/null +++ b/verifier-ray/testdata/generate/go.sum @@ -0,0 +1,42 @@ +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/consensys/gnark v0.14.1-0.20260219004710-bbfb2f70a565 h1:NlOAmbLYsVb/hcuOBxza6CAA+233tB0eFiunGVEMyv4= +github.com/consensys/gnark v0.14.1-0.20260219004710-bbfb2f70a565/go.mod h1:EoWWbEboQRydCqJDSA7zrFxucIeoy/5R+MDx04oFpF4= +github.com/consensys/gnark-crypto v0.20.1 h1:PXDUBvk8AzhvWowHLWBEAfUQcV1/aZgWIqD6eMpXmDg= +github.com/consensys/gnark-crypto v0.20.1/go.mod h1:RBWrSgy+IDbGR69RRV313th3M/aZU1ubk2om+qHuTSc= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ronanh/intcomp v1.1.1 h1:+1bGV/wEBiHI0FvzS7RHgzqOpfbBJzLIxkqMJ9e6yxY= +github.com/ronanh/intcomp v1.1.1/go.mod h1:7FOLy3P3Zj3er/kVrU/pl+Ql7JFZj7bwliMGketo0IU= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/verifier-ray/testdata/generate/main.go b/verifier-ray/testdata/generate/main.go new file mode 100644 index 00000000000..5418a1cc1b4 --- /dev/null +++ b/verifier-ray/testdata/generate/main.go @@ -0,0 +1,435 @@ +package main + +import ( + "bytes" + "fmt" + "math/rand/v2" + "os" + "os/exec" + "path/filepath" + "strings" + + fiatshamir "github.com/consensys/linea-monorepo/prover-ray/crypto/koalabear/fiatshamir" + poseidon2 "github.com/consensys/linea-monorepo/prover-ray/crypto/koalabear/poseidon2" + "github.com/consensys/linea-monorepo/prover-ray/maths/koalabear/field" + "github.com/consensys/linea-monorepo/prover-ray/maths/koalabear/polynomials" +) + +const koalaModulus = uint64(2_130_706_433) + +func main() { + var out bytes.Buffer + writeHeader(&out) + writeFieldCases(&out) + writeExtCases(&out) + writePolynomialCases(&out) + writePoseidonCases(&out) + writeFiatShamirCases(&out) + + data := out.Bytes() + zigfmt, err := runZigFmt(data) + if err == nil { + data = zigfmt + } + + outputPath := filepath.Join("..", "generated", "vectors.zig") + if err := os.WriteFile(outputPath, data, 0o644); err != nil { + panic(err) + } +} + +func writeHeader(out *bytes.Buffer) { + fmt.Fprintln(out, "// Code generated by verifier-ray/testdata/generate; DO NOT EDIT.") + fmt.Fprintln(out) + fmt.Fprintln(out, "pub const FieldCase = struct {") + fmt.Fprintln(out, " a: u32,") + fmt.Fprintln(out, " b: u32,") + fmt.Fprintln(out, " add: u32,") + fmt.Fprintln(out, " sub: u32,") + fmt.Fprintln(out, " mul: u32,") + fmt.Fprintln(out, " square_a: u32,") + fmt.Fprintln(out, " neg_a: u32,") + fmt.Fprintln(out, " has_inv_a: bool,") + fmt.Fprintln(out, " inv_a: u32,") + fmt.Fprintln(out, " has_div_ab: bool,") + fmt.Fprintln(out, " div_ab: u32,") + fmt.Fprintln(out, " bytes_a: [4]u8,") + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + fmt.Fprintln(out, "pub const ExtCase = struct {") + fmt.Fprintln(out, " a: [4]u32,") + fmt.Fprintln(out, " b: [4]u32,") + fmt.Fprintln(out, " scalar: u32,") + fmt.Fprintln(out, " add: [4]u32,") + fmt.Fprintln(out, " sub: [4]u32,") + fmt.Fprintln(out, " mul: [4]u32,") + fmt.Fprintln(out, " square_a: [4]u32,") + fmt.Fprintln(out, " neg_a: [4]u32,") + fmt.Fprintln(out, " mul_by_base: [4]u32,") + fmt.Fprintln(out, " has_inv_a: bool,") + fmt.Fprintln(out, " inv_a: [4]u32,") + fmt.Fprintln(out, " bytes_a: [16]u8,") + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + fmt.Fprintln(out, "pub const CanonicalBaseCase = struct { coeffs: []const u32, point: u32, expected: u32 };") + fmt.Fprintln(out, "pub const CanonicalBaseAtExtCase = struct { coeffs: []const u32, point: [4]u32, expected: [4]u32 };") + fmt.Fprintln(out, "pub const CanonicalExtAtBaseCase = struct { coeffs: []const [4]u32, point: u32, expected: [4]u32 };") + fmt.Fprintln(out, "pub const CanonicalExtCase = struct { coeffs: []const [4]u32, point: [4]u32, expected: [4]u32 };") + fmt.Fprintln(out, "pub const LagrangeBaseCase = CanonicalBaseCase;") + fmt.Fprintln(out, "pub const LagrangeBaseAtExtCase = CanonicalBaseAtExtCase;") + fmt.Fprintln(out, "pub const LagrangeExtAtBaseCase = CanonicalExtAtBaseCase;") + fmt.Fprintln(out, "pub const LagrangeExtCase = CanonicalExtCase;") + fmt.Fprintln(out) + fmt.Fprintln(out, "pub const PoseidonCompressCase = struct { left: [8]u32, right: [8]u32, expected: [8]u32 };") + fmt.Fprintln(out, "pub const PoseidonMdCase = struct { message: []const u32, expected: [8]u32 };") + fmt.Fprintln(out, "pub const FiatShamirCase = struct { base_updates: []const u32, ext_updates: []const [4]u32, random_field: [8]u32, random_ext: [4]u32 };") + fmt.Fprintln(out) +} + +func writeFieldCases(out *bytes.Buffer) { + rng := rand.New(rand.NewPCG(0x12345678, 0x9abcdef0)) + values := []field.Element{ + elem(0), + elem(1), + elem(2), + elem(3), + elem(koalaModulus - 1), + elem(koalaModulus - 2), + elem(uint64(field.RootOfUnity.Bits()[0])), + elem(uint64(field.MontConstant.Bits()[0])), + } + for range 8 { + values = append(values, field.PseudoRand(rng)) + } + + fmt.Fprintln(out, "pub const field_cases = [_]FieldCase{") + for i := 0; i < len(values)-1; i++ { + writeFieldCase(out, values[i], values[i+1]) + } + fmt.Fprintln(out, "};") + fmt.Fprintln(out) +} + +func writeFieldCase(out *bytes.Buffer, a, b field.Element) { + var add, sub, mul, square, neg, inv, div field.Element + add.Add(&a, &b) + sub.Sub(&a, &b) + mul.Mul(&a, &b) + square.Square(&a) + neg.Neg(&a) + + hasInv := !a.IsZero() + if hasInv { + inv.Inverse(&a) + } + hasDiv := !b.IsZero() + if hasDiv { + div.Div(&a, &b) + } + + fmt.Fprintf(out, " .{ .a = %d, .b = %d, .add = %d, .sub = %d, .mul = %d, .square_a = %d, .neg_a = %d, .has_inv_a = %t, .inv_a = %d, .has_div_ab = %t, .div_ab = %d, .bytes_a = %s },\n", + u(a), u(b), u(add), u(sub), u(mul), u(square), u(neg), hasInv, u(inv), hasDiv, u(div), bytes4(a.Bytes())) +} + +func writeExtCases(out *bytes.Buffer) { + rng := rand.New(rand.NewPCG(0x11111111, 0x22222222)) + values := []field.Ext{ + field.UintsToExt(0, 0, 0, 0), + field.UintsToExt(1, 0, 0, 0), + field.UintsToExt(0, 1, 0, 0), + field.UintsToExt(0, 0, 1, 0), + field.UintsToExt(0, 0, 0, 1), + field.UintsToExt(1, 2, 3, 4), + field.UintsToExt(koalaModulus-1, koalaModulus-2, 7, 11), + } + for range 6 { + values = append(values, field.PseudoRandExt(rng)) + } + + scalars := []field.Element{elem(0), elem(1), elem(2), elem(17), elem(koalaModulus - 1)} + + fmt.Fprintln(out, "pub const ext_cases = [_]ExtCase{") + for i := 0; i < len(values)-1; i++ { + writeExtCase(out, values[i], values[i+1], scalars[i%len(scalars)]) + } + fmt.Fprintln(out, "};") + fmt.Fprintln(out) +} + +func writeExtCase(out *bytes.Buffer, a, b field.Ext, scalar field.Element) { + var add, sub, mul, square, neg, byBase, inv field.Ext + add.Add(&a, &b) + sub.Sub(&a, &b) + mul.Mul(&a, &b) + square.Square(&a) + neg.Neg(&a) + byBase.MulByElement(&a, &scalar) + + hasInv := !a.IsZero() + if hasInv { + inv.Inverse(&a) + } + + bytesA := field.ExtToBytes(&a) + fmt.Fprintf(out, " .{ .a = %s, .b = %s, .scalar = %d, .add = %s, .sub = %s, .mul = %s, .square_a = %s, .neg_a = %s, .mul_by_base = %s, .has_inv_a = %t, .inv_a = %s, .bytes_a = %s },\n", + ext4(a), ext4(b), u(scalar), ext4(add), ext4(sub), ext4(mul), ext4(square), ext4(neg), ext4(byBase), hasInv, ext4(inv), bytes16(bytesA)) +} + +func writePolynomialCases(out *bytes.Buffer) { + basePolyA := []field.Element{elem(0), elem(1), elem(koalaModulus - 1), elem(17), elem(123456)} + basePolyB := []field.Element{elem(9), elem(8), elem(7), elem(6)} + extPolyA := []field.Ext{ + field.UintsToExt(1, 2, 3, 4), + field.UintsToExt(0, 1, 0, 1), + field.UintsToExt(9, 0, 8, 0), + field.UintsToExt(11, 12, 13, 14), + } + extPolyB := []field.Ext{ + field.UintsToExt(4, 3, 2, 1), + field.UintsToExt(5, 0, 0, 6), + field.UintsToExt(7, 8, 9, 10), + } + + basePoint := elem(5) + basePoint2 := elem(19) + extPoint := field.UintsToExt(5, 1, 7, 2) + extPoint2 := field.UintsToExt(13, 3, 0, 4) + + fmt.Fprintln(out, "pub const canonical_base_cases = [_]CanonicalBaseCase{") + writeCanonicalBaseCase(out, basePolyA, basePoint) + writeCanonicalBaseCase(out, basePolyB, basePoint2) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const canonical_base_at_ext_cases = [_]CanonicalBaseAtExtCase{") + writeCanonicalBaseAtExtCase(out, basePolyA, extPoint) + writeCanonicalBaseAtExtCase(out, basePolyB, extPoint2) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const canonical_ext_at_base_cases = [_]CanonicalExtAtBaseCase{") + writeCanonicalExtAtBaseCase(out, extPolyA, basePoint) + writeCanonicalExtAtBaseCase(out, extPolyB, basePoint2) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const canonical_ext_cases = [_]CanonicalExtCase{") + writeCanonicalExtCase(out, extPolyA, extPoint) + writeCanonicalExtCase(out, extPolyB, extPoint2) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + lagrangeBase := []field.Element{elem(3), elem(1), elem(4), elem(1)} + lagrangeExt := []field.Ext{ + field.UintsToExt(3, 1, 4, 1), + field.UintsToExt(5, 9, 2, 6), + field.UintsToExt(5, 3, 5, 8), + field.UintsToExt(9, 7, 9, 3), + } + lagrangeBasePoint := elem(7) + lagrangeExtPoint := field.UintsToExt(7, 2, 0, 1) + + fmt.Fprintln(out, "pub const lagrange_base_cases = [_]LagrangeBaseCase{") + writeLagrangeBaseCase(out, lagrangeBase, lagrangeBasePoint) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const lagrange_base_at_ext_cases = [_]LagrangeBaseAtExtCase{") + writeLagrangeBaseAtExtCase(out, lagrangeBase, lagrangeExtPoint) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const lagrange_ext_at_base_cases = [_]LagrangeExtAtBaseCase{") + writeLagrangeExtAtBaseCase(out, lagrangeExt, lagrangeBasePoint) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const lagrange_ext_cases = [_]LagrangeExtCase{") + writeLagrangeExtCase(out, lagrangeExt, lagrangeExtPoint) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) +} + +func writeCanonicalBaseCase(out *bytes.Buffer, coeffs []field.Element, point field.Element) { + res := polynomials.EvalCanonical(field.VecFromBase(coeffs), field.ElemFromBase(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %d, .expected = %d },\n", elemSlice(coeffs), u(point), u(res.AsBase())) +} + +func writeCanonicalBaseAtExtCase(out *bytes.Buffer, coeffs []field.Element, point field.Ext) { + res := polynomials.EvalCanonical(field.VecFromBase(coeffs), field.ElemFromExt(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %s, .expected = %s },\n", elemSlice(coeffs), ext4(point), ext4(res.AsExt())) +} + +func writeCanonicalExtAtBaseCase(out *bytes.Buffer, coeffs []field.Ext, point field.Element) { + res := polynomials.EvalCanonical(field.VecFromExt(coeffs), field.ElemFromBase(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %d, .expected = %s },\n", extSlice(coeffs), u(point), ext4(res.AsExt())) +} + +func writeCanonicalExtCase(out *bytes.Buffer, coeffs []field.Ext, point field.Ext) { + res := polynomials.EvalCanonical(field.VecFromExt(coeffs), field.ElemFromExt(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %s, .expected = %s },\n", extSlice(coeffs), ext4(point), ext4(res.AsExt())) +} + +func writeLagrangeBaseCase(out *bytes.Buffer, values []field.Element, point field.Element) { + res := polynomials.EvalLagrange(field.VecFromBase(values), field.ElemFromBase(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %d, .expected = %d },\n", elemSlice(values), u(point), u(res.AsBase())) +} + +func writeLagrangeBaseAtExtCase(out *bytes.Buffer, values []field.Element, point field.Ext) { + res := polynomials.EvalLagrange(field.VecFromBase(values), field.ElemFromExt(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %s, .expected = %s },\n", elemSlice(values), ext4(point), ext4(res.AsExt())) +} + +func writeLagrangeExtAtBaseCase(out *bytes.Buffer, values []field.Ext, point field.Element) { + res := polynomials.EvalLagrange(field.VecFromExt(values), field.ElemFromBase(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %d, .expected = %s },\n", extSlice(values), u(point), ext4(res.AsExt())) +} + +func writeLagrangeExtCase(out *bytes.Buffer, values []field.Ext, point field.Ext) { + res := polynomials.EvalLagrange(field.VecFromExt(values), field.ElemFromExt(point)) + fmt.Fprintf(out, " .{ .coeffs = &%s, .point = %s, .expected = %s },\n", extSlice(values), ext4(point), ext4(res.AsExt())) +} + +func writePoseidonCases(out *bytes.Buffer) { + leftA := octuplet(0, 1, 2, 3, 4, 5, 6, 7) + rightA := octuplet(8, 9, 10, 11, 12, 13, 14, 15) + leftB := octuplet(koalaModulus-1, koalaModulus-2, 17, 19, 23, 29, 31, 37) + rightB := octuplet(41, 43, 47, 53, 59, 61, 67, 71) + + fmt.Fprintln(out, "pub const poseidon_compress_cases = [_]PoseidonCompressCase{") + writePoseidonCompressCase(out, leftA, rightA) + writePoseidonCompressCase(out, leftB, rightB) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) + + fmt.Fprintln(out, "pub const poseidon_md_cases = [_]PoseidonMdCase{") + writePoseidonMdCase(out, nil) + writePoseidonMdCase(out, []field.Element{elem(1)}) + writePoseidonMdCase(out, []field.Element{elem(1), elem(2), elem(3), elem(4), elem(5), elem(6), elem(7)}) + writePoseidonMdCase(out, []field.Element{elem(1), elem(2), elem(3), elem(4), elem(5), elem(6), elem(7), elem(8)}) + writePoseidonMdCase(out, []field.Element{elem(1), elem(2), elem(3), elem(4), elem(5), elem(6), elem(7), elem(8), elem(9)}) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) +} + +func writePoseidonCompressCase(out *bytes.Buffer, left, right field.Octuplet) { + res := poseidon2.Compress(left, right) + fmt.Fprintf(out, " .{ .left = %s, .right = %s, .expected = %s },\n", oct8(left), oct8(right), oct8(res)) +} + +func writePoseidonMdCase(out *bytes.Buffer, msg []field.Element) { + h := poseidon2.NewMDHasher() + h.WriteElements(msg...) + res := h.SumElement() + fmt.Fprintf(out, " .{ .message = &%s, .expected = %s },\n", elemSlice(msg), oct8(res)) +} + +func writeFiatShamirCases(out *bytes.Buffer) { + fmt.Fprintln(out, "pub const fiat_shamir_cases = [_]FiatShamirCase{") + writeFiatShamirCase(out, + []field.Element{elem(3), elem(4), elem(5)}, + []field.Ext{field.UintsToExt(1, 2, 3, 4)}, + ) + writeFiatShamirCase(out, + []field.Element{elem(koalaModulus - 1), elem(9)}, + []field.Ext{field.UintsToExt(5, 0, 6, 0), field.UintsToExt(7, 8, 9, 10)}, + ) + fmt.Fprintln(out, "};") + fmt.Fprintln(out) +} + +func writeFiatShamirCase(out *bytes.Buffer, baseUpdates []field.Element, extUpdates []field.Ext) { + fs := fiatshamir.NewFiatShamir() + fs.Update(baseUpdates...) + fs.UpdateExt(extUpdates...) + randomField := fs.RandomField() + randomExt := fs.RandomFext() + fmt.Fprintf(out, " .{ .base_updates = &%s, .ext_updates = &%s, .random_field = %s, .random_ext = %s },\n", + elemSlice(baseUpdates), extSlice(extUpdates), oct8(randomField), ext4(randomExt)) +} + +func elem(v uint64) field.Element { + var e field.Element + e.SetUint64(v) + return e +} + +func octuplet(values ...uint64) field.Octuplet { + if len(values) != 8 { + panic("octuplet requires 8 values") + } + var out field.Octuplet + for i, value := range values { + out[i] = elem(value) + } + return out +} + +func u(e field.Element) uint64 { + return uint64(e.Bits()[0]) +} + +func ext4(e field.Ext) string { + a0, a1, b0, b1 := field.ExtToUint64s(&e) + return fmt.Sprintf(".{ %d, %d, %d, %d }", a0, a1, b0, b1) +} + +func oct8(values field.Octuplet) string { + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = fmt.Sprintf("%d", u(value)) + } + return ".{ " + strings.Join(parts, ", ") + " }" +} + +func elemSlice(values []field.Element) string { + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = fmt.Sprintf("%d", u(value)) + } + return ".{ " + strings.Join(parts, ", ") + " }" +} + +func extSlice(values []field.Ext) string { + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = ext4(value) + } + return ".{ " + strings.Join(parts, ", ") + " }" +} + +func bytes4(data [4]byte) string { + return fmt.Sprintf(".{ 0x%02x, 0x%02x, 0x%02x, 0x%02x }", data[0], data[1], data[2], data[3]) +} + +func bytes16(data [16]byte) string { + parts := make([]string, len(data)) + for i, b := range data { + parts[i] = fmt.Sprintf("0x%02x", b) + } + return ".{ " + strings.Join(parts, ", ") + " }" +} + +func runZigFmt(data []byte) ([]byte, error) { + tmp, err := os.CreateTemp("", "verifier-ray-vectors-*.zig") + if err != nil { + return nil, err + } + defer os.Remove(tmp.Name()) + if _, err := tmp.Write(data); err != nil { + tmp.Close() + return nil, err + } + if err := tmp.Close(); err != nil { + return nil, err + } + + cmd := os.Getenv("ZIG") + if cmd == "" { + cmd = "zig" + } + if err := exec.Command(cmd, "fmt", tmp.Name()).Run(); err != nil { + return nil, err + } + return os.ReadFile(tmp.Name()) +} diff --git a/verifier-ray/testdata/generated/.gitkeep b/verifier-ray/testdata/generated/.gitkeep new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/verifier-ray/testdata/generated/.gitkeep @@ -0,0 +1 @@ + diff --git a/verifier-ray/testdata/generated/vectors.zig b/verifier-ray/testdata/generated/vectors.zig new file mode 100644 index 00000000000..8bfa6357aa0 --- /dev/null +++ b/verifier-ray/testdata/generated/vectors.zig @@ -0,0 +1,131 @@ +// Code generated by verifier-ray/testdata/generate; DO NOT EDIT. + +pub const FieldCase = struct { + a: u32, + b: u32, + add: u32, + sub: u32, + mul: u32, + square_a: u32, + neg_a: u32, + has_inv_a: bool, + inv_a: u32, + has_div_ab: bool, + div_ab: u32, + bytes_a: [4]u8, +}; + +pub const ExtCase = struct { + a: [4]u32, + b: [4]u32, + scalar: u32, + add: [4]u32, + sub: [4]u32, + mul: [4]u32, + square_a: [4]u32, + neg_a: [4]u32, + mul_by_base: [4]u32, + has_inv_a: bool, + inv_a: [4]u32, + bytes_a: [16]u8, +}; + +pub const CanonicalBaseCase = struct { coeffs: []const u32, point: u32, expected: u32 }; +pub const CanonicalBaseAtExtCase = struct { coeffs: []const u32, point: [4]u32, expected: [4]u32 }; +pub const CanonicalExtAtBaseCase = struct { coeffs: []const [4]u32, point: u32, expected: [4]u32 }; +pub const CanonicalExtCase = struct { coeffs: []const [4]u32, point: [4]u32, expected: [4]u32 }; +pub const LagrangeBaseCase = CanonicalBaseCase; +pub const LagrangeBaseAtExtCase = CanonicalBaseAtExtCase; +pub const LagrangeExtAtBaseCase = CanonicalExtAtBaseCase; +pub const LagrangeExtCase = CanonicalExtCase; + +pub const PoseidonCompressCase = struct { left: [8]u32, right: [8]u32, expected: [8]u32 }; +pub const PoseidonMdCase = struct { message: []const u32, expected: [8]u32 }; +pub const FiatShamirCase = struct { base_updates: []const u32, ext_updates: []const [4]u32, random_field: [8]u32, random_ext: [4]u32 }; + +pub const field_cases = [_]FieldCase{ + .{ .a = 0, .b = 1, .add = 1, .sub = 2130706432, .mul = 0, .square_a = 0, .neg_a = 0, .has_inv_a = false, .inv_a = 0, .has_div_ab = true, .div_ab = 0, .bytes_a = .{ 0x00, 0x00, 0x00, 0x00 } }, + .{ .a = 1, .b = 2, .add = 3, .sub = 2130706432, .mul = 2, .square_a = 1, .neg_a = 2130706432, .has_inv_a = true, .inv_a = 1, .has_div_ab = true, .div_ab = 1065353217, .bytes_a = .{ 0x00, 0x00, 0x00, 0x01 } }, + .{ .a = 2, .b = 3, .add = 5, .sub = 2130706432, .mul = 6, .square_a = 4, .neg_a = 2130706431, .has_inv_a = true, .inv_a = 1065353217, .has_div_ab = true, .div_ab = 1420470956, .bytes_a = .{ 0x00, 0x00, 0x00, 0x02 } }, + .{ .a = 3, .b = 2130706432, .add = 2, .sub = 4, .mul = 2130706430, .square_a = 9, .neg_a = 2130706430, .has_inv_a = true, .inv_a = 710235478, .has_div_ab = true, .div_ab = 2130706430, .bytes_a = .{ 0x00, 0x00, 0x00, 0x03 } }, + .{ .a = 2130706432, .b = 2130706431, .add = 2130706430, .sub = 1, .mul = 2, .square_a = 1, .neg_a = 1, .has_inv_a = true, .inv_a = 2130706432, .has_div_ab = true, .div_ab = 1065353217, .bytes_a = .{ 0x7f, 0x00, 0x00, 0x00 } }, + .{ .a = 2130706431, .b = 1791270792, .add = 1791270790, .sub = 339435639, .mul = 678871282, .square_a = 4, .neg_a = 2, .has_inv_a = true, .inv_a = 1065353216, .has_div_ab = true, .div_ab = 668932212, .bytes_a = .{ 0x7e, 0xff, 0xff, 0xff } }, + .{ .a = 1791270792, .b = 33554430, .add = 1824825222, .sub = 1757716362, .mul = 331895189, .square_a = 1760025929, .neg_a = 339435641, .has_inv_a = true, .inv_a = 1796240327, .has_div_ab = true, .div_ab = 110130396, .bytes_a = .{ 0x6a, 0xc4, 0x9f, 0x88 } }, + .{ .a = 33554430, .b = 1154326406, .add = 1187880836, .sub = 1009934457, .mul = 1078943669, .square_a = 402124772, .neg_a = 2097152003, .has_inv_a = true, .inv_a = 1057030144, .has_div_ab = true, .div_ab = 1609681581, .bytes_a = .{ 0x01, 0xff, 0xff, 0xfe } }, + .{ .a = 1154326406, .b = 850338525, .add = 2004664931, .sub = 303987881, .mul = 1087840703, .square_a = 1522362804, .neg_a = 976380027, .has_inv_a = true, .inv_a = 957615221, .has_div_ab = true, .div_ab = 1373040814, .bytes_a = .{ 0x44, 0xcd, 0x9f, 0x86 } }, + .{ .a = 850338525, .b = 889499591, .add = 1739838116, .sub = 2091545367, .mul = 977566695, .square_a = 984300413, .neg_a = 1280367908, .has_inv_a = true, .inv_a = 1280577179, .has_div_ab = true, .div_ab = 252864598, .bytes_a = .{ 0x32, 0xaf, 0x22, 0xdd } }, + .{ .a = 889499591, .b = 713282608, .add = 1602782199, .sub = 176216983, .mul = 2089229741, .square_a = 1321698985, .neg_a = 1241206842, .has_inv_a = true, .inv_a = 192111, .has_div_ab = true, .div_ab = 1014278061, .bytes_a = .{ 0x35, 0x04, 0xaf, 0xc7 } }, + .{ .a = 713282608, .b = 1431217132, .add = 13793307, .sub = 1412771909, .mul = 1488151811, .square_a = 331613943, .neg_a = 1417423825, .has_inv_a = true, .inv_a = 869278921, .has_div_ab = true, .div_ab = 940371276, .bytes_a = .{ 0x2a, 0x83, 0xd4, 0x30 } }, + .{ .a = 1431217132, .b = 1790558302, .add = 1091069001, .sub = 1771365263, .mul = 1739383924, .square_a = 1232676114, .neg_a = 699489301, .has_inv_a = true, .inv_a = 1718024436, .has_div_ab = true, .div_ab = 1790751643, .bytes_a = .{ 0x55, 0x4e, 0xa3, 0xec } }, + .{ .a = 1790558302, .b = 1906838810, .add = 1566690679, .sub = 2014425925, .mul = 1842294464, .square_a = 1756958258, .neg_a = 340148131, .has_inv_a = true, .inv_a = 1468725738, .has_div_ab = true, .div_ab = 1322498137, .bytes_a = .{ 0x6a, 0xb9, 0xc0, 0x5e } }, + .{ .a = 1906838810, .b = 1819036473, .add = 1595168850, .sub = 87802337, .mul = 1378977056, .square_a = 135570653, .neg_a = 223867623, .has_inv_a = true, .inv_a = 779675792, .has_div_ab = true, .div_ab = 931437265, .bytes_a = .{ 0x71, 0xa8, 0x0d, 0x1a } }, +}; + +pub const ext_cases = [_]ExtCase{ + .{ .a = .{ 0, 0, 0, 0 }, .b = .{ 1, 0, 0, 0 }, .scalar = 0, .add = .{ 1, 0, 0, 0 }, .sub = .{ 2130706432, 0, 0, 0 }, .mul = .{ 0, 0, 0, 0 }, .square_a = .{ 0, 0, 0, 0 }, .neg_a = .{ 0, 0, 0, 0 }, .mul_by_base = .{ 0, 0, 0, 0 }, .has_inv_a = false, .inv_a = .{ 0, 0, 0, 0 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + .{ .a = .{ 1, 0, 0, 0 }, .b = .{ 0, 1, 0, 0 }, .scalar = 1, .add = .{ 1, 1, 0, 0 }, .sub = .{ 1, 2130706432, 0, 0 }, .mul = .{ 0, 1, 0, 0 }, .square_a = .{ 1, 0, 0, 0 }, .neg_a = .{ 2130706432, 0, 0, 0 }, .mul_by_base = .{ 1, 0, 0, 0 }, .has_inv_a = true, .inv_a = .{ 1, 0, 0, 0 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + .{ .a = .{ 0, 1, 0, 0 }, .b = .{ 0, 0, 1, 0 }, .scalar = 2, .add = .{ 0, 1, 1, 0 }, .sub = .{ 0, 1, 2130706432, 0 }, .mul = .{ 0, 0, 0, 1 }, .square_a = .{ 3, 0, 0, 0 }, .neg_a = .{ 0, 2130706432, 0, 0 }, .mul_by_base = .{ 0, 2, 0, 0 }, .has_inv_a = true, .inv_a = .{ 0, 710235478, 0, 0 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + .{ .a = .{ 0, 0, 1, 0 }, .b = .{ 0, 0, 0, 1 }, .scalar = 17, .add = .{ 0, 0, 1, 1 }, .sub = .{ 0, 0, 1, 2130706432 }, .mul = .{ 3, 0, 0, 0 }, .square_a = .{ 0, 1, 0, 0 }, .neg_a = .{ 0, 0, 2130706432, 0 }, .mul_by_base = .{ 0, 0, 17, 0 }, .has_inv_a = true, .inv_a = .{ 0, 0, 0, 710235478 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 } }, + .{ .a = .{ 0, 0, 0, 1 }, .b = .{ 1, 2, 3, 4 }, .scalar = 2130706432, .add = .{ 1, 2, 3, 5 }, .sub = .{ 2130706432, 2130706431, 2130706430, 2130706430 }, .mul = .{ 9, 12, 6, 1 }, .square_a = .{ 0, 3, 0, 0 }, .neg_a = .{ 0, 0, 0, 2130706432 }, .mul_by_base = .{ 0, 0, 0, 2130706432 }, .has_inv_a = true, .inv_a = .{ 0, 0, 710235478, 0 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + .{ .a = .{ 1, 2, 3, 4 }, .b = .{ 2130706432, 2130706431, 7, 11 }, .scalar = 0, .add = .{ 0, 0, 10, 15 }, .sub = .{ 2, 4, 2130706429, 2130706426 }, .mul = .{ 170, 149, 46, 15 }, .square_a = .{ 85, 61, 54, 20 }, .neg_a = .{ 2130706432, 2130706431, 2130706430, 2130706429 }, .mul_by_base = .{ 0, 0, 0, 0 }, .has_inv_a = true, .inv_a = .{ 2001037481, 838755646, 1727052586, 940422997 }, .bytes_a = .{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04 } }, + .{ .a = .{ 2130706432, 2130706431, 7, 11 }, .b = .{ 581101456, 937723482, 873148439, 376412444 }, .scalar = 1, .add = .{ 581101455, 937723480, 873148446, 376412455 }, .sub = .{ 1549604976, 1192982949, 1257558001, 1754294000 }, .mul = .{ 681227401, 1518778300, 2051071933, 179938903 }, .square_a = .{ 475, 416, 2130706287, 2130706383 }, .neg_a = .{ 1, 2, 2130706426, 2130706422 }, .mul_by_base = .{ 2130706432, 2130706431, 7, 11 }, .has_inv_a = true, .inv_a = .{ 1483819292, 212926827, 772257747, 1353039476 }, .bytes_a = .{ 0x7f, 0x00, 0x00, 0x00, 0x7e, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0b } }, + .{ .a = .{ 581101456, 937723482, 873148439, 376412444 }, .b = .{ 1808808001, 452370499, 316552724, 287130054 }, .scalar = 2, .add = .{ 259203024, 1390093981, 1189701163, 663542498 }, .sub = .{ 902999888, 485352983, 556595715, 89282390 }, .mul = .{ 1796159882, 1924650875, 1038101197, 1609624120 }, .square_a = .{ 1902402354, 139673039, 1337772181, 1198439519 }, .neg_a = .{ 1549604977, 1192982951, 1257557994, 1754293989 }, .mul_by_base = .{ 1162202912, 1875446964, 1746296878, 752824888 }, .has_inv_a = true, .inv_a = .{ 1327388303, 1047064136, 1920274888, 1083073788 }, .bytes_a = .{ 0x22, 0xa2, 0xe7, 0x90, 0x37, 0xe4, 0x86, 0x5a, 0x34, 0x0b, 0x30, 0x17, 0x16, 0x6f, 0x99, 0x1c } }, + .{ .a = .{ 1808808001, 452370499, 316552724, 287130054 }, .b = .{ 1575914907, 1716366296, 1373539121, 1951415406 }, .scalar = 17, .add = .{ 1254016475, 38030362, 1690091845, 107839027 }, .sub = .{ 232893094, 866710636, 1073720036, 466421081 }, .mul = .{ 214555138, 646653179, 230282914, 1811248659 }, .square_a = .{ 41742348, 1159536509, 358214566, 1017749032 }, .neg_a = .{ 321898432, 1678335934, 1814153709, 1843576379 }, .mul_by_base = .{ 919845955, 1298179184, 1119983442, 619798052 }, .has_inv_a = true, .inv_a = .{ 271242035, 94227657, 462127520, 820769618 }, .bytes_a = .{ 0x6b, 0xd0, 0x38, 0x41, 0x1a, 0xf6, 0xa0, 0x43, 0x12, 0xde, 0x36, 0x14, 0x11, 0x1d, 0x41, 0xc6 } }, + .{ .a = .{ 1575914907, 1716366296, 1373539121, 1951415406 }, .b = .{ 466741667, 48262571, 2037176501, 274951509 }, .scalar = 2130706432, .add = .{ 2042656574, 1764628867, 1280009189, 95660482 }, .sub = .{ 1109173240, 1668103725, 1467069053, 1676463897 }, .mul = .{ 1330579668, 1943054773, 1658625489, 350536612 }, .square_a = .{ 1680685398, 1107290646, 1200430436, 742825161 }, .neg_a = .{ 554791526, 414340137, 757167312, 179291027 }, .mul_by_base = .{ 554791526, 414340137, 757167312, 179291027 }, .has_inv_a = true, .inv_a = .{ 163853284, 1469286729, 1023675580, 573508968 }, .bytes_a = .{ 0x5d, 0xee, 0x8d, 0x9b, 0x66, 0x4d, 0xab, 0xd8, 0x51, 0xde, 0x8b, 0x31, 0x74, 0x50, 0x3c, 0x6e } }, + .{ .a = .{ 466741667, 48262571, 2037176501, 274951509 }, .b = .{ 637078102, 130803307, 1283919584, 1233931457 }, .scalar = 0, .add = .{ 1103819769, 179065878, 1190389652, 1508882966 }, .sub = .{ 1960369998, 2048165697, 753256917, 1171726485 }, .mul = .{ 253218910, 384806574, 2127023266, 170667736 }, .square_a = .{ 1144239636, 124269693, 1597899524, 846288148 }, .neg_a = .{ 1663964766, 2082443862, 93529932, 1855754924 }, .mul_by_base = .{ 0, 0, 0, 0 }, .has_inv_a = true, .inv_a = .{ 1805657499, 1658230872, 1670198309, 891391481 }, .bytes_a = .{ 0x1b, 0xd1, 0xe9, 0xa3, 0x02, 0xe0, 0x6d, 0xab, 0x79, 0x6c, 0xd8, 0xb5, 0x10, 0x63, 0x6d, 0x55 } }, + .{ .a = .{ 637078102, 130803307, 1283919584, 1233931457 }, .b = .{ 245334799, 1013392137, 1757801242, 904723341 }, .scalar = 1, .add = .{ 882412901, 1144195444, 911014393, 7948365 }, .sub = .{ 391743303, 1248117603, 1656824775, 329208116 }, .mul = .{ 965477412, 1902592978, 1366053127, 1275073920 }, .square_a = .{ 1605567586, 1661322284, 547867736, 808775679 }, .neg_a = .{ 1493628331, 1999903126, 846786849, 896774976 }, .mul_by_base = .{ 637078102, 130803307, 1283919584, 1233931457 }, .has_inv_a = true, .inv_a = .{ 1621380103, 392632152, 1383328447, 1166101300 }, .bytes_a = .{ 0x25, 0xf9, 0x0a, 0x56, 0x07, 0xcb, 0xe6, 0x6b, 0x4c, 0x87, 0x0e, 0xe0, 0x49, 0x8c, 0x4c, 0xc1 } }, +}; + +pub const canonical_base_cases = [_]CanonicalBaseCase{ + .{ .coeffs = &.{ 0, 1, 2130706432, 17, 123456 }, .point = 5, .expected = 77162105 }, + .{ .coeffs = &.{ 9, 8, 7, 6 }, .point = 19, .expected = 43842 }, +}; + +pub const canonical_base_at_ext_cases = [_]CanonicalBaseAtExtCase{ + .{ .coeffs = &.{ 0, 1, 2130706432, 17, 123456 }, .point = .{ 5, 1, 7, 2 }, .expected = .{ 1219449790, 1091022438, 1925099278, 247072636 } }, + .{ .coeffs = &.{ 9, 8, 7, 6 }, .point = .{ 13, 3, 0, 4 }, .expected = .{ 28761, 21750, 20808, 14872 } }, +}; + +pub const canonical_ext_at_base_cases = [_]CanonicalExtAtBaseCase{ + .{ .coeffs = &.{ .{ 1, 2, 3, 4 }, .{ 0, 1, 0, 1 }, .{ 9, 0, 8, 0 }, .{ 11, 12, 13, 14 } }, .point = 5, .expected = .{ 1601, 1507, 1828, 1759 } }, + .{ .coeffs = &.{ .{ 4, 3, 2, 1 }, .{ 5, 0, 0, 6 }, .{ 7, 8, 9, 10 } }, .point = 19, .expected = .{ 2626, 2891, 3251, 3725 } }, +}; + +pub const canonical_ext_cases = [_]CanonicalExtCase{ + .{ .coeffs = &.{ .{ 1, 2, 3, 4 }, .{ 0, 1, 0, 1 }, .{ 9, 0, 8, 0 }, .{ 11, 12, 13, 14 } }, .point = .{ 5, 1, 7, 2 }, .expected = .{ 178709, 102003, 133633, 76148 } }, + .{ .coeffs = &.{ .{ 4, 3, 2, 1 }, .{ 5, 0, 0, 6 }, .{ 7, 8, 9, 10 } }, .point = .{ 13, 3, 0, 4 }, .expected = .{ 9433, 6308, 8600, 4497 } }, +}; + +pub const lagrange_base_cases = [_]LagrangeBaseCase{ + .{ .coeffs = &.{ 3, 1, 4, 1 }, .point = 7, .expected = 2130706409 }, +}; + +pub const lagrange_base_at_ext_cases = [_]LagrangeBaseAtExtCase{ + .{ .coeffs = &.{ 3, 1, 4, 1 }, .point = .{ 7, 2, 0, 1 }, .expected = .{ 1065353131, 2130706376, 532676558, 1065353188 } }, +}; + +pub const lagrange_ext_at_base_cases = [_]LagrangeExtAtBaseCase{ + .{ .coeffs = &.{ .{ 3, 1, 4, 1 }, .{ 5, 9, 2, 6 }, .{ 5, 3, 5, 8 }, .{ 9, 7, 9, 3 } }, .point = 7, .expected = .{ 1353711035, 1453850477, 1303641413, 50069150 } }, +}; + +pub const lagrange_ext_cases = [_]LagrangeExtCase{ + .{ .coeffs = &.{ .{ 3, 1, 4, 1 }, .{ 5, 9, 2, 6 }, .{ 5, 3, 5, 8 }, .{ 9, 7, 9, 3 } }, .point = .{ 7, 2, 0, 1 }, .expected = .{ 2055615319, 693549217, 1128084541, 701937943 } }, +}; + +pub const poseidon_compress_cases = [_]PoseidonCompressCase{ + .{ .left = .{ 0, 1, 2, 3, 4, 5, 6, 7 }, .right = .{ 8, 9, 10, 11, 12, 13, 14, 15 }, .expected = .{ 932254713, 1939458218, 918369290, 1249949514, 673662327, 1800209056, 549432741, 1013224050 } }, + .{ .left = .{ 2130706432, 2130706431, 17, 19, 23, 29, 31, 37 }, .right = .{ 41, 43, 47, 53, 59, 61, 67, 71 }, .expected = .{ 1644332214, 1970046955, 1884038903, 207530700, 1298678359, 870519737, 195578452, 203554986 } }, +}; + +pub const poseidon_md_cases = [_]PoseidonMdCase{ + .{ .message = &.{}, .expected = .{ 0, 0, 0, 0, 0, 0, 0, 0 } }, + .{ .message = &.{1}, .expected = .{ 1395488266, 596674548, 1490173669, 1239108183, 1904302648, 1981213791, 834572541, 1762166437 } }, + .{ .message = &.{ 1, 2, 3, 4, 5, 6, 7 }, .expected = .{ 1402915949, 2083603674, 612464613, 903296317, 1327519169, 1646568100, 1786812904, 1917132066 } }, + .{ .message = &.{ 1, 2, 3, 4, 5, 6, 7, 8 }, .expected = .{ 1285814214, 193126342, 1859572879, 1940525937, 1148414906, 1307517087, 1533278800, 2037675123 } }, + .{ .message = &.{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, .expected = .{ 674318445, 903856591, 298949470, 1303321501, 945372390, 779534616, 1307723104, 465848063 } }, +}; + +pub const fiat_shamir_cases = [_]FiatShamirCase{ + .{ .base_updates = &.{ 3, 4, 5 }, .ext_updates = &.{.{ 1, 2, 3, 4 }}, .random_field = .{ 1587635484, 913030242, 114719685, 592841063, 1570019966, 582413647, 1425693690, 137448553 }, .random_ext = .{ 1256156366, 205445551, 1655569589, 1899439024 } }, + .{ .base_updates = &.{ 2130706432, 9 }, .ext_updates = &.{ .{ 5, 0, 6, 0 }, .{ 7, 8, 9, 10 } }, .random_field = .{ 458749237, 992875179, 1893076489, 781474296, 1078790770, 1795495057, 1948532239, 1276399547 }, .random_ext = .{ 528796958, 1835122464, 2097019190, 229067829 } }, +}; diff --git a/verifier-ray/testdata/transcript/.gitkeep b/verifier-ray/testdata/transcript/.gitkeep new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/verifier-ray/testdata/transcript/.gitkeep @@ -0,0 +1 @@ + diff --git a/verifier-ray/testdata/vortex/.gitkeep b/verifier-ray/testdata/vortex/.gitkeep new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/verifier-ray/testdata/vortex/.gitkeep @@ -0,0 +1 @@ +