mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
e2e: point the suite at this repo, and make its three broken scenarios real
e2e/go.mod required github.com/luxfi/precompile v0.5.27 with no replace, so the
suite compiled against a published artifact 33 patch releases behind the tree it
lives in. e2e/clob already had `replace => ../..`; the parent module never did.
The consequence was not that the tests were weak, it was that they could not
observe this repo at all — seven of them had been failing against real code and
nothing could report it. Adding the replace also removes the checksum failure
they hit on the way in: the v0.5.27 tag was force-moved after publication, so
github now serves bytes that do not match what sum.golang.org attests, and the
suite could not resolve its own parent.
With the replace in place, the seven pre-existing failures are visible, and all
seven were the tests being wrong:
StableSwap x4 — the calldata was written by hand at each of four call sites
with a one-byte token count and the amplification factor last. getD and getDy
read a four-byte count and take the amplification before the balances. The
layout now lives in encodeGetD/encodeGetDy and nowhere else, which is what
stops it drifting again.
CGGMP21 — threshold ECDSA on secp256k1, handed a P-256 key. The precompile
refused it correctly. Now generates a real secp256k1 key and signature.
FROST — verifies secp256k1 x-only Schnorr via curve.Secp256k1{}.LiftX; the
test built an edwards25519 key, on the strength of a comment claiming FROST
keys are Ed25519-compatible, which is not true of this implementation.
Ed25519 — took 32 bytes from rand.Read and required them to be a canonical
scalar, which holds about one time in sixteen, then hand-rolled a SHA-256
Schnorr with s = k - e*x. RFC 8032 uses SHA-512 and s = r + e*a, and the
precompile calls ed25519.Verify, so it could not have verified even with a
valid scalar. Now signs with crypto/ed25519.
Keys come from a seeded source so a failure reproduces. Added the negative
cases these lacked entirely: signature bound to its message, every bit-flip
refused, all-zero key refused, truncated and over-long frames refused, and
gas charged on refusal. FROST keeps only its refusal cases here — the positive
path needs the threshold library's domain-separated challenge, which belongs in
frost/real_sig_test.go rather than in a second copy.
Also removes cmd/safekatdump and cmd/safekatverify. safekatdump's doc comment
claimed it "generates real known-answer test (KAT) vectors"; it generates a
fresh key and a fresh signature from rand.Reader on every run and safekatverify
then checks that our verifier accepts what our signer just produced. That
passes unchanged if the implementation is uniformly wrong on both sides, which
is the failure a KAT exists to catch. Nothing consumed the output: no Makefile
target, no CI step, no checked-in fixture, and no reference to either program
anywhere outside their own doc comments. Genuine frozen vectors now live in the
packages, under go test, where they run.
e2e/clob is left alone: it replaces github.com/luxfi/dex with a sibling working
checkout, so it currently fails on an undefined symbol from that tree's
in-progress state rather than anything here.
This commit is contained in:
@@ -1,307 +0,0 @@
|
||||
// safekatdump generates real known-answer test (KAT) vectors and the
|
||||
// EXACT precompile input bytes for the Corona (0x012206), Pulsar (0x012204),
|
||||
// Magnetar (0x012207) and CGGMP21 (0x0800..0003) verify precompiles, so the
|
||||
// safe-{corona,pulsar,magnetar,cggmp21} Foundry tests can assert that their
|
||||
// Solidity calldata framing matches the on-chain precompile wire format
|
||||
// byte-for-byte.
|
||||
//
|
||||
// Output is JSON to stdout: one object per scheme with the precompile
|
||||
// address, the verify context, the component fields, and the full framed
|
||||
// `input` blob (== what the precompile's Run() parses).
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
csign "github.com/luxfi/corona/sign"
|
||||
cthr "github.com/luxfi/corona/threshold"
|
||||
"github.com/luxfi/crypto"
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
)
|
||||
|
||||
type katOut struct {
|
||||
Scheme string `json:"scheme"`
|
||||
Address string `json:"address"`
|
||||
Context string `json:"context"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Threshold uint32 `json:"threshold,omitempty"`
|
||||
Parties uint32 `json:"parties,omitempty"`
|
||||
PubKeyHex string `json:"pubkey,omitempty"`
|
||||
SigHex string `json:"sig"`
|
||||
MsgHex string `json:"msg,omitempty"`
|
||||
MsgHash string `json:"msgHash,omitempty"`
|
||||
InputHex string `json:"input"`
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Pulsar (0x012204): byte-equal to single-party ML-DSA-65 -------------
|
||||
// Wire: mode(1) || pk || msgLen:uint256(32) || msg || sig
|
||||
func pulsarKAT() katOut {
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
must(err)
|
||||
ctx := []byte("lux-evm-precompile-pulsar-v1")
|
||||
// The Safe message is always the 32-byte Safe-tx hash.
|
||||
msg := sha256.Sum256([]byte("safe-pulsar KAT message"))
|
||||
sigBytes, err := priv.SignCtx(rand.Reader, msg[:], ctx)
|
||||
must(err)
|
||||
pub := priv.PublicKey.Bytes()
|
||||
|
||||
var input bytes.Buffer
|
||||
input.WriteByte(0x65) // ML-DSA-65 mode
|
||||
input.Write(pub)
|
||||
var ml [32]byte
|
||||
binary.BigEndian.PutUint64(ml[24:], uint64(len(msg)))
|
||||
input.Write(ml[:])
|
||||
input.Write(msg[:])
|
||||
input.Write(sigBytes)
|
||||
|
||||
return katOut{
|
||||
Scheme: "pulsar",
|
||||
Address: "0x0000000000000000000000000000000000012204",
|
||||
Context: string(ctx),
|
||||
Mode: "0x65",
|
||||
PubKeyHex: hex.EncodeToString(pub),
|
||||
SigHex: hex.EncodeToString(sigBytes),
|
||||
MsgHash: hex.EncodeToString(msg[:]),
|
||||
InputHex: hex.EncodeToString(input.Bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Magnetar (0x012207): byte-equal to single-party SLH-DSA-SHA2-128s ----
|
||||
// Wire: mode(1) || pkLen:uint16 || pk || msgLen:uint16 || msg || sig
|
||||
func magnetarKAT() katOut {
|
||||
priv, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
||||
must(err)
|
||||
ctx := []byte("lux-evm-precompile-magnetar-v1")
|
||||
msg := sha256.Sum256([]byte("safe-magnetar KAT message"))
|
||||
sigBytes, err := priv.SignCtx(rand.Reader, msg[:], ctx)
|
||||
must(err)
|
||||
pub := priv.PublicKey.Bytes()
|
||||
|
||||
var input bytes.Buffer
|
||||
input.WriteByte(0x00) // SHA2-128s mode
|
||||
var pl [2]byte
|
||||
binary.BigEndian.PutUint16(pl[:], uint16(len(pub)))
|
||||
input.Write(pl[:])
|
||||
input.Write(pub)
|
||||
var mlh [2]byte
|
||||
binary.BigEndian.PutUint16(mlh[:], uint16(len(msg)))
|
||||
input.Write(mlh[:])
|
||||
input.Write(msg[:])
|
||||
input.Write(sigBytes)
|
||||
|
||||
return katOut{
|
||||
Scheme: "magnetar",
|
||||
Address: "0x0000000000000000000000000000000000012207",
|
||||
Context: string(ctx),
|
||||
Mode: "0x00",
|
||||
PubKeyHex: hex.EncodeToString(pub),
|
||||
SigHex: hex.EncodeToString(sigBytes),
|
||||
MsgHash: hex.EncodeToString(msg[:]),
|
||||
InputHex: hex.EncodeToString(input.Bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Corona (0x012206): real 2-round LWE threshold signature -------------
|
||||
// Wire: t:uint32 || n:uint32 || msgHash:32 || serializedSig
|
||||
// serializedSig (per precompile contract.go deserializeSignature):
|
||||
//
|
||||
// c || z[N] || Delta[M] || A[M][N] || bTilde[M], each poly = N()*8 bytes BE.
|
||||
func coronaKAT(t, n uint32) katOut {
|
||||
shares, groupKey, err := cthr.GenerateKeysTrustedDealer(int(t), int(n), rand.Reader)
|
||||
must(err)
|
||||
|
||||
prfKey := make([]byte, csign.KeySize)
|
||||
_, _ = rand.Read(prfKey)
|
||||
|
||||
signers := make([]int, n)
|
||||
for i := range signers {
|
||||
signers[i] = i
|
||||
}
|
||||
sessionID := 1
|
||||
|
||||
tsigners := make([]*cthr.Signer, n)
|
||||
for i, sh := range shares {
|
||||
tsigners[i] = cthr.NewSigner(sh)
|
||||
}
|
||||
|
||||
// 32-byte message hash exactly as the precompile receives it.
|
||||
var msgHash [32]byte
|
||||
copy(msgHash[:], []byte("corona-kat-msg"))
|
||||
signMessage := fmt.Sprintf("lux-evm-precompile-corona-v1|%x", msgHash[:])
|
||||
|
||||
round1 := make(map[int]*cthr.Round1Data)
|
||||
for i, s := range tsigners {
|
||||
r1, err := s.Round1(sessionID, prfKey, signers)
|
||||
must(err)
|
||||
round1[i] = r1
|
||||
}
|
||||
round2 := make(map[int]*cthr.Round2Data)
|
||||
for i, s := range tsigners {
|
||||
r2, err := s.Round2(sessionID, signMessage, prfKey, signers, round1)
|
||||
must(err)
|
||||
round2[i] = r2
|
||||
}
|
||||
sig, err := tsigners[0].Finalize(round2)
|
||||
must(err)
|
||||
|
||||
if !cthr.Verify(groupKey, signMessage, sig) {
|
||||
must(fmt.Errorf("corona sanity verify failed pre-serialization"))
|
||||
}
|
||||
|
||||
sigBytes, err := coronaSerialize(groupKey.Params, sig, groupKey)
|
||||
must(err)
|
||||
|
||||
var input bytes.Buffer
|
||||
var tn [8]byte
|
||||
binary.BigEndian.PutUint32(tn[0:4], t)
|
||||
binary.BigEndian.PutUint32(tn[4:8], n)
|
||||
input.Write(tn[:])
|
||||
input.Write(msgHash[:])
|
||||
input.Write(sigBytes)
|
||||
|
||||
return katOut{
|
||||
Scheme: "corona",
|
||||
Address: "0x0000000000000000000000000000000000012206",
|
||||
Context: "lux-evm-precompile-corona-v1",
|
||||
Threshold: t,
|
||||
Parties: n,
|
||||
SigHex: hex.EncodeToString(sigBytes),
|
||||
MsgHash: hex.EncodeToString(msgHash[:]),
|
||||
InputHex: hex.EncodeToString(input.Bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
// coronaSerialize replicates precompile/corona/contract_test.go serializeSignature
|
||||
// EXACTLY (8-byte big-endian coefficients, component order
|
||||
// c || z[N] || Delta[M] || A[M][N] || bTilde[M]).
|
||||
func coronaSerialize(params *cthr.Params, sig *cthr.Signature, groupKey *cthr.GroupKey) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
r := params.R
|
||||
rXi := params.RXi
|
||||
rNu := params.RNu
|
||||
|
||||
cCopy := *sig.C.CopyNew()
|
||||
r.IMForm(cCopy, cCopy)
|
||||
r.INTT(cCopy, cCopy)
|
||||
if err := serializePoly(&buf, r, cCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < csign.N; i++ {
|
||||
zCopy := *sig.Z[i].CopyNew()
|
||||
r.IMForm(zCopy, zCopy)
|
||||
r.INTT(zCopy, zCopy)
|
||||
if err := serializePoly(&buf, r, zCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i := 0; i < csign.M; i++ {
|
||||
if err := serializePoly(&buf, rNu, sig.Delta[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i := 0; i < csign.M; i++ {
|
||||
for j := 0; j < csign.N; j++ {
|
||||
aCopy := *groupKey.A[i][j].CopyNew()
|
||||
r.IMForm(aCopy, aCopy)
|
||||
r.INTT(aCopy, aCopy)
|
||||
if err := serializePoly(&buf, r, aCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < csign.M; i++ {
|
||||
if err := serializePoly(&buf, rXi, groupKey.BTilde[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func serializePoly(buf *bytes.Buffer, r *ring.Ring, poly ring.Poly) error {
|
||||
coeffs := make([]*big.Int, r.N())
|
||||
r.PolyToBigint(poly, 1, coeffs)
|
||||
for _, coeff := range coeffs {
|
||||
cb := make([]byte, 8)
|
||||
coeff.FillBytes(cb)
|
||||
if _, err := buf.Write(cb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- CGGMP21 (0x0800..0003): threshold ECDSA, byte-equal to single-party --
|
||||
// secp256k1 ECDSA. The precompile parses a standard ECDSA signature and
|
||||
// asserts the recovered pubkey equals the supplied aggregated pubkey, so a
|
||||
// real KAT is just a genuine secp256k1 signature whose key is the group key.
|
||||
// Wire: t:uint32(4) || n:uint32(4) || pubkey(65: 0x04||x||y) || msgHash(32) ||
|
||||
//
|
||||
// sig(65: r||s||v).
|
||||
func cggmp21KAT(t, n uint32) katOut {
|
||||
priv, err := crypto.GenerateKey()
|
||||
must(err)
|
||||
|
||||
// 32-byte message hash exactly as the precompile receives it.
|
||||
msg := sha256.Sum256([]byte("safe-cggmp21 KAT message"))
|
||||
|
||||
// crypto.Sign produces [R || S || V] with V in {0,1}, the exact 65-byte
|
||||
// layout the precompile parses at input[105:170].
|
||||
sigBytes, err := crypto.Sign(msg[:], priv)
|
||||
must(err)
|
||||
|
||||
// Uncompressed group public key: 0x04 || x || y (65 bytes).
|
||||
pub := crypto.FromECDSAPub(&priv.PublicKey)
|
||||
if len(pub) != 65 {
|
||||
must(fmt.Errorf("cggmp21: unexpected pubkey length %d", len(pub)))
|
||||
}
|
||||
|
||||
var input bytes.Buffer
|
||||
var tn [8]byte
|
||||
binary.BigEndian.PutUint32(tn[0:4], t)
|
||||
binary.BigEndian.PutUint32(tn[4:8], n)
|
||||
input.Write(tn[:])
|
||||
input.Write(pub)
|
||||
input.Write(msg[:])
|
||||
input.Write(sigBytes)
|
||||
|
||||
return katOut{
|
||||
Scheme: "cggmp21",
|
||||
Address: "0x0800000000000000000000000000000000000003",
|
||||
Context: "",
|
||||
Threshold: t,
|
||||
Parties: n,
|
||||
PubKeyHex: hex.EncodeToString(pub),
|
||||
SigHex: hex.EncodeToString(sigBytes),
|
||||
MsgHash: hex.EncodeToString(msg[:]),
|
||||
InputHex: hex.EncodeToString(input.Bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
out := []katOut{
|
||||
pulsarKAT(),
|
||||
magnetarKAT(),
|
||||
coronaKAT(2, 3),
|
||||
cggmp21KAT(2, 3),
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
must(enc.Encode(out))
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// safekatverify feeds the KAT input blobs produced by safekatdump into the
|
||||
// REAL precompile Run() implementations and asserts each returns the success
|
||||
// word bytes32(1). This is the ground-truth check that the framed bytes the
|
||||
// Solidity tests will assert against actually verify on-chain.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
|
||||
cggmp21 "github.com/luxfi/precompile/cggmp21"
|
||||
corona "github.com/luxfi/precompile/corona"
|
||||
magnetar "github.com/luxfi/precompile/magnetar"
|
||||
pulsar "github.com/luxfi/precompile/pulsar"
|
||||
)
|
||||
|
||||
type katOut struct {
|
||||
Scheme string `json:"scheme"`
|
||||
InputHex string `json:"input"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var kats []katOut
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&kats); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "decode:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fail := false
|
||||
for _, k := range kats {
|
||||
input, err := hex.DecodeString(k.InputHex)
|
||||
if err != nil {
|
||||
fmt.Println(k.Scheme, "hex decode FAIL", err)
|
||||
fail = true
|
||||
continue
|
||||
}
|
||||
switch k.Scheme {
|
||||
case "pulsar":
|
||||
// pulsar Run returns (nil, gas, nil) on success, typed error on fail.
|
||||
_, _, err := pulsar.PulsarVerifyPrecompile.Run(nil, common.Address{}, pulsar.ContractPulsarVerifyAddress, input, 50_000_000, true)
|
||||
report("pulsar", err == nil, err, &fail)
|
||||
case "magnetar":
|
||||
out, _, err := magnetar.MagnetarVerifyPrecompile.Run(nil, common.Address{}, magnetar.ContractMagnetarVerifyAddress, input, 50_000_000, true)
|
||||
ok := err == nil && len(out) == 32 && out[31] == 1
|
||||
report("magnetar", ok, err, &fail)
|
||||
case "corona":
|
||||
out, _, err := corona.CoronaThresholdPrecompile.Run(nil, common.Address{}, corona.ContractCoronaThresholdAddress, input, 50_000_000, true)
|
||||
ok := err == nil && len(out) == 32 && out[31] == 1
|
||||
report("corona", ok, err, &fail)
|
||||
case "cggmp21":
|
||||
out, _, err := cggmp21.CGGMP21VerifyPrecompile.Run(nil, common.Address{}, cggmp21.ContractCGGMP21VerifyAddress, input, 50_000_000, true)
|
||||
ok := err == nil && len(out) == 32 && out[31] == 1
|
||||
report("cggmp21", ok, err, &fail)
|
||||
default:
|
||||
fmt.Println("unknown scheme", k.Scheme)
|
||||
fail = true
|
||||
}
|
||||
}
|
||||
if fail {
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("ALL KAT INPUTS VERIFY AGAINST REAL PRECOMPILES")
|
||||
}
|
||||
|
||||
func report(name string, ok bool, err error, fail *bool) {
|
||||
if ok {
|
||||
fmt.Printf("%-10s VERIFY=1 (real precompile accepted KAT input)\n", name)
|
||||
} else {
|
||||
fmt.Printf("%-10s VERIFY=FAIL err=%v\n", name, err)
|
||||
*fail = true
|
||||
}
|
||||
}
|
||||
+24
-17
@@ -6,9 +6,9 @@ require (
|
||||
filippo.io/edwards25519 v1.2.0
|
||||
github.com/consensys/gnark-crypto v0.20.1
|
||||
github.com/holiman/uint256 v1.3.2
|
||||
github.com/luxfi/crypto v1.19.17
|
||||
github.com/luxfi/geth v1.16.98
|
||||
github.com/luxfi/precompile v0.5.27
|
||||
github.com/luxfi/crypto v1.19.26
|
||||
github.com/luxfi/geth v1.17.12
|
||||
github.com/luxfi/precompile v0.5.44
|
||||
github.com/stretchr/testify v1.11.1
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ require (
|
||||
github.com/ALTree/bigfloat v0.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
|
||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||
@@ -25,57 +26,63 @@ require (
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/rpc v1.2.1 // indirect
|
||||
github.com/grandcat/zeroconf v1.0.0 // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/luxfi/accel v1.1.9 // indirect
|
||||
github.com/luxfi/ai v0.1.0 // indirect
|
||||
github.com/luxfi/accel v1.2.4 // indirect
|
||||
github.com/luxfi/atomic v1.0.0 // indirect
|
||||
github.com/luxfi/cache v1.2.1 // indirect
|
||||
github.com/luxfi/codec v1.1.4 // indirect
|
||||
github.com/luxfi/compress v0.0.5 // indirect
|
||||
github.com/luxfi/concurrent v0.0.3 // indirect
|
||||
github.com/luxfi/consensus v1.25.0 // indirect
|
||||
github.com/luxfi/constants v1.5.8-0.20260603055356-93c2c2ceb9ca // indirect
|
||||
github.com/luxfi/consensus v1.25.29 // indirect
|
||||
github.com/luxfi/constants v1.5.8 // indirect
|
||||
github.com/luxfi/container v0.0.4 // indirect
|
||||
github.com/luxfi/crypto/ipa v1.2.4 // indirect
|
||||
github.com/luxfi/database v1.18.3 // indirect
|
||||
github.com/luxfi/database v1.20.4 // indirect
|
||||
github.com/luxfi/fhe v1.8.2 // indirect
|
||||
github.com/luxfi/ids v1.2.10 // indirect
|
||||
github.com/luxfi/ids v1.2.15 // indirect
|
||||
github.com/luxfi/lattice/v7 v7.1.4 // indirect
|
||||
github.com/luxfi/log v1.4.1 // indirect
|
||||
github.com/luxfi/log v1.4.3 // indirect
|
||||
github.com/luxfi/math v1.4.1 // indirect
|
||||
github.com/luxfi/math/big v0.1.0 // indirect
|
||||
github.com/luxfi/metric v1.5.7 // indirect
|
||||
github.com/luxfi/mdns v0.1.1 // indirect
|
||||
github.com/luxfi/metric v1.5.9 // indirect
|
||||
github.com/luxfi/mock v0.1.1 // indirect
|
||||
github.com/luxfi/p2p v1.21.1 // indirect
|
||||
github.com/luxfi/pq v1.0.3 // indirect
|
||||
github.com/luxfi/runtime v1.1.0 // indirect
|
||||
github.com/luxfi/runtime v1.1.3 // indirect
|
||||
github.com/luxfi/sampler v1.1.0 // indirect
|
||||
github.com/luxfi/threshold v1.12.0 // indirect
|
||||
github.com/luxfi/utils v1.1.5 // indirect
|
||||
github.com/luxfi/utils v1.2.0 // indirect
|
||||
github.com/luxfi/validators v1.2.0 // indirect
|
||||
github.com/luxfi/version v1.0.1 // indirect
|
||||
github.com/luxfi/vm v1.2.0 // indirect
|
||||
github.com/luxfi/warp v1.18.6 // indirect
|
||||
github.com/luxfi/vm v1.2.5 // indirect
|
||||
github.com/luxfi/warp v1.23.0 // indirect
|
||||
github.com/luxfi/zap v0.8.11 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
|
||||
github.com/mr-tron/base58 v1.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/supranational/blst v0.3.16 // indirect
|
||||
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
gonum.org/v1/gonum v0.17.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/luxfi/precompile => ..
|
||||
|
||||
+82
-35
@@ -6,6 +6,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/consensys/gnark-crypto v0.20.1 h1:PXDUBvk8AzhvWowHLWBEAfUQcV1/aZgWIqD6eMpXmDg=
|
||||
@@ -40,6 +42,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
|
||||
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
|
||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
||||
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
||||
github.com/gtank/ristretto255 v0.2.0 h1:LeOuWr6giplWkkMizx2emfG03SRPJqKt1nfIHLVHQ/0=
|
||||
@@ -47,6 +51,7 @@ github.com/gtank/ristretto255 v0.2.0/go.mod h1:OJ1ox/dWcp7sJ5grYDcZ+kkHYuj5nelW5
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -57,87 +62,102 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/accel v1.1.9 h1:Tsk6gXj2uKE19501bD0ajRYdeCHIlTGb6jYyLc+F8hc=
|
||||
github.com/luxfi/accel v1.1.9/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
|
||||
github.com/luxfi/ai v0.1.0 h1:PwTGob0GJivbdqNpUs82bvwcE8/qxyTokxfY7BZVPoo=
|
||||
github.com/luxfi/ai v0.1.0/go.mod h1:lTuY32dGjEJUgVheYuanlt1O0jHj0AZ09sUSpRsmH6M=
|
||||
github.com/luxfi/accel v1.2.4 h1:5VbIHyEvvfobn2zBiTFODxDw1CeqxCepZOLlvkuf9yQ=
|
||||
github.com/luxfi/accel v1.2.4/go.mod h1:ISIwAX+ZfsL/S5nsP2JvfldXN6Nc+QzoWf6Jtaq+xsQ=
|
||||
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
|
||||
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
|
||||
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
|
||||
github.com/luxfi/cache v1.2.1/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
|
||||
github.com/luxfi/codec v1.1.4 h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
|
||||
github.com/luxfi/codec v1.1.4/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
|
||||
github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
|
||||
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
|
||||
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
|
||||
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
|
||||
github.com/luxfi/consensus v1.25.0 h1:ak82ufqeklLJGQMCizxx5agiwLkjbrnrk7kFezvEtZo=
|
||||
github.com/luxfi/consensus v1.25.0/go.mod h1:jkKzKyIgg/JqaEumxZOJd9ofWM3pXnVVLlQp+3qo9SM=
|
||||
github.com/luxfi/constants v1.5.8-0.20260603055356-93c2c2ceb9ca h1:N2lM2gMJ2Hk5yZ6GqURWtg6nIVtodsjqMe+MGNiuCNw=
|
||||
github.com/luxfi/constants v1.5.8-0.20260603055356-93c2c2ceb9ca/go.mod h1:z+Wc7skybZAA+xuBWNcmtv402S/BFqixL+FiSQXPg8U=
|
||||
github.com/luxfi/consensus v1.25.29 h1:z9UWFnuBY7XtTJ6klA7u8UV8ZV5JjHqh+jfKqcRcUPU=
|
||||
github.com/luxfi/consensus v1.25.29/go.mod h1:cerfisfzmUJv8gbMcjcQUSdxlLpfn/+LdLY+4D95Nw0=
|
||||
github.com/luxfi/constants v1.5.8 h1:iNP9AWNUcM4Tps7jYnx49CwtCWAC9mYRxJfGou2za0g=
|
||||
github.com/luxfi/constants v1.5.8/go.mod h1:Pu5jWHdnUtQRbWC43yTUjU/pbIIKMDOd2a2yroSfo48=
|
||||
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
|
||||
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
|
||||
github.com/luxfi/corona v0.7.6 h1:CJP6smygD55dL0HHkKkWryL9H24a+wXvs+L+WchK7Nc=
|
||||
github.com/luxfi/corona v0.7.6/go.mod h1:4aD7+ZqnlZ2aVuU/DBQ5aspIagv5ux45LW2sJ4+siY8=
|
||||
github.com/luxfi/crypto v1.19.17 h1:l2LLu7UFyICtJVfraLDLRi+lFGiDXKHSL18M9/m1gsQ=
|
||||
github.com/luxfi/crypto v1.19.17/go.mod h1:INjdZtke85k8hX/QAmTMAY8bbZ4gzGZQLqURg3xf6Gk=
|
||||
github.com/luxfi/corona v0.10.3 h1:Yi1oAkW0HEsf5fvst/tUN0AjRVg6DoNHB/IC0qrFWZE=
|
||||
github.com/luxfi/corona v0.10.3/go.mod h1:xe5qRir0p+FA6eETpyGDv4LjYySg1zVB13kmHpy9x94=
|
||||
github.com/luxfi/crypto v1.19.26 h1:+aHn/L479ak2ih7s/DkBZojjuhcyHBLqu3nYT81vcrU=
|
||||
github.com/luxfi/crypto v1.19.26/go.mod h1:0DCU62kX8+zhYU2qeM07A4pifJyPkPujnUOfgc8TOFQ=
|
||||
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
|
||||
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
|
||||
github.com/luxfi/database v1.18.3 h1:gg+xwhKUxXa7fDoOD8IS91E71QqoEtcDCl2nfS61Jgg=
|
||||
github.com/luxfi/database v1.18.3/go.mod h1:sv0pYCGKlK1aNJTICxFUDpVWCJTigoLlshHmV/1pg7c=
|
||||
github.com/luxfi/database v1.20.4 h1:WOt2GIGJxf8AFpg49odMz8DZ8RFSLDrozGhZtmorN70=
|
||||
github.com/luxfi/database v1.20.4/go.mod h1:S/LvmfzNYWVNslcEcZwDrntqUO2ksaL8ql1nRmLUA/Q=
|
||||
github.com/luxfi/dkg v0.3.5 h1:s2L2mMQaz+n9m0b0ghvoV5VZNxiwb2z4WrGugvK0udY=
|
||||
github.com/luxfi/dkg v0.3.5/go.mod h1:M+WH7GFRN+YUD851Rlnumdp0Md98kplNN8pVx65U8I8=
|
||||
github.com/luxfi/fhe v1.8.2 h1:QllnObNFbi6D4mvFI6uQkepW8HgLtdy4RMR1TKYAInA=
|
||||
github.com/luxfi/fhe v1.8.2/go.mod h1:16yxwhcnCez/rNcd/C9JjH9IjbEz73X+0tvlsONyLeA=
|
||||
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQVXyNHA=
|
||||
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
|
||||
github.com/luxfi/ids v1.2.10 h1:f1WILZE199ayMuqnEyB2WP1qfMZkmozOQXSVYtB3e5k=
|
||||
github.com/luxfi/ids v1.2.10/go.mod h1:QBIwy3OHvrtskbUqKh1+OYRa6PsyR7f7oNX33sOfK7w=
|
||||
github.com/luxfi/geth v1.17.12 h1:UP/fhpcfbGPTrkOCwX3d88Oc3jVm5gTOgfjgq+lek6s=
|
||||
github.com/luxfi/geth v1.17.12/go.mod h1:3vQfQJd9JC+AVBjxNXa9PYQOqpbE2dKu8E3jqhPZ3LU=
|
||||
github.com/luxfi/ids v1.2.15 h1:omE+E4+0Poj9DzM11ejSFgteaSQ3KDHi5g54iH6jcxI=
|
||||
github.com/luxfi/ids v1.2.15/go.mod h1:Fj73K5xcblvdE0SxU/ip+jE8VqNdu+80548su5KJ7xI=
|
||||
github.com/luxfi/lattice/v7 v7.1.4 h1:hQR02M6cHTAV5+joOPi9gb9Gm+z/hKJnhJF4IlciIJs=
|
||||
github.com/luxfi/lattice/v7 v7.1.4/go.mod h1:DmIQFi3mJiehVsR235l1NKYEU0JhU649OX5p7gMEW2c=
|
||||
github.com/luxfi/lens v0.1.4 h1:goGjGDXx2BNdjzXDunL5QT8elK2ZyCcc0z8TAbtWYrg=
|
||||
github.com/luxfi/lens v0.1.4/go.mod h1:mL+G8IK+9L41d78/2FYRgfhEzAjcr5+VEXB8SGuHbus=
|
||||
github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw=
|
||||
github.com/luxfi/log v1.4.1/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
|
||||
github.com/luxfi/log v1.4.3 h1:xkUKRWvQ4ZwvlUC2e0/RTtHYZOYSMvSQ9W9lbjwBmiI=
|
||||
github.com/luxfi/log v1.4.3/go.mod h1:myIkufyiQomSQH34K981kbz6cG4WUoerRUh7F4XhlQI=
|
||||
github.com/luxfi/math v1.4.1 h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
|
||||
github.com/luxfi/math v1.4.1/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
|
||||
github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
|
||||
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
|
||||
github.com/luxfi/metric v1.5.7 h1:LoSPEUpak2SLcynF+LT2cXjl9ECp4nY+Lia9zudmDv4=
|
||||
github.com/luxfi/metric v1.5.7/go.mod h1:CMguEhyuLi4YUWyXimJ+UHply99BDFrL0pxedB7rBqM=
|
||||
github.com/luxfi/mdns v0.1.1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
|
||||
github.com/luxfi/mdns v0.1.1/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
|
||||
github.com/luxfi/metric v1.5.9 h1:UAgXMNZf5oN/XJwwuKorf8iMaCj3nyP6thHPCwkUwY4=
|
||||
github.com/luxfi/metric v1.5.9/go.mod h1:ux+w3RZQCfF1zM8MO0wAWyNj/CsDlPd2mwTGshB9vY0=
|
||||
github.com/luxfi/mlwe v0.2.1 h1:pRwTjNUUtzUxRIlMbUPpeh9DE2/NdqfS17hfdogazp4=
|
||||
github.com/luxfi/mlwe v0.2.1/go.mod h1:DD9EHTeiyh/y0KGGeqL+q9S4n8raeGiGdaG/BQPAvT0=
|
||||
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
|
||||
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
|
||||
github.com/luxfi/p2p v1.21.1 h1:gmz1JMDhzHIL3dQlhwIDvR4OlFuhNVfnWUl/ipYhAIo=
|
||||
github.com/luxfi/p2p v1.21.1/go.mod h1:SsNPR5fPGWWNem9plGWhSmRqyDoysJ3kPAN0zG0g3iw=
|
||||
github.com/luxfi/pq v1.0.3 h1:ksw1dmfTR0dqqNMRS7BjGcprCO2Fhc+3Iiq2/NMuONw=
|
||||
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
|
||||
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
|
||||
github.com/luxfi/precompile v0.5.27 h1:lEF5gXY8ny5xXp8Qg8PK0Y2RQlaRNOZeDU+wP6Sj24M=
|
||||
github.com/luxfi/precompile v0.5.27/go.mod h1:boKRJeoa4XEhCoylRnPI/0IGfBgm5bZnFNI9llDktWo=
|
||||
github.com/luxfi/runtime v1.1.0 h1:6TrvzAmZVCTVbR1ebntHTO3/kVBaogPUSkxdDMnrTiw=
|
||||
github.com/luxfi/runtime v1.1.0/go.mod h1:Mfv2zlXqvfRFMS+/zXgG1TieyP9VnvtVzOGB437+o4Y=
|
||||
github.com/luxfi/runtime v1.1.3 h1:6Yp/PKwQCohjXmBR9GA+gamdSAp+xA2rdN6J/74Y4aw=
|
||||
github.com/luxfi/runtime v1.1.3/go.mod h1:r1uonDnxRCnPz6N6WYwaC72HW95KbFIAyChnJyxePGs=
|
||||
github.com/luxfi/sampler v1.1.0 h1:u3iRDl7V06ARh0e85h3HT+aZ1saCFo2yMMsh+dCJbqk=
|
||||
github.com/luxfi/sampler v1.1.0/go.mod h1:kJa53S3tC9+VSbuV3RFu68MmbCCBlr2UM39LOClQ/Hs=
|
||||
github.com/luxfi/utils v1.1.5 h1:6q85557b7djbZ/OF6S1k5wyGddozWnsoUuhokPHQRvo=
|
||||
github.com/luxfi/utils v1.1.5/go.mod h1:T2OCKT1xG9jtKR/gyJQoSkticzrE9WFQ8eohJHGu9Fg=
|
||||
github.com/luxfi/threshold v1.12.0 h1:JJ369xC/YyDvrqXj+xFoK98nP2rUM099qFs03hBvq/M=
|
||||
github.com/luxfi/threshold v1.12.0/go.mod h1:iuRQGDAy8ZKjQhZjkSKg7NtbP75/8Up9zj52y7IuyZo=
|
||||
github.com/luxfi/utils v1.2.0 h1:gtEiI7/NM6PQ/OasEpH0PvB+e5hIS/tpum9r64pYjMc=
|
||||
github.com/luxfi/utils v1.2.0/go.mod h1:T2OCKT1xG9jtKR/gyJQoSkticzrE9WFQ8eohJHGu9Fg=
|
||||
github.com/luxfi/validators v1.2.0 h1:VygpiBqBAdGrfkb7xzE2yrVmnXaqE+hm8FLWdGXO7G8=
|
||||
github.com/luxfi/validators v1.2.0/go.mod h1:GYLulrNXAan23ZlX7sgWVbVnLpUexeB/m2qr2ymsXok=
|
||||
github.com/luxfi/version v1.0.1 h1:T/1KYWEMmsrNQk7pN7PFPAwh/7XbeX7cFAKLBqI37Sk=
|
||||
github.com/luxfi/version v1.0.1/go.mod h1:Y5fPkQ2DB0XRBCxgSPXp4ISzL1/jptKnmFknShRJCyg=
|
||||
github.com/luxfi/vm v1.2.0 h1:jTwQRHdC9VmyRZTPSn+IqjMju7f6xlxLc6P9CEg+Y2M=
|
||||
github.com/luxfi/vm v1.2.0/go.mod h1:qasVIBRerVQuvy9vFGrX3H8X8pPMPG5un/KbZSyq5YY=
|
||||
github.com/luxfi/warp v1.18.6 h1:+ly7mHz77ig4yUyLax32aAjk7tiQQ6eygzOSDNfYRbQ=
|
||||
github.com/luxfi/warp v1.18.6/go.mod h1:OBN23yiGl+E4qupkPHuetBIEqsI8q5leE8WJH8soFjY=
|
||||
github.com/luxfi/vm v1.2.5 h1:L1etY/gh68f9tns1BtyDUpZcBVqc3Ng1mqU3n38GyLo=
|
||||
github.com/luxfi/vm v1.2.5/go.mod h1:TCCg4lDcQFCjxaxfXnxPIrpRSVAyyf2ucT4A4w654Hg=
|
||||
github.com/luxfi/warp v1.23.0 h1:lVPnEG3pj3YjYK6VZaNLKsCPheEEQINMgSZobwG2xDo=
|
||||
github.com/luxfi/warp v1.23.0/go.mod h1:4a6ZqXSsQVpm1nl1Q5i6SYPjDcdHvPyEoj4M9pvrIHE=
|
||||
github.com/luxfi/zap v0.8.11 h1:NIiZp/YyS1TQHfU/PHnMXynzPOKCfZOQssn5ytCdYXg=
|
||||
github.com/luxfi/zap v0.8.11/go.mod h1:JfqII8VtVQYLLTX6obU1DP9sjGqf9L24vfug5ifh0b8=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/montanaflynn/stats v0.9.0 h1:tsBJ0RXwph9BmAuFoCmqGv6e8xa0MENQ8m0ptKq29mQ=
|
||||
github.com/montanaflynn/stats v0.9.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=
|
||||
github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
@@ -147,7 +167,9 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
||||
github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
|
||||
github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
||||
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
||||
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
||||
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
@@ -160,13 +182,38 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9 h1:4d4PbuBNwaxMXkXI8yiIYjydtMU+04RHeuSxJdgKftM=
|
||||
golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
+134
-81
@@ -5,10 +5,20 @@
|
||||
// 3. Verify invariant preservation (D stays constant through swap)
|
||||
// 4. Test edge cases (zero swap, unbalanced pool)
|
||||
//
|
||||
// The calldata layout lives in encodeGetD/encodeGetDy and nowhere else. It used
|
||||
// to be spelled out at each call site, which is how every one of them drifted
|
||||
// from the precompile: they wrote a one-byte token count and put the
|
||||
// amplification factor last, while stableswap reads a four-byte count and takes
|
||||
// the amplification before the balances. The suite could not notice, because
|
||||
// e2e/go.mod required a published github.com/luxfi/precompile rather than the
|
||||
// module in this repo, so these tests ran against v0.5.27 and never saw the
|
||||
// working tree.
|
||||
//
|
||||
// Precompiles exercised: StableSwap.
|
||||
package scenarios
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
@@ -17,23 +27,53 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// word left-pads v to a 32-byte EVM word.
|
||||
func word(v uint64) []byte {
|
||||
return harness.PadLeft(new(big.Int).SetUint64(v).Bytes(), 32)
|
||||
}
|
||||
|
||||
// u32 encodes v as a 4-byte big-endian count.
|
||||
func u32(v uint32) []byte {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, v)
|
||||
return b
|
||||
}
|
||||
|
||||
// encodeGetD builds calldata for OpGetD:
|
||||
//
|
||||
// op(1) || n:uint32(4) || amp(32) || balances(n*32)
|
||||
func encodeGetD(amp uint64, balances ...uint64) []byte {
|
||||
input := []byte{stableswap.OpGetD}
|
||||
input = append(input, u32(uint32(len(balances)))...)
|
||||
input = append(input, word(amp)...)
|
||||
for _, b := range balances {
|
||||
input = append(input, word(b)...)
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// encodeGetDy builds calldata for OpGetDy:
|
||||
//
|
||||
// op(1) || i:uint32(4) || j:uint32(4) || dx(32) || n:uint32(4) || amp(32) || balances(n*32)
|
||||
func encodeGetDy(i, j uint32, dx, amp uint64, balances ...uint64) []byte {
|
||||
input := []byte{stableswap.OpGetDy}
|
||||
input = append(input, u32(i)...)
|
||||
input = append(input, u32(j)...)
|
||||
input = append(input, word(dx)...)
|
||||
input = append(input, u32(uint32(len(balances)))...)
|
||||
input = append(input, word(amp)...)
|
||||
for _, b := range balances {
|
||||
input = append(input, word(b)...)
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func TestDEXSwap_StableSwapGetD(t *testing.T) {
|
||||
// Compute invariant D for a balanced 2-token pool
|
||||
// Each token: 1,000,000 units, amplification A=100
|
||||
balance := harness.PadLeft(new(big.Int).SetUint64(1_000_000).Bytes(), 32)
|
||||
amp := harness.PadLeft(new(big.Int).SetUint64(100).Bytes(), 32)
|
||||
|
||||
input := make([]byte, 0, 1+1+64+32)
|
||||
input = append(input, stableswap.OpGetD)
|
||||
input = append(input, 2) // numTokens
|
||||
input = append(input, balance...)
|
||||
input = append(input, balance...)
|
||||
input = append(input, amp...)
|
||||
|
||||
// Balanced 2-token pool, 1,000,000 units each, amplification A=100.
|
||||
out, gas, err := harness.Call(
|
||||
stableswap.Precompile,
|
||||
stableswap.ContractAddress,
|
||||
input,
|
||||
encodeGetD(100, 1_000_000, 1_000_000),
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err, "GetD failed")
|
||||
@@ -43,37 +83,22 @@ func TestDEXSwap_StableSwapGetD(t *testing.T) {
|
||||
require.True(t, d.Sign() > 0, "D must be positive")
|
||||
harness.GasReport(t, "StableSwap GetD", gas)
|
||||
|
||||
// For a balanced pool with 1M each, D should be ~2M
|
||||
// For a balanced pool holding 1M of each token the invariant is the total,
|
||||
// 2M, independent of the amplification factor.
|
||||
expectedD := new(big.Int).SetUint64(2_000_000)
|
||||
diff := new(big.Int).Sub(d, expectedD)
|
||||
diff.Abs(diff)
|
||||
// Allow 1% tolerance for Newton iteration rounding
|
||||
tolerance := new(big.Int).Div(expectedD, big.NewInt(100))
|
||||
tolerance := new(big.Int).Div(expectedD, big.NewInt(100)) // 1% for Newton rounding
|
||||
require.True(t, diff.Cmp(tolerance) <= 0,
|
||||
"D=%s should be close to 2M (diff=%s)", d.String(), diff.String())
|
||||
t.Logf("Pool invariant D = %s", d.String())
|
||||
}
|
||||
|
||||
func TestDEXSwap_StableSwapGetDy(t *testing.T) {
|
||||
// Swap 1000 of token 0 for token 1 in a balanced pool
|
||||
balance := harness.PadLeft(new(big.Int).SetUint64(1_000_000).Bytes(), 32)
|
||||
amp := harness.PadLeft(new(big.Int).SetUint64(100).Bytes(), 32)
|
||||
dx := harness.PadLeft(new(big.Int).SetUint64(1000).Bytes(), 32)
|
||||
|
||||
input := make([]byte, 0, 1+1+1+32+1+64+32)
|
||||
input = append(input, stableswap.OpGetDy)
|
||||
input = append(input, 0) // i = token 0
|
||||
input = append(input, 1) // j = token 1
|
||||
input = append(input, dx...) // swap 1000
|
||||
input = append(input, 2) // numTokens
|
||||
input = append(input, balance...)
|
||||
input = append(input, balance...)
|
||||
input = append(input, amp...)
|
||||
|
||||
out, gas, err := harness.Call(
|
||||
stableswap.Precompile,
|
||||
stableswap.ContractAddress,
|
||||
input,
|
||||
encodeGetDy(0, 1, 1000, 100, 1_000_000, 1_000_000),
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err, "GetDy failed")
|
||||
@@ -83,90 +108,118 @@ func TestDEXSwap_StableSwapGetDy(t *testing.T) {
|
||||
require.True(t, dy.Sign() > 0, "dy must be positive")
|
||||
harness.GasReport(t, "StableSwap GetDy", gas)
|
||||
|
||||
// In a balanced stableswap pool (A=100), swapping 1000 should yield ~999
|
||||
// (very close to 1:1 due to high amplification)
|
||||
// A balanced pool at A=100 trades very close to 1:1, and never above it —
|
||||
// the pool must not pay out more than it took in.
|
||||
t.Logf("Swap 1000 token0 -> %s token1", dy.String())
|
||||
require.True(t, dy.Uint64() >= 990 && dy.Uint64() <= 1000,
|
||||
"dy=%d should be ~999 for balanced pool with A=100", dy.Uint64())
|
||||
}
|
||||
|
||||
func TestDEXSwap_InvariantPreservation(t *testing.T) {
|
||||
// Verify that D remains constant after a swap:
|
||||
// 1. Compute D with initial balances
|
||||
// 2. Compute swap output dy
|
||||
// 3. Compute D with post-swap balances
|
||||
// 4. Assert D_before == D_after
|
||||
const (
|
||||
balA = uint64(1_000_000)
|
||||
balB = uint64(1_000_000)
|
||||
swapAmt = uint64(10_000)
|
||||
ampVal = uint64(100)
|
||||
)
|
||||
|
||||
balA := uint64(1_000_000)
|
||||
balB := uint64(1_000_000)
|
||||
swapAmt := uint64(10_000)
|
||||
ampVal := uint64(100)
|
||||
|
||||
// Compute D_before
|
||||
dBefore := computeD(t, balA, balB, ampVal)
|
||||
|
||||
// Compute dy
|
||||
dy := computeDy(t, 0, 1, swapAmt, balA, balB, ampVal)
|
||||
|
||||
// Post-swap balances: balA + dx, balB - dy
|
||||
newBalA := balA + swapAmt
|
||||
newBalB := balB - dy
|
||||
// Post-swap balances: the pool gains dx of token 0 and pays out dy of token 1.
|
||||
dAfter := computeD(t, balA+swapAmt, balB-dy, ampVal)
|
||||
|
||||
// Compute D_after
|
||||
dAfter := computeD(t, newBalA, newBalB, ampVal)
|
||||
|
||||
// D should be preserved (within rounding tolerance)
|
||||
diff := new(big.Int).Sub(dBefore, dAfter)
|
||||
diff.Abs(diff)
|
||||
// Allow 1 unit tolerance for integer rounding
|
||||
require.True(t, diff.Cmp(big.NewInt(1)) <= 0,
|
||||
"D before (%s) != D after (%s), diff=%s", dBefore.String(), dAfter.String(), diff.String())
|
||||
|
||||
// The invariant may only ever grow: a swap that decreased D would mean the
|
||||
// pool paid out more than the curve allows, which is how a stableswap gets
|
||||
// drained one trade at a time.
|
||||
require.True(t, dAfter.Cmp(dBefore) >= 0,
|
||||
"D decreased across a swap: before=%s after=%s", dBefore, dAfter)
|
||||
|
||||
t.Logf("D_before=%s, D_after=%s (invariant preserved)", dBefore.String(), dAfter.String())
|
||||
}
|
||||
|
||||
func TestDEXSwap_UnbalancedPool(t *testing.T) {
|
||||
// Test swap in an unbalanced pool (10:1 ratio)
|
||||
balA := uint64(10_000_000)
|
||||
balB := uint64(1_000_000)
|
||||
swapAmt := uint64(1000)
|
||||
ampVal := uint64(100)
|
||||
// A 10:1 pool. Selling the scarce side into the deep side still pays out,
|
||||
// but the curve must charge for the imbalance rather than trading 1:1.
|
||||
const (
|
||||
balA = uint64(10_000_000)
|
||||
balB = uint64(1_000_000)
|
||||
swapAmt = uint64(1000)
|
||||
ampVal = uint64(100)
|
||||
)
|
||||
|
||||
dy := computeDy(t, 0, 1, swapAmt, balA, balB, ampVal)
|
||||
t.Logf("Unbalanced pool (10:1): swap 1000 token0 -> %d token1", dy)
|
||||
// In an unbalanced pool, the output should be less than the balanced case
|
||||
require.True(t, dy > 0, "dy must be positive even in unbalanced pool")
|
||||
|
||||
// Pushing the pool further from balance must not be more profitable than
|
||||
// trading against a balanced one.
|
||||
balanced := computeDy(t, 0, 1, swapAmt, balA, balA, ampVal)
|
||||
require.LessOrEqual(t, dy, balanced,
|
||||
"selling into the shallow side paid %d, better than the balanced %d", dy, balanced)
|
||||
}
|
||||
|
||||
// The precompile refuses input the balances do not back, rather than reading
|
||||
// past them.
|
||||
func TestDEXSwap_RefusesTruncatedBalances(t *testing.T) {
|
||||
full := encodeGetD(100, 1_000_000, 1_000_000)
|
||||
|
||||
// Same header, one balance word short of what n promises.
|
||||
truncated := full[:len(full)-32]
|
||||
_, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress, truncated, true)
|
||||
require.Error(t, err, "a token count larger than the balances supplied was accepted")
|
||||
|
||||
// And an empty body behind a well-formed opcode.
|
||||
_, _, err = harness.Call(stableswap.Precompile, stableswap.ContractAddress,
|
||||
[]byte{stableswap.OpGetD}, true)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// A swap must name two distinct, in-range tokens.
|
||||
func TestDEXSwap_RefusesBadIndices(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
i, j uint32
|
||||
}{
|
||||
{"same token", 0, 0},
|
||||
{"i out of range", 2, 1},
|
||||
{"j out of range", 0, 2},
|
||||
{"both out of range", 7, 9},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress,
|
||||
encodeGetDy(tc.i, tc.j, 1000, 100, 1_000_000, 1_000_000), true)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A zero or absent amplification factor is refused: A=0 collapses the curve.
|
||||
func TestDEXSwap_RefusesZeroAmplification(t *testing.T) {
|
||||
_, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress,
|
||||
encodeGetD(0, 1_000_000, 1_000_000), true)
|
||||
require.Error(t, err, "amplification of zero was accepted")
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func computeD(t *testing.T, balA, balB, amp uint64) *big.Int {
|
||||
t.Helper()
|
||||
input := make([]byte, 0, 1+1+64+32)
|
||||
input = append(input, stableswap.OpGetD)
|
||||
input = append(input, 2)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(balA).Bytes(), 32)...)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(balB).Bytes(), 32)...)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(amp).Bytes(), 32)...)
|
||||
|
||||
out, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress, input, true)
|
||||
out, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress,
|
||||
encodeGetD(amp, balA, balB), true)
|
||||
require.NoError(t, err)
|
||||
return new(big.Int).SetBytes(out)
|
||||
}
|
||||
|
||||
func computeDy(t *testing.T, i, j byte, dx, balA, balB, amp uint64) uint64 {
|
||||
func computeDy(t *testing.T, i, j uint32, dx, balA, balB, amp uint64) uint64 {
|
||||
t.Helper()
|
||||
input := make([]byte, 0, 1+1+1+32+1+64+32)
|
||||
input = append(input, stableswap.OpGetDy)
|
||||
input = append(input, i)
|
||||
input = append(input, j)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(dx).Bytes(), 32)...)
|
||||
input = append(input, 2)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(balA).Bytes(), 32)...)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(balB).Bytes(), 32)...)
|
||||
input = append(input, harness.PadLeft(new(big.Int).SetUint64(amp).Bytes(), 32)...)
|
||||
|
||||
out, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress, input, true)
|
||||
out, _, err := harness.Call(stableswap.Precompile, stableswap.ContractAddress,
|
||||
encodeGetDy(i, j, dx, amp, balA, balB), true)
|
||||
require.NoError(t, err)
|
||||
return new(big.Int).SetBytes(out).Uint64()
|
||||
}
|
||||
|
||||
@@ -1,130 +1,203 @@
|
||||
// keygen_sign_verify_test exercises the full MPC lifecycle:
|
||||
// keygen_sign_verify_test drives the threshold-signature precompiles through
|
||||
// the precompile dispatch path with real signatures.
|
||||
//
|
||||
// CGGMP21 keygen (off-chain) -> sign message -> verify via precompile
|
||||
// FROST keygen (off-chain) -> sign message -> verify via precompile
|
||||
// What these tests used to do, and why none of them could pass:
|
||||
//
|
||||
// - CGGMP21 is threshold ECDSA on secp256k1, and the test generated a P-256
|
||||
// key. The precompile rejected it, correctly, with "invalid secp256k1
|
||||
// public key".
|
||||
// - FROST here is secp256k1 with BIP-340-style x-only keys (see
|
||||
// verifySchnorrSignature, which calls curve.Secp256k1{}.LiftX), and the
|
||||
// test built an edwards25519 key. Wrong curve entirely; the comment
|
||||
// claiming "FROST produces Ed25519-compatible keys" does not hold for this
|
||||
// implementation.
|
||||
// - Both FROST and Ed25519 seeded a scalar with 32 bytes straight from
|
||||
// rand.Read and passed them to SetCanonicalBytes. An ed25519 scalar must be
|
||||
// below the group order L, which is just under 2^252, so uniform 256-bit
|
||||
// bytes are canonical only about one time in sixteen. The tests failed on
|
||||
// their own key generation, before reaching a precompile, roughly 94% of
|
||||
// runs.
|
||||
// - The "Ed25519" test then hand-rolled a SHA-256 Schnorr scheme with the
|
||||
// verification equation s = k - e*x. RFC 8032 uses SHA-512 and s = r + e*a.
|
||||
// Even with a valid scalar it could never have verified against the
|
||||
// precompile, which calls ed25519.Verify.
|
||||
//
|
||||
// None of this was visible because e2e/go.mod required a published
|
||||
// github.com/luxfi/precompile instead of the module in this repo.
|
||||
//
|
||||
// Keys are drawn from a seeded source so a failure reproduces.
|
||||
//
|
||||
// Precompiles exercised: CGGMP21, FROST, Ed25519.
|
||||
package scenarios
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
mathrand "math/rand/v2"
|
||||
"testing"
|
||||
|
||||
"filippo.io/edwards25519"
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/crypto"
|
||||
"github.com/luxfi/precompile/cggmp21"
|
||||
"github.com/luxfi/precompile/e2e/harness"
|
||||
"github.com/luxfi/precompile/ed25519"
|
||||
precompileed25519 "github.com/luxfi/precompile/ed25519"
|
||||
"github.com/luxfi/precompile/frost"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestKeygenSignVerify_CGGMP21 simulates a threshold ECDSA signing ceremony:
|
||||
// 1. Generate ECDSA keypair (represents output of CGGMP21 keygen)
|
||||
// 2. Sign a message (represents threshold signing)
|
||||
// 3. Verify via the CGGMP21 precompile
|
||||
func TestKeygenSignVerify_CGGMP21(t *testing.T) {
|
||||
// Step 1: Keygen (off-chain CGGMP21 produces a standard ECDSA key)
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err, "keygen failed")
|
||||
pub := &priv.PublicKey
|
||||
// seeded is a reproducible byte source for key generation. Tests must not turn
|
||||
// on the operating system's entropy: a signature scheme that fails one run in
|
||||
// sixteen is indistinguishable from flaky infrastructure.
|
||||
type seeded struct{ rng *mathrand.Rand }
|
||||
|
||||
// Step 2: Sign
|
||||
msg := []byte("Lux e2e: full CGGMP21 lifecycle test")
|
||||
hash := sha256.Sum256(msg)
|
||||
r, s, err := ecdsa.Sign(rand.Reader, priv, hash[:])
|
||||
require.NoError(t, err, "sign failed")
|
||||
func newSeeded(seed uint64) *seeded {
|
||||
return &seeded{rng: mathrand.New(mathrand.NewPCG(seed, seed^0x5deece66d))}
|
||||
}
|
||||
|
||||
// Step 3: Verify via precompile
|
||||
// Uncompressed public key: 0x04 || x(32) || y(32) = 65 bytes
|
||||
pubKey := make([]byte, 65)
|
||||
pubKey[0] = 0x04
|
||||
copy(pubKey[1:33], harness.PadLeft(pub.X.Bytes(), 32))
|
||||
copy(pubKey[33:65], harness.PadLeft(pub.Y.Bytes(), 32))
|
||||
func (s *seeded) Read(p []byte) (int, error) {
|
||||
for i := range p {
|
||||
p[i] = byte(s.rng.Uint32())
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Signature: r(32) || s(32) || v(1) = 65 bytes
|
||||
sig := make([]byte, 65)
|
||||
copy(sig[0:32], harness.PadLeft(r.Bytes(), 32))
|
||||
copy(sig[32:64], harness.PadLeft(s.Bytes(), 32))
|
||||
sig[64] = 0 // v = 0
|
||||
// verified reports whether a precompile call accepted the signature. A refusal
|
||||
// may surface either as an error or as a non-true return word, and a rejecting
|
||||
// precompile may return no bytes at all — so this never indexes blindly.
|
||||
func verified(out []byte, err error) bool {
|
||||
return err == nil && len(out) == 32 && out[31] == 1
|
||||
}
|
||||
|
||||
// Input: threshold(4) + totalSigners(4) + pubkey(65) + hash(32) + sig(65)
|
||||
input := make([]byte, 0, 4+4+65+32+65)
|
||||
input = append(input, harness.Uint32BE(2)...) // threshold = 2
|
||||
input = append(input, harness.Uint32BE(3)...) // total signers = 3
|
||||
// ─── CGGMP21 (threshold ECDSA, secp256k1) ─────────────────────────────────────
|
||||
|
||||
// encodeCGGMP21 lays out the precompile's calldata:
|
||||
//
|
||||
// threshold(4) || totalSigners(4) || pubkey(65) || msgHash(32) || sig(65)
|
||||
func encodeCGGMP21(threshold, total uint32, pubKey, msgHash, sig []byte) []byte {
|
||||
input := make([]byte, 0, cggmp21.MinInputSize)
|
||||
input = append(input, harness.Uint32BE(threshold)...)
|
||||
input = append(input, harness.Uint32BE(total)...)
|
||||
input = append(input, pubKey...)
|
||||
input = append(input, hash[:]...)
|
||||
input = append(input, msgHash...)
|
||||
input = append(input, sig...)
|
||||
return input
|
||||
}
|
||||
|
||||
// cggmp21Material produces a secp256k1 key and a signature over msg, in the
|
||||
// encodings the precompile expects: an uncompressed 65-byte public key and a
|
||||
// 65-byte [R || S || V] signature. An aggregated CGGMP21 threshold signature is
|
||||
// an ordinary ECDSA signature, so this is what the ceremony emits.
|
||||
func cggmp21Material(t *testing.T, msg []byte, seed uint64) (pubKey, msgHash, sig []byte) {
|
||||
t.Helper()
|
||||
priv, err := ecdsa.GenerateKey(crypto.S256(), newSeeded(seed))
|
||||
require.NoError(t, err, "secp256k1 keygen")
|
||||
|
||||
h := sha256.Sum256(msg)
|
||||
sig, err = crypto.Sign(h[:], priv)
|
||||
require.NoError(t, err, "sign")
|
||||
|
||||
return crypto.FromECDSAPub(&priv.PublicKey), h[:], sig
|
||||
}
|
||||
|
||||
func TestKeygenSignVerify_CGGMP21(t *testing.T) {
|
||||
pubKey, msgHash, sig := cggmp21Material(t, []byte("Lux e2e: full CGGMP21 lifecycle test"), 1)
|
||||
require.Len(t, pubKey, 65)
|
||||
require.Len(t, sig, 65)
|
||||
|
||||
out, gasUsed, err := harness.Call(
|
||||
cggmp21.CGGMP21VerifyPrecompile,
|
||||
cggmp21.ContractCGGMP21VerifyAddress,
|
||||
input,
|
||||
encodeCGGMP21(2, 3, pubKey, msgHash, sig),
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err, "CGGMP21 verify call error")
|
||||
require.Len(t, out, 32, "output should be 32 bytes")
|
||||
require.Equal(t, byte(1), out[31], "CGGMP21 verify should return true")
|
||||
require.Equal(t, byte(1), out[31], "a valid signature must verify")
|
||||
harness.GasReport(t, "CGGMP21 verify", gasUsed)
|
||||
}
|
||||
|
||||
// TestKeygenSignVerify_FROST simulates a threshold Schnorr signing ceremony:
|
||||
// 1. Generate Ed25519 keypair (represents output of FROST keygen)
|
||||
// 2. Produce a Schnorr signature
|
||||
// 3. Verify via the FROST precompile
|
||||
func TestKeygenSignVerify_FROST(t *testing.T) {
|
||||
// Step 1: Generate Ed25519 key (FROST produces Ed25519-compatible keys)
|
||||
privScalar := make([]byte, 32)
|
||||
_, err := rand.Read(privScalar)
|
||||
require.NoError(t, err)
|
||||
// The signature must bind the message. Verifying a signature against a hash it
|
||||
// was not made over is the whole property; if this passes, the precompile is
|
||||
// decorative.
|
||||
func TestKeygenSignVerify_CGGMP21RejectsWrongMessage(t *testing.T) {
|
||||
pubKey, _, sig := cggmp21Material(t, []byte("the message that was signed"), 2)
|
||||
other := sha256.Sum256([]byte("a message that was not signed"))
|
||||
|
||||
scalarBytes, err := edwards25519.NewScalar().SetCanonicalBytes(privScalar)
|
||||
require.NoError(t, err)
|
||||
pubPoint := edwards25519.NewGeneratorPoint().ScalarMult(scalarBytes, edwards25519.NewGeneratorPoint())
|
||||
pubBytes := pubPoint.Bytes() // 32 bytes compressed
|
||||
out, _, err := harness.Call(
|
||||
cggmp21.CGGMP21VerifyPrecompile,
|
||||
cggmp21.ContractCGGMP21VerifyAddress,
|
||||
encodeCGGMP21(2, 3, pubKey, other[:], sig),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "a signature verified against the wrong message")
|
||||
}
|
||||
|
||||
// Step 2: Create a Schnorr signature (R || s)
|
||||
// k = random nonce
|
||||
kBytes := make([]byte, 32)
|
||||
_, err = rand.Read(kBytes)
|
||||
require.NoError(t, err)
|
||||
// Every single-bit corruption of the signature must be rejected.
|
||||
func TestKeygenSignVerify_CGGMP21RejectsCorruptedSignature(t *testing.T) {
|
||||
pubKey, msgHash, sig := cggmp21Material(t, []byte("Lux e2e: corruption corpus"), 3)
|
||||
|
||||
kScalar, err := edwards25519.NewScalar().SetCanonicalBytes(kBytes)
|
||||
require.NoError(t, err)
|
||||
rPoint := edwards25519.NewGeneratorPoint().ScalarMult(kScalar, edwards25519.NewGeneratorPoint())
|
||||
rBytes := rPoint.Bytes() // R = k*G, 32 bytes
|
||||
for _, bit := range []int{0, 7, 8, 255, 256, 263, 511} {
|
||||
corrupt := make([]byte, len(sig))
|
||||
copy(corrupt, sig)
|
||||
corrupt[bit/8] ^= 1 << (bit % 8)
|
||||
|
||||
msg := []byte("Lux e2e: full FROST lifecycle test")
|
||||
hash := sha256.Sum256(msg)
|
||||
out, _, err := harness.Call(
|
||||
cggmp21.CGGMP21VerifyPrecompile,
|
||||
cggmp21.ContractCGGMP21VerifyAddress,
|
||||
encodeCGGMP21(2, 3, pubKey, msgHash, corrupt),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "signature with bit %d flipped verified", bit)
|
||||
}
|
||||
}
|
||||
|
||||
// e = H(R || pubkey || message_hash)
|
||||
eInput := make([]byte, 0, 32+32+32)
|
||||
eInput = append(eInput, rBytes...)
|
||||
eInput = append(eInput, pubBytes...)
|
||||
eInput = append(eInput, hash[:]...)
|
||||
eHash := sha256.Sum256(eInput)
|
||||
// Input shorter than the fixed layout is refused rather than read past.
|
||||
func TestKeygenSignVerify_CGGMP21RefusesShortInput(t *testing.T) {
|
||||
pubKey, msgHash, sig := cggmp21Material(t, []byte("short input"), 4)
|
||||
full := encodeCGGMP21(2, 3, pubKey, msgHash, sig)
|
||||
|
||||
eScalar, err := edwards25519.NewScalar().SetCanonicalBytes(eHash[:])
|
||||
require.NoError(t, err)
|
||||
for _, n := range []int{0, 1, 8, 73, 105, len(full) - 1} {
|
||||
_, _, err := harness.Call(
|
||||
cggmp21.CGGMP21VerifyPrecompile,
|
||||
cggmp21.ContractCGGMP21VerifyAddress,
|
||||
full[:n],
|
||||
true,
|
||||
)
|
||||
require.Error(t, err, "a %d-byte input was accepted", n)
|
||||
}
|
||||
}
|
||||
|
||||
// s = k - e * privKey
|
||||
sScalar := edwards25519.NewScalar().MultiplyAdd(edwards25519.NewScalar().Negate(eScalar), scalarBytes, kScalar)
|
||||
// An all-zero public key is not a point on secp256k1 and must be refused.
|
||||
func TestKeygenSignVerify_CGGMP21RefusesZeroKey(t *testing.T) {
|
||||
_, msgHash, sig := cggmp21Material(t, []byte("zero key"), 5)
|
||||
|
||||
// Signature: R(32) || s(32) = 64 bytes
|
||||
frostSig := make([]byte, 64)
|
||||
copy(frostSig[0:32], rBytes)
|
||||
copy(frostSig[32:64], sScalar.Bytes())
|
||||
out, _, err := harness.Call(
|
||||
cggmp21.CGGMP21VerifyPrecompile,
|
||||
cggmp21.ContractCGGMP21VerifyAddress,
|
||||
encodeCGGMP21(2, 3, make([]byte, 65), msgHash, sig),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "the all-zero public key verified a signature")
|
||||
}
|
||||
|
||||
// Input: threshold(4) + totalSigners(4) + pubkey(32) + hash(32) + sig(64)
|
||||
input := make([]byte, 0, 4+4+32+32+64)
|
||||
input = append(input, harness.Uint32BE(2)...) // threshold = 2
|
||||
input = append(input, harness.Uint32BE(3)...) // total signers = 3
|
||||
input = append(input, pubBytes...)
|
||||
input = append(input, hash[:]...)
|
||||
input = append(input, frostSig...)
|
||||
// ─── FROST (Schnorr, secp256k1 x-only) ────────────────────────────────────────
|
||||
|
||||
// The positive case — a genuine aggregated FROST signature verifying — is owned
|
||||
// by frost/real_sig_test.go, which can reach the threshold library's signing
|
||||
// internals and the precompile's domain-separated challenge construction.
|
||||
// Rebuilding that here would put the challenge construction in two places, and
|
||||
// a second copy of a wire format is exactly what let the StableSwap scenarios
|
||||
// drift out of sync unnoticed. What e2e adds instead is the dispatch path: that
|
||||
// the precompile is reachable at its address and refuses what it should.
|
||||
func TestKeygenSignVerify_FROSTRefusesMalformed(t *testing.T) {
|
||||
// A well-formed frame carrying a signature that is not one.
|
||||
input := make([]byte, 0, frost.MinInputSize)
|
||||
input = append(input, harness.Uint32BE(2)...) // threshold
|
||||
input = append(input, harness.Uint32BE(3)...) // total signers
|
||||
input = append(input, make([]byte, 32)...) // public key (all zero)
|
||||
msgHash := sha256.Sum256([]byte("Lux e2e: FROST dispatch"))
|
||||
input = append(input, msgHash[:]...)
|
||||
input = append(input, make([]byte, 64)...) // signature (all zero)
|
||||
|
||||
out, gasUsed, err := harness.Call(
|
||||
frost.FROSTVerifyPrecompile,
|
||||
@@ -132,68 +205,133 @@ func TestKeygenSignVerify_FROST(t *testing.T) {
|
||||
input,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err, "FROST verify call error")
|
||||
require.Len(t, out, 32, "output should be 32 bytes")
|
||||
// FROST with our hand-rolled Schnorr should verify
|
||||
require.Equal(t, byte(1), out[31], "FROST verify should return true")
|
||||
harness.GasReport(t, "FROST verify", gasUsed)
|
||||
}
|
||||
require.False(t, verified(out, err),
|
||||
"an all-zero key and signature verified")
|
||||
harness.GasReport(t, "FROST verify (refusal)", gasUsed)
|
||||
|
||||
// TestKeygenSignVerify_Ed25519Direct tests standalone Ed25519 verification.
|
||||
// And truncations of the same frame.
|
||||
for _, n := range []int{0, 8, 40, 72, len(input) - 1} {
|
||||
_, _, err := harness.Call(
|
||||
frost.FROSTVerifyPrecompile,
|
||||
frost.ContractFROSTVerifyAddress,
|
||||
input[:n],
|
||||
true,
|
||||
)
|
||||
require.Error(t, err, "a %d-byte input was accepted", n)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Ed25519 (RFC 8032) ───────────────────────────────────────────────────────
|
||||
|
||||
// encodeEd25519 lays out the precompile's calldata:
|
||||
//
|
||||
// message(32) || signature(64) || publicKey(32)
|
||||
func encodeEd25519(msgHash, sig, pubKey []byte) []byte {
|
||||
input := make([]byte, 0, precompileed25519.InputLength)
|
||||
input = append(input, msgHash...)
|
||||
input = append(input, sig...)
|
||||
input = append(input, pubKey...)
|
||||
return input
|
||||
}
|
||||
|
||||
func ed25519Material(t *testing.T, msg []byte, seed uint64) (pubKey, msgHash, sig []byte) {
|
||||
t.Helper()
|
||||
pub, priv, err := ed25519.GenerateKey(newSeeded(seed))
|
||||
require.NoError(t, err, "ed25519 keygen")
|
||||
|
||||
h := sha256.Sum256(msg)
|
||||
// The precompile verifies over the 32-byte word it is handed, so that word
|
||||
// is what gets signed.
|
||||
return pub, h[:], ed25519.Sign(priv, h[:])
|
||||
}
|
||||
|
||||
func TestKeygenSignVerify_Ed25519Direct(t *testing.T) {
|
||||
// Generate key
|
||||
privScalar := make([]byte, 32)
|
||||
_, err := rand.Read(privScalar)
|
||||
require.NoError(t, err)
|
||||
|
||||
scalar, err := edwards25519.NewScalar().SetCanonicalBytes(privScalar)
|
||||
require.NoError(t, err)
|
||||
pubPoint := edwards25519.NewGeneratorPoint().ScalarMult(scalar, edwards25519.NewGeneratorPoint())
|
||||
pubBytes := pubPoint.Bytes()
|
||||
|
||||
// Sign (simplified Ed25519-like: we use the message hash directly)
|
||||
msg := []byte("Lux e2e: Ed25519 direct verify")
|
||||
hash := sha256.Sum256(msg)
|
||||
|
||||
// The Ed25519 precompile expects: hash(32) + signature(64) + pubkey(32) = 128 bytes
|
||||
// For this test we construct a deterministic signature
|
||||
kBytes := make([]byte, 32)
|
||||
_, err = rand.Read(kBytes)
|
||||
require.NoError(t, err)
|
||||
kScalar, err := edwards25519.NewScalar().SetCanonicalBytes(kBytes)
|
||||
require.NoError(t, err)
|
||||
rPoint := edwards25519.NewGeneratorPoint().ScalarMult(kScalar, edwards25519.NewGeneratorPoint())
|
||||
|
||||
// Build challenge
|
||||
challenge := sha256.Sum256(append(append(rPoint.Bytes(), pubBytes...), hash[:]...))
|
||||
eScalar, err := edwards25519.NewScalar().SetCanonicalBytes(challenge[:])
|
||||
require.NoError(t, err)
|
||||
sScalar := edwards25519.NewScalar().MultiplyAdd(edwards25519.NewScalar().Negate(eScalar), scalar, kScalar)
|
||||
|
||||
sig := make([]byte, 64)
|
||||
copy(sig[0:32], rPoint.Bytes())
|
||||
copy(sig[32:64], sScalar.Bytes())
|
||||
|
||||
// Ed25519 precompile input: hash(32) + sig(64) + pubkey(32) = 128
|
||||
input := make([]byte, 0, 128)
|
||||
input = append(input, hash[:]...)
|
||||
input = append(input, sig...)
|
||||
input = append(input, pubBytes...)
|
||||
pubKey, msgHash, sig := ed25519Material(t, []byte("Lux e2e: Ed25519 direct verify"), 6)
|
||||
|
||||
out, gasUsed, err := harness.Call(
|
||||
ed25519.Ed25519VerifyPrecompile,
|
||||
common.HexToAddress("0x0800000000000000000000000000000000000001"),
|
||||
input,
|
||||
precompileed25519.Ed25519VerifyPrecompile,
|
||||
precompileed25519.ContractAddress,
|
||||
encodeEd25519(msgHash, sig, pubKey),
|
||||
true,
|
||||
)
|
||||
// Ed25519 precompile may use a different signature format (standard Ed25519 vs Schnorr).
|
||||
// We verify the call doesn't panic and returns clean output.
|
||||
if err == nil && len(out) == 32 {
|
||||
harness.GasReport(t, "Ed25519 verify", gasUsed)
|
||||
t.Logf("Ed25519 result: %x", out)
|
||||
} else {
|
||||
// Some Ed25519 implementations require RFC 8032 format.
|
||||
// The call should not panic -- just return an error or false.
|
||||
t.Logf("Ed25519 returned err=%v (acceptable for non-RFC8032 sig format)", err)
|
||||
require.NoError(t, err, "Ed25519 verify call error")
|
||||
require.Len(t, out, 32, "output should be 32 bytes")
|
||||
require.Equal(t, byte(1), out[31], "a valid RFC 8032 signature must verify")
|
||||
harness.GasReport(t, "Ed25519 verify", gasUsed)
|
||||
}
|
||||
|
||||
func TestKeygenSignVerify_Ed25519RejectsWrongMessage(t *testing.T) {
|
||||
pubKey, _, sig := ed25519Material(t, []byte("the signed message"), 7)
|
||||
other := sha256.Sum256([]byte("a different message"))
|
||||
|
||||
out, _, err := harness.Call(
|
||||
precompileed25519.Ed25519VerifyPrecompile,
|
||||
precompileed25519.ContractAddress,
|
||||
encodeEd25519(other[:], sig, pubKey),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "a signature verified against the wrong message")
|
||||
}
|
||||
|
||||
func TestKeygenSignVerify_Ed25519RejectsCorruption(t *testing.T) {
|
||||
pubKey, msgHash, sig := ed25519Material(t, []byte("Lux e2e: ed25519 corruption"), 8)
|
||||
|
||||
// Corrupt the signature, then the key. Both must be refused.
|
||||
for _, bit := range []int{0, 255, 256, 511} {
|
||||
corrupt := make([]byte, len(sig))
|
||||
copy(corrupt, sig)
|
||||
corrupt[bit/8] ^= 1 << (bit % 8)
|
||||
|
||||
out, _, err := harness.Call(
|
||||
precompileed25519.Ed25519VerifyPrecompile,
|
||||
precompileed25519.ContractAddress,
|
||||
encodeEd25519(msgHash, corrupt, pubKey),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "signature with bit %d flipped verified", bit)
|
||||
}
|
||||
|
||||
badKey := make([]byte, len(pubKey))
|
||||
copy(badKey, pubKey)
|
||||
badKey[0] ^= 1
|
||||
out, _, err := harness.Call(
|
||||
precompileed25519.Ed25519VerifyPrecompile,
|
||||
precompileed25519.ContractAddress,
|
||||
encodeEd25519(msgHash, sig, badKey),
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "a corrupted public key verified")
|
||||
}
|
||||
|
||||
// The precompile takes a fixed 128-byte frame and must refuse anything else, in
|
||||
// both directions.
|
||||
//
|
||||
// Note the refusal SHAPE differs across this repo's verifiers: ed25519 reports
|
||||
// failure by returning empty output with a nil error (documented at the top of
|
||||
// its Run), while cggmp21 and frost return an error. Both are safe — neither
|
||||
// can be mistaken for the success word — but a caller cannot distinguish
|
||||
// "malformed frame" from "signature did not verify" here, which for a verifier
|
||||
// is the conservative choice. Assert the property that matters in both shapes:
|
||||
// the call did not verify.
|
||||
func TestKeygenSignVerify_Ed25519RefusesWrongLength(t *testing.T) {
|
||||
pubKey, msgHash, sig := ed25519Material(t, []byte("length discipline"), 9)
|
||||
full := encodeEd25519(msgHash, sig, pubKey)
|
||||
require.Len(t, full, precompileed25519.InputLength)
|
||||
|
||||
for _, in := range [][]byte{
|
||||
nil,
|
||||
full[:precompileed25519.InputLength-1],
|
||||
append(append([]byte{}, full...), 0x00),
|
||||
} {
|
||||
out, gasUsed, err := harness.Call(
|
||||
precompileed25519.Ed25519VerifyPrecompile,
|
||||
precompileed25519.ContractAddress,
|
||||
in,
|
||||
true,
|
||||
)
|
||||
require.False(t, verified(out, err), "a %d-byte input verified", len(in))
|
||||
// And it was still charged: a malformed frame must not be free to repeat.
|
||||
require.NotZero(t, gasUsed,
|
||||
"a %d-byte input was refused without charging gas", len(in))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user