Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ALLOWED_TYPES='feat|fix|docs|style|refactor|perf|test|chore|ci|revert|build'

# Allowed scopes
ALLOWED_SCOPES=\
'coordinator prover prover-ray postman tx-exclusion-api '\
'coordinator prover prover-ray verifier-ray postman tx-exclusion-api '\
'linea-besu contracts sdk-core sdk-ethers sdk-viem '\
'tracer sequencer state-recovery jvm-libs blob-libs '\
'e2e ci docker deps misc maru'
Expand Down
2 changes: 2 additions & 0 deletions verifier-ray/src/crypto/fiat_shamir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub const Transcript = struct {
challenge[1],
challenge[2],
challenge[3],
challenge[4],
challenge[5],
} };
}

Expand Down
103 changes: 76 additions & 27 deletions verifier-ray/src/field/koalabear_ext.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const base = @import("koalabear.zig");

pub const degree = 4;
pub const degree = 6;
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.
/// B0.A0, B0.A1, B1.A0, B1.A1, B2.A0, B2.A1
/// for the tower F_{p^6} = F_{p^2}[v]/(v^3 - (u+1)) with F_{p^2} = F_p[u]/(u^2 - 3).
limbs: [degree]base.Element,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should follow the definition in gnark-crypto/ray i.e.

pub const Ext = struct {
    B0: E2,
    B1: E2,
    B2: E2,
}

Memory-wise it doesn't make a difference, but corresponds more clearly to the gnark-crypto definition. And imo this makes the implementation also a bit clearer in mul where we right now manually construct E2 values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! That's more structured and highly readable.


pub fn zero() Ext {
Expand All @@ -14,6 +15,8 @@ pub const Ext = struct {
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
} };
}

Expand All @@ -27,6 +30,8 @@ pub const Ext = struct {
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
base.Element.zero(),
} };
}

Expand All @@ -38,7 +43,8 @@ pub const Ext = struct {
}

pub fn isBase(self: Ext) bool {
return self.limbs[1].isZero() and self.limbs[2].isZero() and self.limbs[3].isZero();
return self.limbs[1].isZero() and self.limbs[2].isZero() and self.limbs[3].isZero() and
self.limbs[4].isZero() and self.limbs[5].isZero();
}

pub fn eql(self: Ext, rhs: Ext) bool {
Expand Down Expand Up @@ -87,43 +93,67 @@ pub const Ext = struct {
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 a2 = E2{ .a0 = self.limbs[4], .a1 = self.limbs[5] };
const b0 = E2{ .a0 = rhs.limbs[0], .a1 = rhs.limbs[1] };
const b1 = E2{ .a0 = rhs.limbs[2], .a1 = rhs.limbs[3] };
const b2 = E2{ .a0 = rhs.limbs[4], .a1 = rhs.limbs[5] };

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));
// F_{p^6} = F_{p^2}[v]/(v^3 - (u+1)), so v^3 = u+1
// D0 = A0*B0 + (A1*B2 + A2*B1)*(u+1)
// D1 = A0*B1 + A1*B0 + A2*B2*(u+1)
// D2 = A0*B2 + A2*B0 + A1*B1
const d0 = a0.mul(b0).add(a1.mul(b2).add(a2.mul(b1)).mulByNonResidue());
const d1 = a0.mul(b1).add(a1.mul(b0)).add(a2.mul(b2).mulByNonResidue());
const d2 = a0.mul(b2).add(a2.mul(b0)).add(a1.mul(b1));

return .{ .limbs = .{ c0.a0, c0.a1, c1.a0, c1.a1 } };
return .{ .limbs = .{ d0.a0, d0.a1, d1.a0, d1.a1, d2.a0, d2.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);

const b0 = E2{ .a0 = self.limbs[0], .a1 = self.limbs[1] };
const b1 = E2{ .a0 = self.limbs[2], .a1 = self.limbs[3] };
const b2 = E2{ .a0 = self.limbs[4], .a1 = self.limbs[5] };

// Adjugate elements for the cubic extension inverse:
// A = b0^2 - (u+1)*b1*b2
// B = (u+1)*b2^2 - b0*b1
// C = b1^2 - b0*b2
const cap_a = b0.mul(b0).sub(b1.mul(b2).mulByNonResidue());
const cap_b = b2.mul(b2).mulByNonResidue().sub(b0.mul(b1));
const cap_c = b1.mul(b1).sub(b0.mul(b2));

// Norm: d = b0*A + (u+1)*(b2*B + b1*C)
const d = b0.mul(cap_a).add(b2.mul(cap_b).add(b1.mul(cap_c)).mulByNonResidue());
const d_inv = d.inverse();

const e0 = cap_a.mul(d_inv);
const e1 = cap_b.mul(d_inv);
const e2 = cap_c.mul(d_inv);

return .{ .limbs = .{ e0.a0, e0.a1, e1.a0, e1.a1, e2.a0, e2.a1 } };
}

pub fn div(self: Ext, rhs: Ext) Ext {
return self.mul(rhs.inverse());
}

pub fn pow(self: Ext, exponent: u64) Ext {
var result = Ext.one();
var b = self;
var exp = exponent;
while (exp != 0) : (exp >>= 1) {
if ((exp & 1) == 1) result = result.mul(b);
b = b.square();
}
return result;
}
Comment thread
cursor[bot] marked this conversation as resolved.

pub fn toBytes(self: Ext) [bytes]u8 {
var out: [bytes]u8 = undefined;
for (self.limbs, 0..) |limb, i| {
Expand All @@ -139,13 +169,12 @@ pub const Ext = struct {
try base.Element.fromBytesCanonical(encoded[4..8].*),
try base.Element.fromBytesCanonical(encoded[8..12].*),
try base.Element.fromBytesCanonical(encoded[12..16].*),
try base.Element.fromBytesCanonical(encoded[16..20].*),
try base.Element.fromBytesCanonical(encoded[20..24].*),
} };
}
};

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,
Expand All @@ -154,14 +183,34 @@ const E2 = struct {
return .{ .a0 = self.a0.add(rhs.a0), .a1 = self.a1.add(rhs.a1) };
}

fn sub(self: E2, rhs: E2) E2 {
return .{ .a0 = self.a0.sub(rhs.a0), .a1 = self.a1.sub(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 };
// Multiply by the non-residue (u+1) of the cubic extension: (a0 + 3*a1) + (a0 + a1)*u
fn mulByNonResidue(self: E2) E2 {
const three = base.Element.init(3);
return .{
.a0 = self.a0.add(self.a1.mul(three)),
.a1 = self.a0.add(self.a1),
};
}

// Invert in F_p[u]/(u^2 - 3): norm = a0^2 - 3*a1^2
fn inverse(self: E2) E2 {
const three = base.Element.init(3);
const norm = self.a0.mul(self.a0).sub(self.a1.mul(self.a1).mul(three));
const norm_inv = norm.inverse();
return .{
.a0 = self.a0.mul(norm_inv),
.a1 = self.a1.neg().mul(norm_inv),
};
}
};

Expand Down
22 changes: 14 additions & 8 deletions verifier-ray/test/golden_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ test "lagrange evaluation returns domain value at roots of unity" {
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 }),
extElem(.{ 3, 1, 4, 1, 5, 9 }),
extElem(.{ 5, 9, 2, 6, 5, 3 }),
extElem(.{ 5, 3, 5, 8, 9, 7 }),
extElem(.{ 9, 7, 9, 3, 2, 3 }),
};

const omega = try field.rootOfUnityBy(base_values.len);
Expand All @@ -143,6 +143,8 @@ test "lagrange evaluation returns domain value at roots of unity" {
0,
0,
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));
Expand Down Expand Up @@ -185,12 +187,14 @@ fn elem(value: u32) field.Element {
return field.Element.init(value);
}

fn extElem(limbs: [4]u32) ext.Ext {
fn extElem(limbs: [6]u32) ext.Ext {
return .{ .limbs = .{
elem(limbs[0]),
elem(limbs[1]),
elem(limbs[2]),
elem(limbs[3]),
elem(limbs[4]),
elem(limbs[5]),
} };
}

Expand All @@ -208,7 +212,7 @@ fn fillElems(out: []field.Element, values: []const u32) void {
}
}

fn fillExts(out: []ext.Ext, values: []const [4]u32) void {
fn fillExts(out: []ext.Ext, values: []const [6]u32) void {
for (values, 0..) |value, i| {
out[i] = extElem(value);
}
Expand All @@ -218,18 +222,20 @@ fn expectElem(actual: field.Element, expected: u32) !void {
try std.testing.expectEqual(expected, actual.value);
}

fn expectExt(actual: ext.Ext, expected: [4]u32) !void {
fn expectExt(actual: ext.Ext, expected: [6]u32) !void {
for (actual.limbs, expected) |actual_limb, expected_limb| {
try expectElem(actual_limb, expected_limb);
}
}

fn extValues(value: ext.Ext) [4]u32 {
fn extValues(value: ext.Ext) [6]u32 {
return .{
value.limbs[0].value,
value.limbs[1].value,
value.limbs[2].value,
value.limbs[3].value,
value.limbs[4].value,
value.limbs[5].value,
};
}

Expand Down
2 changes: 1 addition & 1 deletion verifier-ray/testdata/generate/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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-20260518154448-1f6880839cd2
require github.com/consensys/linea-monorepo/prover-ray v0.0.0-20260519010204-24a53941da53

require (
github.com/bits-and-blooms/bitset v1.24.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions verifier-ray/testdata/generate/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/consensys/gnark-crypto v0.20.2-0.20260514182922-df0578435b08 h1:EdljQ
github.com/consensys/gnark-crypto v0.20.2-0.20260514182922-df0578435b08/go.mod h1:NzeBHSZ49bIM7RtrNTYYR2kymTqwvI/A4eTgQlyQc+Q=
github.com/consensys/linea-monorepo/prover-ray v0.0.0-20260518154448-1f6880839cd2 h1:Ggp+FPN2SmxdPC1k77w9YCNef4RUOL9C4algaGIUuWk=
github.com/consensys/linea-monorepo/prover-ray v0.0.0-20260518154448-1f6880839cd2/go.mod h1:HZncWpppP5LS0qB3moZR1ESi/vIbCEnkG1w65TsAojU=
github.com/consensys/linea-monorepo/prover-ray v0.0.0-20260519010204-24a53941da53 h1:DpSMTgN3kJYo7vbBGnSXGJXQx4yVVR35WUE/NFqYdWY=
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, please also run go mod tidy when updating dependencies - it will remove the stale checksums

github.com/consensys/linea-monorepo/prover-ray v0.0.0-20260519010204-24a53941da53/go.mod h1:qHFJ8kDpr4/FxFebRGZ1aPg74qHUbSaDv7j1K5yKefY=
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=
Expand Down
Loading
Loading