@evolu/common 6.0.1-preview.4 → 6.0.1-preview.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.
@@ -16,9 +16,12 @@
16
16
  * assert(true, "true is not true"); // no-op
17
17
  * assert(false, "true is not true"); // throws Error
18
18
  *
19
- * const size = fooSize - buffer.getLength();
20
- * // Ensure required type
21
- * assert(PositiveInt.is(size), "size is not positive int");
19
+ * const length = buffer.getLength();
20
+ * // We know length is logically non-negative, but TypeScript doesn't
21
+ * assert(
22
+ * NonNegativeInt.is(length),
23
+ * "buffer length should be non-negative",
24
+ * );
22
25
  * ```
23
26
  */
24
27
  export declare const assert: (condition: unknown, message: string) => asserts condition;
@@ -1 +1 @@
1
- {"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAK3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,CAO3E"}
1
+ {"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAK3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,CAO3E"}
@@ -16,9 +16,12 @@
16
16
  * assert(true, "true is not true"); // no-op
17
17
  * assert(false, "true is not true"); // throws Error
18
18
  *
19
- * const size = fooSize - buffer.getLength();
20
- * // Ensure required type
21
- * assert(PositiveInt.is(size), "size is not positive int");
19
+ * const length = buffer.getLength();
20
+ * // We know length is logically non-negative, but TypeScript doesn't
21
+ * assert(
22
+ * NonNegativeInt.is(length),
23
+ * "buffer length should be non-negative",
24
+ * );
22
25
  * ```
23
26
  */
24
27
  export const assert = (condition, message) => {
@@ -79,4 +79,15 @@ export declare const padmePaddedLength: (length: NonNegativeInt) => NonNegativeI
79
79
  * {@link padmePaddedLength}.
80
80
  */
81
81
  export declare const padmePaddingLength: (length: NonNegativeInt) => NonNegativeInt;
82
+ /**
83
+ * Performs a timing-safe comparison of two Uint8Arrays. Returns true if they
84
+ * are equal, false otherwise. Takes constant time regardless of where the
85
+ * arrays differ.
86
+ *
87
+ * @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
88
+ */
89
+ export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
90
+ export interface TimingSafeEqualDep {
91
+ readonly timingSafeEqual: TimingSafeEqual;
92
+ }
82
93
  //# sourceMappingURL=Crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../../src/Crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAc,MAAM,EAAW,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,EAAE,EAEF,QAAQ,EACR,cAAc,EAEf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;CAC/C;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,EAAE,iBACS,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,eAAO,MAAM,qBAAqB,EAAE,cACe,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,GAAI,UAAU,QAAQ,KAAG,YACR,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,UAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,EAUF,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,aAAa,8fAAiD,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAAI,MAAM,YAAY,KAAG,aACS,CAAC;AAEnE,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IAErC,QAAQ,CAAC,OAAO,EAAE,CAChB,SAAS,EAAE,UAAU,EACrB,aAAa,EAAE,aAAa,KACzB;QACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;KACjC,CAAC;IAEF,QAAQ,CAAC,OAAO,EAAE,CAChB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,UAAU,KACd,MAAM,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,6BAA6B,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,oBAAoB,KACzB,eAyBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,cAO1D,CAAC;AAEF;;;GAGG;AAEH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,cAAc,KAAG,cAE3D,CAAC"}
1
+ {"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../../src/Crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAc,MAAM,EAAW,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,EAAE,EAEF,QAAQ,EACR,cAAc,EAEf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;CAC/C;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,EAAE,iBACS,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,eAAO,MAAM,qBAAqB,EAAE,cACe,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,GAAI,UAAU,QAAQ,KAAG,YACR,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,UAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,EAUF,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,aAAa,8fAAiD,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAAI,MAAM,YAAY,KAAG,aACS,CAAC;AAEnE,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IAErC,QAAQ,CAAC,OAAO,EAAE,CAChB,SAAS,EAAE,UAAU,EACrB,aAAa,EAAE,aAAa,KACzB;QACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;KACjC,CAAC;IAEF,QAAQ,CAAC,OAAO,EAAE,CAChB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,UAAU,KACd,MAAM,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,6BAA6B,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,oBAAoB,KACzB,eAyBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,cAO1D,CAAC;AAEF;;;GAGG;AAEH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,cAAc,KAAG,cAE3D,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C"}
@@ -720,6 +720,7 @@ const createClientStorage = (deps) => (options) => {
720
720
  const storage = {
721
721
  ...sqliteStorageBase.value,
722
722
  validateWriteKey: constFalse,
723
+ setWriteKey: constFalse,
723
724
  writeMessages: (_ownerId, messages) => {
724
725
  // TODO: Get owner by _ownerId when we support more.
725
726
  const owner = deps.ownerRowRef.get();
@@ -15,21 +15,39 @@
15
15
  *
16
16
  * ### Message Structure
17
17
  *
18
- * | Field | Notes |
19
- * | :----------------------------- | :------------------------- |
20
- * | **Header** | |
21
- * | - {@link protocolVersion} | |
22
- * | - {@link OwnerId} | |
23
- * | - {@link ProtocolErrorCode} | In non-initiator response. |
24
- * | **Messages** | |
25
- * | - {@link NonNegativeInt} | A number of messages. |
26
- * | - {@link EncryptedCrdtMessage} | |
27
- * | - {@link WriteKey} | In initiator request. |
28
- * | **Ranges** | |
29
- * | - {@link NonNegativeInt} | Number of ranges. |
30
- * | - {@link Range} | |
31
- *
32
- * Every protocol message belongs to an {@link Owner}.
18
+ * | Field | Notes |
19
+ * | :----------------------------- | :------------------------ |
20
+ * | **Header** | |
21
+ * | - {@link protocolVersion} | |
22
+ * | - {@link OwnerId} | {@link Owner} |
23
+ * | **Initiator** | |
24
+ * | - {@link WriteKeyMode} | |
25
+ * | - {@link WriteKey} | If WriteKeyMode >= 1 |
26
+ * | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
27
+ * | **Non-initiator** | |
28
+ * | - {@link ProtocolErrorCode} | |
29
+ * | **Messages** | |
30
+ * | - {@link NonNegativeInt} | A number of messages. |
31
+ * | - {@link EncryptedCrdtMessage} | |
32
+ * | **Ranges** | |
33
+ * | - {@link NonNegativeInt} | Number of ranges. |
34
+ * | - {@link Range} | |
35
+ *
36
+ * ### WriteKey Validation
37
+ *
38
+ * The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
39
+ * for write operations and two for key rotation (current and new). Note that
40
+ * it's ok to not send any key if initiator is going to be synced with readonly
41
+ * owner. The non-initiator validates them immediately after parsing the
42
+ * initiator header, before processing any messages or ranges.
43
+ *
44
+ * ### WriteKey Rotation
45
+ *
46
+ * When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
47
+ * present:
48
+ *
49
+ * 1. Current WriteKey (for validation)
50
+ * 2. New WriteKey (to be stored)
33
51
  *
34
52
  * ### Synchronization
35
53
  *
@@ -147,6 +165,11 @@ export declare const ProtocolErrorCode: {
147
165
  readonly SyncError: 3;
148
166
  };
149
167
  type ProtocolErrorCode = (typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
168
+ export declare const WriteKeyMode: {
169
+ readonly None: 0;
170
+ readonly Single: 1;
171
+ readonly Rotation: 2;
172
+ };
150
173
  /**
151
174
  * Evolu Protocol Storage
152
175
  *
@@ -168,13 +191,10 @@ export interface Storage {
168
191
  readonly fingerprintRanges: (ownerId: BinaryOwnerId, buckets: ReadonlyArray<NonNegativeInt>, upperBound?: RangeUpperBound) => ReadonlyArray<FingerprintRange> | null;
169
192
  readonly findLowerBound: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, upperBound: RangeUpperBound) => NonNegativeInt | null;
170
193
  readonly iterate: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean) => void;
171
- /**
172
- * Authorizes the initiator's {@link WriteKey} for the given
173
- * {@link BinaryOwnerId}.
174
- *
175
- * For a client that does not expect foreign writes, return `false`.
176
- */
194
+ /** Validates the {@link WriteKey} for the given {@link Owner}. */
177
195
  readonly validateWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
196
+ /** Sets the {@link WriteKey} for the given {@link Owner}. */
197
+ readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
178
198
  /** Write encrypted {@link CrdtMessage}s to storage. */
179
199
  readonly writeMessages: (ownerId: BinaryOwnerId, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => boolean;
180
200
  /** Read encrypted {@link DbChange}s from storage. */
@@ -306,6 +326,8 @@ export interface ProtocolSyncError extends ProtocolErrorBase {
306
326
  export declare const createProtocolMessageFromCrdtMessages: (deps: SymmetricCryptoDep & CreateRandomBytesDep) => (owner: OwnerWithWriteAccess, messages: NonEmptyReadonlyArray<CrdtMessage>, maxSize?: PositiveInt) => ProtocolMessage;
307
327
  /** Creates a {@link ProtocolMessage} for sync. */
308
328
  export declare const createProtocolMessageForSync: (deps: StorageDep) => (ownerId: OwnerId) => ProtocolMessage | null;
329
+ /** Creates a ProtocolMessage for {@link WriteKey} rotation. */
330
+ export declare const createProtocolMessageForWriteKeyRotation: (ownerId: OwnerId, currentWriteKey: WriteKey, newWriteKey: WriteKey) => ProtocolMessage;
309
331
  /**
310
332
  * Mutable builder for constructing {@link ProtocolMessage} respecting size
311
333
  * limits.
@@ -319,13 +341,18 @@ export interface ProtocolMessageBuffer {
319
341
  readonly unwrap: () => ProtocolMessage;
320
342
  readonly getSize: () => PositiveInt;
321
343
  }
322
- export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options?: {
323
- readonly errorCode?: ProtocolErrorCode;
324
- readonly writeKey?: WriteKey;
344
+ export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options: {
325
345
  readonly totalMaxSize?: PositiveInt | undefined;
326
346
  readonly rangesMaxSize?: PositiveInt | undefined;
327
347
  readonly version?: NonNegativeInt;
328
- }) => ProtocolMessageBuffer;
348
+ } & ({
349
+ readonly type: "initiator";
350
+ /** Single key or [current, new] for rotation. */
351
+ readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
352
+ } | {
353
+ readonly type: "non-initiator";
354
+ readonly errorCode: ProtocolErrorCode;
355
+ })) => ProtocolMessageBuffer;
329
356
  export interface TimestampsBuffer {
330
357
  readonly add: (timestamp: Timestamp) => void;
331
358
  readonly addInfinite: () => void;
@@ -425,5 +452,18 @@ export declare const ProtocolValueType: {
425
452
  };
426
453
  export declare const encodeSqliteValue: (buffer: Buffer, value: SqliteValue) => void;
427
454
  export declare const decodeSqliteValue: (buffer: Buffer) => SqliteValue;
455
+ /**
456
+ * Decodes a ProtocolMessage into a readable JSON object for debugging.
457
+ *
458
+ * Note: This is a stub for future implementation. It should use:
459
+ *
460
+ * - DecodeVersionAndOwner
461
+ * - DecodeError or decodeWriteKeys (depending on context)
462
+ * - DecodeMessages
463
+ * - DecodeRanges
464
+ *
465
+ * If you want to help, please contribute to this function.
466
+ */
467
+ export declare const decodeProtocolMessageToJson: (_protocolMessage: ProtocolMessage, _isInitiator: boolean) => unknown;
428
468
  export {};
429
469
  //# sourceMappingURL=Protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"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"}
@@ -15,21 +15,39 @@
15
15
  *
16
16
  * ### Message Structure
17
17
  *
18
- * | Field | Notes |
19
- * | :----------------------------- | :------------------------- |
20
- * | **Header** | |
21
- * | - {@link protocolVersion} | |
22
- * | - {@link OwnerId} | |
23
- * | - {@link ProtocolErrorCode} | In non-initiator response. |
24
- * | **Messages** | |
25
- * | - {@link NonNegativeInt} | A number of messages. |
26
- * | - {@link EncryptedCrdtMessage} | |
27
- * | - {@link WriteKey} | In initiator request. |
28
- * | **Ranges** | |
29
- * | - {@link NonNegativeInt} | Number of ranges. |
30
- * | - {@link Range} | |
18
+ * | Field | Notes |
19
+ * | :----------------------------- | :------------------------ |
20
+ * | **Header** | |
21
+ * | - {@link protocolVersion} | |
22
+ * | - {@link OwnerId} | {@link Owner} |
23
+ * | **Initiator** | |
24
+ * | - {@link WriteKeyMode} | |
25
+ * | - {@link WriteKey} | If WriteKeyMode >= 1 |
26
+ * | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
27
+ * | **Non-initiator** | |
28
+ * | - {@link ProtocolErrorCode} | |
29
+ * | **Messages** | |
30
+ * | - {@link NonNegativeInt} | A number of messages. |
31
+ * | - {@link EncryptedCrdtMessage} | |
32
+ * | **Ranges** | |
33
+ * | - {@link NonNegativeInt} | Number of ranges. |
34
+ * | - {@link Range} | |
31
35
  *
32
- * Every protocol message belongs to an {@link Owner}.
36
+ * ### WriteKey Validation
37
+ *
38
+ * The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
39
+ * for write operations and two for key rotation (current and new). Note that
40
+ * it's ok to not send any key if initiator is going to be synced with readonly
41
+ * owner. The non-initiator validates them immediately after parsing the
42
+ * initiator header, before processing any messages or ranges.
43
+ *
44
+ * ### WriteKey Rotation
45
+ *
46
+ * When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
47
+ * present:
48
+ *
49
+ * 1. Current WriteKey (for validation)
50
+ * 2. New WriteKey (to be stored)
33
51
  *
34
52
  * ### Synchronization
35
53
  *
@@ -124,7 +142,7 @@ import { sha256 } from "@noble/hashes/sha2";
124
142
  import { pack, unpack, unpackMultiple } from "msgpackr";
125
143
  import { isNonEmptyReadonlyArray } from "../Array.js";
126
144
  import { assert } from "../Assert.js";
127
- import { BufferError, bytesToHex, bytesToUtf8, concatBytes, createBuffer, hexToBytes, utf8ToBytes, } from "../Buffer.js";
145
+ import { BufferError, bytesToHex, bytesToUtf8, createBuffer, hexToBytes, utf8ToBytes, } from "../Buffer.js";
128
146
  import { padmePaddingLength, } from "../Crypto.js";
129
147
  import { eqArrayNumber } from "../Eq.js";
130
148
  import { computeBalancedBuckets } from "../Number.js";
@@ -149,6 +167,11 @@ export const ProtocolErrorCode = {
149
167
  /** A code for {@link ProtocolSyncError}. */
150
168
  SyncError: 3,
151
169
  };
170
+ export const WriteKeyMode = {
171
+ None: 0,
172
+ Single: 1,
173
+ Rotation: 2,
174
+ };
152
175
  /**
153
176
  * Base64Url string with maximum length of 256 characters. Encoding strings as
154
177
  * Base64UrlString saves up to 25% in size compared to regular strings.
@@ -181,6 +204,7 @@ export const zeroFingerprint = new Uint8Array(fingerprintSize);
181
204
  */
182
205
  export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages, maxSize) => {
183
206
  const buffer = createProtocolMessageBuffer(owner.id, {
207
+ type: "initiator",
184
208
  totalMaxSize: maxSize ?? maxProtocolMessageSize,
185
209
  writeKey: owner.writeKey,
186
210
  });
@@ -224,7 +248,7 @@ export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages,
224
248
  };
225
249
  /** Creates a {@link ProtocolMessage} for sync. */
226
250
  export const createProtocolMessageForSync = (deps) => (ownerId) => {
227
- const buffer = createProtocolMessageBuffer(ownerId);
251
+ const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
228
252
  const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
229
253
  const size = deps.storage.getSize(binaryOwnerId);
230
254
  // Errors are handled by the storage.
@@ -233,8 +257,16 @@ export const createProtocolMessageForSync = (deps) => (ownerId) => {
233
257
  splitRange(deps)(binaryOwnerId, 0, size, InfiniteUpperBound, buffer);
234
258
  return buffer.unwrap();
235
259
  };
236
- export const createProtocolMessageBuffer = (ownerId, options = {}) => {
237
- const { errorCode, writeKey, totalMaxSize = maxProtocolMessageSize, rangesMaxSize = maxProtocolMessageRangesSize, version = protocolVersion, } = options;
260
+ /** Creates a ProtocolMessage for {@link WriteKey} rotation. */
261
+ export const createProtocolMessageForWriteKeyRotation = (ownerId, currentWriteKey, newWriteKey) => {
262
+ const buffer = createProtocolMessageBuffer(ownerId, {
263
+ type: "initiator",
264
+ writeKey: [currentWriteKey, newWriteKey],
265
+ });
266
+ return buffer.unwrap();
267
+ };
268
+ export const createProtocolMessageBuffer = (ownerId, options) => {
269
+ const { totalMaxSize = maxProtocolMessageSize, rangesMaxSize = maxProtocolMessageRangesSize, version = protocolVersion, } = options;
238
270
  const buffers = {
239
271
  header: createBuffer(),
240
272
  messages: {
@@ -249,17 +281,29 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
249
281
  };
250
282
  encodeNonNegativeInt(buffers.header, version);
251
283
  buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
252
- if (errorCode != null)
253
- buffers.header.extend([errorCode]);
284
+ if (options.type === "initiator") {
285
+ if (!options.writeKey) {
286
+ buffers.header.extend([WriteKeyMode.None]);
287
+ }
288
+ else if (!Array.isArray(options.writeKey)) {
289
+ buffers.header.extend([WriteKeyMode.Single]);
290
+ buffers.header.extend(options.writeKey);
291
+ }
292
+ else {
293
+ buffers.header.extend([WriteKeyMode.Rotation]);
294
+ buffers.header.extend(options.writeKey[0]); // current
295
+ buffers.header.extend(options.writeKey[1]); // new
296
+ }
297
+ }
298
+ else {
299
+ buffers.header.extend([options.errorCode]);
300
+ }
254
301
  let isLastRangeInfinite = false;
255
302
  const isWithinSizeLimits = () => getSize() <= totalMaxSize;
256
303
  const getSize = () => (getHeaderAndMessagesSize() + getRangesSize());
257
304
  const getHeaderAndMessagesSize = () => buffers.header.getLength() +
258
305
  buffers.messages.timestamps.getLength() +
259
- buffers.messages.dbChanges.getLength() +
260
- (buffers.messages.timestamps.getCount() > 0 && writeKey
261
- ? writeKeyLength
262
- : 0);
306
+ buffers.messages.dbChanges.getLength();
263
307
  const getRangesSize = () => buffers.ranges.timestamps.getCount() > 0
264
308
  ? buffers.ranges.timestamps.getLength() +
265
309
  buffers.ranges.types.getLength() +
@@ -345,8 +389,6 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
345
389
  }
346
390
  buffers.messages.timestamps.append(buffers.header);
347
391
  buffers.header.extend(buffers.messages.dbChanges.unwrap());
348
- if (buffers.messages.timestamps.getCount() > 0 && writeKey)
349
- buffers.header.extend(writeKey);
350
392
  if (buffers.ranges.timestamps.getCount() > 0) {
351
393
  buffers.ranges.timestamps.append(buffers.header);
352
394
  buffers.header.extend(buffers.ranges.types.unwrap());
@@ -434,7 +476,6 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
434
476
  ownerId,
435
477
  });
436
478
  }
437
- const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
438
479
  const errorCode = input.shift();
439
480
  if (errorCode !== ProtocolErrorCode.NoError) {
440
481
  switch (errorCode) {
@@ -458,6 +499,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
458
499
  }
459
500
  }
460
501
  const messages = decodeMessages(input);
502
+ const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
461
503
  if (isNonEmptyReadonlyArray(messages) &&
462
504
  !deps.storage.writeMessages(binaryOwnerId, messages)) {
463
505
  return ok(null);
@@ -468,6 +510,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
468
510
  if (writeKey == null)
469
511
  return ok(null);
470
512
  const output = createProtocolMessageBuffer(ownerId, {
513
+ type: "initiator",
471
514
  writeKey,
472
515
  totalMaxSize,
473
516
  rangesMaxSize,
@@ -477,8 +520,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
477
520
  export const applyProtocolMessageAsRelay = (deps) => (inputMessage, { subscribe, broadcast, totalMaxSize, rangesMaxSize, } = {},
478
521
  /** For testing purposes only; should not be used in production. */
479
522
  version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
480
- const requestedVersion = decodeNonNegativeInt(input);
481
- const ownerId = decodeOwnerId(input);
523
+ const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
482
524
  const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
483
525
  if (requestedVersion !== version) {
484
526
  // Non-initiator responds with its version and ownerId.
@@ -488,26 +530,68 @@ version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
488
530
  return ok(output.unwrap());
489
531
  }
490
532
  subscribe?.(ownerId);
533
+ const writeKeyMode = input.shift();
534
+ let writeKey;
535
+ let newWriteKey;
536
+ if (writeKeyMode !== WriteKeyMode.None) {
537
+ writeKey = input.shiftN(writeKeyLength);
538
+ switch (writeKeyMode) {
539
+ case WriteKeyMode.Single:
540
+ break;
541
+ case WriteKeyMode.Rotation:
542
+ newWriteKey = input.shiftN(writeKeyLength);
543
+ break;
544
+ default:
545
+ throw new ProtocolDecodeError(`Invalid WriteKeyMode: ${writeKeyMode}`);
546
+ }
547
+ }
548
+ if (writeKey) {
549
+ const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
550
+ if (!isValid) {
551
+ return ok(createProtocolMessageBuffer(ownerId, {
552
+ type: "non-initiator",
553
+ errorCode: ProtocolErrorCode.WriteKeyError,
554
+ }).unwrap());
555
+ }
556
+ if (newWriteKey) {
557
+ const rotationSuccess = deps.storage.setWriteKey(binaryOwnerId, newWriteKey);
558
+ if (!rotationSuccess) {
559
+ return ok(createProtocolMessageBuffer(ownerId, {
560
+ type: "non-initiator",
561
+ errorCode: ProtocolErrorCode.WriteError,
562
+ }).unwrap());
563
+ }
564
+ }
565
+ }
491
566
  const messages = decodeMessages(input);
492
567
  if (isNonEmptyReadonlyArray(messages)) {
493
- const messagesEnd = inputMessage.length - input.getLength();
494
- const writeKey = input.shiftN(writeKeyLength);
495
- const writeKeyIsValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
496
- if (!writeKeyIsValid)
568
+ if (!writeKey)
497
569
  return ok(createProtocolMessageBuffer(ownerId, {
570
+ type: "non-initiator",
498
571
  errorCode: ProtocolErrorCode.WriteKeyError,
499
572
  }).unwrap());
500
- if (broadcast) {
501
- // Instead of encoding a new protocol message, we reuse the inputMessage.
502
- const broadcastMessage = concatBytes(inputMessage.slice(0, 17), new Uint8Array([ProtocolErrorCode.NoError]), inputMessage.slice(17, messagesEnd));
503
- broadcast(ownerId, broadcastMessage);
573
+ // Only broadcast if there's no ranges.
574
+ if (broadcast && input.getLength() === 0) {
575
+ const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
576
+ type: "non-initiator",
577
+ errorCode: ProtocolErrorCode.NoError,
578
+ totalMaxSize,
579
+ rangesMaxSize,
580
+ version,
581
+ });
582
+ for (const message of messages) {
583
+ broadcastBuffer.addMessage(message);
584
+ }
585
+ broadcast(ownerId, broadcastBuffer.unwrap());
504
586
  }
505
587
  if (!deps.storage.writeMessages(binaryOwnerId, messages))
506
588
  return ok(createProtocolMessageBuffer(ownerId, {
589
+ type: "non-initiator",
507
590
  errorCode: ProtocolErrorCode.WriteError,
508
591
  }).unwrap());
509
592
  }
510
593
  const output = createProtocolMessageBuffer(ownerId, {
594
+ type: "non-initiator",
511
595
  errorCode: ProtocolErrorCode.NoError,
512
596
  totalMaxSize,
513
597
  rangesMaxSize,
@@ -534,6 +618,10 @@ const tryDecodeProtocolData = (data, callback) => {
534
618
  }
535
619
  };
536
620
  const decodeVersionAndOwner = (input) => {
621
+ // This structure must never change across protocol versions. The version
622
+ // and owner ID must always be the first two fields in every protocol message
623
+ // to enable version negotiation and owner identification before any other
624
+ // processing occurs.
537
625
  const version = decodeNonNegativeInt(input);
538
626
  const ownerId = decodeOwnerId(input);
539
627
  return [version, ownerId];
@@ -573,6 +661,7 @@ const sync = (deps) => (role, input, output, ownerId) => {
573
661
  return ok(null);
574
662
  }
575
663
  const message = createProtocolMessageBuffer(binaryOwnerId, {
664
+ type: "non-initiator",
576
665
  errorCode: ProtocolErrorCode.SyncError,
577
666
  });
578
667
  return ok(message.unwrap());
@@ -1192,3 +1281,23 @@ export const decodeSqliteValue = (buffer) => {
1192
1281
  throw new ProtocolDecodeError("invalid ProtocolValueType");
1193
1282
  }
1194
1283
  };
1284
+ /**
1285
+ * Decodes a ProtocolMessage into a readable JSON object for debugging.
1286
+ *
1287
+ * Note: This is a stub for future implementation. It should use:
1288
+ *
1289
+ * - DecodeVersionAndOwner
1290
+ * - DecodeError or decodeWriteKeys (depending on context)
1291
+ * - DecodeMessages
1292
+ * - DecodeRanges
1293
+ *
1294
+ * If you want to help, please contribute to this function.
1295
+ */
1296
+ export const decodeProtocolMessageToJson = (_protocolMessage, _isInitiator) => {
1297
+ // TODO: Implement using
1298
+ // - decodeVersionAndOwner
1299
+ // -- decodeError or decodeWriteKeys (should be refactored out),
1300
+ // -- decodeMessages, and decodeRanges.
1301
+ // This is a stub for PRs and community contributions.
1302
+ throw new Error("decodeProtocolMessageToJson is not implemented yet.");
1303
+ };
@@ -1,4 +1,5 @@
1
1
  import { ConsoleConfig } from "../Console.js";
2
+ import { TimingSafeEqualDep } from "../Crypto.js";
2
3
  import { Result } from "../Result.js";
3
4
  import { SqliteError } from "../Sqlite.js";
4
5
  import { SimpleName } from "../Type.js";
@@ -9,5 +10,6 @@ export interface Relay extends Disposable {
9
10
  export interface RelayConfig extends ConsoleConfig {
10
11
  readonly name?: SimpleName;
11
12
  }
12
- export declare const createRelayStorage: (deps: SqliteStorageDeps) => (options: CreateSqliteStorageBaseOptions) => Result<Storage, SqliteError>;
13
+ export type RelaySqliteStorageDeps = SqliteStorageDeps & TimingSafeEqualDep;
14
+ export declare const createRelayStorage: (deps: RelaySqliteStorageDeps) => (options: CreateSqliteStorageBaseOptions) => Result<Storage, SqliteError>;
13
15
  //# sourceMappingURL=Relay.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAqB,OAAO,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAEL,8BAA8B,EAC9B,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,KAAM,SAAQ,UAAU;CAAG;AAE5C,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,eAAO,MAAM,kBAAkB,GAC5B,MAAM,iBAAiB,MACvB,SAAS,8BAA8B,KAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAsHrE,CAAC"}
1
+ {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAqB,OAAO,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAEL,8BAA8B,EAC9B,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,KAAM,SAAQ,UAAU;CAAG;AAE5C,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE5E,eAAO,MAAM,kBAAkB,GAC5B,MAAM,sBAAsB,MAC5B,SAAS,8BAA8B,KAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAqIrE,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { isNonEmptyReadonlyArray } from "../Array.js";
2
- import { eqArrayNumber } from "../Eq.js";
3
2
  import { ok } from "../Result.js";
4
3
  import { sql } from "../Sqlite.js";
5
4
  import { createSqliteStorageBase, } from "./Storage.js";
@@ -64,7 +63,20 @@ export const createRelayStorage = (deps) => (options) => {
64
63
  }
65
64
  return true;
66
65
  }
67
- return eqArrayNumber(rows[0].writeKey, writeKey);
66
+ return deps.timingSafeEqual(rows[0].writeKey, writeKey);
67
+ },
68
+ setWriteKey: (ownerId, writeKey) => {
69
+ const upsertWriteKey = deps.sqlite.exec(sql `
70
+ insert into evolu_writeKey (ownerId, writeKey)
71
+ values (${ownerId}, ${writeKey})
72
+ on conflict (ownerId) do update
73
+ set writeKey = excluded.writeKey;
74
+ `);
75
+ if (!upsertWriteKey.ok) {
76
+ options.onStorageError(upsertWriteKey.error);
77
+ return false;
78
+ }
79
+ return true;
68
80
  },
69
81
  writeMessages: (ownerId, messages) => {
70
82
  const result = deps.sqlite.transaction(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "6.0.1-preview.4",
3
+ "version": "6.0.1-preview.6",
4
4
  "description": "TypeScript library and local-first framework",
5
5
  "keywords": [
6
6
  "evolu",
package/src/Assert.ts CHANGED
@@ -32,9 +32,12 @@ import type { Type } from "./Type.js";
32
32
  * assert(true, "true is not true"); // no-op
33
33
  * assert(false, "true is not true"); // throws Error
34
34
  *
35
- * const size = fooSize - buffer.getLength();
36
- * // Ensure required type
37
- * assert(PositiveInt.is(size), "size is not positive int");
35
+ * const length = buffer.getLength();
36
+ * // We know length is logically non-negative, but TypeScript doesn't
37
+ * assert(
38
+ * NonNegativeInt.is(length),
39
+ * "buffer length should be non-negative",
40
+ * );
38
41
  * ```
39
42
  */
40
43
  export const assert: (
package/src/Crypto.ts CHANGED
@@ -189,3 +189,16 @@ export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
189
189
  export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
190
190
  return (padmePaddedLength(length) - length) as NonNegativeInt;
191
191
  };
192
+
193
+ /**
194
+ * Performs a timing-safe comparison of two Uint8Arrays. Returns true if they
195
+ * are equal, false otherwise. Takes constant time regardless of where the
196
+ * arrays differ.
197
+ *
198
+ * @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
199
+ */
200
+ export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
201
+
202
+ export interface TimingSafeEqualDep {
203
+ readonly timingSafeEqual: TimingSafeEqual;
204
+ }
package/src/Evolu/Db.ts CHANGED
@@ -1190,6 +1190,7 @@ const createClientStorage =
1190
1190
  ...sqliteStorageBase.value,
1191
1191
 
1192
1192
  validateWriteKey: constFalse,
1193
+ setWriteKey: constFalse,
1193
1194
 
1194
1195
  writeMessages: (_ownerId, messages) => {
1195
1196
  // TODO: Get owner by _ownerId when we support more.
@@ -15,21 +15,39 @@
15
15
  *
16
16
  * ### Message Structure
17
17
  *
18
- * | Field | Notes |
19
- * | :----------------------------- | :------------------------- |
20
- * | **Header** | |
21
- * | - {@link protocolVersion} | |
22
- * | - {@link OwnerId} | |
23
- * | - {@link ProtocolErrorCode} | In non-initiator response. |
24
- * | **Messages** | |
25
- * | - {@link NonNegativeInt} | A number of messages. |
26
- * | - {@link EncryptedCrdtMessage} | |
27
- * | - {@link WriteKey} | In initiator request. |
28
- * | **Ranges** | |
29
- * | - {@link NonNegativeInt} | Number of ranges. |
30
- * | - {@link Range} | |
18
+ * | Field | Notes |
19
+ * | :----------------------------- | :------------------------ |
20
+ * | **Header** | |
21
+ * | - {@link protocolVersion} | |
22
+ * | - {@link OwnerId} | {@link Owner} |
23
+ * | **Initiator** | |
24
+ * | - {@link WriteKeyMode} | |
25
+ * | - {@link WriteKey} | If WriteKeyMode >= 1 |
26
+ * | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
27
+ * | **Non-initiator** | |
28
+ * | - {@link ProtocolErrorCode} | |
29
+ * | **Messages** | |
30
+ * | - {@link NonNegativeInt} | A number of messages. |
31
+ * | - {@link EncryptedCrdtMessage} | |
32
+ * | **Ranges** | |
33
+ * | - {@link NonNegativeInt} | Number of ranges. |
34
+ * | - {@link Range} | |
31
35
  *
32
- * Every protocol message belongs to an {@link Owner}.
36
+ * ### WriteKey Validation
37
+ *
38
+ * The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
39
+ * for write operations and two for key rotation (current and new). Note that
40
+ * it's ok to not send any key if initiator is going to be synced with readonly
41
+ * owner. The non-initiator validates them immediately after parsing the
42
+ * initiator header, before processing any messages or ranges.
43
+ *
44
+ * ### WriteKey Rotation
45
+ *
46
+ * When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
47
+ * present:
48
+ *
49
+ * 1. Current WriteKey (for validation)
50
+ * 2. New WriteKey (to be stored)
33
51
  *
34
52
  * ### Synchronization
35
53
  *
@@ -130,7 +148,6 @@ import {
130
148
  BufferError,
131
149
  bytesToHex,
132
150
  bytesToUtf8,
133
- concatBytes,
134
151
  createBuffer,
135
152
  hexToBytes,
136
153
  utf8ToBytes,
@@ -163,7 +180,6 @@ import {
163
180
  } from "../Type.js";
164
181
  import { Brand, Predicate } from "../Types.js";
165
182
  import {
166
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
167
183
  Owner,
168
184
  OwnerId,
169
185
  OwnerWithWriteAccess,
@@ -205,6 +221,14 @@ export const ProtocolErrorCode = {
205
221
  type ProtocolErrorCode =
206
222
  (typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
207
223
 
224
+ export const WriteKeyMode = {
225
+ None: 0,
226
+ Single: 1,
227
+ Rotation: 2,
228
+ } as const;
229
+
230
+ type WriteKeyMode = (typeof WriteKeyMode)[keyof typeof WriteKeyMode];
231
+
208
232
  /**
209
233
  * Evolu Protocol Storage
210
234
  *
@@ -249,17 +273,15 @@ export interface Storage {
249
273
  callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean,
250
274
  ) => void;
251
275
 
252
- /**
253
- * Authorizes the initiator's {@link WriteKey} for the given
254
- * {@link BinaryOwnerId}.
255
- *
256
- * For a client that does not expect foreign writes, return `false`.
257
- */
276
+ /** Validates the {@link WriteKey} for the given {@link Owner}. */
258
277
  readonly validateWriteKey: (
259
278
  ownerId: BinaryOwnerId,
260
279
  writeKey: WriteKey,
261
280
  ) => boolean;
262
281
 
282
+ /** Sets the {@link WriteKey} for the given {@link Owner}. */
283
+ readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
284
+
263
285
  /** Write encrypted {@link CrdtMessage}s to storage. */
264
286
  readonly writeMessages: (
265
287
  ownerId: BinaryOwnerId,
@@ -435,6 +457,7 @@ export const createProtocolMessageFromCrdtMessages =
435
457
  maxSize?: PositiveInt,
436
458
  ): ProtocolMessage => {
437
459
  const buffer = createProtocolMessageBuffer(owner.id, {
460
+ type: "initiator",
438
461
  totalMaxSize: maxSize ?? maxProtocolMessageSize,
439
462
  writeKey: owner.writeKey,
440
463
  });
@@ -490,7 +513,7 @@ export const createProtocolMessageFromCrdtMessages =
490
513
  export const createProtocolMessageForSync =
491
514
  (deps: StorageDep) =>
492
515
  (ownerId: OwnerId): ProtocolMessage | null => {
493
- const buffer = createProtocolMessageBuffer(ownerId);
516
+ const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
494
517
  const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
495
518
 
496
519
  const size = deps.storage.getSize(binaryOwnerId);
@@ -508,6 +531,19 @@ export const createProtocolMessageForSync =
508
531
  return buffer.unwrap();
509
532
  };
510
533
 
534
+ /** Creates a ProtocolMessage for {@link WriteKey} rotation. */
535
+ export const createProtocolMessageForWriteKeyRotation = (
536
+ ownerId: OwnerId,
537
+ currentWriteKey: WriteKey,
538
+ newWriteKey: WriteKey,
539
+ ): ProtocolMessage => {
540
+ const buffer = createProtocolMessageBuffer(ownerId, {
541
+ type: "initiator",
542
+ writeKey: [currentWriteKey, newWriteKey],
543
+ });
544
+ return buffer.unwrap();
545
+ };
546
+
511
547
  /**
512
548
  * Mutable builder for constructing {@link ProtocolMessage} respecting size
513
549
  * limits.
@@ -535,16 +571,22 @@ export interface ProtocolMessageBuffer {
535
571
  export const createProtocolMessageBuffer = (
536
572
  ownerId: OwnerId,
537
573
  options: {
538
- readonly errorCode?: ProtocolErrorCode;
539
- readonly writeKey?: WriteKey;
540
574
  readonly totalMaxSize?: PositiveInt | undefined;
541
575
  readonly rangesMaxSize?: PositiveInt | undefined;
542
576
  readonly version?: NonNegativeInt;
543
- } = {},
577
+ } & (
578
+ | {
579
+ readonly type: "initiator";
580
+ /** Single key or [current, new] for rotation. */
581
+ readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
582
+ }
583
+ | {
584
+ readonly type: "non-initiator";
585
+ readonly errorCode: ProtocolErrorCode;
586
+ }
587
+ ),
544
588
  ): ProtocolMessageBuffer => {
545
589
  const {
546
- errorCode,
547
- writeKey,
548
590
  totalMaxSize = maxProtocolMessageSize,
549
591
  rangesMaxSize = maxProtocolMessageRangesSize,
550
592
  version = protocolVersion,
@@ -565,7 +607,21 @@ export const createProtocolMessageBuffer = (
565
607
 
566
608
  encodeNonNegativeInt(buffers.header, version);
567
609
  buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
568
- if (errorCode != null) buffers.header.extend([errorCode]);
610
+
611
+ if (options.type === "initiator") {
612
+ if (!options.writeKey) {
613
+ buffers.header.extend([WriteKeyMode.None]);
614
+ } else if (!Array.isArray(options.writeKey)) {
615
+ buffers.header.extend([WriteKeyMode.Single]);
616
+ buffers.header.extend(options.writeKey as WriteKey);
617
+ } else {
618
+ buffers.header.extend([WriteKeyMode.Rotation]);
619
+ buffers.header.extend(options.writeKey[0] as WriteKey); // current
620
+ buffers.header.extend(options.writeKey[1] as WriteKey); // new
621
+ }
622
+ } else {
623
+ buffers.header.extend([options.errorCode]);
624
+ }
569
625
 
570
626
  let isLastRangeInfinite = false;
571
627
 
@@ -577,10 +633,7 @@ export const createProtocolMessageBuffer = (
577
633
  const getHeaderAndMessagesSize = () =>
578
634
  buffers.header.getLength() +
579
635
  buffers.messages.timestamps.getLength() +
580
- buffers.messages.dbChanges.getLength() +
581
- (buffers.messages.timestamps.getCount() > 0 && writeKey
582
- ? writeKeyLength
583
- : 0);
636
+ buffers.messages.dbChanges.getLength();
584
637
 
585
638
  const getRangesSize = () =>
586
639
  buffers.ranges.timestamps.getCount() > 0
@@ -695,8 +748,6 @@ export const createProtocolMessageBuffer = (
695
748
 
696
749
  buffers.messages.timestamps.append(buffers.header);
697
750
  buffers.header.extend(buffers.messages.dbChanges.unwrap());
698
- if (buffers.messages.timestamps.getCount() > 0 && writeKey)
699
- buffers.header.extend(writeKey);
700
751
 
701
752
  if (buffers.ranges.timestamps.getCount() > 0) {
702
753
  buffers.ranges.timestamps.append(buffers.header);
@@ -846,8 +897,6 @@ export const applyProtocolMessageAsClient =
846
897
  });
847
898
  }
848
899
 
849
- const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
850
-
851
900
  const errorCode = input.shift() as ProtocolErrorCode;
852
901
  if (errorCode !== ProtocolErrorCode.NoError) {
853
902
  switch (errorCode) {
@@ -874,6 +923,7 @@ export const applyProtocolMessageAsClient =
874
923
  }
875
924
 
876
925
  const messages = decodeMessages(input);
926
+ const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
877
927
 
878
928
  if (
879
929
  isNonEmptyReadonlyArray(messages) &&
@@ -887,6 +937,7 @@ export const applyProtocolMessageAsClient =
887
937
  if (writeKey == null) return ok(null);
888
938
 
889
939
  const output = createProtocolMessageBuffer(ownerId, {
940
+ type: "initiator",
890
941
  writeKey,
891
942
  totalMaxSize,
892
943
  rangesMaxSize,
@@ -921,8 +972,7 @@ export const applyProtocolMessageAsRelay =
921
972
  version = protocolVersion,
922
973
  ): Result<ProtocolMessage | null, ProtocolInvalidDataError> =>
923
974
  tryDecodeProtocolData(inputMessage, (input) => {
924
- const requestedVersion = decodeNonNegativeInt(input);
925
- const ownerId = decodeOwnerId(input);
975
+ const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
926
976
  const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
927
977
 
928
978
  if (requestedVersion !== version) {
@@ -935,44 +985,89 @@ export const applyProtocolMessageAsRelay =
935
985
 
936
986
  subscribe?.(ownerId);
937
987
 
938
- const messages = decodeMessages(input);
988
+ const writeKeyMode = input.shift() as WriteKeyMode;
989
+ let writeKey: WriteKey | undefined;
990
+ let newWriteKey: WriteKey | undefined;
991
+
992
+ if (writeKeyMode !== WriteKeyMode.None) {
993
+ writeKey = input.shiftN(writeKeyLength) as WriteKey;
994
+ switch (writeKeyMode) {
995
+ case WriteKeyMode.Single:
996
+ break;
997
+ case WriteKeyMode.Rotation:
998
+ newWriteKey = input.shiftN(writeKeyLength) as WriteKey;
999
+ break;
1000
+ default:
1001
+ throw new ProtocolDecodeError(
1002
+ `Invalid WriteKeyMode: ${writeKeyMode}`,
1003
+ );
1004
+ }
1005
+ }
939
1006
 
940
- if (isNonEmptyReadonlyArray(messages)) {
941
- const messagesEnd = inputMessage.length - input.getLength();
942
- const writeKey = input.shiftN(writeKeyLength) as WriteKey;
1007
+ if (writeKey) {
1008
+ const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
1009
+ if (!isValid) {
1010
+ return ok(
1011
+ createProtocolMessageBuffer(ownerId, {
1012
+ type: "non-initiator",
1013
+ errorCode: ProtocolErrorCode.WriteKeyError,
1014
+ }).unwrap(),
1015
+ );
1016
+ }
943
1017
 
944
- const writeKeyIsValid = deps.storage.validateWriteKey(
945
- binaryOwnerId,
946
- writeKey,
947
- );
1018
+ if (newWriteKey) {
1019
+ const rotationSuccess = deps.storage.setWriteKey(
1020
+ binaryOwnerId,
1021
+ newWriteKey,
1022
+ );
1023
+ if (!rotationSuccess) {
1024
+ return ok(
1025
+ createProtocolMessageBuffer(ownerId, {
1026
+ type: "non-initiator",
1027
+ errorCode: ProtocolErrorCode.WriteError,
1028
+ }).unwrap(),
1029
+ );
1030
+ }
1031
+ }
1032
+ }
948
1033
 
949
- if (!writeKeyIsValid)
1034
+ const messages = decodeMessages(input);
1035
+
1036
+ if (isNonEmptyReadonlyArray(messages)) {
1037
+ if (!writeKey)
950
1038
  return ok(
951
1039
  createProtocolMessageBuffer(ownerId, {
1040
+ type: "non-initiator",
952
1041
  errorCode: ProtocolErrorCode.WriteKeyError,
953
1042
  }).unwrap(),
954
1043
  );
955
1044
 
956
- if (broadcast) {
957
- // Instead of encoding a new protocol message, we reuse the inputMessage.
958
- const broadcastMessage = concatBytes(
959
- inputMessage.slice(0, 17),
960
- new Uint8Array([ProtocolErrorCode.NoError]),
961
- inputMessage.slice(17, messagesEnd),
962
- ) as ProtocolMessage;
963
-
964
- broadcast(ownerId, broadcastMessage);
1045
+ // Only broadcast if there's no ranges.
1046
+ if (broadcast && input.getLength() === 0) {
1047
+ const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
1048
+ type: "non-initiator",
1049
+ errorCode: ProtocolErrorCode.NoError,
1050
+ totalMaxSize,
1051
+ rangesMaxSize,
1052
+ version,
1053
+ });
1054
+ for (const message of messages) {
1055
+ broadcastBuffer.addMessage(message);
1056
+ }
1057
+ broadcast(ownerId, broadcastBuffer.unwrap());
965
1058
  }
966
1059
 
967
1060
  if (!deps.storage.writeMessages(binaryOwnerId, messages))
968
1061
  return ok(
969
1062
  createProtocolMessageBuffer(ownerId, {
1063
+ type: "non-initiator",
970
1064
  errorCode: ProtocolErrorCode.WriteError,
971
1065
  }).unwrap(),
972
1066
  );
973
1067
  }
974
1068
 
975
1069
  const output = createProtocolMessageBuffer(ownerId, {
1070
+ type: "non-initiator",
976
1071
  errorCode: ProtocolErrorCode.NoError,
977
1072
  totalMaxSize,
978
1073
  rangesMaxSize,
@@ -1005,6 +1100,10 @@ const tryDecodeProtocolData = <T, E>(
1005
1100
  };
1006
1101
 
1007
1102
  const decodeVersionAndOwner = (input: Buffer): [NonNegativeInt, OwnerId] => {
1103
+ // This structure must never change across protocol versions. The version
1104
+ // and owner ID must always be the first two fields in every protocol message
1105
+ // to enable version negotiation and owner identification before any other
1106
+ // processing occurs.
1008
1107
  const version = decodeNonNegativeInt(input);
1009
1108
  const ownerId = decodeOwnerId(input);
1010
1109
  return [version, ownerId];
@@ -1062,6 +1161,7 @@ const sync =
1062
1161
  return ok(null);
1063
1162
  }
1064
1163
  const message = createProtocolMessageBuffer(binaryOwnerId, {
1164
+ type: "non-initiator",
1065
1165
  errorCode: ProtocolErrorCode.SyncError,
1066
1166
  });
1067
1167
  return ok(message.unwrap());
@@ -1898,3 +1998,27 @@ export const decodeSqliteValue = (buffer: Buffer): SqliteValue => {
1898
1998
  throw new ProtocolDecodeError("invalid ProtocolValueType");
1899
1999
  }
1900
2000
  };
2001
+
2002
+ /**
2003
+ * Decodes a ProtocolMessage into a readable JSON object for debugging.
2004
+ *
2005
+ * Note: This is a stub for future implementation. It should use:
2006
+ *
2007
+ * - DecodeVersionAndOwner
2008
+ * - DecodeError or decodeWriteKeys (depending on context)
2009
+ * - DecodeMessages
2010
+ * - DecodeRanges
2011
+ *
2012
+ * If you want to help, please contribute to this function.
2013
+ */
2014
+ export const decodeProtocolMessageToJson = (
2015
+ _protocolMessage: ProtocolMessage,
2016
+ _isInitiator: boolean,
2017
+ ): unknown => {
2018
+ // TODO: Implement using
2019
+ // - decodeVersionAndOwner
2020
+ // -- decodeError or decodeWriteKeys (should be refactored out),
2021
+ // -- decodeMessages, and decodeRanges.
2022
+ // This is a stub for PRs and community contributions.
2023
+ throw new Error("decodeProtocolMessageToJson is not implemented yet.");
2024
+ };
@@ -1,6 +1,6 @@
1
1
  import { isNonEmptyReadonlyArray } from "../Array.js";
2
2
  import { ConsoleConfig } from "../Console.js";
3
- import { eqArrayNumber } from "../Eq.js";
3
+ import { TimingSafeEqualDep } from "../Crypto.js";
4
4
  import { ok, Result } from "../Result.js";
5
5
  import { sql, SqliteError } from "../Sqlite.js";
6
6
  import { SimpleName } from "../Type.js";
@@ -19,8 +19,10 @@ export interface RelayConfig extends ConsoleConfig {
19
19
  readonly name?: SimpleName;
20
20
  }
21
21
 
22
+ export type RelaySqliteStorageDeps = SqliteStorageDeps & TimingSafeEqualDep;
23
+
22
24
  export const createRelayStorage =
23
- (deps: SqliteStorageDeps) =>
25
+ (deps: RelaySqliteStorageDeps) =>
24
26
  (options: CreateSqliteStorageBaseOptions): Result<Storage, SqliteError> => {
25
27
  const sqliteStorageBase = createSqliteStorageBase(deps)(options);
26
28
  if (!sqliteStorageBase.ok) return sqliteStorageBase;
@@ -87,7 +89,22 @@ export const createRelayStorage =
87
89
  return true;
88
90
  }
89
91
 
90
- return eqArrayNumber(rows[0].writeKey, writeKey);
92
+ return deps.timingSafeEqual(rows[0].writeKey, writeKey);
93
+ },
94
+
95
+ setWriteKey: (ownerId, writeKey) => {
96
+ const upsertWriteKey = deps.sqlite.exec(sql`
97
+ insert into evolu_writeKey (ownerId, writeKey)
98
+ values (${ownerId}, ${writeKey})
99
+ on conflict (ownerId) do update
100
+ set writeKey = excluded.writeKey;
101
+ `);
102
+ if (!upsertWriteKey.ok) {
103
+ options.onStorageError(upsertWriteKey.error);
104
+ return false;
105
+ }
106
+
107
+ return true;
91
108
  },
92
109
 
93
110
  writeMessages: (ownerId, messages) => {