Files
zeekay 3183dab89c Make the whole module build and test again after the port
go list ./... returned nothing, so no package here could be built or tested and
the failure was invisible: one directory held a Go test beside fifteen .cpp
files, which makes it a Go package the toolchain then refuses. The generator
moves to the golden/ subdirectory the other two C++ chains already use.

That silence was hiding a package that does not compile. host/ calls
EnsureIdentity and identity.go defines LoadIdentity — a rename that happened on
one side — and it has no importers, because the Go host is cmd/luxd now. Deleted
rather than repaired: the thing it was a stand-in for arrived.

The operator tools came across too (backup, blscheck, ceremony, config,
genesishash, keytoaddr, pqkeygen, repair-proposervm, wire) plus benchmarks and
examples, so the package set now differs from the retired repo by exactly one
name: main/ is cmd/luxd.

Two tests were right and are now satisfied rather than silenced. The purity test
looked for ./main; it looks where the daemon lives. The chain-address test found
conformance/dex spelling /v1/chain by hand — but that is its own module on
purpose, since it links an EVM ABI and a signer the node must not carry, so it
cannot call Chain. The rule is about this module, and the walk now says so.
2026-09-07 17:16:26 -07:00

336 lines
9.5 KiB
Go

package main
import (
"crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)
// initCeremony creates the initial ceremony state with powers of the generators.
// This is the "toxic waste" starting point -- before any real randomness is mixed in.
func initCeremony(circuit string, numConstraints, participants int) (*CeremonyState, error) {
// Powers needed = numConstraints + 1 (for Groth16 SRS)
n := numConstraints + 1
_, _, g1, g2 := bn254.Generators()
// tau^i * G1 for i in [0, n)
tauG1 := make([]G1Point, n)
tauG1[0] = G1Point{g1}
for i := 1; i < n; i++ {
tauG1[i] = G1Point{g1} // initially all just G1 generator
}
// tau^i * G2 for i in [0, 2) -- Groth16 only needs tau^0 and tau^1 in G2
tauG2 := make([]G2Point, 2)
tauG2[0] = G2Point{g2}
tauG2[1] = G2Point{g2}
// alpha * tau^i * G1
alphaG1 := make([]G1Point, n)
for i := range alphaG1 {
alphaG1[i] = G1Point{g1}
}
// beta * tau^i * G1
betaG1 := make([]G1Point, n)
for i := range betaG1 {
betaG1[i] = G1Point{g1}
}
// beta * G2
betaG2 := G2Point{g2}
return &CeremonyState{
Circuit: circuit,
NumConstraints: numConstraints,
PowersNeeded: n,
Participants: participants,
TauG1: tauG1,
TauG2: tauG2,
AlphaG1: alphaG1,
BetaG1: betaG1,
BetaG2: betaG2,
}, nil
}
// contribute applies a participant's random contribution to the ceremony state.
// Returns the contribution record. The random scalars are zeroed after use.
func contribute(state *CeremonyState, participant string) (Contribution, error) {
// Generate random scalars from crypto/rand
var tau, alpha, beta fr.Element
if _, err := tau.SetRandom(); err != nil {
return Contribution{}, fmt.Errorf("generate tau: %w", err)
}
if _, err := alpha.SetRandom(); err != nil {
return Contribution{}, fmt.Errorf("generate alpha: %w", err)
}
if _, err := beta.SetRandom(); err != nil {
return Contribution{}, fmt.Errorf("generate beta: %w", err)
}
// Commitment hash: SHA-256(tau || alpha || beta) before they're used
commitHash := commitmentHash(&tau, &alpha, &beta)
var tauBI, alphaBI, betaBI big.Int
tau.BigInt(&tauBI)
alpha.BigInt(&alphaBI)
beta.BigInt(&betaBI)
// Update tauG1: tauG1[i] *= tau^i
// We accumulate tau powers: tau^0=1, tau^1, tau^2, ...
var tauPow big.Int
tauPow.SetInt64(1)
for i := range state.TauG1 {
state.TauG1[i].ScalarMultiplication(&state.TauG1[i].G1Affine, &tauPow)
if i < len(state.TauG1)-1 {
tauPow.Mul(&tauPow, &tauBI)
tauPow.Mod(&tauPow, fr.Modulus())
}
}
// Update tauG2: tauG2[i] *= tau^i
tauPow.SetInt64(1)
for i := range state.TauG2 {
state.TauG2[i].ScalarMultiplication(&state.TauG2[i].G2Affine, &tauPow)
if i < len(state.TauG2)-1 {
tauPow.Mul(&tauPow, &tauBI)
tauPow.Mod(&tauPow, fr.Modulus())
}
}
// Update alphaG1: alphaG1[i] *= alpha * tau^i
var scale big.Int
tauPow.SetInt64(1)
for i := range state.AlphaG1 {
scale.Mul(&alphaBI, &tauPow)
scale.Mod(&scale, fr.Modulus())
state.AlphaG1[i].ScalarMultiplication(&state.AlphaG1[i].G1Affine, &scale)
if i < len(state.AlphaG1)-1 {
tauPow.Mul(&tauPow, &tauBI)
tauPow.Mod(&tauPow, fr.Modulus())
}
}
// Update betaG1: betaG1[i] *= beta * tau^i
tauPow.SetInt64(1)
for i := range state.BetaG1 {
scale.Mul(&betaBI, &tauPow)
scale.Mod(&scale, fr.Modulus())
state.BetaG1[i].ScalarMultiplication(&state.BetaG1[i].G1Affine, &scale)
if i < len(state.BetaG1)-1 {
tauPow.Mul(&tauPow, &tauBI)
tauPow.Mod(&tauPow, fr.Modulus())
}
}
// Update betaG2: betaG2 *= beta
state.BetaG2.ScalarMultiplication(&state.BetaG2.G2Affine, &betaBI)
// Zero all secret scalars: fr.Elements, big.Ints, and intermediates
tau.SetZero()
alpha.SetZero()
beta.SetZero()
zeroBI(&tauBI)
zeroBI(&alphaBI)
zeroBI(&betaBI)
zeroBI(&tauPow)
zeroBI(&scale)
// Overwrite stack memory with random bytes
var garbage [32]byte
rand.Read(garbage[:])
// Compute hash chain fields
prevHash := ""
if len(state.Contributions) > 0 {
prevHash = state.Contributions[len(state.Contributions)-1].StateHash
}
stateHash := computeStateHash(state)
return newContribution(participant, commitHash, prevHash, stateHash), nil
}
// verifyCeremony checks the consistency of the ceremony state.
// It verifies that tauG1 and tauG2 form consistent geometric sequences
// using pairing checks: e(tauG1[i], tauG2[0]) == e(tauG1[i-1], tauG2[1])
func verifyCeremony(state *CeremonyState) error {
if len(state.TauG1) < 2 {
return fmt.Errorf("need at least 2 powers, got %d", len(state.TauG1))
}
if len(state.TauG2) < 2 {
return fmt.Errorf("need 2 G2 powers, got %d", len(state.TauG2))
}
if len(state.Contributions) == 0 {
return fmt.Errorf("no contributions recorded")
}
// Check tauG1 forms a geometric sequence with ratio matching tauG2.
// We want tauG1[i+1]/tauG1[i] == tauG2[1]/tauG2[0] == tau.
// sameRatio(n1, d1, n2, d2) checks n1/d1 == n2/d2 via e(n1,d2)*e(-d1,n2)==1.
for i := 0; i < len(state.TauG1)-1; i++ {
if !sameRatio(
state.TauG1[i+1].G1Affine, state.TauG1[i].G1Affine,
state.TauG2[1].G2Affine, state.TauG2[0].G2Affine,
) {
return fmt.Errorf("tauG1 consistency check failed at index %d", i)
}
}
// Check alphaG1 has same tau ratio between consecutive elements.
for i := 0; i < len(state.AlphaG1)-1; i++ {
if !sameRatio(
state.AlphaG1[i+1].G1Affine, state.AlphaG1[i].G1Affine,
state.TauG2[1].G2Affine, state.TauG2[0].G2Affine,
) {
return fmt.Errorf("alphaG1 consistency check failed at index %d", i)
}
}
// Check betaG1 has same tau ratio between consecutive elements.
for i := 0; i < len(state.BetaG1)-1; i++ {
if !sameRatio(
state.BetaG1[i+1].G1Affine, state.BetaG1[i].G1Affine,
state.TauG2[1].G2Affine, state.TauG2[0].G2Affine,
) {
return fmt.Errorf("betaG1 consistency check failed at index %d", i)
}
}
// Cross-consistency: verify alphaG1 uses the same tau ratio as tauG1/tauG2
// alphaG1[1]/alphaG1[0] must equal tauG2[1]/tauG2[0] (same tau)
if len(state.AlphaG1) >= 2 {
if !sameRatio(
state.AlphaG1[1].G1Affine, state.AlphaG1[0].G1Affine,
state.TauG2[1].G2Affine, state.TauG2[0].G2Affine,
) {
return fmt.Errorf("alpha-tau cross-consistency check failed")
}
}
// Cross-consistency: verify betaG1[0] and betaG2 encode the same beta scalar
// betaG1[0]/G1Gen must equal betaG2/G2Gen
var g1Gen bn254.G1Affine
var g2Gen bn254.G2Affine
_, _, g1Gen, g2Gen = bn254.Generators()
if !sameRatio(
state.BetaG1[0].G1Affine, g1Gen,
state.BetaG2.G2Affine, g2Gen,
) {
return fmt.Errorf("betaG1/betaG2 cross-consistency check failed")
}
// Check none of the key elements are the point at infinity
if state.TauG1[0].IsInfinity() || state.TauG2[0].IsInfinity() {
return fmt.Errorf("generator points are at infinity")
}
if state.BetaG2.IsInfinity() {
return fmt.Errorf("betaG2 is at infinity")
}
// Verify contribution hash chain
for i, c := range state.Contributions {
if i == 0 {
if c.PrevHash != "" {
return fmt.Errorf("first contribution has non-empty PrevHash")
}
} else {
if c.PrevHash != state.Contributions[i-1].StateHash {
return fmt.Errorf("contribution %d: PrevHash does not match previous StateHash", i)
}
}
if c.StateHash == "" {
return fmt.Errorf("contribution %d: empty StateHash", i)
}
}
// Verify final StateHash matches current SRS state
finalStateHash := computeStateHash(state)
lastContrib := state.Contributions[len(state.Contributions)-1]
if lastContrib.StateHash != finalStateHash {
return fmt.Errorf("final contribution StateHash does not match current SRS state")
}
return nil
}
// exportSRS writes the binary SRS (tauG1, tauG2, alphaG1, betaG1, betaG2)
// in uncompressed form for use by the Groth16 prover/verifier.
func exportSRS(state *CeremonyState) []byte {
// Format: [4 bytes n][tauG1...][tauG2...][alphaG1...][betaG1...][betaG2]
// All points in uncompressed form (G1: 64 bytes, G2: 128 bytes)
n := len(state.TauG1)
// Header: 4 bytes for n
// tauG1: n * 64
// tauG2: 2 * 128
// alphaG1: n * 64
// betaG1: n * 64
// betaG2: 1 * 128
size := 4 + n*64 + 2*128 + n*64 + n*64 + 128
buf := make([]byte, 0, size)
// Write n as 4-byte big-endian
buf = append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
for i := range state.TauG1 {
buf = append(buf, state.TauG1[i].Marshal()...)
}
for i := range state.TauG2 {
buf = append(buf, state.TauG2[i].Marshal()...)
}
for i := range state.AlphaG1 {
buf = append(buf, state.AlphaG1[i].Marshal()...)
}
for i := range state.BetaG1 {
buf = append(buf, state.BetaG1[i].Marshal()...)
}
buf = append(buf, state.BetaG2.Marshal()...)
return buf
}
// sameRatio checks n1/d1 == n2/d2 via pairing: e(n1, d2) == e(d1, n2)
// Specifically: e(n1, d2) * e(-d1, n2) == 1
func sameRatio(n1, d1 bn254.G1Affine, n2, d2 bn254.G2Affine) bool {
var negD1 bn254.G1Affine
negD1.Neg(&d1)
ok, err := bn254.PairingCheck(
[]bn254.G1Affine{n1, negD1},
[]bn254.G2Affine{d2, n2},
)
if err != nil {
return false
}
return ok
}
// zeroBI overwrites a big.Int's internal limbs with zeros.
func zeroBI(x *big.Int) {
if bits := x.Bits(); bits != nil {
for i := range bits {
bits[i] = 0
}
}
x.SetInt64(0)
}
// commitmentHash returns SHA-256(tau.Bytes() || alpha.Bytes() || beta.Bytes())
func commitmentHash(tau, alpha, beta *fr.Element) []byte {
h := sha256.New()
var buf [32]byte
tb := tau.Bytes()
copy(buf[:], tb[:])
h.Write(buf[:])
ab := alpha.Bytes()
copy(buf[:], ab[:])
h.Write(buf[:])
bb := beta.Bytes()
copy(buf[:], bb[:])
h.Write(buf[:])
return h.Sum(nil)
}