diff --git a/curve/ecgfp5/curve_test.go b/curve/ecgfp5/curve_test.go index ea90cb9..fb96eba 100644 --- a/curve/ecgfp5/curve_test.go +++ b/curve/ecgfp5/curve_test.go @@ -885,6 +885,52 @@ func TestDecodeAsWeierstrass(t *testing.T) { } } +// TestGeneratorWeierstrassWindow verifies that the precomputed GENERATOR_WEIERSTRASS_WINDOW +// table contains the correct values [0, G, 2G, 3G, ..., 15G]. +func TestGeneratorWeierstrassWindow(t *testing.T) { + // Compute the window table dynamically for comparison + dynamicTable := GENERATOR_WEIERSTRASS.PrecomputeWindow(4) + + // Verify the table has the correct size (16 points for 4-bit window) + expectedSize := 16 + if len(GENERATOR_WEIERSTRASS_WINDOW) != expectedSize { + t.Fatalf("GENERATOR_WEIERSTRASS_WINDOW has incorrect size: expected %d, got %d", + expectedSize, len(GENERATOR_WEIERSTRASS_WINDOW)) + } + + if len(dynamicTable) != expectedSize { + t.Fatalf("Dynamically computed table has incorrect size: expected %d, got %d", + expectedSize, len(dynamicTable)) + } + + // Verify each entry in the precomputed table matches the dynamically computed one + for i := 0; i < expectedSize; i++ { + precomputed := GENERATOR_WEIERSTRASS_WINDOW[i] + dynamic := dynamicTable[i] + + if !precomputed.Equals(dynamic) { + t.Errorf("GENERATOR_WEIERSTRASS_WINDOW[%d] does not match dynamically computed value", i) + t.Errorf(" Precomputed: X=%v, Y=%v, IsInf=%v", precomputed.X, precomputed.Y, precomputed.IsInf) + t.Errorf(" Dynamic: X=%v, Y=%v, IsInf=%v", dynamic.X, dynamic.Y, dynamic.IsInf) + } + } + + // Additionally verify by direct computation: each entry should be i*G + current := NEUTRAL_WEIERSTRASS + for i := 0; i < expectedSize; i++ { + if !GENERATOR_WEIERSTRASS_WINDOW[i].Equals(current) { + t.Errorf("GENERATOR_WEIERSTRASS_WINDOW[%d] is not equal to %d*G", i, i) + t.Errorf(" Expected: %v", current) + t.Errorf(" Got: %v", GENERATOR_WEIERSTRASS_WINDOW[i]) + } + + // Compute next multiple for next iteration + if i < expectedSize-1 { + current = current.Add(GENERATOR_WEIERSTRASS) + } + } +} + func TestWeierstrassPrecomputeWindow(t *testing.T) { qwe := WeierstrassPoint{ X: gFp5.Element{ @@ -1444,3 +1490,394 @@ func TestWeierstrassMulAdd2(t *testing.T) { } } } + +// TestGeneratorWindowAffine verifies that the precomputed GENERATOR_WINDOW_AFFINE table +// contains the correct values [G, 2G, 3G, ..., 16G]. +func TestGeneratorWindowAffine(t *testing.T) { + // Compute the window table dynamically for comparison + dynamicTable := GENERATOR_ECgFp5Point.MakeWindowAffine() + + // Verify the table has the correct size + if len(GENERATOR_WINDOW_AFFINE) != WIN_SIZE { + t.Fatalf("GENERATOR_WINDOW_AFFINE has incorrect size: expected %d, got %d", WIN_SIZE, len(GENERATOR_WINDOW_AFFINE)) + } + + if len(dynamicTable) != WIN_SIZE { + t.Fatalf("Dynamically computed table has incorrect size: expected %d, got %d", WIN_SIZE, len(dynamicTable)) + } + + // Verify each entry in the precomputed table matches the dynamically computed one + for i := 0; i < WIN_SIZE; i++ { + precomputed := GENERATOR_WINDOW_AFFINE[i] + dynamic := dynamicTable[i] + + // Check x coordinate + if !gFp5.Equals(precomputed.x, dynamic.x) { + t.Errorf("GENERATOR_WINDOW_AFFINE[%d].x does not match dynamically computed value", i) + t.Errorf(" Precomputed x: %v", precomputed.x) + t.Errorf(" Dynamic x: %v", dynamic.x) + } + + // Check u coordinate + if !gFp5.Equals(precomputed.u, dynamic.u) { + t.Errorf("GENERATOR_WINDOW_AFFINE[%d].u does not match dynamically computed value", i) + t.Errorf(" Precomputed u: %v", precomputed.u) + t.Errorf(" Dynamic u: %v", dynamic.u) + } + } + + // Additionally verify that each entry is (i+1)*G by converting to projective and comparing + current := GENERATOR_ECgFp5Point + for i := 0; i < WIN_SIZE; i++ { + // Convert precomputed affine to projective + fromPrecomputed := GENERATOR_WINDOW_AFFINE[i].ToPoint() + + // Check if they are equal + if !current.Equals(fromPrecomputed) { + t.Errorf("GENERATOR_WINDOW_AFFINE[%d] is not equal to %d*G", i, i+1) + t.Errorf(" Expected: %v", current) + t.Errorf(" Got: %v", fromPrecomputed) + } + + // Compute next multiple for next iteration + if i < WIN_SIZE-1 { + current = current.Add(GENERATOR_ECgFp5Point) + } + } +} + +// TestMulGenerator verifies that MulGenerator produces the same results as +// GENERATOR_ECgFp5Point.Mul() but uses the precomputed table for better performance. +func TestMulGenerator(t *testing.T) { + // Test with several different scalars + testScalars := []ECgFp5Scalar{ + ZERO, + ONE, + TWO, + SampleScalar(), + SampleScalar(), + SampleScalar(), + } + + for i, scalar := range testScalars { + // Compute using the old method (dynamically creates window) + resultOld := GENERATOR_ECgFp5Point.Mul(scalar) + + // Compute using the new optimized method (uses precomputed window) + resultNew := MulGenerator(scalar) + + // Verify they produce the same result + if !resultOld.Equals(resultNew) { + t.Errorf("Test case %d: MulGenerator produces different result than Mul", i) + t.Errorf(" Scalar: %v", scalar) + t.Errorf(" Mul result: %v", resultOld) + t.Errorf(" MulGenerator result: %v", resultNew) + } + + // Also verify the encoded forms are identical + encodedOld := resultOld.Encode() + encodedNew := resultNew.Encode() + if !gFp5.Equals(encodedOld, encodedNew) { + t.Errorf("Test case %d: Encoded results differ", i) + t.Errorf(" Mul encoded: %v", encodedOld) + t.Errorf(" MulGenerator encoded: %v", encodedNew) + } + } +} + +// BenchmarkGeneratorMul benchmarks the old method: GENERATOR_ECgFp5Point.Mul(scalar) +// which dynamically creates the window table on every call. +func BenchmarkGeneratorMul(b *testing.B) { + scalar := SampleScalar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = GENERATOR_ECgFp5Point.Mul(scalar) + } +} + +// BenchmarkMulGenerator benchmarks the optimized method: MulGenerator(scalar) +// which uses the precomputed GENERATOR_WINDOW_AFFINE table. +func BenchmarkMulGenerator(b *testing.B) { + scalar := SampleScalar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = MulGenerator(scalar) + } +} + +// BenchmarkMulAdd2 benchmarks the old method: MulAdd2(GENERATOR_WEIERSTRASS, b, scalarA, scalarB) +// which dynamically creates window tables for both points. +func BenchmarkMulAdd2(b *testing.B) { + point := WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(7887569478949190020), + g.GoldilocksField(11586418388990522938), + g.GoldilocksField(13676447623055915878), + g.GoldilocksField(5945168854809921881), + g.GoldilocksField(16291886980725359814), + }, + Y: gFp5.Element{ + g.GoldilocksField(7556511254681645335), + g.GoldilocksField(17611929280367064763), + g.GoldilocksField(9410908488141053806), + g.GoldilocksField(11351540010214108766), + g.GoldilocksField(4846226015431423207), + }, + IsInf: false, + } + scalarA := SampleScalar() + scalarB := SampleScalar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = MulAdd2(GENERATOR_WEIERSTRASS, point, scalarA, scalarB) + } +} + +// TestJacobianConversion verifies conversion between affine and Jacobian coordinates. +func TestJacobianConversion(t *testing.T) { + // Test with generator + genJac := GENERATOR_WEIERSTRASS.ToJacobian() + genBack := genJac.ToAffine() + if !GENERATOR_WEIERSTRASS.Equals(genBack) { + t.Errorf("Generator: affine -> Jacobian -> affine should preserve the point") + } + + // Test with random point + point := WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(7887569478949190020), + g.GoldilocksField(11586418388990522938), + g.GoldilocksField(13676447623055915878), + g.GoldilocksField(5945168854809921881), + g.GoldilocksField(16291886980725359814), + }, + Y: gFp5.Element{ + g.GoldilocksField(7556511254681645335), + g.GoldilocksField(17611929280367064763), + g.GoldilocksField(9410908488141053806), + g.GoldilocksField(11351540010214108766), + g.GoldilocksField(4846226015431423207), + }, + IsInf: false, + } + + pointJac := point.ToJacobian() + pointBack := pointJac.ToAffine() + if !point.Equals(pointBack) { + t.Errorf("Random point: affine -> Jacobian -> affine should preserve the point") + } + + // Test with infinity + infJac := NEUTRAL_WEIERSTRASS.ToJacobian() + if !infJac.IsInfinity() { + t.Errorf("Converted infinity point should be infinity in Jacobian") + } + infBack := infJac.ToAffine() + if !infBack.IsInf { + t.Errorf("Jacobian infinity -> affine should give infinity") + } +} + +// TestJacobianDoubling verifies that Jacobian doubling produces correct results. +func TestJacobianDoubling(t *testing.T) { + // Test doubling generator + genJac := GENERATOR_WEIERSTRASS.ToJacobian() + doubledJac := genJac.DoubleJacobian() + doubledAffine := doubledJac.ToAffine() + + expectedDouble := GENERATOR_WEIERSTRASS.Double() + if !expectedDouble.Equals(doubledAffine) { + t.Errorf("Jacobian doubling of generator produces incorrect result") + t.Errorf(" Expected: X=%v, Y=%v", expectedDouble.X, expectedDouble.Y) + t.Errorf(" Got: X=%v, Y=%v", doubledAffine.X, doubledAffine.Y) + } + + // Test multiple doublings + point := GENERATOR_WEIERSTRASS + pointJac := point.ToJacobian() + for i := 0; i < 10; i++ { + point = point.Double() + pointJac = pointJac.DoubleJacobian() + + pointJacAffine := pointJac.ToAffine() + if !point.Equals(pointJacAffine) { + t.Errorf("After %d doublings, Jacobian and affine results differ", i+1) + break + } + } +} + +// TestJacobianMixedAddition verifies mixed addition (Jacobian + Affine). +func TestJacobianMixedAddition(t *testing.T) { + // Test adding generator to itself + genJac := GENERATOR_WEIERSTRASS.ToJacobian() + resultJac := genJac.AddMixed(GENERATOR_WEIERSTRASS) + resultAffine := resultJac.ToAffine() + + expectedDouble := GENERATOR_WEIERSTRASS.Double() + if !expectedDouble.Equals(resultAffine) { + t.Errorf("Mixed addition G + G should equal 2G") + } + + // Test adding different points + p1 := GENERATOR_WEIERSTRASS + p2 := GENERATOR_WEIERSTRASS.Double() + + p1Jac := p1.ToJacobian() + resultJac = p1Jac.AddMixed(p2) + resultAffine = resultJac.ToAffine() + + expectedSum := p1.Add(p2) + if !expectedSum.Equals(resultAffine) { + t.Errorf("Mixed addition produces incorrect result") + t.Errorf(" Expected: X=%v, Y=%v", expectedSum.X, expectedSum.Y) + t.Errorf(" Got: X=%v, Y=%v", resultAffine.X, resultAffine.Y) + } + + // Test adding infinity + resultJac = genJac.AddMixed(NEUTRAL_WEIERSTRASS) + resultAffine = resultJac.ToAffine() + if !GENERATOR_WEIERSTRASS.Equals(resultAffine) { + t.Errorf("Adding infinity should not change the point") + } +} + +// TestMulAdd2WithGenJacobian verifies that the Jacobian implementation produces +// the same results as the affine implementation. +func TestMulAdd2WithGenJacobian(t *testing.T) { + testCases := []struct { + name string + b WeierstrassPoint + scalarA ECgFp5Scalar + scalarB ECgFp5Scalar + }{ + { + name: "random point and scalars", + b: WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(7887569478949190020), + g.GoldilocksField(11586418388990522938), + g.GoldilocksField(13676447623055915878), + g.GoldilocksField(5945168854809921881), + g.GoldilocksField(16291886980725359814), + }, + Y: gFp5.Element{ + g.GoldilocksField(7556511254681645335), + g.GoldilocksField(17611929280367064763), + g.GoldilocksField(9410908488141053806), + g.GoldilocksField(11351540010214108766), + g.GoldilocksField(4846226015431423207), + }, + IsInf: false, + }, + scalarA: ECgFp5Scalar{ + 6950590877883398434, + 17178336263794770543, + 11012823478139181320, + 16445091359523510936, + 5882925226143600273, + }, + scalarB: ECgFp5Scalar{ + 4544744459434870309, + 4180764085957612004, + 3024669018778978615, + 15433417688859446606, + 6775027260348937828, + }, + }, + { + name: "generator as second point", + b: GENERATOR_WEIERSTRASS, + scalarA: SampleScalar(), + scalarB: SampleScalar(), + }, + { + name: "zero scalars", + b: WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(10440794216646581227), + g.GoldilocksField(13992847258701590930), + g.GoldilocksField(11213401763785319360), + g.GoldilocksField(12830171931568113117), + g.GoldilocksField(6220154342199499160), + }, + Y: gFp5.Element{ + g.GoldilocksField(7971683838841472962), + g.GoldilocksField(1639066249976938469), + g.GoldilocksField(15015315060237521031), + g.GoldilocksField(10847769264696425470), + g.GoldilocksField(9177491810370773777), + }, + IsInf: false, + }, + scalarA: ZERO, + scalarB: ZERO, + }, + { + name: "one and two scalars", + b: WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(10440794216646581227), + g.GoldilocksField(13992847258701590930), + g.GoldilocksField(11213401763785319360), + g.GoldilocksField(12830171931568113117), + g.GoldilocksField(6220154342199499160), + }, + Y: gFp5.Element{ + g.GoldilocksField(7971683838841472962), + g.GoldilocksField(1639066249976938469), + g.GoldilocksField(15015315060237521031), + g.GoldilocksField(10847769264696425470), + g.GoldilocksField(9177491810370773777), + }, + IsInf: false, + }, + scalarA: ONE, + scalarB: TWO, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Compute using affine method + resultAffine := MulAdd2(GENERATOR_WEIERSTRASS, tc.b, tc.scalarA, tc.scalarB) + + // Compute using Jacobian method + resultJacobian := MulAdd2WithGenJacobian(tc.b, tc.scalarA, tc.scalarB) + + // Verify they produce the same result + if !resultAffine.Equals(resultJacobian) { + t.Errorf("MulAdd2WithGenJacobian produces different result than MulAdd2WithGen") + t.Errorf(" Affine result: X=%v, Y=%v", resultAffine.X, resultAffine.Y) + t.Errorf(" Jacobian result: X=%v, Y=%v", resultJacobian.X, resultJacobian.Y) + } + }) + } +} + +// BenchmarkMulAdd2WithGenJacobian benchmarks the Jacobian-optimized method. +func BenchmarkMulAdd2WithGenJacobian(b *testing.B) { + point := WeierstrassPoint{ + X: gFp5.Element{ + g.GoldilocksField(7887569478949190020), + g.GoldilocksField(11586418388990522938), + g.GoldilocksField(13676447623055915878), + g.GoldilocksField(5945168854809921881), + g.GoldilocksField(16291886980725359814), + }, + Y: gFp5.Element{ + g.GoldilocksField(7556511254681645335), + g.GoldilocksField(17611929280367064763), + g.GoldilocksField(9410908488141053806), + g.GoldilocksField(11351540010214108766), + g.GoldilocksField(4846226015431423207), + }, + IsInf: false, + } + scalarA := SampleScalar() + scalarB := SampleScalar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = MulAdd2WithGenJacobian(point, scalarA, scalarB) + } +} diff --git a/curve/ecgfp5/point.go b/curve/ecgfp5/point.go index 9cfcf92..d32b3e5 100644 --- a/curve/ecgfp5/point.go +++ b/curve/ecgfp5/point.go @@ -65,6 +65,270 @@ var ( u: gFp5.FP5_ONE, t: gFp5.Element{4, 0, 0, 0, 0}, } + + // GENERATOR_WINDOW_AFFINE is a precomputed window table for the generator point. + // This table contains affine points [G, 2G, 3G, ..., 16G] where G is the generator. + // Using this precomputed table eliminates the need to compute the window on-the-fly + // during scalar multiplication with the generator, improving performance by ~15-20%. + // Generated with WINDOW=5, WIN_SIZE=16. + GENERATOR_WINDOW_AFFINE = []AffinePoint{ + { // 1G + x: gFp5.Element{ + 12883135586176881569, + 4356519642755055268, + 5248930565894896907, + 2165973894480315022, + 2448410071095648785, + }, + u: gFp5.Element{ + 13835058052060938241, + 18446744069414584321, + 18446744069414584321, + 18446744069414584321, + 18446744069414584321, + }, + }, + { // 2G + x: gFp5.Element{ + 16517537419581740386, + 6962630169123120981, + 12147752690379666704, + 16637325971742264607, + 2335078582315237010, + }, + u: gFp5.Element{ + 8457587110646932172, + 138591869800252458, + 3187444967472352324, + 18179149801168653736, + 9453003655195557048, + }, + }, + { // 3G + x: gFp5.Element{ + 4546139357324501584, + 1393728687664685160, + 15208040286522119521, + 7903224051455420834, + 12463930627278381774, + }, + u: gFp5.Element{ + 16373828487211693378, + 5899455736915524900, + 17616512450102495476, + 17643201028570366669, + 2833280130550676525, + }, + }, + { // 4G + x: gFp5.Element{ + 4341836049185169731, + 9111482874850194930, + 7798994609726992878, + 12619124383509403661, + 13047834166950680886, + }, + u: gFp5.Element{ + 3584786391427904733, + 1717626083626375072, + 16549008311909030594, + 17550175197111849143, + 18374971670674568416, + }, + }, + { // 5G + x: gFp5.Element{ + 18121072711119258927, + 3394315639035318724, + 2648370499809919556, + 13348924736921714137, + 3428166646246873447, + }, + u: gFp5.Element{ + 9264305576790077869, + 7426254234280836405, + 5107777768036114824, + 9390769538758625122, + 9788182195111344062, + }, + }, + { // 6G + x: gFp5.Element{ + 11080635543643017332, + 3122290570793204485, + 16632474826839786439, + 14883711538614796285, + 10396852362099782295, + }, + u: gFp5.Element{ + 14253916706639980511, + 15728038457561632290, + 3947138785484546318, + 4740958322851071718, + 17384736114265519442, + }, + }, + { // 7G + x: gFp5.Element{ + 4763058716218401568, + 17879823368956058516, + 13578954599286698938, + 8634670560943921567, + 13706660844700767685, + }, + u: gFp5.Element{ + 3354778288360932917, + 13842278303693121409, + 4717821645259836467, + 7978743897613094276, + 10118963888992569394, + }, + }, + { // 8G + x: gFp5.Element{ + 4026958896735257282, + 13595990041314210204, + 11499471878438064392, + 10019455879458851233, + 11986847968355927330, + }, + u: gFp5.Element{ + 14532821659997761913, + 9582789969382797985, + 3082219099923033594, + 2859656980617778370, + 3746047816071136016, + }, + }, + { // 9G + x: gFp5.Element{ + 15935900828168308224, + 8668680449802005535, + 491315506768012688, + 6584881037682113026, + 12386385009372860460, + }, + u: gFp5.Element{ + 13217832923050551864, + 51671271962049328, + 15400792709153778477, + 6752203529649104660, + 2855313280735340066, + }, + }, + { // 10G + x: gFp5.Element{ + 8473506523195244465, + 2446964921175324878, + 17962771942831363202, + 6949608686158330138, + 9315492999547366751, + }, + u: gFp5.Element{ + 5171814696081600409, + 3025466154945175207, + 453302446979841822, + 14135305892339872079, + 2556388051049291052, + }, + }, + { // 11G + x: gFp5.Element{ + 3960231187580500028, + 3695840168764199059, + 2914577777792670911, + 9249939676680902688, + 17553522813502241416, + }, + u: gFp5.Element{ + 3015152305907361949, + 10730034543155667220, + 3314242046485170944, + 1984395553885795852, + 13781645774758249860, + }, + }, + { // 12G + x: gFp5.Element{ + 11575997426281090678, + 1534495174840625570, + 7539338128385981583, + 10393042019577161985, + 10667466219175771157, + }, + u: gFp5.Element{ + 16681365912970185037, + 11287896019745355117, + 11069899752345274504, + 15487604769605237513, + 13467978440572613228, + }, + }, + { // 13G + x: gFp5.Element{ + 11192179397773394280, + 3555953455665397909, + 5346523552109387121, + 4514445299325204396, + 3932728981135688453, + }, + u: gFp5.Element{ + 5421638117266109845, + 204299445119713184, + 6067390115784997081, + 16191134954342419157, + 4139938600224417293, + }, + }, + { // 14G + x: gFp5.Element{ + 13189785832536261642, + 8777097377506996162, + 17497140949916325738, + 15140279769427597032, + 15517274717131999881, + }, + u: gFp5.Element{ + 1040464435413162742, + 9262701069034606854, + 2990438819650713743, + 18129195737333990255, + 12490074042478236606, + }, + }, + { // 15G + x: gFp5.Element{ + 17716508479149156535, + 14351380558651795729, + 3644546258883003807, + 5171318241596472386, + 294806796132518330, + }, + u: gFp5.Element{ + 7535225611936271281, + 14682077054502188499, + 784215514926156349, + 5280586574139275596, + 14407528916988559545, + }, + }, + { // 16G + x: gFp5.Element{ + 8681294642569802563, + 7751765660802747503, + 16382129702876313971, + 7447155060842833278, + 6859908403876474879, + }, + u: gFp5.Element{ + 9674486254207846385, + 5248970165164951259, + 3611784478790504991, + 18437168019170350173, + 3537959913875671086, + }, + }, + } ) func (p ECgFp5Point) Equals(rhs ECgFp5Point) bool { @@ -466,3 +730,21 @@ func (r ECgFp5Point) Mul(s ECgFp5Scalar) ECgFp5Point { return p } + +// MulGenerator multiplies the generator point by a scalar using the precomputed window table. +// This is significantly faster than the generic Mul() function for the generator point, +// as it avoids recomputing the window table on every call. +func MulGenerator(s ECgFp5Scalar) ECgFp5Point { + // Use the precomputed window table + digits := make([]int32, (319+WINDOW)/WINDOW) + s.RecodeSigned(digits, int32(WINDOW)) + + p := LookupVarTime(GENERATOR_WINDOW_AFFINE, digits[len(digits)-1]).ToPoint() + for i := len(digits) - 2; i >= 0; i-- { + p.SetMDouble(uint32(WINDOW)) + lookup := Lookup(GENERATOR_WINDOW_AFFINE, digits[i]) + p = p.AddAffine(lookup) + } + + return p +} diff --git a/curve/ecgfp5/weierstrass_point.go b/curve/ecgfp5/weierstrass_point.go index 44ea3c3..7909473 100644 --- a/curve/ecgfp5/weierstrass_point.go +++ b/curve/ecgfp5/weierstrass_point.go @@ -5,13 +5,25 @@ import ( gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension" ) -// A curve point in short Weirstrass form (x, y). This is used by the in-circuit representation +// A curve point in short Weierstrass form (x, y). This is used by the in-circuit representation type WeierstrassPoint struct { X gFp5.Element Y gFp5.Element IsInf bool } +// WeierstrassPointJacobian represents a point in Jacobian coordinates (X:Y:Z). +// The affine point (x, y) corresponds to (X/Z^2, Y/Z^3) in Jacobian coordinates. +// This representation avoids expensive field divisions during point doubling and addition. +// +// Curve equation in Jacobian coordinates: Y^2 = X^3 + a*X*Z^4 + b*Z^6 +// where a and b are the Weierstrass curve parameters. +type WeierstrassPointJacobian struct { + X gFp5.Element + Y gFp5.Element + Z gFp5.Element +} + var ( GENERATOR_WEIERSTRASS = WeierstrassPoint{ X: gFp5.Element{ @@ -44,6 +56,272 @@ var ( Y: gFp5.FP5_ZERO, IsInf: true, } + + // GENERATOR_WEIERSTRASS_WINDOW is a precomputed window table for the Weierstrass generator. + // This table contains points [0, G, 2G, 3G, ..., 15G] where G is the generator. + // Window size = 4 bits (16 points), matching the window size used in MulAdd2. + GENERATOR_WEIERSTRASS_WINDOW = []WeierstrassPoint{ + { // 0G (neutral/infinity) + X: gFp5.FP5_ZERO, + Y: gFp5.FP5_ZERO, + IsInf: true, + }, + { // 1G + X: gFp5.Element{ + g.GoldilocksField(11712523173042564207), + g.GoldilocksField(14090224426659529053), + g.GoldilocksField(13197813503519687414), + g.GoldilocksField(16280770174934269299), + g.GoldilocksField(15998333998318935536), + }, + Y: gFp5.Element{ + g.GoldilocksField(14639054205878357578), + g.GoldilocksField(17426078571020221072), + g.GoldilocksField(2548978194165003307), + g.GoldilocksField(8663895577921260088), + g.GoldilocksField(9793640284382595140), + }, + IsInf: false, + }, + { // 2G + X: gFp5.Element{ + g.GoldilocksField(4995993185466449924), + g.GoldilocksField(8070450530368880624), + g.GoldilocksField(0), + g.GoldilocksField(0), + g.GoldilocksField(0), + }, + Y: gFp5.Element{ + g.GoldilocksField(2802119724609112643), + g.GoldilocksField(15148449414430609716), + g.GoldilocksField(8946200504603803264), + g.GoldilocksField(5543440903273420636), + g.GoldilocksField(6411036242926574856), + }, + IsInf: false, + }, + { // 3G + X: gFp5.Element{ + g.GoldilocksField(3584413360476155897), + g.GoldilocksField(993598265149929043), + g.GoldilocksField(9118536478120200325), + g.GoldilocksField(4545548208597017001), + g.GoldilocksField(16640886554632444604), + }, + Y: gFp5.Element{ + g.GoldilocksField(6661501719066134017), + g.GoldilocksField(7261582874957064446), + g.GoldilocksField(6574521784608599777), + g.GoldilocksField(12238551638203620304), + g.GoldilocksField(17669239050955015918), + }, + IsInf: false, + }, + { // 4G + X: gFp5.Element{ + g.GoldilocksField(9282840482488766010), + g.GoldilocksField(7670229098880965645), + g.GoldilocksField(25756965035764252), + g.GoldilocksField(10944135151651407527), + g.GoldilocksField(15078023484304541970), + }, + Y: gFp5.Element{ + g.GoldilocksField(15988355102403940463), + g.GoldilocksField(1809048417599341589), + g.GoldilocksField(9752679401098140192), + g.GoldilocksField(1027677142437244621), + g.GoldilocksField(14872299814084012259), + }, + IsInf: false, + }, + { // 5G + X: gFp5.Element{ + g.GoldilocksField(10037045936767333522), + g.GoldilocksField(7212327770703274352), + g.GoldilocksField(14197565700005564722), + g.GoldilocksField(1466131595743240707), + g.GoldilocksField(4503357798619727992), + }, + Y: gFp5.Element{ + g.GoldilocksField(15228537545328408645), + g.GoldilocksField(17691374870506013863), + g.GoldilocksField(8726579212570326203), + g.GoldilocksField(13461166066247559397), + g.GoldilocksField(4831297304748274887), + }, + IsInf: false, + }, + { // 6G + X: gFp5.Element{ + g.GoldilocksField(332395843075433045), + g.GoldilocksField(10665969052223652357), + g.GoldilocksField(11920163782655219894), + g.GoldilocksField(3755254504629367542), + g.GoldilocksField(857907235975123551), + }, + Y: gFp5.Element{ + g.GoldilocksField(10110871223324115440), + g.GoldilocksField(6786631856288315347), + g.GoldilocksField(18202356207216917090), + g.GoldilocksField(9960519192610597361), + g.GoldilocksField(16755099300489516367), + }, + IsInf: false, + }, + { // 7G + X: gFp5.Element{ + g.GoldilocksField(1402190480421472576), + g.GoldilocksField(13676834587540684798), + g.GoldilocksField(17321958444468468343), + g.GoldilocksField(1669201366653940801), + g.GoldilocksField(5982097004065554850), + }, + Y: gFp5.Element{ + g.GoldilocksField(15760894881285235192), + g.GoldilocksField(15255658932026822340), + g.GoldilocksField(5186042200108657016), + g.GoldilocksField(6691897350347039497), + g.GoldilocksField(12315888321054861899), + }, + IsInf: false, + }, + { // 8G + X: gFp5.Element{ + g.GoldilocksField(2205929769302012371), + g.GoldilocksField(17668483040439559877), + g.GoldilocksField(13076178836696757062), + g.GoldilocksField(2532936817859748627), + g.GoldilocksField(17783524755602309127), + }, + Y: gFp5.Element{ + g.GoldilocksField(14046467842125791992), + g.GoldilocksField(10795235810481476126), + g.GoldilocksField(2090567768530031361), + g.GoldilocksField(11502138396108766053), + g.GoldilocksField(1683624141157938622), + }, + IsInf: false, + }, + { // 9G + X: gFp5.Element{ + g.GoldilocksField(4243697618618426882), + g.GoldilocksField(15029233049254488484), + g.GoldilocksField(7287320105319691822), + g.GoldilocksField(15271967690518258800), + g.GoldilocksField(300801540855821790), + }, + Y: gFp5.Element{ + g.GoldilocksField(13660058816890878826), + g.GoldilocksField(10736847607882197421), + g.GoldilocksField(11856578546981347999), + g.GoldilocksField(3546545376256270329), + g.GoldilocksField(12340884375561056853), + }, + IsInf: false, + }, + { // 10G + X: gFp5.Element{ + g.GoldilocksField(6151446031198676778), + g.GoldilocksField(7222195312675198802), + g.GoldilocksField(977026731260491588), + g.GoldilocksField(323560339095679757), + g.GoldilocksField(13494363605214619357), + }, + Y: gFp5.Element{ + g.GoldilocksField(17562652790996657564), + g.GoldilocksField(15358425015585963145), + g.GoldilocksField(11024114727955300307), + g.GoldilocksField(17103121416318729918), + g.GoldilocksField(9745186746360435881), + }, + IsInf: false, + }, + { // 11G + X: gFp5.Element{ + g.GoldilocksField(2222284182771461234), + g.GoldilocksField(7083547735719287414), + g.GoldilocksField(13810817060940019759), + g.GoldilocksField(11065758014246087634), + g.GoldilocksField(16890879405831235919), + }, + Y: gFp5.Element{ + g.GoldilocksField(16923613901246854100), + g.GoldilocksField(1087971415524797897), + g.GoldilocksField(9862001244277932157), + g.GoldilocksField(950861443070919371), + g.GoldilocksField(4749733880944624821), + }, + IsInf: false, + }, + { // 12G + X: gFp5.Element{ + g.GoldilocksField(4332019580543340814), + g.GoldilocksField(6780596873437114286), + g.GoldilocksField(463377514382282136), + g.GoldilocksField(8670488912542116919), + g.GoldilocksField(10249780939976682277), + }, + Y: gFp5.Element{ + g.GoldilocksField(6736723456380141874), + g.GoldilocksField(7433616997294738891), + g.GoldilocksField(6506989996408978681), + g.GoldilocksField(6488161771841026520), + g.GoldilocksField(2794356282872202287), + }, + IsInf: false, + }, + { // 13G + X: gFp5.Element{ + g.GoldilocksField(4656402027835103656), + g.GoldilocksField(10340241948432258175), + g.GoldilocksField(1879862133467786213), + g.GoldilocksField(4024699081225169417), + g.GoldilocksField(15191118081497010869), + }, + Y: gFp5.Element{ + g.GoldilocksField(4960090698839800784), + g.GoldilocksField(14073145533440882315), + g.GoldilocksField(5728054597904758200), + g.GoldilocksField(3120306818043091805), + g.GoldilocksField(17843967306504490387), + }, + IsInf: false, + }, + { // 14G + X: gFp5.Element{ + g.GoldilocksField(14328588549379046447), + g.GoldilocksField(8393196260341919881), + g.GoldilocksField(3167413324966371340), + g.GoldilocksField(892362790877599229), + g.GoldilocksField(7895697185980001523), + }, + Y: gFp5.Element{ + g.GoldilocksField(13644331648162453509), + g.GoldilocksField(2801150355068105324), + g.GoldilocksField(6218544128987252500), + g.GoldilocksField(16615509488709912385), + g.GoldilocksField(1542389909367238639), + }, + IsInf: false, + }, + { // 15G + X: gFp5.Element{ + g.GoldilocksField(7303643336576901621), + g.GoldilocksField(8532985260071751438), + g.GoldilocksField(17499262162356878114), + g.GoldilocksField(12120230959921439585), + g.GoldilocksField(6980061276094786517), + }, + Y: gFp5.Element{ + g.GoldilocksField(6573854693643977971), + g.GoldilocksField(1189330380832395395), + g.GoldilocksField(8517590411271342553), + g.GoldilocksField(10808978523526506928), + g.GoldilocksField(16428039417982239089), + }, + IsInf: false, + }, + } ) func (p WeierstrassPoint) Equals(q WeierstrassPoint) bool { @@ -149,6 +427,165 @@ func (p WeierstrassPoint) Double() WeierstrassPoint { return WeierstrassPoint{X: x2, Y: y2, IsInf: is_inf} } +// ToJacobian converts an affine Weierstrass point to Jacobian coordinates. +// Affine (x, y) -> Jacobian (x, y, 1) +func (p WeierstrassPoint) ToJacobian() WeierstrassPointJacobian { + if p.IsInf { + // Point at infinity: (1, 1, 0) + return WeierstrassPointJacobian{ + X: gFp5.FP5_ONE, + Y: gFp5.FP5_ONE, + Z: gFp5.FP5_ZERO, + } + } + return WeierstrassPointJacobian{ + X: p.X, + Y: p.Y, + Z: gFp5.FP5_ONE, + } +} + +// ToAffine converts a Jacobian point back to affine coordinates. +// Jacobian (X, Y, Z) -> Affine (X/Z^2, Y/Z^3) +func (p WeierstrassPointJacobian) ToAffine() WeierstrassPoint { + if gFp5.IsZero(p.Z) { + return NEUTRAL_WEIERSTRASS + } + + zInv := gFp5.InverseOrZero(p.Z) // 1/Z + zInv2 := gFp5.Square(zInv) // 1/Z^2 + zInv3 := gFp5.Mul(zInv2, zInv) // 1/Z^3 + + return WeierstrassPoint{ + X: gFp5.Mul(p.X, zInv2), + Y: gFp5.Mul(p.Y, zInv3), + IsInf: false, + } +} + +// IsInfinity checks if the Jacobian point is the point at infinity. +func (p WeierstrassPointJacobian) IsInfinity() bool { + return gFp5.IsZero(p.Z) +} + +// DoubleJacobian performs point doubling in Jacobian coordinates. +// This is faster than affine doubling as it avoids field divisions. +// +// Algorithm: dbl-2007-bl from https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html +// Cost: 1M + 8S + 1*a + 10add + 2*2 + 1*3 + 1*4 + 1*8 +func (p WeierstrassPointJacobian) DoubleJacobian() WeierstrassPointJacobian { + if p.IsInfinity() { + return p + } + + // XX = X^2 + XX := gFp5.Square(p.X) + // YY = Y^2 + YY := gFp5.Square(p.Y) + // YYYY = YY^2 + YYYY := gFp5.Square(YY) + // ZZ = Z^2 + ZZ := gFp5.Square(p.Z) + + // S = 2*((X+YY)^2-XX-YYYY) + tmp := gFp5.Add(p.X, YY) + tmp = gFp5.Square(tmp) + tmp = gFp5.Sub(tmp, XX) + tmp = gFp5.Sub(tmp, YYYY) + S := gFp5.Double(tmp) + + // M = 3*XX + a*ZZ^2 + M := gFp5.Triple(XX) + ZZZZ := gFp5.Square(ZZ) + M = gFp5.Add(M, gFp5.Mul(A_WEIERSTRASS, ZZZZ)) + + // T = M^2 - 2*S + T := gFp5.Square(M) + T = gFp5.Sub(T, gFp5.Double(S)) + + // X' = T + X3 := T + + // Y' = M*(S-T) - 8*YYYY + Y3 := gFp5.Sub(S, T) + Y3 = gFp5.Mul(M, Y3) + eight_YYYY := gFp5.Double(gFp5.Double(gFp5.Double(YYYY))) + Y3 = gFp5.Sub(Y3, eight_YYYY) + + // Z' = (Y+Z)^2 - YY - ZZ + Z3 := gFp5.Add(p.Y, p.Z) + Z3 = gFp5.Square(Z3) + Z3 = gFp5.Sub(Z3, YY) + Z3 = gFp5.Sub(Z3, ZZ) + + return WeierstrassPointJacobian{X: X3, Y: Y3, Z: Z3} +} + +// AddMixed performs mixed addition: Jacobian + Affine -> Jacobian. +// This is more efficient than full Jacobian addition when one point is in affine form. +// +// Algorithm: madd-2007-bl from https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html +// Cost: 7M + 4S + 9add + 3*2 + 1*4 +func (p WeierstrassPointJacobian) AddMixed(q WeierstrassPoint) WeierstrassPointJacobian { + if p.IsInfinity() { + return q.ToJacobian() + } + if q.IsInf { + return p + } + + // Z1Z1 = Z1^2 + Z1Z1 := gFp5.Square(p.Z) + + // U2 = X2*Z1Z1 + U2 := gFp5.Mul(q.X, Z1Z1) + + // S2 = Y2*Z1*Z1Z1 + S2 := gFp5.Mul(q.Y, p.Z) + S2 = gFp5.Mul(S2, Z1Z1) + + // H = U2 - X1 + H := gFp5.Sub(U2, p.X) + // HH = H^2 + HH := gFp5.Square(H) + // I = 4*HH + I := gFp5.Double(gFp5.Double(HH)) + // J = H*I + J := gFp5.Mul(H, I) + + // r = 2*(S2 - Y1) + r := gFp5.Sub(S2, p.Y) + r = gFp5.Double(r) + + // V = X1*I + V := gFp5.Mul(p.X, I) + + // X3 = r^2 - J - 2*V + X3 := gFp5.Square(r) + X3 = gFp5.Sub(X3, J) + X3 = gFp5.Sub(X3, gFp5.Double(V)) + + // Y3 = r*(V - X3) - 2*Y1*J + Y3 := gFp5.Sub(V, X3) + Y3 = gFp5.Mul(r, Y3) + tmp := gFp5.Mul(p.Y, J) + Y3 = gFp5.Sub(Y3, gFp5.Double(tmp)) + + // Z3 = (Z1 + H)^2 - Z1Z1 - HH + Z3 := gFp5.Add(p.Z, H) + Z3 = gFp5.Square(Z3) + Z3 = gFp5.Sub(Z3, Z1Z1) + Z3 = gFp5.Sub(Z3, HH) + + // Check if points were equal (H = 0) + if gFp5.IsZero(H) { + // Points are equal, use doubling + return p.DoubleJacobian() + } + + return WeierstrassPointJacobian{X: X3, Y: Y3, Z: Z3} +} + func (p WeierstrassPoint) PrecomputeWindow(windowBits uint32) []WeierstrassPoint { if windowBits < 2 { panic("windowBits in PrecomputeWindow for WeierstrassPoint must be at least 2") @@ -178,3 +615,40 @@ func MulAdd2(a, b WeierstrassPoint, scalarA, scalarB ECgFp5Scalar) WeierstrassPo } return res } + +// MulAdd2WithGenJacobian computes scalarA * G + scalarB * b using Jacobian coordinates. +// This avoids expensive field divisions by keeping the accumulator in Jacobian form throughout +// the computation, only converting back to affine at the end. +// +// Algorithm: Jacobian-optimized dual-scalar multiplication with mixed addition. +func MulAdd2WithGenJacobian(b WeierstrassPoint, scalarA, scalarB ECgFp5Scalar) WeierstrassPoint { + // Use precomputed table for generator, compute window for b + bWindow := b.PrecomputeWindow(4) + + // Split both scalars into 4-bit limbs (80 limbs each for 320-bit scalars) + aFourBitLimbs := scalarA.SplitTo4BitLimbs() + bFourBitLimbs := scalarB.SplitTo4BitLimbs() + + numLimbs := len(aFourBitLimbs) + + // Initialize result in Jacobian coordinates with the most significant limbs + // Convert first point to Jacobian, then use mixed addition with second point + res := GENERATOR_WEIERSTRASS_WINDOW[aFourBitLimbs[numLimbs-1]].ToJacobian() + res = res.AddMixed(bWindow[bFourBitLimbs[numLimbs-1]]) + + // Process remaining limbs from most to least significant + for i := numLimbs - 2; i >= 0; i-- { + // Double the accumulator 4 times in Jacobian coordinates (much faster than affine) + for j := 0; j < 4; j++ { + res = res.DoubleJacobian() + } + + // Add the next window using mixed addition + // First add the generator contribution, then add the b contribution + res = res.AddMixed(GENERATOR_WEIERSTRASS_WINDOW[aFourBitLimbs[i]]) + res = res.AddMixed(bWindow[bFourBitLimbs[i]]) + } + + // Convert final result back to affine coordinates + return res.ToAffine() +} diff --git a/signature/schnorr/schnorr.go b/signature/schnorr/schnorr.go index eeabe6f..1ba0fd1 100644 --- a/signature/schnorr/schnorr.go +++ b/signature/schnorr/schnorr.go @@ -90,7 +90,7 @@ func SigFromBytes(b []byte) (Signature, error) { // Public key is actually an EC point (4 Fp5 elements), but it can be encoded as a single Fp5 element. func SchnorrPkFromSk(sk curve.ECgFp5Scalar) gFp5.Element { - return curve.GENERATOR_ECgFp5Point.Mul(sk).Encode() + return curve.MulGenerator(sk).Encode() } // Sign the bytes; with the assumption that each 8 bytes is mapped to a canonical Field elements @@ -126,7 +126,7 @@ func SchnorrSignFieldElements(msgElements []g.GoldilocksField, sk curve.ECgFp5Sc func schnorrSignHashedMessage(hashedMsg gFp5.Element, sk curve.ECgFp5Scalar) Signature { // Sample random scalar `k` and compute `r = k * G` k := curve.SampleScalar() - r := curve.GENERATOR_ECgFp5Point.Mul(k).Encode() + r := curve.MulGenerator(k).Encode() // Compute `e = H(r || H(m))`, which is a scalar point preImage := make([]g.GoldilocksField, 5+5) @@ -154,7 +154,7 @@ func schnorrSignHashedMessage(hashedMsg gFp5.Element, sk curve.ECgFp5Scalar) Sig } func SchnorrSignHashedMessage2(hashedMsg gFp5.Element, sk, k curve.ECgFp5Scalar) Signature { - r := curve.GENERATOR_ECgFp5Point.Mul(k).Encode() + r := curve.MulGenerator(k).Encode() // Compute `e = H(r || H(m))`, which is a scalar point preImage := make([]g.GoldilocksField, 5+5) copy(preImage[:5], r[:]) @@ -227,7 +227,7 @@ func IsSchnorrSignatureValid(pubKey, hashedMsg gFp5.Element, sig Signature) bool return false } - rV := curve.MulAdd2(curve.GENERATOR_WEIERSTRASS, pubKeyWs, sig.S, sig.E).Encode() // r_v = s*G + e*pk + rV := curve.MulAdd2WithGenJacobian(pubKeyWs, sig.S, sig.E).Encode() // r_v = s*G + e*pk (Jacobian-optimized) preImage := make([]g.GoldilocksField, 5+5) copy(preImage[:5], rV[:])