attestix

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

attestix-go

test Go Reference Go Report Card

Offline verifier — in pure Go, no Python runtime, no network — for credentials and delegations issued by the Attestix Python core. Verify Ed25519 W3C Verifiable Credentials, did:key identities, and UCAN delegation chains anywhere Go runs: agent runtimes, MCP servers, Kubernetes admission controllers, CLIs.

This port is validated, byte-for-byte, against the shared cross-language conformance vectors (spec/verify/v1/vectors.json in the core repo, vendored here as testdata/vectors.json). All vectors pass.

Install

go get github.com/VibeTensor/attestix-go@v0.4.0
import attestix "github.com/VibeTensor/attestix-go"

Verify a credential (10 lines)

package main

import (
	"fmt"
	"os"

	attestix "github.com/VibeTensor/attestix-go"
)

func main() {
	vc, _ := os.ReadFile("credential.json")        // a VC issued by Attestix
	res, err := attestix.VerifyCredential(vc)        // resolves the issuer did:key, checks Ed25519 + expiry + revocation
	if err != nil {
		panic(err)
	}
	fmt.Printf("valid=%v  signature=%v  expired=%v  revoked=%v\n",
		res.Verify(), res.SignatureValid, !res.NotExpired, !res.NotRevoked)
}

Verify() is the AND of SignatureValid, NotExpired, and NotRevoked (plus a structural check) — exactly the reference verdict.

API

Function Purpose
VerifyCredential(raw []byte) (CredentialResult, error) Verify a W3C VC; issuer key resolved from the document's did:key.
VerifyCredentialAt(raw, now) / VerifyCredentialWithKey(raw, pub, now) Explicit verification time / pinned issuer key.
Canonicalize(raw []byte) ([]byte, error) The Attestix JCS-style canonical UTF-8 bytes (the signed form).
DecodeDidKey(did string) (ed25519.PublicKey, error) Decode an Ed25519 did:key to its raw 32-byte public key.
EncodeDidKey / VerificationMethod / DidKeyMultibase did:key helpers.
VerifyDelegationChain(child, parent string, childAtt, parentAtt []string, pub) (DelegationResult, error) Verify a UCAN parent→child delegation: EdDSA signatures + capability attenuation.
VerifyChainFull(token, pub, now) Recursive prf chain verification (signature + expiry + cycle detection + attenuation).
VerifyToken(token, pub) Verify a single UCAN EdDSA JWT (rejects alg:none).

The canonical form is JCS-style, not strict RFC 8785

This is the single most error-prone part of any Attestix port, so it is worth stating loudly: Attestix does not sign over strict RFC 8785. It signs over a practical JCS subset that differs from RFC 8785 in two load-bearing ways:

  1. Unicode NFC normalization is applied to every string value and every object key. RFC 8785 explicitly does not normalize. (This port uses golang.org/x/text/unicode/norm.)
  2. Number formatting follows Python json.dumps: whole-number floats collapse to integers (1.01), integers are preserved at arbitrary precision (e.g. 9007199254740993, beyond 2^53, is emitted verbatim), and non-whole floats keep their literal form. Signed payloads should avoid non-trivial floats; the vectors only use integers and 1.5.

Otherwise: keys sorted by Unicode code point, separators "," / ":" with no whitespace, raw UTF-8 output (no \uXXXX escapes). Canonicalize reproduces this exactly — see canonical.go and the canon-001 vector.

Other contract details mirrored here: VC signatures cover every top-level field except proof and credentialStatus; proofValue is base64url with padding; did:key is did:key:z + base58btc(0xed01 ‖ 32-byte key); UCAN tokens are EdDSA JWTs whose signed message is the compact base64url(header).base64url(payload) (base64url without padding), with alg:none rejected and child capabilities required to be a subset of the parent's.

Spec & references

Run the tests

go test ./...

The suite loads testdata/vectors.json and asserts every conformance vector (canonicalization byte-match, did:key decode, VC verify — valid / tampered / expired, and UCAN chain — valid attenuation / escalation).

License

Apache-2.0 © 2026 VibeTensor

Documentation

Overview

Package attestix is an offline verifier for credentials and delegations issued by the Attestix Python core (https://github.com/VibeTensor/attestix).

It verifies, with no Python runtime and no network, the three artefact types Attestix produces:

  • W3C Verifiable Credentials with Ed25519 proofs (VerifyCredential)
  • did:key Ed25519 identities (DecodeDidKey)
  • UCAN delegation chains as EdDSA JWTs (VerifyDelegationChain)

The crux is the canonical form. Attestix signs over a JCS-STYLE canonicalization that is deliberately NOT strict RFC 8785: it applies Unicode NFC normalization to every string value and object key, sorts keys by code point, emits raw UTF-8 (no \uXXXX escapes), collapses whole-number floats to integers, and preserves integers at arbitrary precision. Canonicalize reproduces that form byte-for-byte; the package is validated against the shared cross-language conformance vectors (testdata/vectors.json).

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Canonicalize

func Canonicalize(raw []byte) ([]byte, error)

Canonicalize produces the Attestix JCS-style canonical UTF-8 bytes for an arbitrary JSON document supplied as raw bytes.

This is NOT strict RFC 8785. It reproduces, byte-for-byte, the output of the reference implementation attestix/auth/crypto.py::canonicalize_json (attestix 0.4.0). The rules:

  1. Every string VALUE and every object KEY is NFC-normalized.
  2. Object keys are sorted ascending by Unicode code point.
  3. Separators are "," and ":" with no whitespace.
  4. Non-ASCII characters are emitted as raw UTF-8, never \uXXXX.
  5. Whole-number floats collapse to integers (1.0 -> 1); a literal that has no fraction/exponent is an int and is preserved verbatim, including values larger than 2^53 (arbitrary precision via math/big).
  6. Non-whole numbers keep their literal form (the vectors only use 1.5).
  7. true / false / null are the lowercase JSON literals.

func CanonicalizeValue

func CanonicalizeValue(v interface{}) ([]byte, error)

CanonicalizeValue canonicalizes an already-decoded value. The value must use the decoder's UseNumber() representation (json.Number for numbers) so that integer precision and literal form are preserved. This is exposed so callers that already hold a parsed document (e.g. a VC with fields removed) can canonicalize without re-marshalling.

func DecodeDidKey

func DecodeDidKey(did string) (ed25519.PublicKey, error)

DecodeDidKey decodes an Ed25519 did:key into its raw 32-byte public key.

did:key form: "did:key:z" + base58btc(0xed 0x01 || raw32). The leading "z" is the multibase code for base58btc.

Example

ExampleDecodeDidKey resolves an Ed25519 public key from a did:key.

package main

import (
	"fmt"

	attestix "github.com/VibeTensor/attestix-go"
)

func main() {
	pub, err := attestix.DecodeDidKey("did:key:z6Mko5TBPGKHkCxSgmf3aC6p6SGj2auwCfRmBydXJFEwL4ev")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%x\n", pub)
}
Output:
8022fe847be6106443a4030d74d390c8d9a91319b9f51526bc7c3d88a27c9b7b

func DecodeMultibaseKey

func DecodeMultibaseKey(mb string) (ed25519.PublicKey, error)

DecodeMultibaseKey decodes a "z..." base58btc multibase string carrying the 0xed01 multicodec prefix into the raw 32-byte Ed25519 public key.

func DidKeyMultibase

func DidKeyMultibase(did string) (string, error)

DidKeyMultibase returns the multibase portion (the "z..." string) of a did:key, i.e. everything after the "did:key:" prefix. The verificationMethod fragment for a did:key is "#" + this value.

func EncodeDidKey

func EncodeDidKey(pub ed25519.PublicKey) (string, error)

EncodeDidKey is the inverse of DecodeDidKey: it renders a raw Ed25519 public key as a did:key string. Provided for round-trip testing and issuance tools.

func VerificationMethod

func VerificationMethod(did string) (string, error)

VerificationMethod returns the canonical verificationMethod identifier for a did:key, "<did>#<multibase>".

func VerifyChainFull

func VerifyChainFull(token string, pub ed25519.PublicKey, now time.Time) (bool, error)

VerifyChainFull recursively verifies a UCAN token and its entire prf ancestor chain against pub: every token's EdDSA signature must verify, none may be expired (exp claim, compared against now), and the chain must contain no cycles (a repeated jti). Capability attenuation is asserted at each link: each token's att must be a subset of every parent it references in prf.

Types

type CredentialResult

type CredentialResult struct {
	// SignatureValid is true when proof.proofValue is a valid Ed25519 signature
	// over the JCS-canonical bytes of the VC with proof + credentialStatus
	// removed.
	SignatureValid bool
	// NotExpired is true when there is no expirationDate, or the verification
	// time precedes it.
	NotExpired bool
	// NotRevoked is true unless credentialStatus.revoked is truthy.
	NotRevoked bool
	// StructureValid is true when the VC has the required shape (proof with a
	// proofValue, etc.).
	StructureValid bool
}

CredentialResult is the structured outcome of verifying a W3C VC.

func VerifyCredential

func VerifyCredential(raw []byte) (CredentialResult, error)

VerifyCredential verifies a W3C Verifiable Credential supplied as raw JSON bytes. The issuer public key is resolved from the credential's issuer.id / verificationMethod did:key. Verification time is time.Now().

Example

ExampleVerifyCredential shows the 10-line offline verification of a W3C VC issued by the Attestix Python core. No network, no Python runtime.

package main

import (
	"fmt"
	"os"

	attestix "github.com/VibeTensor/attestix-go"
)

func main() {
	vc, err := os.ReadFile("testdata/sample-vc.json")
	if err != nil {
		fmt.Println("read:", err)
		return
	}
	res, err := attestix.VerifyCredential(vc)
	if err != nil {
		fmt.Println("verify:", err)
		return
	}
	fmt.Printf("signature=%v expired=%v revoked=%v verify=%v\n",
		res.SignatureValid, !res.NotExpired, !res.NotRevoked, res.Verify())
}

func VerifyCredentialAt

func VerifyCredentialAt(raw []byte, now time.Time) (CredentialResult, error)

VerifyCredentialAt is VerifyCredential with an explicit verification time, used for deterministic testing of the expiry check.

func VerifyCredentialWithKey

func VerifyCredentialWithKey(raw []byte, pub ed25519.PublicKey, now time.Time) (CredentialResult, error)

VerifyCredentialWithKey verifies a VC against a caller-supplied issuer public key, bypassing did:key resolution from the document. Use when the trusted key is pinned out of band.

func (CredentialResult) Verify

func (r CredentialResult) Verify() bool

Verify reports the overall verdict: signature valid AND not expired AND not revoked (structure must also be valid).

type DelegationClaims

type DelegationClaims struct {
	Iss string   `json:"iss"`
	Aud string   `json:"aud"`
	Sub string   `json:"sub"`
	Iat int64    `json:"iat"`
	Exp int64    `json:"exp"`
	Nbf int64    `json:"nbf"`
	Jti string   `json:"jti"`
	Att []string `json:"att"`
	Prf []string `json:"prf"`
}

DelegationClaims are the UCAN payload claims relevant to verification.

func VerifyToken

func VerifyToken(token string, pub ed25519.PublicKey) (DelegationClaims, bool, error)

VerifyToken verifies a single UCAN JWT signature against pub, returning the decoded claims. It rejects any algorithm other than EdDSA. It does not walk the prf chain; use VerifyChainFull for recursive verification.

type DelegationResult

type DelegationResult struct {
	// ParentSignatureValid is the EdDSA signature verdict for the parent token.
	ParentSignatureValid bool
	// ChildSignatureValid is the EdDSA signature verdict for the child token.
	ChildSignatureValid bool
	// AttenuationIsSubset is true when the child att is a subset of parent att.
	AttenuationIsSubset bool
}

DelegationResult is the structured outcome of verifying a delegation chain.

func VerifyDelegationChain

func VerifyDelegationChain(childToken, parentToken string, childAtt, parentAtt []string, pub ed25519.PublicKey) (DelegationResult, error)

VerifyDelegationChain verifies a two-link UCAN delegation (parent -> child). Each token is an EdDSA-signed compact JWT; the signed message is base64url(header)."."base64url(payload) (unpadded, per the JWT spec). Only alg=EdDSA is accepted; alg:none is rejected.

Both tokens are verified against pub (in Attestix every token in a chain is signed by the single server key). The child att MUST be a subset of the parent att or the chain is rejected for privilege escalation.

childAtt and parentAtt, when non-nil, are the authoritative capability lists to compare; when nil they are taken from the respective token payloads.

func (DelegationResult) Verify

func (r DelegationResult) Verify() bool

Verify reports the overall delegation verdict: both signatures valid AND the child capabilities are a subset of the parent's.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL