# Attesto Go SDK Official Go SDK for Attesto 2.0 Proofstream. The default API base URL is `https://verify.attesto.eu`. Use it from server-side, infrastructure, security tooling, CI, evidence exporters, and operator automation. Do not embed Attesto API keys in browser bundles, mobile apps, or public artifacts. ## Install ```shell go get git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go ``` The first release is VCS-resolved from the Attesto repository. It intentionally uses only the Go standard library. ## Quickstart ```go package main import ( "context" "fmt" "log" "os" "time" attesto "git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go" ) func main() { ctx := context.Background() client, err := attesto.NewClient(os.Getenv("ATTESTO_API_KEY")) if err != nil { log.Fatal(err) } stream, err := client.CreateStream(ctx, attesto.StreamCreateInput{ UseCase: "ai-governance", PolicyID: "policy-main", }) if err != nil { log.Fatal(err) } receipt, err := client.LogEvent(ctx, stream.StreamID, attesto.EventInput{ SourceRef: "decision-42", OccurredAt: time.Now().UTC().Format(time.RFC3339Nano), Payload: attesto.M{ "model": "risk-classifier", "score": 0.92, }, }) if err != nil { log.Fatal(err) } fmt.Println(receipt.StreamEventID, receipt.EventHash) } ``` Attesto stores source-system time separately from backend ingest time. `OccurredAt` must be RFC3339 with a timezone offset. The Go SDK fills it with `time.Now().UTC()` when omitted, but production integrations should pass the real upstream event timestamp whenever the source system provides one. ## Committed payload number rule When events are committed to a Proofstream, payload and metadata numbers must serialize identically across Python, Go, and JavaScript. Non-integer numbers and integers beyond ±(2^53−1) are rejected at ingestion (HTTP 422); encode decimals and large integers as strings (e.g. `{"score": "0.87"}`). This keeps cross-language commitment recomputation byte-exact (`CanonicalJSON`). The SDK enforces the same rule **locally** before sending, so you see it at dev time rather than as a production 422. `LogEvent` / `LogEvents` return an `*UnsafeNumberError` (with `.Path`, the JSON path to the offending value). Set `RequestOptions{SkipPreflight: true}` to defer to the server. ```go // Commitment a Proofstream stores for a payload, byte-identical to the server // (and to the Python / TypeScript SDKs): commitment, _ := attesto.PayloadCommitment(map[string]any{"decision": "approve", "score_bp": 8700}) // commitment["canonical_payload_hash"] == server's stored hash ok, _ := attesto.VerifyPayloadCommitment(myPayload, event) // recompute and compare ``` ## Verification Remote verification uses Attesto's public `/v2/verify` API. Offline receipt verification uses `ATTESTO-PROOFSTREAM-001` canonical JSON, domain-separated hashes, and Ed25519 signature verification locally. ```go report := attesto.VerifyReceiptOffline(receipt.Receipt, publicKeyHex) if !report.OK { log.Fatalf("receipt failed verification: %v", report.Problems) } ``` The offline trust model extends across the whole proof chain — all client-side: ```go ok, _ := attesto.VerifyInclusionProof(leafHash, proof, windowRoot) // event in a window root ok, _ = attesto.VerifyCheckpointRoot(windowHashes, checkpointRoot) // windows fold to checkpoint root ext := attesto.VerifyCheckpointExtension(previous, current) // one checkpoint continues the previous comp := attesto.VerifyCompleteness(events, 5, 8) // no events omitted in [5, 8] ``` `VerifyCompleteness` proves **no events were omitted** in a range: the sequence numbers must be gap-free and each event's `prev_event_hash` must chain to the previous event's `event_hash`. ## Your SDK is a witness The client remembers the last accepted `(seqNo, eventHash)` per stream and checks every new receipt links forward. If the server ever rewinds a sequence number or presents a divergent lineage, `LogEvent` / `LogEvents` return a `*ForkDetectedError` and the stored head is not advanced. The default store is in-memory; use a file store for fork detection across process invocations, or disable it. ```go // Persist across CLI invocations (atomic, 0600 at ~/.attesto/heads.json): client, _ := attesto.NewClient(apiKey, attesto.WithHeadStore(attesto.NewFileHeadStore(""))) // Disable fork detection: client, _ = attesto.NewClient(apiKey, attesto.WithHeadStore(nil)) ``` ## Verify anchors on-chain `VerifyAnchorOnchain` checks an anchor epoch against the chain itself — one raw JSON-RPC `eth_call` to the anchoring contract's `getCommitment(batchId)` (comparing the on-chain merkle root) plus a transaction-receipt check (status, block). No web3 dependency; the RPC endpoint is yours, so this never asks Attesto to confirm Attesto. ```go anchor, _ := client.GetAnchorEpoch(ctx, "aep_...") report := attesto.VerifyAnchorOnchain(ctx, anchor, "https://polygon-rpc.example", 15*time.Second) if !report.OK { log.Fatalf("anchor failed on-chain verification: %v", report.Problems) } ``` CLI equivalent (fetch + on-chain check in one step): ```bash attesto anchors verify aep_... --rpc-url https://polygon-rpc.example ``` ## Receiving Attesto webhooks ```go func handler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) headers := map[string]string{ "X-Attesto-Timestamp": r.Header.Get("X-Attesto-Timestamp"), "X-Attesto-Signature": r.Header.Get("X-Attesto-Signature"), } if !attesto.VerifyWebhook(body, headers, webhookSecret, 300) { w.WriteHeader(http.StatusUnauthorized) return } process(body) } ``` Verification recomputes `hmac_sha256(secret, "." + body)` from the `X-Attesto-Timestamp` / `X-Attesto-Signature` headers, rejects timestamps more than the allowed skew from now (replay protection), and compares with `hmac.Equal` (constant time). ## Operator and Admin Endpoints System-key clients are created with `attesto.NewClient`. Tenant/operator endpoints, including connector installation and Local Vault installation management, use `attesto.NewBearerClient` with a tenant bearer token obtained from the dashboard session flow. Secrets returned once by connector creation are present only in the returned struct and are never logged by the SDK.