feat(harbour): schema-first Gaia-X Loire compliance, delegated signing, did:ethr#2
Merged
Conversation
- Add delegation challenge encoding spec (v2.0) with OID4VP alignment - Implement Python delegation module with TransactionData dataclass - Add 48 unit tests for challenge creation, parsing, and validation - Add did:web vs did:webs evaluation document - Download reference specs (OID4VP, did:web, did:webs, KERI) for offline access - Update documentation structure with guides and specs sections - Add contributing guide and architecture overview The delegated signing model enables users to authorize blockchain transactions through any VC wallet, with a signing service executing on their behalf. Challenge format uses hash-only approach for QR code compatibility while maintaining full auditability. BREAKING CHANGE: Evidence format changes from nonce-based to proof.challenge with ABNF-defined structure. Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
… delegation modules Restructure the evidence model for W3C-correct, OID4VP-aligned credentials with three-layer privacy (public / authorized / full audit). Schema changes (LinkML): - Merge EmailVerification + IssuanceEvidence → CredentialEvidence - Simplify DelegatedSignatureEvidence to use OID4VP transactionData - Remove TransactionIntent class and legacy slots Python: - Refactor delegation.py TransactionData to OID4VP §8.4 fields (type, credential_ids, iat/exp as Unix timestamps, txn, nonce) - Fix pre-existing SD-JWT VP test bugs (wrong parameter names, missing vct) TypeScript: - Add delegation.ts with full feature parity (challenge, hash, display) - Add sd-jwt-vp.ts for SD-JWT VP issue/verify with evidence - Add cross-runtime canonicalization interop tests (5 tests) Examples: - Add delegated-signing-receipt.json and consent-vp.json - Update evidence types in existing examples Housekeeping: - Update CLAUDE.md module table and key imports for new modules - Expose delegation and sd_jwt_vp in harbour __init__.py - Use pathlib.Path in delegation.py CLI (per coding conventions) - Fix duplicate section numbering in delegation spec 293 Python + 97 TypeScript + 11 interop tests — all passing. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- enforce delegation challenge derivation and verification from transaction_data (`<nonce> HARBOUR_DELEGATE <sha256(canonical(transaction_data))>`) in Python and TypeScript - enforce KB-JWT transaction_data hash and audience bindings in SD-JWT VP flows - add and align cross-runtime canonicalization vectors/tests for JSON, hashes, challenges, and OID4VP transaction_data parameter/hash handling - standardize delegated signing examples/docs on OID4VP snake_case fields (`transaction_data`, `credential_ids`, `transaction_data_hashes*`, `asset_id`) - migrate natural/legal person examples to did:webs identifiers and add did-webs DID document examples - model EmailPass as credential evidence issued by a did:webs issuer DID - extend SHACL validation pipeline with ontology loading probe and required ontology checks (`cs`, `cred`, `core`, `harbour`, `gx`) via ontology-management-base validation suite Refs: #2 Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
…s sole issuer Refactor the credential issuance model so the Harbour Signing Service is the sole issuer of ALL credentials (LegalPerson + NaturalPerson), acting "on behalf of" an authorizing party. Evidence VPs now contain authorization proof instead of empty key-ownership VPs. Signing Service DID document: - Add #key-2 (P-256) for capabilityDelegation (delegated txn signing) - #key-1 remains assertionMethod (credential issuance) - Both keys listed under authentication Trust Anchor DID document: - Add LinkedCredentialService endpoint pointing to self-signed credential - Self-signed LegalPersonCredential (issuer == subject, root of trust) Credential issuance chain: - LegalPersonCredential: Trust Anchor authorizes org via VP containing its self-signed credential → Signing Service issues with VP as evidence - NaturalPersonCredential: Org authorizes employee via VP containing its LegalPersonCredential (SD-JWT, PII redacted) → Signing Service issues with VP as evidence Delete Harbour Credential Issuer: - Remove harbour-credential-issuer.did.json (DID Eoc79uQyWN5vo9...) - Merge issuer role into Signing Service (DID Er9_mnFstIFyj7...) - Update all references in examples, docs, and tests LinkML schema: - Update CredentialEvidence description for authorization model - Add LinkedCredentialService class to ServiceUnion - Update gaiax-domain.yaml evidence comments for both credential types Remove consent-vp.json: - Redundant — the receipt credential embeds the consent VP as evidence - The consent VP is an ephemeral artifact, not a persisted example Fix vc+ld+jwt → vc+jwt in signer/verifier source code (both runtimes): - VC-JOSE-COSE spec: "No +ld+ media types exist" - signer.py/signer.ts: typ="vc+jwt" for VCs, typ="vp+jwt" for VPs - verifier.py/verifier.ts: expected_typ updated to match - All test assertions updated (sign, tamper, interop, example_signer) Clean up legacy test data: - Replace simpulse-id VCT URIs with Harbour namespace in SD-JWT and KB-JWT tests (Python + TypeScript) - Replace ASCS/BMW sample claims with generic example data - Fix did:web → did:webs in test_sd_jwt_vp.py Docs audit — fix legacy references across all specs and ADRs: - delegation-challenge-encoding.md: replace did:web with did:webs for Signing Service (5 occurrences), remove simpulse-id-credentials refs - did-method-evaluation.md: update from SimpulseID to Harbour, rewrite section 7 for current did:webs implementation - ADR-001: fix vc+ld+jwt → vc+jwt (VC-JOSE-COSE spec compliance), update did:web → did:webs for key resolution - ADR-004: update ASCS/BMW examples to Harbour entities, did:web → did:webs - architecture.md: update key resolution from did:web to did:webs - ADR-001, ADR-004: remove backwards-compatibility and "legacy" language (this is v1 — no compatibility concerns exist) Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
The Cross-Runtime Interop CI job installed TypeScript dependencies but never ran `yarn build`, so the compiled `dist/` output (delegation.js, sd-jwt-vp.js) didn't exist. The 7 interop tests that import from `./dist/delegation.js` failed with "Qualified path resolution failed". CI fix: - Add `yarn build` step to `test-interop` job after `yarn install` Makefile fix: - `make test-all` now runs `build-ts` before `test`, ensuring interop tests have fresh compiled output even from a clean state Root cause: locally `dist/` persists across runs so the failure was never observed. CI starts fresh on every run. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
…ensions Base examples are now harbour-only skeletons (no gxParticipant, no Gaia-X context). Gaia-X domain extensions live in examples/gaiax/ with schema:name as the authoritative name on the gxParticipant inner node (no duplicate name on the outer credentialSubject). LinkML schemas restructured: harbour.yaml contains only the abstract HarbourCredential base class plus evidence types, CRSet, and DID document structure. Concrete credential types (LegalPersonCredential, NaturalPersonCredential) and participant types live in gaiax-domain.yaml. ServiceOffering removed entirely. Claim mappings split into MAPPINGS (base) + GAIAX_MAPPINGS registries with context-aware get_mapping_for_vc() dispatch. Example signer auto-discovers gaiax/ subdirectory and uses per-file output directories. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Inline the id slot and type comment from core.yaml into harbour.yaml, eliminating a separate schema file that only defined one slot. Simplifies the schema structure to two files: harbour.yaml (base) and gaiax-domain.yaml (domain layer). Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
CI jobs now delegate to Makefile targets (make lint, make test, etc.) instead of inlining shell commands. Added make test-interop target for cross-runtime tests. Replaced all emoji in Makefile with ASCII text. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
06736bd to
64a99c5
Compare
lint-ts and test-ts were missing yarn install, causing CI failures where dependencies hadn't been installed yet. Locally this worked because make setup had already run yarn install. Now all TS targets (build-ts, test-ts, lint-ts) consistently run corepack enable + yarn install before execution. Removed redundant corepack enable steps from CI workflow since Makefile handles it. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
cd into OMB_SUBMODULE_DIR before invoking $(PYTHON), so the relative path .venv/bin/python3 no longer resolves. Use $(abspath $(PYTHON)) to produce an absolute path, consistent with how submodule-setup already passes PYTHON to sub-makes. Signed-off-by: felix hoops <9974641+flhps@users.noreply.github.com>
Remove duplicate 'Run Tests' subsection from under Validating Credentials. Consolidate into a single Testing section using make targets throughout: make test-ts instead of yarn, make test-interop instead of raw pytest. Note make build-ts as a prerequisite for TypeScript and interop tests. Signed-off-by: felix hoops <9974641+flhps@users.noreply.github.com>
…harbour-gx-credential Clarify the separation between infrastructure types (HarbourCredential, CRSet, Evidence, DID) and Gaia-X concrete types (LegalPersonCredential, NaturalPersonCredential, gxParticipant) by renaming the schema files. - linkml/harbour.yaml -> linkml/harbour-core-credential.yaml - linkml/gaiax-domain.yaml -> linkml/harbour-gx-credential.yaml - Makefile DOMAINS updated to match new filenames - Test artifact paths and doc references updated - Add BMW legal-person-credential example for IRI reference testing - RDF namespace/prefix stays harbour: — no URI changes Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
In CI, PYTHON is a bare 'python3' command. $(abspath python3) wrongly resolves to $CWD/python3 instead of using PATH. Introduce PYTHON_ABS that uses $(shell which) in CI and $(abspath) locally. Also update the required ontology check from harbour-core-credential to harbour-gx-credential — the auto-discovery loads the gx ontology (which transitively imports core) based on types found in the example data. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
a0a4006 to
ce73098
Compare
- Create gaiax/natural-person-credential-andreas.json (UUID b2c3d4e5) - Create gaiax/natural-person-credential-max.json (UUID c3d4e5f6) - Both use did:web:did.ascs.digital subject DIDs and BMW as memberOf - Resolves broken harbourCredential references from simpulseid layer Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
…eKind - Remove id from class slots: lists — identifier: true already maps to @id; listing in slots generated invalid sh:path harbour:id property shapes (7 classes: DIDDocument, TrustAnchorService, LinkedCredentialService, CRSetRevocationRegistryService, HarbourCredential, CRSetEntry, VerificationMethod) - Change Evidence class_uri from cred:Evidence to harbour:Evidence to match subclass namespace and produce correct sh:class constraint - Add HarbourShaclGenerator with cred:issuer nodeKind fix — W3C VC v2 context defines issuer as @type: @id (IRI), not sh:Literal - Replace gen-shacl/gen-owl/gen-jsonld-context CLI calls in Makefile with unified generate_artifacts.py script - Fix CRSet status ID URIs: #fragment to /path (did:webs uses path separators for service sub-resources, not fragments) Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
…raints - Add GitHub issue references (linkml/linkml#2914) to HarbourShaclGenerator - Strip sh:class linkml:Any from generated SHACL (meta-type not in instance data) - Add Gaia-X 25.11 compliance fields to harbour-gx-credential schema - Add X.509 revocation checking stub Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
- Replace did:web with did:ethr using ERC-1056 on Base Sepolia (0x14a34) - Add did:ethr DID document examples for all ecosystem entities - Add ADR-005 documenting did:ethr migration rationale - Add did:ethr method specification reference - Update delegation challenge encoding for did:ethr format - Update DID method evaluation with did:ethr comparison - Update all examples and test fixtures to did:ethr identifiers - Remove deprecated did:web examples and references Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
5 tasks
…nt tooling LinkML schema improvements: - Eliminate SHACL ghost properties (remove id from class slots lists) - Fix Evidence class_uri to harbour:Evidence namespace - Add HarbourShaclGenerator with cred:issuer nodeKind fix - Clean up harbour-core-credential and harbour-gx-credential schemas - Add w3c-vc.yaml schema for transitive imports Artifact generation: - Replace gen-shacl/gen-owl/gen-jsonld-context CLI calls with unified generate_artifacts.py script - Add DomainContextGenerator to exclude W3C @Protected terms Documentation: - Add reference specs (did-core, oid4vp, vc-data-model-2.0, sd-jwt-rfc9901, gx-architecture-document-25.11) - Convert did-web-method.txt to proper markdown - Fix markdown lint violations across all docs (MD040, MD046, MD036, MD032) Markdown lint tooling: - Add .markdownlint-cli2.yaml config with tuned rules - Add markdownlint-cli2 pre-commit hook (v0.17.2) - Add lint-md and format-md Makefile targets - Add lint-markdown CI job in GitHub Actions workflow Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
- Add docs/schema/credential-model.md with Mermaid class diagrams: credential type hierarchy, evidence inheritance, subject types, Gaia-X composition pattern, revocation infrastructure, DID model, artifact generation pipeline, and complete class map - Expand docs/architecture.md from stub to full overview with component diagram, signing flow sequence diagram, and ADR-005 reference - Add Schema section and ADR-005 to mkdocs.yml navigation - Fix did-webs-spec.md ::: markers clashing with mkdocstrings - Add site/ to .gitignore Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
Add did: https://www.w3.org/ns/did# to prefixes and replace 6 full URIs with compact did: notation (controller, serviceEndpoint, DIDDocument, service, verificationMethod, VerificationMethod). Eliminates 'No namespace defined for URI' warnings during make generate. Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
The generate pipeline requires ASCS-eV/linkml fork features (deterministic output, normalize-prefixes, xsd-anyuri-as-iri, exclude-external-imports). Previously 'make install dev' ran 'pip install linkml' which installed upstream PyPI linkml without these features, causing: TypeError: OwlSchemaGenerator.__init__() got an unexpected keyword argument 'deterministic' Changes: - Add LINKML_SUBMODULE_DIR variable pointing to OMB's linkml submodule - Move linkml installation from _install_dev to _setup_submodules, installing from the fork submodule (editable mode) - Remove standalone 'pip install linkml' from all targets - Lint/test jobs no longer install linkml (not needed for those tasks) - CI generate-validate job installs deps + submodules before generation Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
test_shacl_failures.py imports rdflib directly. Previously this was transitively available via 'pip install linkml', but now that linkml is only installed in the generate-validate job (from the fork submodule), test jobs need rdflib declared explicitly. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
JSON-LD expansion maps _comment to harbour:_comment triples, which violate sh:closed shapes. Remove documentation-only _comment keys from the nested evidence example. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Set use_native_uris=False on OwlSchemaGenerator so OWL class IRIs are derived from class_uri instead of default_prefix + ClassName. This makes the rdfs:subClassOf hierarchy use the same IRIs as SHACL sh:targetClass and JSON-LD @type, allowing RDFS inference to resolve the type hierarchy directly. Removes the _patch_owl_equivalences post-processing function (-51 lines) that was copying rdfs:subClassOf triples and adding owl:equivalentClass axioms to bridge the class_uri / native URI mismatch. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Remove redundant 'Harbour' prefix from class_uris — the namespace
prefix already establishes context:
- harbour:HarbourCredential → harbour:Credential
- harbour:HarbourPresentation → harbour:Presentation
- (implicit) HarbourVerifiable* → harbour:VerifiableCredential,
harbour:VerifiablePresentation
Clean up delegation evidence naming:
- harbour:DelegatedSignatureEvidence → harbour:SignatureEvidence
(stays in core where it is defined)
- harbour:DelegatedSigningReceipt → harbour.delegate:SigningReceipt
(receipt type only used in delegation examples)
Convention: W3C terms stay bare ('VerifiableCredential'), all domain
types use prefixed CURIEs ('harbour:VerifiableCredential'). Follows
the same pattern as harbour.gx:LegalPerson, harbour.gx:NaturalPerson.
Update all examples, tests (Python + TypeScript), and sd_jwt_vp.py
evidence type whitelist. LinkML class names unchanged (internal).
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
18f0443 to
f4ac3f5
Compare
…redential arrays - Remove harbour:Evidence from type arrays where a child type (CredentialEvidence, SignatureEvidence) is present; RDFS inference infers the parent via rdfs:subClassOf - Remove harbour:VerifiableCredential from domain credential type arrays (LegalPerson, NaturalPerson, DelegatedSigningReceipt) where a domain-specific type already inherits all constraints from harbour:Credential - Refactor test_shacl_failures.py to use the OMB ShaclValidator (with RDFS inference enabled) instead of calling pyshacl directly with inference=none - Add harbour:SignatureEvidence to TypeScript DELEGATED_EVIDENCE_TYPES whitelist (was missing, causing 3 TS test failures) Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
- serviceEndpoint: range uri per DID Core §5.4 - transaction_data: range JsonLiteral per OID4VP §8.4 - Post-process JSON-LD contexts to use @JSON keyword - Add standards compliance rules to AGENTS.md The linkml:Any range caused SHACL closed-shape violations when RDFS inference added rdf:type linkml:Any to IRI objects. Replacing with spec-aligned ranges eliminates the issue. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
On Windows, yarn.cmd invokes cmd.exe which mangles JS code containing braces and parentheses passed via -e flag. Write scripts to temp .mjs files instead, and resolve yarn via shutil.which() for cross-platform subprocess compatibility. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- check_dev_setup: verify both linkml AND harbour are importable (was only checking linkml — missed harbour installation issues) - _test_default: exclude tests/interop/ (requires TypeScript built; use 'make test full' for all including interop) - Add check target: generate + validate (matches simpulseid pattern) - Update all target: lint + check + test (was lint + test only) - Add jsonld-lint and turtle-lint pre-commit hooks for JSON-LD and Turtle syntax validation (matching OMB and simpulseid patterns) Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
a3b0c52 to
132b8bd
Compare
Test across OS: ubuntu-latest, macos-latest, windows-latest Matrix applied to: - test-python: 3 OS × 2 Python (3.12, 3.13) = 6 jobs - test-ts: 3 OS = 3 jobs - test-interop: 3 OS = 3 jobs - generate-validate: 3 OS = 3 jobs Lint jobs remain ubuntu-only (formatting is OS-independent). Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
d9059ea to
21c1b7e
Compare
New 'Credential Story' job runs the full generate → sign → verify → SHACL validate pipeline across 3 OS (ubuntu/macos/windows). Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
f6fed47 to
c836fdb
Compare
The verify_signed_examples.py script uses Unicode checkmarks (✓) in print output. On Windows, the default cp1252 encoding cannot encode these characters. Setting PYTHONIOENCODING=utf-8 fixes this. OMB's validators use emojis too but run via pytest which handles encoding internally. Story targets run scripts directly, so they need the explicit encoding override. Closes #5 Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
The DIDDocument class only modeled 5 of 10 W3C DID Core properties. The generated SHACL shape (sh:closed true) rejected spec-compliant optional properties like capabilityDelegation. Added per W3C DID Core (https://www.w3.org/TR/did-core/): - keyAgreement (§5.3.3) → sec:keyAgreementMethod - capabilityInvocation (§5.3.4) → sec:capabilityInvocationMethod - capabilityDelegation (§5.3.5) → sec:capabilityDelegationMethod - alsoKnownAs (§5.1.3) → as:alsoKnownAs URI suffixes follow the DID Core JSON-LD context convention (imports/did/did.context.jsonld) where verification relationships expand to sec:*Method URIs. Also fixes harbour-signing-service.did.json: replaced undefined didcore:serviceEndpoint prefix with spec-compliant serviceEndpoint. Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Pin ontology-management-base submodule to the v0.1.6 release which includes CI artifact generation verification, cross-platform test matrix, and Makefile CI detection fixes. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update w3id.org submodule to upstream master (ecada1d3). The reachhaven/ and simpulse-id/ namespace redirect rules have been merged upstream. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Standardize JSON-LD type declarations in DID service endpoints. Inside serviceEndpoint objects, @type is needed for proper JSON-LD expansion — plain type is only aliased at the DID document level by the DID Core context. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update w3id.org fork to latest upstream master (d004a453). Both reachhaven/harbour and ascs-ev/simpulse-id namespace redirect rules are now merged in the public w3id.org repository. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
…tion Per EVES-009 §5 security requirements: - Log warning when credentialStatus is present but revocation check is not performed (caller responsibility per spec) - Add optional check_transaction_freshness parameter to verify_sd_jwt_vp() that validates transaction data timestamps (iat, exp) via validate_transaction_data() - Default off for backwards compatibility; callers opt in with check_transaction_freshness=True Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch transforms harbour-credentials from a basic VC signing library into a full credential infrastructure for the ENVITED-X Data Space. It adds Gaia-X Loire (25.11) compliance, an OID4VP-based delegated signing evidence protocol, and migrates from
did:webtodid:ethron Base (ERC-1056).141 files changed, 27,956 insertions, 2,153 deletions across 78 commits.
Design Decisions
Why did:ethr over did:web?
did:webties DID resolution to DNS and HTTPS infrastructure — if a domain changes or a TLS certificate expires, all credentials become unresolvable.did:ethranchors identity on-chain via ERC-1056, making DIDs independent of any single hosting provider. The trade-off is blockchain dependency, but Base (Ethereum L2) provides low-cost operations with Ethereum-grade finality. P-256 was chosen over secp256k1 because Gaia-X and the W3C VC ecosystem standardize on ES256 (P-256), and EUDI wallets require it.Why OID4VP dual-hash binding instead of simple nonce = hash(message)?
EVES-009 specifies that the VP nonce should equal the message hash. Harbour instead uses the OID4VP standard's dual-binding approach: the VP
noncecarries a random value for replay prevention, while the KB-JWTtransaction_data_hashesarray carriesSHA-256(base64url(transaction_data))for message binding. This was chosen because:A review comment on EVES-009 suggests the spec be relaxed to accommodate both approaches.
Why delegation via evidence VPs, not DID capabilityDelegation?
The ENVITED use case requires proving that a specific user consented to a specific action (e.g., issuing a credential, executing a blockchain transaction). DID-level
capabilityDelegationgrants blanket authority — it cannot be scoped to individual transactions or carry consent evidence. The evidence VP approach binds consent to a deterministic challenge derived from the action, creating an independently verifiable audit trail. This follows the pattern described in EVES-009 §1.Why SD-JWT-VC as the recommended format?
SD-JWT-VC (RFC 9901) supports selective disclosure — holders can redact claims before presenting credentials. This is critical for GDPR compliance: an administrator credential contains
givenName,familyName,email, andaddress, but a service verifying membership only needs thememberOfreference. SD-JWT-VC lets the holder present only what's needed. The format also supports key binding (proving holder possession) and is the format mandated by the EUDI Architecture Reference Framework.Why schema-first with LinkML?
All credential types, subject shapes, and property constraints are defined in LinkML YAML schemas. A single
make generateproduces OWL ontologies, SHACL validation shapes, and JSON-LD contexts. This eliminates drift between the data model and its machine-readable artifacts. The SHACL shapes usesh:closed trueso unexpected properties are rejected, andexclude_imports=Trueensures only harbour-defined types get shapes (Gaia-X base types are validated by upstream shapes).Why Gaia-X composition over flat credentials?
Harbour credentials use a composition pattern:
gx:LegalPersonandharbour.gx:NaturalPersonare inlined as blank nodes inside the credential subject'sparticipantproperty, rather than flattened into the subject itself. This preserves the Gaia-X type hierarchy (aNaturalPersonIS-Agx:Participant) while allowing domain layers like SimpulseID to add their own properties (harbourCredential,legalForm,memberOf) without polluting the Gaia-X namespace.Why CRSet revocation instead of X.509 CRL/OCSP?
Harbour uses a Credential Revocation Set (
harbour:CRSetEntry) queryable via DID service endpoints, rather than X.509-style certificate revocation. CRSet entries are identified by<registry-did>/<credential-hash>, making revocation checks resolvable through the same DID infrastructure used for identity. This avoids introducing a separate PKI dependency.Changes by category
LinkML schemas (8 files, +1,652/−458)
harbour-core-credential.yaml— W3C VC v2 infrastructure types (HarbourCredential, CRSet, Evidence, DIDDocument, VerificationMethod)harbour-gx-credential.yaml— Gaia-X concrete types (LegalPerson, NaturalPerson, gxParticipant composition slot)harbour-core-delegation.yaml— OID4VP transaction data, delegation challenge encoding, evidence bindingw3c-vc.yaml— thin shim projecting W3C VC v2 vocabulary terms into LinkMLimportmap.json— 80+ cross-directory import mappings through the OMB submodule chainPython source (15 files, +2,056/−493)
delegation.py— TransactionData creation/validation, challenge format (<nonce> HARBOUR_DELEGATE <hash>), SIWE-style consent display, OID4VPtransaction_dataparameter encodingsd_jwt.py— SD-JWT-VC issuance with structured selective disclosure (RFC 9901 §6.2), flat and nested disclosure pathskb_jwt.py— Key Binding JWT withsd_hashandtransaction_data_hashesper OID4VP Appendix B.3.3sd_jwt_vp.py— SD-JWT VP issuance/verification with evidence binding, revocation status warning, optional transaction freshness checkx509.py— X.509 self-signed certificate generation, chain validation, x5c header supportkeys.py— did:ethr derivation (P-256 uncompressed → keccak256 → Ethereum address), did:key multibase encodingcredentials/example_signer.py— multi-key trust chain signing pipeline with role-based DID → kid mappingTypeScript source (7 files, +1,046/−22)
delegation.ts,sd-jwt.ts,kb-jwt.ts,sd-jwt-vp.ts,x509.ts,keys.ts,signer.tsjoselibrary (WebCrypto API) for all crypto operationsTests (34 files, +4,268/−437)
test_delegation.py(55 tests) — shared canonicalization vectors, challenge parse/verify, CLI, display rendering, validation edge casestest_sd_jwt.py(28 tests) — RFC 9901 structured disclosure, partial/full/no disclosure, cnf binding, Ed25519 supporttest_kb_jwt.py(18 tests) — transaction data hashing, sd_hash computation, wrong key/nonce/audience detectiontest_sd_jwt_vp.py(23 tests) — full delegated consent flow, privacy-preserving audit scenario, multiple evidence itemstest_shacl_failures.py— mutation-based SHACL tests for harbour credential shapesDocumentation (36 files, +16,213/−263)
Examples (25 files, +1,805/−166)
CI/CD and build (7 files, +733/−244)
Test plan
make test fullpasses (Python + TypeScript + interop)make storypasses (generate → sign → verify → validate)make lintpasses