sdk/go/cmd/attesto-verify-wasm compiles the offline verification functions (receipt, inclusion, checkpoint root, completeness) — and nothing else — to WebAssembly, exported on a global attestoVerify object. scripts/build_wasm_verifier.sh prefers TinyGo and falls back to Go stdlib (current build: stdlib, 5.9 MB; the <4 MB target applies when TinyGo is in the toolchain). docs-site /verify is a drag-drop page that verifies receipts entirely in the browser against a user-pinned witness key. Verified, both wired into CI as a new wasm-verifier job: - scripts/wasm_verifier_smoke.mjs loads the wasm in Node with no network and reproduces all 19 sdk-parity corpus cases (receipts + inclusion + checkpoint-root + completeness) — the same corpus gating the three SDKs; - the smoke also asserts the /verify page is zero-network: its only fetch is the same-origin wasm asset and no script references an absolute URL. wasm + page hashed into the release manifest; docs-hub contract green (shared chrome + content rules). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
98 lines
3.3 KiB
Go
98 lines
3.3 KiB
Go
//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 {}
|
|
}
|