@pinkparrot/qsafe-mayo-wasm 0.0.13 → 0.0.15

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/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023-2025 the MAYO team. All rights reserved.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinkparrot/qsafe-mayo-wasm",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "author": "Pink Parrot",
5
5
  "description": "Post-quantum signatures in WebAssembly — MAYO-1 & MAYO-2, browser & Node.js ready",
6
6
  "type": "module",
package/readme.md CHANGED
@@ -2,14 +2,22 @@
2
2
 
3
3
  > Post-quantum signatures in WebAssembly — MAYO-1 & MAYO-2, browser & Node.js ready
4
4
 
5
- JavaScript/WASM wrapper around [MAYO-C](https://github.com/PQCMayo/MAYO-C), a NIST post-quantum signature scheme finalist. Ships as two self-contained single-file modules with an unified JS API.
5
+ JavaScript/WASM wrapper around [MAYO-C](https://github.com/PQCMayo/MAYO-C), a NIST post-quantum signature scheme. Ships as self-contained single-file modules with a unified JS API.
6
6
 
7
7
  Special thanks to Ward Beullens and his team — post-quantum crypto is hard enough without having to invent it yourself.
8
8
 
9
+ ## ⚠️ Security notice
10
+
11
+ **Do not use MAYO alone in production.**
12
+
13
+ MAYO-C is a relatively recent scheme and has not yet accumulated the same cryptanalytic scrutiny as classical algorithms. Until post-quantum cryptography matures further, always combine MAYO with a classical signature (e.g. Ed25519) in a hybrid double-signing scheme. This library is designed to be used as one half of such a pair.
14
+
15
+ This library is provided as-is, without warranty. It wraps MAYO-C via Emscripten and has not undergone independent security audit. Use at your own risk.
16
+
9
17
  ## Links
10
18
 
11
19
  - **NPM:** [npmjs.com/package/@pinkparrot/qsafe-mayo-wasm](https://www.npmjs.com/package/@pinkparrot/qsafe-mayo-wasm)
12
- - **BROWSER** [unpkg.com/@pinkparrot/qsafe-mayo-wasm/dist/mayo.browser.min.js](https://unpkg.com/@pinkparrot/qsafe-mayo-wasm/dist/mayo.browser.min.js)
20
+ - **Browser bundle:** [unpkg.com/@pinkparrot/qsafe-mayo-wasm/dist/mayo.browser.min.js](https://unpkg.com/@pinkparrot/qsafe-mayo-wasm/dist/mayo.browser.min.js)
13
21
  - **MAYO spec:** [pqmayo.org](https://pqmayo.org)
14
22
  - **MAYO-C source:** [github.com/PQCMayo/MAYO-C](https://github.com/PQCMayo/MAYO-C)
15
23
 
@@ -25,7 +33,7 @@ import { MayoSigner } from 'qsafe-mayo-wasm';
25
33
  const mayo = await MayoSigner.create('mayo1'); // or 'mayo2'
26
34
 
27
35
  // Generate a deterministic keypair from a 24-byte seed
28
- // Param2 "storeSecretKey" (default true) -> set to false when you only need to generate keys without doing operations
36
+ // storeSecretKey (default true) set to false if you only need the keypair bytes
29
37
  const { publicKey, secretKey } = mayo.keypairFromSeed(seed);
30
38
 
31
39
  // Sign
@@ -34,7 +42,7 @@ const signature = mayo.sign(msg);
34
42
  // Verify (stateless — no secret key needed)
35
43
  const valid = mayo.verify(msg, signature, publicKey);
36
44
 
37
- // *Optional* Use loadSecretKey when you already have a key from a previous session
45
+ // Load an existing secret key from a previous session
38
46
  mayo.loadSecretKey(secretKey);
39
47
  ```
40
48
 
@@ -44,43 +52,51 @@ mayo.loadSecretKey(secretKey);
44
52
  Factory method. Loads the WASM module and returns a ready instance.
45
53
  - `variant`: `'mayo1'` (default) or `'mayo2'`
46
54
 
47
- ### `new MayoSigner(variant?)` + `await signer.init()`
48
- Alternative if you need manual lifecycle control. Use `signer.ready` to check initialization state.
55
+ ### `new MayoSigner(variant?)` + `await signer.init(maxMsgSize?)`
56
+ Alternative if you need manual lifecycle control.
57
+ - `maxMsgSize`: maximum message size in bytes (default: `204800` — 200 KB). Messages exceeding this will throw at sign/verify time.
58
+ - Use `signer.ready` to check initialization state.
49
59
 
50
60
  ### `keypairFromSeed(seed, storeSecretKey?)` → `Keypair | null`
51
61
  Derives a keypair deterministically from a 24-byte `Uint8Array` seed.
52
- Stores the secret key internally by default (`storeSecretKey = true`).
62
+ - `storeSecretKey` (default `true`): stores the secret key internally for subsequent `sign()` calls.
63
+ - Returns `null` if key generation failed.
53
64
 
54
65
  ### `loadSecretKey(secretKey)`
55
- Loads an existing secret key for subsequent `sign()` calls.
66
+ Loads an existing 24-byte secret key for subsequent `sign()` calls.
56
67
 
57
68
  ### `sign(msg)` → `Uint8Array | null`
58
- Signs a message using the loaded secret key. Returns `null` on failure.
69
+ Signs a message using the loaded secret key.
70
+ - Returns `null` on failure.
71
+ - **Throws** if no secret key is loaded, or if the message exceeds `maxMsgSize`.
59
72
 
60
73
  ### `verify(msg, signature, publicKey)` → `boolean`
61
74
  Verifies a signature. Stateless — no secret key required.
75
+ - **Throws** if the message exceeds `maxMsgSize`.
62
76
 
63
- ## Key sizes
77
+ ## Key & signature sizes
64
78
 
65
79
  | Variant | Secret key | Public key | Signature |
66
80
  |---------|-----------|------------|-----------|
67
81
  | MAYO-1 | 24 B | 1420 B | 454 B |
68
82
  | MAYO-2 | 24 B | 4912 B | 186 B |
69
83
 
84
+ MAYO-2 trades a larger public key for a smaller signature — useful when bandwidth matters more than storage.
85
+
70
86
  ## Building from source
71
87
 
72
88
  Prerequisites:
73
89
  - **Node.js** ≥ 22
74
- - **Emscripten** (emcc) — see [emscripten.org](https://emscripten.org/docs/getting_started/downloads.html)
90
+ - **Emscripten** (`emcc`) — see [emscripten.org](https://emscripten.org/docs/getting_started/downloads.html)
75
91
  - **cmake** (tested with 4.x)
76
92
 
77
93
  For exact Emscripten/cmake version requirements, see the [MAYO-C build notes](https://github.com/PQCMayo/MAYO-C).
78
-
79
94
  ```powershell
80
95
  git clone --recurse-submodules https://github.com/Seigneur-Machiavel/qsafe-mayo-wasm
81
96
  npm i --include=dev
82
- .\build_mayo1.ps1 # → dist/mayo1.cjs
83
- .\build_mayo2.ps1 # → dist/mayo2.cjs
97
+ .\build_mayo1.ps1 # → dist/mayo1.cjs + dist/mayo1.js
98
+ .\build_mayo2.ps1 # → dist/mayo2.cjs + dist/mayo2.js
99
+ npm run build # → dist/mayo.browser.min.js (browser bundle)
84
100
  ```
85
101
 
86
102
  WASM is inlined in each `.js` file (`SINGLE_FILE=1`) — no separate `.wasm` asset needed.
@@ -88,4 +104,5 @@ WASM is inlined in each `.js` file (`SINGLE_FILE=1`) — no separate `.wasm` ass
88
104
  ## License
89
105
 
90
106
  Apache-2.0 — see [LICENSE](./LICENSE).
91
- MAYO-C is also Apache-2.0 — see [mayo-c/LICENSE](./mayo-c/LICENSE).
107
+ MAYO-C is also Apache-2.0 — see [mayo-c/LICENSE](./mayo-c/LICENSE).
108
+ MAYO-C NOTICE — see [NOTICE-MAYO-C](./NOTICE-MAYO-C)
@@ -1,135 +0,0 @@
1
- import Mayo1Module from './mayo1.cjs';
2
- import Mayo2Module from './mayo2.cjs';
3
-
4
- /** @typedef {{ secretKey: Uint8Array, publicKey: Uint8Array }} Keypair */
5
-
6
- const SIZES = {
7
- mayo1: { secretKeySize: 24, publicKeySize: 1420, signatureSize: 454 },
8
- mayo2: { secretKeySize: 24, publicKeySize: 4912, signatureSize: 186 },
9
- }; // Note: if we want to use MAYO5 we needs at least 40 bytes of seed.
10
-
11
- function alloc(m, size) {
12
- const ptr = m._malloc(size);
13
- if (ptr === 0) throw new Error(`malloc failed (${size} bytes)`);
14
- return ptr;
15
- }
16
-
17
- export class MayoSigner {
18
- #m = null;
19
- #secretKey = null;
20
- #secretKeySize; #publicKeySize; #signatureSize;
21
-
22
- /** @param {string} [variant] Default: 'mayo1' */
23
- constructor(variant = 'mayo1') {
24
- if (variant !== 'mayo1' && variant !== 'mayo2') throw new Error(`Unsupported MAYO variant: ${variant}`);
25
- this.variant = variant;
26
- ({ secretKeySize: this.#secretKeySize,
27
- publicKeySize: this.#publicKeySize,
28
- signatureSize: this.#signatureSize } = SIZES[variant]);
29
- }
30
-
31
- /** Factory — preferred way to instantiate.
32
- * @param {string} [variant] Default: 'mayo1' */
33
- static async create(variant = 'mayo1') {
34
- const instance = new MayoSigner(variant);
35
- await instance.init();
36
- return instance;
37
- }
38
-
39
- /** Loads the WASM module. Must be called before any crypto operation if not using create(). */
40
- async init() {
41
- if (this.#m) return;
42
- if (this.variant === 'mayo1') this.#m = await Mayo1Module();
43
- if (this.variant === 'mayo2') this.#m = await Mayo2Module();
44
- throw new Error(`Unsupported MAYO variant: ${this.variant}`);
45
- }
46
-
47
- /** Indicates whether the WASM module is loaded and ready for crypto operations. */
48
- get ready() { return this.#m !== null; }
49
-
50
- #assertReady() {
51
- if (!this.#m) throw new Error('MayoSigner not initialized — call await init() first');
52
- }
53
-
54
- /** Derives a keypair deterministically from a seed, and loads the secret key.
55
- * @param {Uint8Array} seed - Must be exactly 24 bytes
56
- * @param {boolean} storeSecretKey - Whether to store the secret key internally for subsequent sign() calls. Set to false if you only need to generate keypair.
57
- * @returns {Keypair|null} null if key generation failed */
58
- keypairFromSeed(seed, storeSecretKey = true) {
59
- this.#assertReady();
60
- if (!(seed instanceof Uint8Array) || seed.length !== this.#secretKeySize)
61
- throw new TypeError(`seed must be a Uint8Array of ${this.#secretKeySize} bytes`);
62
-
63
- const m = this.#m;
64
- const seedPtr = alloc(m, seed.length);
65
- const publicKeyPtr = alloc(m, this.#publicKeySize);
66
- const secretKeyPtr = alloc(m, this.#secretKeySize);
67
-
68
- m.HEAPU8.set(seed, seedPtr);
69
- const ret = m._keypair_from_seed(seedPtr, publicKeyPtr, secretKeyPtr);
70
- const keypair = ret === 0 ? {
71
- publicKey: m.HEAPU8.slice(publicKeyPtr, publicKeyPtr + this.#publicKeySize),
72
- secretKey: m.HEAPU8.slice(secretKeyPtr, secretKeyPtr + this.#secretKeySize),
73
- } : null;
74
-
75
- m._free(seedPtr); m._free(publicKeyPtr); m._free(secretKeyPtr);
76
-
77
- if (keypair && storeSecretKey) this.#secretKey = keypair.secretKey;
78
- return keypair;
79
- }
80
-
81
- /** Loads a secret key for subsequent sign() calls.
82
- * @param {Uint8Array} secretKey - Must be exactly 24 bytes */
83
- loadSecretKey(secretKey) {
84
- if (!(secretKey instanceof Uint8Array) || secretKey.length !== this.#secretKeySize)
85
- throw new TypeError(`secretKey must be a Uint8Array of ${this.#secretKeySize} bytes`);
86
- this.#secretKey = secretKey;
87
- }
88
-
89
- /** Signs a message using the loaded secret key.
90
- * @param {Uint8Array} msg @returns {Uint8Array|null} signature, or null if signing failed */
91
- sign(msg) {
92
- this.#assertReady();
93
- if (!this.#secretKey) throw new Error('No secret key loaded — call keypairFromSeed() or loadSecretKey() first');
94
- if (!(msg instanceof Uint8Array)) throw new TypeError('msg must be a Uint8Array');
95
-
96
- const m = this.#m;
97
- const msgPtr = alloc(m, msg.length);
98
- const secretKeyPtr = alloc(m, this.#secretKeySize);
99
- const signaturePtr = alloc(m, this.#signatureSize);
100
- const signatureLenPtr = alloc(m, 4); // size_t in WASM32
101
-
102
- m.HEAPU8.set(msg, msgPtr);
103
- m.HEAPU8.set(this.#secretKey, secretKeyPtr);
104
-
105
- const ret = m._sign(msgPtr, msg.length, secretKeyPtr, signaturePtr, signatureLenPtr);
106
- const result = ret === 0 ? m.HEAPU8.slice(signaturePtr, signaturePtr + this.#signatureSize) : null;
107
-
108
- m._free(msgPtr); m._free(secretKeyPtr); m._free(signaturePtr); m._free(signatureLenPtr);
109
- return result;
110
- }
111
-
112
- /** Verifies a signature against a message and public key.
113
- * @param {Uint8Array} msg @param {Uint8Array} signature @param {Uint8Array} publicKey */
114
- verify(msg, signature, publicKey) {
115
- this.#assertReady();
116
- if (!(msg instanceof Uint8Array)) throw new TypeError('msg must be a Uint8Array');
117
- if (!(signature instanceof Uint8Array) || signature.length !== this.#signatureSize)
118
- throw new TypeError(`signature must be a Uint8Array of ${this.#signatureSize} bytes`);
119
- if (!(publicKey instanceof Uint8Array) || publicKey.length !== this.#publicKeySize)
120
- throw new TypeError(`publicKey must be a Uint8Array of ${this.#publicKeySize} bytes`);
121
-
122
- const m = this.#m;
123
- const msgPtr = alloc(m, msg.length);
124
- const signaturePtr = alloc(m, this.#signatureSize);
125
- const publicKeyPtr = alloc(m, this.#publicKeySize);
126
-
127
- m.HEAPU8.set(msg, msgPtr);
128
- m.HEAPU8.set(signature, signaturePtr);
129
- m.HEAPU8.set(publicKey, publicKeyPtr);
130
-
131
- const ret = m._verify(msgPtr, msg.length, signaturePtr, publicKeyPtr);
132
- m._free(msgPtr); m._free(signaturePtr); m._free(publicKeyPtr);
133
- return ret === 0;
134
- }
135
- }