mirror of
https://github.com/luxfi/crypto.git
synced 2026-10-30 16:59:13 +00:00
The Banderwagon prime-order group used by Verkle / IPA had been vendored internally under lux/crypto/ipa/banderwagon. Extract it to lux/crypto/banderwagon as the canonical Lux public surface; rewrite all internal ipa callers (prover, verifier, config, multiproof, common, transcript, test_helper, tests) to import from the canonical home. - Provenance comment on every file: github.com/crate-crypto/go-ipa (Apache-2.0 / MIT dual). Adds LICENSE-GO-IPA-APACHE2 and LICENSE-GO-IPA-MIT alongside the package. - Element / Generator / Identity / Fr / MSMPrecomp / PrecompPoint / CompressedSize / UncompressedSize / SetBytes / SetBytesUncompressed / ElementsToBytes / BatchToBytesUncompressed / BatchNormalize / MapToScalarField / BatchMapToScalarField / Add / Sub / Double / Neg / ScalarMul / MultiExp now have one and only one home: lux/crypto/banderwagon. - Verkle (parallel branch) will switch its banderwagon import to this canonical surface. - KAT vectors from the upstream go-ipa Verkle reference suite preserved (TestEncodingFixedVectors, TestPointAtInfinityComponent) plus all precomp MSM-vs-gnark random-round tests (1000 * NumCPU rounds). Tests: go test ./banderwagon/... ./ipa/... — all PASS (banderwagon 27s, ipa 16s, ipa/bandersnatch 24s, fp/fr 4s+2s, common 3s).
145 lines
3.9 KiB
Go
145 lines
3.9 KiB
Go
// Copyright (C) 2026 Lux Industries Inc. All rights reserved.
|
|
// Copyright (C) Crate Crypto contributors.
|
|
// Licensed under Apache-2.0 OR MIT (see LICENSE-GO-IPA-APACHE2, LICENSE-GO-IPA-MIT).
|
|
//
|
|
// Provenance: ported from github.com/crate-crypto/go-ipa/banderwagon.
|
|
|
|
package banderwagon
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto/ipa/bandersnatch"
|
|
"github.com/luxfi/crypto/ipa/bandersnatch/fp"
|
|
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
|
|
)
|
|
|
|
func TestPrecompCorrectness(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Generate a 256-basis. It returns the same points as Banderwagon points and affine points.
|
|
// This is only necessary since our APIs and gnark APIs receive different representations of the same
|
|
// points.
|
|
pointsWagon, pointsAffine := generateRandomPoints(256)
|
|
|
|
msmEngine, err := NewPrecompMSM(pointsWagon)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// We split it in NumCPU() rounds to parallelize the test, each checking 1000 random MSM.
|
|
for round := 0; round < runtime.NumCPU(); round++ {
|
|
t.Run(fmt.Sprintf("round %d", round), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for i := 0; i < 1_000; i++ {
|
|
// Generate random scalars.
|
|
scalars := make([]fr.Element, len(pointsWagon))
|
|
for i := 0; i < len(scalars); i++ {
|
|
if _, err := scalars[i].SetRandom(); err != nil {
|
|
t.Fatalf("error generating random scalar: %v", err)
|
|
}
|
|
}
|
|
|
|
// Calculate the MSM with our precomputed tables.
|
|
precompResult := msmEngine.MSM(scalars)
|
|
|
|
// Calculate the same MSM with gnark.
|
|
var gnarkResult bandersnatch.PointProj
|
|
if _, err := bandersnatch.MultiExp(&gnarkResult, pointsAffine, scalars, bandersnatch.MultiExpConfig{ScalarsMont: true}); err != nil {
|
|
t.Fatalf("error in gnark multiexp: %v", err)
|
|
}
|
|
|
|
// Test that both results are equal.
|
|
if !precompResult.inner.Equal(&gnarkResult) {
|
|
t.Fatalf("msm result does not match gnark result (%s)", scalars[0].String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkPrecompMSM(b *testing.B) {
|
|
msmLength := []int{1, 2, 4, 8, 16, 32, 64, 128, 256}
|
|
|
|
pointsWagon, _ := generateRandomPoints(256)
|
|
msmEngine, err := NewPrecompMSM(pointsWagon)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
for _, k := range msmLength {
|
|
b.Run(fmt.Sprintf("msm_length=%d", k), func(b *testing.B) {
|
|
// Generate random scalars.
|
|
scalars := make([]fr.Element, 256)
|
|
for i := 0; i < k; i++ {
|
|
if _, err := scalars[i].SetRandom(); err != nil {
|
|
b.Fatalf("error generating random scalar: %v", err)
|
|
}
|
|
}
|
|
|
|
b.Run("precomp", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = msmEngine.MSM(scalars)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkPrecompInitialize(b *testing.B) {
|
|
points, _ := generateRandomPoints(256)
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = NewPrecompMSM(points)
|
|
}
|
|
}
|
|
|
|
// generateRandomPoints is a similar version of the one that exist in the ipa package
|
|
// but we're pulling it here for tests to avoid an import cycle and output point format convenience.
|
|
func generateRandomPoints(numPoints uint64) ([]Element, []bandersnatch.PointAffine) {
|
|
seed := "eth_verkle_oct_2021"
|
|
|
|
pointsWagon := []Element{}
|
|
pointsAffine := []bandersnatch.PointAffine{}
|
|
var increment uint64 = 0
|
|
for uint64(len(pointsWagon)) != numPoints {
|
|
digest := sha256.New()
|
|
digest.Write([]byte(seed))
|
|
|
|
b := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(b, increment)
|
|
digest.Write(b)
|
|
|
|
hash := digest.Sum(nil)
|
|
|
|
var x fp.Element
|
|
x.SetBytes(hash)
|
|
|
|
increment++
|
|
|
|
x_as_bytes := x.Bytes()
|
|
var point_found Element
|
|
err := point_found.SetBytes(x_as_bytes[:])
|
|
if err != nil {
|
|
// This point is not in the correct subgroup or on the curve
|
|
continue
|
|
}
|
|
pointsWagon = append(pointsWagon, point_found)
|
|
var pointAffine bandersnatch.PointAffine
|
|
pointAffine.FromProj(&point_found.inner)
|
|
pointsAffine = append(pointsAffine, pointAffine)
|
|
|
|
}
|
|
|
|
return pointsWagon, pointsAffine
|
|
}
|