Privacy-preserving verified reviews and dispute resolution on Midnight.
This project is built on the Midnight Network.
Information Flow gives every ordinary user the same account at registration. What they can do changes only after real events: a merchant records a purchase, a buyer receives a private purchase credential, a customer publishes a verified review or requests support, and an independent reviewer receives a specific escalated case. Public readers can verify the minimum public result without seeing the customer's identity, order details, or private support history.
The Wave 2 foundation includes a restartable account-first web application, a migration-driven SQLite schema, encrypted signing identities, expiring sessions, generic organizations and products, scoped authority lifecycle management, a Compact contract that stores append-only public commitments, a live Midnight deployment and reconnection test, and 27 passing ordinary tests.
The MVP treats InformationFlow as the aggregate root of the ontology:
Actor + Context + Record + Claim + View -> InformationFlow
The MVP validates private flows off-chain and stores only their public commitments on Midnight. A commitment is a cryptographic fingerprint: it can later prove that private flow data has not changed without publishing that data on-chain. The contract retains an append-only sequence of action commitments and the latest value for backward-compatible readers.
src/information-flow.ts defines the complete private aggregate and provides:
- strict runtime validation of ontology relationships;
- canonical serialization with stable ordering and Unicode normalization;
- a cryptographically secure, 32-byte nonce generator;
- a versioned, length-delimited SHA-256 commitment function.
The private InformationFlow and nonce stay off-chain. Only the resulting
64-character commitment is passed to the Compact contract.
src/behavior-derived-system.ts implements the concrete Acme Audio H1
acceptance scenario. All accounts register with the same neutral schema. The
system derives contextual actions from signed authority grants, signed purchase
credentials, organization control, credential control, and case relationships;
there is no setRole operation.
The scenario exercises four accounts from a blank state:
- Actor B receives authority to issue H1 purchase credentials.
- B signs Actor A's private credential for order
ORD-88421. - A publishes one verified H1 review without exposing A or the order.
- Actor C reads the public review but gains no private access.
- A opens support case
CASE-992; B sees only the required eligibility facts. - B records an integrity-protected support decision.
- A authorizes a recipient-, case-, purpose-, and record-bound bridge for
REG-443to Actor D. - The exact disclosure, rather than a global reviewer role, allows only D to verify that case.
src/four-actor-scenario.ts remains the deterministic protocol fixture used by
the live commitment test. The user-facing application is implemented by
src/platform-demo.ts, src/demo-server.ts, and web/. It opens with ordinary
login and registration modes plus a separate administrator-login mode, creates
every ordinary user with the same account schema, and uses an HTTP-only session
cookie. Purchase, business,
support, or case tools appear only after the corresponding credential,
authority grant, or case relationship exists. An ordinary account may request an organization, but the
bootstrap system administrator must approve it before the requester receives
organization control. The organization creator approves later membership
requests. Scoped product authority can be issued only to approved members, and
regulator capability is granted only after a separate administrator-approved
application. Existing ordinary accounts keep the same neutral account schema.
The SQLite schema lives in migrations/ and is applied explicitly through the
migration runner in src/persistence.ts. Accounts, password hashes, encrypted
Ed25519 private keys, login sessions, organizations, control relationships,
products, grants, inbox events, and immutable audit events survive a restart.
Only a SHA-256 hash of each session token is stored. Grant expiration and
revocation are evaluated by the centralized policy layer before every covered
operation.
From Linux or Ubuntu WSL, use the primary startup script:
./start.shstart.sh creates the ignored .env file with a stable cryptographically
random data-encryption key on the first run, installs missing npm dependencies,
builds the application, and starts it at http://127.0.0.1:4173. The same key
is required after every restart, so the script never replaces an existing key.
Press Ctrl+C to stop the server.
On the first run, the script stores the initial administrator as normal database
data: the account row is linked from system_administrators. Administrator
credentials remain configuration data and are never shown on the login page.
The administrator approves organization-creation and regulator applications;
the creator of an approved organization separately approves membership
requests.
Use another port or explicitly refresh dependencies when needed:
./start.sh --port 4175
./start.sh --installWindows PowerShell is an alternative entry point. It delegates to the same
start.sh implementation through WSL:
.\start.ps1
.\start.ps1 -Port 4175
.\start.ps1 -InstallOrdinary-user registration accepts any password from 8 to 128 characters and never grants administrator status. New administrators are not self-registered; they remain separately managed database records. Purchases, reviews, support cases, disclosures, and Midnight submission tracking are still in-memory Wave 1 boundaries and are the next persistence slice. Ed25519 signatures and disclosure policy are verified in the off-chain domain layer; the Compact contract anchors commitments but does not itself prove those policy decisions.
src/register-information-flow.ts connects the private domain layer to a
minimal CommitmentAnchor boundary. Registration deliberately has two phases:
- Prepare and retain the private nonce before making an unreliable network call.
- Submit only the public commitment and return a public transaction receipt.
Reusing the prepared registration retries the same commitment. The application
tests use an in-memory fake anchor, while src/midnight-commitment-anchor.ts
adapts the real Midnight callTx.registerFlow shape without exposing the
privacy-sensitive remainder of its result. src/midnight-providers.ts composes
the official provider set, src/midnight-wallet.ts provides a local-only wallet
factory, and src/midnight-composition.ts assembles those pieces with a
connected contract interface. src/midnight-contract-connection.ts supplies
the stateless deployment and public-address reconnection boundary. Persistent
commitment-submission tracking is not implemented yet, so the caller remains
responsible for securely retaining the private flow and nonce.
src/midnight-information-flow-contract.ts combines the compiler-generated
contract binding with its prover, verifier, and ZKIR artifacts as a typed
CompiledContract. This definition contains no wallet, private flow, nonce, or
network connection; later composition code will use it to deploy or connect to
the contract.
contracts/information-flow.compact stores each disclosed commitment under an
append-only Uint<64> sequence and increments flowCount. It also retains
flowCommitment as the latest value so the original single-commitment reader
continues to work. No actor ID, credential, review, support record, regulatory
record, or nonce is stored in public ledger state.
src/midnight-providers.ts assembles the Midnight.js provider set from public
indexer and proof-server endpoints plus an already-created wallet capability.
It derives a local storage scope from the wallet's public coin key and receives
the private-state encryption password only as a callback. Provider construction
does not read that password, contact the network, or submit a transaction.
src/midnight-wallet.ts is the local-only wallet boundary. It accepts a seed or
mnemonic only through an injected callback, uses the official Midnight testkit
builder without its seed-logging convenience method, starts the wallet, waits
for waitForSyncedState(), and returns a stoppable wallet capability. The
factory refuses non-undeployed networks and never returns the supplied secret.
src/midnight-composition.ts creates the application-facing runtime from an
already connected contract interface. Deployment and lookup remain explicit
operations: deployInformationFlowContract deploys the stateless compiled
contract without application data, and connectInformationFlowContract
reconnects using only its public contract address. Both return the official
Midnight.js callTx interface consumed by the narrow commitment adapter.
Midnight.js 4.1.1 requires one shared @midnight-ntwrk/onchain-runtime-v3
instance across the compiler runtime and transaction runtime. The package
override pins that transitive dependency to 3.0.0; without deduplication, two
WASM class instances cause valid contract state to fail identity checks.
Run inside Ubuntu WSL:
npm install
npm run compile
npm run typecheck
npm testThe ordinary suite uses injected fakes and makes no network requests. After the disposable services are healthy and the example accounts have been funded, run either opt-in live check from Windows PowerShell:
npm run test:live
npm run test:live:scenarioBoth live checks read the first disposable account from the adjacent Local Dev
checkout in memory, create a random compliant storage password and isolated
temporary LevelDB path, deploy the contract, and reconnect by public address.
test:live submits one fixed 64-character commitment and checks both the latest
value and sequence position zero. test:live:scenario executes the four-actor
domain journey, submits its four action commitments, and asserts that indexed
positions zero through three equal the prepared review, support request, signed
decision, and regulatory-bridge commitments. The scripts print only public
addresses, transaction IDs, commitments, and aggregate test evidence; they
never print or copy the mnemonic.
The official
Midnight Local Dev
checkout lives beside this repository at ../midnight-local-dev and is pinned
for this MVP at commit 902561ddc27a4b096f19835ab1528f38ace515f1.
It runs three localhost-only services:
| Service | Endpoint | Pinned image |
|---|---|---|
| Midnight node | http://127.0.0.1:9944 |
midnight-node:1.0.0 |
| Indexer GraphQL | http://127.0.0.1:8088/api/v4/graphql |
indexer-standalone:4.3.3 |
| Proof server | http://127.0.0.1:6300 |
proof-server:8.1.0 |
Start or resume the network from Windows PowerShell:
cd ..\midnight-local-dev
docker compose -p midnight-local-dev -f standalone.yml up -d
docker compose -p midnight-local-dev -f standalone.yml psFor a fresh local ledger, initialize the repository's local-only example accounts with NIGHT and DUST:
npm start -- --fund-config .\accounts.example.jsonNever reuse those example mnemonics outside this disposable local network, and never copy them into this repository. To stop the services without deliberately resetting the ledger:
docker compose -p midnight-local-dev -f standalone.yml stopCopy .env.example to an ignored .env only when an application adapter needs
the endpoint values. It contains no wallet seed or signing secret.
On this workstation, run Local Dev and Docker commands from Windows PowerShell; Docker integration is not enabled inside Ubuntu WSL. Continue to run this repository's TypeScript and Compact checks inside WSL.
Docker Desktop 4.91.0 is installed, but this Windows host can still hit Docker's
open stale AF_UNIX socket issue after an interrupted shutdown. The safe recovery
used here was to stop Docker completely, rename the exact Docker\run and
docker-secrets-engine runtime directories to timestamped backups, create a new
empty Docker\run directory, and start Docker again. Do not use Reset to
factory defaults for this symptom; consult the recorded procedure in
AGENTS.md and the
upstream issue first.
Run inside Ubuntu WSL:
npm run compileThe generated contract bindings are written to
contracts/managed/information-flow/ and are intentionally not committed.