nightpay 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/bin/cli.js +56 -0
- package/package.json +39 -0
- package/skills/nightpay/SKILL.md +112 -0
- package/skills/nightpay/contracts/receipt.compact +195 -0
- package/skills/nightpay/openclaw-fragment.json +38 -0
- package/skills/nightpay/rules/content-safety.md +187 -0
- package/skills/nightpay/rules/escrow-safety.md +132 -0
- package/skills/nightpay/rules/privacy-first.md +30 -0
- package/skills/nightpay/rules/receipt-format.md +45 -0
- package/skills/nightpay/scripts/bounty-board.sh +325 -0
- package/skills/nightpay/scripts/gateway.sh +578 -0
- package/skills/nightpay/scripts/mip003-server.sh +174 -0
- package/skills/nightpay/scripts/update-blocklist.sh +194 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Content Safety Rule
|
|
2
|
+
|
|
3
|
+
## Principle: Classify-Then-Forget
|
|
4
|
+
|
|
5
|
+
The gateway sees the plaintext job description **in memory only** — long enough to
|
|
6
|
+
classify it, then immediately hashes it. The plaintext is never logged, persisted,
|
|
7
|
+
or transmitted. This preserves funder privacy while enforcing safety.
|
|
8
|
+
|
|
9
|
+
## Three-Layer Defense
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Layer 1: Live rules file (auto-updated by update-blocklist.sh)
|
|
13
|
+
|
|
|
14
|
+
v if no rules file exists
|
|
15
|
+
Layer 2: Hardcoded fallback (14 patterns baked into gateway.sh)
|
|
16
|
+
|
|
|
17
|
+
v if local rules pass
|
|
18
|
+
Layer 3: External moderation API (AI-powered classification, optional)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Every bounty must pass **all available layers** before a commitment is created.
|
|
22
|
+
|
|
23
|
+
## What is Rejected
|
|
24
|
+
|
|
25
|
+
Any bounty whose job description matches one or more of these categories:
|
|
26
|
+
|
|
27
|
+
| Category | Examples |
|
|
28
|
+
|---|---|
|
|
29
|
+
| **Child sexual abuse material (CSAM)** | Any content sexualizing minors |
|
|
30
|
+
| **Violence / assassination** | "Kill", "harm", "attack [person]", physical threats |
|
|
31
|
+
| **Weapons of mass destruction** | Biological, chemical, nuclear, radiological |
|
|
32
|
+
| **Human trafficking / slavery** | Forced labor, organ harvesting, exploitation |
|
|
33
|
+
| **Terrorism / extremism** | Recruitment, planning, financing of terror acts |
|
|
34
|
+
| **Non-consensual intimate imagery** | Deepfakes, revenge content, sextortion |
|
|
35
|
+
| **Financial fraud** | Money laundering, counterfeiting, sanctions evasion |
|
|
36
|
+
| **Critical infrastructure attack** | Power grids, water systems, hospitals, elections |
|
|
37
|
+
| **Doxxing / stalking** | Identifying, tracking, or surveilling private individuals |
|
|
38
|
+
| **Drug manufacturing** | Synthesis of controlled substances (not research) |
|
|
39
|
+
|
|
40
|
+
This list is not exhaustive. The live rules file and external API extend coverage.
|
|
41
|
+
|
|
42
|
+
## Enforcement Points
|
|
43
|
+
|
|
44
|
+
Two gates, defense-in-depth:
|
|
45
|
+
|
|
46
|
+
1. **`post-bounty`** — before commitment hash is created. Rejected bounties never
|
|
47
|
+
produce a commitment and no funds move.
|
|
48
|
+
2. **`hire-and-pay`** — before Masumi escrow is created. Catches descriptions that
|
|
49
|
+
were committed outside the gateway (e.g. direct circuit call).
|
|
50
|
+
|
|
51
|
+
## What Happens on Rejection
|
|
52
|
+
|
|
53
|
+
- The gateway prints a `REJECTED` status with the matched category
|
|
54
|
+
- No commitment is created, no hash is emitted, no funds move
|
|
55
|
+
- The plaintext description is **not logged** — only the category name
|
|
56
|
+
- Exit code 2 (distinct from validation errors which use exit code 1)
|
|
57
|
+
|
|
58
|
+
## Auto-Updating Rules (update-blocklist.sh)
|
|
59
|
+
|
|
60
|
+
The static regex list goes stale. `update-blocklist.sh` keeps it current:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Run every 6 hours via cron
|
|
64
|
+
0 */6 * * * /path/to/scripts/update-blocklist.sh
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Sources
|
|
68
|
+
|
|
69
|
+
| Source | What It Provides | Update Frequency |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| **Base rules** (hardcoded) | 14 core patterns for the 10 categories | Static — code changes only |
|
|
72
|
+
| **stamparm/maltrail** | Malicious campaign names, known-bad keywords | Daily (GitHub raw) |
|
|
73
|
+
| **Community complaints** | Patterns derived from user reports that hit freeze threshold | Real-time (on flag) |
|
|
74
|
+
| **Operator custom rules** | `~/.nightpay/safety/custom-rules.json` | Operator-managed |
|
|
75
|
+
|
|
76
|
+
### Custom Rules Format
|
|
77
|
+
|
|
78
|
+
Operators can add domain-specific patterns in `~/.nightpay/safety/custom-rules.json`:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"rules": [
|
|
83
|
+
{"category": "scam", "pattern": "\\b(ponzi|pyramid)\\b.*\\b(scheme|invest)\\b"},
|
|
84
|
+
{"category": "gambling", "pattern": "\\b(casino|betting|slots)\\b"}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Invalid regex patterns are skipped with a warning, not fatal.
|
|
90
|
+
|
|
91
|
+
### Output
|
|
92
|
+
|
|
93
|
+
Rules are merged, deduplicated, and written atomically to:
|
|
94
|
+
```
|
|
95
|
+
~/.nightpay/safety/safety-rules.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The gateway hot-loads this file on every `safety_check()` call — no restart required.
|
|
99
|
+
|
|
100
|
+
## Community Complaint System
|
|
101
|
+
|
|
102
|
+
Anyone can report a bounty they believe is harmful:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Report a bounty (REPORTER_ID is hashed for privacy-preserving dedup)
|
|
106
|
+
REPORTER_ID=alice ./bounty-board.sh report <commitment> <category> [reason]
|
|
107
|
+
|
|
108
|
+
# View complaints for a specific bounty
|
|
109
|
+
./bounty-board.sh reports <commitment>
|
|
110
|
+
|
|
111
|
+
# View all flagged bounties
|
|
112
|
+
./bounty-board.sh reports
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Valid Report Categories
|
|
116
|
+
|
|
117
|
+
`csam`, `violence`, `weapons_of_mass_destruction`, `human_trafficking`,
|
|
118
|
+
`terrorism`, `ncii`, `financial_fraud`, `infrastructure_attack`,
|
|
119
|
+
`doxxing`, `drug_manufacturing`, `other`
|
|
120
|
+
|
|
121
|
+
### Auto-Freeze
|
|
122
|
+
|
|
123
|
+
When a bounty reaches **3 complaints** (configurable via `COMPLAINT_FREEZE_THRESHOLD`):
|
|
124
|
+
|
|
125
|
+
1. Bounty status changes from `active` to `flagged`
|
|
126
|
+
2. Flagged bounties are hidden from `list` (no longer discoverable)
|
|
127
|
+
3. The complaint data is exported to `community-reports.json`
|
|
128
|
+
4. On next `update-blocklist.sh` run, complaint patterns feed back into rules
|
|
129
|
+
|
|
130
|
+
### Privacy Guarantees for Reporters
|
|
131
|
+
|
|
132
|
+
- Reporter identity is **SHA-256 hashed** before storage — only used for dedup
|
|
133
|
+
- The `REPORTER_ID` env var is never persisted in the database
|
|
134
|
+
- Complaint reasons are stored but the bounty description is not (it was never stored)
|
|
135
|
+
- Reporters cannot see other reporters' identities
|
|
136
|
+
|
|
137
|
+
### Feedback Loop
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
User reports bounty → complaint stored in SQLite
|
|
141
|
+
→ threshold hit → bounty auto-frozen
|
|
142
|
+
→ complaint categories exported to community-reports.json
|
|
143
|
+
→ update-blocklist.sh reads community-reports.json
|
|
144
|
+
→ trending complaint categories become new regex rules
|
|
145
|
+
→ gateway.sh loads updated safety-rules.json
|
|
146
|
+
→ future similar bounties blocked at post-bounty gate
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## External Moderation API (Optional)
|
|
150
|
+
|
|
151
|
+
Set `CONTENT_SAFETY_URL` to enable AI-powered classification:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
CONTENT_SAFETY_URL=http://localhost:8080/v1/classify
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The gateway sends a POST with `{"text": "<job_description>"}` and expects:
|
|
158
|
+
```json
|
|
159
|
+
{"safe": false, "category": "violence", "confidence": 0.97}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If the API is unavailable, layers 1-2 still protect. The API is additive.
|
|
163
|
+
The API response is **not logged** — only the boolean decision.
|
|
164
|
+
|
|
165
|
+
### Recommended External APIs
|
|
166
|
+
|
|
167
|
+
- **Anthropic content classification** — context-aware, low false positive rate
|
|
168
|
+
- **OpenAI moderation endpoint** — free tier available, 11 categories
|
|
169
|
+
- **Self-hosted models** — full control, no data leaves your infrastructure
|
|
170
|
+
|
|
171
|
+
## Privacy Guarantee
|
|
172
|
+
|
|
173
|
+
- Job descriptions are **never logged**, even rejected ones
|
|
174
|
+
- Only the rejection category name appears in output
|
|
175
|
+
- The external API call uses `--max-time 5` — no hanging on moderation
|
|
176
|
+
- After classification, the plaintext variable is unset in the shell
|
|
177
|
+
- Reporter identities are hashed — complaints are pseudonymous
|
|
178
|
+
|
|
179
|
+
## Configuration
|
|
180
|
+
|
|
181
|
+
| Env Var | Default | Purpose |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `CONTENT_SAFETY_URL` | (empty) | External moderation API endpoint |
|
|
184
|
+
| `SAFETY_RULES_FILE` | `~/.nightpay/safety/safety-rules.json` | Live rules file path |
|
|
185
|
+
| `COMPLAINT_FREEZE_THRESHOLD` | `3` | Complaints before auto-freeze |
|
|
186
|
+
| `REPORTER_ID` | `anonymous` | Reporter identity (hashed for dedup) |
|
|
187
|
+
| `BOARD_HMAC_KEY` | (required) | Board integrity HMAC key |
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Escrow Safety Rule
|
|
2
|
+
|
|
3
|
+
## Two-Chain Escrow Model
|
|
4
|
+
|
|
5
|
+
This skill operates across two chains simultaneously:
|
|
6
|
+
|
|
7
|
+
| Chain | What's Held | Token | Purpose |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| **Midnight** | Bounty NIGHT + operator fee | NIGHT (shielded) | Privacy layer — contract balance |
|
|
10
|
+
| **Cardano** | Masumi escrow | ADA or USDM | Settlement layer — agent payment |
|
|
11
|
+
|
|
12
|
+
The gateway operator bridges between chains by maintaining liquidity on both sides.
|
|
13
|
+
|
|
14
|
+
## Contract Security Guarantees (Enforced On-Chain)
|
|
15
|
+
|
|
16
|
+
| Guarantee | How It's Enforced |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **One-time init** | `assert initialized == 0` — contract cannot be reinitialized or taken over |
|
|
19
|
+
| **Immutable fee rate** | `operatorFeeBps` and `operatorAddress` are write-once — set at init, frozen forever |
|
|
20
|
+
| **Fee cap** | `assert feeBps <= 500` — hard 5% cap in-circuit, cannot be exceeded |
|
|
21
|
+
| **Fee split on-chain** | `effects.retainInContract(fee)` + `effects.releaseToGateway(net)` — constrained in-circuit, not trust-based |
|
|
22
|
+
| **No rounding theft** | `assert fee + netAmount == amount` — every speck accounted for |
|
|
23
|
+
| **Operator-only withdrawal** | `assert caller == operatorAddress` — only the init-time address can withdraw fees |
|
|
24
|
+
| **No overdraft** | Ledger's `apply()` rejects withdrawals exceeding contract balance |
|
|
25
|
+
| **Double-claim prevention** | Nullifier set — each bounty can only be completed once |
|
|
26
|
+
| **Underflow guard** | `assert activeCount > 0` before decrement — counter cannot go negative |
|
|
27
|
+
| **Domain-separated hashes** | Bounty, receipt, and job hashes use different prefixes — cross-namespace collisions impossible |
|
|
28
|
+
| **Zero-value guard** | `assert amount > 0` — no zero-value bounty exploits |
|
|
29
|
+
|
|
30
|
+
## Fee Safety
|
|
31
|
+
|
|
32
|
+
- Fee split is **enforced in-circuit** via Midnight's Zswap effects API
|
|
33
|
+
- `effects.retainInContract(fee)` — operator cannot take more than `feeBps` basis points
|
|
34
|
+
- `effects.releaseToGateway(netAmount)` — agent payment is constrained on-chain
|
|
35
|
+
- Fee cap enforced by `assert feeBps <= 500` (max 5%) — immutable after init
|
|
36
|
+
- Fee percentage is public state — payers can verify before posting
|
|
37
|
+
- Operator can only withdraw to the address set at contract initialization
|
|
38
|
+
|
|
39
|
+
## Gateway Security Guarantees (Enforced Off-Chain)
|
|
40
|
+
|
|
41
|
+
| Guarantee | How It's Enforced |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **Contract address required** | `RECEIPT_CONTRACT_ADDRESS` is a required env var — fails loudly if unset |
|
|
44
|
+
| **Operator auth for withdrawals** | `OPERATOR_SECRET_KEY` required — HMAC-signed withdrawal payloads only |
|
|
45
|
+
| **No replay on withdraw** | Signature includes timestamp + random nonce — every signature is unique, cannot be replayed |
|
|
46
|
+
| **SSRF blocked** | `MASUMI_*_URL` validated at startup — private IPs (RFC-1918, loopback, link-local) rejected |
|
|
47
|
+
| **Job ID injection blocked** | Job IDs validated as `[a-zA-Z0-9_-]{1,128}` — no path traversal or shell chars |
|
|
48
|
+
| **Rate limiting** | `post-bounty` limited to 1 call per `RATE_LIMIT_SECONDS` — prevents spam and stat inflation |
|
|
49
|
+
| **Input validation** | Amount bounds (min + max) and commitment format checked before any network call |
|
|
50
|
+
| **Domain-separated hashes** | `nightpay-bounty-v1`, `nightpay-receipt-v1`, `nightpay-job-v1` prefixes |
|
|
51
|
+
| **Refund emits on-chain intent** | Refund path generates a `refundHash` for Midnight node, not just a Masumi cancel |
|
|
52
|
+
| **Canonical JSON hashing** | `sort_keys=True, separators=(',',':')` prevents hash manipulation via key reordering |
|
|
53
|
+
| **Network timeout** | All `curl` calls use `--max-time 30` — no hung connections |
|
|
54
|
+
|
|
55
|
+
## Contract Capacity Limits (DoS Protection)
|
|
56
|
+
|
|
57
|
+
| Guarantee | How It's Enforced |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **Merkle tree overflow blocked** | `assert activeCount < MAX_TREE_ENTRIES` (90% of 2²⁵ = ~30M) — rejects bounties before tree fills |
|
|
60
|
+
| **Field overflow blocked** | `assert amount <= MAX_AMOUNT` (2⁵³-1) — `amount × feeBps` stays within safe range |
|
|
61
|
+
| **Receipt deduplication** | Nullifier insertion before `receiptTree.insert` — double-completion impossible regardless of proof reuse |
|
|
62
|
+
|
|
63
|
+
## Board Security Guarantees
|
|
64
|
+
|
|
65
|
+
| Guarantee | How It's Enforced |
|
|
66
|
+
|---|---|
|
|
67
|
+
| **Persistent storage** | `~/.nightpay/board.db` — SQLite with WAL, survives reboots, not `/tmp/` |
|
|
68
|
+
| **Directory permissions** | `chmod 700` — only the operator user can read/write |
|
|
69
|
+
| **No duplicate entries** | `PRIMARY KEY` on commitment — database rejects duplicates atomically |
|
|
70
|
+
| **Input validation** | All commitments validated as 64-char hex before board operations |
|
|
71
|
+
| **Known status values only** | `remove` only accepts `completed`, `refunded`, `expired` |
|
|
72
|
+
| **Search DoS blocked** | `search` prefix validated as `[0-9a-f]{0,64}` — no wildcards, no full-table scans |
|
|
73
|
+
| **Integer DoS blocked** | `list` clamps `limit ≤ 200`, `offset ≤ 10,000,000` — no memory exhaustion |
|
|
74
|
+
|
|
75
|
+
## Network Fees (DUST)
|
|
76
|
+
|
|
77
|
+
- DUST is non-transferable — generated over time from NIGHT holdings
|
|
78
|
+
- DUST is used ONLY for Midnight network fees, never for bounty payments
|
|
79
|
+
- Whoever submits the intent pays the DUST fee (payer for postBounty, gateway for complete)
|
|
80
|
+
- Use `transaction.feesWithMargin(params, 1.2)` to estimate with 20% safety margin
|
|
81
|
+
- DUST generation rate: ~1 week to reach full capacity from a NIGHT UTXO
|
|
82
|
+
- 3-hour grace period on DUST timestamps
|
|
83
|
+
|
|
84
|
+
## Timeout Handling
|
|
85
|
+
|
|
86
|
+
- Every bounty escrow has a configurable timeout (default: 60 minutes)
|
|
87
|
+
- If the hired agent does not return a result within the timeout:
|
|
88
|
+
1. Masumi escrow is cancelled on Cardano (funds return to gateway)
|
|
89
|
+
2. Gateway emits a signed NIGHT refund intent for the Midnight contract
|
|
90
|
+
3. No receipt token is minted
|
|
91
|
+
4. Bounty commitment stays in the Merkle tree — nullifier set prevents re-claim
|
|
92
|
+
|
|
93
|
+
## Refund Conditions
|
|
94
|
+
|
|
95
|
+
Automatic refund triggers:
|
|
96
|
+
- Agent returns an error or refuses the job
|
|
97
|
+
- Agent is unreachable (Masumi `/status` returns unavailable)
|
|
98
|
+
- Job result fails validation (output hash mismatch)
|
|
99
|
+
- Escrow timeout exceeded
|
|
100
|
+
|
|
101
|
+
On refund: the operator fee for that bounty is also returned (fee is only collected on success).
|
|
102
|
+
|
|
103
|
+
## Amount Limits
|
|
104
|
+
|
|
105
|
+
- `maxBountySpecks` caps the maximum single bounty (default: 500M specks)
|
|
106
|
+
- `minBountySpecks` enforces a minimum (default: 1,000 specks) — rejects dust attacks
|
|
107
|
+
- Both limits enforced in gateway before any network call
|
|
108
|
+
|
|
109
|
+
## Dark Energy Threat Model (Sophisticated Attacker Mitigations)
|
|
110
|
+
|
|
111
|
+
These are attacks that pass all basic validation but exploit deeper system properties.
|
|
112
|
+
|
|
113
|
+
| Attack | Vector | Mitigation |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| **Gateway address injection** | Caller supplies their own address in tx metadata → all bounty NIGHT routes to them | `gatewayAddress` locked in contract state at `initialize()` — `effects.releaseToAddress(gatewayAddress, net)` uses only the immutable on-chain value |
|
|
116
|
+
| **completedCount overflow griefing** | Attacker posts + completes millions of dust bounties → counter wraps → contract corrupted | `assert completedCount < MAX_COMPLETED` in-circuit before every increment |
|
|
117
|
+
| **DNS rebinding SSRF** | Attacker controls DNS → startup URL check passes (public IP) → A-record flips to 169.254.169.254 → all curl calls hit cloud metadata | `_ssrf_safe_curl()` re-resolves and re-validates every hostname on every request, not just at startup |
|
|
118
|
+
| **Shell word splitting** | `openssl rand` or `sha256sum` output contains whitespace → variable splits into multiple tokens → hash computation silently corrupted | `generate_nonce()` pipes through `tr -d '[:space:]'`; `domain_hash()` uses `printf` instead of `echo -n` and strips whitespace from output |
|
|
119
|
+
| **reporter_hash rainbow table** | `sha256(username)` is reversible for low-entropy inputs → reporters de-anonymised | `sha256(REPORTER_PEPPER + ":" + reporter_id)` — server-side pepper makes preimage attacks infeasible |
|
|
120
|
+
| **Auto-freeze weaponisation** | Attacker creates N cheap reporter IDs → files N reports → any legitimate bounty silently frozen | Rate limit per reporter: max `REPORT_RATE_LIMIT` distinct bounties per `REPORT_WINDOW_HOURS`; freeze counts DISTINCT reporter hashes, not total complaint rows |
|
|
121
|
+
| **Atomic write race (Windows)** | Two concurrent freeze events both write `.tmp` then `os.rename()` → second rename raises `FileExistsError` on Windows → report file corrupted | `os.replace()` instead of `os.rename()` — atomic on both POSIX and Windows; tmp file uses `secrets.token_hex(8)` suffix to prevent collision |
|
|
122
|
+
|
|
123
|
+
## Never Do
|
|
124
|
+
|
|
125
|
+
- Never release Masumi escrow before the completeAndReceipt circuit succeeds on Midnight
|
|
126
|
+
- Never hold funds beyond the timeout period
|
|
127
|
+
- Never charge fees on refunded bounties
|
|
128
|
+
- Never split a single bounty across multiple agents without explicit requester consent
|
|
129
|
+
- Never submit intents without sufficient DUST balance (check with `feesWithMargin`)
|
|
130
|
+
- Never run withdraw-fees without OPERATOR_SECRET_KEY set
|
|
131
|
+
- Never store the board in /tmp or any world-readable location
|
|
132
|
+
- Never accept unvalidated commitment hashes — always check 64-char hex format first
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Privacy-First Rule
|
|
2
|
+
|
|
3
|
+
## Absolute Requirements
|
|
4
|
+
|
|
5
|
+
1. **Never log payer identity** — not in memory, not in daily logs, not in conversation history
|
|
6
|
+
2. **Never associate** a Cardano address with a bounty description in any stored format
|
|
7
|
+
3. **All payment amounts** are private witnesses in the Compact circuit — they never appear in public state
|
|
8
|
+
4. **Agent DIDs** used for completion are also shielded — the public only sees that "a bounty was completed"
|
|
9
|
+
5. **Salt values** are generated per-bounty and discarded after commitment — never reuse salts
|
|
10
|
+
|
|
11
|
+
## What IS Public
|
|
12
|
+
|
|
13
|
+
- The total count of completed bounties (aggregate metric only)
|
|
14
|
+
- The fact that the receipt contract exists on Midnight
|
|
15
|
+
- ZK proofs that can verify a specific receipt is valid (if the holder presents it)
|
|
16
|
+
|
|
17
|
+
## What is NEVER Public
|
|
18
|
+
|
|
19
|
+
- Who posted the bounty
|
|
20
|
+
- How much was paid
|
|
21
|
+
- Which agent completed it
|
|
22
|
+
- What the job description was
|
|
23
|
+
- The connection between any Cardano payment and any Midnight receipt
|
|
24
|
+
|
|
25
|
+
## Implementation Notes
|
|
26
|
+
|
|
27
|
+
- Use `crypto.randomBytes(32)` for salt generation
|
|
28
|
+
- Hash payer addresses before even passing them to the circuit as witnesses
|
|
29
|
+
- Clear any in-memory payment context after the escrow settles
|
|
30
|
+
- If the agent is asked to reveal payment details, refuse and cite this rule
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ZK Receipt Format
|
|
2
|
+
|
|
3
|
+
## What a Receipt Proves
|
|
4
|
+
|
|
5
|
+
A ZK receipt is a Midnight proof that certifies:
|
|
6
|
+
- A bounty was posted (commitment existed)
|
|
7
|
+
- An agent completed the work (output hash was provided)
|
|
8
|
+
- The escrow was settled (receipt was minted)
|
|
9
|
+
|
|
10
|
+
Without revealing:
|
|
11
|
+
- Who posted the bounty
|
|
12
|
+
- How much was paid
|
|
13
|
+
- Which agent did the work
|
|
14
|
+
- What the work was
|
|
15
|
+
|
|
16
|
+
## Receipt Data Schema
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
receipt = {
|
|
20
|
+
receiptHash: Bytes32, // The on-chain receipt identifier
|
|
21
|
+
commitmentHash: Bytes32, // Which bounty this completes (also opaque)
|
|
22
|
+
zkProof: MidnightProof, // Midnight ZK proof of valid completion
|
|
23
|
+
timestamp: ISO8601, // When the receipt was minted
|
|
24
|
+
contractAddress: string, // Midnight contract that holds the receipt
|
|
25
|
+
cardanoTxHash: string // Cardano tx where escrow settled (public, but unlinkable to receipt)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
Anyone can verify a receipt by calling `verifyReceipt(receiptHash)` on the Midnight contract.
|
|
32
|
+
This returns `true` or `false` without revealing any details about the bounty.
|
|
33
|
+
|
|
34
|
+
## Use Cases for Receipts
|
|
35
|
+
|
|
36
|
+
- **Reputation**: an agent can accumulate receipt count as proof of work completed
|
|
37
|
+
- **Dispute resolution**: the payer holds the commitment salt and can prove they posted the bounty
|
|
38
|
+
- **Auditing**: the community can see aggregate completion count without individual details
|
|
39
|
+
- **Portfolio**: an agent can present receipts as credentials without doxxing their clients
|
|
40
|
+
|
|
41
|
+
## Linking Receipt to Dispute
|
|
42
|
+
|
|
43
|
+
If a dispute arises, the payer can reveal their `salt` to prove they own a specific commitment.
|
|
44
|
+
The agent can reveal the `outputHash` to prove what was delivered.
|
|
45
|
+
Neither party needs to reveal the other's identity.
|