@verbeth/sdk 0.1.4 → 0.1.6
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 +20 -168
- package/dist/esm/src/addresses.d.ts +20 -0
- package/dist/esm/src/addresses.d.ts.map +1 -0
- package/dist/esm/src/addresses.js +33 -0
- package/dist/esm/src/client/HsrTagIndex.d.ts +77 -0
- package/dist/esm/src/client/HsrTagIndex.d.ts.map +1 -0
- package/dist/esm/src/client/HsrTagIndex.js +157 -0
- package/dist/esm/src/client/PendingManager.d.ts +65 -0
- package/dist/esm/src/client/PendingManager.d.ts.map +1 -0
- package/dist/esm/src/client/PendingManager.js +84 -0
- package/dist/esm/src/client/SessionManager.d.ts +65 -0
- package/dist/esm/src/client/SessionManager.d.ts.map +1 -0
- package/dist/esm/src/client/SessionManager.js +146 -0
- package/dist/esm/src/client/VerbethClient.d.ts +153 -99
- package/dist/esm/src/client/VerbethClient.d.ts.map +1 -1
- package/dist/esm/src/client/VerbethClient.js +429 -123
- package/dist/esm/src/client/VerbethClientBuilder.d.ts +105 -0
- package/dist/esm/src/client/VerbethClientBuilder.d.ts.map +1 -0
- package/dist/esm/src/client/VerbethClientBuilder.js +146 -0
- package/dist/esm/src/client/hsrMatcher.d.ts +22 -0
- package/dist/esm/src/client/hsrMatcher.d.ts.map +1 -0
- package/dist/esm/src/client/hsrMatcher.js +31 -0
- package/dist/esm/src/client/index.d.ts +6 -1
- package/dist/esm/src/client/index.d.ts.map +1 -1
- package/dist/esm/src/client/index.js +2 -0
- package/dist/esm/src/client/types.d.ts +151 -10
- package/dist/esm/src/client/types.d.ts.map +1 -1
- package/dist/esm/src/crypto(old).d.ts +46 -0
- package/dist/esm/src/crypto(old).d.ts.map +1 -0
- package/dist/esm/src/crypto(old).js +137 -0
- package/dist/esm/src/crypto.d.ts +7 -29
- package/dist/esm/src/crypto.d.ts.map +1 -1
- package/dist/esm/src/crypto.js +36 -72
- package/dist/esm/src/executor.d.ts +17 -18
- package/dist/esm/src/executor.d.ts.map +1 -1
- package/dist/esm/src/executor.js +54 -70
- package/dist/esm/src/handshake.d.ts +51 -0
- package/dist/esm/src/handshake.d.ts.map +1 -0
- package/dist/esm/src/handshake.js +105 -0
- package/dist/esm/src/identity.d.ts +24 -18
- package/dist/esm/src/identity.d.ts.map +1 -1
- package/dist/esm/src/identity.js +126 -31
- package/dist/esm/src/index.d.ts +11 -7
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +10 -7
- package/dist/esm/src/payload.d.ts +3 -30
- package/dist/esm/src/payload.d.ts.map +1 -1
- package/dist/esm/src/payload.js +3 -77
- package/dist/esm/src/pq/kem.d.ts +33 -0
- package/dist/esm/src/pq/kem.d.ts.map +1 -0
- package/dist/esm/src/pq/kem.js +40 -0
- package/dist/esm/src/ratchet/auth.d.ts +34 -0
- package/dist/esm/src/ratchet/auth.d.ts.map +1 -0
- package/dist/esm/src/ratchet/auth.js +88 -0
- package/dist/esm/src/ratchet/codec.d.ts +52 -0
- package/dist/esm/src/ratchet/codec.d.ts.map +1 -0
- package/dist/esm/src/ratchet/codec.js +127 -0
- package/dist/esm/src/ratchet/decrypt.d.ts +28 -0
- package/dist/esm/src/ratchet/decrypt.d.ts.map +1 -0
- package/dist/esm/src/ratchet/decrypt.js +255 -0
- package/dist/esm/src/ratchet/encrypt.d.ts +17 -0
- package/dist/esm/src/ratchet/encrypt.d.ts.map +1 -0
- package/dist/esm/src/ratchet/encrypt.js +78 -0
- package/dist/esm/src/ratchet/index.d.ts +8 -0
- package/dist/esm/src/ratchet/index.d.ts.map +1 -0
- package/dist/esm/src/ratchet/index.js +8 -0
- package/dist/esm/src/ratchet/kdf.d.ts +60 -0
- package/dist/esm/src/ratchet/kdf.d.ts.map +1 -0
- package/dist/esm/src/ratchet/kdf.js +91 -0
- package/dist/esm/src/ratchet/session.d.ts +43 -0
- package/dist/esm/src/ratchet/session.d.ts.map +1 -0
- package/dist/esm/src/ratchet/session.js +139 -0
- package/dist/esm/src/ratchet/types.d.ts +168 -0
- package/dist/esm/src/ratchet/types.d.ts.map +1 -0
- package/dist/esm/src/ratchet/types.js +27 -0
- package/dist/esm/src/safeSessionSigner.d.ts +35 -0
- package/dist/esm/src/safeSessionSigner.d.ts.map +1 -0
- package/dist/esm/src/safeSessionSigner.js +59 -0
- package/dist/esm/src/send.d.ts +32 -24
- package/dist/esm/src/send.d.ts.map +1 -1
- package/dist/esm/src/send.js +84 -39
- package/dist/esm/src/types.d.ts +8 -13
- package/dist/esm/src/types.d.ts.map +1 -1
- package/dist/esm/src/utils/safeSessionSigner.d.ts +23 -0
- package/dist/esm/src/utils/safeSessionSigner.d.ts.map +1 -0
- package/dist/esm/src/utils/safeSessionSigner.js +59 -0
- package/dist/esm/src/utils/txQueue.d.ts +12 -0
- package/dist/esm/src/utils/txQueue.d.ts.map +1 -0
- package/dist/esm/src/utils/txQueue.js +25 -0
- package/dist/esm/src/utils.d.ts +2 -3
- package/dist/esm/src/utils.d.ts.map +1 -1
- package/dist/esm/src/utils.js +5 -5
- package/dist/esm/src/verify.d.ts +9 -25
- package/dist/esm/src/verify.d.ts.map +1 -1
- package/dist/esm/src/verify.js +49 -50
- package/dist/src/addresses.d.ts +20 -0
- package/dist/src/addresses.d.ts.map +1 -0
- package/dist/src/addresses.js +33 -0
- package/dist/src/client/HsrTagIndex.d.ts +77 -0
- package/dist/src/client/HsrTagIndex.d.ts.map +1 -0
- package/dist/src/client/HsrTagIndex.js +157 -0
- package/dist/src/client/PendingManager.d.ts +65 -0
- package/dist/src/client/PendingManager.d.ts.map +1 -0
- package/dist/src/client/PendingManager.js +84 -0
- package/dist/src/client/SessionManager.d.ts +65 -0
- package/dist/src/client/SessionManager.d.ts.map +1 -0
- package/dist/src/client/SessionManager.js +146 -0
- package/dist/src/client/VerbethClient.d.ts +153 -99
- package/dist/src/client/VerbethClient.d.ts.map +1 -1
- package/dist/src/client/VerbethClient.js +429 -123
- package/dist/src/client/VerbethClientBuilder.d.ts +105 -0
- package/dist/src/client/VerbethClientBuilder.d.ts.map +1 -0
- package/dist/src/client/VerbethClientBuilder.js +146 -0
- package/dist/src/client/hsrMatcher.d.ts +22 -0
- package/dist/src/client/hsrMatcher.d.ts.map +1 -0
- package/dist/src/client/hsrMatcher.js +31 -0
- package/dist/src/client/index.d.ts +6 -1
- package/dist/src/client/index.d.ts.map +1 -1
- package/dist/src/client/index.js +2 -0
- package/dist/src/client/types.d.ts +151 -10
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/crypto(old).d.ts +46 -0
- package/dist/src/crypto(old).d.ts.map +1 -0
- package/dist/src/crypto(old).js +137 -0
- package/dist/src/crypto.d.ts +7 -29
- package/dist/src/crypto.d.ts.map +1 -1
- package/dist/src/crypto.js +36 -72
- package/dist/src/executor.d.ts +17 -18
- package/dist/src/executor.d.ts.map +1 -1
- package/dist/src/executor.js +54 -70
- package/dist/src/handshake.d.ts +51 -0
- package/dist/src/handshake.d.ts.map +1 -0
- package/dist/src/handshake.js +105 -0
- package/dist/src/identity.d.ts +24 -18
- package/dist/src/identity.d.ts.map +1 -1
- package/dist/src/identity.js +126 -31
- package/dist/src/index.d.ts +11 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -7
- package/dist/src/payload.d.ts +3 -30
- package/dist/src/payload.d.ts.map +1 -1
- package/dist/src/payload.js +3 -77
- package/dist/src/pq/kem.d.ts +33 -0
- package/dist/src/pq/kem.d.ts.map +1 -0
- package/dist/src/pq/kem.js +40 -0
- package/dist/src/ratchet/auth.d.ts +34 -0
- package/dist/src/ratchet/auth.d.ts.map +1 -0
- package/dist/src/ratchet/auth.js +88 -0
- package/dist/src/ratchet/codec.d.ts +52 -0
- package/dist/src/ratchet/codec.d.ts.map +1 -0
- package/dist/src/ratchet/codec.js +127 -0
- package/dist/src/ratchet/decrypt.d.ts +28 -0
- package/dist/src/ratchet/decrypt.d.ts.map +1 -0
- package/dist/src/ratchet/decrypt.js +255 -0
- package/dist/src/ratchet/encrypt.d.ts +17 -0
- package/dist/src/ratchet/encrypt.d.ts.map +1 -0
- package/dist/src/ratchet/encrypt.js +78 -0
- package/dist/src/ratchet/index.d.ts +8 -0
- package/dist/src/ratchet/index.d.ts.map +1 -0
- package/dist/src/ratchet/index.js +8 -0
- package/dist/src/ratchet/kdf.d.ts +60 -0
- package/dist/src/ratchet/kdf.d.ts.map +1 -0
- package/dist/src/ratchet/kdf.js +91 -0
- package/dist/src/ratchet/session.d.ts +43 -0
- package/dist/src/ratchet/session.d.ts.map +1 -0
- package/dist/src/ratchet/session.js +139 -0
- package/dist/src/ratchet/types.d.ts +168 -0
- package/dist/src/ratchet/types.d.ts.map +1 -0
- package/dist/src/ratchet/types.js +27 -0
- package/dist/src/safeSessionSigner.d.ts +35 -0
- package/dist/src/safeSessionSigner.d.ts.map +1 -0
- package/dist/src/safeSessionSigner.js +59 -0
- package/dist/src/send.d.ts +32 -24
- package/dist/src/send.d.ts.map +1 -1
- package/dist/src/send.js +84 -39
- package/dist/src/types.d.ts +8 -13
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/safeSessionSigner.d.ts +23 -0
- package/dist/src/utils/safeSessionSigner.d.ts.map +1 -0
- package/dist/src/utils/safeSessionSigner.js +59 -0
- package/dist/src/utils/txQueue.d.ts +12 -0
- package/dist/src/utils/txQueue.d.ts.map +1 -0
- package/dist/src/utils/txQueue.js +25 -0
- package/dist/src/utils.d.ts +2 -3
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +5 -5
- package/dist/src/verify.d.ts +9 -25
- package/dist/src/verify.d.ts.map +1 -1
- package/dist/src/verify.js +49 -50
- package/package.json +2 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary Codec for Ratchet Messages.
|
|
3
|
+
*
|
|
4
|
+
* Wire format:
|
|
5
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
6
|
+
* │ Offset │ Size │ Field │
|
|
7
|
+
* ├────────┼──────┼─────────────────────────────────────────────┤
|
|
8
|
+
* │ 0 │ 1 │ Version (0x01) │
|
|
9
|
+
* │ 1 │ 64 │ Ed25519 signature │
|
|
10
|
+
* │ 65 │ 32 │ DH ratchet public key │
|
|
11
|
+
* │ 97 │ 4 │ pn (uint32 BE) - previous chain length │
|
|
12
|
+
* │ 101 │ 4 │ n (uint32 BE) - message number │
|
|
13
|
+
* │ 105 │ var │ Ciphertext (nonce + AEAD output) │
|
|
14
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
15
|
+
*
|
|
16
|
+
* Total minimum: 105 bytes + ciphertext
|
|
17
|
+
*/
|
|
18
|
+
import { MessageHeader, ParsedRatchetPayload } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Package ratchet message components into binary format.
|
|
21
|
+
*
|
|
22
|
+
* @param signature - Ed25519 signature (64 bytes)
|
|
23
|
+
* @param header - Message header (dh, pn, n)
|
|
24
|
+
* @param ciphertext - Encrypted payload (nonce + secretbox output)
|
|
25
|
+
* @returns Binary payload ready for on-chain submission
|
|
26
|
+
*/
|
|
27
|
+
export declare function packageRatchetPayload(signature: Uint8Array, header: MessageHeader, ciphertext: Uint8Array): Uint8Array;
|
|
28
|
+
/**
|
|
29
|
+
* Parse a binary ratchet payload.
|
|
30
|
+
*
|
|
31
|
+
* @param payload - Raw binary payload
|
|
32
|
+
* @returns Parsed components, or null if invalid format
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseRatchetPayload(payload: Uint8Array): ParsedRatchetPayload | null;
|
|
35
|
+
/**
|
|
36
|
+
* Check if payload is in ratchet format.
|
|
37
|
+
* Used to distinguish ratchet messages from legacy JSON format.
|
|
38
|
+
*
|
|
39
|
+
* @param payload - Raw payload bytes
|
|
40
|
+
* @returns true if payload starts with ratchet version byte
|
|
41
|
+
*/
|
|
42
|
+
export declare function isRatchetPayload(payload: Uint8Array): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Check if hex string represents a ratchet payload.
|
|
45
|
+
*
|
|
46
|
+
* @param hexPayload - Hex string (with or without 0x prefix)
|
|
47
|
+
* @returns true if payload is ratchet format
|
|
48
|
+
*/
|
|
49
|
+
export declare function isRatchetPayloadHex(hexPayload: string): boolean;
|
|
50
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
51
|
+
export declare function bytesToHex(bytes: Uint8Array, prefix?: boolean): string;
|
|
52
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/codec.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAKrF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,UAAU,EACrB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,UAAU,GACrB,UAAU,CAiCZ;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,GAAG,oBAAoB,GAAG,IAAI,CAmCpF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAQ/D;AAGD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAOlD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,GAAE,OAAc,GAAG,MAAM,CAK5E"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/codec.ts
|
|
2
|
+
/**
|
|
3
|
+
* Binary Codec for Ratchet Messages.
|
|
4
|
+
*
|
|
5
|
+
* Wire format:
|
|
6
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
7
|
+
* │ Offset │ Size │ Field │
|
|
8
|
+
* ├────────┼──────┼─────────────────────────────────────────────┤
|
|
9
|
+
* │ 0 │ 1 │ Version (0x01) │
|
|
10
|
+
* │ 1 │ 64 │ Ed25519 signature │
|
|
11
|
+
* │ 65 │ 32 │ DH ratchet public key │
|
|
12
|
+
* │ 97 │ 4 │ pn (uint32 BE) - previous chain length │
|
|
13
|
+
* │ 101 │ 4 │ n (uint32 BE) - message number │
|
|
14
|
+
* │ 105 │ var │ Ciphertext (nonce + AEAD output) │
|
|
15
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
16
|
+
*
|
|
17
|
+
* Total minimum: 105 bytes + ciphertext
|
|
18
|
+
*/
|
|
19
|
+
import { RATCHET_VERSION_V1 } from './types.js';
|
|
20
|
+
/** Minimum payload length: version(1) + sig(64) + dh(32) + pn(4) + n(4) */
|
|
21
|
+
const MIN_PAYLOAD_LENGTH = 1 + 64 + 32 + 4 + 4; // 105 bytes
|
|
22
|
+
/**
|
|
23
|
+
* Package ratchet message components into binary format.
|
|
24
|
+
*
|
|
25
|
+
* @param signature - Ed25519 signature (64 bytes)
|
|
26
|
+
* @param header - Message header (dh, pn, n)
|
|
27
|
+
* @param ciphertext - Encrypted payload (nonce + secretbox output)
|
|
28
|
+
* @returns Binary payload ready for on-chain submission
|
|
29
|
+
*/
|
|
30
|
+
export function packageRatchetPayload(signature, header, ciphertext) {
|
|
31
|
+
if (signature.length !== 64) {
|
|
32
|
+
throw new Error(`Invalid signature length: ${signature.length}, expected 64`);
|
|
33
|
+
}
|
|
34
|
+
if (header.dh.length !== 32) {
|
|
35
|
+
throw new Error(`Invalid DH key length: ${header.dh.length}, expected 32`);
|
|
36
|
+
}
|
|
37
|
+
// Total: 1 + 64 + 32 + 4 + 4 + ciphertext.length = 105 + ciphertext.length
|
|
38
|
+
const payload = new Uint8Array(MIN_PAYLOAD_LENGTH + ciphertext.length);
|
|
39
|
+
const view = new DataView(payload.buffer);
|
|
40
|
+
let offset = 0;
|
|
41
|
+
payload[offset++] = RATCHET_VERSION_V1;
|
|
42
|
+
payload.set(signature, offset);
|
|
43
|
+
offset += 64;
|
|
44
|
+
payload.set(header.dh, offset);
|
|
45
|
+
offset += 32;
|
|
46
|
+
// pn (uint32 big-endian)
|
|
47
|
+
view.setUint32(offset, header.pn, false);
|
|
48
|
+
offset += 4;
|
|
49
|
+
// n (uint32 big-endian)
|
|
50
|
+
view.setUint32(offset, header.n, false);
|
|
51
|
+
offset += 4;
|
|
52
|
+
payload.set(ciphertext, offset);
|
|
53
|
+
return payload;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse a binary ratchet payload.
|
|
57
|
+
*
|
|
58
|
+
* @param payload - Raw binary payload
|
|
59
|
+
* @returns Parsed components, or null if invalid format
|
|
60
|
+
*/
|
|
61
|
+
export function parseRatchetPayload(payload) {
|
|
62
|
+
if (payload.length < MIN_PAYLOAD_LENGTH) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
66
|
+
let offset = 0;
|
|
67
|
+
const version = payload[offset++];
|
|
68
|
+
if (version !== RATCHET_VERSION_V1) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const signature = payload.slice(offset, offset + 64);
|
|
72
|
+
offset += 64;
|
|
73
|
+
const dh = payload.slice(offset, offset + 32);
|
|
74
|
+
offset += 32;
|
|
75
|
+
// pn (uint32 big-endian)
|
|
76
|
+
const pn = view.getUint32(offset, false);
|
|
77
|
+
offset += 4;
|
|
78
|
+
// n (uint32 big-endian)
|
|
79
|
+
const n = view.getUint32(offset, false);
|
|
80
|
+
offset += 4;
|
|
81
|
+
const ciphertext = payload.slice(offset);
|
|
82
|
+
return {
|
|
83
|
+
version,
|
|
84
|
+
signature,
|
|
85
|
+
header: { dh, pn, n },
|
|
86
|
+
ciphertext,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if payload is in ratchet format.
|
|
91
|
+
* Used to distinguish ratchet messages from legacy JSON format.
|
|
92
|
+
*
|
|
93
|
+
* @param payload - Raw payload bytes
|
|
94
|
+
* @returns true if payload starts with ratchet version byte
|
|
95
|
+
*/
|
|
96
|
+
export function isRatchetPayload(payload) {
|
|
97
|
+
return payload.length >= MIN_PAYLOAD_LENGTH && payload[0] === RATCHET_VERSION_V1;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if hex string represents a ratchet payload.
|
|
101
|
+
*
|
|
102
|
+
* @param hexPayload - Hex string (with or without 0x prefix)
|
|
103
|
+
* @returns true if payload is ratchet format
|
|
104
|
+
*/
|
|
105
|
+
export function isRatchetPayloadHex(hexPayload) {
|
|
106
|
+
const hex = hexPayload.startsWith('0x') ? hexPayload.slice(2) : hexPayload;
|
|
107
|
+
if (hex.length < MIN_PAYLOAD_LENGTH * 2) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// Check first byte is version
|
|
111
|
+
const firstByte = parseInt(hex.slice(0, 2), 16);
|
|
112
|
+
return firstByte === RATCHET_VERSION_V1;
|
|
113
|
+
}
|
|
114
|
+
export function hexToBytes(hex) {
|
|
115
|
+
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
116
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
117
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
118
|
+
bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
|
|
119
|
+
}
|
|
120
|
+
return bytes;
|
|
121
|
+
}
|
|
122
|
+
export function bytesToHex(bytes, prefix = true) {
|
|
123
|
+
const hex = Array.from(bytes)
|
|
124
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
125
|
+
.join('');
|
|
126
|
+
return prefix ? `0x${hex}` : hex;
|
|
127
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RatchetSession, MessageHeader, DecryptResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Decrypt a message using the ratchet.
|
|
4
|
+
*
|
|
5
|
+
* @param session - Current ratchet session state
|
|
6
|
+
* @param header - Parsed message header
|
|
7
|
+
* @param ciphertext - Encrypted payload (nonce + secretbox output)
|
|
8
|
+
* @returns Decrypt result with new session state and plaintext, or null on failure
|
|
9
|
+
*/
|
|
10
|
+
export declare function ratchetDecrypt(session: RatchetSession, header: MessageHeader, ciphertext: Uint8Array): DecryptResult | null;
|
|
11
|
+
/**
|
|
12
|
+
* Prune expired skipped keys from session.
|
|
13
|
+
*
|
|
14
|
+
* @param session - Current session
|
|
15
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
|
16
|
+
* @returns Session with pruned skipped keys
|
|
17
|
+
*/
|
|
18
|
+
export declare function pruneExpiredSkippedKeys(session: RatchetSession, maxAgeMs?: number): RatchetSession;
|
|
19
|
+
/**
|
|
20
|
+
* Check if topic matches this session.
|
|
21
|
+
* Returns match type or null.
|
|
22
|
+
*
|
|
23
|
+
* @param session - Ratchet session to check
|
|
24
|
+
* @param topic - Topic to match against
|
|
25
|
+
* @returns 'current' if matches current inbound, 'previous' if matches previous (within grace), null otherwise
|
|
26
|
+
*/
|
|
27
|
+
export declare function matchesSessionTopic(session: RatchetSession, topic: `0x${string}`): 'current' | 'previous' | null;
|
|
28
|
+
//# sourceMappingURL=decrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/decrypt.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,cAAc,EACd,aAAa,EACb,aAAa,EAKd,MAAM,YAAY,CAAC;AAGpB;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,UAAU,GACrB,aAAa,GAAG,IAAI,CA4EtB;AAgKD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,EACvB,QAAQ,GAAE,MAA4B,GACrC,cAAc,CAyBhB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,KAAK,MAAM,EAAE,GACnB,SAAS,GAAG,UAAU,GAAG,IAAI,CAgB/B"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/decrypt.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ratchet Decryption with Skip Key Handling.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Normal sequential message decryption
|
|
7
|
+
* - DH ratchet steps when sender's DH key changes
|
|
8
|
+
* - Out-of-order messages via skipped keys
|
|
9
|
+
* - Topic ratcheting synchronized with DH ratchet
|
|
10
|
+
*/
|
|
11
|
+
import nacl from 'tweetnacl';
|
|
12
|
+
import { hexlify } from 'ethers';
|
|
13
|
+
import { MAX_SKIP_PER_MESSAGE, MAX_STORED_SKIPPED_KEYS, TOPIC_TRANSITION_WINDOW_MS, } from './types.js';
|
|
14
|
+
import { kdfRootKey, kdfChainKey, dh, generateDHKeyPair, deriveTopic } from './kdf.js';
|
|
15
|
+
/**
|
|
16
|
+
* Decrypt a message using the ratchet.
|
|
17
|
+
*
|
|
18
|
+
* @param session - Current ratchet session state
|
|
19
|
+
* @param header - Parsed message header
|
|
20
|
+
* @param ciphertext - Encrypted payload (nonce + secretbox output)
|
|
21
|
+
* @returns Decrypt result with new session state and plaintext, or null on failure
|
|
22
|
+
*/
|
|
23
|
+
export function ratchetDecrypt(session, header, ciphertext) {
|
|
24
|
+
// Sanity check: even authenticated messages shouldn't require insane skips
|
|
25
|
+
const skipNeeded = Math.max(0, header.n - session.receivingMsgNumber);
|
|
26
|
+
if (skipNeeded > MAX_SKIP_PER_MESSAGE || header.pn > MAX_SKIP_PER_MESSAGE) {
|
|
27
|
+
console.error(`Message requires ${skipNeeded} skips (pn=${header.pn}) — likely corrupted or malicious peer`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const dhPubHex = hexlify(header.dh);
|
|
31
|
+
const currentTheirDHHex = session.dhTheirPublicKey
|
|
32
|
+
? hexlify(session.dhTheirPublicKey)
|
|
33
|
+
: null;
|
|
34
|
+
// 1. Try skipped keys first (handles out-of-order messages)
|
|
35
|
+
const skippedResult = trySkippedKeys(session, dhPubHex, header.n, ciphertext);
|
|
36
|
+
if (skippedResult) {
|
|
37
|
+
return skippedResult;
|
|
38
|
+
}
|
|
39
|
+
// 2. Clone session for modifications
|
|
40
|
+
let newSession = { ...session, skippedKeys: [...session.skippedKeys] };
|
|
41
|
+
// 3. Check if we need to perform a DH ratchet step
|
|
42
|
+
if (dhPubHex !== currentTheirDHHex) {
|
|
43
|
+
// Skip any remaining messages from previous receiving chain
|
|
44
|
+
if (newSession.receivingChainKey) {
|
|
45
|
+
newSession = skipMessages(newSession, newSession.receivingMsgNumber, header.pn);
|
|
46
|
+
}
|
|
47
|
+
// Perform DH ratchet step
|
|
48
|
+
newSession = dhRatchetStep(newSession, header.dh);
|
|
49
|
+
}
|
|
50
|
+
// 4. Skip messages if n > receivingMsgNumber (within current epoch)
|
|
51
|
+
if (header.n > newSession.receivingMsgNumber) {
|
|
52
|
+
newSession = skipMessages(newSession, newSession.receivingMsgNumber, header.n);
|
|
53
|
+
}
|
|
54
|
+
// 5. Derive message key
|
|
55
|
+
if (!newSession.receivingChainKey) {
|
|
56
|
+
console.error('No receiving chain key available');
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const { chainKey: newReceivingChainKey, messageKey } = kdfChainKey(newSession.receivingChainKey);
|
|
60
|
+
// 6. Decrypt
|
|
61
|
+
const plaintext = decryptWithKey(ciphertext, messageKey);
|
|
62
|
+
// 7. Wipe message key
|
|
63
|
+
try {
|
|
64
|
+
messageKey.fill(0);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Ignore if fill fails
|
|
68
|
+
}
|
|
69
|
+
if (!plaintext) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// 8. Update session state
|
|
73
|
+
newSession = {
|
|
74
|
+
...newSession,
|
|
75
|
+
receivingChainKey: newReceivingChainKey,
|
|
76
|
+
receivingMsgNumber: header.n + 1,
|
|
77
|
+
updatedAt: Date.now(),
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
session: newSession,
|
|
81
|
+
plaintext,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* DH ratchet step on receipt of a message with a new remote DH public key.
|
|
86
|
+
*
|
|
87
|
+
* Topic derivation is sender-centric: `deriveTopicFromDH(x, 'outbound')` denotes
|
|
88
|
+
* the topic used by the party who *sent* the DH pubkey for their sending direction.
|
|
89
|
+
* Therefore, when we ratchet on receive, we swap labels for topics derived from `dhReceive`.
|
|
90
|
+
*/
|
|
91
|
+
function dhRatchetStep(session, theirNewDHPub) {
|
|
92
|
+
// Advance receiving chain (based on our current DH secret and their new DH pubkey)
|
|
93
|
+
const dhReceive = dh(session.dhMySecretKey, theirNewDHPub);
|
|
94
|
+
const { rootKey: rootKey1, chainKey: receivingChainKey } = kdfRootKey(session.rootKey, dhReceive);
|
|
95
|
+
// Generate new DH keypair for our next sending chain
|
|
96
|
+
const newDHKeyPair = generateDHKeyPair();
|
|
97
|
+
// Advance sending chain
|
|
98
|
+
const dhSend = dh(newDHKeyPair.secretKey, theirNewDHPub);
|
|
99
|
+
const { rootKey: rootKey2, chainKey: sendingChainKey } = kdfRootKey(rootKey1, dhSend);
|
|
100
|
+
// Current topics (post ratchet) - swapped since we're the receiver
|
|
101
|
+
// Use rootKey1 (derived from dhReceive) for PQ-secure topic derivation
|
|
102
|
+
const newTopicOut = deriveTopic(rootKey1, dhReceive, 'inbound');
|
|
103
|
+
const newTopicIn = deriveTopic(rootKey1, dhReceive, 'outbound');
|
|
104
|
+
// Next topics (for our next DH pubkey) - normal labels because we will be the sender
|
|
105
|
+
// Use rootKey2 (derived from dhSend) for PQ-secure topic derivation
|
|
106
|
+
const nextTopicOut = deriveTopic(rootKey2, dhSend, 'outbound');
|
|
107
|
+
const nextTopicIn = deriveTopic(rootKey2, dhSend, 'inbound');
|
|
108
|
+
return {
|
|
109
|
+
...session,
|
|
110
|
+
rootKey: rootKey2,
|
|
111
|
+
dhMySecretKey: newDHKeyPair.secretKey,
|
|
112
|
+
dhMyPublicKey: newDHKeyPair.publicKey,
|
|
113
|
+
dhTheirPublicKey: theirNewDHPub,
|
|
114
|
+
receivingChainKey,
|
|
115
|
+
receivingMsgNumber: 0,
|
|
116
|
+
sendingChainKey,
|
|
117
|
+
sendingMsgNumber: 0,
|
|
118
|
+
previousChainLength: session.sendingMsgNumber,
|
|
119
|
+
nextTopicOutbound: nextTopicOut,
|
|
120
|
+
nextTopicInbound: nextTopicIn,
|
|
121
|
+
currentTopicOutbound: newTopicOut,
|
|
122
|
+
currentTopicInbound: newTopicIn,
|
|
123
|
+
previousTopicInbound: session.currentTopicInbound,
|
|
124
|
+
previousTopicExpiry: Date.now() + TOPIC_TRANSITION_WINDOW_MS,
|
|
125
|
+
topicEpoch: session.topicEpoch + 1,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Skip messages by deriving and storing their keys for later out-of-order decryption.
|
|
130
|
+
*
|
|
131
|
+
* Called when:
|
|
132
|
+
* - header.n > receivingMsgNumber (messages skipped in current epoch)
|
|
133
|
+
* - DH ratchet step with header.pn > 0 (messages from previous epoch)
|
|
134
|
+
*/
|
|
135
|
+
function skipMessages(session, start, until) {
|
|
136
|
+
if (!session.receivingChainKey || until <= start) {
|
|
137
|
+
return session;
|
|
138
|
+
}
|
|
139
|
+
const skippedKeys = [...session.skippedKeys];
|
|
140
|
+
let chainKey = session.receivingChainKey;
|
|
141
|
+
const dhPubHex = hexlify(session.dhTheirPublicKey);
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
for (let i = start; i < until; i++) {
|
|
144
|
+
const { chainKey: newChainKey, messageKey } = kdfChainKey(chainKey);
|
|
145
|
+
skippedKeys.push({
|
|
146
|
+
dhPubKeyHex: dhPubHex,
|
|
147
|
+
msgNumber: i,
|
|
148
|
+
messageKey: new Uint8Array(messageKey),
|
|
149
|
+
createdAt: now,
|
|
150
|
+
});
|
|
151
|
+
chainKey = newChainKey;
|
|
152
|
+
}
|
|
153
|
+
// Prune if we have too many skipped keys
|
|
154
|
+
let prunedKeys = skippedKeys;
|
|
155
|
+
if (skippedKeys.length > MAX_STORED_SKIPPED_KEYS) {
|
|
156
|
+
prunedKeys = skippedKeys
|
|
157
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
158
|
+
.slice(0, MAX_STORED_SKIPPED_KEYS);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...session,
|
|
162
|
+
receivingChainKey: chainKey,
|
|
163
|
+
skippedKeys: prunedKeys,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function trySkippedKeys(session, dhPubHex, msgNumber, ciphertext) {
|
|
167
|
+
const idx = session.skippedKeys.findIndex((sk) => sk.dhPubKeyHex === dhPubHex && sk.msgNumber === msgNumber);
|
|
168
|
+
if (idx === -1) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const skippedKey = session.skippedKeys[idx];
|
|
172
|
+
const plaintext = decryptWithKey(ciphertext, skippedKey.messageKey);
|
|
173
|
+
if (!plaintext) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const newSkippedKeys = [...session.skippedKeys];
|
|
177
|
+
newSkippedKeys.splice(idx, 1);
|
|
178
|
+
// Wipe the key
|
|
179
|
+
try {
|
|
180
|
+
skippedKey.messageKey.fill(0);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
session: {
|
|
186
|
+
...session,
|
|
187
|
+
skippedKeys: newSkippedKeys,
|
|
188
|
+
updatedAt: Date.now(),
|
|
189
|
+
},
|
|
190
|
+
plaintext,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Decrypt ciphertext with message key using XSalsa20-Poly1305.
|
|
195
|
+
* Ciphertext format: nonce (24 bytes) + secretbox output
|
|
196
|
+
*/
|
|
197
|
+
function decryptWithKey(ciphertext, messageKey) {
|
|
198
|
+
if (ciphertext.length < nacl.secretbox.nonceLength) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const nonce = ciphertext.slice(0, nacl.secretbox.nonceLength);
|
|
202
|
+
const box = ciphertext.slice(nacl.secretbox.nonceLength);
|
|
203
|
+
const result = nacl.secretbox.open(box, nonce, messageKey);
|
|
204
|
+
return result || null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Prune expired skipped keys from session.
|
|
208
|
+
*
|
|
209
|
+
* @param session - Current session
|
|
210
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
|
211
|
+
* @returns Session with pruned skipped keys
|
|
212
|
+
*/
|
|
213
|
+
export function pruneExpiredSkippedKeys(session, maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
const cutoff = now - maxAgeMs;
|
|
216
|
+
const prunedKeys = session.skippedKeys.filter((sk) => sk.createdAt > cutoff);
|
|
217
|
+
// Wipe expired keys
|
|
218
|
+
for (const sk of session.skippedKeys) {
|
|
219
|
+
if (sk.createdAt <= cutoff) {
|
|
220
|
+
try {
|
|
221
|
+
sk.messageKey.fill(0);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (prunedKeys.length === session.skippedKeys.length) {
|
|
228
|
+
return session; // No change
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
...session,
|
|
232
|
+
skippedKeys: prunedKeys,
|
|
233
|
+
updatedAt: now,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if topic matches this session.
|
|
238
|
+
* Returns match type or null.
|
|
239
|
+
*
|
|
240
|
+
* @param session - Ratchet session to check
|
|
241
|
+
* @param topic - Topic to match against
|
|
242
|
+
* @returns 'current' if matches current inbound, 'previous' if matches previous (within grace), null otherwise
|
|
243
|
+
*/
|
|
244
|
+
export function matchesSessionTopic(session, topic) {
|
|
245
|
+
const t = topic.toLowerCase();
|
|
246
|
+
if (session.currentTopicInbound.toLowerCase() === t) {
|
|
247
|
+
return 'current';
|
|
248
|
+
}
|
|
249
|
+
if (session.previousTopicInbound?.toLowerCase() === t &&
|
|
250
|
+
session.previousTopicExpiry &&
|
|
251
|
+
Date.now() < session.previousTopicExpiry) {
|
|
252
|
+
return 'previous';
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RatchetSession, MessageHeader, EncryptResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encode message header as 40 bytes for signing.
|
|
4
|
+
* Format: dh (32) + pn (4, BE) + n (4, BE)
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeHeader(header: MessageHeader): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Encrypt a message using the ratchet.
|
|
9
|
+
*
|
|
10
|
+
* @param session - Current ratchet session state
|
|
11
|
+
* @param plaintext - Message to encrypt
|
|
12
|
+
* @param signingSecretKey - Ed25519 secret key for signing (64 bytes)
|
|
13
|
+
* @returns Encrypt result with new session state, header, ciphertext, signature, and topic
|
|
14
|
+
* @throws If session is not ready to send (no sending chain key)
|
|
15
|
+
*/
|
|
16
|
+
export declare function ratchetEncrypt(session: RatchetSession, plaintext: Uint8Array, signingSecretKey: Uint8Array): EncryptResult;
|
|
17
|
+
//# sourceMappingURL=encrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/encrypt.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG1E;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CAM9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,UAAU,EACrB,gBAAgB,EAAE,UAAU,GAC3B,aAAa,CAqDf"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/encrypt.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ratchet Encryption.
|
|
4
|
+
*
|
|
5
|
+
* Encrypts plaintext using the current sending chain, advances chain state,
|
|
6
|
+
* and signs the message with Ed25519.
|
|
7
|
+
*
|
|
8
|
+
* Returns a new session object. Caller must persist session state
|
|
9
|
+
* immediately for forward secrecy (before tx submission).
|
|
10
|
+
*/
|
|
11
|
+
import nacl from 'tweetnacl';
|
|
12
|
+
import { kdfChainKey } from './kdf.js';
|
|
13
|
+
/**
|
|
14
|
+
* Encode message header as 40 bytes for signing.
|
|
15
|
+
* Format: dh (32) + pn (4, BE) + n (4, BE)
|
|
16
|
+
*/
|
|
17
|
+
export function encodeHeader(header) {
|
|
18
|
+
const buf = new Uint8Array(40);
|
|
19
|
+
buf.set(header.dh, 0);
|
|
20
|
+
new DataView(buf.buffer).setUint32(32, header.pn, false); // big-endian
|
|
21
|
+
new DataView(buf.buffer).setUint32(36, header.n, false);
|
|
22
|
+
return buf;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Encrypt a message using the ratchet.
|
|
26
|
+
*
|
|
27
|
+
* @param session - Current ratchet session state
|
|
28
|
+
* @param plaintext - Message to encrypt
|
|
29
|
+
* @param signingSecretKey - Ed25519 secret key for signing (64 bytes)
|
|
30
|
+
* @returns Encrypt result with new session state, header, ciphertext, signature, and topic
|
|
31
|
+
* @throws If session is not ready to send (no sending chain key)
|
|
32
|
+
*/
|
|
33
|
+
export function ratchetEncrypt(session, plaintext, signingSecretKey) {
|
|
34
|
+
if (!session.sendingChainKey) {
|
|
35
|
+
throw new Error('Session not ready to send (no sending chain key)');
|
|
36
|
+
}
|
|
37
|
+
// 1. Advance sending chain to get message key
|
|
38
|
+
const { chainKey: newChainKey, messageKey } = kdfChainKey(session.sendingChainKey);
|
|
39
|
+
// 2. Create header with current DH public key and message numbers
|
|
40
|
+
const header = {
|
|
41
|
+
dh: session.dhMyPublicKey,
|
|
42
|
+
pn: session.previousChainLength,
|
|
43
|
+
n: session.sendingMsgNumber,
|
|
44
|
+
};
|
|
45
|
+
// 3. Encrypt with message key using XSalsa20-Poly1305
|
|
46
|
+
const nonce = nacl.randomBytes(nacl.secretbox.nonceLength); // 24 bytes
|
|
47
|
+
const ciphertext = nacl.secretbox(plaintext, nonce, messageKey);
|
|
48
|
+
// 4. Combine nonce + ciphertext
|
|
49
|
+
const encryptedPayload = new Uint8Array(nonce.length + ciphertext.length);
|
|
50
|
+
encryptedPayload.set(nonce, 0);
|
|
51
|
+
encryptedPayload.set(ciphertext, nonce.length);
|
|
52
|
+
// 5. Sign (header || encryptedPayload) with Ed25519
|
|
53
|
+
const headerBytes = encodeHeader(header);
|
|
54
|
+
const dataToSign = new Uint8Array(headerBytes.length + encryptedPayload.length);
|
|
55
|
+
dataToSign.set(headerBytes, 0);
|
|
56
|
+
dataToSign.set(encryptedPayload, headerBytes.length);
|
|
57
|
+
const signature = nacl.sign.detached(dataToSign, signingSecretKey);
|
|
58
|
+
// 6. Create new session state (don't mutate original)
|
|
59
|
+
const newSession = {
|
|
60
|
+
...session,
|
|
61
|
+
sendingChainKey: newChainKey,
|
|
62
|
+
sendingMsgNumber: session.sendingMsgNumber + 1,
|
|
63
|
+
updatedAt: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
// 7. Wipe message key from memory
|
|
66
|
+
try {
|
|
67
|
+
messageKey.fill(0);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
session: newSession,
|
|
73
|
+
header,
|
|
74
|
+
ciphertext: encryptedPayload,
|
|
75
|
+
signature,
|
|
76
|
+
topic: session.currentTopicOutbound,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { MAX_SKIP_PER_MESSAGE, MAX_STORED_SKIPPED_KEYS, MAX_SKIPPED_KEYS_AGE_MS, SYNC_BATCH_SIZE, RATCHET_VERSION_V1, TOPIC_TRANSITION_WINDOW_MS, type RatchetSession, type SkippedKey, type MessageHeader, type EncryptResult, type DecryptResult, type ParsedRatchetPayload, type InitResponderParams, type InitInitiatorParams, } from './types.js';
|
|
2
|
+
export { kdfRootKey, kdfChainKey, dh, generateDHKeyPair, deriveTopic, hybridInitialSecret, } from './kdf.js';
|
|
3
|
+
export { initSessionAsResponder, initSessionAsInitiator, computeConversationId, } from './session.js';
|
|
4
|
+
export { ratchetEncrypt, encodeHeader, } from './encrypt.js';
|
|
5
|
+
export { ratchetDecrypt, pruneExpiredSkippedKeys, matchesSessionTopic, } from './decrypt.js';
|
|
6
|
+
export { packageRatchetPayload, parseRatchetPayload, isRatchetPayload, isRatchetPayloadHex, hexToBytes, bytesToHex, } from './codec.js';
|
|
7
|
+
export { verifyMessageSignature, signMessage, isValidPayloadFormat, } from './auth.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,0BAA0B,EAE1B,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,aAAa,EAElB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAEzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EACV,WAAW,EACX,EAAE,EACF,iBAAiB,EACjB,WAAW,EACX,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/index.ts
|
|
2
|
+
export { MAX_SKIP_PER_MESSAGE, MAX_STORED_SKIPPED_KEYS, MAX_SKIPPED_KEYS_AGE_MS, SYNC_BATCH_SIZE, RATCHET_VERSION_V1, TOPIC_TRANSITION_WINDOW_MS, } from './types.js';
|
|
3
|
+
export { kdfRootKey, kdfChainKey, dh, generateDHKeyPair, deriveTopic, hybridInitialSecret, } from './kdf.js';
|
|
4
|
+
export { initSessionAsResponder, initSessionAsInitiator, computeConversationId, } from './session.js';
|
|
5
|
+
export { ratchetEncrypt, encodeHeader, } from './encrypt.js';
|
|
6
|
+
export { ratchetDecrypt, pruneExpiredSkippedKeys, matchesSessionTopic, } from './decrypt.js';
|
|
7
|
+
export { packageRatchetPayload, parseRatchetPayload, isRatchetPayload, isRatchetPayloadHex, hexToBytes, bytesToHex, } from './codec.js';
|
|
8
|
+
export { verifyMessageSignature, signMessage, isValidPayloadFormat, } from './auth.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive new root key and chain key from DH output.
|
|
3
|
+
*
|
|
4
|
+
* @param rootKey - Current root key (32 bytes)
|
|
5
|
+
* @param dhOutput - DH shared secret (32 bytes)
|
|
6
|
+
* @returns New root key and chain key
|
|
7
|
+
*/
|
|
8
|
+
export declare function kdfRootKey(rootKey: Uint8Array, dhOutput: Uint8Array): {
|
|
9
|
+
rootKey: Uint8Array;
|
|
10
|
+
chainKey: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Derive message key and advance chain key.
|
|
14
|
+
*
|
|
15
|
+
* @param chainKey - Current chain key (32 bytes)
|
|
16
|
+
* @returns New chain key and message key for encryption/decryption
|
|
17
|
+
*/
|
|
18
|
+
export declare function kdfChainKey(chainKey: Uint8Array): {
|
|
19
|
+
chainKey: Uint8Array;
|
|
20
|
+
messageKey: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Perform X25519 Diffie-Hellman key exchange.
|
|
24
|
+
*
|
|
25
|
+
* @param mySecretKey - My X25519 secret key (32 bytes)
|
|
26
|
+
* @param theirPublicKey - Their X25519 public key (32 bytes)
|
|
27
|
+
* @returns Shared secret (32 bytes)
|
|
28
|
+
*/
|
|
29
|
+
export declare function dh(mySecretKey: Uint8Array, theirPublicKey: Uint8Array): Uint8Array;
|
|
30
|
+
/**
|
|
31
|
+
* Generate new X25519 keypair for DH ratchet step.
|
|
32
|
+
*
|
|
33
|
+
* @returns New keypair with secretKey and publicKey
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateDHKeyPair(): {
|
|
36
|
+
secretKey: Uint8Array;
|
|
37
|
+
publicKey: Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Derive topic from DH output using rootKey as PQ-secure salt.
|
|
41
|
+
*
|
|
42
|
+
* The rootKey (PQ-secure from hybrid handshake) acts as HKDF salt,
|
|
43
|
+
* providing quantum-resistant topic unlinkability even if dhOutput
|
|
44
|
+
* is later computed by a quantum adversary.
|
|
45
|
+
*
|
|
46
|
+
* @param rootKey - Current root key (32 bytes, PQ-secure)
|
|
47
|
+
* @param dhOutput - DH shared secret from ratchet step (32 bytes)
|
|
48
|
+
* @param direction - 'outbound' or 'inbound' for topic direction
|
|
49
|
+
* @returns bytes32 topic as hex string
|
|
50
|
+
*/
|
|
51
|
+
export declare function deriveTopic(rootKey: Uint8Array, dhOutput: Uint8Array, direction: 'outbound' | 'inbound'): `0x${string}`;
|
|
52
|
+
/**
|
|
53
|
+
* Combines classical (X25519) and post-quantum (ML-KEM-768) key exchange
|
|
54
|
+
*
|
|
55
|
+
* @param x25519Secret - X25519 DH shared secret (32 bytes)
|
|
56
|
+
* @param kemSecret - ML-KEM-768 shared secret (32 bytes)
|
|
57
|
+
* @returns Hybrid shared secret (32 bytes)
|
|
58
|
+
*/
|
|
59
|
+
export declare function hybridInitialSecret(x25519Secret: Uint8Array, kemSecret: Uint8Array): Uint8Array;
|
|
60
|
+
//# sourceMappingURL=kdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/kdf.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,UAAU,GACnB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,UAAU,CAAA;CAAE,CAM/C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,UAAU,GACnB;IAAE,QAAQ,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAUlD;AAED;;;;;;GAMG;AACH,wBAAgB,EAAE,CAChB,WAAW,EAAE,UAAU,EACvB,cAAc,EAAE,UAAU,GACzB,UAAU,CAEZ;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,UAAU,CAAA;CAAE,CAGpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,UAAU,EACpB,SAAS,EAAE,UAAU,GAAG,SAAS,GAChC,KAAK,MAAM,EAAE,CAIf;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,UAAU,EACxB,SAAS,EAAE,UAAU,GACpB,UAAU,CAKZ"}
|