nightpay 0.1.2 → 0.2.1
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.
Potentially problematic release.
This version of nightpay might be problematic. Click here for more details.
- package/LICENCE +1 -0
- package/LICENSE +201 -21
- package/README.md +213 -203
- package/bin/cli.js +56 -56
- package/package.json +39 -39
- package/skills/nightpay/SKILL.md +141 -105
- package/skills/nightpay/contracts/receipt.compact +197 -195
- package/skills/nightpay/openclaw-fragment.json +20 -20
- package/skills/nightpay/rules/content-safety.md +187 -187
- package/skills/nightpay/rules/escrow-safety.md +140 -132
- package/skills/nightpay/rules/privacy-first.md +30 -30
- package/skills/nightpay/rules/receipt-format.md +45 -45
- package/skills/nightpay/scripts/bounty-board.sh +325 -325
- package/skills/nightpay/scripts/gateway.sh +282 -25
- package/skills/nightpay/scripts/mip003-server.sh +532 -32
- package/skills/nightpay/scripts/update-blocklist.sh +194 -194
|
@@ -1,132 +1,140 @@
|
|
|
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
|
-
| **
|
|
121
|
-
| **
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
| **Fake work submission** | Anyone knowing a `job_id` calls `POST /provide_input/<id>` with fabricated results | `job_token = HMAC-SHA256("nightpay-job-token-v1:{job_id}", JOB_TOKEN_SECRET)` — only the agent that called `start_job` holds the token; 401/403 on missing/invalid token |
|
|
121
|
+
| **Result-swap after commit** | Agent waits to see funder's expected output, then constructs a matching `work_nonce` to pass reveal check | `work_commit = sha256("nightpay-work-reveal-v1:{work}:{nonce}")` committed at `start_job` before work begins — SHA-256 preimage resistance makes post-hoc matching infeasible |
|
|
122
|
+
| **Multisig double-counting** | One approver submits two signatures with different nonces → counted as two votes | `used_keys` set in verifier tracks which key index already matched — each entry in `APPROVER_KEYS` counts at most once regardless of how many valid blobs it signs |
|
|
123
|
+
| **Arbitrator keys in M-of-N** | `APPROVER_KEYS` may include community arbitrators; key compromise could approve completion | Same HMAC verification as operator keys; no separate role or endpoint — arbitrators use `approve-multisig`; key custody is per-approver responsibility |
|
|
124
|
+
| **Stale approval replay** | Attacker captures a valid M-of-N approval blob and reuses it months later on a different job | Approval payload includes `job_id + output_hash + ts + nonce`; verifier rejects if `age > 86400s`; `job_id` binding makes blobs non-transferable |
|
|
125
|
+
| **Clock skew abuse** | Approver pre-signs with a timestamp 25h in the future to extend the 24h expiry window | `age < -300s` check rejects approvals more than 5 minutes in the future |
|
|
126
|
+
| **Optimistic double-complete** | `optimistic-sweep` cron and operator manually run `complete` concurrently on same job | Midnight nullifier set is canonical — second `completeAndReceipt` circuit call is rejected on-chain regardless of race condition off-chain |
|
|
127
|
+
| **Job status filter injection** | `GET /jobs?status='; DROP TABLE jobs--` sent to MIP-003 server | `KNOWN_STATUSES` whitelist check before any DB query — unknown values return 400, never reach SQLite |
|
|
128
|
+
| **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 |
|
|
129
|
+
| **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 |
|
|
130
|
+
|
|
131
|
+
## Never Do
|
|
132
|
+
|
|
133
|
+
- Never release Masumi escrow before the completeAndReceipt circuit succeeds on Midnight
|
|
134
|
+
- Never hold funds beyond the timeout period
|
|
135
|
+
- Never charge fees on refunded bounties
|
|
136
|
+
- Never split a single bounty across multiple agents without explicit requester consent
|
|
137
|
+
- Never submit intents without sufficient DUST balance (check with `feesWithMargin`)
|
|
138
|
+
- Never run withdraw-fees without OPERATOR_SECRET_KEY set
|
|
139
|
+
- Never store the board in /tmp or any world-readable location
|
|
140
|
+
- Never accept unvalidated commitment hashes — always check 64-char hex format first
|
|
@@ -1,30 +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
|
|
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
|
|
@@ -1,45 +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.
|
|
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.
|