mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
The proof argument carries the p3q header, and this precompile never looked at it -- only the "P3Q1" tag and the length framing. Everything about how hard the verification would be, log_blowup and num_queries, arrived off calldata chosen by whoever submitted the proof, and at the weakest settings the wire admits that is about one bit. admit() is now the one question both entry points ask: the envelope tag, a frame long enough to state parameters at all, and log_blowup * num_queries >= MinSoundnessBits. The verifier enforces the same floor, but a consensus rule belongs where the chain can be read, not only inside a linked staticlib -- and refusing here means the node crosses no FFI boundary for a proof it was never going to accept. The gas charge is unchanged; it is a function of calldata length alone, so the saving is the node's work, not the caller's fee. Under the starkfri_p3q tag a test reads p3q_min_soundness_bits() out of the linked library, so the two copies cannot drift. The fixtures move with it. "P3Q1" followed by arbitrary bytes was standing in for a well-formed proof, but those bytes ARE the parameters, so whether such a fixture cleared the floor was an accident -- and under crypto/rand, not the same accident twice. sfProof builds a frame that states what it means to state.
112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
// Copyright (C) 2025-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package starkfri
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// buildBenchInput re-implements the package-local buildInput helper for
|
|
// bench scope (cannot share private test helpers across files cleanly).
|
|
// Wire: [1B version][4B proof_len][N proof][4B pub_len][M pub].
|
|
func buildBenchInput(version byte, proof, pub []byte) []byte {
|
|
out := make([]byte, 0, 1+4+len(proof)+4+len(pub))
|
|
out = append(out, version)
|
|
var pl [4]byte
|
|
binary.BigEndian.PutUint32(pl[:], uint32(len(proof)))
|
|
out = append(out, pl[:]...)
|
|
out = append(out, proof...)
|
|
var ul [4]byte
|
|
binary.BigEndian.PutUint32(ul[:], uint32(len(pub)))
|
|
out = append(out, ul[:]...)
|
|
out = append(out, pub...)
|
|
return out
|
|
}
|
|
|
|
// BenchmarkStarkFRI_Verify_AcceptAll measures the full precompile path
|
|
// (gas check + wire parse + MagicHeader check + verifier dispatch) with
|
|
// an accept-all callback registered. Proof body is a representative 2KiB
|
|
// payload — typical STARK-FRI proof footprint for Goldilocks/Plonky3.
|
|
func BenchmarkStarkFRI_Verify_AcceptAll(b *testing.B) {
|
|
RegisterVerifier(func(byte, []byte, []byte) (bool, error) { return true, nil })
|
|
defer RegisterVerifier(nil)
|
|
|
|
body := make([]byte, 2048-MinProofLength)
|
|
for i := range body {
|
|
body[i] = byte(i)
|
|
}
|
|
proof := sfProof(body)
|
|
pub := make([]byte, 128)
|
|
input := buildBenchInput(VersionV1, proof, pub)
|
|
gas := StarkFRIVerifyPrecompile.RequiredGas(input) * 2
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, err := StarkFRIVerifyPrecompile.Run(nil, common.Address{}, ContractStarkFRIVerifyAddress, input, gas, true)
|
|
if err != nil {
|
|
b.Fatalf("Run: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkStarkFRI_Verify_RejectMagic measures the structural-only
|
|
// path: MagicHeader missing, verifier never invoked. Tests how cheap
|
|
// the early-reject is when input fails the magic check.
|
|
func BenchmarkStarkFRI_Verify_RejectMagic(b *testing.B) {
|
|
RegisterVerifier(func(byte, []byte, []byte) (bool, error) { return true, nil })
|
|
defer RegisterVerifier(nil)
|
|
|
|
body := make([]byte, 2048)
|
|
for i := range body {
|
|
body[i] = byte(i)
|
|
}
|
|
// Wrong magic header — first 4 bytes are not "P3Q1".
|
|
proof := body
|
|
pub := make([]byte, 128)
|
|
input := buildBenchInput(VersionV1, proof, pub)
|
|
gas := StarkFRIVerifyPrecompile.RequiredGas(input) * 2
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = StarkFRIVerifyPrecompile.Run(nil, common.Address{}, ContractStarkFRIVerifyAddress, input, gas, true)
|
|
}
|
|
}
|
|
|
|
// BenchmarkStarkFRI_Verify_Standalone measures the in-process Verify
|
|
// entry-point (off-chain pre-flight) at the same 2 KiB proof size.
|
|
func BenchmarkStarkFRI_Verify_Standalone(b *testing.B) {
|
|
RegisterVerifier(func(byte, []byte, []byte) (bool, error) { return true, nil })
|
|
defer RegisterVerifier(nil)
|
|
|
|
body := make([]byte, 2048-MinProofLength)
|
|
for i := range body {
|
|
body[i] = byte(i)
|
|
}
|
|
proof := sfProof(body)
|
|
pub := make([]byte, 128)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := Verify(proof, pub)
|
|
if err != nil {
|
|
b.Fatalf("Verify: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkStarkFRI_RequiredGas isolates the gas-only path.
|
|
func BenchmarkStarkFRI_RequiredGas(b *testing.B) {
|
|
input := make([]byte, 2048)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = StarkFRIVerifyPrecompile.RequiredGas(input)
|
|
}
|
|
}
|