Skip to content

Commit c97c0f4

Browse files
committed
Add Go SDK (P1.1) — v0.2.0 surface parity
Brings AgentPin to four-language parity (Rust, JavaScript, Python, Go). The Go SDK lives at github.com/ThirdKeyAi/agentpin/go and mirrors the v0.2.0 stable surface of the Rust crate. v0.3.0-alpha.1 features (A2A AgentCard types, DNS TXT cross-verification) are intentionally NOT included — they will follow in a separate alpha PR after the Rust PRs land. Package layout (mirrors SchemaPin's Go SDK conventions): go/pkg/{crypto,jwk,jwt,types,discovery,credential,verification, revocation,pinning,delegation,mutual,nonce,bundle,resolver}/ go/cmd/agentpin/ keygen | issue | verify | bundle subcommands go/internal/version/version.go declares 0.2.0 (matches Rust/JS/Python) Security guarantees: * ES256 only. The JWT verifier rejects every algorithm except ES256 and every typ except agentpin-credential+jwt before any signature work happens. crypto/ecdsa is used directly — no third-party JWT dependency with permissive alg defaults. Algorithm-rejection tests cover none, HS256, RS256, ES384, and empty alg. * Wire format compatibility. JWK thumbprint (RFC 7638), discovery documents, credentials, revocation lists, and trust bundles all round-trip byte-identically with the Rust SDK. The cross-language interop tests under go/pkg/verification/cross_language_test.go load Rust-generated PEM / JWK / discovery / JWT fixtures and prove the Go SDK can verify them; the test suite also includes a Go-issued credential cycle that re-verifies the bidirectional path. * 12-step verification flow preserved verbatim from the Rust crate. The package doc comment in go/pkg/verification/verification.go enumerates all twelve steps and notes that any drift here must be mirrored in Rust / JS / Python. Tests: 106 Go tests pass, all packages green, go vet clean, gofmt -l empty. Rust workspace (135 tests) still passes. End-to-end CLI test confirmed bidirectional interop: Rust CLI verifies a Go-issued credential and vice versa. CI: * New .github/workflows/go.yml runs gofmt / go vet / go test on every PR touching go/** across Go 1.21 and 1.22. * The version-consistency check in .github/workflows/release.yml is extended to also validate go/internal/version/version.go. Documentation: * go/README.md — install, quickstart, API reference table mapping Go symbols to the Rust equivalents, security guarantees, and a manual fixture-regeneration recipe. * Top-level README.md — Go added to SDK list and project structure. * SKILL.md — Go quickstart section in the same shape as the existing JS/Python sections; language API reference table extended. * context7.json — description and folders updated; *.go added to excludeFiles. * CHANGELOG.md — Unreleased section documenting the new SDK above the existing 0.2.0 entry. No existing entries changed.
1 parent eeab269 commit c97c0f4

66 files changed

Lines changed: 4871 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/go.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Go CI
2+
on:
3+
push:
4+
branches: [main, dev]
5+
paths: ['go/**', '.github/workflows/go.yml']
6+
pull_request:
7+
paths: ['go/**', '.github/workflows/go.yml']
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
go-version: ['1.21', '1.22']
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-go@v5
18+
with:
19+
go-version: ${{ matrix.go-version }}
20+
- name: gofmt
21+
working-directory: go
22+
run: |
23+
out=$(gofmt -l .)
24+
if [ -n "$out" ]; then
25+
echo "::error::gofmt would reformat the following files:"
26+
echo "$out"
27+
exit 1
28+
fi
29+
- name: go vet
30+
working-directory: go
31+
run: go vet ./...
32+
- name: go test
33+
working-directory: go
34+
run: go test ./...

.github/workflows/release.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ jobs:
1414
RUST_VER=$(grep '^version' crates/agentpin/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
1515
PY_VER=$(grep 'version' python/pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
1616
JS_VER=$(node -e "console.log(require('./javascript/package.json').version)")
17+
GO_VER=$(grep -E '^const Version' go/internal/version/version.go | sed 's/.*"\(.*\)".*/\1/')
1718
echo "Rust: $RUST_VER"
1819
echo "Python: $PY_VER"
1920
echo "JavaScript: $JS_VER"
20-
if [ "$RUST_VER" != "$PY_VER" ] || [ "$RUST_VER" != "$JS_VER" ]; then
21-
echo "::error::Version mismatch! Rust=$RUST_VER Python=$PY_VER JavaScript=$JS_VER"
21+
echo "Go: $GO_VER"
22+
if [ "$RUST_VER" != "$PY_VER" ] || [ "$RUST_VER" != "$JS_VER" ] || [ "$RUST_VER" != "$GO_VER" ]; then
23+
echo "::error::Version mismatch! Rust=$RUST_VER Python=$PY_VER JavaScript=$JS_VER Go=$GO_VER"
2224
exit 1
2325
fi
2426
echo "All versions match: $RUST_VER"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Cargo.lock
55
# Private keys — NEVER commit these
66
*.private.pem
77
*.private.jwk.json
8+
# Exception: cross-language interop test fixtures under testdata/ ARE
9+
# deliberately committed throwaway keypairs used only for SDK interop tests.
10+
# They are NOT used to sign anything in production.
11+
!go/pkg/verification/testdata/*.private.pem
812

913
# IDE
1014
.idea/

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to the AgentPin project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
#### Go SDK
13+
- **New `go/` SDK** — fourth language port, wire-compatible with Rust, JavaScript, and Python at the v0.2.0 surface. Mirrors the package layout of the SchemaPin Go SDK.
14+
- **Module path**: `github.com/ThirdKeyAi/agentpin/go`
15+
- **Packages**: `crypto`, `jwk`, `jwt`, `types`, `discovery`, `credential`, `verification`, `revocation`, `pinning`, `delegation`, `mutual`, `nonce`, `bundle`, `resolver`
16+
- **CLI**: `cmd/agentpin` with `keygen`, `issue`, `verify`, `bundle` subcommands matching the Rust binary
17+
- **ES256-only** enforcement is implemented inline using `crypto/ecdsa`. The JWT verifier rejects `none`, `HS256`, `RS256`, `ES384`, and any other algorithm before any signature work. No third-party JWT dependency.
18+
- **Cross-language interop tests** under `go/pkg/verification/cross_language_test.go` validate that Rust-generated PEM keypairs, JWKs, discovery documents, and JWTs round-trip correctly through the Go SDK.
19+
- **CI**: new `.github/workflows/go.yml` runs `go test`, `go vet`, and `gofmt -l` on every PR touching `go/**`. Version-consistency check extended to also validate the Go SDK's declared version.
20+
821
## [0.2.0] - 2026-02-12
922

1023
### Added

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ AgentPin lets organizations publish verifiable identity for their AI agents. Iss
1616
- **Credential revocation** at credential, agent, and key level
1717
- **Mutual authentication** with challenge-response
1818
- **Trust bundles** for air-gapped and enterprise environments
19-
- **Cross-language** — Rust, JavaScript, and Python SDKs produce interoperable credentials
19+
- **Cross-language** — Rust, JavaScript, Python, and Go SDKs produce interoperable credentials
2020

2121
## Quick Start
2222

@@ -58,6 +58,13 @@ npm install agentpin
5858
pip install agentpin
5959
```
6060

61+
### Go
62+
63+
```bash
64+
go install github.com/ThirdKeyAi/agentpin/go/cmd/agentpin@latest
65+
go get github.com/ThirdKeyAi/agentpin/go
66+
```
67+
6168
## Documentation
6269

6370
| Topic | Link |
@@ -80,6 +87,7 @@ crates/
8087
└── agentpin-server/ # HTTP server for .well-known endpoints
8188
javascript/ # JavaScript/Node.js SDK
8289
python/ # Python SDK
90+
go/ # Go SDK
8391
```
8492

8593
## License

SKILL.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,40 @@ pin_store = KeyPinStore()
157157
result = verify_credential(credential, discovery_doc, pin_store)
158158
```
159159

160+
### Go
161+
162+
```bash
163+
go get github.com/ThirdKeyAi/agentpin/go
164+
go install github.com/ThirdKeyAi/agentpin/go/cmd/agentpin@latest
165+
```
166+
167+
```go
168+
import (
169+
"github.com/ThirdKeyAi/agentpin/go/pkg/credential"
170+
"github.com/ThirdKeyAi/agentpin/go/pkg/crypto"
171+
"github.com/ThirdKeyAi/agentpin/go/pkg/pinning"
172+
"github.com/ThirdKeyAi/agentpin/go/pkg/types"
173+
"github.com/ThirdKeyAi/agentpin/go/pkg/verification"
174+
)
175+
176+
kp, _ := crypto.GenerateKeyPair()
177+
priv, _ := crypto.LoadPrivateKey(kp.PrivateKeyPEM)
178+
179+
cred, _ := credential.IssueCredential(
180+
priv, "my-key-2026", "example.com",
181+
"urn:agentpin:example.com:my-agent",
182+
"verifier.com",
183+
[]types.Capability{"read:data", "write:report"},
184+
nil, nil, 3600,
185+
)
186+
187+
pinStore := pinning.NewKeyPinStore()
188+
result := verification.VerifyCredentialOffline(
189+
cred, discoveryDoc, nil, pinStore,
190+
"verifier.com", verification.DefaultVerifierConfig(),
191+
)
192+
```
193+
160194
### Serve .well-known Endpoints
161195

162196
```bash
@@ -193,14 +227,14 @@ Serves:
193227

194228
### Language API Reference
195229

196-
| Operation | Rust | JavaScript | Python |
197-
|-----------|------|------------|--------|
198-
| Generate keys | `crypto::generate_keypair()` | `generateKeypair()` | `generate_keypair()` |
199-
| Issue credential | `CredentialBuilder::new().sign()` | `issueCredential()` | `issue_credential()` |
200-
| Verify credential | `verify_credential()` | `verifyCredential()` | `verify_credential()` |
201-
| Key pinning | `KeyPinStore` | `KeyPinStore` | `KeyPinStore` |
202-
| Trust bundle | `TrustBundle::from_json()` | `TrustBundle.fromJson()` | `TrustBundle.from_json()` |
203-
| Mutual auth | `MutualAuth::challenge()` | `createChallenge()` | `create_challenge()` |
230+
| Operation | Rust | JavaScript | Python | Go |
231+
|-----------|------|------------|--------|-----|
232+
| Generate keys | `crypto::generate_key_pair()` | `generateKeypair()` | `generate_keypair()` | `crypto.GenerateKeyPair()` |
233+
| Issue credential | `credential::issue_credential()` | `issueCredential()` | `issue_credential()` | `credential.IssueCredential()` |
234+
| Verify credential | `verification::verify_credential_offline()` | `verifyCredentialOffline()` | `verify_credential_offline()` | `verification.VerifyCredentialOffline()` |
235+
| Key pinning | `KeyPinStore` | `KeyPinStore` | `KeyPinStore` | `pinning.KeyPinStore` |
236+
| Trust bundle | `TrustBundle::new()` | `new TrustBundle()` | `TrustBundle()` | `bundle.NewTrustBundle()` |
237+
| Mutual auth | `mutual::create_challenge()` | `createChallenge()` | `create_challenge()` | `mutual.CreateChallenge()` |
204238

205239
### Feature Flags
206240

@@ -348,4 +382,4 @@ cargo fmt --check
348382
7. **Feature-gate HTTP** — use the `fetch` feature only when online discovery is needed; default is offline-capable
349383
8. **Cross-compatible with SchemaPin** — both use ECDSA P-256, same crypto primitives
350384
9. **Trust bundles** are ideal for CI/CD and air-gapped deployments — pre-package discovery + revocation data
351-
10. **JavaScript and Python SDKs** provide identical verification guarantees to the Rust crate
385+
10. **JavaScript, Python, and Go SDKs** provide identical verification guarantees to the Rust crate

context7.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
"url": "https://context7.com/thirdkeyai/agentpin",
44
"public_key": "pk_Ehy7QXQTu2Keb0e5BNeyx",
55
"projectTitle": "AgentPin",
6-
"description": "Domain-anchored cryptographic identity protocol for AI agents — ES256 JWT credentials, 12-step verification, TOFU key pinning, revocation checking, delegation chains, and mutual authentication. Implementations in Rust, JavaScript, and Python. Part of the ThirdKey trust stack.",
6+
"description": "Domain-anchored cryptographic identity protocol for AI agents — ES256 JWT credentials, 12-step verification, TOFU key pinning, revocation checking, delegation chains, and mutual authentication. Implementations in Rust, JavaScript, Python, and Go. Part of the ThirdKey trust stack.",
77
"folders": [
88
"SKILL.md",
99
"README.md",
1010
"AGENTPIN_TECHNICAL_SPECIFICATION.md",
1111
"ROADMAP.md",
1212
"javascript/README.md",
1313
"python/README.md",
14+
"go/README.md",
1415
"docs/index.md",
1516
"docs/getting-started.md",
1617
"docs/verification-flow.md",
@@ -37,6 +38,7 @@
3738
"**/*.py",
3839
"**/*.ts",
3940
"**/*.js",
41+
"**/*.go",
4042
"**/*.lock",
4143
"**/*.toml",
4244
"**/*.cfg",

go/README.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# AgentPin Go SDK
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/ThirdKeyAi/agentpin/go.svg)](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

Comments
 (0)