Skip to content

Secondary formats: vc+jwt, CAIP-261, i,j,v CSV, AT Protocol/Nostr labels, schema.org - #27

Open
harlantwood wants to merge 3 commits into
claude/v1-formats-bsuvj4from
claude/export-formats-bsuvj4
Open

harlantwood wants to merge 3 commits into
claude/v1-formats-bsuvj4from
claude/export-formats-bsuvj4

Conversation

@harlantwood

Copy link
Copy Markdown
Member

TL;DR

Trust Atoms can now be signed as application/vc+jwt (W3C VC-JOSE-COSE, alg: Ed25519) and verified as strictly as Data Integrity, and exported to CAIP-261 PeerTrustCredential (import too), the OpenRank/EigenTrust i,j,v CSV, unsigned AT Protocol labels and Nostr NIP-32 events, and schema.org Review JSON-LD. All of it is pure core code behind api, and the same functions are in wasm, node, d.ts and trust convert.

Stacked on #26 (v1 formats: CIDv1 IDs, strict profile, replaces). Its base is claude/v1-formats-bsuvj4. Once #26 merges it will be retargeted to master. It composes with #23 (Reputon) because it uses the same convert shape: --to is optional when --from is given, and there is an InputFormat enum. Expect a small textual merge in cli.rs/commands.rs/api.rs.

What it does

application/vc+jwt (trustgraph_core::jose)

  • Payload is exactly the unsecured v1 credential, written as JCS. The protected header is {"alg":"Ed25519","cty":"vc","kid":"did:key:z…#z…","typ":"vc+jwt"}, also canonical. Signing is therefore deterministic, and the jose library produces byte-identical tokens (checked in CI).

  • alg: we write Ed25519 (RFC 9864, Oct 2025, deprecates polymorphic EdDSA). On verify we also accept EdDSA, because a did:key Ed25519 key makes it unambiguous.

  • Verification is as strict as the DI path. It requires:

    • three canonical unpadded base64url segments
    • typ = vc+jwt, and cty absent or vc
    • no crit or b64
    • kid = the issuer's did:key Multikey method (VC-JOSE-COSE §4.1.1)
    • a valid Ed25519 signature
    • a payload in the strict v1 profile, with validFrom and no proof

    JWT claims (iss/iat/exp/vc) are rejected because they are not in the profile, which also keeps verification clock-free. Unknown header params are ignored (RFC 7515).

  • credentialId of a JWT is the CID of its bytes. id is the same atom ID as the DI form.

Exports (trustgraph_core::export)

  • Shared loader: signed credentials are verified, replaced ones (replaces) are dropped, and the latest atom per source/target/content wins, the same rules as the lens.
  • CAIP-261 (spec text followed from ChainAgnostic/CAIPs):
    • VC 1.1 shape (issuanceDate, 2018 context).
    • One credential per (source, target), because the spec says a new assertion MUST supersede earlier ones from the same issuer about the same subject.
    • content↔scope, value↔level (number), extra.reason↔reason[].
    • Output is unsigned.
    • Import does not verify proofs (EIP-712 is out of scope) and rejects revocations.
    • The spec's own example is a round-trip test.
  • i,j,v CSV:
    • Header i,j,v, rows sorted, RFC 4180 quoting.
    • Multiple topics are averaged, like lens edges.
    • Non-positive edges are dropped by default. OpenRank runs "positive EigenTrust". --negative keep writes them anyway.
    • --topic filters.
  • AT Protocol labels:
    • {ver:1, src (must be a DID), uri, val, neg?, cts}.
    • Values are tokens only: trusted / distrusted, or trusted-<topic-slug>. The spec says no numbers.
    • neg labels retract what superseded atoms would have labelled.
    • Unsigned: atproto signing needs a DRISL/secp256k1 or P-256 #atproto_label key.
  • Nostr NIP-32:
    • Unsigned kind:1985 templates, namespace net.trustgraph.
    • nostr:npub1… → p tag (bech32 checked), nostr:note1… → e, anything else → r.
    • No retractions (those need NIP-09 deletion).
    • No secp256k1 added.
  • schema.org: one @graph of Reviews:
    • @id is ipfs://<atom ID>
    • author / itemReviewed
    • a Rating with ratingValue, bestRating: 1, worstRating: -1, and reviewAspect

Surfaces

  • api, wasm, node, d.ts: signVcJwt, verifyVcJwt, toPeerTrust, fromPeerTrust, toIjvCsv, toAtprotoLabels, toNostrLabels, toSchemaOrg.
  • CLI:
    • trust convert --to vc-jwt|caip-261|ijv-csv|atproto-label|nostr-label|schema-org
    • --from vc-jwt|caip-261
    • --to vc-jwt signs with --key.
    • trust verify and the input reader accept eyJ… tokens, one per line.
  • Docs: doc/formats/{vc-jwt,caip-261,ijv-csv,labels,schema-org}.md, each citing its primary sources. Also README format table, architecture API table, and protocol §10.
  • Golden files: test-vectors/exports/ (one input with the W3C test key, one output per format), regenerated byte-for-byte by tests/exports.rs.

Size

WebAssembly: 645 KiB (260 KiB gzipped), up from 558 KiB on the base branch. The 1 MiB budget still holds, with no new Rust dependencies: base64url and bech32 are small hand-rolled modules.

Verified

  • cargo fmt --all --check
  • RUSTFLAGS=-D warnings:
    • cargo clippy --workspace --all-targets --locked on stable and on +1.99
    • cargo clippy -p trustgraph-core --all-targets --locked on stable and on +1.99
    • cargo clippy --target wasm32-unknown-unknown -p trustgraph-core -p trustgraph-wasm
  • RUSTDOCFLAGS=-D warnings cargo doc --workspace --no-deps --locked
  • scripts/check-core-purity.sh
  • cargo test --workspace --locked on stable and on 1.85 (MSRV)
  • scripts/build-wasm-package.sh, then smoke.mjs (wasm node + web) and interop.mjs (wasm). interop.mjs now includes jose@6.2.12:
    • golden JWTs verify with jwtVerify (alg: Ed25519, typ: vc+jwt)
    • jose and the core sign identically
    • the core verifies jose's tokens, including legacy EdDSA
    • the core rejects wrong typ, missing kid, iat, a vc wrapper and crit
  • Native addon (debug build): smoke.mjs and interop.mjs
  • pnpm run typecheck

Not in this PR

  • Storing vc+jwt in the local store and using it in the lens. For now, convert with --from vc-jwt.
  • Signing labels: atproto (DRISL + secp256k1/P-256 #atproto_label) and Nostr (secp256k1 Schnorr). This needs non-Ed25519 keys and is deliberately not added.
  • An eddsa-jcs-2022 proof on exported CAIP-261 credentials, and a published CAIP-261 JSON Schema. The spec requires credentialSchema but publishes none.
  • DIF Trust Establishment, NIP-85, the second eddsa-rdfc-2022 proof, vc+sd-jwt/vc+cose. SD-JWT VC, BBS and mdoc are out of scope.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UdKneRqUnsbv1t7NyvcT68


Generated by Claude Code

claude added 3 commits October 8, 2026 15:55
- jose: Trust Atom credentials secured as application/vc+jwt
  (VC-JOSE-COSE), alg Ed25519 (RFC 9864; legacy EdDSA accepted on
  verify). The payload is exactly the unsecured v1 credential (JCS), so
  signing is deterministic; verification is as strict as the Data
  Integrity path (typ, kid = issuer's did:key method, no crit/b64, strict
  base64url, v1 profile, no JWT claims).
- export: shared "current atoms" loader (signed credentials verified,
  replaced ones dropped, latest per source/target/content), plus
  CAIP-261 PeerTrustCredential export and import (one credential per
  source and target, as the spec's supersession rule requires), the
  OpenRank/EigenTrust i,j,v CSV (non-positive edges dropped by default),
  unsigned AT Protocol labels (with neg retractions) and Nostr NIP-32
  kind 1985 event templates, and schema.org Review/Rating JSON-LD.
- api + wasm + node + d.ts: signVcJwt, verifyVcJwt, toPeerTrust,
  fromPeerTrust, toIjvCsv, toAtprotoLabels, toNostrLabels, toSchemaOrg.
- CLI: trust convert --to vc-jwt|caip-261|ijv-csv|atproto-label|
  nostr-label|schema-org, --from vc-jwt|caip-261; trust verify accepts
  vc+jwt tokens.
- Golden files in test-vectors/exports; JS interop with jose (byte-
  identical JWTs both ways); docs in doc/formats/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdKneRqUnsbv1t7NyvcT68
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdKneRqUnsbv1t7NyvcT68
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants