@midnight-ntwrk/credential-did-midnight
Release stage:
supportedMaturity:core extensionPackage class:dist
Composable Midnight DID binding for the protocol-independent VC/VP primitives in @midnight-ntwrk/credential-compact.
See package changelog for package release history.
Install
pnpm add -E @midnight-ntwrk/credential-did-midnight@0.2.00.2.0 is the current stable release. It installs the matching Compact core dependency. To evaluate the current 0.3 release candidate explicitly, install @midnight-ntwrk/credential-did-midnight@0.3.0-rc1 and keep the model and Compact packages on that same version. Confirm the moving rc tags before adopting the prerelease graph.
The TypeScript adapter resolves an on-chain did:midnight document through an injected structural MidnightDIDResolutionSource. The published @midnight-ntwrk/midnight-did 0.7 resolver satisfies that interface, but the full resolver implementation is not a runtime dependency of this package. The adapter binds a subject-owned native Jubjub verification method to the core VerificationMethodRef, the observed DID state version, and one supported verification relationship. Bounded software helpers can sign credential and presentation proofs with that method. The package does not store keys, deploy or mutate a DID, select a trust policy, or call another contract.
The supported resolver profile is Midnight DID 0.7.0. Jubjub JWK coordinates are canonical unpadded base64url of exactly 32 unsigned big-endian bytes and are decoded with @midnight-ntwrk/midnight-did-domain's public codec. Persisted 0.6 little-endian DID-document snapshots must be re-resolved through a 0.7 resolver or explicitly migrated from authenticated 0.6 provenance. The adapter does not auto-detect the byte order. Not every 0.6 snapshot is detectably invalid under the 0.7 decoder, so supplying one directly can silently bind a different native point rather than fail.
Ledger 8 security boundary: this package's binding circuits compare DID references and keys; they do not verify signatures or prove that resolved DID state is current. Always compose them with the matching core context-proof circuit over a root recomputed from the complete VC/VP input, then pin the accepted binding root or verify an authority-signed descriptor.
Migrating Midnight DID 0.6 snapshots
Prefer re-resolution: resolve the DID through a Midnight DID 0.7 resolver, then call resolveMidnightDIDMethodBinding with that resolver, DID, method ID, and required verification relationship. This validates the current DID document and captures its observed resolver versionId in the binding.
When re-resolution is unavailable, an external migration must authenticate the snapshot's DID, method, relationship, positive observed versionId, and 0.6 encoding profile. It must decode the known little-endian coordinates and re-encode the same native point as canonical 0.7 JWK coordinates while preserving the authenticated binding fields. The provenance must also authenticate that the DID was not deactivated at that observed historical state. Expose the migrated document through a snapshot-backed MidnightDIDResolutionSource, then call resolveMidnightDIDMethodBinding; do not construct a MidnightDIDMethodBinding directly. The adapter will still enforce the on-chain subject, subject-owned method, relationship membership, native Jubjub profile, canonical method ID, and positive uint64 state version. This package does not authenticate or migrate the snapshot itself.
An offline snapshot cannot prove current DID activation or method state. Never try both byte orders or use versionId as an encoding marker. Discard or quarantine the old snapshot after either path. See the normative canonical mapping for the required validation and migration boundary.
Resolve a current method
import {
createMidnightDIDSignerDescriptor,
resolveMidnightDIDMethodBinding,
} from "@midnight-ntwrk/credential-did-midnight";
import {
AuthorizationState,
SignerRole,
} from "@midnight-ntwrk/credential-compact";
const method = await resolveMidnightDIDMethodBinding({
resolver,
did,
verificationMethodId: "#issuer-key",
relationship: "assertionMethod",
});
const descriptor = createMidnightDIDSignerDescriptor(method, {
authorizationId,
decisionSequence: 1n,
state: AuthorizationState.active,
role: SignerRole.issuer,
scopeCommitment,
policyCommitment,
});Signing credential and presentation proofs
The signing helpers accept a software-held Jubjub secret scalar and require it to match the resolved method binding. They derive each nonce with HMAC-SHA-512 over the secret, operation domain, signed inputs, method reference, fresh platform entropy, and retry counter. Secret and signed-input binding preserves per-message separation even if the entropy source repeats; the helper retries only when the derived scalar is zero. Each completed proof is verified before it is returned.
import {
signMidnightDIDCredentialProof,
signMidnightDIDPresentationProof,
} from "@midnight-ntwrk/credential-did-midnight";
const credentialProof = signMidnightDIDCredentialProof({
methodBinding: issuerMethod,
secretScalar: issuerSecretScalar,
bodyRoot: credentialBodyRoot,
createdAt: issuedAt,
challengeHash: issuanceContextHash,
});
const presentationProof = signMidnightDIDPresentationProof({
methodBinding: holderMethod,
secretScalar: holderSecretScalar,
bodyRoot: presentationBodyRoot,
createdAt: presentedAt,
challengeHash: verifierChallengeHash,
});signMidnightDIDCredentialProof requires an assertionMethod binding; signMidnightDIDPresentationProof requires an authentication binding. The caller remains responsible for secure scalar storage and for the application semantics of createdAt and challengeHash. bodyRoot must be derived with the matching core circuit from the complete credential or presentation; signing an arbitrary caller-provided root does not authenticate a different envelope.
Do not substitute @midnight-ntwrk/midnight-did-jubjub-schnorr's signJubjubPayloadFromSeed. That operation intentionally uses the DID contract's payload-digest challenge domain. These helpers reuse its Jubjub key and scalar primitives but use the VC core's issuance or presentation challenge. Wallet and hardware-backed signers should implement that same two-phase nonce/challenge/response operation without exporting their secret scalar.
methodId is SHA-256(UTF-8(fragment)) over the exact canonical, case-sensitive fragment, including #. Only on-chain Midnight DIDs are accepted because the core controller is a Compact ContractAddress.
Compact composition
Add each installed package's dist directory to the Compact compiler search path. On Unix-like systems, for example:
compact compile --compact-path \
"node_modules/@midnight-ntwrk/credential-compact/dist:node_modules/@midnight-ntwrk/credential-did-midnight/dist" \
src/contract.compact managed/contractUse the standalone entrypoint when the consumer does not already include the VC core:
include "did-midnight";Use the composition entrypoint when assembling a larger contract. Include the core root exactly once, then this extension:
include "credentials/composable";
include "did-midnight/composable";The extension exports MidnightDIDMethodBinding, MidnightDIDHolderBinding, and circuits that validate the method binding, bind a proof or holder, bind an authorized signer descriptor, and derive a stable binding root.
The exported assertMidnightDID* circuits are low-level equality checks, not proof-of-possession checks. For issuance use VC<>::assertValidCredentialProof only for cryptographic validity of the self-declared issuer. Authorization-aware issuance MUST use VC<>::assertAuthorizedIssuerProof unless equivalent authorization is enforced independently. For presentation, use ExplicitHolderPresentationProof<>::assertValidPresentationProof with the credential and an independently accepted MidnightDIDHolderBinding.methodBinding.publicKey, followed by assertMidnightDIDHolderBinding to validate the authentication relationship and the proof and holder references against the supplied DID method snapshot. The holder helper does not independently accept the snapshot's state version. On Ledger 8 the complete method-binding root must be pinned or authenticated outside the circuit; supplying a matching binding and key as an untrusted witness does not establish DID authorization. Family-specific disclosure relations remain separate. Authority and verifier decisions likewise require the core assertValidSignerAuthorizationProof or assertAuthorizedVerifierProof circuit.
The packed-package release gate compiles and executes one synthetic composition using only public tarball surfaces. It resolves separate issuer and holder Midnight DID methods, validates nonempty family metadata with @midnight-ntwrk/credential-model, signs typed credential and presentation bodies, and rejects credential-claim, disclosure, body, issuer, holder, method, key, relationship, context, and signature substitution. This is package-composition evidence, not an issuance protocol, product credential family, wallet flow, or trust-policy implementation.
Ledger 8 trust boundary
These circuits prove internal consistency between values already supplied to a consumer contract. They cannot query the DID contract on Ledger 8 and therefore do not prove that a method is current. The consumer must pin an accepted binding root or verify an authority-signed descriptor before relying on it. DID and Trust Registry policy stays outside this package.
The supported build profile is Compact 0.31.1, runtime 0.16.0, and Ledger 8.0.2. Ledger 9 cross-contract validation is future work.
Compatibility and support
- The TypeScript surface is ESM-only and supports Node.js 24 or newer.
- The adapter profile is Midnight DID
0.7.0; it is not compatible with raw 0.6 little-endian DID-document snapshots. - The Compact profile is compiler
0.31.1, runtime0.16.0, and Ledger8.0.2. - During
0.x, breaking API changes may ship in a minor release. Keep exact versions pinned and read package changelog before upgrading. - Release candidates are supported only until a newer candidate or stable release in the same minor line is published.
Technical ownership belongs to @midnightntwrk/ex-identus. Release operations belong to @midnightntwrk/mn-sre. Security reports follow the repository security policy process.