ex_vc

Hex.pmHex DocsLicense

ex_vc is a focused Verifiable Credentials 2.0 library for Elixir.

Quick links: Hex package | Hex docs | Supported features | Interop notes | Fixture policy

What Standard Is This?

Verifiable Credentials are a way to package signed claims such as identity, authorization, affiliation, or delegation in a portable format. A credential says something like “issuer X asserts subject Y has property Z”, and a verifier can check the signature and the credential rules.

ex_vc implements the VC Data Model 2.0 core boundary for Elixir, with a release-1 focus on generic VC validation, Verifiable Presentation boundaries, and vc+jwt.

If you want the formal standards context, start with:

Why You Might Use It

Use ex_vc when you need to:

Release 1 is intentionally narrow:

Data Integrity and SD-JWT VC code remains in-tree for later parity work, but they are not part of the release 1 support contract, Hex support posture, or parity claims.

Status

Release 1 supported surface:

Runtime deliberately does not require:

Those toolchains are maintainer-only and reserved for refreshing parity fixtures.

Installation

def deps do
  [
    {:ex_vc, "~> 0.1.1"}
  ]
end

Usage

Validate a VC envelope

credential = %{
  "@context" => ["https://www.w3.org/ns/credentials/v2"],
  "type" => ["VerifiableCredential"],
  "issuer" => "did:web:example.com",
  "credentialSubject" => %{"id" => "did:key:z6MkwExample"}
}

{:ok, report} = ExVc.validate(credential)
report.parsed.normalized

Issue a normalized credential

{:ok, credential} =
  ExVc.issue(
    %{"credentialSubject" => %{"id" => "did:key:z6MkwExample"}},
    issuer: "did:web:example.com",
    types: ["VerifiableCredential", "ExampleCredential"]
  )

Sign and verify VC-JWT

{:ok, jwt} = ExVc.sign_jwt_vc(credential, issuer_jwk, alg: "ES256")
{:ok, verification} = ExVc.verify_jwt_vc(jwt, jwk: issuer_public_jwk)

verification.format
verification.credential

You can also verify through DID resolution instead of passing a raw JWK:

{:ok, verification} = ExVc.verify_jwt_vc(jwt, issuer_did: "did:key:zDna...")

Use auto-detected verification

{:ok, verification} = ExVc.verify(jwt, issuer_did: "did:key:zDna...")
verification.verified

Validate a Verifiable Presentation

presentation = %{
  "@context" => ["https://www.w3.org/ns/credentials/v2"],
  "type" => ["VerifiablePresentation"],
  "holder" => "did:key:z6MkwHolder",
  "verifiableCredential" => [credential]
}

{:ok, result} = ExVc.verify_presentation(presentation)
result.verified

verify_presentation/2 in release 1 is a strict VP boundary:

It is not advertised as full proof-bearing VP interoperability yet.

Validation Model

ExVc.validate/2 enforces the generic VC envelope:

Application or profile semantics belong in caller-supplied validators.

Interoperability Strategy

ex_vc uses three evidence layers:

The parity fixture layout mirrors ex_did:

Release 1 parity claims are intentionally narrow:

See:

Open Source Notes

Maintainer Workflow

ex_vc is developed in the delegate monorepo. The public github.com/bawolf/ex_vc repository is the mirrored OSS surface for issues, discussions, releases, and Hex publishing.

The monorepo copy is authoritative for:

Direct standalone-repo edits are temporary hotfixes only and must be backported to the monorepo immediately.

The intended workflow is:

  1. make library changes in libs/ex_vc
  2. run scripts/release_preflight.sh
  3. publish the corresponding ex_did dependency release first when ex_vc depends on a newer ex_did version
  4. sync the package into a clean checkout of github.com/bawolf/ex_vc
  5. verify the mirrored required file set with scripts/verify_standalone_repo.sh
  6. review and push from the standalone repo
  7. trigger the publish workflow from the standalone repo

A helper to sync all public package repos from the monorepo lives at /Users/bryantwolf/workspace/delegate/scripts/sync_public_libs.sh.

The standalone repository should carry GitHub Actions workflows for:

The publish workflow expects a HEX_API_KEY repository secret in the standalone ex_vc repository. Once triggered, it publishes to Hex and then creates the matching Git tag and GitHub release automatically.

Maintainer Tooling

Refresh TypeScript parity fixtures:

cd libs/ex_vc/scripts/upstream_parity
pnpm install
pnpm run record:released
pnpm run record:main

Refresh Rust parity fixtures:

cd libs/ex_vc/scripts/ssi_parity
cargo run -- released
cargo run -- main

Normal users and release consumers do not need either toolchain.

Release Automation

The standalone ex_vc repository is expected to carry:

The publish workflow should be triggered through workflow_dispatch after the version and changelog are ready. It publishes to Hex first and then creates the matching Git tag and GitHub release automatically. It expects a HEX_API_KEY repository secret in the standalone ex_vc repository.

Releasing From GitHub

Releases are cut from the public github.com/bawolf/ex_vc repository, not from the private monorepo checkout.

The shortest safe path is:

  1. finish the change in libs/ex_vc
  2. run scripts/release_preflight.sh
  3. if mix.exs points at a newer ex_did, publish ex_did first
  4. sync and verify the standalone repo with scripts/sync_standalone_repo.sh and scripts/verify_standalone_repo.sh
  5. push the mirrored release commit to main in github.com/bawolf/ex_vc
  6. in GitHub, go to Actions, choose Publish, and run it with the version from mix.exs

The GitHub workflow is responsible for:

Run the local preflight with:

scripts/release_preflight.sh