@pinkparrot/qsafe-sig 0.0.2 → 0.0.4
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/.vscode/launch.json +20 -0
- package/README.md +2 -1
- package/benchmark.mjs +45 -0
- package/binary-writer-reader.mjs +20 -0
- package/constants.mjs +56 -0
- package/dist/qsafe-sig.browser.min.js +3102 -0
- package/package.json +19 -6
- package/publish.bat +22 -0
- package/qsafeHelper.mjs +28 -0
- package/test-helpers.mjs +21 -0
- package/test.mjs +83 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"configurations": [
|
|
3
|
+
{
|
|
4
|
+
"type": "node",
|
|
5
|
+
"request": "launch",
|
|
6
|
+
"name": "test",
|
|
7
|
+
"skipFiles": ["<node_internals>/**"],
|
|
8
|
+
"program": "${workspaceFolder}/test.mjs",
|
|
9
|
+
"runtimeArgs": ["--inspect"]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "node",
|
|
13
|
+
"request": "launch",
|
|
14
|
+
"name": "benchmark",
|
|
15
|
+
"skipFiles": ["<node_internals>/**"],
|
|
16
|
+
"program": "${workspaceFolder}/benchmark.mjs",
|
|
17
|
+
"runtimeArgs": ["--inspect"]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ Built on top of [`@pinkparrot/qsafe-mayo-wasm`](https://github.com/Seigneur-Mach
|
|
|
10
10
|
|
|
11
11
|
## Links
|
|
12
12
|
|
|
13
|
-
- **NPM:** https://www.npmjs.com/package/qsafe-sig
|
|
13
|
+
- **NPM:** [qsafe-sig] (https://www.npmjs.com/package/@pinkparrot/qsafe-sig)
|
|
14
|
+
- **Browser bundle:** https://unpkg.com/@pinkparrot/qsafe-sig/dist/qsafe-sig.browser.min.js
|
|
14
15
|
- **MAYO-WASM:** https://github.com/Seigneur-Machiavel/qsafe-mayo-wasm
|
|
15
16
|
- **MAYO spec:** [pqmayo.org](https://pqmayo.org)
|
|
16
17
|
|
package/benchmark.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { QsafeSigner } from './index.mjs';
|
|
3
|
+
import { createRandomMessage } from './test-helpers.mjs';
|
|
4
|
+
|
|
5
|
+
const SEED = crypto.getRandomValues(new Uint8Array(32));
|
|
6
|
+
const COUNT = 1000;
|
|
7
|
+
const MSG_SIZE = 256;
|
|
8
|
+
|
|
9
|
+
/** @param {'mayo1'|'mayo2'} variant */
|
|
10
|
+
async function chainVerify(variant) {
|
|
11
|
+
console.log(`\n-- Chain verify x${COUNT}: ${variant} --`);
|
|
12
|
+
|
|
13
|
+
const signer = await QsafeSigner.create(variant);
|
|
14
|
+
const { publicKey } = signer.loadMasterKey(SEED);
|
|
15
|
+
|
|
16
|
+
// -- Sign phase --
|
|
17
|
+
const messages = [];
|
|
18
|
+
const sigs = [];
|
|
19
|
+
for (let i = 0; i < COUNT; i++) messages.push(createRandomMessage(MSG_SIZE));
|
|
20
|
+
|
|
21
|
+
const t0sign = performance.now();
|
|
22
|
+
for (const msg of messages) sigs.push(signer.sign(msg));
|
|
23
|
+
|
|
24
|
+
const signMs = performance.now() - t0sign;
|
|
25
|
+
console.log(`Sign : ${signMs.toFixed(2)} ms total | ~${(signMs / COUNT).toFixed(3)} ms/op`);
|
|
26
|
+
|
|
27
|
+
// -- Verify phase --
|
|
28
|
+
const verifier = await QsafeSigner.createFull();
|
|
29
|
+
let failures = 0;
|
|
30
|
+
|
|
31
|
+
const t0verify = performance.now();
|
|
32
|
+
for (let i = 0; i < COUNT; i++)
|
|
33
|
+
if (!await verifier.verify(messages[i], sigs[i], publicKey)) failures++;
|
|
34
|
+
|
|
35
|
+
const verifyMs = performance.now() - t0verify;
|
|
36
|
+
console.log(`Verify : ${verifyMs.toFixed(2)} ms total | ~${(verifyMs / COUNT).toFixed(3)} ms/op`);
|
|
37
|
+
|
|
38
|
+
if (failures > 0) console.error(`✗ ${failures} verification(s) failed!`);
|
|
39
|
+
else console.log(`✓ All ${COUNT} signatures verified successfully`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await chainVerify('mayo1');
|
|
43
|
+
await chainVerify('mayo2');
|
|
44
|
+
|
|
45
|
+
console.log('\n-- DONE --');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Minimal binary writer/reader for the custom signature format. Not a general-purpose library, just enough for our needs.
|
|
2
|
+
export class BinaryWriter {
|
|
3
|
+
cursor = 0;
|
|
4
|
+
view;
|
|
5
|
+
|
|
6
|
+
constructor(size) { this.view = new Uint8Array(size); }
|
|
7
|
+
writeByte(b) { this.view[this.cursor++] = b; }
|
|
8
|
+
writeU16BE(v) { this.view[this.cursor++] = (v >> 8) & 0xff; this.view[this.cursor++] = v & 0xff; }
|
|
9
|
+
writeBytes(buf) { this.view.set(buf, this.cursor); this.cursor += buf.length; }
|
|
10
|
+
getBytes() { return this.view; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class BinaryReader {
|
|
14
|
+
cursor = 0;
|
|
15
|
+
view;
|
|
16
|
+
constructor(buf) { this.view = new Uint8Array(buf); }
|
|
17
|
+
readByte() { return this.view[this.cursor++]; }
|
|
18
|
+
readU16BE() { return (this.view[this.cursor++] << 8) | this.view[this.cursor++]; }
|
|
19
|
+
read(n) { const s = this.cursor; this.cursor += n; return this.view.slice(s, this.cursor); }
|
|
20
|
+
}
|
package/constants.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { MayoSigner } from '@pinkparrot/qsafe-mayo-wasm';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{ sigSize: number, pubKeySize: number, seedSize: number }} VariantDesc
|
|
6
|
+
* @typedef {{ publicKey: Uint8Array, secretKey: Uint8Array }} Keypair */
|
|
7
|
+
|
|
8
|
+
/** Versioned protocol descriptors and loaders.
|
|
9
|
+
* - Each version entry describes the crypto params for that protocol version.
|
|
10
|
+
* - When MAYO bumps its params, add a new version entry pointing to the new package.
|
|
11
|
+
* - Never remove old entries — they are needed for backward-compatible verify().
|
|
12
|
+
* @type {Record<string, { variants: Record<string, VariantDesc>, loader: (variant: string) => Promise<MayoSigner> }>} */
|
|
13
|
+
const PROTOCOL_VERSIONS = {
|
|
14
|
+
'1': {
|
|
15
|
+
variants: {
|
|
16
|
+
/** @type {VariantDesc} */
|
|
17
|
+
'mayo1': { sigSize: 454, pubKeySize: 1420, seedSize: 24 },
|
|
18
|
+
/** @type {VariantDesc} */
|
|
19
|
+
'mayo2': { sigSize: 186, pubKeySize: 4912, seedSize: 24 },
|
|
20
|
+
},
|
|
21
|
+
// If a future version needs a different wasm package, add a loader here.
|
|
22
|
+
// For v1 both variants come from the same @pinkparrot/qsafe-mayo-wasm import.
|
|
23
|
+
loader: /** @param {string} variant */ async (variant) => await MayoSigner.create(variant),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const CURRENT_VERSION = '1'; // Current protocol version used when signing.
|
|
28
|
+
const DEFAULT_VARIANT = 'mayo1'; // Default variant used when signing.
|
|
29
|
+
const AVAILABLE_VERSIONS = Object.keys(PROTOCOL_VERSIONS);
|
|
30
|
+
|
|
31
|
+
const HKDF_INFO_ED25519 = new TextEncoder().encode('qsafe-ed25519');
|
|
32
|
+
const HKDF_INFO_MAYO = new TextEncoder().encode('qsafe-mayo');
|
|
33
|
+
|
|
34
|
+
const ED25519_PRIV_SIZE = 32;
|
|
35
|
+
const ED25519_PUB_SIZE = 32;
|
|
36
|
+
const ED25519_SIG_SIZE = 64;
|
|
37
|
+
|
|
38
|
+
const HEADER_SIZE = 3; // Header layout: version(u16 BE) + variant_id(u8) = 3 bytes
|
|
39
|
+
const VARIANT_ID = { 'mayo1': 0x01, 'mayo2': 0x02 }; // variant_id byte values — stable across protocol versions
|
|
40
|
+
/** @type {Record<number, 'mayo1' | 'mayo2'>} */
|
|
41
|
+
const VARIANT_BY_ID = { 0x01: 'mayo1', 0x02: 'mayo2' }; // Reverse lookup for parsing signature headers
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
PROTOCOL_VERSIONS,
|
|
45
|
+
CURRENT_VERSION,
|
|
46
|
+
DEFAULT_VARIANT,
|
|
47
|
+
AVAILABLE_VERSIONS,
|
|
48
|
+
HKDF_INFO_ED25519,
|
|
49
|
+
HKDF_INFO_MAYO,
|
|
50
|
+
ED25519_PRIV_SIZE,
|
|
51
|
+
ED25519_PUB_SIZE,
|
|
52
|
+
ED25519_SIG_SIZE,
|
|
53
|
+
HEADER_SIZE,
|
|
54
|
+
VARIANT_ID,
|
|
55
|
+
VARIANT_BY_ID
|
|
56
|
+
}
|