|
1 | | -# SolidPay protocol — v0 (with the v1 signature draft) |
2 | | - |
3 | | -SolidPay is Ryan Fugger's original Ripple (2004) — bilateral credit lines and |
4 | | -payments routed through chains of trust — rebuilt on Solid ideas: every user |
5 | | -is a URI, every transition is hash-chained, and (from v1) every transition is |
6 | | -signed with a key the actor holds. No blockchain, no native token, no global |
7 | | -consensus. |
8 | | - |
9 | | -Lineage: [classic.ripplepay.com](https://classic.ripplepay.com/) · Fugger, |
10 | | -*"Money as IOUs in Social Trust Networks"* · |
11 | | -[webcontracts trustline.v1](https://github.com/webcontracts/webcontracts.github.io/issues/4) |
12 | | -· proven first as the [JSS `ripple` plugin](https://github.com/JavaScriptSolidServer/plugins). |
13 | | - |
14 | | -## 1. Identity — every user is a URI |
15 | | - |
16 | | -An **agent** is identified by a URI: |
17 | | - |
18 | | -- a node-local agent: `https://<node>/u/<name>#me` — MUST dereference to a |
19 | | - profile document (JSON) whose `@id` is the agent URI; |
20 | | -- a Solid WebID: `https://<host>/<pod>/profile/card#me`; |
21 | | -- a DID, e.g. `did:nostr:<hex>` (hex form canonical; npub is display-only). |
22 | | - |
23 | | -**Canonical spelling.** One agent MUST have exactly one spelling everywhere in |
24 | | -the ledger. Implementations MUST canonicalize known aliases at every id entry |
25 | | -point; specifically the WebID *document* form `/profile/card.jsonld#me` |
26 | | -normalizes to the *fragment* form `/profile/card#me`. (An identity-keyed graph |
27 | | -silently splits on spelling drift — this rule exists because we hit it live.) |
28 | | - |
29 | | -## 2. Amounts |
30 | | - |
31 | | -Amounts cross APIs as decimal numbers with at most **6 decimal places**, |
32 | | -`0 < amount ≤ 10^12`. All arithmetic is integer micro-units (amount × 10⁶). |
33 | | -A currency code matches `[A-Z0-9]{1,12}` (uppercased on input) and carries no |
34 | | -built-in meaning — USD, SATS, HRS, BEER are equally valid. |
35 | | - |
36 | | -## 3. Trustlines |
37 | | - |
38 | | -A **trustline** `(creditor, debtor, currency, limit)` is a unilateral grant: |
39 | | -the creditor permits the debtor to owe them up to `limit` of `currency`. |
40 | | - |
41 | | -- Only the creditor MAY create, resize (including to 0 = freeze), or remove |
42 | | - their line. The authenticated actor **is** the creditor — never a parameter. |
43 | | -- Self-trust is invalid. |
44 | | -- Removal MUST be refused (409) while the debtor owes on the line; the IOU |
45 | | - record itself lives in the balance (§4), not the line. |
46 | | - |
47 | | -## 4. Balances |
48 | | - |
49 | | -One **signed** balance per unordered pair + currency, stored as "lo owes hi" |
50 | | -with `[lo, hi] = sort(a, b)`. The two directions are the same number negated — |
51 | | -two mirrored entries are forbidden (they can desync; one signed number |
52 | | -cannot). A zero balance is removed. |
53 | | - |
54 | | -`debt(x→y)` = what x currently owes y (may be negative). |
55 | | -`capacity(x→y) = limit(y→x) − debt(x→y)` — spendable on a hop from x to y. |
56 | | -Negative debt makes owed-to-you credit spendable with **no trustline at all**: |
57 | | -paying back clears debt first ("clearing"). |
58 | | - |
59 | | -## 5. Payments |
60 | | - |
61 | | -A payment `(from, to, currency, amount)`: |
62 | | - |
63 | | -1. Find a path from→to where **every** hop has `capacity ≥ amount` |
64 | | - (breadth-first, shortest hops, path length ≤ 8; single path, no partial |
65 | | - fills — a payment either routes whole or fails with "no route"). |
66 | | -2. Shift `debt(pᵢ→pᵢ₊₁) += amount` along every hop **atomically**. |
67 | | - |
68 | | -Intermediaries need no per-payment consent: each hop consumes credit its next |
69 | | -node already granted. Pre-authorization *is* the routing permission; the only |
70 | | -per-request authorization is the sender's. |
71 | | - |
72 | | -**Settle**: the creditor records out-of-band repayment, reducing |
73 | | -`debt(peer→creditor)` by at most its current value. Only the party whose |
74 | | -claim shrinks may record it. |
75 | | - |
76 | | -## 6. The transition log |
77 | | - |
78 | | -Every state change appends an entry: |
79 | | - |
80 | | -```json |
81 | | -{ "seq": 7, "prev": "sha256:…", "ts": "2026-07-29T00:01:57Z", |
82 | | - "actor": "<agent URI>", "type": "send-payment", |
83 | | - "params": { "from": "…", "to": "…", "currency": "USD", |
84 | | - "amount": 300, "path": ["…","…","…"] }, |
85 | | - "hash": "sha256:…" } |
86 | | -``` |
87 | | - |
88 | | -`hash = sha256( JCS(entry \ {hash, sig}) )` with JCS = RFC 8785 canonical |
89 | | -JSON. `prev` is the previous entry's hash (`null` for seq 1); the newest hash |
90 | | -is the **tip**. Types: `create-trustline`, `update-trustline`, |
91 | | -`remove-trustline`, `send-payment`, `settle`. `GET /api/log/verify` re-derives |
92 | | -the chain; anyone can do the same from `GET /api/log`. |
93 | | - |
94 | | -## 7. Signatures — v1 (draft) |
95 | | - |
96 | | -v0 authenticates writes with node-local sessions; the node is trusted to |
97 | | -attribute actors honestly. v1 removes that trust: |
98 | | - |
99 | | -- Each transition gains `sig`: a BIP340 schnorr signature by the **actor's** |
100 | | - secp256k1 key over the same canonical bytes the hash covers |
101 | | - (`JCS(entry \ {hash, sig})`). |
102 | | -- The actor URI binds to the key via `did:nostr:<hex>` directly, or via a |
103 | | - verification method in the agent's profile document / WebID card. |
104 | | -- A node MUST reject a transition whose signature does not verify against the |
105 | | - actor's key — including its own; the node becomes a coordinator, not an |
106 | | - authority. Anyone replaying the log re-verifies every signature. |
107 | | -- A signed transition is deliberately shaped like a nostr event (pubkey, |
108 | | - created_at, kind, content, sig) so the ledger can be carried by nostr |
109 | | - infrastructure — which is the on-ramp to federation (v2): cross-node routes |
110 | | - as signed, relayable, independently verifiable events. |
111 | | - |
112 | | -## 8. HTTP API (v0) |
113 | | - |
114 | | -``` |
115 | | -POST /api/register {username,password} → 201 {agent, token} |
116 | | -POST /api/login {username,password} → 200 {agent, token} |
117 | | -GET /api/whoami → {agent|null} |
118 | | -GET /u/<name> → profile document |
119 | | -GET /api/graph → {trustlines[], balances[], seq, tip} |
120 | | -GET /api/balances?agent=<uri> → {agent, positions[], net{}} |
121 | | -GET /api/path?from&to¤cy&amount → {path[], hops} | 404 |
122 | | -POST /api/trustlines {peer,currency,limit} → 201/200 [creditor] |
123 | | -POST /api/trustlines/remove {peer,currency} → 200 | 409 [creditor] |
124 | | -POST /api/payments {to,currency,amount} → 200 | 404 [sender] |
125 | | -POST /api/settle {peer,currency,amount} → 200 | 409 [creditor] |
126 | | -GET /api/log?limit=N → {seq, tip, entries[]} |
127 | | -GET /api/log/verify → {valid, seq, tip} |
128 | | -``` |
129 | | - |
130 | | -Writes are `Authorization: Bearer` (stateless HMAC in v0; signature-bearing |
131 | | -bodies in v1 make the bearer optional). Errors are `{error}` with honest |
132 | | -status codes. The graph is public in v0 — per-agent visibility is an open |
133 | | -design question tracked in the roadmap. |
134 | | - |
135 | | -## 9. Known limits of v0 |
136 | | - |
137 | | -- **Single node.** Atomicity comes free in-process; cross-node payment is the |
138 | | - federation problem (v2) — hold/commit or hashlocks, not hand-waving. |
139 | | -- **Public graph.** Fugger's RipplePay showed users only their own lines; |
140 | | - a privacy model (per-agent views, maybe pod-mirrored statements) is v1+. |
141 | | -- **Log growth.** The state file rewrites whole; an append-log is the obvious |
142 | | - fix when it matters. |
| 1 | +# SolidPay Protocol 1.0 |
| 2 | + |
| 3 | +The normative specification lives at **[docs/spec/](https://jss.live/solidpay/docs/spec/)** |
| 4 | +([source](spec/index.html)) — an Editor's Draft covering conformance classes |
| 5 | +(Node / Client / Auditor), identity and canonical agent spelling, the data |
| 6 | +model (micro-unit amounts, unilateral trustlines, signed single balances), |
| 7 | +deterministic algorithms (JCS, capacity, route discovery), the five |
| 8 | +transitions with error semantics, the hash-chained transition log and its |
| 9 | +verification algorithm, the level-1 did:nostr signature draft, the HTTP API, |
| 10 | +security and privacy considerations, and **Appendix A test vectors** that the |
| 11 | +reference implementation pins in `test/vectors.test.js`. |
| 12 | + |
| 13 | +Quick links: |
| 14 | + |
| 15 | +- [Rendered spec](https://jss.live/solidpay/docs/spec/) |
| 16 | +- [Roadmap](roadmap.md) |
| 17 | +- [Reference implementation](https://github.com/JavaScriptSolidServer/solidpay) |
0 commit comments