Files
relay/vm/receipt_fuzz_test.go
zeekay 6a37d7fa16 vm: wire receipt signing through profile gate; fix unregistered-node soundness bug
- SignedReceipt now carries an explicit Scheme tag (wire-stable enum
  0x01=ml-dsa-65, 0x02=ed25519) so the verifier dispatches deterministically.
- RegisterNodePublicKey(nodeID, scheme, pub) replaces the Ed25519-only
  signature. Registration accepts both schemes; verification is the
  gate (single call to profile.Verify).
- verifyReceiptSignature: ONE call site, ONE function — profile.Verify
  with the active vm.policy. No more direct ed25519.Verify in the VM.
- vm.Config.LegacyClassicalEnabled wires the chain-level toggle into
  profile.Policy.
- Soundness fix: previously, an unregistered NodeID caused verify to
  "log and accept" — a latent forgery path. It now returns
  errInvalidSignature explicitly.

4 e2e tests cover the PQ-default path, strict-PQ classical refusal,
legacy opt-in, and the unregistered-node refuse. 2 fuzz targets
(FuzzReceiptDecode, FuzzMessageDecode) survive ≥50k execs with no
panics.
2026-05-19 08:36:37 -07:00

55 lines
1.3 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vm
import (
"encoding/json"
"testing"
)
// FuzzReceiptDecode feeds the JSON receipt decoder arbitrary bytes to confirm
// it never panics — the decoder is at the trust boundary between R-Chain RPC
// callers and the receipt registry.
func FuzzReceiptDecode(f *testing.F) {
// Seed with a known-good receipt shape.
good := &SignedReceipt{
Scheme: 0x01,
Timestamp: 1,
Signature: []byte("seed-sig"),
}
if b, err := json.Marshal(good); err == nil {
f.Add(b)
}
// Edge cases.
f.Add([]byte(`{}`))
f.Add([]byte(`null`))
f.Add([]byte(`{"scheme":255,"signature":null}`))
f.Add([]byte(`{"scheme":1,"timestamp":-1}`))
f.Fuzz(func(t *testing.T, data []byte) {
var r SignedReceipt
_ = json.Unmarshal(data, &r) // must not panic
})
}
// FuzzMessageDecode does the same for the cross-chain Message envelope.
func FuzzMessageDecode(f *testing.F) {
good := &Message{
Sequence: 1,
State: MessagePending,
Payload: []byte("x"),
}
if b, err := json.Marshal(good); err == nil {
f.Add(b)
}
f.Add([]byte(`{}`))
f.Add([]byte(`null`))
f.Add([]byte(`{"sequence":-1}`))
f.Fuzz(func(t *testing.T, data []byte) {
var m Message
_ = json.Unmarshal(data, &m) // must not panic
})
}