Preview release.These docs are a work in progress. Pages are still being written, links may break, and structure may shift without notice. Treat everything here as a draft and report issues onGitHub.
Use this tutorial to run Registry Notary locally, query OpenCRVS for a test birth record, and issue a local demo Selective Disclosure JSON Web Token Verifiable Credential (SD-JWT VC).
This tutorial does not cover JSON claim results or batch evaluation. Those are system-to-system evaluation topics.
What you will test
Section titled “What you will test”You will:
- Generate a local Registry Notary config for OpenCRVS.
- Add OpenCRVS birth-record query settings.
- Add OpenCRVS OAuth client credentials.
- Check the live OpenCRVS connection with a known test UIN.
- Issue a demo SD-JWT VC from the OpenCRVS evidence result.
Registry Notary runs locally at:
http://127.0.0.1:4255Registry Notary calls the OpenCRVS API at:
https://dci-crvs-api.farajaland-integration.opencrvs.devThe tutorial evaluates this claim:
opencrvs-birth-record-existsBefore you start
Section titled “Before you start”You need:
- A
registry-notarybinary built from theregistry-notaryrepository. curl.jq.- An OpenCRVS OAuth client ID and client secret.
- A known test UIN from the OpenCRVS environment owner.
From the registry-notary repository root, build the binary when you do not
already have one:
export REGISTRY_NOTARY_REPO="$PWD"cargo build --release -p registry-notaryexport PATH="$PWD/target/release:$PATH"registry-notary --helpCreate the local config
Section titled “Create the local config”Create a working folder:
mkdir -p "$HOME/opencrvs-notary-demo"cd "$HOME/opencrvs-notary-demo"Generate a generic DCI starter config:
registry-notary init dci \ --with-env-file \ --demo-issuer \ --base-url https://dci-crvs-api.farajaland-integration.opencrvs.dev \ --token-url https://dci-crvs-api.farajaland-integration.opencrvs.dev/oauth2/client/token \ --lookup-field UIN \ --claim-id opencrvs-birth-record-exists \ --claim-title "OpenCRVS birth record exists"The command writes dci-notary.yaml, .env.local, .env.local.example, and
README.dci.md.
--demo-issuer creates local Registry Notary signing material for the demo
credential. It does not create an OpenCRVS credential.
Configure the OpenCRVS query
Section titled “Configure the OpenCRVS query”Open dci-notary.yaml.
Under evidence.source_connections.dci_registry.dci, add the OpenCRVS registry
filters:
dci: search_path: /registry/sync/search sender_id: registry-notary query_type: idtype-value registry_type: ns:org:RegistryType:Civil registry_event_type: birth records_path: /message/search_response/0/data/reg_recordsIn the generated source binding, change the lookup to read the UIN from the request identifier:
lookup: input: target.identifiers.UIN field: UIN op: eq cardinality: oneAdd OpenCRVS credentials
Section titled “Add OpenCRVS credentials”Edit .env.local:
DCI_CLIENT_ID=paste-client-id-hereDCI_CLIENT_SECRET=paste-client-secret-hereKeep .env.local private:
chmod 600 .env.localDo not add an OpenCRVS bearer token. Registry Notary fetches and refreshes the source token from the configured OAuth client-credentials endpoint.
Check the OpenCRVS connection
Section titled “Check the OpenCRVS connection”Run local config checks:
registry-notary doctor \ --config dci-notary.yaml \ --env-file .env.localSet the known test UIN:
export OPENCRVS_DEMO_SUBJECT_UIN='<known test UIN>'Run the live OAuth and record probe:
registry-notary doctor \ --config dci-notary.yaml \ --env-file .env.local \ --live \ --target-id "$OPENCRVS_DEMO_SUBJECT_UIN"doctor must not print the source token or target UIN.
Start Registry Notary
Section titled “Start Registry Notary”Start the server:
registry-notary \ --config dci-notary.yaml \ --env-file .env.localIn another terminal, load the local API key from .env.local:
cd "$HOME/opencrvs-notary-demo"set -a. ./.env.localset +aEvaluate the OpenCRVS claim
Section titled “Evaluate the OpenCRVS claim”Evaluate the claim in SD-JWT VC format and capture the stored evaluation ID:
EVALUATION_ID="$( curl -fsS http://127.0.0.1:4255/v1/evaluations \ -H "content-type: application/json" \ -H "x-api-key: $REGISTRY_NOTARY_API_KEY" \ -H "data-purpose: https://demo.example.gov/purpose/opencrvs-dci" \ -d '{ "target": { "type": "Person", "identifiers": [ { "scheme": "UIN", "value": "'"$OPENCRVS_DEMO_SUBJECT_UIN"'", "issuer": "opencrvs" } ] }, "relationship": { "type": "service_delivery" }, "claims": ["opencrvs-birth-record-exists"], "disclosure": "value", "format": "application/dc+sd-jwt" }' | jq -r '.results[0].evaluation_id')"
test -n "$EVALUATION_ID"Issue the demo credential
Section titled “Issue the demo credential”Issue the demo credential from the stored evaluation:
curl -fsS http://127.0.0.1:4255/v1/credentials \ -H "content-type: application/json" \ -H "x-api-key: $REGISTRY_NOTARY_API_KEY" \ -H "data-purpose: https://demo.example.gov/purpose/opencrvs-dci" \ -d '{ "evaluation_id": "'"$EVALUATION_ID"'", "credential_profile": "dci_record_sd_jwt", "claims": ["opencrvs-birth-record-exists"], "disclosure": "value", "format": "application/dc+sd-jwt" }' | jq '{ credential_profile, format, credential_present: (.credential != null) }'Expected result:
{ "credential_profile": "dci_record_sd_jwt", "format": "application/dc+sd-jwt", "credential_present": true}The credential is a demo SD-JWT VC issued by Registry Notary from OpenCRVS
evidence. It is not an OpenCRVS-issued credential.
The generated dci_record_sd_jwt profile sets holder_binding.mode: none, so
this direct demo request does not require wallet holder material.
Issue a birth-attributes credential
Section titled “Issue a birth-attributes credential”Now issue a second credential from the same OpenCRVS record. This credential discloses the child’s given name, family name, date of birth, and place of birth. Use it only when the relying party needs those attributes, because it exposes more personal data than the boolean existence credential.
Copy the provided config into your demo folder:
cd "$HOME/opencrvs-notary-demo"cp "$REGISTRY_NOTARY_REPO/demo/config/opencrvs-dci-birth-attributes-registry-notary.yaml" \ ./opencrvs-birth-attributes-notary.yamlThe config uses these OpenCRVS record paths:
field_paths: child_given_name: /name/given_name child_family_name: /name/surname child_birth_date: /birth_date child_place_of_birth: /birth_placeIt models each attribute as an extract claim, then issues one SD-JWT VC
profile named opencrvs_birth_attributes_sd_jwt that allows those claims.
Run the checks:
registry-notary doctor \ --config opencrvs-birth-attributes-notary.yaml \ --env-file .env.local \ --live \ --target-id "$OPENCRVS_DEMO_SUBJECT_UIN"Stop the first Registry Notary process, then start the attribute config:
registry-notary \ --config opencrvs-birth-attributes-notary.yaml \ --env-file .env.localEvaluate and issue the attribute credential:
ATTRIBUTE_EVALUATION_ID="$( curl -fsS http://127.0.0.1:4255/v1/evaluations \ -H "content-type: application/json" \ -H "x-api-key: $REGISTRY_NOTARY_API_KEY" \ -H "data-purpose: https://demo.example.gov/purpose/opencrvs-dci" \ -d '{ "target": { "type": "Person", "identifiers": [ { "scheme": "UIN", "value": "'"$OPENCRVS_DEMO_SUBJECT_UIN"'", "issuer": "opencrvs" } ] }, "relationship": { "type": "service_delivery" }, "claims": [ "opencrvs-child-given-name", "opencrvs-child-family-name", "opencrvs-child-date-of-birth", "opencrvs-child-place-of-birth" ], "disclosure": "value", "format": "application/dc+sd-jwt" }' | jq -r '.results[0].evaluation_id')"
curl -fsS http://127.0.0.1:4255/v1/credentials \ -H "content-type: application/json" \ -H "x-api-key: $REGISTRY_NOTARY_API_KEY" \ -H "data-purpose: https://demo.example.gov/purpose/opencrvs-dci" \ -d '{ "evaluation_id": "'"$ATTRIBUTE_EVALUATION_ID"'", "credential_profile": "opencrvs_birth_attributes_sd_jwt", "claims": [ "opencrvs-child-given-name", "opencrvs-child-family-name", "opencrvs-child-date-of-birth", "opencrvs-child-place-of-birth" ], "disclosure": "value", "format": "application/dc+sd-jwt" }' | jq '{ credential_profile, format, credential_present: (.credential != null) }'Expected result:
{ "credential_profile": "opencrvs_birth_attributes_sd_jwt", "format": "application/dc+sd-jwt", "credential_present": true}Optional: use name and date of birth instead of UIN
Section titled “Optional: use name and date of birth instead of UIN”If your OpenCRVS environment supports expression search over birth-record fields, use the demographic demo config instead of the UIN lookup config:
demo/config/opencrvs-dci-demographic-registry-notary.yamlThat config queries OpenCRVS with these target attributes:
dci: query_type: expression
source_bindings: birth_record: lookup: input: target.attributes.given_name field: given_name op: eq cardinality: one query_fields: - input: target.attributes.given_name field: given_name op: eq - input: target.attributes.family_name field: surname op: eq - input: target.attributes.birthdate field: birth_date op: eqThe evaluation request uses attributes instead of identifiers:
{ "target": { "type": "Person", "attributes": { "given_name": "Amina", "family_name": "Diallo", "birthdate": "2020-01-02" } }, "relationship": { "type": "service_delivery" }, "claims": ["opencrvs-birth-record-exists-by-demographics"], "disclosure": "value", "format": "application/dc+sd-jwt"}Use it when the tester knows the exact first name, last name, and date of birth in the OpenCRVS test environment. If the same demographic combination can match more than one birth record, Registry Notary rejects the result as ambiguous.
The same binding-level query_fields shape is available for
registry_data_api. Use
demo/config/opencrvs-rda-demographic-registry-notary.yaml when the source is a
Registry Relay endpoint instead of a DCI endpoint. Relay must allow filters on
given_name, surname, and birth_date.
Known limitations
Section titled “Known limitations”- The supported DCI query shape is
idtype-valuewithquery.type = UIN. - The birth-attributes credential uses UIN lookup. The supported OpenCRVS
record paths are
/name/given_name,/name/surname,/birth_date, and/birth_place. - The demographic demo config uses DCI
expressionquery fields for OpenCRVS deployments that expose first-name, last-name, and date-of-birth search. The Farajaland (the OpenCRVS demo environment) integration endpoint does not narrow expression searches (this is an external endpoint limitation), so the UIN path is the supported credential path for that environment. - The supported event filter is
registry_event_type: birth. - The OpenCRVS middleware accepts unsigned requests.
- Death record checks require a separate source connection or claim with
registry_event_type: death.
Troubleshooting
Section titled “Troubleshooting”If doctor reports a missing DCI_CLIENT_ID or DCI_CLIENT_SECRET, check that
.env.local contains non-placeholder values and that you passed
--env-file .env.local.
If the live OAuth check fails, check the OpenCRVS client ID, client secret, and token URL.
If OpenCRVS returns HTTP 400, check these values in dci-notary.yaml:
query_type: idtype-valuelookup.input: target.identifiers.UINlookup.field: UINregistry_type: ns:org:RegistryType:Civilregistry_event_type: birthrecords_path: /message/search_response/0/data/reg_records
If OpenCRVS returns no records, ask the OpenCRVS environment owner to confirm that the test UIN exists in that environment.
Security notes
Section titled “Security notes”- Do not commit
.env.local. - Do not commit OpenCRVS client credentials, bearer tokens, test UINs, or generated issuer private keys.
- Do not store OpenCRVS access tokens in the config or env file.
- The generated demo credential profile uses
holder_binding.mode: nonefor direct local issuance. For citizen-wallet issuance, create a profile withholder_binding.mode: did,proof_of_possession: required, andallowed_did_methods: [did:jwk].