const std = @import("std"); const Secp256k1 = std.crypto.ecc.Secp256k1; const Fe = @import("field.zig").Fe; const jacobian_mod = @import("jacobian.zig"); const AffinePoint = jacobian_mod.AffinePoint; const JacobianPoint = jacobian_mod.JacobianPoint; /// Cube root of unity in GF(p): beta^3 = 1 mod p. /// phi(x, y) = (beta * x, y) is equivalent to scalar multiplication by lambda, /// but costs only 1 field multiply instead of ~65 doublings. pub const beta = Fe.fromInt( 55594575648329892869085402983802832744385952214688224221778511981742606582254, ); /// Apply the endomorphism to an affine point: (beta * x, y). pub fn phiAffine(p: AffinePoint) AffinePoint { return .{ .x = p.x.mul(beta), .y = p.y }; } /// Apply the endomorphism to a Jacobian point: (beta * X, Y, Z). /// phi preserves the Z coordinate since it only scales the x-coordinate. pub fn phiJacobian(p: JacobianPoint) JacobianPoint { return .{ .x = p.x.mul(beta), .y = p.y, .z = p.z }; } /// Re-export stdlib's GLV scalar decomposition. /// Decomposes k into (r1, r2) such that k = r1 + r2*lambda (mod n), /// where |r1|, |r2| < sqrt(n) (approximately 128 bits each). pub const splitScalar = Secp256k1.Endormorphism.splitScalar; pub const SplitScalar = Secp256k1.Endormorphism.SplitScalar; test "phi(G) matches lambda * G" { const G = Secp256k1.basePoint; const g_affine = G.affineCoordinates(); const g26 = AffinePoint.fromStdlib(g_affine); const phi_G = phiAffine(g26); // lambda * G computed via scalar multiplication const lambda_s = comptime s: { var buf: [32]u8 = undefined; std.mem.writeInt( u256, &buf, 37718080363155996902926221483475020450927657555482586988616620542887997980018, .little, ); break :s buf; }; const lambda_G = try G.mulPublic(lambda_s, .little); const lambda_G_affine = lambda_G.affineCoordinates(); // compare via bytes const phi_x = phi_G.x.normalize().toBytes(.big); const lambda_x = Fe.fromStdlib(lambda_G_affine.x).normalize().toBytes(.big); try std.testing.expectEqualSlices(u8, &lambda_x, &phi_x); const phi_y = phi_G.y.normalize().toBytes(.big); const lambda_y = Fe.fromStdlib(lambda_G_affine.y).normalize().toBytes(.big); try std.testing.expectEqualSlices(u8, &lambda_y, &phi_y); }