@enbox/dwn-sdk-js 0.4.4 → 0.4.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.
Files changed (201) hide show
  1. package/dist/browser.mjs +15 -15
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/generated/precompiled-validators.js +5406 -2391
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/json-schemas/definitions.json +18 -0
  6. package/dist/esm/src/core/constants.js +3 -3
  7. package/dist/esm/src/core/constants.js.map +1 -1
  8. package/dist/esm/src/core/dwn-error.js +29 -1
  9. package/dist/esm/src/core/dwn-error.js.map +1 -1
  10. package/dist/esm/src/core/messages-grant-authorization.js +20 -0
  11. package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
  12. package/dist/esm/src/core/protocol-authorization-validation.js +80 -3
  13. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  14. package/dist/esm/src/core/recording-validation-state-reader.js +5 -0
  15. package/dist/esm/src/core/recording-validation-state-reader.js.map +1 -1
  16. package/dist/esm/src/core/replication-apply.js +163 -7
  17. package/dist/esm/src/core/replication-apply.js.map +1 -1
  18. package/dist/esm/src/core/validation-state-reader.js +20 -0
  19. package/dist/esm/src/core/validation-state-reader.js.map +1 -1
  20. package/dist/esm/src/dwn.js +19 -2
  21. package/dist/esm/src/dwn.js.map +1 -1
  22. package/dist/esm/src/handlers/messages-query.js +3 -5
  23. package/dist/esm/src/handlers/messages-query.js.map +1 -1
  24. package/dist/esm/src/handlers/protocols-configure.js +10 -4
  25. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  26. package/dist/esm/src/handlers/records-write.js +1 -1
  27. package/dist/esm/src/handlers/records-write.js.map +1 -1
  28. package/dist/esm/src/index.js +2 -1
  29. package/dist/esm/src/index.js.map +1 -1
  30. package/dist/esm/src/interfaces/protocols-configure.js +23 -10
  31. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  32. package/dist/esm/src/interfaces/records-write-signing.js +2 -8
  33. package/dist/esm/src/interfaces/records-write-signing.js.map +1 -1
  34. package/dist/esm/src/interfaces/records-write.js +43 -65
  35. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  36. package/dist/esm/src/protocols/encryption.js +411 -0
  37. package/dist/esm/src/protocols/encryption.js.map +1 -0
  38. package/dist/esm/src/protocols/permission-grant.js +0 -5
  39. package/dist/esm/src/protocols/permission-grant.js.map +1 -1
  40. package/dist/esm/src/types/message-types.js.map +1 -1
  41. package/dist/esm/src/types/permission-types.js.map +1 -1
  42. package/dist/esm/src/types/protocols-types.js.map +1 -1
  43. package/dist/esm/src/utils/encryption.js +156 -188
  44. package/dist/esm/src/utils/encryption.js.map +1 -1
  45. package/dist/esm/src/utils/hd-key.js +0 -1
  46. package/dist/esm/src/utils/hd-key.js.map +1 -1
  47. package/dist/esm/src/utils/permission-scope.js +9 -6
  48. package/dist/esm/src/utils/permission-scope.js.map +1 -1
  49. package/dist/esm/src/utils/protocols.js +12 -9
  50. package/dist/esm/src/utils/protocols.js.map +1 -1
  51. package/dist/esm/src/utils/records.js +54 -56
  52. package/dist/esm/src/utils/records.js.map +1 -1
  53. package/dist/esm/src/utils/replication.js +27 -4
  54. package/dist/esm/src/utils/replication.js.map +1 -1
  55. package/dist/esm/tests/core/replication-apply.spec.js +141 -1
  56. package/dist/esm/tests/core/replication-apply.spec.js.map +1 -1
  57. package/dist/esm/tests/core/replication-replay-property.spec.js +1 -0
  58. package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
  59. package/dist/esm/tests/core/validation-read-closure.spec.js +111 -0
  60. package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
  61. package/dist/esm/tests/core/validation-state-reader.spec.js +89 -0
  62. package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -1
  63. package/dist/esm/tests/features/protocol-composition.spec.js +30 -31
  64. package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
  65. package/dist/esm/tests/fuzz/encryption.fuzz.spec.js +29 -121
  66. package/dist/esm/tests/fuzz/encryption.fuzz.spec.js.map +1 -1
  67. package/dist/esm/tests/handlers/messages-query.spec.js +71 -3
  68. package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
  69. package/dist/esm/tests/handlers/messages-read.spec.js +2 -2
  70. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  71. package/dist/esm/tests/handlers/records-read.spec.js +9 -135
  72. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  73. package/dist/esm/tests/handlers/records-write.spec.js +279 -19
  74. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  75. package/dist/esm/tests/interfaces/protocols-configure.spec.js +17 -22
  76. package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
  77. package/dist/esm/tests/interfaces/records-write.spec.js +170 -150
  78. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  79. package/dist/esm/tests/protocols/encryption.spec.js +914 -0
  80. package/dist/esm/tests/protocols/encryption.spec.js.map +1 -0
  81. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +100 -100
  82. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
  83. package/dist/esm/tests/store/message-store-level.spec.js +20 -2
  84. package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
  85. package/dist/esm/tests/utils/encryption-callbacks.spec.js +88 -125
  86. package/dist/esm/tests/utils/encryption-callbacks.spec.js.map +1 -1
  87. package/dist/esm/tests/utils/encryption.spec.js +121 -271
  88. package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
  89. package/dist/esm/tests/utils/permission-scope.spec.js +5 -1
  90. package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -1
  91. package/dist/esm/tests/utils/records.spec.js +15 -5
  92. package/dist/esm/tests/utils/records.spec.js.map +1 -1
  93. package/dist/esm/tests/utils/test-data-generator.js +7 -44
  94. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  95. package/dist/esm/tests/validation/json-schemas/encryption/audience-key.spec.js +40 -0
  96. package/dist/esm/tests/validation/json-schemas/encryption/audience-key.spec.js.map +1 -0
  97. package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js +72 -0
  98. package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js.map +1 -0
  99. package/dist/types/generated/precompiled-validators.d.ts +140 -92
  100. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  101. package/dist/types/src/core/constants.d.ts +2 -2
  102. package/dist/types/src/core/constants.d.ts.map +1 -1
  103. package/dist/types/src/core/core-protocol.d.ts +1 -1
  104. package/dist/types/src/core/core-protocol.d.ts.map +1 -1
  105. package/dist/types/src/core/dwn-error.d.ts +29 -1
  106. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  107. package/dist/types/src/core/messages-grant-authorization.d.ts +2 -0
  108. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  109. package/dist/types/src/core/protocol-authorization-validation.d.ts +2 -2
  110. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  111. package/dist/types/src/core/recording-validation-state-reader.d.ts +9 -0
  112. package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -1
  113. package/dist/types/src/core/replication-apply.d.ts +4 -3
  114. package/dist/types/src/core/replication-apply.d.ts.map +1 -1
  115. package/dist/types/src/core/validation-state-reader.d.ts +9 -0
  116. package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
  117. package/dist/types/src/dwn.d.ts +1 -0
  118. package/dist/types/src/dwn.d.ts.map +1 -1
  119. package/dist/types/src/handlers/messages-query.d.ts.map +1 -1
  120. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  121. package/dist/types/src/index.d.ts +6 -5
  122. package/dist/types/src/index.d.ts.map +1 -1
  123. package/dist/types/src/interfaces/protocols-configure.d.ts +1 -1
  124. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  125. package/dist/types/src/interfaces/records-write-signing.d.ts +4 -8
  126. package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -1
  127. package/dist/types/src/interfaces/records-write.d.ts +10 -26
  128. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  129. package/dist/types/src/protocols/encryption.d.ts +43 -0
  130. package/dist/types/src/protocols/encryption.d.ts.map +1 -0
  131. package/dist/types/src/protocols/permission-grant.d.ts +0 -7
  132. package/dist/types/src/protocols/permission-grant.d.ts.map +1 -1
  133. package/dist/types/src/types/encryption-types.d.ts +5 -38
  134. package/dist/types/src/types/encryption-types.d.ts.map +1 -1
  135. package/dist/types/src/types/message-types.d.ts +0 -13
  136. package/dist/types/src/types/message-types.d.ts.map +1 -1
  137. package/dist/types/src/types/permission-types.d.ts +0 -9
  138. package/dist/types/src/types/permission-types.d.ts.map +1 -1
  139. package/dist/types/src/types/protocols-types.d.ts +9 -14
  140. package/dist/types/src/types/protocols-types.d.ts.map +1 -1
  141. package/dist/types/src/types/records-types.d.ts +3 -3
  142. package/dist/types/src/types/records-types.d.ts.map +1 -1
  143. package/dist/types/src/types/validation-state-reader.d.ts +12 -0
  144. package/dist/types/src/types/validation-state-reader.d.ts.map +1 -1
  145. package/dist/types/src/utils/encryption.d.ts +61 -145
  146. package/dist/types/src/utils/encryption.d.ts.map +1 -1
  147. package/dist/types/src/utils/hd-key.d.ts +1 -1
  148. package/dist/types/src/utils/hd-key.d.ts.map +1 -1
  149. package/dist/types/src/utils/permission-scope.d.ts +4 -3
  150. package/dist/types/src/utils/permission-scope.d.ts.map +1 -1
  151. package/dist/types/src/utils/protocols.d.ts +3 -5
  152. package/dist/types/src/utils/protocols.d.ts.map +1 -1
  153. package/dist/types/src/utils/records.d.ts +5 -13
  154. package/dist/types/src/utils/records.d.ts.map +1 -1
  155. package/dist/types/src/utils/replication.d.ts +3 -0
  156. package/dist/types/src/utils/replication.d.ts.map +1 -1
  157. package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
  158. package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
  159. package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
  160. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  161. package/dist/types/tests/protocols/encryption.spec.d.ts +2 -0
  162. package/dist/types/tests/protocols/encryption.spec.d.ts.map +1 -0
  163. package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
  164. package/dist/types/tests/utils/test-data-generator.d.ts +0 -5
  165. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  166. package/dist/types/tests/validation/json-schemas/encryption/audience-key.spec.d.ts +2 -0
  167. package/dist/types/tests/validation/json-schemas/encryption/audience-key.spec.d.ts.map +1 -0
  168. package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts +2 -0
  169. package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts.map +1 -0
  170. package/package.json +3 -3
  171. package/src/core/constants.ts +3 -3
  172. package/src/core/core-protocol.ts +1 -1
  173. package/src/core/dwn-error.ts +29 -1
  174. package/src/core/messages-grant-authorization.ts +27 -0
  175. package/src/core/protocol-authorization-validation.ts +145 -6
  176. package/src/core/recording-validation-state-reader.ts +13 -0
  177. package/src/core/replication-apply.ts +222 -8
  178. package/src/core/validation-state-reader.ts +30 -0
  179. package/src/dwn.ts +24 -2
  180. package/src/handlers/messages-query.ts +6 -5
  181. package/src/handlers/protocols-configure.ts +19 -3
  182. package/src/handlers/records-write.ts +1 -1
  183. package/src/index.ts +15 -5
  184. package/src/interfaces/protocols-configure.ts +39 -12
  185. package/src/interfaces/records-write-signing.ts +5 -12
  186. package/src/interfaces/records-write.ts +75 -92
  187. package/src/protocols/encryption.ts +649 -0
  188. package/src/protocols/permission-grant.ts +0 -6
  189. package/src/types/encryption-types.ts +9 -38
  190. package/src/types/message-types.ts +0 -13
  191. package/src/types/permission-types.ts +0 -10
  192. package/src/types/protocols-types.ts +9 -16
  193. package/src/types/records-types.ts +3 -3
  194. package/src/types/validation-state-reader.ts +13 -0
  195. package/src/utils/encryption.ts +258 -280
  196. package/src/utils/hd-key.ts +1 -1
  197. package/src/utils/permission-scope.ts +10 -6
  198. package/src/utils/protocols.ts +16 -15
  199. package/src/utils/records.ts +86 -71
  200. package/src/utils/replication.ts +30 -4
  201. package/dist/esm/tests/vectors/protocol-definitions/chat.json +0 -70
@@ -16,9 +16,9 @@ export type ProtocolScope = {
16
16
  *
17
17
  * Matching fails closed for invalid combinations: `protocolPath` and
18
18
  * `contextId` are mutually exclusive, and either field requires `protocol`.
19
- * `protocol` and `protocolPath` match by exact equality. `contextId` scopes
20
- * match a context subtree: the target context must equal the scoped context or
21
- * begin with the scoped context followed by `/`.
19
+ * `protocol` matches by exact equality. `protocolPath` and `contextId` scopes
20
+ * match subtrees: the target value must equal the scoped value or begin with the
21
+ * scoped value followed by `/`.
22
22
  */
23
23
  export class PermissionScopeMatcher {
24
24
  /**
@@ -38,7 +38,7 @@ export class PermissionScopeMatcher {
38
38
  }
39
39
 
40
40
  if (scope.protocolPath !== undefined) {
41
- return scope.protocolPath === target.protocolPath;
41
+ return PermissionScopeMatcher.matchesSubtree(scope.protocolPath, target.protocolPath);
42
42
  }
43
43
 
44
44
  if (scope.contextId !== undefined) {
@@ -49,7 +49,11 @@ export class PermissionScopeMatcher {
49
49
  }
50
50
 
51
51
  private static matchesContextId(scopeContextId: string, candidateContextId: unknown): boolean {
52
- return typeof candidateContextId === 'string' &&
53
- (candidateContextId === scopeContextId || candidateContextId.startsWith(scopeContextId + '/'));
52
+ return PermissionScopeMatcher.matchesSubtree(scopeContextId, candidateContextId);
53
+ }
54
+
55
+ private static matchesSubtree(scopeValue: string, candidateValue: unknown): boolean {
56
+ return typeof candidateValue === 'string' &&
57
+ (candidateValue === scopeValue || candidateValue.startsWith(scopeValue + '/'));
54
58
  }
55
59
  }
@@ -77,11 +77,10 @@ export function getRuleSetAtPath(protocolPath: string, structure: { [key: string
77
77
  */
78
78
  export class Protocols {
79
79
  /**
80
- * Derives public encryptions keys and inject it in the `$encryption` property for each protocol path segment of the given Protocol definition,
81
- * then returns the final encryption-enabled protocol definition.
80
+ * Derives public encryption keys and injects them in `$keyAgreement`.
82
81
  * NOTE: The original definition passed in is unmodified.
83
82
  *
84
- * `$ref` nodes (cross-protocol attachment points) are skipped during `$encryption` injection
83
+ * `$ref` nodes (cross-protocol attachment points) are skipped during `$keyAgreement` injection
85
84
  * because their records belong to the referenced protocol, whose own encryption keys govern them.
86
85
  * Children of `$ref` nodes are still processed because they belong to the composing protocol.
87
86
  *
@@ -94,8 +93,7 @@ export class Protocols {
94
93
  ): Promise<ProtocolDefinition>;
95
94
 
96
95
  /**
97
- * Overload 2 (raw-key, existing): Takes rootKeyId and raw PrivateKeyJwk directly.
98
- * Preserved for backward compatibility with tests and non-KMS callers.
96
+ * Overload 2 (raw-key): Takes rootKeyId and raw PrivateKeyJwk directly.
99
97
  */
100
98
  public static async deriveAndInjectPublicEncryptionKeys(
101
99
  protocolDefinition: ProtocolDefinition,
@@ -103,7 +101,7 @@ export class Protocols {
103
101
  privateJwk: PrivateKeyJwk,
104
102
  ): Promise<ProtocolDefinition>;
105
103
 
106
- // Implementation dispatches based on argument type
104
+ // Implementation dispatches based on argument type.
107
105
  public static async deriveAndInjectPublicEncryptionKeys(
108
106
  protocolDefinition: ProtocolDefinition,
109
107
  rootKeyIdOrKeyDeriver: string | EncryptionKeyDeriver,
@@ -116,6 +114,9 @@ export class Protocols {
116
114
  // Callback-based path
117
115
  const keyDeriver = rootKeyIdOrKeyDeriver;
118
116
  const basePath = [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol];
117
+ clone.$keyAgreement = {
118
+ publicKeyJwk: await keyDeriver.derivePublicKey(basePath),
119
+ };
119
120
 
120
121
  async function injectKeysViaCallback(
121
122
  ruleSet: ProtocolRuleSet, parentPath: string[],
@@ -133,10 +134,7 @@ export class Protocols {
133
134
  }
134
135
 
135
136
  const publicKeyJwk = await keyDeriver.derivePublicKey(currentPath);
136
- childRuleSet.$encryption = {
137
- rootKeyId: keyDeriver.rootKeyId,
138
- publicKeyJwk,
139
- };
137
+ childRuleSet.$keyAgreement = { publicKeyJwk };
140
138
  await injectKeysViaCallback(childRuleSet, currentPath);
141
139
  }
142
140
  }
@@ -146,13 +144,13 @@ export class Protocols {
146
144
  return clone;
147
145
  }
148
146
 
149
- // Raw-key path (existing logic, unchanged)
147
+ // Raw-key path
150
148
  const rootKeyId = rootKeyIdOrKeyDeriver;
151
149
 
152
- // a function that recursively creates and adds `$encryption` property to every rule set
150
+ // a function that recursively creates and adds `$keyAgreement` property to every rule set
153
151
  async function addEncryptionProperty(ruleSet: ProtocolRuleSet, parentKey: DerivedPrivateJwk): Promise<void> {
154
152
  for (const key in ruleSet) {
155
- // if we encounter a nested rule set (a property name that doesn't begin with '$'), recursively inject the `$encryption` property
153
+ // if we encounter a nested rule set (a property name that doesn't begin with '$'), recursively inject the `$keyAgreement` property
156
154
  if (!key.startsWith('$')) {
157
155
  const derivedPrivateKey = await HdKey.derivePrivateKey(parentKey, [key]);
158
156
  const childRuleSet = ruleSet[key] as ProtocolRuleSet;
@@ -166,7 +164,7 @@ export class Protocols {
166
164
 
167
165
  const publicKeyJwk = await X25519.getPublicKey({ key: derivedPrivateKey.derivedPrivateKey }) as PublicKeyJwk;
168
166
 
169
- childRuleSet.$encryption = { rootKeyId, publicKeyJwk };
167
+ childRuleSet.$keyAgreement = { publicKeyJwk };
170
168
  await addEncryptionProperty(childRuleSet, derivedPrivateKey);
171
169
  }
172
170
  }
@@ -179,8 +177,11 @@ export class Protocols {
179
177
  rootKeyId
180
178
  };
181
179
  const protocolLevelDerivedKey = await HdKey.derivePrivateKey(rootKey, [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol]);
180
+ clone.$keyAgreement = {
181
+ publicKeyJwk: await X25519.getPublicKey({ key: protocolLevelDerivedKey.derivedPrivateKey }) as PublicKeyJwk,
182
+ };
182
183
  await addEncryptionProperty(clone.structure as ProtocolRuleSet, protocolLevelDerivedKey);
183
184
 
184
185
  return clone;
185
186
  }
186
- }
187
+ }
@@ -1,13 +1,16 @@
1
1
  import type { DerivedPrivateJwk } from './hd-key.js';
2
2
  import type { Jwk } from '@enbox/crypto';
3
3
  import type { KeyDecrypter } from '../types/encryption-types.js';
4
+ import type { KeyEncryption } from './encryption.js';
5
+ import type { PublicKeyJwk } from '../types/jose-types.js';
4
6
  import type { Filter, KeyValues, StartsWithFilter } from '../types/query-types.js';
5
7
  import type { GenericMessage, GenericSignaturePayload, MessageSort } from '../types/message-types.js';
6
8
  import type { RecordsCountMessage, RecordsDeleteMessage, RecordsFilter, RecordsQueryMessage, RecordsReadMessage, RecordsSubscribeMessage, RecordsWriteDescriptor, RecordsWriteMessage, RecordsWriteTags, RecordsWriteTagsFilter } from '../types/records-types.js';
7
9
 
10
+ import { Cid } from './cid.js';
11
+ import { DataStream } from './data-stream.js';
8
12
  import { DateSort } from '../types/records-types.js';
9
13
  import { Encoder } from './encoder.js';
10
- import { Encryption } from './encryption.js';
11
14
  import { FilterUtility } from './filter.js';
12
15
  import { Jws } from './jws.js';
13
16
  import { Message } from '../core/message.js';
@@ -17,6 +20,7 @@ import { SortDirection } from '../types/query-types.js';
17
20
  import { X25519 } from '@enbox/crypto';
18
21
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
19
22
  import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
23
+ import { Encryption, ROLE_AUDIENCE_DERIVATION_SCHEME } from './encryption.js';
20
24
  import { HdKey, KeyDerivationScheme } from './hd-key.js';
21
25
  import { normalizeProtocolUrl, normalizeSchemaUrl } from './url.js';
22
26
 
@@ -60,7 +64,7 @@ export class Records {
60
64
  * Decrypts the encrypted data in a message reply.
61
65
  *
62
66
  * Overload 1 (callback-based): Accepts a KeyDecrypter that performs
63
- * HKDF derivation + ECDH-ES key agreement + AES Key Unwrap internally.
67
+ * X25519-HKDF key agreement + AES Key Unwrap internally.
64
68
  */
65
69
  public static async decrypt(
66
70
  recordsWrite: RecordsWriteMessage,
@@ -78,7 +82,7 @@ export class Records {
78
82
  cipherStream: ReadableStream<Uint8Array>,
79
83
  ): Promise<ReadableStream<Uint8Array>>;
80
84
 
81
- // Implementation dispatches based on argument type
85
+ // Implementation dispatches based on argument type.
82
86
  public static async decrypt(
83
87
  recordsWrite: RecordsWriteMessage,
84
88
  keyOrDecrypter: DerivedPrivateJwk | KeyDecrypter,
@@ -92,81 +96,114 @@ export class Records {
92
96
  );
93
97
  }
94
98
 
95
- const isCallback = 'decrypt' in keyOrDecrypter;
99
+ const ciphertext = await DataStream.toBytes(cipherStream);
100
+ const actualDataCid = await Cid.computeDagPbCidFromBytes(ciphertext);
101
+ Records.validateEncryptedDataIntegrity(recordsWrite, actualDataCid, ciphertext.byteLength);
96
102
 
97
- // Parse the JWE protected header to determine the content encryption algorithm
98
- const protectedHeader = Encryption.parseProtectedHeader(encryption.protected);
99
- const enc = protectedHeader.enc;
103
+ const { fullDerivationPath, leafPrivateKey, matchingKeyEncryption } =
104
+ await Records.findMatchingKeyEncryption(recordsWrite, keyOrDecrypter);
100
105
 
101
- // Find matching recipient entry by rootKeyId and derivationScheme
102
- const matchingRecipient = encryption.recipients.find(r =>
103
- r.header.kid === keyOrDecrypter.rootKeyId &&
104
- r.header.derivationScheme === keyOrDecrypter.derivationScheme
105
- );
106
- if (matchingRecipient === undefined) {
106
+ if (matchingKeyEncryption === undefined) {
107
107
  throw new DwnError(
108
108
  DwnErrorCode.RecordsDecryptNoMatchingKeyEncryptedFound,
109
- `Unable to find a JWE recipient matching key \
110
- with ID '${keyOrDecrypter.rootKeyId}' and '${keyOrDecrypter.derivationScheme}' derivation scheme.`
109
+ `Unable to find a matching key encryption entry for '${keyOrDecrypter.rootKeyId}' and '${keyOrDecrypter.derivationScheme}'.`
111
110
  );
112
111
  }
113
112
 
114
- // Construct the full derivation path
115
- const fullDerivationPath = Records.constructKeyDerivationPath(
116
- matchingRecipient.header.derivationScheme, recordsWrite,
117
- );
118
-
119
113
  let cek: Uint8Array;
120
114
 
121
- if (isCallback) {
122
- // Callback-based: delegate HKDF + ECDH-ES + AES Key Unwrap to the KeyDecrypter
123
- const encryptedKeyBytes = Encoder.base64UrlToBytes(matchingRecipient.encrypted_key);
124
-
115
+ if ('decrypt' in keyOrDecrypter) {
125
116
  cek = await keyOrDecrypter.decrypt(fullDerivationPath, {
126
- encryptedKey : encryptedKeyBytes,
127
- ephemeralPublicKey : matchingRecipient.header.epk,
117
+ encryptedKey : Encoder.base64UrlToBytes(matchingKeyEncryption.encryptedKey),
118
+ ephemeralPublicKey : matchingKeyEncryption.ephemeralPublicKey,
119
+ keyEncryption : matchingKeyEncryption,
128
120
  });
129
121
  } else {
130
- // Raw-key path: derive the leaf private key, then ECDH-ES + AES Key Unwrap
131
- const leafPrivateKeyBytes = await Records.derivePrivateKey(keyOrDecrypter, fullDerivationPath);
132
- const leafPrivateKeyJwk = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
133
- const wrappedKeyBytes = Encoder.base64UrlToBytes(matchingRecipient.encrypted_key);
134
-
135
- cek = await Encryption.ecdhEsUnwrapKey(
136
- leafPrivateKeyJwk,
137
- matchingRecipient.header.epk as Jwk,
138
- wrappedKeyBytes,
139
- );
122
+ cek = await Encryption.unwrapKey(leafPrivateKey!, matchingKeyEncryption);
140
123
  }
141
124
 
142
- // AEAD decrypt data using the CEK
143
- const iv = Encoder.base64UrlToBytes(encryption.iv);
144
- const tag = Encoder.base64UrlToBytes(encryption.tag);
145
- const plaintextStream = await Encryption.aeadDecryptStream(enc, cek, iv, cipherStream, tag);
125
+ const iv = Encoder.base64UrlToBytes(encryption.initializationVector);
126
+ const plaintext = await Encryption.decrypt(encryption.algorithm, cek, iv, ciphertext);
127
+ const plaintextStream = DataStream.fromBytes(plaintext);
146
128
 
147
129
  return plaintextStream;
148
130
  }
149
131
 
132
+ private static async findMatchingKeyEncryption(
133
+ recordsWrite: RecordsWriteMessage,
134
+ keyOrDecrypter: DerivedPrivateJwk | KeyDecrypter,
135
+ ): Promise<{ fullDerivationPath: string[]; leafPrivateKey?: Jwk; matchingKeyEncryption?: KeyEncryption }> {
136
+ const { encryption } = recordsWrite;
137
+ const fullDerivationPath = Records.constructKeyDerivationPath(keyOrDecrypter.derivationScheme, recordsWrite);
138
+
139
+ if ('decrypt' in keyOrDecrypter) {
140
+ const publicKey = await keyOrDecrypter.derivePublicKey(fullDerivationPath);
141
+ const keyId = await Encryption.getKeyId(publicKey);
142
+ return {
143
+ fullDerivationPath,
144
+ matchingKeyEncryption: encryption!.keyEncryption.find((entry): boolean =>
145
+ entry.derivationScheme === keyOrDecrypter.derivationScheme && entry.keyId === keyId
146
+ ),
147
+ };
148
+ }
149
+
150
+ const leafPrivateKeyBytes = await Records.derivePrivateKey(keyOrDecrypter, fullDerivationPath);
151
+ const leafPrivateKey = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
152
+
153
+ const publicKey = await X25519.getPublicKey({ key: leafPrivateKey });
154
+ const keyId = keyOrDecrypter.keyId ?? await Encryption.getKeyId(publicKey as PublicKeyJwk);
155
+
156
+ return {
157
+ fullDerivationPath,
158
+ leafPrivateKey,
159
+ matchingKeyEncryption: encryption!.keyEncryption.find((entry): boolean =>
160
+ entry.derivationScheme === keyOrDecrypter.derivationScheme && entry.keyId === keyId
161
+ ),
162
+ };
163
+ }
164
+
165
+ private static validateEncryptedDataIntegrity(
166
+ recordsWrite: RecordsWriteMessage,
167
+ actualDataCid: string,
168
+ actualDataSize: number,
169
+ ): void {
170
+ const { dataCid, dataSize } = recordsWrite.descriptor;
171
+ if (dataCid !== actualDataCid) {
172
+ throw new DwnError(
173
+ DwnErrorCode.RecordsWriteDataCidMismatch,
174
+ `actual data CID ${actualDataCid} does not match dataCid in descriptor: ${dataCid}`
175
+ );
176
+ }
177
+
178
+ if (dataSize !== actualDataSize) {
179
+ throw new DwnError(
180
+ DwnErrorCode.RecordsWriteDataSizeMismatch,
181
+ `actual data size ${actualDataSize} bytes does not match dataSize in descriptor: ${dataSize}`
182
+ );
183
+ }
184
+ }
185
+
150
186
  /**
151
187
  * Constructs full key derivation path using the specified scheme.
152
188
  */
153
189
  public static constructKeyDerivationPath(
154
- keyDerivationScheme: KeyDerivationScheme,
190
+ keyDerivationScheme: KeyDerivationScheme | typeof ROLE_AUDIENCE_DERIVATION_SCHEME,
155
191
  recordsWriteMessage: RecordsWriteMessage
156
192
  ): string[] {
157
193
 
158
194
  const descriptor = recordsWriteMessage.descriptor;
159
- const contextId = recordsWriteMessage.contextId;
160
-
161
- let fullDerivationPath;
162
195
  if (keyDerivationScheme === KeyDerivationScheme.ProtocolPath) {
163
- fullDerivationPath = Records.constructKeyDerivationPathUsingProtocolPathScheme(descriptor);
164
- } else {
165
- // `protocolContext` scheme
166
- fullDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(contextId);
196
+ return Records.constructKeyDerivationPathUsingProtocolPathScheme(descriptor);
167
197
  }
168
198
 
169
- return fullDerivationPath;
199
+ if (keyDerivationScheme === ROLE_AUDIENCE_DERIVATION_SCHEME) {
200
+ return [ROLE_AUDIENCE_DERIVATION_SCHEME];
201
+ }
202
+
203
+ throw new DwnError(
204
+ DwnErrorCode.RecordsDecryptUnsupportedKeyDerivationScheme,
205
+ `Unsupported key derivation scheme '${keyDerivationScheme as string}'.`
206
+ );
170
207
  }
171
208
 
172
209
  /**
@@ -188,28 +225,6 @@ export class Records {
188
225
  return fullDerivationPath;
189
226
  }
190
227
 
191
- /**
192
- * Constructs the full key derivation path using `protocolContext` scheme.
193
- *
194
- * NOTE on protocol composition: When a context tree spans two protocols via `$ref` composition,
195
- * the root `contextId` segment (the `$ref` parent record's ID) is shared across both protocols.
196
- * This means ProtocolContext-encrypted records from the composing protocol and the referenced
197
- * protocol derive the same context key. This is by design — it enables multi-party access within
198
- * a shared context (e.g., thread participants can decrypt messages from both the threads protocol
199
- * and composing protocols that attach to those threads).
200
- */
201
- public static constructKeyDerivationPathUsingProtocolContextScheme(contextId: string): string[] {
202
- // TODO: Extend key derivation support to include the full contextId (https://github.com/enboxorg/enbox/issues/99)
203
- const firstContextSegment = contextId.split('/')[0];
204
-
205
- const fullDerivationPath = [
206
- KeyDerivationScheme.ProtocolContext,
207
- firstContextSegment
208
- ];
209
-
210
- return fullDerivationPath;
211
- }
212
-
213
228
  /**
214
229
  * Derives a descendant private key given an ancestor private key and the full absolute derivation path.
215
230
  * Uses X25519 keys for encryption key derivation.
@@ -1,6 +1,7 @@
1
1
  import type { GenericMessage } from '../types/message-types.js';
2
2
  import type { KeyValues } from '../types/query-types.js';
3
3
 
4
+ import { EncryptionProtocol } from '../protocols/encryption.js';
4
5
  import { PermissionsProtocol } from '../protocols/permissions.js';
5
6
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
6
7
 
@@ -24,6 +25,25 @@ export class Replication {
24
25
  return `perm:${protocolUri}`;
25
26
  }
26
27
 
28
+ public static encryptionDomain(protocolUri: string): string {
29
+ return `enc:${protocolUri}`;
30
+ }
31
+
32
+ public static taggedCoreProtocolDomains(protocolUri: string, protocolsInScope: ReadonlySet<string> = new Set()): string[] {
33
+ if (Replication.isCoreProtocolUri(protocolUri)) {
34
+ return [];
35
+ }
36
+
37
+ const domains: string[] = [];
38
+ if (!protocolsInScope.has(PermissionsProtocol.uri)) {
39
+ domains.push(Replication.permissionDomain(protocolUri));
40
+ }
41
+ if (!protocolsInScope.has(EncryptionProtocol.uri)) {
42
+ domains.push(Replication.encryptionDomain(protocolUri));
43
+ }
44
+ return domains;
45
+ }
46
+
27
47
  public static async deriveStreamId(tenant: string): Promise<string> {
28
48
  const bytes = new TextEncoder().encode(tenant);
29
49
  const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
@@ -39,11 +59,13 @@ export class Replication {
39
59
  if (typeof protocol === 'string') {
40
60
  scopes.push(Replication.protocolDomain(protocol));
41
61
 
42
- if (protocol === PermissionsProtocol.uri) {
43
- const indexedTaggedProtocol = indexes['tag.protocol'];
44
- const taggedProtocol = indexedTaggedProtocol ?? descriptor.tags?.protocol;
45
- if (typeof taggedProtocol === 'string') {
62
+ const indexedTaggedProtocol = indexes['tag.protocol'];
63
+ const taggedProtocol = indexedTaggedProtocol ?? descriptor.tags?.protocol;
64
+ if (typeof taggedProtocol === 'string') {
65
+ if (protocol === PermissionsProtocol.uri) {
46
66
  scopes.push(Replication.permissionDomain(taggedProtocol));
67
+ } else if (protocol === EncryptionProtocol.uri) {
68
+ scopes.push(Replication.encryptionDomain(taggedProtocol));
47
69
  }
48
70
  }
49
71
  }
@@ -78,6 +100,10 @@ export class Replication {
78
100
  );
79
101
  }
80
102
 
103
+ private static isCoreProtocolUri(protocolUri: string): boolean {
104
+ return protocolUri === PermissionsProtocol.uri || protocolUri === EncryptionProtocol.uri;
105
+ }
106
+
81
107
  public static async hashMessageCid(messageCid: string): Promise<Uint8Array> {
82
108
  const bytes = new TextEncoder().encode(messageCid);
83
109
  const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
@@ -1,70 +0,0 @@
1
- {
2
- "protocol": "http://chat-protocol.xyz",
3
- "published": true,
4
- "types": {
5
- "thread": {
6
- "schema": "thread",
7
- "dataFormats": [
8
- "application/json"
9
- ]
10
- },
11
- "message": {
12
- "schema": "message",
13
- "dataFormats": [
14
- "application/json"
15
- ]
16
- }
17
- },
18
- "structure": {
19
- "thread": {
20
- "$actions": [
21
- {
22
- "who": "anyone",
23
- "can": [
24
- "create",
25
- "update"
26
- ]
27
- },
28
- {
29
- "who": "author",
30
- "of": "thread",
31
- "can": [
32
- "read"
33
- ]
34
- },
35
- {
36
- "who": "recipient",
37
- "of": "thread",
38
- "can": [
39
- "read"
40
- ]
41
- }
42
- ],
43
- "message": {
44
- "$actions": [
45
- {
46
- "who": "anyone",
47
- "can": [
48
- "create",
49
- "update"
50
- ]
51
- },
52
- {
53
- "who": "author",
54
- "of": "thread/message",
55
- "can": [
56
- "read"
57
- ]
58
- },
59
- {
60
- "who": "recipient",
61
- "of": "thread/message",
62
- "can": [
63
- "read"
64
- ]
65
- }
66
- ]
67
- }
68
- }
69
- }
70
- }