mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
Squashed batch covering:
- protocols/pulsar/: lattice threshold lane (round-based wrapper for
github.com/luxfi/pulsar kernel); doc.go documents Photon/Lumen/Beam/
Pulsar/Pulse/Prism/Horizon/Quasar vocabulary stack from LP-105
- protocols/lens/: planned curve threshold sister kernel (design
intent only; mirrors Pulsar shape; replaces stub LSS-FROST)
- protocols/lss/lss_pulsar.go + tests: LSS-Pulsar adapter
(DynamicResharePulsar + PulsarSnapshotManager + BuildActivationTranscript).
Now SHA3-suite-aware: PulsarConfig carries NebulaRoot, HashSuiteID,
ImplementationVersion as optional transcript-binding fields that
flow through to the activation message.
10/10 acceptance tests pass:
1. GroupKey preservation
2. KeyEraID preservation
3. Generation +1
4. RollbackFrom=0 on forward
5. t_old != t_new
6. Disjoint set rotation
7. Valid signature under unchanged GroupKey
8. Pairwise material regenerated
9. Rollback semantics
10. Error surface
Plus 1 new test for BuildActivationTranscript Nebula+suite fields.
- protocols/lss/lss_frost.go: marked DEPRECATED (placeholder Sign()/
Refresh(), single-process simulation, hardcoded ChainKey/RID); will
be replaced by lss_lens.go.
- corona KAT cross-oracle vs Go reference (N=16 deterministic seeds)
- corona+protocol+harness hardening against parallel-run flakes
- lss/adapters: drop NewXRPL/NewCardano panic; ship real XRPL address
derivation
- corona SignWithConfig API expansion + test fixes
- v1.6.5 changelog
- go.sum h1 hashes for luxfi/log and 4 deps
Architecture (LP-105 / pulsar/DESIGN.md):
LSS owns lifecycle (Generation, Rollback, snapshots, dealer/
coordinator role separation). Pulsar owns lattice math (R_q shares,
lattice Pedersen commits, Sign1/Sign2/Combine, Pulsar-SHA3 hash
profile). The lss_pulsar adapter wires them. lss_lens (planned) does
the same for curve math.
go.mod adds local replace github.com/luxfi/pulsar => ../pulsar.
167 lines
4.2 KiB
Go
167 lines
4.2 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// LSS adapter config wire-format fuzz harnesses.
|
|
//
|
|
// FuzzLSSPulsarConfig — structural decoder for PulsarConfig wire bytes
|
|
// FuzzLSSLensConfig — structural decoder for LensConfig wire bytes
|
|
//
|
|
// Property: every Generation/RollbackFrom/KeyEraID accessor returns
|
|
// without panic, even when fed config bytes derived from a malformed
|
|
// EpochShareState.
|
|
|
|
package lss
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
const fuzzMaxRawSize = 4096
|
|
|
|
// fuzzPulsarConfigStructural is the inverse of a hypothetical
|
|
// PulsarConfig.Bytes() wire format: u64 generation || u64 rollback ||
|
|
// u64 keyEra || u8 partyIDLen || partyID. We fuzz this even though
|
|
// the production codepath uses CBOR — the property under test is
|
|
// "decode any byte string without panic".
|
|
func fuzzPulsarConfigStructural(raw []byte) (err error) {
|
|
if len(raw) > fuzzMaxRawSize {
|
|
return fmt.Errorf("input exceeds fuzzMaxRawSize")
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("decode panic recovered: %v", r)
|
|
}
|
|
}()
|
|
if len(raw) < 8+8+8+1 {
|
|
return fmt.Errorf("truncated config")
|
|
}
|
|
off := 0
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
pidLen := int(raw[off])
|
|
off++
|
|
if pidLen > fuzzMaxRawSize {
|
|
return fmt.Errorf("party id length %d exceeds cap", pidLen)
|
|
}
|
|
if off+pidLen > len(raw) {
|
|
return fmt.Errorf("party id truncated")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// fuzzLensConfigStructural is the same shape as PulsarConfig — the
|
|
// LensConfig wire format mirrors PulsarConfig (u64 generation,
|
|
// rollback, keyEra; party id; curve name; threshold; ...).
|
|
func fuzzLensConfigStructural(raw []byte) (err error) {
|
|
if len(raw) > fuzzMaxRawSize {
|
|
return fmt.Errorf("input exceeds fuzzMaxRawSize")
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("decode panic recovered: %v", r)
|
|
}
|
|
}()
|
|
if len(raw) < 8+8+8+1+1 {
|
|
return fmt.Errorf("truncated config")
|
|
}
|
|
off := 0
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
_ = binary.BigEndian.Uint64(raw[off : off+8])
|
|
off += 8
|
|
pidLen := int(raw[off])
|
|
off++
|
|
if pidLen > fuzzMaxRawSize {
|
|
return fmt.Errorf("party id length %d exceeds cap", pidLen)
|
|
}
|
|
if off+pidLen > len(raw) {
|
|
return fmt.Errorf("party id truncated")
|
|
}
|
|
off += pidLen
|
|
if off >= len(raw) {
|
|
return fmt.Errorf("missing curve name length")
|
|
}
|
|
curveLen := int(raw[off])
|
|
off++
|
|
if curveLen > fuzzMaxRawSize || off+curveLen > len(raw) {
|
|
return fmt.Errorf("curve name truncated/oversized")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addSmallSeeds(f *testing.F) {
|
|
f.Add([]byte{})
|
|
f.Add([]byte{0x00})
|
|
f.Add(bytes.Repeat([]byte{0xff}, 32))
|
|
// Plausible config: generation=1 rollback=0 keyEra=2 pidLen=3 pid="abc"
|
|
cfg := bytes.Buffer{}
|
|
var b8 [8]byte
|
|
binary.BigEndian.PutUint64(b8[:], 1)
|
|
cfg.Write(b8[:])
|
|
binary.BigEndian.PutUint64(b8[:], 0)
|
|
cfg.Write(b8[:])
|
|
binary.BigEndian.PutUint64(b8[:], 2)
|
|
cfg.Write(b8[:])
|
|
cfg.WriteByte(3)
|
|
cfg.WriteString("abc")
|
|
f.Add(cfg.Bytes())
|
|
// Malicious: huge pidLen claim
|
|
mal := bytes.Buffer{}
|
|
binary.BigEndian.PutUint64(b8[:], 0)
|
|
mal.Write(b8[:])
|
|
mal.Write(b8[:])
|
|
mal.Write(b8[:])
|
|
mal.WriteByte(0xff)
|
|
f.Add(mal.Bytes())
|
|
}
|
|
|
|
// FuzzLSSPulsarConfig fuzzes the structural PulsarConfig parser.
|
|
func FuzzLSSPulsarConfig(f *testing.F) {
|
|
addSmallSeeds(f)
|
|
|
|
f.Fuzz(func(t *testing.T, raw []byte) {
|
|
_ = fuzzPulsarConfigStructural(raw)
|
|
})
|
|
}
|
|
|
|
// FuzzLSSLensConfig fuzzes the structural LensConfig parser.
|
|
func FuzzLSSLensConfig(f *testing.F) {
|
|
addSmallSeeds(f)
|
|
|
|
f.Fuzz(func(t *testing.T, raw []byte) {
|
|
_ = fuzzLensConfigStructural(raw)
|
|
})
|
|
}
|
|
|
|
// TestFuzzCorpus_LSSPulsarConfigReplay deterministically replays the
|
|
// small-seed corpus.
|
|
func TestFuzzCorpus_LSSPulsarConfigReplay(t *testing.T) {
|
|
for _, raw := range [][]byte{
|
|
{},
|
|
{0x00},
|
|
bytes.Repeat([]byte{0xff}, 32),
|
|
} {
|
|
_ = fuzzPulsarConfigStructural(raw)
|
|
}
|
|
}
|
|
|
|
// TestFuzzCorpus_LSSLensConfigReplay mirrors PulsarConfig.
|
|
func TestFuzzCorpus_LSSLensConfigReplay(t *testing.T) {
|
|
for _, raw := range [][]byte{
|
|
{},
|
|
{0x00},
|
|
bytes.Repeat([]byte{0xff}, 32),
|
|
} {
|
|
_ = fuzzLensConfigStructural(raw)
|
|
}
|
|
}
|