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.
@@ -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.