@theermite/morphic-wasm-core 2.0.0-alpha.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/LICENSE +661 -0
- package/README.md +76 -0
- package/package.json +24 -0
- package/pkg/README.md +76 -0
- package/pkg/morphic_wasm_core.d.ts +98 -0
- package/pkg/morphic_wasm_core.js +492 -0
- package/pkg/morphic_wasm_core_bg.wasm +0 -0
- package/pkg/morphic_wasm_core_bg.wasm.d.ts +15 -0
- package/pkg/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @theermite/morphic-wasm-core
|
|
2
|
+
|
|
3
|
+
Rust → WebAssembly critical paths for the Morphic Adaptation Engine.
|
|
4
|
+
|
|
5
|
+
**Status**: B-018 (live). NaCl box primitives shipped.
|
|
6
|
+
CDC ref: F-017 (Tri-layer Rust→WASM critical).
|
|
7
|
+
|
|
8
|
+
## What it provides
|
|
9
|
+
|
|
10
|
+
NaCl-compatible authenticated encryption (Curve25519 + XSalsa20 + Poly1305)
|
|
11
|
+
via the audited [`crypto_box`](https://crates.io/crates/crypto_box) crate
|
|
12
|
+
(RustCrypto, pure Rust). Output is byte-identical to `tweetnacl.box`, so
|
|
13
|
+
ciphertexts produced by either side are interchangeable.
|
|
14
|
+
|
|
15
|
+
Exported API (TypeScript via `wasm-bindgen`):
|
|
16
|
+
|
|
17
|
+
| Function | Returns | Purpose |
|
|
18
|
+
|----------|---------|---------|
|
|
19
|
+
| `wasmGenerateKeypair()` | `WasmKeyPair` (publicKey + secretKey) | Curve25519 key pair via OsRng (Web Crypto in browser) |
|
|
20
|
+
| `wasmGenerateNonce()` | `Uint8Array` (24 bytes) | Random XSalsa20 nonce |
|
|
21
|
+
| `wasmRandomBytes(len)` | `Uint8Array` | CSPRNG bytes |
|
|
22
|
+
| `wasmEncryptBox(plaintext, recipientPk, senderSk, nonce)` | `Uint8Array` | Authenticated ciphertext (plaintext + 16-byte Poly1305 tag) |
|
|
23
|
+
| `wasmDecryptBox(ciphertext, nonce, senderPk, recipientSk)` | `Uint8Array` | Plaintext, or throws `JsError` on auth failure |
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm --filter @theermite/morphic-wasm-core build # target web
|
|
29
|
+
pnpm --filter @theermite/morphic-wasm-core build:bundler # target bundler (Vite/webpack)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Output lands in `pkg/` (ESM + `.d.ts` + raw `.wasm`, ~58 KB). The bundle
|
|
33
|
+
is loaded **lazily** by `packages/engine/src/wasm-bridge.ts` so projects
|
|
34
|
+
that don't need WASM crypto pay 0 KB.
|
|
35
|
+
|
|
36
|
+
## Tests
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm --filter @theermite/morphic-wasm-core test # native cargo tests
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Runs 9 tests, including 4 property-based tests × 1024 cases (= 4096
|
|
43
|
+
encrypt/decrypt round-trips) covering:
|
|
44
|
+
|
|
45
|
+
- Round-trip identity (`decrypt(encrypt(m)) == m`)
|
|
46
|
+
- Bit-flip tamper detection (Poly1305 catches every alteration)
|
|
47
|
+
- Wrong-nonce rejection
|
|
48
|
+
- Wrong-key rejection (with a positive sanity check inside)
|
|
49
|
+
|
|
50
|
+
Plus 5 deterministic fixtures (key/nonce lengths, tag overhead, empty
|
|
51
|
+
plaintext, truncated ciphertext).
|
|
52
|
+
|
|
53
|
+
## Why Rust → WASM (vs staying on tweetnacl-js)
|
|
54
|
+
|
|
55
|
+
- **Maintenance**: `tweetnacl-js` is unmaintained since 2020. `crypto_box`
|
|
56
|
+
is part of the actively-maintained RustCrypto ecosystem.
|
|
57
|
+
- **Audit surface**: pure-Rust, no JS legacy. Smaller dependency tree.
|
|
58
|
+
- **Performance**: WASM crypto ~2-5× faster than JS for sustained workloads
|
|
59
|
+
(large message batches, many keypairs).
|
|
60
|
+
- **Determinism**: same binary across Node, Deno, browsers — no engine
|
|
61
|
+
variance on a critical path.
|
|
62
|
+
|
|
63
|
+
## Defensive assertions (PET §5)
|
|
64
|
+
|
|
65
|
+
| Function | Assertions |
|
|
66
|
+
|----------|-----------|
|
|
67
|
+
| `wasm_encrypt_box` / `wasm_decrypt_box` | key lengths = 32 bytes; nonce length = 24 bytes |
|
|
68
|
+
| `wasm_decrypt_box` | authentication failures surface as `Err` (no silent corruption) |
|
|
69
|
+
| `wasm_generate_keypair` | uses `OsRng` (browser Web Crypto via `getrandom` js feature) |
|
|
70
|
+
|
|
71
|
+
Length checks pulled into a single `validate_box_inputs` helper to keep
|
|
72
|
+
the audit trail concentrated.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
AGPL-3.0-or-later (matches the rest of the Morphic Engine).
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theermite/morphic-wasm-core",
|
|
3
|
+
"version": "2.0.0-alpha.0",
|
|
4
|
+
"description": "Shinkofa Morphic Adaptation — Rust→WASM critical paths (NaCl box, B-018)",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./pkg/morphic_wasm_core.js",
|
|
8
|
+
"types": "./pkg/morphic_wasm_core.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"pkg/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org/"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "wasm-pack build --target web --release && rm -f pkg/.gitignore",
|
|
19
|
+
"build:bundler": "wasm-pack build --target bundler --release && rm -f pkg/.gitignore",
|
|
20
|
+
"test": "cargo test --release",
|
|
21
|
+
"test:proptest": "cargo test --release --test proptest_roundtrip",
|
|
22
|
+
"clean": "rm -rf pkg target"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/pkg/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @morphic/wasm-core
|
|
2
|
+
|
|
3
|
+
Rust → WebAssembly critical paths for the Morphic Adaptation Engine.
|
|
4
|
+
|
|
5
|
+
**Status**: B-018 (live). NaCl box primitives shipped.
|
|
6
|
+
CDC ref: F-017 (Tri-layer Rust→WASM critical).
|
|
7
|
+
|
|
8
|
+
## What it provides
|
|
9
|
+
|
|
10
|
+
NaCl-compatible authenticated encryption (Curve25519 + XSalsa20 + Poly1305)
|
|
11
|
+
via the audited [`crypto_box`](https://crates.io/crates/crypto_box) crate
|
|
12
|
+
(RustCrypto, pure Rust). Output is byte-identical to `tweetnacl.box`, so
|
|
13
|
+
ciphertexts produced by either side are interchangeable.
|
|
14
|
+
|
|
15
|
+
Exported API (TypeScript via `wasm-bindgen`):
|
|
16
|
+
|
|
17
|
+
| Function | Returns | Purpose |
|
|
18
|
+
|----------|---------|---------|
|
|
19
|
+
| `wasmGenerateKeypair()` | `WasmKeyPair` (publicKey + secretKey) | Curve25519 key pair via OsRng (Web Crypto in browser) |
|
|
20
|
+
| `wasmGenerateNonce()` | `Uint8Array` (24 bytes) | Random XSalsa20 nonce |
|
|
21
|
+
| `wasmRandomBytes(len)` | `Uint8Array` | CSPRNG bytes |
|
|
22
|
+
| `wasmEncryptBox(plaintext, recipientPk, senderSk, nonce)` | `Uint8Array` | Authenticated ciphertext (plaintext + 16-byte Poly1305 tag) |
|
|
23
|
+
| `wasmDecryptBox(ciphertext, nonce, senderPk, recipientSk)` | `Uint8Array` | Plaintext, or throws `JsError` on auth failure |
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm --filter @morphic/wasm-core build # target web
|
|
29
|
+
pnpm --filter @morphic/wasm-core build:bundler # target bundler (Vite/webpack)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Output lands in `pkg/` (ESM + `.d.ts` + raw `.wasm`, ~58 KB). The bundle
|
|
33
|
+
is loaded **lazily** by `packages/engine/src/wasm-bridge.ts` so projects
|
|
34
|
+
that don't need WASM crypto pay 0 KB.
|
|
35
|
+
|
|
36
|
+
## Tests
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm --filter @morphic/wasm-core test # native cargo tests
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Runs 9 tests, including 4 property-based tests × 1024 cases (= 4096
|
|
43
|
+
encrypt/decrypt round-trips) covering:
|
|
44
|
+
|
|
45
|
+
- Round-trip identity (`decrypt(encrypt(m)) == m`)
|
|
46
|
+
- Bit-flip tamper detection (Poly1305 catches every alteration)
|
|
47
|
+
- Wrong-nonce rejection
|
|
48
|
+
- Wrong-key rejection (with a positive sanity check inside)
|
|
49
|
+
|
|
50
|
+
Plus 5 deterministic fixtures (key/nonce lengths, tag overhead, empty
|
|
51
|
+
plaintext, truncated ciphertext).
|
|
52
|
+
|
|
53
|
+
## Why Rust → WASM (vs staying on tweetnacl-js)
|
|
54
|
+
|
|
55
|
+
- **Maintenance**: `tweetnacl-js` is unmaintained since 2020. `crypto_box`
|
|
56
|
+
is part of the actively-maintained RustCrypto ecosystem.
|
|
57
|
+
- **Audit surface**: pure-Rust, no JS legacy. Smaller dependency tree.
|
|
58
|
+
- **Performance**: WASM crypto ~2-5× faster than JS for sustained workloads
|
|
59
|
+
(large message batches, many keypairs).
|
|
60
|
+
- **Determinism**: same binary across Node, Deno, browsers — no engine
|
|
61
|
+
variance on a critical path.
|
|
62
|
+
|
|
63
|
+
## Defensive assertions (PET §5)
|
|
64
|
+
|
|
65
|
+
| Function | Assertions |
|
|
66
|
+
|----------|-----------|
|
|
67
|
+
| `wasm_encrypt_box` / `wasm_decrypt_box` | key lengths = 32 bytes; nonce length = 24 bytes |
|
|
68
|
+
| `wasm_decrypt_box` | authentication failures surface as `Err` (no silent corruption) |
|
|
69
|
+
| `wasm_generate_keypair` | uses `OsRng` (browser Web Crypto via `getrandom` js feature) |
|
|
70
|
+
|
|
71
|
+
Length checks pulled into a single `validate_box_inputs` helper to keep
|
|
72
|
+
the audit trail concentrated.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
AGPL-3.0-or-later (matches the rest of the Morphic Engine).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Result type for a generated key pair, exposed to JS as a struct with
|
|
6
|
+
* `publicKey` and `secretKey` getters returning Uint8Array.
|
|
7
|
+
*/
|
|
8
|
+
export class WasmKeyPair {
|
|
9
|
+
private constructor();
|
|
10
|
+
free(): void;
|
|
11
|
+
[Symbol.dispose](): void;
|
|
12
|
+
readonly publicKey: Uint8Array;
|
|
13
|
+
readonly secretKey: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Decrypt `ciphertext` using NaCl box.open.
|
|
18
|
+
*
|
|
19
|
+
* Inputs:
|
|
20
|
+
* - ciphertext: bytes produced by `wasm_encrypt_box` (or any NaCl-compatible
|
|
21
|
+
* box implementation, including tweetnacl-js)
|
|
22
|
+
* - nonce: 24 bytes used at encryption time
|
|
23
|
+
* - sender_pk: 32 bytes (peer's public key)
|
|
24
|
+
* - recipient_sk: 32 bytes (our secret key)
|
|
25
|
+
*
|
|
26
|
+
* Returns the plaintext, or Err on any authentication failure (wrong key,
|
|
27
|
+
* tampered ciphertext, wrong nonce, truncated input).
|
|
28
|
+
*/
|
|
29
|
+
export function wasmDecryptBox(ciphertext: Uint8Array, nonce: Uint8Array, sender_pk: Uint8Array, recipient_sk: Uint8Array): Uint8Array;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Encrypt `plaintext` using NaCl box (Curve25519 + XSalsa20Poly1305).
|
|
33
|
+
*
|
|
34
|
+
* Inputs:
|
|
35
|
+
* - plaintext: arbitrary bytes
|
|
36
|
+
* - recipient_pk: 32 bytes (Curve25519 public key)
|
|
37
|
+
* - sender_sk: 32 bytes (Curve25519 secret key)
|
|
38
|
+
* - nonce: 24 bytes (must be unique per (sender, recipient) pair)
|
|
39
|
+
*
|
|
40
|
+
* Output: ciphertext bytes (includes 16-byte Poly1305 tag).
|
|
41
|
+
*/
|
|
42
|
+
export function wasmEncryptBox(plaintext: Uint8Array, recipient_pk: Uint8Array, sender_sk: Uint8Array, nonce: Uint8Array): Uint8Array;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate a Curve25519 key pair using the system CSPRNG
|
|
46
|
+
* (Web Crypto API in browser via `getrandom` js feature).
|
|
47
|
+
*/
|
|
48
|
+
export function wasmGenerateKeypair(): WasmKeyPair;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sample a fresh nonce using the construction's recommended generator.
|
|
52
|
+
*/
|
|
53
|
+
export function wasmGenerateNonce(): Uint8Array;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate `len` random bytes using the system CSPRNG.
|
|
57
|
+
*/
|
|
58
|
+
export function wasmRandomBytes(len: number): Uint8Array;
|
|
59
|
+
|
|
60
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
61
|
+
|
|
62
|
+
export interface InitOutput {
|
|
63
|
+
readonly memory: WebAssembly.Memory;
|
|
64
|
+
readonly __wbg_wasmkeypair_free: (a: number, b: number) => void;
|
|
65
|
+
readonly wasmDecryptBox: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
|
|
66
|
+
readonly wasmEncryptBox: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
|
|
67
|
+
readonly wasmGenerateKeypair: () => number;
|
|
68
|
+
readonly wasmGenerateNonce: (a: number) => void;
|
|
69
|
+
readonly wasmRandomBytes: (a: number, b: number) => void;
|
|
70
|
+
readonly wasmkeypair_publicKey: (a: number, b: number) => void;
|
|
71
|
+
readonly wasmkeypair_secretKey: (a: number, b: number) => void;
|
|
72
|
+
readonly __wbindgen_export: (a: number) => void;
|
|
73
|
+
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
74
|
+
readonly __wbindgen_export2: (a: number, b: number) => number;
|
|
75
|
+
readonly __wbindgen_export3: (a: number, b: number, c: number) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
82
|
+
* a precompiled `WebAssembly.Module`.
|
|
83
|
+
*
|
|
84
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
85
|
+
*
|
|
86
|
+
* @returns {InitOutput}
|
|
87
|
+
*/
|
|
88
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
92
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
93
|
+
*
|
|
94
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
95
|
+
*
|
|
96
|
+
* @returns {Promise<InitOutput>}
|
|
97
|
+
*/
|
|
98
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|