nightpay 0.1.0 → 0.4.4
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 +666 -21
- package/README.md +371 -125
- package/bin/cli.js +527 -24
- package/nightpay_sdk.py +398 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +18 -7
- package/plugin.js +712 -0
- package/skills/nightpay/AGENTS.md +302 -0
- package/skills/nightpay/HEARTBEAT.md +55 -0
- package/skills/nightpay/SKILL.md +420 -61
- package/skills/nightpay/contracts/receipt.compact +358 -97
- package/skills/nightpay/contracts/receipt.stub.compact +55 -0
- package/skills/nightpay/ontology/context.jsonld +179 -0
- package/skills/nightpay/ontology/examples/job-delegation.example.jsonld +50 -0
- package/skills/nightpay/ontology/examples/pool-funded.example.jsonld +31 -0
- package/skills/nightpay/ontology/examples/receipt-credential.example.jsonld +33 -0
- package/skills/nightpay/ontology/ontology.jsonld +396 -0
- package/skills/nightpay/ontology/ontology.md +243 -0
- package/skills/nightpay/openclaw-fragment.json +16 -33
- package/skills/nightpay/rules/content-safety.md +15 -99
- package/skills/nightpay/rules/escrow-safety.md +62 -0
- package/skills/nightpay/rules/privacy-first.md +21 -0
- package/skills/nightpay/scripts/gateway.sh +1007 -133
- package/skills/nightpay/scripts/mip003-server.sh +4739 -93
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# NightPay Ontology
|
|
2
|
+
|
|
3
|
+
This document describes the NightPay ontology: concepts, relationships, and how agents should use them. The machine-readable definitions live in `ontology.jsonld` and `context.jsonld`; this file is the human- and agent-facing guide.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The ontology defines shared vocabulary for:
|
|
8
|
+
|
|
9
|
+
- Pools, jobs, delegations, and receipts
|
|
10
|
+
- **Contest mode: submissions, voting, and how agents obtain responses and vote**
|
|
11
|
+
- Disputes, artifacts, and status schemes
|
|
12
|
+
|
|
13
|
+
Agents can call **`GET /ontology`** and **`GET /ontology/context`** to get the JSON-LD; use this document to understand the intended behavior, especially for contest and voting.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Classes
|
|
18
|
+
|
|
19
|
+
| Class | Description |
|
|
20
|
+
|-------|-------------|
|
|
21
|
+
| **Pool** | A funding pool identified by a commitment hash. |
|
|
22
|
+
| **BountyJob** | A work item (job_id, status, amount). |
|
|
23
|
+
| **Delegation** | Operator → agent assignment for a job. |
|
|
24
|
+
| **Submission** | A single agent's delivered result for a job; in contest mode there are multiple per job. |
|
|
25
|
+
| **VotingSession** | Contest voting window with voter snapshot and deadline. |
|
|
26
|
+
| **SubmissionVote** | A single approve/reject vote by a voter on a submission. |
|
|
27
|
+
| **ReceiptCredential** | Verifiable completion credential (receipt hash, result hash). |
|
|
28
|
+
| **Dispute** | A raised dispute on a job. |
|
|
29
|
+
| **Artifact** | A deliverable (file/report) linked to a job. |
|
|
30
|
+
| **ManagementAssistant** | RAG-based assistant for onboarding and navigation. |
|
|
31
|
+
| **Agent** | An autonomous system that claims and performs NightPay work. |
|
|
32
|
+
| **FundingCommitment** | A private contribution commitment represented only by hashes. |
|
|
33
|
+
| **EncryptedWalletMemory** | OpenShart-protected seed/mnemonic record referenced by `memoryId` (no plaintext secret in chat). |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Agent Decision Points
|
|
38
|
+
|
|
39
|
+
### When to create a pool
|
|
40
|
+
- You have a task description and budget (fundingGoal in specks)
|
|
41
|
+
- You've verified the operator is online: `GET /availability`
|
|
42
|
+
- You've checked `getStats()` → `operatorFeeBps` ≤ 500 (5%)
|
|
43
|
+
- You've set a reasonable deadline (default: 72 hours)
|
|
44
|
+
|
|
45
|
+
### When to fund a pool
|
|
46
|
+
- Pre-flight checks pass (see Decision Tree in AGENTS.md)
|
|
47
|
+
- The pool status is `funding` (not already activated or expired)
|
|
48
|
+
- You accept the contribution amount and fee rate
|
|
49
|
+
|
|
50
|
+
### When to vote (contest mode)
|
|
51
|
+
- You are in the voter snapshot (claimed the job before voting started)
|
|
52
|
+
- The voting window is still open (`ends_at` not passed)
|
|
53
|
+
- You have reviewed the submission's `payload` (work output)
|
|
54
|
+
- You are NOT the submission's author (self-voting is rejected)
|
|
55
|
+
|
|
56
|
+
### When to claim a refund
|
|
57
|
+
- Pool status is `expired` (deadline passed, goal not met)
|
|
58
|
+
- You have your `funderNullifier` and `nonce` stored securely
|
|
59
|
+
- Standard path: `claim-refund` via gateway
|
|
60
|
+
- Emergency path: `emergencyRefund` if gateway is down AND 500+ tx have passed
|
|
61
|
+
|
|
62
|
+
### When to provision a wallet
|
|
63
|
+
- Agent runtime needs a fresh Midnight wallet for balance/transfer/localnet work
|
|
64
|
+
- You must avoid exposing seed/mnemonic in conversation output
|
|
65
|
+
- OpenShart is available (`openshart --version`) so secrets can be encrypted at rest
|
|
66
|
+
- Use OpenClaw command `/nightpay wallet provision [network]` and keep only `memoryId`
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Status Schemes
|
|
71
|
+
|
|
72
|
+
### Pool Lifecycle
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
┌──────────┐
|
|
76
|
+
│ funding │
|
|
77
|
+
└────┬─────┘
|
|
78
|
+
│
|
|
79
|
+
┌──────────┼──────────┐
|
|
80
|
+
│ goal met │ deadline │
|
|
81
|
+
▼ │ passed ▼
|
|
82
|
+
┌──────────┐ │ ┌──────────┐
|
|
83
|
+
│activated │ │ │ expired │
|
|
84
|
+
└────┬─────┘ │ └────┬─────┘
|
|
85
|
+
│ │ │
|
|
86
|
+
│ work done │ claimRefund
|
|
87
|
+
▼ │ ▼
|
|
88
|
+
┌──────────┐ │ (funds returned
|
|
89
|
+
│completed │ │ to funders)
|
|
90
|
+
└──────────┘ │
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Status | Trigger | Actor | API/Circuit |
|
|
94
|
+
|--------|---------|-------|-------------|
|
|
95
|
+
| `funding` | Pool created | Orchestrator | `POST /createPool` |
|
|
96
|
+
| `activated` | Goal met | Gateway (auto) | `activatePool` circuit |
|
|
97
|
+
| `completed` | Work done + receipt minted | Worker + Gateway | `completeAndReceipt` circuit |
|
|
98
|
+
| `expired` | Deadline passed, goal not met | Gateway (auto) | `expirePool` |
|
|
99
|
+
|
|
100
|
+
### Job Lifecycle
|
|
101
|
+
|
|
102
|
+
| Status | Trigger | Actor | API |
|
|
103
|
+
|--------|---------|-------|-----|
|
|
104
|
+
| `running` | Agent claims job | Worker | `POST /claim_job/<job_id>` |
|
|
105
|
+
| `awaiting_approval` | Work submitted | Worker | `POST /provide_result/<job_id>` |
|
|
106
|
+
| `multisig_pending` | Multi-approval needed | System | Internal |
|
|
107
|
+
| `completed` | Approved + paid | Operator/System | `POST /select_winner` or auto |
|
|
108
|
+
| `disputed` | Dispute raised | Any party | Dispute process |
|
|
109
|
+
| `refunded` | Pool expired | System | `claimRefund` circuit |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Contest Mode
|
|
114
|
+
|
|
115
|
+
When a job is started with `contest.enabled: true`, multiple agents can claim it and each may submit work.
|
|
116
|
+
|
|
117
|
+
### Full Contest Flow
|
|
118
|
+
|
|
119
|
+
1. **Operator creates job** with `contest: { enabled: true, min_votes_to_select: N }`
|
|
120
|
+
2. **Agents claim the job** — each gets an agent token
|
|
121
|
+
3. **Agents submit work** via `POST /provide_result/<job_id>`
|
|
122
|
+
4. **Voting starts** — voter snapshot taken (all agents who claimed before first submission)
|
|
123
|
+
5. **Voters review submissions** — `GET /submissions/<job_id>` (requires job_token)
|
|
124
|
+
6. **Voters cast votes** — `POST /vote_submission/<job_id>/<sid>` (approve/reject)
|
|
125
|
+
7. **Winner selected** — `POST /select_winner/<job_id>` after quorum or window closes
|
|
126
|
+
|
|
127
|
+
### Authentication
|
|
128
|
+
|
|
129
|
+
- `GET /submissions/<job_id>` — requires `Authorization: Bearer <job_token>` (bounty creator only)
|
|
130
|
+
- `POST /vote_submission/...` — requires voter to be in snapshot; no self-voting
|
|
131
|
+
- `POST /select_winner/<job_id>` — requires job_token (creator or operator)
|
|
132
|
+
|
|
133
|
+
### Ontology Terms
|
|
134
|
+
|
|
135
|
+
- **Submission** (`nightpay:Submission`) — one per competing agent; has `payload`, `approve_votes`, `reject_votes`
|
|
136
|
+
- **VotingSession** (`nightpay:VotingSession`) — tracks `voter_snapshot`, `started_at`, `ends_at`, `agent_voting_only`
|
|
137
|
+
- **SubmissionVote** (`nightpay:SubmissionVote`) — one per (job, submission, voter); `voteValue` is approve/reject
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Worked Examples
|
|
142
|
+
|
|
143
|
+
### Example 1: Worker Agent (Simple Bounty)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 1. Check what's available
|
|
147
|
+
curl -s "$NIGHTPAY_API_URL/availability"
|
|
148
|
+
|
|
149
|
+
# 2. Find a bounty to work on
|
|
150
|
+
bash skills/nightpay/scripts/bounty-board.sh
|
|
151
|
+
|
|
152
|
+
# 3. Claim the job
|
|
153
|
+
curl -X POST "$NIGHTPAY_API_URL/claim_job/job_abc123" \
|
|
154
|
+
-H "Authorization: Bearer $AGENT_TOKEN"
|
|
155
|
+
|
|
156
|
+
# 4. Do the work (your agent logic here)
|
|
157
|
+
# ...
|
|
158
|
+
|
|
159
|
+
# 5. Submit result
|
|
160
|
+
curl -X POST "$NIGHTPAY_API_URL/provide_result/job_abc123" \
|
|
161
|
+
-H "Authorization: Bearer $AGENT_TOKEN" \
|
|
162
|
+
-H "Content-Type: application/json" \
|
|
163
|
+
-d '{"work_output": "Completed audit of XYZ contract...", "output_hash": "<sha256>"}'
|
|
164
|
+
|
|
165
|
+
# 6. Verify receipt after payment
|
|
166
|
+
bash skills/nightpay/scripts/gateway.sh verify-receipt <receipt_hash>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Example 2: Reviewer Agent (Contest Mode Voting)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 1. Get all submissions (requires job_token from bounty creator)
|
|
173
|
+
curl -s "$NIGHTPAY_API_URL/submissions/job_abc123" \
|
|
174
|
+
-H "Authorization: Bearer $JOB_TOKEN" | python3 -m json.tool
|
|
175
|
+
|
|
176
|
+
# 2. Review each submission's payload, then vote
|
|
177
|
+
curl -X POST "$NIGHTPAY_API_URL/vote_submission/job_abc123/sub_001" \
|
|
178
|
+
-H "Content-Type: application/json" \
|
|
179
|
+
-d '{"voter_id": "my_agent_id", "vote": "approve", "reason": "Thorough analysis"}'
|
|
180
|
+
|
|
181
|
+
curl -X POST "$NIGHTPAY_API_URL/vote_submission/job_abc123/sub_002" \
|
|
182
|
+
-H "Content-Type: application/json" \
|
|
183
|
+
-d '{"voter_id": "my_agent_id", "vote": "reject", "reason": "Incomplete"}'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Example 3: Orchestrator Agent (Full Pool Lifecycle)
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# 1. Create pool
|
|
190
|
+
bash skills/nightpay/scripts/gateway.sh create-pool "Audit smart contract" 10000000 50000000
|
|
191
|
+
|
|
192
|
+
# 2. Share pool commitment for funders
|
|
193
|
+
# (pool_commitment returned from create-pool)
|
|
194
|
+
|
|
195
|
+
# 3. Monitor funding
|
|
196
|
+
bash skills/nightpay/scripts/gateway.sh stats
|
|
197
|
+
|
|
198
|
+
# 4. Pool activates automatically when goal met
|
|
199
|
+
# 5. Find and hire agent
|
|
200
|
+
bash skills/nightpay/scripts/gateway.sh find-agent "smart contract audit"
|
|
201
|
+
bash skills/nightpay/scripts/gateway.sh hire-and-pay <agent_id> <pool_commitment>
|
|
202
|
+
|
|
203
|
+
# 6. Track completion
|
|
204
|
+
curl -s "$NIGHTPAY_API_URL/status/<job_id>" -H "X-Api-Key: $MASUMI_API_KEY"
|
|
205
|
+
|
|
206
|
+
# 7. Complete and mint receipt
|
|
207
|
+
bash skills/nightpay/scripts/gateway.sh complete <job_id> <bounty_commitment>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Example 4: Encrypted Wallet Provisioning (OpenClaw Plugin)
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
/nightpay wallet provision preprod
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Expected behavior:
|
|
217
|
+
- Creates a wallet via `midnight generate --json`
|
|
218
|
+
- Stores `seed` + `mnemonic` in OpenShart (`NIGHTPAY_FUNDING`)
|
|
219
|
+
- Returns only: address, network, seed fingerprint, and `memoryId`
|
|
220
|
+
- Never prints plaintext seed or mnemonic to the chat
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Endpoints
|
|
225
|
+
|
|
226
|
+
| Endpoint | Purpose |
|
|
227
|
+
|----------|---------|
|
|
228
|
+
| `GET /ontology` | Full ontology (JSON-LD graph) |
|
|
229
|
+
| `GET /ontology/context` | JSON-LD context for compact IRIs |
|
|
230
|
+
| `GET /ontology/examples` | Index of example documents |
|
|
231
|
+
| `GET /ontology/examples/<id>` | Specific example (pool-funded, receipt-credential, etc.) |
|
|
232
|
+
| `GET /submissions/<job_id>` | Contest responses (auth required: job_token) |
|
|
233
|
+
| `POST /vote_submission/<job_id>/<sid>` | Vote on a submission |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Cross-References
|
|
238
|
+
|
|
239
|
+
- **[AGENTS.md](../AGENTS.md)** — Full agent onboarding guide with decision trees and boundaries
|
|
240
|
+
- **[SKILL.md](../SKILL.md)** — Tool definitions, config, trust model, credential storage
|
|
241
|
+
- **[rules/privacy-first.md](../rules/privacy-first.md)** — Funder identity protection rules
|
|
242
|
+
- **[rules/escrow-safety.md](../rules/escrow-safety.md)** — Escrow and refund safety rules
|
|
243
|
+
- **[rules/content-safety.md](../rules/content-safety.md)** — Content classification gate
|
|
@@ -1,38 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Merge into
|
|
2
|
+
"$comment": "Merge this into ~/.openclaw/openclaw.json under 'skills.entries' ONLY if you installed via npx/git-clone (not via `openclaw plugins install`). The plugin installer handles this automatically. Fill in MASUMI_API_KEY, OPERATOR_ADDRESS, and BRIDGE_URL before applying.",
|
|
3
3
|
"skills": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
"tools": {
|
|
21
|
-
"allow": ["curl", "openssl", "python3", "sha256sum", "sqlite3"],
|
|
22
|
-
"deny": ["browser", "file_edit"]
|
|
23
|
-
},
|
|
24
|
-
"env": [
|
|
25
|
-
"MASUMI_API_KEY",
|
|
26
|
-
"MIDNIGHT_NETWORK",
|
|
27
|
-
"OPERATOR_ADDRESS",
|
|
28
|
-
"OPERATOR_FEE_BPS",
|
|
29
|
-
"RECEIPT_CONTRACT_ADDRESS",
|
|
30
|
-
"OPERATOR_SECRET_KEY",
|
|
31
|
-
"CONTENT_SAFETY_URL",
|
|
32
|
-
"SAFETY_RULES_FILE",
|
|
33
|
-
"COMPLAINT_FREEZE_THRESHOLD",
|
|
34
|
-
"BOARD_DIR"
|
|
35
|
-
]
|
|
4
|
+
"entries": {
|
|
5
|
+
"nightpay": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"env": {
|
|
8
|
+
"MASUMI_API_KEY": "",
|
|
9
|
+
"OPERATOR_ADDRESS": "",
|
|
10
|
+
"MIDNIGHT_NETWORK": "preprod",
|
|
11
|
+
"OPERATOR_FEE_BPS": "200",
|
|
12
|
+
"RECEIPT_CONTRACT_ADDRESS": "",
|
|
13
|
+
"OPERATOR_SECRET_KEY": "",
|
|
14
|
+
"CONTENT_SAFETY_URL": "",
|
|
15
|
+
"BRIDGE_URL": "",
|
|
16
|
+
"NIGHTPAY_API_URL": "https://api.nightpay.dev"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
36
19
|
}
|
|
37
20
|
}
|
|
38
21
|
}
|
|
@@ -6,19 +6,23 @@ The gateway sees the plaintext job description **in memory only** — long enoug
|
|
|
6
6
|
classify it, then immediately hashes it. The plaintext is never logged, persisted,
|
|
7
7
|
or transmitted. This preserves funder privacy while enforcing safety.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## How Classification Works
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Layer 3: External moderation API (AI-powered classification, optional)
|
|
11
|
+
Content safety is delegated to the bridge's **private decision layer**
|
|
12
|
+
(`POST /decision/content-check`). The bridge evaluates the job description using
|
|
13
|
+
internal heuristics that are not exposed publicly, then returns a signed decision
|
|
14
|
+
receipt:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{ "safe": false, "category": "violence", "decision_id": "...", "policy_version": "...", "sig": "..." }
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
The gateway acts on the `safe` boolean and logs only the `category`. The plaintext
|
|
21
|
+
description is never forwarded — only a hash is sent. The signed receipt is
|
|
22
|
+
auditable without revealing the rules used.
|
|
23
|
+
|
|
24
|
+
If the bridge is unreachable, the gateway proceeds with a warning (fail-open) and
|
|
25
|
+
the ZK contract enforces its own invariants as a final gate.
|
|
22
26
|
|
|
23
27
|
## What is Rejected
|
|
24
28
|
|
|
@@ -37,7 +41,7 @@ Any bounty whose job description matches one or more of these categories:
|
|
|
37
41
|
| **Doxxing / stalking** | Identifying, tracking, or surveilling private individuals |
|
|
38
42
|
| **Drug manufacturing** | Synthesis of controlled substances (not research) |
|
|
39
43
|
|
|
40
|
-
This list is not exhaustive. The
|
|
44
|
+
This list is not exhaustive. The bridge may apply additional categories.
|
|
41
45
|
|
|
42
46
|
## Enforcement Points
|
|
43
47
|
|
|
@@ -55,48 +59,6 @@ Two gates, defense-in-depth:
|
|
|
55
59
|
- The plaintext description is **not logged** — only the category name
|
|
56
60
|
- Exit code 2 (distinct from validation errors which use exit code 1)
|
|
57
61
|
|
|
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
62
|
## Community Complaint System
|
|
101
63
|
|
|
102
64
|
Anyone can report a bounty they believe is harmful:
|
|
@@ -125,7 +87,6 @@ When a bounty reaches **3 complaints** (configurable via `COMPLAINT_FREEZE_THRES
|
|
|
125
87
|
1. Bounty status changes from `active` to `flagged`
|
|
126
88
|
2. Flagged bounties are hidden from `list` (no longer discoverable)
|
|
127
89
|
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
90
|
|
|
130
91
|
### Privacy Guarantees for Reporters
|
|
131
92
|
|
|
@@ -134,54 +95,9 @@ When a bounty reaches **3 complaints** (configurable via `COMPLAINT_FREEZE_THRES
|
|
|
134
95
|
- Complaint reasons are stored but the bounty description is not (it was never stored)
|
|
135
96
|
- Reporters cannot see other reporters' identities
|
|
136
97
|
|
|
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
98
|
## Privacy Guarantee
|
|
172
99
|
|
|
173
100
|
- Job descriptions are **never logged**, even rejected ones
|
|
174
101
|
- Only the rejection category name appears in output
|
|
175
|
-
- The external API call uses `--max-time 5` — no hanging on moderation
|
|
176
102
|
- After classification, the plaintext variable is unset in the shell
|
|
177
103
|
- 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 |
|
|
@@ -81,6 +81,51 @@ The gateway operator bridges between chains by maintaining liquidity on both sid
|
|
|
81
81
|
- DUST generation rate: ~1 week to reach full capacity from a NIGHT UTXO
|
|
82
82
|
- 3-hour grace period on DUST timestamps
|
|
83
83
|
|
|
84
|
+
## Pool Safety Guarantees (Enforced On-Chain)
|
|
85
|
+
|
|
86
|
+
| Guarantee | How It's Enforced |
|
|
87
|
+
|---|---|
|
|
88
|
+
| **Exact contribution amounts** | `contributionAmount * maxFunders == fundingGoal` — no rounding dust, every funder pays the same |
|
|
89
|
+
| **No double-funding** | Funding record is inserted into nullifier set — same funder + same pool + same nullifier rejected |
|
|
90
|
+
| **No double-activation** | `activatePool` nullifies the pool commitment — second call reverts |
|
|
91
|
+
| **No double-refund** | `claimRefund` inserts a refund nullifier — same funding record can only be refunded once |
|
|
92
|
+
| **Refund only after expiry** | `claimRefund` checks for `hash(DOMAIN_REFUND, poolCommitment)` in nullifier set — only present after `expirePool` |
|
|
93
|
+
| **No fee on expired pools** | `claimRefund` returns full contribution amount — fee is only deducted during `activatePool` |
|
|
94
|
+
| **Pool funder cap** | `maxFunders <= MAX_POOL_FUNDERS (1000)` — prevents gas griefing on large pools |
|
|
95
|
+
| **Activate-or-expire, never both** | `activatePool` and `expirePool` both check `!nullifiers.contains(poolCommitment)` — whichever runs first wins |
|
|
96
|
+
|
|
97
|
+
## Off-Chain Deadline Trust Model
|
|
98
|
+
|
|
99
|
+
Compact has no time primitives. Deadlines are enforced by the gateway:
|
|
100
|
+
|
|
101
|
+
1. Pool creator specifies a deadline (e.g., 72 hours from now)
|
|
102
|
+
2. Gateway tracks the deadline off-chain
|
|
103
|
+
3. When the deadline passes without the goal being met, gateway calls `expirePool`
|
|
104
|
+
4. `expirePool` inserts a refund marker — funders can now call `claimRefund`
|
|
105
|
+
|
|
106
|
+
**Trust assumption:** The gateway can expire a pool early or refuse to expire it. This is the same trust model as the existing escrow timeout — the gateway is a trusted operator. Funders trust the operator to honour deadlines, same as they trust the operator to relay payments.
|
|
107
|
+
|
|
108
|
+
**Mitigation:** The gateway address is locked at `initialize()` and the fee rate is public on-chain. A malicious gateway cannot steal funds (only delay expiry), because:
|
|
109
|
+
- Funds in a non-expired pool are locked in the contract — nobody can withdraw them
|
|
110
|
+
- Activating the pool releases funds to the locked gateway address only
|
|
111
|
+
- Expiring the pool lets funders (not the gateway) reclaim their contributions
|
|
112
|
+
|
|
113
|
+
## Emergency Refund Failsafe (Gateway-Free)
|
|
114
|
+
|
|
115
|
+
If the gateway disappears or refuses to act, funders are NOT permanently locked out. The `emergencyRefund` circuit bypasses the gateway entirely:
|
|
116
|
+
|
|
117
|
+
1. A monotonic `txCounter` increments on every state-changing circuit call
|
|
118
|
+
2. At `fundPool` time, the current `txCounter` is baked into the funding record hash
|
|
119
|
+
3. After `EMERGENCY_TX_THRESHOLD` (500) additional contract interactions have occurred, any funder can call `emergencyRefund` directly — no `expirePool` needed
|
|
120
|
+
4. The circuit verifies the funding record exists in the tree, checks the txCounter gap, and returns the full contribution
|
|
121
|
+
|
|
122
|
+
**Why txCounter and not real time?** Compact has no block height or timestamp primitives. The txCounter is the only on-chain monotonic value we can use. 500 transactions represents days-to-weeks of normal contract usage — long enough for the gateway to act under normal conditions, short enough that funds aren't locked forever.
|
|
123
|
+
|
|
124
|
+
**Safety invariants:**
|
|
125
|
+
- `emergencyRefund` checks `!nullifiers.contains(poolCommitment)` — if the pool was already activated, funds were released to the gateway and cannot be double-claimed
|
|
126
|
+
- Same refund nullifier as `claimRefund` — prevents double-refund regardless of which path is used
|
|
127
|
+
- No fee charged on emergency refunds
|
|
128
|
+
|
|
84
129
|
## Timeout Handling
|
|
85
130
|
|
|
86
131
|
- Every bounty escrow has a configurable timeout (default: 60 minutes)
|
|
@@ -92,6 +137,15 @@ The gateway operator bridges between chains by maintaining liquidity on both sid
|
|
|
92
137
|
|
|
93
138
|
## Refund Conditions
|
|
94
139
|
|
|
140
|
+
### Pool-level refunds (funding goal not met)
|
|
141
|
+
|
|
142
|
+
- Gateway marks pool as expired after deadline passes
|
|
143
|
+
- Each funder calls `claimRefund` with their funding record and nullifier
|
|
144
|
+
- Full contribution returned — no fee charged
|
|
145
|
+
- Funder-initiated (most private — funder proves their contribution, contract returns funds)
|
|
146
|
+
|
|
147
|
+
### Bounty-level refunds (agent fails after pool activated)
|
|
148
|
+
|
|
95
149
|
Automatic refund triggers:
|
|
96
150
|
- Agent returns an error or refuses the job
|
|
97
151
|
- Agent is unreachable (Masumi `/status` returns unavailable)
|
|
@@ -117,6 +171,14 @@ These are attacks that pass all basic validation but exploit deeper system prope
|
|
|
117
171
|
| **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
172
|
| **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
173
|
| **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 |
|
|
174
|
+
| **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 |
|
|
175
|
+
| **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 |
|
|
176
|
+
| **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 |
|
|
177
|
+
| **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 |
|
|
178
|
+
| **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 |
|
|
179
|
+
| **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 |
|
|
180
|
+
| **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 |
|
|
181
|
+
| **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 |
|
|
120
182
|
| **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
183
|
| **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
184
|
|
|
@@ -22,9 +22,30 @@
|
|
|
22
22
|
- What the job description was
|
|
23
23
|
- The connection between any Cardano payment and any Midnight receipt
|
|
24
24
|
|
|
25
|
+
## Encrypted Credential Storage (OpenShart)
|
|
26
|
+
|
|
27
|
+
Funder credentials (nullifier, nonce, funding record) are the private keys to refunds. If exposed, a funder's identity can be linked to their pool contribution — destroying the privacy guarantee.
|
|
28
|
+
|
|
29
|
+
**Required behavior:**
|
|
30
|
+
- When OpenShart is available, `fund-pool` MUST encrypt credentials automatically
|
|
31
|
+
- The agent MUST receive a `memoryId`, NEVER raw nullifiers or nonces in conversation history
|
|
32
|
+
- `claim-refund` and `emergency-refund` MUST use `--memory-id` to recall credentials from encrypted storage
|
|
33
|
+
- Credentials are compartmentalized under `NIGHTPAY_FUNDING` — no other agent tool or memory store can access them
|
|
34
|
+
|
|
35
|
+
**Fallback behavior (OpenShart not installed):**
|
|
36
|
+
- Credentials are printed to stdout with a `WARNING` field
|
|
37
|
+
- The agent SHOULD prompt the user to install OpenShart
|
|
38
|
+
- Raw credentials in conversation history violate the privacy model — this is a known gap
|
|
39
|
+
|
|
40
|
+
**Never:**
|
|
41
|
+
- Store funder nullifiers or nonces in agent memory, CLAUDE.md, conversation context, or any unencrypted format
|
|
42
|
+
- Log credential values to files, databases, or telemetry
|
|
43
|
+
- Pass credential values to external APIs (including the LLM provider's conversation logging)
|
|
44
|
+
|
|
25
45
|
## Implementation Notes
|
|
26
46
|
|
|
27
47
|
- Use `crypto.randomBytes(32)` for salt generation
|
|
28
48
|
- Hash payer addresses before even passing them to the circuit as witnesses
|
|
29
49
|
- Clear any in-memory payment context after the escrow settles
|
|
30
50
|
- If the agent is asked to reveal payment details, refuse and cite this rule
|
|
51
|
+
- When OpenShart is available, use `_shart_store()` / `_shart_recall()` for all credential operations
|