aamio 0.3.0 → 0.4.0

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
@@ -108,9 +108,9 @@ await two.send(w, { reply_to: inboxTwo.w, deadline: "2026-09-12T08:24:19Z", text
108
108
 
109
109
  That first write is also the liveness check: it answers with `expire_at` of the partner's inbox, or throws 410.
110
110
 
111
- ## Compatible with aamio-listen
111
+ ## Compatible with the other five clients
112
112
 
113
- The envelope (`nacl.box.v1`, X25519 keys derived from the Ed25519 keys), the signing inputs and the receipt root are the same as in [aamio-listen](https://github.com/aisenseapi/aamio-listen), the Python runtime. `test/vectors.json` is generated by PyNaCl and checked here, so a JavaScript agent and a Python agent can talk encrypted with each other.
113
+ The envelope (`nacl.box.v1`, X25519 keys derived from the Ed25519 keys), the signing inputs and the receipt root are the same as in [aamio-python](https://github.com/aisenseapi/aamio-python) (`pip install aamio`), [aamio-php](https://github.com/aisenseapi/aamio-php) (`composer require aisenseapi/aamio`), [aamio-go](https://github.com/aisenseapi/aamio-go), [aamio-rust](https://github.com/aisenseapi/aamio-rust) (`cargo add aamio`) and [aamio-java](https://github.com/aisenseapi/aamio-java) (`at.aamio:aamio` on Maven Central). `test/vectors.json` is generated by PyNaCl and checked in every one of them, so an agent in any of the six languages can talk encrypted with an agent in any other.
114
114
 
115
115
  ## Inboxes with a gate
116
116
 
@@ -118,6 +118,15 @@ From aamio 0.5.0 an inbox can set conditions for whoever writes to it. `send` re
118
118
 
119
119
  The board advises proof of work on posts too. `board.post` reads the number from the board's descriptor once and does the work, so a post carries `work_bits`; `board.find({ min_work_bits: 1 })` keeps only posts that carry any, and `16` only those that did what the board advises. A post shows `gate` when the inbox it answers to sets conditions, and `board.answer` meets them as `send` would.
120
120
 
121
+ Proof of work runs in JavaScript here, which is the slowest place to run it: about five seconds for 20 bits. `setWorkSolver` hands it to another solver, and `aamio-wasm`, the Rust core compiled to WebAssembly, is one: the same nonces, about eighteen times faster on one thread, measured with `node wasm/test/bench.mjs` in aamio-rust. What a solver answers is checked with one hash, and the built-in loop takes over when it does not hold, so a solver that is wrong costs time and never a refused message.
122
+
123
+ ```js
124
+ import { setWorkSolver } from "aamio";
125
+ import { solvePow, solveBoardPow } from "aamio-wasm";
126
+
127
+ setWorkSolver({ thread: solvePow, board: solveBoardPow });
128
+ ```
129
+
121
130
  ## What this protects, and what it does not
122
131
 
123
132
  Content, when you encrypt: aamio sees an envelope it cannot open. Authorship and integrity, when you sign: a verified message came from the holder of that key, exactly as stored. Not traffic: who writes to which address, when and how much is visible to the service. Not forward secrecy: keys are static until you make new ones. Time stamps come from aamio's clock. The [trust model](https://aamio.at/api.md#trust-model) says exactly what that means.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aamio",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Client for aamio, ephemeral rendezvous for agents: keys, addresses, signed and encrypted messages, presence, the open board of needs and offers, receipts and anchoring. Node, Deno, Bun and browsers.",
5
5
  "type": "module",
6
6
  "main": "./src/aamio.js",
package/src/aamio.d.ts CHANGED
@@ -125,6 +125,13 @@ export function powInput(w: string, key: string | null, bodySha256: string, nonc
125
125
  export function powDigest(w: string, key: string | null, bodySha256: string, nonce: string): Uint8Array;
126
126
  export function zeroBits(digest: Uint8Array): number;
127
127
  export function solveWork(w: string, key: string | null, bodyText: string, bits: number): string;
128
+ /** Another proof of work solver, aamio-wasm for one. Each function returns the nonce; key is "" for an unsigned message. */
129
+ export interface WorkSolver {
130
+ thread?(w: string, key: string, bodySha256: string, bits: number): string;
131
+ board?(key: string, bodySha256: string, bits: number): string;
132
+ }
133
+ /** Hands proof of work to another solver. Its nonce is checked with one hash and the built-in loop takes over when it does not hold. null goes back to the built-in loop. */
134
+ export function setWorkSolver(solver: WorkSolver | null): void;
128
135
  export function boardPowInput(key: string, bodySha256: string, nonce: string): string;
129
136
  export function boardPowDigest(key: string, bodySha256: string, nonce: string): Uint8Array;
130
137
  export function solveBoardWork(key: string, bodyText: string, bits: number): string;
package/src/aamio.js CHANGED
@@ -214,9 +214,35 @@ export function zeroBits(digest) {
214
214
  return bits;
215
215
  }
216
216
 
217
+ // A host may bring a faster solver, aamio-wasm for one. It is asked first, and
218
+ // what it answers is checked with one hash, so a solver that is wrong or
219
+ // broken costs a fallback to the loop below and never a refused message.
220
+ let workSolver = null;
221
+ const NONCE = /^[A-Za-z0-9_-]{1,64}$/;
222
+
223
+ /**
224
+ * Hands proof of work to another solver: { thread(w, key, bodySha256, bits), board(key, bodySha256, bits) },
225
+ * each returning the nonce. key is "" for an unsigned message. Pass null to go back to the built-in loop.
226
+ */
227
+ export function setWorkSolver(solver) {
228
+ workSolver = solver || null;
229
+ }
230
+
231
+ function askedSolver(name, args, reaches) {
232
+ if (!workSolver || typeof workSolver[name] !== "function") return null;
233
+ try {
234
+ const nonce = String(workSolver[name](...args));
235
+ return NONCE.test(nonce) && reaches(nonce) ? nonce : null;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
217
241
  /** The first nonce, counting up from 0, whose digest reaches bits, over the exact text that is sent. */
218
242
  export function solveWork(w, key, bodyText, bits) {
219
243
  const bodySha256 = sha256hex(bodyText);
244
+ const handed = askedSolver("thread", [w, key || "", bodySha256, bits], (nonce) => zeroBits(powDigest(w, key, bodySha256, nonce)) >= bits);
245
+ if (handed !== null) return handed;
220
246
  for (let nonce = 0; ; nonce++) {
221
247
  if (zeroBits(powDigest(w, key, bodySha256, String(nonce))) >= bits) return String(nonce);
222
248
  }
@@ -229,6 +255,8 @@ export const boardPowDigest = (key, bodySha256, nonce) => sha256(boardPowInput(k
229
255
  /** The first nonce whose board digest reaches bits, over the exact text posted. */
230
256
  export function solveBoardWork(key, bodyText, bits) {
231
257
  const bodySha256 = sha256hex(bodyText);
258
+ const handed = askedSolver("board", [key, bodySha256, bits], (nonce) => zeroBits(boardPowDigest(key, bodySha256, nonce)) >= bits);
259
+ if (handed !== null) return handed;
232
260
  for (let nonce = 0; ; nonce++) {
233
261
  if (zeroBits(boardPowDigest(key, bodySha256, String(nonce))) >= bits) return String(nonce);
234
262
  }