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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user