JOSE signing and verification library for W3C Verifiable Credentials, supporting both EUDI (SD-JWT-VC) and Gaia-X (VC-JOSE-COSE) formats.
- Dual Format Support: SD-JWT-VC (EUDI/OIDC4VP) + VC-JOSE-COSE (Gaia-X)
- Dual Runtime: Python and TypeScript with feature parity
- ES256 (P-256): EUDI HAIP compliant algorithm
- EdDSA (Ed25519): Supported (deprecated per RFC 9864, use ES256 for production)
- X.509 Support: Certificate chains via
x5cheader - DID Support:
did:keykey identifiers plusdid:ethrsubject identifiers (resolution handled by integrators) - Selective Disclosure: Native SD-JWT-VC with disclosable claims
- Key Binding: KB-JWT for holder binding in presentations
- Harbour Credential Types: Base credential framework with composition slots for Gaia-X compliance
pip install harbour-credentialsOr for development:
git clone --recurse-submodules https://github.com/reachhaven/harbour-credentials.git
cd harbour-credentials
make setup
# PowerShell
.\.venv\Scripts\Activate.ps1
# macOS / Linux / Git Bash
source .venv/bin/activateNote: The
--recurse-submodulesflag is required to clone the ontology-management-base and w3id.org submodules.
make setupinstalls Python dev dependencies (.[dev]), LinkML, pre-commit hooks, and bootstraps TypeScript dependencies withcorepack yarn installinsrc/typescript/harbour. Usemake install devonly if you need to refresh an existing Python environment.
If you already cloned without submodules:
git submodule update --init --recursive --depth 1# If you already ran `make setup`, TypeScript dependencies are already bootstrapped.
# Otherwise:
make setup tsfrom harbour import generate_p256_keypair, sign_vc_jose, verify_vc_jose
# Generate a P-256 keypair
private_key, public_key = generate_p256_keypair()
# Sign a Verifiable Credential
vc = {
"@context": ["https://www.w3.org/ns/credentials/v2"],
"type": ["VerifiableCredential"],
"issuer": "did:ethr:0x14a34:0x4ff70ba2fe8c4724a11da529381cbc391e5d8423",
"credentialSubject": {"id": "did:example:holder", "name": "Alice"}
}
jwt = sign_vc_jose(vc, private_key)
# Verify the credential
payload = verify_vc_jose(jwt, public_key)import {
generateP256Keypair,
signVcJose,
verifyVcJose,
} from "@reachhaven/harbour-credentials";
// Generate a P-256 keypair
const { privateKey, publicKey } = await generateP256Keypair();
// Sign a Verifiable Credential
const vc = {
"@context": ["https://www.w3.org/ns/credentials/v2"],
type: ["VerifiableCredential"],
issuer: "did:ethr:0x14a34:0x4ff70ba2fe8c4724a11da529381cbc391e5d8423",
credentialSubject: { id: "did:example:holder", name: "Alice" },
};
const jwt = await signVcJose(vc, privateKey);
// Verify the credential
const payload = await verifyVcJose(jwt, publicKey);Harbour provides a base credential framework (harbour-core-credential.yaml) with skeleton credentials that define the minimum required structure. A Gaia-X domain layer (harbour-gx-credential.yaml) extends the skeletons with participant types using a composition pattern:
| Credential Type | Subject Type | Composition Slot | Gaia-X Inner Type |
|---|---|---|---|
harbour:LegalPersonCredential |
harbour:LegalPerson |
gxParticipant |
gx:LegalPerson |
harbour:NaturalPersonCredential |
harbour:NaturalPerson |
gxParticipant |
gx:Participant |
All harbour credentials require:
issuer- DID of the credential issuervalidFrom- Mandatory datetimecredentialStatus- At least oneharbour:CRSetEntryfor revocation support
Base skeleton examples live in examples/ (no Gaia-X data). Gaia-X domain extensions with gxParticipant live in examples/gaiax/. The composition pattern keeps harbour properties on the harbour-typed outer node and Gaia-X properties on a gx-typed inner blank node, so both harbour and Gaia-X SHACL shapes validate independently:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/gaia-x/development#",
"https://w3id.org/reachhaven/harbour/core/v1/"
],
"type": ["VerifiableCredential", "harbour:LegalPersonCredential"],
"issuer": "did:ethr:0x14a34:0xf8abbe34d226eff3c1bc85ba9d567b9ab50b38c3",
"validFrom": "2024-01-15T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0x14a34:0xf7ef72f0ad8256df1a731ca0cb26230683518dab",
"type": "harbour:LegalPerson",
"name": "Example Corporation GmbH",
"gxParticipant": {
"type": "gx:LegalPerson",
"gx:legalName": "Example Corporation GmbH",
"gx:registrationNumber": "DE123456789",
"gx:headquartersAddress": {
"type": "gx:Address",
"gx:countryCode": "DE"
},
"gx:legalAddress": {
"type": "gx:Address",
"gx:countryCode": "DE"
}
}
},
"credentialStatus": [
{
"id": "did:ethr:0x14a34:0xf8abbe34d226eff3c1bc85ba9d567b9ab50b38c3:services:revocation-registry#abc123",
"type": "harbour:CRSetEntry",
"statusPurpose": "revocation"
}
]
}Validate harbour credentials against SHACL shapes using the ontology-management-base validation suite:
# Generate artifacts from LinkML schemas
make generate
# Validate examples against SHACL shapes (harbour + gx)
make validate shacl
# Run structural validation tests
make validate
# See validation subcommands
make validate helpAll Python modules have CLI entry points:
# Key management
python -m harbour.keys generate --algorithm ES256 --output key.jwk
python -m harbour.keys convert --input key.jwk --format did-key
# Signing
python -m harbour.signer sign-vc --credential vc.json --key key.jwk
# Verification
python -m harbour.verifier verify-vc --jwt vc.jwt --public-key key.jwk
# SD-JWT-VC
python -m harbour.sd_jwt issue --claims claims.json --key key.jwk --vct https://example.com/vc
python -m harbour.sd_jwt verify --sd-jwt token.txt --public-key key.jwk
# X.509 Certificates
python -m harbour.x509 generate --key key.jwk --subject "Test Issuer" --output cert.pem
src/
├── python/
│ ├── harbour/ # Crypto library
│ │ ├── keys.py # Key generation, JWK, DID:key
│ │ ├── signer.py # VC/VP signing
│ │ ├── verifier.py # VC/VP verification
│ │ ├── sd_jwt.py # SD-JWT-VC issue/verify
│ │ ├── kb_jwt.py # Key Binding JWT
│ │ └── x509.py # X.509 certificates
│ └── credentials/ # Credential processing pipeline
│ └── example_signer.py
└── typescript/
└── harbour/ # Crypto library (feature parity)
├── keys.ts
├── signer.ts
├── verifier.ts
├── sd-jwt.ts
└── x509.ts
submodules/
├── ontology-management-base/ # Validation pipeline, SHACL tools
└── w3id.org/ # W3ID context resolution
examples/
├── legal-person-credential.json # Harbour skeleton credentials
├── natural-person-credential.json # (canonical unsigned JSON-LD)
├── gaiax/ # Gaia-X domain extensions
└── did-ethr/ # Example did:ethr DID documents used by examples
tests/
├── fixtures/ # Shared test fixtures
│ ├── keys/ # Test keypairs
│ ├── tokens/ # Signed token fixtures
│ └── sample-vc.json # Shared unsigned VC payload
├── interop/ # Cross-runtime interop tests
├── python/ # Python tests
│ ├── harbour/ # harbour module tests
│ └── credentials/ # credentials module tests
└── typescript/harbour/ # TypeScript tests
linkml/
├── harbour-core-credential.yaml # Harbour base credential framework
└── harbour-gx-credential.yaml # Gaia-X domain layer (participant/service types)
artifacts/ # Generated per domain (make generate)
├── harbour-core-credential/ # Base OWL/SHACL/context
└── harbour-gx-credential/ # Domain OWL/SHACL/context
# Python tests
make test
# TypeScript tests (requires make build first)
make build
make test ts
# Cross-runtime interop tests (requires make build first)
make test interop
# Full pipeline: Python + SHACL conformance + TypeScript (builds TS automatically)
make test full
# Python tests with coverage
make test cov
# Lint
make lint
# See grouped subcommands
make test help
make story helpEPL-2.0