diff --git a/cmd/attesto-verify-wasm/main.go b/cmd/attesto-verify-wasm/main.go new file mode 100644 index 0000000..c55bd74 --- /dev/null +++ b/cmd/attesto-verify-wasm/main.go @@ -0,0 +1,97 @@ +//go:build js && wasm + +// attesto-verify-wasm [P3.1] — the verifier-only WebAssembly build. +// +// Exposes the offline verification functions (and nothing else: no client, +// no CLI, no network capability is ever invoked) on a global +// `attestoVerify` object for the docs-site /verify drop-zone. Every +// function takes JSON strings and returns a JSON string, so the JS side +// stays a thin shell. +package main + +import ( + "encoding/json" + "syscall/js" + + attesto "go.attesto.eu/sdk" +) + +func respond(value any) string { + raw, err := json.Marshal(value) + if err != nil { + return `{"ok":false,"problems":["internal: response marshal failed"]}` + } + return string(raw) +} + +func fail(problem string) string { + return respond(map[string]any{"ok": false, "problems": []string{problem}}) +} + +// verifyReceipt(receiptJSON, publicKeyHex) -> VerifyReport JSON +func verifyReceipt(_ js.Value, args []js.Value) any { + if len(args) != 2 { + return fail("usage: verifyReceipt(receiptJSON, publicKeyHex)") + } + var receipt attesto.SignedReceipt + if err := json.Unmarshal([]byte(args[0].String()), &receipt); err != nil { + return fail("receipt is not valid JSON: " + err.Error()) + } + return respond(attesto.VerifyReceiptOffline(receipt, args[1].String())) +} + +// verifyInclusion(leafHash, proofJSON, rootHash) -> {ok, problems} +func verifyInclusion(_ js.Value, args []js.Value) any { + if len(args) != 3 { + return fail("usage: verifyInclusion(leafHash, proofJSON, rootHash)") + } + var proof []attesto.InclusionStep + if err := json.Unmarshal([]byte(args[1].String()), &proof); err != nil { + return fail("proof is not valid JSON: " + err.Error()) + } + ok, err := attesto.VerifyInclusionProof(args[0].String(), proof, args[2].String()) + if err != nil { + return fail(err.Error()) + } + return respond(map[string]any{"ok": ok, "problems": []string{}}) +} + +// verifyCheckpointRoot(windowHashesJSON, expectedRoot) -> {ok, problems} +func verifyCheckpointRoot(_ js.Value, args []js.Value) any { + if len(args) != 2 { + return fail("usage: verifyCheckpointRoot(windowHashesJSON, expectedRoot)") + } + var hashes []string + if err := json.Unmarshal([]byte(args[0].String()), &hashes); err != nil { + return fail("windowHashes is not valid JSON: " + err.Error()) + } + ok, err := attesto.VerifyCheckpointRoot(hashes, args[1].String()) + if err != nil { + return fail(err.Error()) + } + return respond(map[string]any{"ok": ok, "problems": []string{}}) +} + +// verifyCompleteness(eventsJSON, fromSeqNo, toSeqNo) -> VerifyReport JSON +func verifyCompleteness(_ js.Value, args []js.Value) any { + if len(args) != 3 { + return fail("usage: verifyCompleteness(eventsJSON, fromSeqNo, toSeqNo)") + } + var events []map[string]any + if err := json.Unmarshal([]byte(args[0].String()), &events); err != nil { + return fail("events is not valid JSON: " + err.Error()) + } + return respond(attesto.VerifyCompleteness(events, args[1].Int(), args[2].Int())) +} + +func main() { + exports := js.Global().Get("Object").New() + exports.Set("verifyReceipt", js.FuncOf(verifyReceipt)) + exports.Set("verifyInclusion", js.FuncOf(verifyInclusion)) + exports.Set("verifyCheckpointRoot", js.FuncOf(verifyCheckpointRoot)) + exports.Set("verifyCompleteness", js.FuncOf(verifyCompleteness)) + exports.Set("sdkVersion", attesto.SDKVersion) + js.Global().Set("attestoVerify", exports) + // Keep the runtime alive for calls from JS. + select {} +}