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.
Files changed (2) hide show
  1. package/PROTOCOL.md +65 -65
  2. package/package.json +1 -1
package/PROTOCOL.md CHANGED
@@ -1,51 +1,51 @@
1
- # Organic Money — Protocole inter-instances (v1)
1
+ # Organic Money — Inter-instance Protocol (v1)
2
2
 
3
- Ce document est le **standard** que toute implémentation compatible (application ou serveur) doit respecter. Le paquet npm [`organic-protocol`](https://www.npmjs.com/package/organic-protocol) en est la traduction TypeScript exacte ; les implémentations JS/TS devraient l'importer plutôt que réimplémenter. La cryptographie (signatures, chaînes, chiffrement) est définie par [`organic-money`](https://www.npmjs.com/package/organic-money) et n'est pas répétée ici.
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
- Version du protocole : **1**. Version du format QR : **1** (indépendante, voir §4).
5
+ Protocol version: **1**. QR format version: **1** (independent, see §4).
6
6
 
7
7
  ---
8
8
 
9
- ## 1. Identité fédérée
9
+ ## 1. Federated identity
10
10
 
11
- Une clé publique seule ne suffit pas à joindre quelqu'un dans un réseau décentralisé. Une **identité** est toujours la paire :
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": "<clé publique SECP256K1 compressée, hex>", "url": "<URL racine du serveur référent>" }
14
+ { "pk": "<compressed SECP256K1 public key, hex>", "url": "<root URL of the referent server>" }
15
15
  ```
16
16
 
17
- L'`url` est l'URL **racine** du serveur (sans `/api`, sans slash final), normalisée : schéma `https` par défaut (`http` toléré s'il est explicite réseaux locaux, développement), hôte en minuscules. Voir `normalizeServerUrl`.
17
+ The `url` is the server's **root** URL (no `/api`, no trailing slash), normalized: `https` scheme by default (`http` tolerated when explicitlocal networks, development), lowercase host. See `normalizeServerUrl`.
18
18
 
19
- ## 2. Formats de fil (wire)
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 cible>", "s": "<pk signataire>",
25
- "m": [20260719001], "i": [], "h": "<signature DER hex>" }
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
- | Champ | Nom long | Contenu |
28
+ | Field | Long name | Content |
29
29
  |---|---|---|
30
- | `v` | version | version du protocole (1) |
31
- | `d` | date | entier `YYYYMMDD` |
32
- | `t` | type | type de transaction (§3) |
33
- | `p` | target | clé publique du destinataire |
34
- | `s` | signer | clé publique de l'émetteur |
35
- | `m` | money | ids d'unités de monnaie (`YYYYMMDDXXX`) |
36
- | `i` | invests | ids d'unités d'invest (`YYYYMMDD9XXX`) |
37
- | `h` | signature | signature SECP256K1 DER, hex |
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 Bloc (`BlockWire`)
39
+ ### 2.2 Block (`BlockWire`)
40
40
 
41
41
  ```json
42
- { "v": 1, "d": 20260719, "p": "<signature du bloc précédent>", "s": "<pk>",
43
- "m": [], "i": [], "t": 42, "r": "<racine de Merkle>", "h": "<signature>", "x": [ …TxWire ] }
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` est ici le **total** (expérience économique cumulée) ; `p` est la signature du bloc précédent (chaînage) ; `x` la liste des transactions.
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. Types de transaction
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 : **`OM<version>:<TYPE>:<payload JSON>`** — le préfixe est hors JSON pour permettre le dispatch (et le rejet d'une version inconnue) sans parser. Version courante : `1`.
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
- **Règle de compatibilité** : un lecteur qui rencontre une version qu'il ne connaît pas DOIT refuser proprement (« mettez à jour l'application ») et NE DOIT JAMAIS tenter d'interpréter au mieux.
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 | Forme | Usage |
66
+ | Type | Shape | Usage |
67
67
  |---|---|---|
68
- | `CT` | `OM1:CT:{"pk","url","n","e"?}` | Carte de contact. `n` = nom affiché ; `e` = true si écosystème. |
69
- | `TX` | `OM1:TX:{"tx":<TxWire PAY>,"url"}` | Paiement hors-ligne, caméra contre écran. `url` = serveur du PAYEUR, pour la vérification différée (§5, `tx/verify`). |
70
- | `BR` | `OM1:BR:{"pk","url","n"}` | Demande de validation d'un nouveau citoyen. Les blocs sont récupérés via `GET {url}/api/v1/validations/{pk}` (Phase 2) — le QR est un pointeur, pas un conteneur. |
71
- | `PP` | `OM1:PP:{"tx":<TxWire PAPER>}` | Billet papier imprimé. |
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
- Contraintes : le `tx` d'un `TX` est de type PAY ; celui d'un `PP` est de type PAPER.
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 REST `/api/v1`
75
+ ## 5. REST API `/api/v1`
76
76
 
77
- Toutes les routes vivent sous `{url}/api/v1`. Corps en JSON. Les erreurs portent `{ "error": "<message>", "code": "<ApiErrorCode>"? }`.
77
+ All routes live under `{url}/api/v1`. JSON bodies. Errors carry `{ "error": "<message>", "code": "<ApiErrorCode>"? }`.
78
78
 
79
- ### 5.1 Authentifications
79
+ ### 5.1 Authentication schemes
80
80
 
81
- **Block-auth** (`PUT /users/save`, `PUT /users/sign`) — en-tête `x-signature` = signature du hash du bloc soumis, par la clé du compte :
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`, …) — en-tête `x-signature` = `signHash(sha256(publickey + ":" + timestamp), sk)` avec `publickey` et `timestamp` (secondes Unix) transmis en paramètres/corps. Tolérance : ±5 minutes, sinon 401.
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
- | Méthode & route | Auth | CorpsRéponse | Notes |
88
+ | Method & route | Auth | BodyResponse | Notes |
89
89
  |---|---|---|---|
90
- | `GET /info` | — | → `InfoResponse` | carte d'identité publique du serveur |
91
- | `GET /servers` | — | → `ServerListEntry[]` | annuaire des serveurs connus |
92
- | `POST /users/register` | — | `RegisterBody` → `RegisterResponse` | la `secretkey` arrive CHIFFRÉE (opaque pour le serveur) ; le mot de passe est bcrypté à l'arrivée |
93
- | `POST /users/login` | — | `LoginBody` → `LoginResponse` | **rotation du devicetoken** : l'appareil précédent est révoqué |
94
- | `PUT /users/save` | block | `SaveBlockBody` → 200 | `409 DEVICE_REVOKED` si le devicetoken n'est plus l'actif |
95
- | `PUT /users/sign` | block | `SignBlockBody` → 200 | le serveur signe le dernier bloc (billets, genèse) |
96
- | `POST /users/password` | timestamp | `PasswordChangeBody` → 200 | met à jour bcrypt + sk re-chiffrée, sans rien lire |
97
- | `POST /tx/send` | — | `TxSendBody` → 200 | **vérification croisée OBLIGATOIRE** (§5.3) |
98
- | `GET /tx/list` | timestamp | → `TxWire[]` | paiements en attente pour la clé |
99
- | `POST /tx/verify` | — | `TxVerifyBody` → `TxVerifyResponse` | lecture seule ; statuts : `confirmed` / `pending` / `invalid` / `unknown-sender` |
100
- | `POST /papers/cash` | — | `PapersCashBody` → 200 | exige le PAPER complet, vérifie sa crypto ; `409 ALREADY_CASHED` |
101
- | `GET /papers/isCashed?hash=` | — | → `IsCashedResponse` \| 404 | 404 = jamais encaissé |
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 Vérification croisée (obligation du serveur)
103
+ ### 5.3 Cross-verification (server duty)
104
104
 
105
- Avant d'accepter sur `tx/send` (et, en Phase 2, toute entrée d'écosystème), le serveur DOIT :
105
+ Before accepting on `tx/send` (and, in Phase 2, any ecosystem input), the server MUST:
106
106
 
107
- 1. vérifier la validité cryptographique de la transaction ;
108
- 2. charger la chaîne **sauvegardée** de l'émetteur (`tx.s`) et la valider (`assertIsValid`) ;
109
- 3. vérifier que la transaction figure dans l'historique de cette chaîne (par sa signature `h`).
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
- Sans l'étape 3, un attaquant peut signer une transaction portant des unités qu'il ne possède pas. Conséquence côté client : l'ordre **`pay → save → send` est strict**.
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 Un compte = un appareil actif
113
+ ### 5.4 One account = one active device
114
114
 
115
- Le serveur émet un `devicetoken` opaque (UUID) au `register` et à chaque `login` ; seul le dernier émis est valide. Un `save` portant un jeton périmé reçoit `409 DEVICE_REVOKED` — l'application doit alors passer en lecture seule et proposer la restauration. Ce mécanisme n'est pas cryptographique (l'identité reste prouvée par `x-signature`) ; il empêche le fork accidentel d'une chaîne entre deux appareils.
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 Codes d'erreur
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. Versionnement
121
+ ## 6. Versioning
122
122
 
123
- - **Protocole** (`PROTOCOL_VERSION`, champ `v` des tx/blocs) : n'augmente que sur rupture du format de fil.
124
- - **QR** (`QR_VERSION`, préfixe `OM<v>`) : n'augmente que sur rupture des payloads QR.
125
- - **API** (`/api/v1`) : une rupture de contrat REST crée `/api/v2` ; `GET /info` annonce les versions parlées.
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
- Les trois évoluent indépendamment. Toute implémentation DOIT annoncer ses versions via `GET /info` et refuser proprement ce qu'elle ne parle pas.
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
- *Licence MIT — © suipotryot. Les phases 2 (écosystèmes, validations) et 3 (fédération) étendront ce document sans casser la v1.*
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.0",
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",