raz-encrypt 0.3.0 → 1.1.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/dist/core.d.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * included, fs-backed versions of the same logic — this module is the
11
11
  * un-wired core they're built from, not a separate implementation.
12
12
  */
13
- export { decryptFromCore as decryptFrom, encryptForCore as encryptFor, establishSessionInitiatorCore as establishSessionInitiator, establishSessionResponderCore as establishSessionResponder, signCore as sign, UntrustedPartyError, verifyCore as verify, } from "./crypto/hybrid.js";
14
- export type { Certificate, EncryptedBox, InitiatorSessionResult, LogDeps, SessionKeys, SignOptions, TrustDeps, TrustStatus, VerifyDeps, VerifyFailureReason, VerifyResult, } from "./crypto/hybrid.js";
13
+ export { createResponderHandshakeCore as createResponderHandshake, decryptFromCore as decryptFrom, encryptForCore as encryptFor, establishSessionInitiatorCore as establishSessionInitiator, establishSessionResponderCore as establishSessionResponder, HandshakeVerificationError, signCore as sign, UntrustedPartyError, verifyCore as verify, } from "./crypto/hybrid.js";
14
+ export type { Certificate, EncryptedBox, HandshakeSignature, InitiatorHandshakeMessage, InitiatorSessionResult, LogDeps, ResponderHandshakeInit, ResponderHandshakeMessage, ResponderHandshakeSecrets, ResponderSessionResult, SessionKeys, SignOptions, TrustDeps, TrustStatus, VerifyDeps, VerifyFailureReason, VerifyResult, } from "./crypto/hybrid.js";
15
15
  export { generateClassicalKeypair, classicalSharedSecret, classicalSign, classicalVerify } from "./crypto/classical.js";
16
16
  export type { ClassicalKeypair } from "./crypto/classical.js";
17
17
  export { generatePqcKemKeypair, generatePqcSignKeypair, pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify, } from "./crypto/pqc.js";
@@ -20,4 +20,6 @@ export { canonicalize, hashDocument } from "./canonicalJson.js";
20
20
  export { fingerprint } from "./fingerprint.js";
21
21
  export { decryptFrame, encryptFrame, FrameDecryptionError } from "./crypto/frameCipher.js";
22
22
  export type { EncryptedFrame } from "./crypto/frameCipher.js";
23
+ export { CHAIN_INFO_INITIATOR_TO_RESPONDER, CHAIN_INFO_RESPONDER_TO_INITIATOR, ML_KEM_STEP_CADENCE, performEcdhStep, performKemStep, ratchetChainKey, seedChainKey, } from "./crypto/ratchet.js";
24
+ export type { AsymmetricStepResult, ChainStep } from "./crypto/ratchet.js";
23
25
  export type { Identity, PublicCard } from "./identity.js";
package/dist/core.js CHANGED
@@ -10,10 +10,11 @@
10
10
  * included, fs-backed versions of the same logic — this module is the
11
11
  * un-wired core they're built from, not a separate implementation.
12
12
  */
13
- export { decryptFromCore as decryptFrom, encryptForCore as encryptFor, establishSessionInitiatorCore as establishSessionInitiator, establishSessionResponderCore as establishSessionResponder, signCore as sign, UntrustedPartyError, verifyCore as verify, } from "./crypto/hybrid.js";
13
+ export { createResponderHandshakeCore as createResponderHandshake, decryptFromCore as decryptFrom, encryptForCore as encryptFor, establishSessionInitiatorCore as establishSessionInitiator, establishSessionResponderCore as establishSessionResponder, HandshakeVerificationError, signCore as sign, UntrustedPartyError, verifyCore as verify, } from "./crypto/hybrid.js";
14
14
  export { generateClassicalKeypair, classicalSharedSecret, classicalSign, classicalVerify } from "./crypto/classical.js";
15
15
  export { generatePqcKemKeypair, generatePqcSignKeypair, pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify, } from "./crypto/pqc.js";
16
16
  export { canonicalize, hashDocument } from "./canonicalJson.js";
17
17
  export { fingerprint } from "./fingerprint.js";
18
18
  export { decryptFrame, encryptFrame, FrameDecryptionError } from "./crypto/frameCipher.js";
19
+ export { CHAIN_INFO_INITIATOR_TO_RESPONDER, CHAIN_INFO_RESPONDER_TO_INITIATOR, ML_KEM_STEP_CADENCE, performEcdhStep, performKemStep, ratchetChainKey, seedChainKey, } from "./crypto/ratchet.js";
19
20
  //# sourceMappingURL=core.js.map
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,eAAe,IAAI,WAAW,EAC9B,cAAc,IAAI,UAAU,EAC5B,6BAA6B,IAAI,yBAAyB,EAC1D,6BAA6B,IAAI,yBAAyB,EAC1D,QAAQ,IAAI,IAAI,EAChB,mBAAmB,EACnB,UAAU,IAAI,MAAM,GACrB,MAAM,oBAAoB,CAAC;AAe5B,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxH,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,OAAO,EACP,SAAS,GACV,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,4BAA4B,IAAI,wBAAwB,EACxD,eAAe,IAAI,WAAW,EAC9B,cAAc,IAAI,UAAU,EAC5B,6BAA6B,IAAI,yBAAyB,EAC1D,6BAA6B,IAAI,yBAAyB,EAC1D,0BAA0B,EAC1B,QAAQ,IAAI,IAAI,EAChB,mBAAmB,EACnB,UAAU,IAAI,MAAM,GACrB,MAAM,oBAAoB,CAAC;AAqB5B,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxH,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,OAAO,EACP,SAAS,GACV,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAG3F,OAAO,EACL,iCAAiC,EACjC,iCAAiC,EACjC,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,qBAAqB,CAAC"}
@@ -87,31 +87,105 @@ export declare function encryptForCore(identity: Identity, counterpart: PublicCa
87
87
  */
88
88
  export declare function decryptFromCore<T = unknown>(identity: Identity, counterpart: PublicCard, box: EncryptedBox, deps: TrustDeps): Promise<T>;
89
89
  export interface SessionKeys {
90
- /** AES-256-GCM key for frames sent by the initiator (the side that encapsulated), decrypted by the responder. */
90
+ /** AES-256-GCM key for frames sent by the initiator, decrypted by the responder. */
91
91
  initiatorToResponder: Uint8Array;
92
- /** AES-256-GCM key for frames sent by the responder (the side that decapsulated), decrypted by the initiator. */
92
+ /** AES-256-GCM key for frames sent by the responder, decrypted by the initiator. */
93
93
  responderToInitiator: Uint8Array;
94
94
  }
95
- export interface InitiatorSessionResult {
96
- /** Send this to the responder — the only message this handshake requires; the ECDH half needs no message since both sides derive it locally from already-exchanged Public Cards. */
95
+ /** The dual classical+PQ signature over a handshake message's own signed ephemeral payload (see `handshakeSignedMessage`). */
96
+ export interface HandshakeSignature {
97
+ classicalSignature: string;
98
+ pqcSignature: string;
99
+ }
100
+ /**
101
+ * The responder's half of the handshake: one fresh ECDH-P384 public key,
102
+ * one fresh ML-KEM-1024 public key, and a per-handshake nonce, dual-signed
103
+ * with the responder's long-term classical+PQ signing keys.
104
+ */
105
+ export interface ResponderHandshakeMessage {
106
+ ephemeralClassicalPublicKey: string;
107
+ ephemeralKemPublicKey: string;
108
+ nonce: string;
109
+ signature: HandshakeSignature;
110
+ }
111
+ /**
112
+ * The initiator's reply: its own fresh ECDH-P384 public key and nonce
113
+ * (dual-signed), plus the ML-KEM ciphertext encapsulated toward the
114
+ * responder's ephemeral KEM public key (never the responder's long-term
115
+ * one).
116
+ */
117
+ export interface InitiatorHandshakeMessage {
118
+ ephemeralClassicalPublicKey: string;
119
+ nonce: string;
120
+ signature: HandshakeSignature;
97
121
  kemCipherText: string;
122
+ }
123
+ /**
124
+ * The responder's ephemeral secrets plus everything needed to reconstruct
125
+ * its own signed message for the transcript hash later. Returned to the
126
+ * caller only to be threaded straight into `establishSessionResponderCore`
127
+ * for THIS handshake attempt — never persisted, logged, or attached to
128
+ * Identity, and discarded (left for GC) once that call returns.
129
+ */
130
+ export interface ResponderHandshakeSecrets {
131
+ ephemeralClassicalSecretKey: Uint8Array;
132
+ ephemeralKemSecretKey: Uint8Array;
133
+ message: ResponderHandshakeMessage;
134
+ }
135
+ export interface ResponderHandshakeInit {
136
+ message: ResponderHandshakeMessage;
137
+ ephemeral: ResponderHandshakeSecrets;
138
+ }
139
+ export interface InitiatorSessionResult {
140
+ /** Send this to the responder to complete the handshake. */
141
+ message: InitiatorHandshakeMessage;
98
142
  keys: SessionKeys;
143
+ /** HKDF output #3 — proves both sides derived the same combined secret before any chat/file-transfer traffic is trusted. Using it (e.g. as a MAC key over the transcript) is the caller's concern. */
144
+ confirmationKey: Uint8Array;
145
+ /** HKDF output #4 (additive, issue #29) — seeds a two-party ratchet (see ./ratchet.ts) built on top of this handshake. Not a replacement for `keys`/`confirmationKey`, and never used to encrypt a frame directly. */
146
+ rootKey: Uint8Array;
99
147
  }
148
+ export interface ResponderSessionResult {
149
+ keys: SessionKeys;
150
+ confirmationKey: Uint8Array;
151
+ /** See InitiatorSessionResult.rootKey. */
152
+ rootKey: Uint8Array;
153
+ }
154
+ export declare class HandshakeVerificationError extends Error {
155
+ constructor(message: string);
156
+ }
157
+ /**
158
+ * First step of the handshake, run by the connection's responder: generates
159
+ * one fresh, single-use ECDH-P384 keypair and one fresh, single-use
160
+ * ML-KEM-1024 keypair, a per-handshake nonce, and dual-signs all three with
161
+ * the responder's long-term classical+PQ signing keys. `message` is sent to
162
+ * the initiator (it can ride alongside the responder's PublicCard); `ephemeral`
163
+ * must be held in memory only and passed straight into
164
+ * `establishSessionResponderCore` once the initiator's reply arrives, then
165
+ * discarded.
166
+ */
167
+ export declare function createResponderHandshakeCore(identity: Identity): ResponderHandshakeInit;
100
168
  /**
101
- * Establishes a session as the connection's initiator (the side that
102
- * created the WebRTC offer, in raz-encrypt-agnostic terms: whichever side
103
- * owns the single ML-KEM encapsulation this handshake performs). Computes
104
- * the ECDH-P384 secret with `counterpart` and one ML-KEM-1024 encapsulation
105
- * toward their KEM public key, then HKDF-splits the combined secret into two
106
- * directional AES-256-GCM keys. No trust dependency: establishing a session
107
- * never checks anyone's trust status, same as signCore.
169
+ * Establishes a session as the connection's initiator, given the
170
+ * responder's `ResponderHandshakeMessage`. Verifies the responder's dual
171
+ * signature (throws `HandshakeVerificationError` on failure), generates its
172
+ * own fresh, single-use ECDH-P384 keypair and nonce, dual-signs them,
173
+ * encapsulates toward the responder's ephemeral KEM public key, and derives
174
+ * the Session Keys plus a key-confirmation key from the combined ephemeral
175
+ * ECDH + KEM + long-term ECDH secret, salted by a hash of the full
176
+ * handshake transcript. The returned `message` is sent to the responder to
177
+ * complete the handshake. No trust dependency: establishing a session never
178
+ * checks anyone's trust status, same as signCore.
108
179
  */
109
- export declare function establishSessionInitiatorCore(identity: Identity, counterpart: PublicCard): InitiatorSessionResult;
180
+ export declare function establishSessionInitiatorCore(identity: Identity, counterpart: PublicCard, responderMessage: ResponderHandshakeMessage): InitiatorSessionResult;
110
181
  /**
111
- * Establishes a session as the connection's responder: computes the same
112
- * ECDH-P384 secret, decapsulates the initiator's `kemCipherText` with our
113
- * own KEM secret key, and derives the same two directional keys the
114
- * initiator derived.
182
+ * Completes the handshake as the connection's responder, given the
183
+ * `ephemeral` secrets returned by `createResponderHandshakeCore` and the
184
+ * initiator's `InitiatorHandshakeMessage`. Verifies the initiator's dual
185
+ * signature (throws `HandshakeVerificationError` on failure), decapsulates
186
+ * the KEM ciphertext with the ephemeral KEM secret key, computes the
187
+ * ephemeral ECDH secret, and derives the same Session Keys and
188
+ * confirmation key the initiator derived.
115
189
  */
116
- export declare function establishSessionResponderCore(identity: Identity, counterpart: PublicCard, kemCipherText: string): SessionKeys;
190
+ export declare function establishSessionResponderCore(identity: Identity, counterpart: PublicCard, ephemeral: ResponderHandshakeSecrets, initiatorMessage: InitiatorHandshakeMessage): ResponderSessionResult;
117
191
  export {};
@@ -3,8 +3,8 @@ import { hkdf } from "@noble/hashes/hkdf.js";
3
3
  import { sha384 } from "@noble/hashes/sha2.js";
4
4
  import { bytesToHex, concatBytes, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
5
5
  import { canonicalize, hashDocument } from "../canonicalJson.js";
6
- import { classicalSharedSecret, classicalSign, classicalVerify } from "./classical.js";
7
- import { pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify } from "./pqc.js";
6
+ import { classicalSharedSecret, classicalSign, classicalVerify, generateClassicalKeypair } from "./classical.js";
7
+ import { generatePqcKemKeypair, pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify } from "./pqc.js";
8
8
  // No import of ../store.js or ../trust.js here (only their LogEntry TYPE,
9
9
  // which is erased at compile time) — this file must stay resolvable by a
10
10
  // browser bundler on its own. The node:fs-backed default wiring for
@@ -139,42 +139,175 @@ export async function decryptFromCore(identity, counterpart, box, deps) {
139
139
  //
140
140
  // encryptForCore/decryptFromCore above do a full ECDH + ML-KEM encapsulation
141
141
  // PER CALL — fine for a one-off document, prohibitively expensive if called
142
- // once per 16 KiB chunk of a large file transfer. The functions below run
143
- // that hybrid exchange ONCE per connection and hand back a pair of
144
- // directional keys the caller reuses (via a separate per-frame cipher, not
145
- // implemented here) for as many messages as the connection carries.
146
- const SESSION_INFO_INITIATOR_TO_RESPONDER = utf8ToBytes("raz-encrypt-session-v1|initiator-to-responder");
147
- const SESSION_INFO_RESPONDER_TO_INITIATOR = utf8ToBytes("raz-encrypt-session-v1|responder-to-initiator");
148
- function deriveSessionKeys(ecdhSecret, kemSecret) {
149
- const ikm = concatBytes(ecdhSecret, kemSecret);
142
+ // once per 16 KiB chunk of a large file transfer. The functions below run a
143
+ // hybrid exchange ONCE per connection and hand back a pair of directional
144
+ // keys the caller reuses (via a separate per-frame cipher, not implemented
145
+ // here) for as many messages as the connection carries.
146
+ //
147
+ // This handshake is ephemeral and authenticated (see docs/adr/0006, which
148
+ // supersedes ADR 0004's single-encapsulation design): each side generates a
149
+ // fresh, single-use ECDH-P384 keypair per session attempt (the responder
150
+ // also a fresh, single-use ML-KEM-1024 keypair), signs its own ephemeral
151
+ // public key(s) plus a per-handshake nonce with its long-term classical and
152
+ // PQ signing keys (the same dual-signature mechanism signCore/verifyCore use
153
+ // for documents), and the counterpart verifies both signatures before
154
+ // trusting the signed ephemeral key(s). The combined secret folds together
155
+ // the fresh ephemeral ECDH secret, the KEM-encapsulated secret, AND the
156
+ // existing long-term (static) ECDH secret (defense-in-depth; forward
157
+ // secrecy itself rests entirely on the ephemeral secrets being discarded).
158
+ // None of the ephemeral secret keys are ever attached to Identity,
159
+ // persisted, or logged — they live only in the return value of
160
+ // createResponderHandshakeCore and the stack of establishSessionInitiatorCore,
161
+ // for the lifetime of one handshake attempt.
162
+ //
163
+ // Wire shape: the responder generates its ephemeral keys first and sends
164
+ // ResponderHandshakeMessage (this can ride alongside the responder's
165
+ // PublicCard, since nothing in it depends on anything from the initiator);
166
+ // the initiator replies with InitiatorHandshakeMessage, which is the only
167
+ // message carrying the KEM ciphertext (the initiator is the one who
168
+ // encapsulates, same initiator/responder asymmetry ADR 0004 already
169
+ // established); the responder completes the handshake from that reply.
170
+ const SESSION_INFO_INITIATOR_TO_RESPONDER = utf8ToBytes("raz-encrypt-session-v2|initiator-to-responder");
171
+ const SESSION_INFO_RESPONDER_TO_INITIATOR = utf8ToBytes("raz-encrypt-session-v2|responder-to-initiator");
172
+ const SESSION_INFO_KEY_CONFIRMATION = utf8ToBytes("raz-encrypt-session-v2|key-confirmation");
173
+ // A fourth, additional HKDF output (issue #29/raz-encrypt #29) from the exact
174
+ // same combined input key material and transcript salt as the three outputs
175
+ // above — NOT a repurposing of any of them. This is the seed for a two-party
176
+ // ratchet (see ./ratchet.ts) built on top of the handshake, never itself used
177
+ // to encrypt a frame directly.
178
+ const SESSION_INFO_ROOT_KEY = utf8ToBytes("raz-encrypt-session-v2|root-key");
179
+ export class HandshakeVerificationError extends Error {
180
+ constructor(message) {
181
+ super(message);
182
+ this.name = "HandshakeVerificationError";
183
+ }
184
+ }
185
+ /** Reconstructs the counterpart-facing PublicCard shape from an Identity, without importing identity.js (which pulls in node:fs) — keeps this module browser-safe. */
186
+ function publicCardOf(identity) {
150
187
  return {
151
- initiatorToResponder: hkdf(sha384, ikm, undefined, SESSION_INFO_INITIATOR_TO_RESPONDER, 32),
152
- responderToInitiator: hkdf(sha384, ikm, undefined, SESSION_INFO_RESPONDER_TO_INITIATOR, 32),
188
+ alias: identity.alias,
189
+ classicalPublicKey: bytesToHex(identity.classical.publicKey),
190
+ pqcSignPublicKey: bytesToHex(identity.pqcSign.publicKey),
191
+ pqcKemPublicKey: bytesToHex(identity.pqcKem.publicKey),
192
+ };
193
+ }
194
+ /** The exact bytes a handshake signature is computed over — mirrors `signedMessage`'s canonicalize-then-sign-directly shape, applied to ephemeral handshake material instead of a document. */
195
+ function handshakeSignedMessage(payload) {
196
+ return utf8ToBytes(canonicalize(payload));
197
+ }
198
+ function signHandshakePayload(identity, payload) {
199
+ const message = handshakeSignedMessage(payload);
200
+ return {
201
+ classicalSignature: bytesToHex(classicalSign(message, identity.classical.secretKey)),
202
+ pqcSignature: bytesToHex(pqcSign(message, identity.pqcSign.secretKey)),
203
+ };
204
+ }
205
+ /** Throws HandshakeVerificationError unless BOTH the classical and PQ signatures check out — mirrors verifyCore's all-or-nothing dual-signature requirement. */
206
+ function verifyHandshakeSignature(signer, payload, signature) {
207
+ const message = handshakeSignedMessage(payload);
208
+ const classicalOk = classicalVerify(hexToBytes(signature.classicalSignature), message, hexToBytes(signer.classicalPublicKey));
209
+ if (!classicalOk)
210
+ throw new HandshakeVerificationError(`Handshake rejected: classical signature from "${signer.alias}" did not verify.`);
211
+ const pqcOk = pqcVerify(hexToBytes(signature.pqcSignature), message, hexToBytes(signer.pqcSignPublicKey));
212
+ if (!pqcOk)
213
+ throw new HandshakeVerificationError(`Handshake rejected: PQC signature from "${signer.alias}" did not verify.`);
214
+ }
215
+ function handshakeTranscriptSalt(initiatorCard, responderCard, responderMessage, initiatorEphemeral) {
216
+ return sha384(utf8ToBytes(canonicalize({ initiatorCard, responderCard, responderMessage, initiatorEphemeral })));
217
+ }
218
+ function deriveSessionSecrets(ephemeralEcdhSecret, kemSecret, staticEcdhSecret, transcriptSalt) {
219
+ const ikm = concatBytes(ephemeralEcdhSecret, kemSecret, staticEcdhSecret);
220
+ return {
221
+ keys: {
222
+ initiatorToResponder: hkdf(sha384, ikm, transcriptSalt, SESSION_INFO_INITIATOR_TO_RESPONDER, 32),
223
+ responderToInitiator: hkdf(sha384, ikm, transcriptSalt, SESSION_INFO_RESPONDER_TO_INITIATOR, 32),
224
+ },
225
+ confirmationKey: hkdf(sha384, ikm, transcriptSalt, SESSION_INFO_KEY_CONFIRMATION, 32),
226
+ rootKey: hkdf(sha384, ikm, transcriptSalt, SESSION_INFO_ROOT_KEY, 32),
153
227
  };
154
228
  }
155
229
  /**
156
- * Establishes a session as the connection's initiator (the side that
157
- * created the WebRTC offer, in raz-encrypt-agnostic terms: whichever side
158
- * owns the single ML-KEM encapsulation this handshake performs). Computes
159
- * the ECDH-P384 secret with `counterpart` and one ML-KEM-1024 encapsulation
160
- * toward their KEM public key, then HKDF-splits the combined secret into two
161
- * directional AES-256-GCM keys. No trust dependency: establishing a session
162
- * never checks anyone's trust status, same as signCore.
230
+ * First step of the handshake, run by the connection's responder: generates
231
+ * one fresh, single-use ECDH-P384 keypair and one fresh, single-use
232
+ * ML-KEM-1024 keypair, a per-handshake nonce, and dual-signs all three with
233
+ * the responder's long-term classical+PQ signing keys. `message` is sent to
234
+ * the initiator (it can ride alongside the responder's PublicCard); `ephemeral`
235
+ * must be held in memory only and passed straight into
236
+ * `establishSessionResponderCore` once the initiator's reply arrives, then
237
+ * discarded.
163
238
  */
164
- export function establishSessionInitiatorCore(identity, counterpart) {
165
- const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
166
- const { cipherText, sharedSecret: kemSecret } = pqcEncapsulate(hexToBytes(counterpart.pqcKemPublicKey));
167
- return { kemCipherText: bytesToHex(cipherText), keys: deriveSessionKeys(ecdhSecret, kemSecret) };
239
+ export function createResponderHandshakeCore(identity) {
240
+ const ephemeralClassical = generateClassicalKeypair();
241
+ const ephemeralKem = generatePqcKemKeypair();
242
+ const nonce = bytesToHex(randomBytes(32));
243
+ const payload = {
244
+ ephemeralClassicalPublicKey: bytesToHex(ephemeralClassical.publicKey),
245
+ ephemeralKemPublicKey: bytesToHex(ephemeralKem.publicKey),
246
+ nonce,
247
+ };
248
+ const signature = signHandshakePayload(identity, payload);
249
+ const message = { ...payload, signature };
250
+ return {
251
+ message,
252
+ ephemeral: {
253
+ ephemeralClassicalSecretKey: ephemeralClassical.secretKey,
254
+ ephemeralKemSecretKey: ephemeralKem.secretKey,
255
+ message,
256
+ },
257
+ };
168
258
  }
169
259
  /**
170
- * Establishes a session as the connection's responder: computes the same
171
- * ECDH-P384 secret, decapsulates the initiator's `kemCipherText` with our
172
- * own KEM secret key, and derives the same two directional keys the
173
- * initiator derived.
260
+ * Establishes a session as the connection's initiator, given the
261
+ * responder's `ResponderHandshakeMessage`. Verifies the responder's dual
262
+ * signature (throws `HandshakeVerificationError` on failure), generates its
263
+ * own fresh, single-use ECDH-P384 keypair and nonce, dual-signs them,
264
+ * encapsulates toward the responder's ephemeral KEM public key, and derives
265
+ * the Session Keys plus a key-confirmation key from the combined ephemeral
266
+ * ECDH + KEM + long-term ECDH secret, salted by a hash of the full
267
+ * handshake transcript. The returned `message` is sent to the responder to
268
+ * complete the handshake. No trust dependency: establishing a session never
269
+ * checks anyone's trust status, same as signCore.
174
270
  */
175
- export function establishSessionResponderCore(identity, counterpart, kemCipherText) {
176
- const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
177
- const kemSecret = pqcDecapsulate(hexToBytes(kemCipherText), identity.pqcKem.secretKey);
178
- return deriveSessionKeys(ecdhSecret, kemSecret);
271
+ export function establishSessionInitiatorCore(identity, counterpart, responderMessage) {
272
+ verifyHandshakeSignature(counterpart, {
273
+ ephemeralClassicalPublicKey: responderMessage.ephemeralClassicalPublicKey,
274
+ ephemeralKemPublicKey: responderMessage.ephemeralKemPublicKey,
275
+ nonce: responderMessage.nonce,
276
+ }, responderMessage.signature);
277
+ const ephemeralClassical = generateClassicalKeypair();
278
+ const nonce = bytesToHex(randomBytes(32));
279
+ const payload = { ephemeralClassicalPublicKey: bytesToHex(ephemeralClassical.publicKey), nonce };
280
+ const signature = signHandshakePayload(identity, payload);
281
+ const ephemeralEcdhSecret = classicalSharedSecret(ephemeralClassical.secretKey, hexToBytes(responderMessage.ephemeralClassicalPublicKey));
282
+ const { cipherText, sharedSecret: kemSecret } = pqcEncapsulate(hexToBytes(responderMessage.ephemeralKemPublicKey));
283
+ const staticEcdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
284
+ const transcriptSalt = handshakeTranscriptSalt(publicCardOf(identity), counterpart, responderMessage, payload);
285
+ const { keys, confirmationKey, rootKey } = deriveSessionSecrets(ephemeralEcdhSecret, kemSecret, staticEcdhSecret, transcriptSalt);
286
+ return {
287
+ message: { ...payload, signature, kemCipherText: bytesToHex(cipherText) },
288
+ keys,
289
+ confirmationKey,
290
+ rootKey,
291
+ };
292
+ }
293
+ /**
294
+ * Completes the handshake as the connection's responder, given the
295
+ * `ephemeral` secrets returned by `createResponderHandshakeCore` and the
296
+ * initiator's `InitiatorHandshakeMessage`. Verifies the initiator's dual
297
+ * signature (throws `HandshakeVerificationError` on failure), decapsulates
298
+ * the KEM ciphertext with the ephemeral KEM secret key, computes the
299
+ * ephemeral ECDH secret, and derives the same Session Keys and
300
+ * confirmation key the initiator derived.
301
+ */
302
+ export function establishSessionResponderCore(identity, counterpart, ephemeral, initiatorMessage) {
303
+ verifyHandshakeSignature(counterpart, { ephemeralClassicalPublicKey: initiatorMessage.ephemeralClassicalPublicKey, nonce: initiatorMessage.nonce }, initiatorMessage.signature);
304
+ const ephemeralEcdhSecret = classicalSharedSecret(ephemeral.ephemeralClassicalSecretKey, hexToBytes(initiatorMessage.ephemeralClassicalPublicKey));
305
+ const kemSecret = pqcDecapsulate(hexToBytes(initiatorMessage.kemCipherText), ephemeral.ephemeralKemSecretKey);
306
+ const staticEcdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
307
+ const transcriptSalt = handshakeTranscriptSalt(counterpart, publicCardOf(identity), ephemeral.message, {
308
+ ephemeralClassicalPublicKey: initiatorMessage.ephemeralClassicalPublicKey,
309
+ nonce: initiatorMessage.nonce,
310
+ });
311
+ return deriveSessionSecrets(ephemeralEcdhSecret, kemSecret, staticEcdhSecret, transcriptSalt);
179
312
  }
180
313
  //# sourceMappingURL=hybrid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../../src/crypto/hybrid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../../src/crypto/hybrid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { type ClassicalKeypair } from "./classical.js";
2
+ /** Domain-separation label for `seedChainKey`'s `direction` parameter — mirrors hybrid.ts's SESSION_INFO_* labels, applied to chain keys instead of Session Keys. */
3
+ export declare const CHAIN_INFO_INITIATOR_TO_RESPONDER: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
4
+ /** See CHAIN_INFO_INITIATOR_TO_RESPONDER. */
5
+ export declare const CHAIN_INFO_RESPONDER_TO_INITIATOR: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
6
+ /**
7
+ * How many control-type messages (chat/file-lifecycle; file-chunk/binary
8
+ * traffic is never counted), summed across BOTH directions combined, trigger
9
+ * one ML-KEM-1024 asymmetric step (see `performKemStep`). Named and exported
10
+ * so the recovery-speed-vs-overhead trade-off is visible and adjustable in
11
+ * one place, rather than a magic number buried in `secureConnection.ts`'s
12
+ * (ticket #30's) triggering logic — which is also where the actual counting
13
+ * happens; this constant is consumed there, not here.
14
+ */
15
+ export declare const ML_KEM_STEP_CADENCE = 20;
16
+ /**
17
+ * Seeds a per-direction chain key from the handshake's `rootKey` output (see
18
+ * hybrid.ts's InitiatorSessionResult/ResponderSessionResult). Pass one of
19
+ * CHAIN_INFO_INITIATOR_TO_RESPONDER / CHAIN_INFO_RESPONDER_TO_INITIATOR as
20
+ * `direction` — the two directions always seed distinct chain keys from the
21
+ * same root key, mirroring how a Session's two directional keys are split
22
+ * from one combined secret.
23
+ */
24
+ export declare function seedChainKey(rootKey: Uint8Array, direction: Uint8Array): Uint8Array;
25
+ /** The result of advancing a chain key by one message: the single-use key for THIS message, and the chain key's new value for the next one. */
26
+ export interface ChainStep {
27
+ /** Single-use — encrypt/decrypt exactly one frame with this, then discard it. Never reused. */
28
+ messageKey: Uint8Array;
29
+ /** Replaces the chain key passed into `ratchetChainKey`. The old chain key must not be kept. */
30
+ nextChainKey: Uint8Array;
31
+ }
32
+ /**
33
+ * Derives a single-use message key from `chainKey` AND advances the chain to
34
+ * its next value, in the same operation — so by the time this call returns,
35
+ * the OLD chain key is gone (the caller must not retain it) and a fresh
36
+ * `nextChainKey` stands in its place. Both outputs are one-way functions of
37
+ * `chainKey` (HKDF-SHA384 with distinct, fixed info labels): holding
38
+ * `nextChainKey` alone never lets you recover `chainKey` or `messageKey`
39
+ * (forward secrecy at the symmetric-chain level — see
40
+ * test/ratchet.test.ts's forward-secrecy suite).
41
+ */
42
+ export declare function ratchetChainKey(chainKey: Uint8Array): ChainStep;
43
+ /** The result of an asymmetric step (ECDH or ML-KEM): a new root key, and a freshly re-seeded SENDING chain key for the side that performed the step. The other direction's chain is untouched until it performs its own step (Double-Ratchet-style asymmetry). */
44
+ export interface AsymmetricStepResult {
45
+ rootKey: Uint8Array;
46
+ sendingChainKey: Uint8Array;
47
+ }
48
+ /**
49
+ * The ECDH-P384 asymmetric step: mixes a fresh ephemeral ECDH shared secret
50
+ * into the current root key (producing a new root key) and re-seeds the
51
+ * stepping side's own sending chain key from it. `ephemeral` is a freshly
52
+ * generated keypair (reuse `generateClassicalKeypair` from ./classical.js —
53
+ * this function does not generate keys itself); `counterpartEphemeralPublicKey`
54
+ * is the counterpart's most recently received ephemeral public key.
55
+ *
56
+ * Deciding WHEN to call this ("a reply after receiving") is out of scope —
57
+ * that is ticket #30's `secureConnection.ts` connection-level state.
58
+ */
59
+ export declare function performEcdhStep(rootKey: Uint8Array, ephemeral: ClassicalKeypair, counterpartEphemeralPublicKey: Uint8Array, sendingDirection: Uint8Array): AsymmetricStepResult;
60
+ /**
61
+ * The ML-KEM-1024 asymmetric step: mixes an ML-KEM-encapsulated (sender) or
62
+ * decapsulated (receiver) shared secret — `kemSecret`, from
63
+ * `pqcEncapsulate`/`pqcDecapsulate` in ./pqc.js, which this function does not
64
+ * call itself — into the current root key, and re-seeds the stepping side's
65
+ * sending chain key from the result. Cadence (how often this fires) is
66
+ * `ML_KEM_STEP_CADENCE`, counted and triggered by ticket #30, not here.
67
+ */
68
+ export declare function performKemStep(rootKey: Uint8Array, kemSecret: Uint8Array, sendingDirection: Uint8Array): AsymmetricStepResult;
@@ -0,0 +1,89 @@
1
+ import { hkdf } from "@noble/hashes/hkdf.js";
2
+ import { sha384 } from "@noble/hashes/sha2.js";
3
+ import { concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
4
+ import { classicalSharedSecret } from "./classical.js";
5
+ // The two-party ratchet (issue #17/#29): built ON TOP OF the handshake in
6
+ // ./hybrid.ts, seeded from its additional `rootKey` output. See docs/adr for
7
+ // the full design and its deliberate, explicit contrast with Signal's Double
8
+ // Ratchet (no skipped-message-key cache, no out-of-order handling, no
9
+ // group/multi-device fan-out — this stays two-party-only and in-order-only,
10
+ // matching this project's always-ordered, always-1:1 transport).
11
+ //
12
+ // Everything here is a PURE function: no "when do I step" logic, no
13
+ // connection-level state, no wire format. The caller (`secureConnection.ts`'s
14
+ // Envelope layer, a later, currently-blocked ticket) drives all of that.
15
+ const CHAIN_INFO_PREFIX = utf8ToBytes("raz-encrypt-ratchet-v1|chain|");
16
+ const MESSAGE_KEY_INFO = utf8ToBytes("raz-encrypt-ratchet-v1|message-key");
17
+ const CHAIN_ADVANCE_INFO = utf8ToBytes("raz-encrypt-ratchet-v1|chain-advance");
18
+ const ROOT_KEY_ECDH_STEP_INFO = utf8ToBytes("raz-encrypt-ratchet-v1|root-key|ecdh-step");
19
+ const ROOT_KEY_KEM_STEP_INFO = utf8ToBytes("raz-encrypt-ratchet-v1|root-key|kem-step");
20
+ /** Domain-separation label for `seedChainKey`'s `direction` parameter — mirrors hybrid.ts's SESSION_INFO_* labels, applied to chain keys instead of Session Keys. */
21
+ export const CHAIN_INFO_INITIATOR_TO_RESPONDER = concatBytes(CHAIN_INFO_PREFIX, utf8ToBytes("initiator-to-responder"));
22
+ /** See CHAIN_INFO_INITIATOR_TO_RESPONDER. */
23
+ export const CHAIN_INFO_RESPONDER_TO_INITIATOR = concatBytes(CHAIN_INFO_PREFIX, utf8ToBytes("responder-to-initiator"));
24
+ /**
25
+ * How many control-type messages (chat/file-lifecycle; file-chunk/binary
26
+ * traffic is never counted), summed across BOTH directions combined, trigger
27
+ * one ML-KEM-1024 asymmetric step (see `performKemStep`). Named and exported
28
+ * so the recovery-speed-vs-overhead trade-off is visible and adjustable in
29
+ * one place, rather than a magic number buried in `secureConnection.ts`'s
30
+ * (ticket #30's) triggering logic — which is also where the actual counting
31
+ * happens; this constant is consumed there, not here.
32
+ */
33
+ export const ML_KEM_STEP_CADENCE = 20;
34
+ /**
35
+ * Seeds a per-direction chain key from the handshake's `rootKey` output (see
36
+ * hybrid.ts's InitiatorSessionResult/ResponderSessionResult). Pass one of
37
+ * CHAIN_INFO_INITIATOR_TO_RESPONDER / CHAIN_INFO_RESPONDER_TO_INITIATOR as
38
+ * `direction` — the two directions always seed distinct chain keys from the
39
+ * same root key, mirroring how a Session's two directional keys are split
40
+ * from one combined secret.
41
+ */
42
+ export function seedChainKey(rootKey, direction) {
43
+ return hkdf(sha384, rootKey, undefined, direction, 32);
44
+ }
45
+ /**
46
+ * Derives a single-use message key from `chainKey` AND advances the chain to
47
+ * its next value, in the same operation — so by the time this call returns,
48
+ * the OLD chain key is gone (the caller must not retain it) and a fresh
49
+ * `nextChainKey` stands in its place. Both outputs are one-way functions of
50
+ * `chainKey` (HKDF-SHA384 with distinct, fixed info labels): holding
51
+ * `nextChainKey` alone never lets you recover `chainKey` or `messageKey`
52
+ * (forward secrecy at the symmetric-chain level — see
53
+ * test/ratchet.test.ts's forward-secrecy suite).
54
+ */
55
+ export function ratchetChainKey(chainKey) {
56
+ return {
57
+ messageKey: hkdf(sha384, chainKey, undefined, MESSAGE_KEY_INFO, 32),
58
+ nextChainKey: hkdf(sha384, chainKey, undefined, CHAIN_ADVANCE_INFO, 32),
59
+ };
60
+ }
61
+ /**
62
+ * The ECDH-P384 asymmetric step: mixes a fresh ephemeral ECDH shared secret
63
+ * into the current root key (producing a new root key) and re-seeds the
64
+ * stepping side's own sending chain key from it. `ephemeral` is a freshly
65
+ * generated keypair (reuse `generateClassicalKeypair` from ./classical.js —
66
+ * this function does not generate keys itself); `counterpartEphemeralPublicKey`
67
+ * is the counterpart's most recently received ephemeral public key.
68
+ *
69
+ * Deciding WHEN to call this ("a reply after receiving") is out of scope —
70
+ * that is ticket #30's `secureConnection.ts` connection-level state.
71
+ */
72
+ export function performEcdhStep(rootKey, ephemeral, counterpartEphemeralPublicKey, sendingDirection) {
73
+ const sharedSecret = classicalSharedSecret(ephemeral.secretKey, counterpartEphemeralPublicKey);
74
+ const newRootKey = hkdf(sha384, concatBytes(rootKey, sharedSecret), undefined, ROOT_KEY_ECDH_STEP_INFO, 32);
75
+ return { rootKey: newRootKey, sendingChainKey: seedChainKey(newRootKey, sendingDirection) };
76
+ }
77
+ /**
78
+ * The ML-KEM-1024 asymmetric step: mixes an ML-KEM-encapsulated (sender) or
79
+ * decapsulated (receiver) shared secret — `kemSecret`, from
80
+ * `pqcEncapsulate`/`pqcDecapsulate` in ./pqc.js, which this function does not
81
+ * call itself — into the current root key, and re-seeds the stepping side's
82
+ * sending chain key from the result. Cadence (how often this fires) is
83
+ * `ML_KEM_STEP_CADENCE`, counted and triggered by ticket #30, not here.
84
+ */
85
+ export function performKemStep(rootKey, kemSecret, sendingDirection) {
86
+ const newRootKey = hkdf(sha384, concatBytes(rootKey, kemSecret), undefined, ROOT_KEY_KEM_STEP_INFO, 32);
87
+ return { rootKey: newRootKey, sendingChainKey: seedChainKey(newRootKey, sendingDirection) };
88
+ }
89
+ //# sourceMappingURL=ratchet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratchet.js","sourceRoot":"","sources":["../../src/crypto/ratchet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAyB,MAAM,gBAAgB,CAAC;AAE9E,0EAA0E;AAC1E,6EAA6E;AAC7E,6EAA6E;AAC7E,sEAAsE;AACtE,4EAA4E;AAC5E,iEAAiE;AACjE,EAAE;AACF,oEAAoE;AACpE,8EAA8E;AAC9E,yEAAyE;AAEzE,MAAM,iBAAiB,GAAG,WAAW,CAAC,+BAA+B,CAAC,CAAC;AACvE,MAAM,gBAAgB,GAAG,WAAW,CAAC,oCAAoC,CAAC,CAAC;AAC3E,MAAM,kBAAkB,GAAG,WAAW,CAAC,sCAAsC,CAAC,CAAC;AAC/E,MAAM,uBAAuB,GAAG,WAAW,CAAC,2CAA2C,CAAC,CAAC;AACzF,MAAM,sBAAsB,GAAG,WAAW,CAAC,0CAA0C,CAAC,CAAC;AAEvF,qKAAqK;AACrK,MAAM,CAAC,MAAM,iCAAiC,GAAG,WAAW,CAAC,iBAAiB,EAAE,WAAW,CAAC,wBAAwB,CAAC,CAAC,CAAC;AACvH,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iCAAiC,GAAG,WAAW,CAAC,iBAAiB,EAAE,WAAW,CAAC,wBAAwB,CAAC,CAAC,CAAC;AAEvH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAmB,EAAE,SAAqB;IACrE,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AACzD,CAAC;AAUD;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,QAAoB;IAClD,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,CAAC;QACnE,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,CAAC;KACxE,CAAC;AACJ,CAAC;AAQD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAmB,EACnB,SAA2B,EAC3B,6BAAyC,EACzC,gBAA4B;IAE5B,MAAM,YAAY,GAAG,qBAAqB,CAAC,SAAS,CAAC,SAAS,EAAE,6BAA6B,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;IAC5G,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAAmB,EAAE,SAAqB,EAAE,gBAA4B;IACrG,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,CAAC,CAAC;IACxG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC;AAC9F,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raz-encrypt",
3
- "version": "0.3.0",
3
+ "version": "1.1.0",
4
4
  "description": "Local-first, hybrid classical+post-quantum signing and verification for two consenting parties. No server, no database required.",
5
5
  "license": "MIT",
6
6
  "type": "module",