Completes the offline verification stack (P1.2 -> P1.1 -> P1.3) in all three SDKs, each a faithful port of the backend windows.py / checkpoints.py math on top of the frozen canonical/domain-hash primitives: - verify_inclusion_proof: fold a window inclusion proof to the window root (domain attesto.v2.window; left sibling -> node(sibling,current), right -> node(current,sibling)). - verify_checkpoint_root: recompute a checkpoint root from window hashes (domain attesto.v2.checkpoint), with an odd node at any level **promoted unchanged** rather than duplicated/hashed with itself (the place a naive Merkle port silently diverges). - verify_checkpoint_extension: current.from_seq_no == previous.to_seq_no + 1 and current.previous_checkpoint_hash == previous.checkpoint_hash. - verify_completeness: proves no events were omitted in a range -- gap-free seq_no coverage plus prev_event_hash chaining to the previous event_hash. New corpus golden-vectors/sdk-parity/inclusion.json (5-leaf window exercising the promoted odd node, 3-window checkpoint root, extension + completeness negatives), exported from the backend functions. Proven: Python = TypeScript = Go = backend agree on every case. READMEs updated per SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
204 lines
6.2 KiB
Go
204 lines
6.2 KiB
Go
package attesto
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
type parityVectors struct {
|
|
Accept []struct {
|
|
ID string `json:"id"`
|
|
Payload map[string]any `json:"payload"`
|
|
CanonicalPayloadHash string `json:"canonical_payload_hash"`
|
|
} `json:"accept"`
|
|
Reject []struct {
|
|
ID string `json:"id"`
|
|
Payload map[string]any `json:"payload"`
|
|
Path string `json:"path"`
|
|
} `json:"reject"`
|
|
}
|
|
|
|
func loadParityVectors(t *testing.T) parityVectors {
|
|
t.Helper()
|
|
path := filepath.Join("..", "..", "golden-vectors", "sdk-parity", "canonical-numbers.json")
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read parity vectors: %v", err)
|
|
}
|
|
var v parityVectors
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
t.Fatalf("decode parity vectors: %v", err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestParityAcceptCommitmentHashes(t *testing.T) {
|
|
for _, c := range loadParityVectors(t).Accept {
|
|
commitment, err := PayloadCommitment(c.Payload)
|
|
if err != nil {
|
|
t.Fatalf("%s: PayloadCommitment: %v", c.ID, err)
|
|
}
|
|
if commitment["hash_alg"] != "sha256" {
|
|
t.Errorf("%s: hash_alg %q", c.ID, commitment["hash_alg"])
|
|
}
|
|
if commitment["canonical_payload_hash"] != c.CanonicalPayloadHash {
|
|
t.Errorf("%s: hash %s != %s", c.ID, commitment["canonical_payload_hash"], c.CanonicalPayloadHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParityAcceptPassPreflight(t *testing.T) {
|
|
for _, c := range loadParityVectors(t).Accept {
|
|
if err := AssertCommitmentSafeNumbers(c.Payload, "$"); err != nil {
|
|
t.Errorf("%s: unexpected preflight error: %v", c.ID, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParityRejectPaths(t *testing.T) {
|
|
for _, c := range loadParityVectors(t).Reject {
|
|
err := AssertCommitmentSafeNumbers(c.Payload, "$")
|
|
var ue *UnsafeNumberError
|
|
if !errors.As(err, &ue) {
|
|
t.Errorf("%s: expected *UnsafeNumberError, got %v", c.ID, err)
|
|
continue
|
|
}
|
|
if ue.Path != c.Path {
|
|
t.Errorf("%s: path %s != %s", c.ID, ue.Path, c.Path)
|
|
}
|
|
}
|
|
}
|
|
|
|
type receiptParityVectors struct {
|
|
Cases []struct {
|
|
ID string `json:"id"`
|
|
PublicKeyHex string `json:"public_key_hex"`
|
|
Receipt SignedReceipt `json:"receipt"`
|
|
ExpectOK bool `json:"expect_ok"`
|
|
ExpectProblem []string `json:"expect_problems"`
|
|
} `json:"cases"`
|
|
}
|
|
|
|
func TestParityReceiptVerification(t *testing.T) {
|
|
path := filepath.Join("..", "..", "golden-vectors", "sdk-parity", "receipts.json")
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read receipt vectors: %v", err)
|
|
}
|
|
var v receiptParityVectors
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
t.Fatalf("decode receipt vectors: %v", err)
|
|
}
|
|
for _, c := range v.Cases {
|
|
report := VerifyReceiptOffline(c.Receipt, c.PublicKeyHex)
|
|
if report.OK != c.ExpectOK {
|
|
t.Errorf("%s: ok=%v want %v (problems=%v)", c.ID, report.OK, c.ExpectOK, report.Problems)
|
|
}
|
|
if len(report.Problems) != len(c.ExpectProblem) {
|
|
t.Errorf("%s: problems=%v want %v", c.ID, report.Problems, c.ExpectProblem)
|
|
continue
|
|
}
|
|
for i, p := range c.ExpectProblem {
|
|
if report.Problems[i] != p {
|
|
t.Errorf("%s: problem[%d]=%q want %q", c.ID, i, report.Problems[i], p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type inclusionParityVectors struct {
|
|
Inclusion []struct {
|
|
ID string `json:"id"`
|
|
LeafHash string `json:"leaf_hash"`
|
|
Proof []InclusionStep `json:"proof"`
|
|
RootHash string `json:"root_hash"`
|
|
ExpectOK bool `json:"expect_ok"`
|
|
} `json:"inclusion"`
|
|
CheckpointRoot []struct {
|
|
ID string `json:"id"`
|
|
WindowHashes []string `json:"window_hashes"`
|
|
ExpectedRoot string `json:"expected_root"`
|
|
ExpectOK bool `json:"expect_ok"`
|
|
} `json:"checkpoint_root"`
|
|
CheckpointExtension []struct {
|
|
ID string `json:"id"`
|
|
Previous map[string]any `json:"previous"`
|
|
Current map[string]any `json:"current"`
|
|
ExpectOK bool `json:"expect_ok"`
|
|
} `json:"checkpoint_extension"`
|
|
Completeness []struct {
|
|
ID string `json:"id"`
|
|
Events []map[string]any `json:"events"`
|
|
FromSeqNo int `json:"from_seq_no"`
|
|
ToSeqNo int `json:"to_seq_no"`
|
|
ExpectOK bool `json:"expect_ok"`
|
|
} `json:"completeness"`
|
|
}
|
|
|
|
func TestParityInclusionAndCheckpoint(t *testing.T) {
|
|
path := filepath.Join("..", "..", "golden-vectors", "sdk-parity", "inclusion.json")
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read inclusion vectors: %v", err)
|
|
}
|
|
var v inclusionParityVectors
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
t.Fatalf("decode inclusion vectors: %v", err)
|
|
}
|
|
for _, c := range v.Inclusion {
|
|
ok, err := VerifyInclusionProof(c.LeafHash, c.Proof, c.RootHash)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", c.ID, err)
|
|
}
|
|
if ok != c.ExpectOK {
|
|
t.Errorf("inclusion %s: ok=%v want %v", c.ID, ok, c.ExpectOK)
|
|
}
|
|
}
|
|
for _, c := range v.CheckpointRoot {
|
|
ok, err := VerifyCheckpointRoot(c.WindowHashes, c.ExpectedRoot)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", c.ID, err)
|
|
}
|
|
if ok != c.ExpectOK {
|
|
t.Errorf("checkpoint_root %s: ok=%v want %v", c.ID, ok, c.ExpectOK)
|
|
}
|
|
}
|
|
for _, c := range v.CheckpointExtension {
|
|
report := VerifyCheckpointExtension(c.Previous, c.Current)
|
|
if report.OK != c.ExpectOK {
|
|
t.Errorf("extension %s: ok=%v want %v (%v)", c.ID, report.OK, c.ExpectOK, report.Problems)
|
|
}
|
|
}
|
|
for _, c := range v.Completeness {
|
|
report := VerifyCompleteness(c.Events, c.FromSeqNo, c.ToSeqNo)
|
|
if report.OK != c.ExpectOK {
|
|
t.Errorf("completeness %s: ok=%v want %v (%v)", c.ID, report.OK, c.ExpectOK, report.Problems)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParityVerifyPayloadCommitment(t *testing.T) {
|
|
for _, c := range loadParityVectors(t).Accept {
|
|
commitment, err := PayloadCommitment(c.Payload)
|
|
if err != nil {
|
|
t.Fatalf("%s: PayloadCommitment: %v", c.ID, err)
|
|
}
|
|
stored := map[string]any{}
|
|
for k, v := range commitment {
|
|
stored[k] = v
|
|
}
|
|
event := map[string]any{"envelope": map[string]any{"payload_commitment": stored}}
|
|
ok, err := VerifyPayloadCommitment(c.Payload, event)
|
|
if err != nil || !ok {
|
|
t.Errorf("%s: verify match ok=%v err=%v", c.ID, ok, err)
|
|
}
|
|
bad, err := VerifyPayloadCommitment(map[string]any{"tampered": float64(1)}, event)
|
|
if err != nil || bad {
|
|
t.Errorf("%s: tampered verify bad=%v err=%v", c.ID, bad, err)
|
|
}
|
|
}
|
|
}
|