@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 +13 -0
- package/package.json +1 -1
- package/readme.md +32 -15
- package/dist/mayo_api.browser.js +0 -135
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
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
|
|
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
|
-
- **
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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)
|
package/dist/mayo_api.browser.js
DELETED
|
@@ -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
|
-
}
|