|
| 1 | +# AgentPin Go SDK |
| 2 | + |
| 3 | +[](https://pkg.go.dev/github.com/ThirdKeyAi/agentpin/go) |
| 4 | + |
| 5 | +Go implementation of the AgentPin domain-anchored cryptographic identity |
| 6 | +protocol for AI agents. Wire-compatible with the |
| 7 | +[Rust](../crates/agentpin), [JavaScript](../javascript), and |
| 8 | +[Python](../python) SDKs. |
| 9 | + |
| 10 | +Part of the ThirdKey trust stack: [SchemaPin](https://schemapin.org) → |
| 11 | +**AgentPin** → [Symbiont](https://symbiont.dev). |
| 12 | + |
| 13 | +## Install |
| 14 | + |
| 15 | +```bash |
| 16 | +# Library |
| 17 | +go get github.com/ThirdKeyAi/agentpin/go |
| 18 | + |
| 19 | +# CLI |
| 20 | +go install github.com/ThirdKeyAi/agentpin/go/cmd/agentpin@latest |
| 21 | +``` |
| 22 | + |
| 23 | +Requires Go 1.21+. |
| 24 | + |
| 25 | +## Quick start |
| 26 | + |
| 27 | +```go |
| 28 | +package main |
| 29 | + |
| 30 | +import ( |
| 31 | + "fmt" |
| 32 | + "log" |
| 33 | + |
| 34 | + "github.com/ThirdKeyAi/agentpin/go/pkg/credential" |
| 35 | + "github.com/ThirdKeyAi/agentpin/go/pkg/crypto" |
| 36 | + "github.com/ThirdKeyAi/agentpin/go/pkg/discovery" |
| 37 | + "github.com/ThirdKeyAi/agentpin/go/pkg/jwk" |
| 38 | + "github.com/ThirdKeyAi/agentpin/go/pkg/pinning" |
| 39 | + "github.com/ThirdKeyAi/agentpin/go/pkg/types" |
| 40 | + "github.com/ThirdKeyAi/agentpin/go/pkg/verification" |
| 41 | +) |
| 42 | + |
| 43 | +func main() { |
| 44 | + // 1. Generate a keypair. |
| 45 | + kp, err := crypto.GenerateKeyPair() |
| 46 | + if err != nil { |
| 47 | + log.Fatal(err) |
| 48 | + } |
| 49 | + priv, _ := crypto.LoadPrivateKey(kp.PrivateKeyPEM) |
| 50 | + pub, _ := crypto.LoadPublicKey(kp.PublicKeyPEM) |
| 51 | + |
| 52 | + // 2. Build a discovery document. |
| 53 | + disc := discovery.BuildDiscoveryDocument( |
| 54 | + "example.com", |
| 55 | + types.EntityMaker, |
| 56 | + []types.JWK{jwk.VerifyingKeyToJWK(pub, "example-2026-01")}, |
| 57 | + []types.AgentDeclaration{ |
| 58 | + { |
| 59 | + AgentID: "urn:agentpin:example.com:my-agent", |
| 60 | + Name: "My Agent", |
| 61 | + Capabilities: []types.Capability{"read:data"}, |
| 62 | + Status: types.AgentActive, |
| 63 | + }, |
| 64 | + }, |
| 65 | + 2, // max_delegation_depth |
| 66 | + "2026-01-15T00:00:00Z", |
| 67 | + ) |
| 68 | + |
| 69 | + // 3. Issue a credential. |
| 70 | + cred, err := credential.IssueCredential( |
| 71 | + priv, "example-2026-01", |
| 72 | + "example.com", "urn:agentpin:example.com:my-agent", |
| 73 | + "verifier.com", |
| 74 | + []types.Capability{"read:data"}, |
| 75 | + nil, nil, |
| 76 | + 3600, // ttl_secs |
| 77 | + ) |
| 78 | + if err != nil { |
| 79 | + log.Fatal(err) |
| 80 | + } |
| 81 | + fmt.Println("credential:", cred[:60], "...") |
| 82 | + |
| 83 | + // 4. Verify it offline. |
| 84 | + pinStore := pinning.NewKeyPinStore() |
| 85 | + result := verification.VerifyCredentialOffline( |
| 86 | + cred, &disc, nil, pinStore, |
| 87 | + "verifier.com", |
| 88 | + verification.DefaultVerifierConfig(), |
| 89 | + ) |
| 90 | + if !result.Valid { |
| 91 | + log.Fatalf("verify failed: %s", result.ErrorMessage) |
| 92 | + } |
| 93 | + fmt.Println("verified agent:", result.AgentID) |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +## CLI |
| 98 | + |
| 99 | +The `agentpin` CLI mirrors the Rust binary: |
| 100 | + |
| 101 | +```bash |
| 102 | +agentpin keygen --domain example.com --kid example-2026-01 --output-dir ./keys |
| 103 | +agentpin issue \ |
| 104 | + --private-key ./keys/example-2026-01.private.pem \ |
| 105 | + --kid example-2026-01 \ |
| 106 | + --issuer example.com \ |
| 107 | + --agent-id urn:agentpin:example.com:my-agent \ |
| 108 | + --capabilities read:data,write:report \ |
| 109 | + --ttl 3600 |
| 110 | +agentpin verify \ |
| 111 | + --credential <jwt-string-or-path> \ |
| 112 | + --discovery ./discovery.json \ |
| 113 | + --offline |
| 114 | +agentpin bundle \ |
| 115 | + --discovery ./d1.json --discovery ./d2.json \ |
| 116 | + --output trust-bundle.json |
| 117 | +``` |
| 118 | + |
| 119 | +## Language API reference |
| 120 | + |
| 121 | +| Function (Go) | Rust equivalent | Purpose | |
| 122 | +|---------------------------------------------------------|------------------------------------------------|--------------------------------------| |
| 123 | +| `crypto.GenerateKeyPair` | `agentpin::crypto::generate_key_pair` | New ECDSA P-256 keypair (PEM) | |
| 124 | +| `crypto.SignData` / `VerifySignature` | `sign_data` / `verify_signature` | DER-signature over arbitrary bytes | |
| 125 | +| `crypto.GenerateKeyID` | `generate_key_id` | SHA-256 hex of SPKI DER | |
| 126 | +| `jwk.PEMToJWK` / `JWKToPEM` | `jwk::pem_to_jwk` / `jwk_to_pem` | PEM ↔ JWK conversion | |
| 127 | +| `jwk.JWKThumbprint` | `jwk_thumbprint` | RFC 7638 thumbprint | |
| 128 | +| `jwt.EncodeJWT` / `VerifyJWT` / `DecodeJWTUnverified` | `jwt::encode_jwt` / `verify_jwt` | ES256-only JWT (rejects all else) | |
| 129 | +| `discovery.BuildDiscoveryDocument` | `discovery::build_discovery_document` | Build `.well-known/agent-identity` | |
| 130 | +| `discovery.FetchDiscoveryDocument` | `discovery::fetch_discovery_document` (fetch) | HTTPS fetch (no redirects) | |
| 131 | +| `credential.IssueCredential` | `credential::issue_credential` | Issue agent credential JWT | |
| 132 | +| `verification.VerifyCredentialOffline` | `verification::verify_credential_offline` | 12-step offline verifier | |
| 133 | +| `verification.VerifyCredentialWithResolver` | `verification::verify_credential_with_resolver`| Verify via DiscoveryResolver | |
| 134 | +| `revocation.BuildRevocationDocument` / `CheckRevocation`| `revocation::*` | Build / query revocations | |
| 135 | +| `pinning.KeyPinStore` | `pinning::KeyPinStore` | TOFU key pin store | |
| 136 | +| `delegation.CreateAttestation` / `VerifyAttestation` | `delegation::*` | Maker→deployer attestation chain | |
| 137 | +| `mutual.CreateChallenge` / `VerifyResponse` | `mutual::*` | 128-bit nonce challenge / response | |
| 138 | +| `nonce.InMemoryStore` | `nonce::InMemoryNonceStore` | Replay-protection nonce store | |
| 139 | +| `bundle.NewTrustBundle` | `types::bundle::TrustBundle::new` | Offline trust-bundle builder | |
| 140 | +| `resolver.{WellKnown,LocalFile,TrustBundle,Chain}Resolver` | `resolver::*` | Pluggable discovery resolution | |
| 141 | + |
| 142 | +## Security guarantees |
| 143 | + |
| 144 | +- **ES256 only.** `jwt.DecodeJWTUnverified` rejects every algorithm except |
| 145 | + `ES256` and every typ except `agentpin-credential+jwt` *before* any |
| 146 | + signature work. There is no third-party JWT dependency with permissive |
| 147 | + `alg` defaults — we use `crypto/ecdsa` directly. |
| 148 | +- **Wire compatibility.** Discovery documents, credentials, revocation lists, |
| 149 | + and trust bundles round-trip byte-identically across the Rust, JavaScript, |
| 150 | + Python, and Go SDKs. This is asserted by cross-language interop tests in |
| 151 | + `pkg/verification/cross_language_test.go`. |
| 152 | +- **TOFU key pinning.** Verification fail-closes on key change unless the |
| 153 | + caller explicitly trusts the rotation via `KeyPinStore.AddKey`. |
| 154 | +- **Fail-closed revocation.** When a resolver returns an error fetching a |
| 155 | + revocation document, verification rejects the credential rather than |
| 156 | + proceeding without revocation data. |
| 157 | + |
| 158 | +## Development |
| 159 | + |
| 160 | +```bash |
| 161 | +cd go |
| 162 | +go test ./... # all packages green |
| 163 | +go vet ./... # no findings |
| 164 | +gofmt -l . # must be empty |
| 165 | +``` |
| 166 | + |
| 167 | +To regenerate cross-language test fixtures from the Rust CLI: |
| 168 | + |
| 169 | +```bash |
| 170 | +cargo build -p agentpin-cli --release |
| 171 | +target/release/agentpin keygen \ |
| 172 | + --domain example.com --kid example-2026-01 \ |
| 173 | + --output-dir go/pkg/verification/testdata --format both |
| 174 | +# Then update go/pkg/verification/testdata/discovery.json with the new JWK |
| 175 | +# and reissue go/pkg/verification/testdata/credential.jwt accordingly. |
| 176 | +``` |
| 177 | + |
| 178 | +## License |
| 179 | + |
| 180 | +MIT — see [LICENSE](../LICENSE). |
0 commit comments