From a46be8085f700ce97c6e6de31b1f7fbbdfaef888 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 11 Jun 2026 14:06:53 +0200 Subject: [PATCH] sdk(P1.1): offline receipt verification in Python and TypeScript Closes the trust-model gap where Python/TS could only verify receipts by calling the server (asking the party being distrusted). Both now verify entirely client-side, mirroring the Go SDK's VerifyReceiptOffline one-to-one with identical problem strings so reports are comparable cross-language. - Python: new attesto.verify.verify_receipt + frozen VerifyReport dataclass, using cryptography>=42 (new dependency; not PyNaCl) for Ed25519. - TypeScript: verifyReceipt via WebCrypto subtle.verify({name:"Ed25519"}), throwing a clear AttestoError on runtimes without Ed25519 (Node < 20) rather than silently falling back to the server. Both recompute domain_hash("attesto.v2.receipt", payload) and verify the signature over domain + 0x00 + canonical_json_bytes(payload), reusing the frozen canonical functions. New corpus golden-vectors/sdk-parity/receipts.json (valid + payload/hash/ signature/wrong-key negatives). Proven: all five cases agree across Go, Python, and TypeScript. READMEs document the offline function and note the existing client.verify_receipt as the server-assisted variant. Co-Authored-By: Claude Fable 5 --- proofstream_parity_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/proofstream_parity_test.go b/proofstream_parity_test.go index 9b4f81a..630670a 100644 --- a/proofstream_parity_test.go +++ b/proofstream_parity_test.go @@ -72,6 +72,43 @@ func TestParityRejectPaths(t *testing.T) { } } +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) + } + } + } +} + func TestParityVerifyPayloadCommitment(t *testing.T) { for _, c := range loadParityVectors(t).Accept { commitment, err := PayloadCommitment(c.Payload)