Cross-border digital identity and credential verification on Hyperledger Fabric.
Version 0.9.5. API docs at /api/docs when the server is running. License: Apache 2.0.
The API sits in front of Fabric chaincode. Public identity and credential metadata go on the ledger; PII lives in a private data collection. Redis caches identity reads. Optional IPFS stores large documents off-chain.
- Prerequisites
- Clone and configure
- Build
- Local Fabric network
- Run the API
- Run the client
- Docker
- Production-like stack
- API reference
- API examples
- Authentication
- Configuration
- Kubernetes
- HSM signing
- Integration tests
- Make targets
- Scripts
- Project layout
- Troubleshooting
- Contributing
- Go 1.25+
- Docker and Docker Compose (Docker Desktop on macOS works)
- About 8 GB RAM if you run the full Fabric test network
golangci-lintoptional locally; CI runs it viamake lint
git clone git@github.com:0xJonaseb11/hyperIdentity.git
cd hyperIdentity
cp .env.example .envEdit .env if your paths differ from the defaults. configs/config.yaml is the main app config; override with CONFIG_PATH.
make buildOutputs:
bin/chaincodebin/clientbin/api
Run unit tests:
make testCheck Docker first:
make check-dockerStart Redis (API dependency):
make docker-upBootstrap Fabric (clones fabric-samples, installs binaries, starts test-network, creates channel):
make setupDeploy chaincode with private data collection config:
make deployWait for the peer if you like:
make wait-fabricFor cross-border flows (Org1 and Org2 endorsement), verify Org2 material and deploy with the cross-border policy:
make check-org2
make deploy-cross-borderCustom endorsement policy instead of the bundled cross-border template:
export FABRIC_SIGNATURE_POLICY="AND('Org1MSP.member','Org2MSP.member')"
make deployStop the network when finished:
make teardownNeeds Redis and deployed chaincode.
CONFIG_PATH=configs/config.yaml go run cmd/api/main.goOr the binary:
CONFIG_PATH=configs/config.yaml ./bin/apiCheck:
curl http://localhost:8080/health
curl http://localhost:8080/readyOpen http://localhost:8080/api/docs for Swagger UI.
CONFIG_PATH=configs/config.yaml go run cmd/client/main.goRedis only:
make docker-upRedis and API in containers. Fabric must already run on the host (make setup and make deploy). The API container uses mounted crypto and reaches the peer via host.docker.internal.
make docker-up-apiDev stack (Redis + API, no compose profile):
make docker-devStop:
make docker-downBuild the API image:
make docker-buildLocal staging: Fabric peers on the host, Redis with AOF, API container reaching peers via host.docker.internal. Needs about 8 GB RAM.
make prod-setup
make prod-up
curl http://localhost:8080/health
curl http://localhost:8080/api/docs
make prod-smoke
make prod-down
make teardownWhat each step does:
prod-setup: starts Fabric, deploys cross-border chaincode (same as integration CI), warms chaincode via peer query, exports MSP files todeployments/fabric/crypto/(gitignored)prod-up: starts Redis and API; CI setsPROD_SMOKE_HOST_NETWORK=1so the API uses host networking,config.production-ci.yaml, andlocalhost:7051/9051for Fabricprod-smoke: checks health, ready (Redis), jurisdictions, swagger, then issues and reads an identity on the ledgerprod-down: stops Redis and API; Fabric keeps running untilmake teardown
Production config file: configs/config.production.yaml.
Export crypto without full prod setup:
make prod-export-cryptoBase URL: http://localhost:8080.
| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness |
| GET | /ready |
Readiness (Redis) |
| GET | /api/docs |
Swagger UI |
| GET | /api/openapi.yaml |
OpenAPI spec |
| GET | /api/v1/identities/{id} |
Get public identity |
| POST | /api/v1/identities |
Issue identity |
| POST | /api/v1/identities/{id}/erase |
GDPR erasure |
| GET | /api/v1/credentials/{id} |
Get credential |
| GET | /api/v1/credentials/disclosure-proof |
Merkle proof (batchId, leafIndex) |
| POST | /api/v1/credentials |
Issue credential |
| POST | /api/v1/verifications |
Verify identity |
| GET | /api/v1/migrations |
List by status (default PENDING) |
| GET | /api/v1/migrations/{id} |
Get migration |
| GET | /api/v1/migrations/{id}/endorsement |
Endorsing MSP IDs for this key |
| POST | /api/v1/migrations |
Register migration |
| POST | /api/v1/migrations/{id}/apply |
Apply migration |
| POST | /api/v1/migrations/{id}/fail |
Fail migration |
| POST | /api/v1/documents |
Store on IPFS when enabled |
| GET | /api/v1/jurisdictions |
Supported countries |
| POST | /api/v1/jurisdictions/validate |
Validate identity across countries |
When metrics_enabled is true, Prometheus metrics are on port 9090 (/metrics).
Rate limit defaults to 100 req/s per IP (rate_limit_rps in config). Sanctions screening calls POST {sanctions_api_url}/v1/check when sanctions_enabled is true.
Identity IDs must match did:hyperidentity: plus 64 hex characters.
Health:
curl http://localhost:8080/health
curl http://localhost:8080/readyWith API key (when api_key is set):
export API_KEY=your-key
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:8080/api/v1/identities/did:hyperidentity:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaIssue identity:
curl -X POST http://localhost:8080/api/v1/identities \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"id": "did:hyperidentity:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"givenNames": ["Jane"],
"familyName": "Doe",
"birthDate": 631152000,
"nationality": "US",
"issuingCountry": "US"
}'Issue credential with optional IPFS upload (ipfs_enabled: true):
DOC_B64=$(echo -n '{"type":"passport","number":"X123"}' | base64)
curl -X POST http://localhost:8080/api/v1/credentials \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d "{
\"id\": \"cred-passport-001\",
\"holderDid\": \"did:hyperidentity:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",
\"issuerDid\": \"did:hyperidentity:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",
\"type\": \"passport\",
\"documentBase64\": \"${DOC_B64}\"
}"Verify identity:
curl -X POST http://localhost:8080/api/v1/verifications \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{"identityId": "did:hyperidentity:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'Migrations:
curl -X POST http://localhost:8080/api/v1/migrations \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"id": "migration-001",
"identityDid": "did:hyperidentity:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sourceCountry": "US",
"targetCountry": "GB",
"version": "1.0.0"
}'
curl -H "X-API-Key: $API_KEY" "http://localhost:8080/api/v1/migrations?status=PENDING"
curl -H "X-API-Key: $API_KEY" "http://localhost:8080/api/v1/migrations/migration-001/endorsement"
curl -X POST -H "X-API-Key: $API_KEY" "http://localhost:8080/api/v1/migrations/migration-001/apply"
curl -X POST -H "X-API-Key: $API_KEY" "http://localhost:8080/api/v1/migrations/migration-001/fail"IPFS document store (ipfs_enabled: true):
curl -X POST http://localhost:8080/api/v1/documents \
-H "Content-Type: application/octet-stream" \
-H "X-API-Key: $API_KEY" \
--data-binary @document.pdfSet api_key in config or API_KEY in the environment.
Protected routes accept:
-H "X-API-Key: $API_KEY"
# or
-H "Authorization: Bearer $API_KEY"No key required: /health, /ready, /api/docs, /api/openapi.yaml.
| Variable | Default | Description |
|---|---|---|
CONFIG_PATH |
configs/config.yaml |
App config file |
API_PORT |
8080 |
HTTP port |
API_KEY |
optional | API auth |
REDIS_ADDR |
localhost:6379 |
Redis |
SANCTIONS_ENABLED |
false locally |
Turn on sanctions on verify |
SANCTIONS_API_KEY |
optional | Oracle bearer token |
METRICS_ENABLED |
true locally |
Prometheus server |
METRICS_PORT |
9090 |
Metrics port |
IPFS_ENABLED |
false |
IPFS uploads |
IPFS_API_URL |
http://127.0.0.1:5001 |
IPFS HTTP API |
FABRIC_MSP_ID |
Org1MSP |
MSP |
FABRIC_CHANNEL_NAME |
hyperidentity-channel |
Channel |
FABRIC_CONTRACT_NAME |
hyperidentity |
Chaincode name |
FABRIC_CERT_PATH |
see .env.example |
User cert |
FABRIC_KEY_PATH |
see .env.example |
Private key or keystore dir |
FABRIC_TLS_CERT_PATH |
see .env.example |
Peer TLS CA |
FABRIC_PEER_ENDPOINT |
localhost:7051 |
gRPC endpoint |
FABRIC_GATEWAY_PEER |
peer0.org1.example.com |
TLS hostname |
FABRIC_CC_VERSION |
1.0 |
Chaincode version |
FABRIC_CC_SEQUENCE |
1 |
Sequence; bump on upgrade |
FABRIC_SIGNATURE_POLICY |
optional | Custom deploy policy |
Org2 gateway for cross-border submits: FABRIC_ORG2_* in .env.example or fabric_org2 in YAML.
HSM build: FABRIC_HSM_* env vars and -tags=hsm (see HSM signing).
Build image and apply dev manifests:
make docker-build
make k8s-applyProduction ConfigMap and manifests:
make docker-build
make prod-export-crypto
make k8s-apply-prodCreate Fabric secrets before apply (example Org1):
kubectl create secret generic hyperidentity-fabric-org1 \
--from-file=signcerts=deployments/fabric/crypto/org1/msp/signcerts/cert.pem \
--from-file=keystore=deployments/fabric/crypto/org1/msp/keystore/$(ls deployments/fabric/crypto/org1/msp/keystore) \
--from-file=tls-ca.crt=deployments/fabric/crypto/org1/tls/ca.crtSee deployments/kubernetes/secret-fabric.example.yaml for layout.
Before you run this in production:
- Pin your container image (replace
hyperidentity/api:latest). - Mount certs and keys via Kubernetes secrets, not git.
- Set Fabric env paths on the pod to match mounts.
- Set
api_keyand require auth on/api/v1/*. - Use persistent Redis or a managed cache; scale API replicas horizontally.
SoftHSM for local PKCS#11 testing:
make setup-softhsm
make build-hsm-api
make docker-prod-build-hsmSet FABRIC_HSM_* from .env.example. Point gateway signing at your HSM cert in production.
Needs running test-network and deployed chaincode:
make setup
make deploy-cross-border
make integration-testFor Org2 gateway coverage, uncomment FABRIC_ORG2_* in .env.
Tests use the fabric_integration build tag. They skip if Fabric is down. CI runs the same flow on push to main (.github/workflows/integration.yaml).
| Target | Description |
|---|---|
make help |
List targets |
make build |
Build binaries |
make test |
Unit tests + coverage |
make integration-test |
Fabric integration tests |
make bench |
Go benchmarks |
make bench-ci |
Short bench smoke for CI |
make load-test-ci |
Load gates (-tags=loadtest) |
make lint |
golangci-lint |
make fmt |
gofmt + go mod tidy |
make clean |
Remove bin/ and coverage |
make check-docker |
Docker preflight |
make check-fabric-bin |
Peer binary present |
make check-org2 |
Org2 crypto present |
make setup |
Start test-network |
make teardown |
Stop test-network |
make deploy |
Deploy chaincode + PDC |
make deploy-cross-border |
Deploy with Org1+Org2 policy |
make deploy-domestic |
Deploy with Org1-only policy |
make wait-fabric |
Wait for peer port |
make prod-setup |
Fabric + deploy + export crypto |
make prod-up |
Production compose stack |
make prod-down |
Stop prod compose |
make prod-export-crypto |
Export MSP to deployments/fabric/crypto |
make prod-smoke |
Prod stack smoke test |
make setup-softhsm |
SoftHSM token |
make build-hsm-api |
API with -tags=hsm |
make docker-up |
Redis |
make docker-up-api |
Redis + API |
make docker-down |
Stop compose |
make docker-dev |
Dev compose |
make docker-build |
Build API image |
make docker-prod-build-hsm |
HSM API image |
make k8s-apply |
Apply k8s manifests |
make k8s-apply-prod |
Prod ConfigMap + manifests |
| Script | Purpose |
|---|---|
scripts/setup.sh |
fabric-samples, prereq, network up, channel |
scripts/deploy.sh |
Chaincode + PDC deploy |
scripts/deploy-cross-border.sh |
Cross-border endorsement policy |
scripts/teardown.sh |
Tear down test-network |
scripts/wait-for-fabric.sh |
Wait for peer |
scripts/prod-network.sh |
Full prod bootstrap |
scripts/prod-up.sh |
Start prod compose |
scripts/prod-smoke.sh |
HTTP + Fabric smoke |
scripts/export-fabric-crypto.sh |
Export MSP for mounts |
scripts/check-docker.sh |
Docker CLI/daemon check |
scripts/check-fabric-bin.sh |
Peer binary check |
scripts/check-org2.sh |
Org2 material check |
scripts/compose.sh |
Compose wrapper |
scripts/test.sh |
unit, integration, or all |
cmd/ chaincode, client, api entrypoints
internal/
chaincode/ identity, credential, migration contracts
client/ gateway, repositories, services
api/ handlers, middleware, router
pkg/ config, fabric, redis, logger
configs/ yaml, openapi, couchdb indexes, PDC, policies
deployments/ docker compose, kubernetes
scripts/ setup, deploy, smoke
test/ integration and load tests
.github/ CI workflows
Architecture:
┌──────────────┐
│ IPFS node │ optional
└──────▲───────┘
│
┌─────────────┐ ┌──────┴───────┐ ┌─────────────────────┐
│ REST API │────▶│ Client layer │────▶│ Hyperledger Fabric │
│ cmd/api │ │ + Redis cache│ │ chaincode + PDC │
└─────────────┘ └──────────────┘ └─────────────────────┘
make check-docker runs before docker targets. On macOS it also checks common Docker Desktop paths if docker is missing from PATH.
| Error | Fix |
|---|---|
docker CLI not found on PATH |
Open Docker Desktop, enable CLI tools, or add Docker bin dir to PATH |
docker daemon is not running |
Start Docker Desktop |
docker compose is not available |
Upgrade to Compose V2 plugin |
| Error | Fix |
|---|---|
peer: command not found |
Run make setup |
| Chaincode already defined | Bump FABRIC_CC_SEQUENCE, redeploy |
| Error | Fix |
|---|---|
401 on /api/v1/* |
Send X-API-Key or Bearer header |
| API bootstrap: permission denied on crypto | Run make prod-export-crypto (normalizes permissions) or make prod-setup |
/ready fails |
Check Redis; for prod compose ensure Redis container is healthy |
Policy templates: configs/fabric/endorsement/. Test-network includes Org1 and Org2 after make setup. Migrations where sourceCountry and targetCountry differ need dual-org endorsement at apply time.
Run the API in the same network as Fabric peers (same VPC or k8s cluster). Serverless hosts without peer access only work for health demos, not gateway traffic.
See CONTRIBUTING.md. Release notes: CHANGELOG.md.
Apache 2.0