//go:build live package stm import ( "context" "encoding/json" "io" "net/http" "testing" "time" ) // Fetches the head preprod cert, pulls out the (msg, vk_i, sigma_i) triple, // and tries to BLS-verify ONE signer's sigma against signed_message.as_bytes(). // // Expectation: this FAILS for a single signer because Mithril's STM layer // aggregates sigs/vks before BLS-verifying, so an individual (sigma_i, vk_i) // pair isn't a valid BLS signature of signed_message on its own. // // What we learn from this test: // // - If a single-signer verify passes: BLS works as expected (unlikely). // - If it fails with "signature invalid" but no decode errors: our BLS impl // is correct and the failure is a real property of STM (curve points // decoded fine, pairing ran, result doesn't match — exactly what the // aggregation layer fixes). // - If it fails with a decode error: something in the wire format is off. // // This test documents expected behavior rather than asserting pass. func TestBlsVerify_SingleSignerAgainstSignedMessage(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, http.MethodGet, preprodHead+"/artifact/cardano-database", nil) resp, err := http.DefaultClient.Do(req) if err != nil { t.Skipf("network: %v", err) } defer resp.Body.Close() var snaps []struct { CertificateHash string `json:"certificate_hash"` } if err := json.NewDecoder(resp.Body).Decode(&snaps); err != nil { t.Fatal(err) } req, _ = http.NewRequestWithContext(ctx, http.MethodGet, preprodHead+"/certificate/"+snaps[0].CertificateHash, nil) resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var cert struct { MultiSignature json.RawMessage `json:"multi_signature"` SignedMessage string `json:"signed_message"` AggregateVerificationKey json.RawMessage `json:"aggregate_verification_key"` } if err := json.Unmarshal(body, &cert); err != nil { t.Fatal(err) } ms, err := DecodeMultiSig(cert.MultiSignature) if err != nil { t.Fatal(err) } if len(ms.Signatures) == 0 { t.Fatal("no signers") } // Extract the AVK's mt_commitment.root — the BLS-verified message is // msg || root, where msg is the ASCII bytes of signed_message hex. avk, err := DecodeAVK(cert.AggregateVerificationKey) if err != nil { t.Fatalf("avk decode: %v", err) } t.Logf("avk mt_root=%x total_stake=%d nr_leaves=%d", avk.MerkleRoot, avk.TotalStake, avk.NumLeaves) msg := append([]byte(cert.SignedMessage), avk.MerkleRoot...) s0 := ms.Signatures[0] err = BlsVerify(msg, s0.Sig.Sigma, s0.RegParty.VK) t.Logf("single-signer[0] BLS verify over signed_message: err=%v", err) // Don't assert pass/fail here — documenting behavior. // Aggregate all signers and verify — one entry per signer per lottery // win (a signer with N wins contributes N copies of the same sigma/vk). var sigs, vks [][]byte for _, s := range ms.Signatures { for range s.Sig.Indexes { sigs = append(sigs, s.Sig.Sigma) vks = append(vks, s.RegParty.VK) } } t.Logf("aggregating %d (signer × win) pairs", len(sigs)) if err := BlsAggregateVerify(msg, sigs, vks); err != nil { t.Logf("aggregate verify (with multiplicity = lottery wins): %v", err) } else { t.Logf("aggregate verify (with multiplicity = lottery wins): PASS ✓") } // Alternate: one entry per unique signer (no multiplicity) sigs, vks = nil, nil for _, s := range ms.Signatures { sigs = append(sigs, s.Sig.Sigma) vks = append(vks, s.RegParty.VK) } if err := BlsAggregateVerify(msg, sigs, vks); err != nil { t.Logf("aggregate verify (unique signers only): %v", err) } else { t.Logf("aggregate verify (unique signers only): PASS ✓") } }