organic-protocol 0.1.0 → 0.1.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.
- package/PROTOCOL.md +65 -65
- package/package.json +1 -1
package/PROTOCOL.md
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
# Organic Money —
|
|
1
|
+
# Organic Money — Inter-instance Protocol (v1)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document is the **standard** every compatible implementation (app or server) must follow. The [`organic-protocol`](https://www.npmjs.com/package/organic-protocol) npm package is its exact TypeScript translation; JS/TS implementations should import it rather than reimplement. Cryptography (signatures, chains, encryption) is defined by [`organic-money`](https://www.npmjs.com/package/organic-money) and is not repeated here.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Protocol version: **1**. QR format version: **1** (independent, see §4).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## 1.
|
|
9
|
+
## 1. Federated identity
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
A public key alone is not enough to reach someone in a decentralized network. An **identity** is always the pair:
|
|
12
12
|
|
|
13
13
|
```json
|
|
14
|
-
{ "pk": "<
|
|
14
|
+
{ "pk": "<compressed SECP256K1 public key, hex>", "url": "<root URL of the referent server>" }
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The `url` is the server's **root** URL (no `/api`, no trailing slash), normalized: `https` scheme by default (`http` tolerated when explicit — local networks, development), lowercase host. See `normalizeServerUrl`.
|
|
18
18
|
|
|
19
|
-
## 2.
|
|
19
|
+
## 2. Wire formats
|
|
20
20
|
|
|
21
21
|
### 2.1 Transaction (`TxWire`)
|
|
22
22
|
|
|
23
23
|
```json
|
|
24
|
-
{ "v": 1, "d": 20260719, "t": 3, "p": "<pk
|
|
25
|
-
"m": [20260719001], "i": [], "h": "<signature
|
|
24
|
+
{ "v": 1, "d": 20260719, "t": 3, "p": "<target pk>", "s": "<signer pk>",
|
|
25
|
+
"m": [20260719001], "i": [], "h": "<DER signature, hex>" }
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
|
28
|
+
| Field | Long name | Content |
|
|
29
29
|
|---|---|---|
|
|
30
|
-
| `v` | version | version
|
|
31
|
-
| `d` | date |
|
|
32
|
-
| `t` | type | type
|
|
33
|
-
| `p` | target |
|
|
34
|
-
| `s` | signer |
|
|
35
|
-
| `m` | money |
|
|
36
|
-
| `i` | invests |
|
|
37
|
-
| `h` | signature |
|
|
30
|
+
| `v` | version | protocol version (1) |
|
|
31
|
+
| `d` | date | `YYYYMMDD` integer |
|
|
32
|
+
| `t` | type | transaction type (§3) |
|
|
33
|
+
| `p` | target | recipient's public key |
|
|
34
|
+
| `s` | signer | sender's public key |
|
|
35
|
+
| `m` | money | money unit ids (`YYYYMMDDXXX`) |
|
|
36
|
+
| `i` | invests | invest unit ids (`YYYYMMDD9XXX`) |
|
|
37
|
+
| `h` | signature | SECP256K1 DER signature, hex |
|
|
38
38
|
|
|
39
|
-
### 2.2
|
|
39
|
+
### 2.2 Block (`BlockWire`)
|
|
40
40
|
|
|
41
41
|
```json
|
|
42
|
-
{ "v": 1, "d": 20260719, "p": "<
|
|
43
|
-
"m": [], "i": [], "t": 42, "r": "<
|
|
42
|
+
{ "v": 1, "d": 20260719, "p": "<previous block signature>", "s": "<pk>",
|
|
43
|
+
"m": [], "i": [], "t": 42, "r": "<merkle root>", "h": "<signature>", "x": [ …TxWire ] }
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
`t`
|
|
46
|
+
Here `t` is the **total** (cumulative economic experience); `p` is the previous block's signature (chain link); `x` is the transaction list.
|
|
47
47
|
|
|
48
|
-
## 3.
|
|
48
|
+
## 3. Transaction types
|
|
49
49
|
|
|
50
50
|
| # | Type | # | Type |
|
|
51
51
|
|---|---|---|---|
|
|
@@ -59,73 +59,73 @@ L'`url` est l'URL **racine** du serveur (sans `/api`, sans slash final), normali
|
|
|
59
59
|
|
|
60
60
|
## 4. QR codes
|
|
61
61
|
|
|
62
|
-
Format
|
|
62
|
+
Format: **`OM<version>:<TYPE>:<JSON payload>`** — the prefix lives outside the JSON so any reader can dispatch (or reject an unknown version) without parsing. Current version: `1`.
|
|
63
63
|
|
|
64
|
-
**
|
|
64
|
+
**Compatibility rule**: a reader encountering a version it does not speak MUST refuse cleanly ("please update the app") and MUST NEVER attempt a best-effort interpretation.
|
|
65
65
|
|
|
66
|
-
| Type |
|
|
66
|
+
| Type | Shape | Usage |
|
|
67
67
|
|---|---|---|
|
|
68
|
-
| `CT` | `OM1:CT:{"pk","url","n","e"?}` |
|
|
69
|
-
| `TX` | `OM1:TX:{"tx":<TxWire
|
|
70
|
-
| `BR` | `OM1:BR:{"pk","url","n"}` |
|
|
71
|
-
| `PP` | `OM1:PP:{"tx":<TxWire
|
|
68
|
+
| `CT` | `OM1:CT:{"pk","url","n","e"?}` | Contact card. `n` = display name; `e` = true for an ecosystem. |
|
|
69
|
+
| `TX` | `OM1:TX:{"tx":<PAY TxWire>,"url"}` | Offline payment, camera-to-screen. `url` = the PAYER's server, for deferred verification (§5, `tx/verify`). |
|
|
70
|
+
| `BR` | `OM1:BR:{"pk","url","n"}` | Validation request of a new citizen. Blocks are fetched via `GET {url}/api/v1/validations/{pk}` (Phase 2) — the QR is a pointer, not a container. |
|
|
71
|
+
| `PP` | `OM1:PP:{"tx":<PAPER TxWire>}` | Printed paper bill. |
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Constraints: the `tx` of a `TX` is of type PAY; the one of a `PP` is of type PAPER.
|
|
74
74
|
|
|
75
|
-
## 5. API
|
|
75
|
+
## 5. REST API `/api/v1`
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
All routes live under `{url}/api/v1`. JSON bodies. Errors carry `{ "error": "<message>", "code": "<ApiErrorCode>"? }`.
|
|
78
78
|
|
|
79
|
-
### 5.1
|
|
79
|
+
### 5.1 Authentication schemes
|
|
80
80
|
|
|
81
|
-
**Block-auth** (`PUT /users/save`, `PUT /users/sign`) —
|
|
81
|
+
**Block-auth** (`PUT /users/save`, `PUT /users/sign`) — `x-signature` header = signature of the submitted block's hash, by the account key:
|
|
82
82
|
`x-signature = signHash(block.hash(), sk)`.
|
|
83
83
|
|
|
84
|
-
**Timestamp-auth** (`GET /tx/list`, `POST /users/password`, …) —
|
|
84
|
+
**Timestamp-auth** (`GET /tx/list`, `POST /users/password`, …) — `x-signature` header = `signHash(sha256(publickey + ":" + timestamp), sk)` with `publickey` and `timestamp` (Unix seconds) passed as params/body. Tolerance: ±5 minutes, otherwise 401.
|
|
85
85
|
|
|
86
86
|
### 5.2 Endpoints (Phase 1)
|
|
87
87
|
|
|
88
|
-
|
|
|
88
|
+
| Method & route | Auth | Body → Response | Notes |
|
|
89
89
|
|---|---|---|---|
|
|
90
|
-
| `GET /info` | — | → `InfoResponse` |
|
|
91
|
-
| `GET /servers` | — | → `ServerListEntry[]` |
|
|
92
|
-
| `POST /users/register` | — | `RegisterBody` → `RegisterResponse` |
|
|
93
|
-
| `POST /users/login` | — | `LoginBody` → `LoginResponse` | **rotation
|
|
94
|
-
| `PUT /users/save` | block | `SaveBlockBody` → 200 | `409 DEVICE_REVOKED`
|
|
95
|
-
| `PUT /users/sign` | block | `SignBlockBody` → 200 |
|
|
96
|
-
| `POST /users/password` | timestamp | `PasswordChangeBody` → 200 |
|
|
97
|
-
| `POST /tx/send` | — | `TxSendBody` → 200 | **
|
|
98
|
-
| `GET /tx/list` | timestamp | → `TxWire[]` |
|
|
99
|
-
| `POST /tx/verify` | — | `TxVerifyBody` → `TxVerifyResponse` |
|
|
100
|
-
| `POST /papers/cash` | — | `PapersCashBody` → 200 |
|
|
101
|
-
| `GET /papers/isCashed?hash=` | — | → `IsCashedResponse` \| 404 | 404 =
|
|
90
|
+
| `GET /info` | — | → `InfoResponse` | public identity card of the server |
|
|
91
|
+
| `GET /servers` | — | → `ServerListEntry[]` | directory of known servers |
|
|
92
|
+
| `POST /users/register` | — | `RegisterBody` → `RegisterResponse` | the `secretkey` arrives ENCRYPTED (opaque to the server); the password is bcrypted on arrival |
|
|
93
|
+
| `POST /users/login` | — | `LoginBody` → `LoginResponse` | **devicetoken rotation**: the previous device is revoked |
|
|
94
|
+
| `PUT /users/save` | block | `SaveBlockBody` → 200 | `409 DEVICE_REVOKED` when the devicetoken is no longer the active one |
|
|
95
|
+
| `PUT /users/sign` | block | `SignBlockBody` → 200 | the server signs the last block (bills, genesis) |
|
|
96
|
+
| `POST /users/password` | timestamp | `PasswordChangeBody` → 200 | updates bcrypt + re-encrypted sk, without reading anything |
|
|
97
|
+
| `POST /tx/send` | — | `TxSendBody` → 200 | **MANDATORY cross-verification** (§5.3) |
|
|
98
|
+
| `GET /tx/list` | timestamp | → `TxWire[]` | pending payments for the key |
|
|
99
|
+
| `POST /tx/verify` | — | `TxVerifyBody` → `TxVerifyResponse` | read-only; statuses: `confirmed` / `pending` / `invalid` / `unknown-sender` |
|
|
100
|
+
| `POST /papers/cash` | — | `PapersCashBody` → 200 | requires the full PAPER, verifies its crypto; `409 ALREADY_CASHED` |
|
|
101
|
+
| `GET /papers/isCashed?hash=` | — | → `IsCashedResponse` \| 404 | 404 = never cashed |
|
|
102
102
|
|
|
103
|
-
### 5.3
|
|
103
|
+
### 5.3 Cross-verification (server duty)
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
Before accepting on `tx/send` (and, in Phase 2, any ecosystem input), the server MUST:
|
|
106
106
|
|
|
107
|
-
1.
|
|
108
|
-
2.
|
|
109
|
-
3.
|
|
107
|
+
1. verify the cryptographic validity of the transaction;
|
|
108
|
+
2. load the SAVED chain of the sender (`tx.s`) and validate it (`assertIsValid`);
|
|
109
|
+
3. verify the transaction exists in that chain's history (by its signature `h`).
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Without step 3, an attacker can sign a transaction carrying units they do not own. Client-side consequence: the **`pay → save → send` order is strict**.
|
|
112
112
|
|
|
113
|
-
### 5.4
|
|
113
|
+
### 5.4 One account = one active device
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
The server issues an opaque `devicetoken` (UUID) at `register` and at every `login`; only the last one issued is valid. A `save` carrying a stale token receives `409 DEVICE_REVOKED` — the app must then switch to read-only and offer the restore flow. This mechanism is not cryptographic (identity is still proven by `x-signature`); it prevents the accidental fork of a chain across two devices.
|
|
116
116
|
|
|
117
|
-
### 5.5
|
|
117
|
+
### 5.5 Error codes
|
|
118
118
|
|
|
119
119
|
`INVALID_TX` · `INVALID_CHAIN` · `TX_NOT_IN_CHAIN` · `UNKNOWN_SENDER` · `UNKNOWN_USER` · `INVALID_SIGNATURE` · `DEVICE_REVOKED` · `ALREADY_CASHED`
|
|
120
120
|
|
|
121
|
-
## 6.
|
|
121
|
+
## 6. Versioning
|
|
122
122
|
|
|
123
|
-
- **
|
|
124
|
-
- **QR** (`QR_VERSION`,
|
|
125
|
-
- **API** (`/api/v1`)
|
|
123
|
+
- **Protocol** (`PROTOCOL_VERSION`, `v` field of txs/blocks): bumped only on a wire-format break.
|
|
124
|
+
- **QR** (`QR_VERSION`, `OM<v>` prefix): bumped only on a QR payload break.
|
|
125
|
+
- **API** (`/api/v1`): a REST contract break creates `/api/v2`; `GET /info` announces the spoken versions.
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
The three evolve independently. Every implementation MUST announce its versions via `GET /info` and refuse cleanly what it does not speak.
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
131
|
-
*
|
|
131
|
+
*MIT license — © suipotryot. Phases 2 (ecosystems, validations) and 3 (federation) will extend this document without breaking v1.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "organic-protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The shared protocol of the Organic Economy: wire formats, QR codes and API contracts, verified by the TypeScript compiler. Companion of organic-money.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"organic-economy",
|