Quickstart
This flow creates a DID in the standalone environment, publishes key material, resolves the DID Document, and verifies a SchnorrJubjub signature against the key stored in ledger state.
External consumers should first follow Adopt and migrate to 0.6 to install the exact published 0.6.0 packages and bootstrap the matching ZK bundle. Check the exact release-tested toolchain and runtime pins in the compatibility matrix. The commands below otherwise assume a source checkout of this repository.
Prerequisites
- Node.js 24 and pnpm 10.
- The standalone Midnight services required by
./run.sh api --light --strict. - For a source checkout, Compact compiler
0.31.1and repository dependencies:
pnpm install
compact update 0.31.1Prefer A Runnable Issuer Bootstrap
Use the issuer bootstrap example when you want a complete TypeScript flow with real key creation and ledger publication:
ISSUER_BOOTSTRAP_SEED=<hex-seed> \
ISSUER_KEYSTORE_OUT="$PWD/issuer-keystore.json" \
INDEXER_URL=http://127.0.0.1:8088/api/v4/graphql \
NODE_RPC_URL=http://127.0.0.1:9944 \
PROOF_SERVER_URL=http://127.0.0.1:6300 \
pnpm exec ts-node --esm packages/api/examples/bootstrap-issuer-did.tsThe script creates and publishes an Ed25519 authentication verification method and a SchnorrJubjub assertionMethod verification method, resolves the DID Document to confirm both relations, and writes the local signing secrets to the issuer keystore. See API Examples for the output shape and source link. See Network Endpoints for the canonical profile matrix and standalone endpoint defaults.
The rest of this page shows the same API surface as smaller steps.
Create Providers And Deploy
import {
StandaloneConfig,
buildFreshWallet,
configureProviders,
deploy,
initPrivateState,
} from "@midnight-ntwrk/midnight-did-api";
const config = new StandaloneConfig();
const walletContext = await buildFreshWallet(config);
const providers = await configureProviders(walletContext, config);
await initPrivateState(providers);
const didContract = await deploy(providers);The deployed contract address is the DID subject:
const contractAddress = didContract.deployTxData.public.contractAddress;
const did = `did:midnight:undeployed:${contractAddress}`;Add An Interoperable JWK Key
Use addVerificationMethod for Ed25519, X25519, P-256, secp256k1, BLS12381G1, and BLS12381G2 publicKeyJwk entries.
import { addVerificationMethod } from "@midnight-ntwrk/midnight-did-api";
import {
createVerificationMethod,
CurveType,
KeyType,
VerificationMethodType,
} from "@midnight-ntwrk/midnight-did-domain";
const ed25519Method = createVerificationMethod({
id: "#ed25519-1",
type: VerificationMethodType.JsonWebKey,
controller: did,
publicKeyJwk: {
kty: KeyType.OKP,
crv: CurveType.Ed25519,
x: "11qYAYdkWdtN8H1V1hdY9zFQd9Kq4o2P4pX8zM1xL3A",
},
});
await addVerificationMethod(didContract, providers, ed25519Method);Add A SchnorrJubjub Key
Use the SchnorrJubjub helper when the key must be usable by Midnight-native proof circuits.
import { addSchnorrJubjubVerificationMethod } from "@midnight-ntwrk/midnight-did-api";
import { deriveJubjubPublicKeyFromSeed } from "@midnight-ntwrk/midnight-did-jubjub-schnorr";
const jubjubSeed = crypto.getRandomValues(new Uint8Array(32));
const jubjubPublicKey = deriveJubjubPublicKeyFromSeed(jubjubSeed);
await addSchnorrJubjubVerificationMethod(didContract, providers, {
id: "#jubjub-1",
publicKey: jubjubPublicKey,
});Persist jubjubSeed in your application key store. The DID contract stores only the public JubjubPoint.
Link A Key To A Relationship
import { addVerificationMethodRelation } from "@midnight-ntwrk/midnight-did-api";
import { VerificationMethodRelationType } from "@midnight-ntwrk/midnight-did-domain";
await addVerificationMethodRelation(
didContract,
providers,
VerificationMethodRelationType.Authentication,
"#ed25519-1",
);Resolve The DID
import { resolve } from "@midnight-ntwrk/midnight-did-api";
const resolution = await resolve(providers, did);
console.log(resolution.didDocument);The resolver returns one DID Document with both key maps merged into verificationMethod.
Verify A SchnorrJubjub Signature
import { verifySchnorrJubjubDigestSignature } from "@midnight-ntwrk/midnight-did-api";
import {
payloadToJubjubDigest,
signJubjubDigestFromSeed,
} from "@midnight-ntwrk/midnight-did-jubjub-schnorr";
const payload = new TextEncoder().encode("example payload");
const digest = payloadToJubjubDigest(payload);
const signature = signJubjubDigestFromSeed(jubjubSeed, digest);
await verifySchnorrJubjubDigestSignature(
didContract,
providers,
"#jubjub-1",
digest,
signature,
);The verifier resolves the canonical method identity to the sole existing canonical or compatible legacy ledger key before submitting the transaction. The caller does not supply a public key, so the proof is tied to the current DID Document state.
Next Steps
- Keep consumer packages and ZK artifacts coordinated with Adopt and migrate to 0.6.
- Review the Key Model before choosing key profiles.
- Use Network Endpoints when selecting a runtime profile.
- Use Compact Contract Surface before changing circuits.
- Use Local Development when preparing a PR.