@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,91 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/kdf.ts
|
|
2
|
+
/**
|
|
3
|
+
* Key Derivation Functions for Double Ratchet.
|
|
4
|
+
*
|
|
5
|
+
* Uses HKDF-SHA256 for root key derivation and HMAC-SHA256 for chain key derivation,
|
|
6
|
+
* matching Signal protocol specifications.
|
|
7
|
+
*/
|
|
8
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
9
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
10
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
11
|
+
import { keccak256 } from 'ethers';
|
|
12
|
+
import nacl from 'tweetnacl';
|
|
13
|
+
/**
|
|
14
|
+
* Derive new root key and chain key from DH output.
|
|
15
|
+
*
|
|
16
|
+
* @param rootKey - Current root key (32 bytes)
|
|
17
|
+
* @param dhOutput - DH shared secret (32 bytes)
|
|
18
|
+
* @returns New root key and chain key
|
|
19
|
+
*/
|
|
20
|
+
export function kdfRootKey(rootKey, dhOutput) {
|
|
21
|
+
const output = hkdf(sha256, dhOutput, rootKey, 'VerbethRatchet', 64);
|
|
22
|
+
return {
|
|
23
|
+
rootKey: output.slice(0, 32),
|
|
24
|
+
chainKey: output.slice(32, 64),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Derive message key and advance chain key.
|
|
29
|
+
*
|
|
30
|
+
* @param chainKey - Current chain key (32 bytes)
|
|
31
|
+
* @returns New chain key and message key for encryption/decryption
|
|
32
|
+
*/
|
|
33
|
+
export function kdfChainKey(chainKey) {
|
|
34
|
+
// Message key derived with constant 0x01
|
|
35
|
+
const messageKey = hmac(sha256, chainKey, new Uint8Array([0x01]));
|
|
36
|
+
// New chain key derived with constant 0x02
|
|
37
|
+
const newChainKey = hmac(sha256, chainKey, new Uint8Array([0x02]));
|
|
38
|
+
return {
|
|
39
|
+
chainKey: newChainKey,
|
|
40
|
+
messageKey: messageKey,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Perform X25519 Diffie-Hellman key exchange.
|
|
45
|
+
*
|
|
46
|
+
* @param mySecretKey - My X25519 secret key (32 bytes)
|
|
47
|
+
* @param theirPublicKey - Their X25519 public key (32 bytes)
|
|
48
|
+
* @returns Shared secret (32 bytes)
|
|
49
|
+
*/
|
|
50
|
+
export function dh(mySecretKey, theirPublicKey) {
|
|
51
|
+
return nacl.scalarMult(mySecretKey, theirPublicKey);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate new X25519 keypair for DH ratchet step.
|
|
55
|
+
*
|
|
56
|
+
* @returns New keypair with secretKey and publicKey
|
|
57
|
+
*/
|
|
58
|
+
export function generateDHKeyPair() {
|
|
59
|
+
const kp = nacl.box.keyPair();
|
|
60
|
+
return { secretKey: kp.secretKey, publicKey: kp.publicKey };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Derive topic from DH output using rootKey as PQ-secure salt.
|
|
64
|
+
*
|
|
65
|
+
* The rootKey (PQ-secure from hybrid handshake) acts as HKDF salt,
|
|
66
|
+
* providing quantum-resistant topic unlinkability even if dhOutput
|
|
67
|
+
* is later computed by a quantum adversary.
|
|
68
|
+
*
|
|
69
|
+
* @param rootKey - Current root key (32 bytes, PQ-secure)
|
|
70
|
+
* @param dhOutput - DH shared secret from ratchet step (32 bytes)
|
|
71
|
+
* @param direction - 'outbound' or 'inbound' for topic direction
|
|
72
|
+
* @returns bytes32 topic as hex string
|
|
73
|
+
*/
|
|
74
|
+
export function deriveTopic(rootKey, dhOutput, direction) {
|
|
75
|
+
const info = `verbeth:topic-${direction}:v3`;
|
|
76
|
+
const okm = hkdf(sha256, dhOutput, rootKey, info, 32);
|
|
77
|
+
return keccak256(okm);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Combines classical (X25519) and post-quantum (ML-KEM-768) key exchange
|
|
81
|
+
*
|
|
82
|
+
* @param x25519Secret - X25519 DH shared secret (32 bytes)
|
|
83
|
+
* @param kemSecret - ML-KEM-768 shared secret (32 bytes)
|
|
84
|
+
* @returns Hybrid shared secret (32 bytes)
|
|
85
|
+
*/
|
|
86
|
+
export function hybridInitialSecret(x25519Secret, kemSecret) {
|
|
87
|
+
const combined = new Uint8Array(x25519Secret.length + kemSecret.length);
|
|
88
|
+
combined.set(x25519Secret, 0);
|
|
89
|
+
combined.set(kemSecret, x25519Secret.length);
|
|
90
|
+
return hkdf(sha256, combined, new Uint8Array(32), 'VerbethHybrid', 32);
|
|
91
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RatchetSession, InitResponderParams, InitInitiatorParams } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compute deterministic conversation ID from topics.
|
|
4
|
+
* Sorting ensures both parties derive the same ID regardless of perspective.
|
|
5
|
+
*
|
|
6
|
+
* @param topicA - First topic
|
|
7
|
+
* @param topicB - Second topic
|
|
8
|
+
* @returns Unique conversation identifier
|
|
9
|
+
*/
|
|
10
|
+
export declare function computeConversationId(topicA: string, topicB: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize session as responder (Bob).
|
|
13
|
+
*
|
|
14
|
+
* Called after receiving handshake, before/during sending response.
|
|
15
|
+
* The responder must persist myResponderEphemeralSecret immediately.
|
|
16
|
+
* This becomes dhMySecretKey and is required for all future ratchet operations.
|
|
17
|
+
*
|
|
18
|
+
* Responder starts at epoch 0 (handshake topics).
|
|
19
|
+
*
|
|
20
|
+
* If kemSecret is provided (from ML-KEM encapsulation), uses hybrid KDF
|
|
21
|
+
* combining X25519 DH and ML-KEM for post-quantum security.
|
|
22
|
+
*
|
|
23
|
+
* @param params - Initialization parameters
|
|
24
|
+
* @returns Initialized ratchet session
|
|
25
|
+
*/
|
|
26
|
+
export declare function initSessionAsResponder(params: InitResponderParams): RatchetSession;
|
|
27
|
+
/**
|
|
28
|
+
* Initialize session as initiator (Alice).
|
|
29
|
+
*
|
|
30
|
+
* Called after receiving and validating handshake response.
|
|
31
|
+
*
|
|
32
|
+
* Initiator precomputes epoch 1 topics from its first post-handshake DH step.
|
|
33
|
+
* Outbound should use epoch 1 as soon as we introduce a new DH pubkey.
|
|
34
|
+
* Inbound stays on epoch 0 until the responder ratchets.
|
|
35
|
+
*
|
|
36
|
+
* If kemSecret is provided (from ML-KEM decapsulation), uses hybrid KDF
|
|
37
|
+
* combining X25519 DH and ML-KEM for post-quantum security.
|
|
38
|
+
*
|
|
39
|
+
* @param params - Initialization parameters
|
|
40
|
+
* @returns Initialized ratchet session
|
|
41
|
+
*/
|
|
42
|
+
export declare function initSessionAsInitiator(params: InitInitiatorParams): RatchetSession;
|
|
43
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/session.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAGpB;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,cAAc,CA6DlF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,cAAc,CA4ElF"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/session.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ratchet Session Initialization.
|
|
4
|
+
*
|
|
5
|
+
* Provides functions to initialize ratchet sessions for both
|
|
6
|
+
* initiator (Alice) and responder (Bob) roles.
|
|
7
|
+
*
|
|
8
|
+
* Initial shared secret is derived from ephemeral-to-ephemeral DH only.
|
|
9
|
+
*/
|
|
10
|
+
import { keccak256, toUtf8Bytes } from 'ethers';
|
|
11
|
+
import { kdfRootKey, dh, generateDHKeyPair, deriveTopic, hybridInitialSecret } from './kdf.js';
|
|
12
|
+
/**
|
|
13
|
+
* Compute deterministic conversation ID from topics.
|
|
14
|
+
* Sorting ensures both parties derive the same ID regardless of perspective.
|
|
15
|
+
*
|
|
16
|
+
* @param topicA - First topic
|
|
17
|
+
* @param topicB - Second topic
|
|
18
|
+
* @returns Unique conversation identifier
|
|
19
|
+
*/
|
|
20
|
+
export function computeConversationId(topicA, topicB) {
|
|
21
|
+
const sorted = [topicA.toLowerCase(), topicB.toLowerCase()].sort();
|
|
22
|
+
return keccak256(toUtf8Bytes(sorted.join(':')));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize session as responder (Bob).
|
|
26
|
+
*
|
|
27
|
+
* Called after receiving handshake, before/during sending response.
|
|
28
|
+
* The responder must persist myResponderEphemeralSecret immediately.
|
|
29
|
+
* This becomes dhMySecretKey and is required for all future ratchet operations.
|
|
30
|
+
*
|
|
31
|
+
* Responder starts at epoch 0 (handshake topics).
|
|
32
|
+
*
|
|
33
|
+
* If kemSecret is provided (from ML-KEM encapsulation), uses hybrid KDF
|
|
34
|
+
* combining X25519 DH and ML-KEM for post-quantum security.
|
|
35
|
+
*
|
|
36
|
+
* @param params - Initialization parameters
|
|
37
|
+
* @returns Initialized ratchet session
|
|
38
|
+
*/
|
|
39
|
+
export function initSessionAsResponder(params) {
|
|
40
|
+
const { myAddress, contactAddress, myResponderEphemeralSecret, myResponderEphemeralPublic, theirHandshakeEphemeralPubKey, topicOutbound, topicInbound, kemSecret, } = params;
|
|
41
|
+
const x25519Secret = dh(myResponderEphemeralSecret, theirHandshakeEphemeralPubKey);
|
|
42
|
+
const sharedSecret = kemSecret
|
|
43
|
+
? hybridInitialSecret(x25519Secret, kemSecret)
|
|
44
|
+
: x25519Secret;
|
|
45
|
+
// Derive initial root key and sending chain key
|
|
46
|
+
const { rootKey, chainKey: sendingChainKey } = kdfRootKey(new Uint8Array(32), // Initial salt (zeros)
|
|
47
|
+
sharedSecret);
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
return {
|
|
50
|
+
conversationId: computeConversationId(topicOutbound, topicInbound),
|
|
51
|
+
topicOutbound,
|
|
52
|
+
topicInbound,
|
|
53
|
+
myAddress,
|
|
54
|
+
contactAddress,
|
|
55
|
+
rootKey,
|
|
56
|
+
// Reuse responder ephemeral as first DH ratchet key
|
|
57
|
+
dhMySecretKey: myResponderEphemeralSecret,
|
|
58
|
+
dhMyPublicKey: myResponderEphemeralPublic,
|
|
59
|
+
dhTheirPublicKey: theirHandshakeEphemeralPubKey,
|
|
60
|
+
sendingChainKey,
|
|
61
|
+
sendingMsgNumber: 0,
|
|
62
|
+
// Receiving chain not yet established (Alice sends first post-handshake)
|
|
63
|
+
receivingChainKey: null,
|
|
64
|
+
receivingMsgNumber: 0,
|
|
65
|
+
previousChainLength: 0,
|
|
66
|
+
skippedKeys: [],
|
|
67
|
+
// Topic Ratcheting - Epoch 0: use handshake-derived topics
|
|
68
|
+
currentTopicOutbound: topicOutbound,
|
|
69
|
+
currentTopicInbound: topicInbound,
|
|
70
|
+
previousTopicInbound: undefined,
|
|
71
|
+
previousTopicExpiry: undefined,
|
|
72
|
+
topicEpoch: 0,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
epoch: 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Initialize session as initiator (Alice).
|
|
80
|
+
*
|
|
81
|
+
* Called after receiving and validating handshake response.
|
|
82
|
+
*
|
|
83
|
+
* Initiator precomputes epoch 1 topics from its first post-handshake DH step.
|
|
84
|
+
* Outbound should use epoch 1 as soon as we introduce a new DH pubkey.
|
|
85
|
+
* Inbound stays on epoch 0 until the responder ratchets.
|
|
86
|
+
*
|
|
87
|
+
* If kemSecret is provided (from ML-KEM decapsulation), uses hybrid KDF
|
|
88
|
+
* combining X25519 DH and ML-KEM for post-quantum security.
|
|
89
|
+
*
|
|
90
|
+
* @param params - Initialization parameters
|
|
91
|
+
* @returns Initialized ratchet session
|
|
92
|
+
*/
|
|
93
|
+
export function initSessionAsInitiator(params) {
|
|
94
|
+
const { myAddress, contactAddress, myHandshakeEphemeralSecret, theirResponderEphemeralPubKey, topicOutbound, topicInbound, kemSecret, } = params;
|
|
95
|
+
const x25519Secret = dh(myHandshakeEphemeralSecret, theirResponderEphemeralPubKey);
|
|
96
|
+
const sharedSecret = kemSecret
|
|
97
|
+
? hybridInitialSecret(x25519Secret, kemSecret)
|
|
98
|
+
: x25519Secret;
|
|
99
|
+
// Derive same initial root key as responder
|
|
100
|
+
const { rootKey: initialRootKey, chainKey: bobsSendingChain } = kdfRootKey(new Uint8Array(32), sharedSecret);
|
|
101
|
+
// Generate first DH keypair for sending (Alice performs first DH ratchet)
|
|
102
|
+
const myDHKeyPair = generateDHKeyPair();
|
|
103
|
+
const dhSend = dh(myDHKeyPair.secretKey, theirResponderEphemeralPubKey);
|
|
104
|
+
const { rootKey: finalRootKey, chainKey: sendingChainKey } = kdfRootKey(initialRootKey, dhSend);
|
|
105
|
+
const conversationId = computeConversationId(topicOutbound, topicInbound);
|
|
106
|
+
// Pre-compute epoch 1 topics (for when our first message is sent)
|
|
107
|
+
// Use finalRootKey as PQ-secure salt for quantum-resistant topic unlinkability
|
|
108
|
+
const epoch1TopicOut = deriveTopic(finalRootKey, dhSend, 'outbound');
|
|
109
|
+
const epoch1TopicIn = deriveTopic(finalRootKey, dhSend, 'inbound');
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
return {
|
|
112
|
+
conversationId,
|
|
113
|
+
topicOutbound,
|
|
114
|
+
topicInbound,
|
|
115
|
+
myAddress,
|
|
116
|
+
contactAddress,
|
|
117
|
+
rootKey: finalRootKey,
|
|
118
|
+
dhMySecretKey: myDHKeyPair.secretKey,
|
|
119
|
+
dhMyPublicKey: myDHKeyPair.publicKey,
|
|
120
|
+
dhTheirPublicKey: theirResponderEphemeralPubKey,
|
|
121
|
+
sendingChainKey,
|
|
122
|
+
sendingMsgNumber: 0,
|
|
123
|
+
// Alice can receive Bob's messages immediately
|
|
124
|
+
receivingChainKey: bobsSendingChain,
|
|
125
|
+
receivingMsgNumber: 0,
|
|
126
|
+
previousChainLength: 0,
|
|
127
|
+
skippedKeys: [],
|
|
128
|
+
// Start with handshake topics
|
|
129
|
+
currentTopicOutbound: topicOutbound,
|
|
130
|
+
currentTopicInbound: topicInbound,
|
|
131
|
+
// Pre-computed next topics (will be promoted when we send)
|
|
132
|
+
nextTopicOutbound: epoch1TopicOut,
|
|
133
|
+
nextTopicInbound: epoch1TopicIn,
|
|
134
|
+
topicEpoch: 0,
|
|
135
|
+
createdAt: now,
|
|
136
|
+
updatedAt: now,
|
|
137
|
+
epoch: 0,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Double Ratchet types and constants.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Sanity cap per ratchet step, protects against malicious peers or corrupted state.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_SKIP_PER_MESSAGE = 100000;
|
|
8
|
+
/**
|
|
9
|
+
* When exceeded, oldest keys are pruned.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_STORED_SKIPPED_KEYS = 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Skipped keys TTL (24 hours is sufficient for reorg tolerance).
|
|
14
|
+
* (this is not message expiry as sequential messages don't use skipped keys)
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_SKIPPED_KEYS_AGE_MS: number;
|
|
17
|
+
/**
|
|
18
|
+
* Yield to UI every N derivations during large backlog processing.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SYNC_BATCH_SIZE = 10000;
|
|
21
|
+
/**
|
|
22
|
+
* Binary payload version byte.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RATCHET_VERSION_V1 = 1;
|
|
25
|
+
export declare const TOPIC_TRANSITION_WINDOW_MS: number;
|
|
26
|
+
/**
|
|
27
|
+
* Ratchet session state.
|
|
28
|
+
*
|
|
29
|
+
* This is stateful - must be persisted after every encrypt/decrypt.
|
|
30
|
+
* Session is identified by conversationId (derived from topics), enabling
|
|
31
|
+
* correct handling of Safe addresses vs EOAs.
|
|
32
|
+
*/
|
|
33
|
+
export interface RatchetSession {
|
|
34
|
+
/** Primary key: keccak256(sort([topicOut, topicIn])) */
|
|
35
|
+
conversationId: string;
|
|
36
|
+
/** Original handshake-derived outbound topic (immutable reference) */
|
|
37
|
+
topicOutbound: `0x${string}`;
|
|
38
|
+
/** Original handshake-derived inbound topic (immutable reference) */
|
|
39
|
+
topicInbound: `0x${string}`;
|
|
40
|
+
/** My EOA address (for convenience/lookup) */
|
|
41
|
+
myAddress: string;
|
|
42
|
+
/** Their EOA address (for convenience/lookup) */
|
|
43
|
+
contactAddress: string;
|
|
44
|
+
/** Current root key (32 bytes) */
|
|
45
|
+
rootKey: Uint8Array;
|
|
46
|
+
/** My current DH secret key (32 bytes) */
|
|
47
|
+
dhMySecretKey: Uint8Array;
|
|
48
|
+
/** My current DH public key (32 bytes) */
|
|
49
|
+
dhMyPublicKey: Uint8Array;
|
|
50
|
+
/** Their last received DH public key (32 bytes) */
|
|
51
|
+
dhTheirPublicKey: Uint8Array;
|
|
52
|
+
/** Current sending chain key (null until first DH ratchet as sender) */
|
|
53
|
+
sendingChainKey: Uint8Array | null;
|
|
54
|
+
/** Next sending message number (Ns) */
|
|
55
|
+
sendingMsgNumber: number;
|
|
56
|
+
/** Current receiving chain key (null until first message received) */
|
|
57
|
+
receivingChainKey: Uint8Array | null;
|
|
58
|
+
/** Next expected receiving message number (Nr) */
|
|
59
|
+
receivingMsgNumber: number;
|
|
60
|
+
/** Message count in previous sending chain (PN header field) */
|
|
61
|
+
previousChainLength: number;
|
|
62
|
+
/** Stored keys for out-of-order messages */
|
|
63
|
+
skippedKeys: SkippedKey[];
|
|
64
|
+
/** Current outbound topic (may differ from original after ratcheting) */
|
|
65
|
+
currentTopicOutbound: `0x${string}`;
|
|
66
|
+
/** Current inbound topic (may differ from original after ratcheting) */
|
|
67
|
+
currentTopicInbound: `0x${string}`;
|
|
68
|
+
/** Pre-computed next outbound topic (for our next DH ratchet) */
|
|
69
|
+
nextTopicOutbound?: `0x${string}`;
|
|
70
|
+
/** Pre-computed next inbound topic (for their next DH ratchet) */
|
|
71
|
+
nextTopicInbound?: `0x${string}`;
|
|
72
|
+
/** Previous inbound topic (grace period for late messages) */
|
|
73
|
+
previousTopicInbound?: `0x${string}`;
|
|
74
|
+
/** Expiry timestamp for previous topic */
|
|
75
|
+
previousTopicExpiry?: number;
|
|
76
|
+
/** Topic epoch counter */
|
|
77
|
+
topicEpoch: number;
|
|
78
|
+
/** Session creation timestamp */
|
|
79
|
+
createdAt: number;
|
|
80
|
+
/** Last state update timestamp */
|
|
81
|
+
updatedAt: number;
|
|
82
|
+
/** Increments on session reset (internal bookkeeping only) */
|
|
83
|
+
epoch: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Stored key for out-of-order message decryption.
|
|
87
|
+
*/
|
|
88
|
+
export interface SkippedKey {
|
|
89
|
+
/** DH epoch identifier (hex of their DH pubkey) */
|
|
90
|
+
dhPubKeyHex: string;
|
|
91
|
+
/** Message number in that epoch */
|
|
92
|
+
msgNumber: number;
|
|
93
|
+
/** Derived message key (32 bytes) */
|
|
94
|
+
messageKey: Uint8Array;
|
|
95
|
+
/** Creation timestamp for TTL pruning */
|
|
96
|
+
createdAt: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Message header (40 bytes when encoded).
|
|
100
|
+
*/
|
|
101
|
+
export interface MessageHeader {
|
|
102
|
+
/** Sender's current DH ratchet public key (32 bytes) */
|
|
103
|
+
dh: Uint8Array;
|
|
104
|
+
/** Previous chain length - messages in sender's previous sending chain */
|
|
105
|
+
pn: number;
|
|
106
|
+
/** Message number in current sending chain */
|
|
107
|
+
n: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Result of ratchetEncrypt - includes new session state for two-phase commit.
|
|
111
|
+
*/
|
|
112
|
+
export interface EncryptResult {
|
|
113
|
+
/** Updated session state (MUST be persisted) */
|
|
114
|
+
session: RatchetSession;
|
|
115
|
+
header: MessageHeader;
|
|
116
|
+
ciphertext: Uint8Array;
|
|
117
|
+
signature: Uint8Array;
|
|
118
|
+
topic: `0x${string}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Result of ratchetDecrypt.
|
|
122
|
+
*/
|
|
123
|
+
export interface DecryptResult {
|
|
124
|
+
/** Updated session state (MUST be persisted) */
|
|
125
|
+
session: RatchetSession;
|
|
126
|
+
plaintext: Uint8Array;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Parsed binary ratchet payload.
|
|
130
|
+
*/
|
|
131
|
+
export interface ParsedRatchetPayload {
|
|
132
|
+
version: number;
|
|
133
|
+
signature: Uint8Array;
|
|
134
|
+
header: MessageHeader;
|
|
135
|
+
ciphertext: Uint8Array;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parameters for initializing session as responder.
|
|
139
|
+
*/
|
|
140
|
+
export interface InitResponderParams {
|
|
141
|
+
myAddress: string;
|
|
142
|
+
contactAddress: string;
|
|
143
|
+
/** Ephemeral secret used in HandshakeResponse (becomes dhMySecretKey) */
|
|
144
|
+
myResponderEphemeralSecret: Uint8Array;
|
|
145
|
+
myResponderEphemeralPublic: Uint8Array;
|
|
146
|
+
/** Initiator's ephemeral from Handshake event */
|
|
147
|
+
theirHandshakeEphemeralPubKey: Uint8Array;
|
|
148
|
+
topicOutbound: `0x${string}`;
|
|
149
|
+
topicInbound: `0x${string}`;
|
|
150
|
+
/** ML-KEM shared secret for PQ-hybrid handshake */
|
|
151
|
+
kemSecret?: Uint8Array;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parameters for initializing session as initiator.
|
|
155
|
+
*/
|
|
156
|
+
export interface InitInitiatorParams {
|
|
157
|
+
myAddress: string;
|
|
158
|
+
contactAddress: string;
|
|
159
|
+
/** Handshake ephemeral secret (must persist until response arrives) */
|
|
160
|
+
myHandshakeEphemeralSecret: Uint8Array;
|
|
161
|
+
/** Responder's ephemeral from HandshakeResponse (inside encrypted payload) */
|
|
162
|
+
theirResponderEphemeralPubKey: Uint8Array;
|
|
163
|
+
topicOutbound: `0x${string}`;
|
|
164
|
+
topicInbound: `0x${string}`;
|
|
165
|
+
/** ML-KEM shared secret for PQ-hybrid handshake */
|
|
166
|
+
kemSecret?: Uint8Array;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/ratchet/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// packages/sdk/src/ratchet/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Double Ratchet types and constants.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Sanity cap per ratchet step, protects against malicious peers or corrupted state.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_SKIP_PER_MESSAGE = 100000;
|
|
9
|
+
/**
|
|
10
|
+
* When exceeded, oldest keys are pruned.
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_STORED_SKIPPED_KEYS = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* Skipped keys TTL (24 hours is sufficient for reorg tolerance).
|
|
15
|
+
* (this is not message expiry as sequential messages don't use skipped keys)
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_SKIPPED_KEYS_AGE_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Yield to UI every N derivations during large backlog processing.
|
|
20
|
+
*/
|
|
21
|
+
export const SYNC_BATCH_SIZE = 10000;
|
|
22
|
+
/**
|
|
23
|
+
* Binary payload version byte.
|
|
24
|
+
*/
|
|
25
|
+
export const RATCHET_VERSION_V1 = 0x01;
|
|
26
|
+
// Previous topic remains valid for this duration after promotion.
|
|
27
|
+
export const TOPIC_TRANSITION_WINDOW_MS = 5 * 60 * 1000;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AbstractSigner, type Provider, type Signer, type TransactionRequest, type TransactionResponse, type TypedDataDomain, type TypedDataField } from "ethers";
|
|
2
|
+
export interface SafeSessionSignerOptions {
|
|
3
|
+
provider: Provider;
|
|
4
|
+
safeAddress: string;
|
|
5
|
+
/** Safe module enabled on the Safe */
|
|
6
|
+
moduleAddress: string;
|
|
7
|
+
/** only allowed target */
|
|
8
|
+
logChainAddress: string;
|
|
9
|
+
/** The EOA session signer (pays gas, calls the module). Must be connected to a provider. */
|
|
10
|
+
sessionSigner: Signer;
|
|
11
|
+
/** Default: execute(address,uint256,bytes,uint8) */
|
|
12
|
+
moduleAbi?: readonly string[];
|
|
13
|
+
/** Default: "execute" */
|
|
14
|
+
executeMethod?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ethers v6 Signer adapter:
|
|
18
|
+
* - Exposes address = Safe
|
|
19
|
+
* - Intercepts txs to LogChain and routes them through the Safe module
|
|
20
|
+
*
|
|
21
|
+
* sessionSigner is an EOA that directly sends the module tx.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SafeSessionSigner extends AbstractSigner {
|
|
24
|
+
private module;
|
|
25
|
+
private executeMethod;
|
|
26
|
+
private opts;
|
|
27
|
+
constructor(opts: SafeSessionSignerOptions);
|
|
28
|
+
getAddress(): Promise<string>;
|
|
29
|
+
signMessage(message: string | Uint8Array): Promise<string>;
|
|
30
|
+
signTransaction(_tx: TransactionRequest): Promise<string>;
|
|
31
|
+
signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
|
|
32
|
+
sendTransaction(tx: TransactionRequest): Promise<TransactionResponse>;
|
|
33
|
+
connect(provider: Provider): SafeSessionSigner;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=safeSessionSigner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeSessionSigner.d.ts","sourceRoot":"","sources":["../../../src/safeSessionSigner.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EAEd,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,QAAQ,CAAC;AAEhB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,4FAA4F;IAC5F,aAAa,EAAE,MAAM,CAAC;IAEtB,oDAAoD;IACpD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,yBAAyB;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,cAAc;IACnD,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,IAAI,CAA2B;gBAE3B,IAAI,EAAE,wBAAwB;IAkB3B,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1D,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAIzD,aAAa,CAC1B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,EAC5C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC;IASH,eAAe,CAAC,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAsB3E,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB;CAGxD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// packages/sdk/src/safeSessionSigner.ts
|
|
2
|
+
import { AbstractSigner, Contract, } from "ethers";
|
|
3
|
+
const DEFAULT_ABI = [
|
|
4
|
+
"function execute(address to, uint256 value, bytes data, uint8 operation)",
|
|
5
|
+
];
|
|
6
|
+
/**
|
|
7
|
+
* Ethers v6 Signer adapter:
|
|
8
|
+
* - Exposes address = Safe
|
|
9
|
+
* - Intercepts txs to LogChain and routes them through the Safe module
|
|
10
|
+
*
|
|
11
|
+
* sessionSigner is an EOA that directly sends the module tx.
|
|
12
|
+
*/
|
|
13
|
+
export class SafeSessionSigner extends AbstractSigner {
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
super(opts.provider);
|
|
16
|
+
this.opts = opts;
|
|
17
|
+
if (!opts.sessionSigner.provider) {
|
|
18
|
+
throw new Error("SafeSessionSigner: sessionSigner must be connected to a Provider (e.g., new Wallet(pk, provider)).");
|
|
19
|
+
}
|
|
20
|
+
this.module = new Contract(opts.moduleAddress, (opts.moduleAbi ?? DEFAULT_ABI), opts.sessionSigner);
|
|
21
|
+
this.executeMethod = opts.executeMethod ?? "execute";
|
|
22
|
+
}
|
|
23
|
+
async getAddress() {
|
|
24
|
+
return this.opts.safeAddress;
|
|
25
|
+
}
|
|
26
|
+
async signMessage(message) {
|
|
27
|
+
return this.opts.sessionSigner.signMessage(message);
|
|
28
|
+
}
|
|
29
|
+
async signTransaction(_tx) {
|
|
30
|
+
throw new Error("SafeSessionSigner: signTransaction not supported; use sendTransaction().");
|
|
31
|
+
}
|
|
32
|
+
async signTypedData(domain, types, value) {
|
|
33
|
+
// delegate
|
|
34
|
+
const anySigner = this.opts.sessionSigner;
|
|
35
|
+
if (typeof anySigner.signTypedData === "function") {
|
|
36
|
+
return anySigner.signTypedData(domain, types, value);
|
|
37
|
+
}
|
|
38
|
+
throw new Error("SafeSessionSigner: underlying sessionSigner does not support signTypedData.");
|
|
39
|
+
}
|
|
40
|
+
async sendTransaction(tx) {
|
|
41
|
+
if (!tx.to)
|
|
42
|
+
throw new Error("SafeSessionSigner: tx.to is required");
|
|
43
|
+
const to = String(tx.to).toLowerCase();
|
|
44
|
+
const logChain = this.opts.logChainAddress.toLowerCase();
|
|
45
|
+
if (to !== logChain) {
|
|
46
|
+
throw new Error(`SafeSessionSigner: only LogChain txs are supported. Got to=${tx.to}`);
|
|
47
|
+
}
|
|
48
|
+
const data = tx.data ?? "0x";
|
|
49
|
+
const fn = this.module[this.executeMethod];
|
|
50
|
+
if (typeof fn !== "function") {
|
|
51
|
+
throw new Error(`SafeSessionSigner: module execute method "${this.executeMethod}" not found on ${this.opts.moduleAddress}`);
|
|
52
|
+
}
|
|
53
|
+
// operation: 0 = CALL, value: 0
|
|
54
|
+
return fn(this.opts.logChainAddress, 0n, data, 0);
|
|
55
|
+
}
|
|
56
|
+
connect(provider) {
|
|
57
|
+
return new SafeSessionSigner({ ...this.opts, provider });
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/esm/src/send.d.ts
CHANGED
|
@@ -2,49 +2,57 @@ import { Signer } from "ethers";
|
|
|
2
2
|
import nacl from 'tweetnacl';
|
|
3
3
|
import { IdentityKeyPair, IdentityProof } from './types.js';
|
|
4
4
|
import { IExecutor } from './executor.js';
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
executor: IExecutor;
|
|
11
|
-
topic: string;
|
|
12
|
-
message: string;
|
|
13
|
-
recipientPubKey: Uint8Array;
|
|
14
|
-
senderAddress: string;
|
|
15
|
-
senderSignKeyPair: nacl.SignKeyPair;
|
|
16
|
-
timestamp: number;
|
|
17
|
-
}): Promise<any>;
|
|
5
|
+
/** ML-KEM keypair for PQ-hybrid handshake */
|
|
6
|
+
export interface KemKeyPair {
|
|
7
|
+
publicKey: Uint8Array;
|
|
8
|
+
secretKey: Uint8Array;
|
|
9
|
+
}
|
|
18
10
|
/**
|
|
19
11
|
* Initiates an on-chain handshake with unified keys and mandatory identity proof.
|
|
20
12
|
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
13
|
+
*
|
|
14
|
+
* Includes ML-KEM-768 public key for post-quantum hybrid key exchange.
|
|
15
|
+
*
|
|
16
|
+
* @returns Transaction, ephemeral keypair, and KEM keypair (MUST be persisted for session init)
|
|
21
17
|
*/
|
|
22
|
-
export declare function initiateHandshake({ executor, recipientAddress, identityKeyPair,
|
|
18
|
+
export declare function initiateHandshake({ executor, recipientAddress, identityKeyPair, plaintextPayload, identityProof, }: {
|
|
23
19
|
executor: IExecutor;
|
|
24
20
|
recipientAddress: string;
|
|
25
21
|
identityKeyPair: IdentityKeyPair;
|
|
26
|
-
ephemeralPubKey: Uint8Array;
|
|
27
22
|
plaintextPayload: string;
|
|
28
23
|
identityProof: IdentityProof;
|
|
29
|
-
signer
|
|
30
|
-
}): Promise<
|
|
24
|
+
signer?: Signer;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
tx: any;
|
|
27
|
+
ephemeralKeyPair: nacl.BoxKeyPair;
|
|
28
|
+
kemKeyPair: KemKeyPair;
|
|
29
|
+
}>;
|
|
31
30
|
/**
|
|
32
31
|
* Responds to a handshake with unified keys and mandatory identity proof.
|
|
33
32
|
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
33
|
+
*
|
|
34
|
+
* Supports PQ-hybrid handshake: if initiator includes KEM public key,
|
|
35
|
+
* encapsulates a shared secret and includes ciphertext in response.
|
|
36
|
+
*
|
|
37
|
+
* @returns Transaction, tag, salt, ephemeral keys, and KEM secret
|
|
34
38
|
*/
|
|
35
|
-
export declare function respondToHandshake({ executor,
|
|
36
|
-
responderIdentityKeyPair, responderEphemeralKeyPair, note, identityProof, signer, initiatorIdentityPubKey, }: {
|
|
39
|
+
export declare function respondToHandshake({ executor, initiatorEphemeralPubKey, responderIdentityKeyPair, note, identityProof, }: {
|
|
37
40
|
executor: IExecutor;
|
|
38
|
-
|
|
41
|
+
/** Initiator's ephemeral key (32 bytes X25519) OR extended key (1216 bytes: X25519 + ML-KEM) */
|
|
42
|
+
initiatorEphemeralPubKey: Uint8Array;
|
|
39
43
|
responderIdentityKeyPair: IdentityKeyPair;
|
|
40
|
-
responderEphemeralKeyPair?: nacl.BoxKeyPair;
|
|
41
44
|
note?: string;
|
|
42
45
|
identityProof: IdentityProof;
|
|
43
|
-
signer
|
|
44
|
-
initiatorIdentityPubKey?: Uint8Array;
|
|
46
|
+
signer?: Signer;
|
|
45
47
|
}): Promise<{
|
|
46
48
|
tx: any;
|
|
47
|
-
salt: Uint8Array
|
|
49
|
+
salt: Uint8Array;
|
|
48
50
|
tag: `0x${string}`;
|
|
51
|
+
/** Responder's DH ratchet secret - must persist as dhMySecretKey in ratchet session */
|
|
52
|
+
responderEphemeralSecret: Uint8Array;
|
|
53
|
+
/** Responder's DH ratchet public - inside encrypted payload, not on-chain */
|
|
54
|
+
responderEphemeralPublic: Uint8Array;
|
|
55
|
+
/** ML-KEM shared secret (32 bytes) - MUST persist for hybrid KDF, undefined if no KEM in handshake */
|
|
56
|
+
kemSharedSecret?: Uint8Array;
|
|
49
57
|
}>;
|
|
50
58
|
//# sourceMappingURL=send.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/send.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,MAAM,EAEP,MAAM,QAAQ,CAAC;AAChB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/send.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,MAAM,EAEP,MAAM,QAAQ,CAAC;AAChB,OAAO,IAAI,MAAM,WAAW,CAAC;AAQ7B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI1C,6CAA6C;AAC7C,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IACV,EAAE,EAAE,GAAG,CAAC;IACR,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC;IAClC,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC,CA6CD;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,QAAQ,EACR,wBAAwB,EACxB,wBAAwB,EACxB,IAAI,EACJ,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,gGAAgG;IAChG,wBAAwB,EAAE,UAAU,CAAC;IACrC,wBAAwB,EAAE,eAAe,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IACV,EAAE,EAAE,GAAG,CAAC;IACR,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,KAAK,MAAM,EAAE,CAAC;IACnB,uFAAuF;IACvF,wBAAwB,EAAE,UAAU,CAAC;IACrC,6EAA6E;IAC7E,wBAAwB,EAAE,UAAU,CAAC;IACrC,sGAAsG;IACtG,eAAe,CAAC,EAAE,UAAU,CAAC;CAC9B,CAAC,CA+FD"}
|