Skip to content

Commit 8c58867

Browse files
authored
Merge pull request #4 from ThirdKeyAI/feature/v0.3.0-go-sdk
Add Go SDK (P1.1) — v0.2.0 surface parity
2 parents eeab269 + e77fca8 commit 8c58867

85 files changed

Lines changed: 6514 additions & 23 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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,46 @@ 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+
## [0.3.0-alpha.1] - 2026-05-01
9+
10+
### Added
11+
12+
#### A2A AgentCard Extension Types & Resolvers (Rust)
13+
14+
- **`AllowedDomains` type** in `types::discovery` — typed wrapper over the list of domains an agent is permitted to interact with. Extracted from `Constraints::allowed_domains` via the new `Constraints::allowed_domains_typed()` helper. Empty list = no restriction (all domains trusted) per the established cross-protocol convention. Includes `intersect()` for composing with cross-protocol callers (most importantly SchemaPin v1.4's `A2aVerificationContext`, which scopes tool verification to the intersection of caller and provider domains).
15+
- **`A2aAgentCard` + supporting types** in `types::a2a` — minimal A2A AgentCard subset (`A2aAgentCard`, `A2aAgentCapabilities`, `A2aAgentSkill`) plus the AgentPin-specific `AgentpinExtension` payload (`agentpin_endpoint`, `public_key_jwk`, `signature`). Inline definition rather than depending on the upstream `a2a-types` crate while the A2A spec is still draft — the public surface lets us re-export from upstream once it stabilises without breaking callers.
16+
- **`A2aAgentCardBuilder`** in new `a2a` module — turns an `AgentDeclaration` into a signed `A2aAgentCard`. Maps capabilities to skills via `capability_to_skill`, propagates `Constraints::allowed_domains` into `A2aAgentCapabilities::allowed_domains`. Detached ECDSA P-256 signature covers the canonical bytes of the AgentCard with the extension cleared.
17+
- **`verify_agentpin_extension(card)`** — verifies the AgentPin extension signature against the JWK embedded in the extension. Sorted-key canonical JSON; matches the canonicalisation pattern used by SchemaPin.
18+
- **`LocalAgentCardStore`** in new `resolver_local` module — in-memory store of pre-registered AgentCards keyed by their AgentPin discovery domain. Implements `DiscoveryResolver` (always available, no `fetch` feature). Verifies the AgentPin extension signature at registration time and pre-derives a `DiscoveryDocument` so the rest of the AgentPin verification stack runs unchanged. Supports Symbiont v1.7.0's push-based external-agent registration where the coordinator receives AgentCard JSON inline rather than fetching it from a `.well-known` endpoint.
19+
- **`A2aAgentCardResolver`** in new `resolver_a2a` module (gated on `fetch`) — fetches `https://{domain}/.well-known/agent-card.json`, verifies the AgentPin extension, cross-checks that the embedded `agentpin_endpoint` host matches the fetched domain, and derives a `DiscoveryDocument`. `last_card()` exposes the original A2A representation alongside the derived doc for callers that want both.
20+
- **`a2a_endpoint` field** on `DiscoveryDocument` — optional URL of the entity's A2A AgentCard endpoint, enabling cross-protocol discovery.
21+
22+
#### DNS TXT Cross-Verification at `_agentpin.{domain}` (Rust)
23+
24+
- **New `dns` module** with `DnsTxtRecord`, `parse_txt_record`, `verify_dns_match`, and `txt_record_name`. Always available; the parser/matcher have no DNS dependencies.
25+
- **`fetch_dns_txt(domain)`**: async lookup behind the new `dns` Cargo feature. Brings in `hickory-resolver`, `tokio`, and `async-trait`.
26+
- **TXT record format**: `_agentpin.{domain}` IN TXT `"v=agentpin1; kid=...; fp=sha256:<hex>"` — whitespace-tolerant parser, case-insensitive on `fp`, ignores unknown fields for forward compatibility. Mirrors SchemaPin's `_schemapin.{domain}` shape exactly with the version tag changed.
27+
- **Multi-key match semantics**: AgentPin discovery docs may carry several keys for rotation; a published TXT record need only match one of them. When the TXT carries an explicit `kid`, the matching key MUST also carry the same `kid`.
28+
- **Fail-closed on mismatch**: a publisher who *intentionally* publishes a TXT record has signaled that DNS is part of their trust chain — divergence between DNS and `.well-known` indicates compromise of one channel and is treated as a hard failure.
29+
30+
#### Go SDK (Fourth Language Port)
31+
32+
- **New `go/` SDK** — wire-compatible with Rust, JavaScript, and Python at the v0.2.0 surface. Mirrors the package layout of the SchemaPin Go SDK. Closes the long-standing four-language-parity gap.
33+
- **Module path**: `github.com/ThirdKeyAi/agentpin/go`
34+
- **Packages**: `crypto`, `jwk`, `jwt`, `types`, `discovery`, `credential`, `verification`, `revocation`, `pinning`, `delegation`, `mutual`, `nonce`, `bundle`, `resolver`
35+
- **CLI**: `cmd/agentpin` with `keygen`, `issue`, `verify`, `bundle` subcommands matching the Rust binary
36+
- **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.
37+
- **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.
38+
- **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.
39+
- **Note**: this initial Go port covers the v0.2.0 stable surface only. The two v0.3.0-alpha.1 features above (A2A AgentCard types and DNS TXT cross-verification) follow in a Go-side `0.3.0-alpha.2` PR.
40+
41+
### Notes
42+
43+
- This is the first v0.3.0 alpha — the unblock for **Symbiont v1.8.0 Phase 3** (AgentPin-verified AgentCards, A2A auth middleware) and **SchemaPin v1.4.0 `A2aVerificationContext`** (which consumes `AllowedDomains` for tool-verification scoping). Both downstream releases were waiting on this surface.
44+
- DNS TXT defends against HTTPS-origin compromise (compromised hosting account, expired domain not removed from CDN, ACME ownership-validation bypass) and TLS cert mis-issuance — the DNS credential chain (registrar, DNS provider, optionally DNSSEC) is independent of the HTTPS hosting chain. Spec § 4.8.3 reserved this slot in v0.1; this PR ships the implementation.
45+
- All additions are purely additive — v0.2.0 callers are unaffected. Discovery documents without `a2a_endpoint`, AgentCards without an `agentpin` extension, and absent `_agentpin` TXT records all behave exactly as before.
46+
- JavaScript and Python SDK ports of the new A2A + DNS surface follow in `0.3.0-alpha.2`.
47+
848
## [0.2.0] - 2026-02-12
949

1050
### 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

ROADMAP.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
|---------|--------|----------|--------|
1515
| **v0.1.0** | 2026-01 | Core identity, verification, delegation | Shipped |
1616
| **v0.2.0** | 2026-02 | Trust bundles, alternative discovery, directory listing | Shipped |
17-
| **v0.3.0** | Q2 2026 | A2A AgentCard extension types + resolver | Planning |
17+
| **v0.3.0-alpha.1** | 2026-05-01 | A2A AgentCard types + resolvers + AllowedDomains (Rust) | **Shipped** |
18+
| **v0.3.0** | Q2 2026 | A2A AgentCard extension types + resolver, JS/Python ports, full A2A integration | In progress |
1819
| **v0.4.0** | Q3 2026 | Mutual auth as A2A handshake, cross-language parity | Planning |
1920
| **v1.0.0** | Q4 2026 | Stable API, full specification compliance | Planning |
2021

@@ -28,7 +29,7 @@ See [CHANGELOG.md](CHANGELOG.md) for full release notes.
2829

2930
---
3031

31-
## v0.3.0 — A2A AgentCard Types + Resolver (Q2 2026)
32+
## v0.3.0 — A2A AgentCard Types + Resolver (Q2 2026)**Rust shipped (alpha.1)**
3233

3334
AgentPin becomes the cryptographic identity layer for A2A (Agent-to-Agent) networks. This release defines extension types for A2A AgentCards and a resolver that discovers AgentPin identity from A2A endpoints.
3435

@@ -124,4 +125,4 @@ We welcome input on roadmap priorities:
124125

125126
---
126127

127-
*Last updated: 2026-03-01 (cross-repo alignment with Symbiont v1.7.0/v1.8.0 and SchemaPin v1.4.0)*
128+
*Last updated: 2026-05-01 (v0.3.0-alpha.1 — Rust A2A AgentCard types, AllowedDomains, LocalAgentCardStore, A2aAgentCardResolver)*

SKILL.md

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
name: agentpin
33
title: AgentPin
4-
description: Domain-anchored cryptographic identity protocol for AI agents — ES256 JWT credentials, 12-step verification, TOFU key pinning, revocation, delegation chains, and mutual authentication
5-
version: 0.2.0
4+
description: Domain-anchored cryptographic identity protocol for AI agents — ES256 JWT credentials, 12-step verification, TOFU key pinning, revocation, delegation chains, mutual authentication, and (v0.3.0-alpha, Rust) A2A AgentCard extension types + signed AgentCard builder + LocalAgentCardStore + A2aAgentCardResolver + AllowedDomains type for cross-protocol use with SchemaPin v1.4, plus DNS TXT cross-verification at _agentpin.{domain} for second-channel trust independent of HTTPS hosting
5+
version: 0.3.0-alpha.1
6+
stable_version: 0.2.0
67
---
78

89
# AgentPin Development Skills Guide
@@ -157,6 +158,40 @@ pin_store = KeyPinStore()
157158
result = verify_credential(credential, discovery_doc, pin_store)
158159
```
159160

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

162197
```bash
@@ -193,14 +228,14 @@ Serves:
193228

194229
### Language API Reference
195230

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()` |
231+
| Operation | Rust | JavaScript | Python | Go |
232+
|-----------|------|------------|--------|-----|
233+
| Generate keys | `crypto::generate_key_pair()` | `generateKeypair()` | `generate_keypair()` | `crypto.GenerateKeyPair()` |
234+
| Issue credential | `credential::issue_credential()` | `issueCredential()` | `issue_credential()` | `credential.IssueCredential()` |
235+
| Verify credential | `verification::verify_credential_offline()` | `verifyCredentialOffline()` | `verify_credential_offline()` | `verification.VerifyCredentialOffline()` |
236+
| Key pinning | `KeyPinStore` | `KeyPinStore` | `KeyPinStore` | `pinning.KeyPinStore` |
237+
| Trust bundle | `TrustBundle::new()` | `new TrustBundle()` | `TrustBundle()` | `bundle.NewTrustBundle()` |
238+
| Mutual auth | `mutual::create_challenge()` | `createChallenge()` | `create_challenge()` | `mutual.CreateChallenge()` |
204239

205240
### Feature Flags
206241

@@ -348,4 +383,4 @@ cargo fmt --check
348383
7. **Feature-gate HTTP** — use the `fetch` feature only when online discovery is needed; default is offline-capable
349384
8. **Cross-compatible with SchemaPin** — both use ECDSA P-256, same crypto primitives
350385
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
386+
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, mutual authentication, and (v0.3.0-alpha, Rust) A2A AgentCard extension types + signed AgentCard builder + LocalAgentCardStore + A2aAgentCardResolver + AllowedDomains type for cross-protocol use, plus DNS TXT cross-verification at _agentpin.{domain} (`v=agentpin1; kid=...; fp=sha256:...`) for second-channel trust independent of HTTPS hosting. Implementations in Rust, JavaScript, Python, and Go (Go is at v0.2.0 surface parity; A2A + DNS in Go follow in alpha.2). Part of the ThirdKey trust stack: SchemaPin (tool integrity) → AgentPin (agent identity) → Symbiont (runtime).",
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",

crates/agentpin-server/src/routes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ mod tests {
8080
revocation_endpoint: None,
8181
policy_url: None,
8282
schemapin_endpoint: None,
83+
a2a_endpoint: None,
8384
max_delegation_depth: 2,
8485
updated_at: "2026-01-01T00:00:00Z".to_string(),
8586
};

crates/agentpin/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "agentpin"
3-
version = "0.2.0"
3+
version = "0.3.0-alpha.1"
44
edition = "2021"
55
rust-version = "1.70"
66
description = "Domain-anchored cryptographic identity protocol for AI agents"
@@ -13,6 +13,7 @@ categories = ["authentication", "cryptography"]
1313
[features]
1414
default = []
1515
fetch = ["reqwest", "tokio", "async-trait"]
16+
dns = ["hickory-resolver", "tokio", "async-trait"]
1617

1718
[dependencies]
1819
p256 = { version = "0.13", features = ["ecdsa", "pem", "jwk"] }
@@ -31,5 +32,8 @@ reqwest = { version = "0.12", features = ["json"], optional = true }
3132
tokio = { version = "1.0", optional = true }
3233
tempfile = { version = "3.0", optional = true }
3334

35+
# Optional dependency for DNS TXT cross-verification (v0.3.0)
36+
hickory-resolver = { version = "0.24", optional = true }
37+
3438
[dev-dependencies]
3539
tempfile = "3.0"

0 commit comments

Comments
 (0)