pow-equix-wasm 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,25 +1,53 @@
1
- # pow-equix-wasm
1
+ # PoW Equi-X en WebAssembly
2
2
 
3
- Preuve de travail **Equi-X** (celle que Tor utilise contre les dénis de service)
4
- compilée en WebAssembly : **un seul module de 45 Kio** résout la preuve dans le
5
- navigateur, sur plusieurs cœurs, et la vérifie côté serveur en quelques centaines
6
- de microsecondes.
3
+ [![Licence LGPL-3.0](https://img.shields.io/badge/licence-LGPL--3.0-blue)](LICENSE)
4
+ [![Vérification](https://github.com/ContribuLibre/pow-equix-wasm/actions/workflows/verification.yml/badge.svg)](https://github.com/ContribuLibre/pow-equix-wasm/actions/workflows/verification.yml)
5
+ [![git tag](https://img.shields.io/github/v/tag/ContribuLibre/pow-equix-wasm?label=git%20tag&labelColor=41454c&color=fe7d37)](https://github.com/ContribuLibre/pow-equix-wasm/releases)
6
+ [![npm](https://img.shields.io/npm/v/pow-equix-wasm?label=npm)](https://www.npmjs.com/package/pow-equix-wasm)
7
7
 
8
- - **Démo et calibrage** : <https://contribulibre.github.io/pow-equix-wasm/>
9
- - **Paquet npm** : [`pow-equix-wasm`](https://www.npmjs.com/package/pow-equix-wasm)
8
+ Preuve de travail **Equi-X**, celle que Tor utilise contre les dénis de service :
9
+ un seul module de 45 Kio résout la preuve dans le navigateur, sur plusieurs
10
+ cœurs, et la vérifie côté serveur en quelques centaines de microsecondes. Là où
11
+ WebAssembly est désactivé, le même module traduit en JavaScript pur prend le
12
+ relais, et le mode dégradé est signalé.
10
13
 
11
- > *English summary: Equi-X proof of work (from Tor's Arti project) as a single
12
- > WebAssembly module. It solves in the browser across Web Workers (cancellable,
13
- > with progress) and verifies server-side in ~0.2 ms per part. The build is
14
- > reproducible byte for byte. LGPL-3.0.*
15
-
16
- ## Pourquoi Equi-X plutôt que SHA-256
14
+ - **Démo en ligne et calibrage :** <https://contribulibre.github.io/pow-equix-wasm/>
15
+ - **Code source :** <https://github.com/ContribuLibre/pow-equix-wasm>
16
+ - **Paquet npm :** [`pow-equix-wasm`](https://www.npmjs.com/package/pow-equix-wasm)
17
17
 
18
- Une preuve SHA-256 (façon hashcash) se calcule des milliers de fois plus vite sur
19
- une carte graphique ou une puce de minage que dans un navigateur. Equi-X tire au
20
- sort un nouveau programme HashX à chaque essai et demande environ 1,8 Mio de
21
- mémoire par essai : l’avantage d’un attaquant équipé tombe à un facteur de
22
- l’ordre de 20 face à un navigateur. La vérification, elle, reste très bon marché.
18
+ > *English summary: Equi-X proof of work (from Tor's Arti project) as a single
19
+ > WebAssembly module, with a pure-JavaScript fallback (wasm2js) where
20
+ > WebAssembly is disabled. It solves in the browser across Web Workers
21
+ > (cancellable, with progress and remaining-time estimate) and verifies
22
+ > server-side in ~0.25 ms per part. Reproducible build. LGPL-3.0.*
23
+
24
+ ## Pourquoi Equi-X
25
+
26
+ Une preuve de travail n’a de sens que si l’attaquant ne peut pas la calculer
27
+ beaucoup plus vite que la personne honnête, et si le serveur la vérifie pour
28
+ presque rien. Ordres de grandeur pour une preuve réglée sur **une minute dans un
29
+ navigateur, sur un cœur** :
30
+
31
+ | | SHA-256 (hashcash) | Argon2id (64 Mio par essai) | **Equi-X** |
32
+ |---|---|---|---|
33
+ | Un essai, navigateur | ≈ 0,1 à 10 µs | ≈ 0,3 à 1 s | **≈ 0,4 s** (mesuré) |
34
+ | Mémoire pour résoudre | < 1 Kio | 64 Mio par essai en cours | **≈ 2 Mio** par essai en cours (mesuré) |
35
+ | La même preuve sur matériel optimisé | carte graphique : ≈ 10 ms ; puce de minage : ≈ 1 µs | ≈ 10 à 30 s : la mémoire freine cartes graphiques et puces | **≈ 3 s** en code natif compilé (mesuré) ; programmes aléatoires conçus pour ne pas avantager les cartes graphiques |
36
+ | Avantage du matériel optimisé | × 10³ à × 10⁷ | × 2 à × 5 | **≈ × 20** |
37
+ | Vérifier une preuve | 1 empreinte : ≈ 1 µs, négligeable | 1 essai complet : ≈ 0,1 à 1 s **et** 64 Mio par vérification | **≈ 0,25 ms par part** en WebAssembly, ≈ 0,2 ms en natif (mesuré) ; pas de mémoire à réserver |
38
+
39
+ - **SHA-256** : les cartes graphiques et, pire, les puces de minage Bitcoin la
40
+ calculent des milliers à des millions de fois plus vite qu’un navigateur. Des
41
+ outils existent pour résoudre ces défis en masse.
42
+ - **Argon2id** : la mémoire exigée égalise bien le matériel, mais vérifier coûte
43
+ autant qu’un essai : chaque fausse preuve envoyée au serveur lui coûte des
44
+ dizaines de mégaoctets et des centaines de millisecondes.
45
+ - **Equi-X** combine les deux qualités : un nouveau programme HashX tiré au sort
46
+ à chaque essai et environ 2 Mio de mémoire, mais une vérification qui ne
47
+ refait qu’une poignée d’évaluations.
48
+
49
+ Les chiffres « mesurés » viennent de ce dépôt (portable x86-64 récent) ; les
50
+ autres sont des ordres de grandeur publics, à affiner avec la page de démo.
23
51
 
24
52
  ## Protocole
25
53
 
@@ -40,24 +68,67 @@ une autre.
40
68
 
41
69
  ## Utilisation
42
70
 
43
- ```ts
44
- import { ModuleEquix, construireGraine, hexadecimal, resoudre } from 'pow-equix-wasm'
71
+ ### Dans le navigateur
45
72
 
46
- // Navigateur : le module est un fichier du paquet, que le bundler publie (ici Vite).
73
+ ```ts
74
+ import { construireGraine, hexadecimal, resoudre } from 'pow-equix-wasm'
75
+ // Le module est un fichier du paquet, que le bundler publie (ici Vite).
47
76
  import urlEquix from 'pow-equix-wasm/equix.wasm?url'
48
- const octets = new Uint8Array(await (await fetch(urlEquix)).arrayBuffer())
49
77
 
78
+ const octets = new Uint8Array(await (await fetch(urlEquix)).arrayBuffer())
50
79
  const graine = await construireGraine('mon-service/1', JSON.stringify(requete))
51
80
  const annulation = new AbortController()
52
- const { parts, essais } = await resoudre({
81
+ const { parts } = await resoudre({
53
82
  octets, graine, effort: 1, nombre: 4,
54
83
  signal: annulation.signal,
55
- onProgression: ({ essais, parts, memoireOctets }) => { /* … */ },
84
+ onProgression: ({ moteur, essais, parts, dureeMs, restantEstimeMs, memoireOctets }) => { /* … */ },
56
85
  })
57
86
  envoyer({ ...requete, preuve: hexadecimal(parts) })
58
87
  ```
59
88
 
60
- Côté serveur (Bun, Node, Deno), la même bibliothèque vérifie :
89
+ `resoudre` répartit les compteurs entre des Web Workers créés depuis un Blob
90
+ (aucun fichier supplémentaire à publier) : par défaut un par cœur annoncé, huit
91
+ au plus, et jamais plus que d’essais attendus (`filsConseilles`). Si les Web
92
+ Workers sont refusés, le calcul continue sur le fil courant. `fils: 0` force ce
93
+ mode. La progression donne le moteur, les essais, les parts trouvées, le temps
94
+ écoulé, une estimation du temps restant mesurée sur l’appareil et la mémoire.
95
+
96
+ ### Sans WebAssembly : repli en JavaScript et mode dégradé
97
+
98
+ `pow-equix-wasm/js` fournit le même module traduit en JavaScript pur par wasm2js
99
+ (binaryen) : mêmes preuves, octet pour octet, mais bien plus lent. Il ne pèse
100
+ qu’environ 120 Ko une fois minifié, et n’a à être chargé que s’il sert :
101
+
102
+ ```ts
103
+ import { estimerDuree, ralentissement, resoudre, webAssemblyDisponible } from 'pow-equix-wasm'
104
+
105
+ const wasm = webAssemblyDisponible()
106
+ const js = wasm ? undefined : (await import('pow-equix-wasm/js')).creerExportsEquixJs
107
+ if (!wasm) {
108
+ // Sans WebAssembly, le JIT est en général coupé aussi : hypothèse prudente.
109
+ const attente = estimerDuree({ effort: 1, nombre: 4, execution: 'jsSansJit' })
110
+ if (attente > 5_000) avertir(`Active WebAssembly pour valider environ ${Math.round(ralentissement('jsSansJit'))} fois plus vite.`)
111
+ }
112
+ const resultat = await resoudre({ octets, js, graine, effort: 1, nombre: 4, onProgression })
113
+ // resultat.moteur vaut 'js' en mode dégradé.
114
+ ```
115
+
116
+ `moteur: 'wasm' | 'js' | 'auto'` impose ou laisse choisir le moteur ;
117
+ `moteurRetenu` dit à l’avance lequel servira. `ModuleEquix.instancier(octets)`
118
+ et `ModuleEquix.depuisJs(creerExportsEquixJs)` donnent chacun un module qui
119
+ résout et vérifie.
120
+
121
+ | Un cœur, portable x86-64 | Un essai | Ralentissement |
122
+ |---|---|---|
123
+ | WebAssembly (Chromium, Bun, Node) | ≈ 410 ms | × 1 |
124
+ | JavaScript avec JIT | ≈ 3,8 s (Chromium), 1,5 s (Node) | × 4 à × 9 |
125
+ | JavaScript sans JIT (Node `--jitless`) | ≈ 79 s | ≈ × 190 |
126
+
127
+ Désactiver WebAssembly s’accompagne presque toujours d’un JavaScript sans JIT
128
+ (Tor Browser en mode renforcé, mode Isolement d’iOS) : prévoir un avertissement
129
+ et une difficulté réaliste pour ces personnes.
130
+
131
+ ### Côté serveur
61
132
 
62
133
  ```ts
63
134
  import { readFile } from 'node:fs/promises'
@@ -68,51 +139,24 @@ const graine = await construireGraine('mon-service/1', JSON.stringify(requeteSan
68
139
  const valide = module.verifier(graine, depuisHexadecimal(preuve) ?? new Uint8Array(), 1, 4)
69
140
  ```
70
141
 
71
- Une page qui ne peut rien télécharger (ouverte en `file://`, script unique) peut
72
- intégrer le module en base64 :
73
-
74
- ```ts
75
- import { octetsEquix } from 'pow-equix-wasm/octets'
76
- const octets = octetsEquix()
77
- ```
142
+ Une page qui ne peut rien télécharger (ouverte en `file://`, script unique)
143
+ intègre le module en base64 avec `octetsEquix()` de `pow-equix-wasm/octets`.
78
144
 
79
145
  Ce qui reste à la charge de l’appelant, et que le paquet ne fait pas :
80
146
  refuser les preuves rejouées, vérifier la fraîcheur de la requête (horodatage
81
147
  dans le contenu haché) et limiter le débit des vérifications.
82
148
 
83
- ### Web Workers
84
-
85
- `resoudre` répartit les compteurs entre des Web Workers créés depuis un Blob
86
- (aucun fichier supplémentaire à publier) : par défaut un par cœur annoncé, huit
87
- au plus, et jamais plus que d’essais attendus. Si les Web Workers sont refusés
88
- (politique de sécurité, `file://` dans certains navigateurs), le calcul continue
89
- sur le fil courant, essai par essai. `fils: 0` force ce mode.
90
-
91
- ## Mesures
92
-
93
- Linux x86_64, un fil, module WebAssembly :
94
-
95
- | | Chromium | Bun 1.4 |
96
- |---|---|---|
97
- | un essai | ≈ 410 ms | ≈ 400 ms |
98
- | vérification d’une part | ≈ 230 µs | ≈ 250 µs |
99
- | mémoire par fil | ≈ 1,8 Mio de zone de travail (2,9 Mio pour l’instance) | |
100
-
101
- Les temps varient fortement d’un appareil à l’autre : c’est tout l’objet de la
102
- page de démo, qui mesure sur l’appareil utilisé le temps d’une preuve (médiane,
103
- 90ᵉ centile), la mémoire par fil et le coût de la vérification, et exporte le
104
- tout en JSON pour comparer plusieurs appareils.
105
-
106
149
  ## Construction reproductible
107
150
 
108
- Le module est construit, jamais versionné : `dist/` et `site/` sont produits
109
- par la CI.
151
+ Rien de construit n’est versionné : `dist/` et `site/` sont produits par la CI.
110
152
 
111
153
  - Rust figé par `rust-toolchain.toml`, dépendances verrouillées par `Cargo.lock`
112
154
  (`cargo build --locked`), chemins locaux effacés du binaire
113
- (`--remap-path-prefix`), pas de compilation incrémentale.
114
- - `bun run verify:reproductible` reconstruit depuis une copie des sources placée
115
- ailleurs et exige un module identique à l’octet près.
155
+ (`--remap-path-prefix`), pas de compilation incrémentale ; binaryen, donc
156
+ wasm2js, figé dans `package.json`.
157
+ - `bun run verify:reproductible` reconstruit le module et sa traduction
158
+ JavaScript depuis une copie des sources placée ailleurs, et exige des
159
+ fichiers identiques à l’octet près.
116
160
  - Chaque release GitHub donne le SHA-256 du module publié ; pour le vérifier :
117
161
  `bun run build && bun run verify:reproductible --attendu <sha256>`.
118
162
 
@@ -127,11 +171,10 @@ Il faut Rust (via rustup, qui installe seul la version figée et la cible
127
171
 
128
172
  ## Publication
129
173
 
130
- - **Démo** : chaque push sur `main` la publie sur GitHub Pages (réglage du dépôt :
131
- *Settings → Pages → Source : GitHub Actions*).
174
+ - **Démo** : chaque push sur `main` la publie sur GitHub Pages.
132
175
  - **npm** : pousser un tag `vX.Y.Z` égal à la version de `package.json`. Le
133
- workflow vérifie tout, publie sans aucun secret (publication de confiance
134
- npm par OIDC, avec provenance signée), puis crée la release GitHub avec
176
+ workflow vérifie tout, publie sans aucun secret (publication de confiance npm
177
+ par OIDC, avec provenance signée), puis crée la release GitHub avec
135
178
  `equix.wasm` et son empreinte.
136
179
 
137
180
  ## Organisation
@@ -141,7 +184,8 @@ Il faut Rust (via rustup, qui installe seul la version figée et la cible
141
184
  dans une application Tauri).
142
185
  - `crates/pow-equix-wasm` : interface C minimale vers WebAssembly, sans
143
186
  wasm-bindgen.
144
- - `src/index.ts` : chargeur, Web Workers, annulation, progression, vérification.
187
+ - `src/index.ts` : chargeur, deux moteurs, Web Workers, annulation,
188
+ progression, estimations, vérification.
145
189
  - `demo/` : page de démo et de calibrage.
146
190
 
147
191
  ## Licence
@@ -1,6 +1,11 @@
1
1
  {
2
- "version": "0.1.0",
2
+ "version": "0.2.1",
3
3
  "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)",
4
4
  "octets": 44968,
5
- "sha256": "f6c72e8e6b48386c23b6d1259c1739fef0711b11ffa692bcaed3e40a5a41c840"
5
+ "sha256": "f6c72e8e6b48386c23b6d1259c1739fef0711b11ffa692bcaed3e40a5a41c840",
6
+ "js": {
7
+ "binaryen": "132.0.0",
8
+ "octets": 497901,
9
+ "sha256": "a4da12ce262d3c8c86b344fd568bab7e5a63d1a51ea35758606d4f8007f3475b"
10
+ }
6
11
  }
@@ -0,0 +1,3 @@
1
+ import type { ExportsEquix } from './index.js'
2
+ /** Crée une instance du module traduit en JavaScript : mêmes exports que equix.wasm. */
3
+ export declare function creerExportsEquixJs(): ExportsEquix