aamio 0.3.0 → 0.4.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 +11 -2
- package/package.json +1 -1
- package/src/aamio.d.ts +7 -0
- package/src/aamio.js +34 -1
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
|
|
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-
|
|
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
|
+
"version": "0.4.1",
|
|
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
|
}
|
|
@@ -677,7 +705,12 @@ export class Aamio {
|
|
|
677
705
|
constructor({ base = DEFAULT_BASE, board = DEFAULT_BOARD, keys = null, fetch = globalThis.fetch } = {}) {
|
|
678
706
|
this.base = base.replace(/\/+$/, "");
|
|
679
707
|
this.keys = keys;
|
|
680
|
-
this.fetch
|
|
708
|
+
// Called as this.fetch(...), a fetch gets this client as its receiver, and
|
|
709
|
+
// the fetch of a browser or a Cloudflare Worker refuses any receiver but
|
|
710
|
+
// the global object with "Illegal invocation". Node does not check, which
|
|
711
|
+
// is how every call from a browser failed from 0.1.0 until 0.4.1 while
|
|
712
|
+
// every test here passed. The wrapper calls it with no receiver at all.
|
|
713
|
+
this.fetch = (input, init) => fetch(input, init);
|
|
681
714
|
this.presence = new Presence(this);
|
|
682
715
|
this.board = new Board(this, board);
|
|
683
716
|
this.boardInbox = null;
|