@enbox/dwn-sdk-js 0.4.3 → 0.4.5

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 (204) 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 -2200
  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 +3 -3
  39. package/dist/esm/src/protocols/permission-grant.js.map +1 -1
  40. package/dist/esm/src/protocols/permissions.js +1 -0
  41. package/dist/esm/src/protocols/permissions.js.map +1 -1
  42. package/dist/esm/src/types/message-types.js.map +1 -1
  43. package/dist/esm/src/types/permission-types.js.map +1 -1
  44. package/dist/esm/src/types/protocols-types.js.map +1 -1
  45. package/dist/esm/src/utils/encryption.js +135 -187
  46. package/dist/esm/src/utils/encryption.js.map +1 -1
  47. package/dist/esm/src/utils/hd-key.js +0 -1
  48. package/dist/esm/src/utils/hd-key.js.map +1 -1
  49. package/dist/esm/src/utils/permission-scope.js +9 -6
  50. package/dist/esm/src/utils/permission-scope.js.map +1 -1
  51. package/dist/esm/src/utils/protocols.js +12 -9
  52. package/dist/esm/src/utils/protocols.js.map +1 -1
  53. package/dist/esm/src/utils/records.js +54 -56
  54. package/dist/esm/src/utils/records.js.map +1 -1
  55. package/dist/esm/src/utils/replication.js +27 -4
  56. package/dist/esm/src/utils/replication.js.map +1 -1
  57. package/dist/esm/tests/core/replication-apply.spec.js +141 -1
  58. package/dist/esm/tests/core/replication-apply.spec.js.map +1 -1
  59. package/dist/esm/tests/core/replication-replay-property.spec.js +1 -0
  60. package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
  61. package/dist/esm/tests/core/validation-read-closure.spec.js +111 -0
  62. package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
  63. package/dist/esm/tests/core/validation-state-reader.spec.js +89 -0
  64. package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -1
  65. package/dist/esm/tests/features/protocol-composition.spec.js +30 -31
  66. package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
  67. package/dist/esm/tests/fuzz/encryption.fuzz.spec.js +29 -121
  68. package/dist/esm/tests/fuzz/encryption.fuzz.spec.js.map +1 -1
  69. package/dist/esm/tests/handlers/messages-query.spec.js +71 -3
  70. package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
  71. package/dist/esm/tests/handlers/messages-read.spec.js +2 -2
  72. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  73. package/dist/esm/tests/handlers/records-read.spec.js +9 -135
  74. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  75. package/dist/esm/tests/handlers/records-write.spec.js +279 -19
  76. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  77. package/dist/esm/tests/interfaces/protocols-configure.spec.js +17 -22
  78. package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
  79. package/dist/esm/tests/interfaces/records-write.spec.js +170 -150
  80. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  81. package/dist/esm/tests/protocols/encryption.spec.js +914 -0
  82. package/dist/esm/tests/protocols/encryption.spec.js.map +1 -0
  83. package/dist/esm/tests/protocols/permission-grant.spec.js +55 -0
  84. package/dist/esm/tests/protocols/permission-grant.spec.js.map +1 -1
  85. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +100 -100
  86. package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
  87. package/dist/esm/tests/store/message-store-level.spec.js +20 -2
  88. package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
  89. package/dist/esm/tests/utils/encryption-callbacks.spec.js +88 -125
  90. package/dist/esm/tests/utils/encryption-callbacks.spec.js.map +1 -1
  91. package/dist/esm/tests/utils/encryption.spec.js +125 -255
  92. package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
  93. package/dist/esm/tests/utils/permission-scope.spec.js +5 -1
  94. package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -1
  95. package/dist/esm/tests/utils/records.spec.js +15 -5
  96. package/dist/esm/tests/utils/records.spec.js.map +1 -1
  97. package/dist/esm/tests/utils/test-data-generator.js +7 -44
  98. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  99. package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js +63 -0
  100. package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js.map +1 -0
  101. package/dist/types/generated/precompiled-validators.d.ts +140 -92
  102. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  103. package/dist/types/src/core/constants.d.ts +2 -2
  104. package/dist/types/src/core/constants.d.ts.map +1 -1
  105. package/dist/types/src/core/core-protocol.d.ts +1 -1
  106. package/dist/types/src/core/core-protocol.d.ts.map +1 -1
  107. package/dist/types/src/core/dwn-error.d.ts +29 -1
  108. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  109. package/dist/types/src/core/messages-grant-authorization.d.ts +2 -0
  110. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  111. package/dist/types/src/core/protocol-authorization-validation.d.ts +2 -2
  112. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  113. package/dist/types/src/core/recording-validation-state-reader.d.ts +9 -0
  114. package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -1
  115. package/dist/types/src/core/replication-apply.d.ts +4 -3
  116. package/dist/types/src/core/replication-apply.d.ts.map +1 -1
  117. package/dist/types/src/core/validation-state-reader.d.ts +9 -0
  118. package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
  119. package/dist/types/src/dwn.d.ts +1 -0
  120. package/dist/types/src/dwn.d.ts.map +1 -1
  121. package/dist/types/src/handlers/messages-query.d.ts.map +1 -1
  122. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  123. package/dist/types/src/index.d.ts +6 -5
  124. package/dist/types/src/index.d.ts.map +1 -1
  125. package/dist/types/src/interfaces/protocols-configure.d.ts +1 -1
  126. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  127. package/dist/types/src/interfaces/records-write-signing.d.ts +4 -8
  128. package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -1
  129. package/dist/types/src/interfaces/records-write.d.ts +10 -26
  130. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  131. package/dist/types/src/protocols/encryption.d.ts +43 -0
  132. package/dist/types/src/protocols/encryption.d.ts.map +1 -0
  133. package/dist/types/src/protocols/permission-grant.d.ts +3 -6
  134. package/dist/types/src/protocols/permission-grant.d.ts.map +1 -1
  135. package/dist/types/src/protocols/permissions.d.ts +2 -1
  136. package/dist/types/src/protocols/permissions.d.ts.map +1 -1
  137. package/dist/types/src/types/encryption-types.d.ts +5 -38
  138. package/dist/types/src/types/encryption-types.d.ts.map +1 -1
  139. package/dist/types/src/types/message-types.d.ts +0 -13
  140. package/dist/types/src/types/message-types.d.ts.map +1 -1
  141. package/dist/types/src/types/permission-types.d.ts +44 -9
  142. package/dist/types/src/types/permission-types.d.ts.map +1 -1
  143. package/dist/types/src/types/protocols-types.d.ts +9 -14
  144. package/dist/types/src/types/protocols-types.d.ts.map +1 -1
  145. package/dist/types/src/types/records-types.d.ts +3 -3
  146. package/dist/types/src/types/records-types.d.ts.map +1 -1
  147. package/dist/types/src/types/validation-state-reader.d.ts +12 -0
  148. package/dist/types/src/types/validation-state-reader.d.ts.map +1 -1
  149. package/dist/types/src/utils/encryption.d.ts +54 -145
  150. package/dist/types/src/utils/encryption.d.ts.map +1 -1
  151. package/dist/types/src/utils/hd-key.d.ts +1 -1
  152. package/dist/types/src/utils/hd-key.d.ts.map +1 -1
  153. package/dist/types/src/utils/permission-scope.d.ts +4 -3
  154. package/dist/types/src/utils/permission-scope.d.ts.map +1 -1
  155. package/dist/types/src/utils/protocols.d.ts +3 -5
  156. package/dist/types/src/utils/protocols.d.ts.map +1 -1
  157. package/dist/types/src/utils/records.d.ts +5 -13
  158. package/dist/types/src/utils/records.d.ts.map +1 -1
  159. package/dist/types/src/utils/replication.d.ts +3 -0
  160. package/dist/types/src/utils/replication.d.ts.map +1 -1
  161. package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
  162. package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
  163. package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
  164. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  165. package/dist/types/tests/protocols/encryption.spec.d.ts +2 -0
  166. package/dist/types/tests/protocols/encryption.spec.d.ts.map +1 -0
  167. package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
  168. package/dist/types/tests/utils/test-data-generator.d.ts +0 -5
  169. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  170. package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts +2 -0
  171. package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts.map +1 -0
  172. package/package.json +3 -3
  173. package/src/core/constants.ts +3 -3
  174. package/src/core/core-protocol.ts +1 -1
  175. package/src/core/dwn-error.ts +29 -1
  176. package/src/core/messages-grant-authorization.ts +27 -0
  177. package/src/core/protocol-authorization-validation.ts +145 -6
  178. package/src/core/recording-validation-state-reader.ts +13 -0
  179. package/src/core/replication-apply.ts +222 -8
  180. package/src/core/validation-state-reader.ts +30 -0
  181. package/src/dwn.ts +24 -2
  182. package/src/handlers/messages-query.ts +6 -5
  183. package/src/handlers/protocols-configure.ts +19 -3
  184. package/src/handlers/records-write.ts +1 -1
  185. package/src/index.ts +15 -5
  186. package/src/interfaces/protocols-configure.ts +39 -12
  187. package/src/interfaces/records-write-signing.ts +5 -12
  188. package/src/interfaces/records-write.ts +75 -92
  189. package/src/protocols/encryption.ts +649 -0
  190. package/src/protocols/permission-grant.ts +4 -5
  191. package/src/protocols/permissions.ts +8 -6
  192. package/src/types/encryption-types.ts +9 -38
  193. package/src/types/message-types.ts +0 -13
  194. package/src/types/permission-types.ts +57 -9
  195. package/src/types/protocols-types.ts +9 -16
  196. package/src/types/records-types.ts +3 -3
  197. package/src/types/validation-state-reader.ts +13 -0
  198. package/src/utils/encryption.ts +213 -283
  199. package/src/utils/hd-key.ts +1 -1
  200. package/src/utils/permission-scope.ts +10 -6
  201. package/src/utils/protocols.ts +16 -15
  202. package/src/utils/records.ts +86 -71
  203. package/src/utils/replication.ts +30 -4
  204. package/dist/esm/tests/vectors/protocol-definitions/chat.json +0 -70
@@ -0,0 +1,914 @@
1
+ import { TestDataGenerator } from '../utils/test-data-generator.js';
2
+ import { describe, expect, it } from 'bun:test';
3
+ import { ContentEncryptionAlgorithm, DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, Encryption, EncryptionProtocol, Jws, KeyDerivationScheme, RecordsWrite, Time, } from '../../src/index.js';
4
+ describe('EncryptionProtocol', () => {
5
+ it('should expose the encryption protocol uri and definition', () => {
6
+ const encryptionProtocol = new EncryptionProtocol();
7
+ expect(encryptionProtocol.uri).toBe(EncryptionProtocol.uri);
8
+ expect(encryptionProtocol.definition).toBe(EncryptionProtocol.definition);
9
+ });
10
+ describe('preProcessWrite()', () => {
11
+ it('should ignore records outside the encryption protocol paths', async () => {
12
+ const alice = await TestDataGenerator.generatePersona();
13
+ const recordsWrite = await RecordsWrite.create({
14
+ data: TestDataGenerator.randomBytes(32),
15
+ dataFormat: 'application/json',
16
+ protocol: EncryptionProtocol.uri,
17
+ protocolPath: 'other',
18
+ signer: Jws.createSigner(alice),
19
+ });
20
+ const encryptionProtocol = new EncryptionProtocol();
21
+ await encryptionProtocol.preProcessWrite(alice.did, recordsWrite.message, createValidationStateReader({}));
22
+ });
23
+ it('should accept audienceEpoch records for role paths with key agreement', async () => {
24
+ const alice = await TestDataGenerator.generatePersona();
25
+ const protocol = 'https://example.com/protocol/chat';
26
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
27
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
28
+ const message = await createAudienceEpochMessage({
29
+ protocol,
30
+ role: 'chat/member',
31
+ contextId: 'chat1',
32
+ epoch: 1,
33
+ publicKeyJwk: rolePublicKey,
34
+ signer: Jws.createSigner(alice),
35
+ });
36
+ const encryptionProtocol = new EncryptionProtocol();
37
+ await encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({ protocolDefinition }));
38
+ });
39
+ it('should reject audienceEpoch records that are encrypted', async () => {
40
+ const alice = await TestDataGenerator.generatePersona();
41
+ const protocol = 'https://example.com/protocol/chat';
42
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
43
+ const message = await createAudienceEpochMessage({
44
+ protocol,
45
+ role: 'chat/member',
46
+ contextId: 'chat1',
47
+ epoch: 1,
48
+ publicKeyJwk: rolePublicKey,
49
+ signer: Jws.createSigner(alice),
50
+ });
51
+ message.encryption = createTestEncryptionProperty();
52
+ const encryptionProtocol = new EncryptionProtocol();
53
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({}))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceEpochEncrypted);
54
+ });
55
+ it('should reject audienceEpoch records with missing required tags', async () => {
56
+ const alice = await TestDataGenerator.generatePersona();
57
+ const protocol = 'https://example.com/protocol/chat';
58
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
59
+ const message = await createAudienceEpochMessage({
60
+ protocol,
61
+ role: 'chat/member',
62
+ contextId: 'chat1',
63
+ epoch: 1,
64
+ publicKeyJwk: rolePublicKey,
65
+ signer: Jws.createSigner(alice),
66
+ });
67
+ delete message.descriptor.tags.contextId;
68
+ const encryptionProtocol = new EncryptionProtocol();
69
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({}))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceMissingRequiredTag);
70
+ });
71
+ it('should reject audienceEpoch records with malformed integer tags', async () => {
72
+ const alice = await TestDataGenerator.generatePersona();
73
+ const protocol = 'https://example.com/protocol/chat';
74
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
75
+ const message = await createAudienceEpochMessage({
76
+ protocol,
77
+ role: 'chat/member',
78
+ contextId: 'chat1',
79
+ epoch: 1,
80
+ publicKeyJwk: rolePublicKey,
81
+ signer: Jws.createSigner(alice),
82
+ });
83
+ message.descriptor.tags.epoch = '1';
84
+ const encryptionProtocol = new EncryptionProtocol();
85
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({}))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceMissingRequiredTag);
86
+ });
87
+ it('should reject audienceEpoch records with inconsistent role context', async () => {
88
+ const alice = await TestDataGenerator.generatePersona();
89
+ const protocol = 'https://example.com/protocol/chat';
90
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
91
+ const message = await createAudienceEpochMessage({
92
+ protocol,
93
+ role: 'chat/member',
94
+ contextId: '',
95
+ epoch: 1,
96
+ publicKeyJwk: rolePublicKey,
97
+ signer: Jws.createSigner(alice),
98
+ });
99
+ const encryptionProtocol = new EncryptionProtocol();
100
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({}))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateRoleAudienceContextInvalid);
101
+ });
102
+ it('should accept same-coordinate audienceEpoch records with different keyIds', async () => {
103
+ const alice = await TestDataGenerator.generatePersona();
104
+ const other = await TestDataGenerator.generatePersona();
105
+ const protocol = 'https://example.com/protocol/chat';
106
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
107
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
108
+ const conflictingEpoch = await createAudienceEpochMessage({
109
+ protocol,
110
+ role: 'chat/member',
111
+ contextId: 'chat1',
112
+ epoch: 1,
113
+ publicKeyJwk: other.encryptionKeyPair.publicJwk,
114
+ signer: Jws.createSigner(other),
115
+ });
116
+ const message = await createAudienceEpochMessage({
117
+ protocol,
118
+ role: 'chat/member',
119
+ contextId: 'chat1',
120
+ epoch: 1,
121
+ publicKeyJwk: rolePublicKey,
122
+ signer: Jws.createSigner(alice),
123
+ });
124
+ const encryptionProtocol = new EncryptionProtocol();
125
+ await encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({
126
+ audienceEpochs: [conflictingEpoch],
127
+ protocolDefinition,
128
+ }));
129
+ });
130
+ it('should reject audienceEpoch records authored by DIDs that cannot create the role', async () => {
131
+ const alice = await TestDataGenerator.generatePersona();
132
+ const protocol = 'https://example.com/protocol/chat';
133
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
134
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, { memberActions: [] });
135
+ const message = await createAudienceEpochMessage({
136
+ protocol,
137
+ role: 'chat/member',
138
+ contextId: 'chat1',
139
+ epoch: 1,
140
+ publicKeyJwk: rolePublicKey,
141
+ signer: Jws.createSigner(alice),
142
+ });
143
+ const encryptionProtocol = new EncryptionProtocol();
144
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ protocolDefinition }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceWriterUnauthorized);
145
+ });
146
+ it('should accept audienceEpoch records authored through an active local role', async () => {
147
+ const alice = await TestDataGenerator.generatePersona();
148
+ const protocol = 'https://example.com/protocol/chat';
149
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
150
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, {
151
+ memberActions: [{ can: ['create'], role: 'chat/admin' }],
152
+ });
153
+ const message = await createAudienceEpochMessage({
154
+ protocol,
155
+ role: 'chat/member',
156
+ contextId: 'chat1',
157
+ epoch: 1,
158
+ publicKeyJwk: rolePublicKey,
159
+ signer: Jws.createSigner(alice),
160
+ protocolRole: 'chat/admin',
161
+ });
162
+ const encryptionProtocol = new EncryptionProtocol();
163
+ await encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({
164
+ hasMatchingRoleRecord: (input) => {
165
+ return input.protocol === protocol &&
166
+ input.protocolPath === 'chat/admin' &&
167
+ input.recipient === alice.did &&
168
+ input.contextIdPrefix === 'chat1';
169
+ },
170
+ protocolDefinition,
171
+ }));
172
+ });
173
+ it('should reject audienceEpoch records when the invoked role does not match the create rule', async () => {
174
+ const alice = await TestDataGenerator.generatePersona();
175
+ const protocol = 'https://example.com/protocol/chat';
176
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
177
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, {
178
+ memberActions: [{ can: ['create'], role: 'chat/admin' }],
179
+ });
180
+ const message = await createAudienceEpochMessage({
181
+ protocol,
182
+ role: 'chat/member',
183
+ contextId: 'chat1',
184
+ epoch: 1,
185
+ publicKeyJwk: rolePublicKey,
186
+ signer: Jws.createSigner(alice),
187
+ protocolRole: 'chat/moderator',
188
+ });
189
+ const encryptionProtocol = new EncryptionProtocol();
190
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ protocolDefinition }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceWriterUnauthorized);
191
+ });
192
+ it('should accept audienceEpoch records authored through an active cross-protocol role', async () => {
193
+ const alice = await TestDataGenerator.generatePersona();
194
+ const protocol = 'https://example.com/protocol/chat';
195
+ const roleProtocol = 'https://example.com/protocol/roles';
196
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
197
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, {
198
+ memberActions: [{ can: ['create'], role: 'roles:admin' }],
199
+ uses: { roles: roleProtocol },
200
+ });
201
+ const message = await createAudienceEpochMessage({
202
+ protocol,
203
+ role: 'chat/member',
204
+ contextId: 'chat1',
205
+ epoch: 1,
206
+ publicKeyJwk: rolePublicKey,
207
+ signer: Jws.createSigner(alice),
208
+ protocolRole: 'roles:admin',
209
+ });
210
+ const encryptionProtocol = new EncryptionProtocol();
211
+ await encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({
212
+ hasMatchingRoleRecord: (input) => {
213
+ return input.protocol === roleProtocol &&
214
+ input.protocolPath === 'admin' &&
215
+ input.recipient === alice.did &&
216
+ input.contextIdPrefix === undefined;
217
+ },
218
+ protocolDefinition,
219
+ }));
220
+ });
221
+ it('should reject audienceEpoch records that invoke a cross-protocol role with an unknown alias', async () => {
222
+ const alice = await TestDataGenerator.generatePersona();
223
+ const protocol = 'https://example.com/protocol/chat';
224
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
225
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, {
226
+ memberActions: [{ can: ['create'], role: 'roles:admin' }],
227
+ });
228
+ const message = await createAudienceEpochMessage({
229
+ protocol,
230
+ role: 'chat/member',
231
+ contextId: 'chat1',
232
+ epoch: 1,
233
+ publicKeyJwk: rolePublicKey,
234
+ signer: Jws.createSigner(alice),
235
+ protocolRole: 'roles:admin',
236
+ });
237
+ const encryptionProtocol = new EncryptionProtocol();
238
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ protocolDefinition }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceWriterUnauthorized);
239
+ });
240
+ it('should reject audienceKey records without a matching audienceEpoch', async () => {
241
+ const alice = await TestDataGenerator.generatePersona();
242
+ const bob = await TestDataGenerator.generatePersona();
243
+ const protocol = 'https://example.com/protocol/chat';
244
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
245
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
246
+ const message = await createAudienceKeyMessage({
247
+ protocol,
248
+ role: 'chat/member',
249
+ contextId: 'chat1',
250
+ epoch: 1,
251
+ publicKeyJwk: rolePublicKey,
252
+ recipient: bob.did,
253
+ signer: Jws.createSigner(alice),
254
+ });
255
+ const encryptionProtocol = new EncryptionProtocol();
256
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({
257
+ audienceEpochs: [],
258
+ hasMatchingRole: true,
259
+ protocolDefinition,
260
+ }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceEpochMissing);
261
+ });
262
+ it('should reject audienceEpoch records for role paths without key agreement', async () => {
263
+ const alice = await TestDataGenerator.generatePersona();
264
+ const protocol = 'https://example.com/protocol/chat';
265
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
266
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey, { keyAgreement: false });
267
+ const message = await createAudienceEpochMessage({
268
+ protocol,
269
+ role: 'chat/member',
270
+ contextId: 'chat1',
271
+ epoch: 1,
272
+ publicKeyJwk: rolePublicKey,
273
+ signer: Jws.createSigner(alice),
274
+ });
275
+ const encryptionProtocol = new EncryptionProtocol();
276
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({ protocolDefinition }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateRoleAudienceInvalid);
277
+ });
278
+ it('should accept audienceKey records for active role holders', async () => {
279
+ const alice = await TestDataGenerator.generatePersona();
280
+ const bob = await TestDataGenerator.generatePersona();
281
+ const protocol = 'https://example.com/protocol/chat';
282
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
283
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
284
+ const audienceEpoch = await createAudienceEpochMessage({
285
+ protocol,
286
+ role: 'chat/member',
287
+ contextId: 'chat1',
288
+ epoch: 1,
289
+ publicKeyJwk: rolePublicKey,
290
+ signer: Jws.createSigner(alice),
291
+ });
292
+ const message = await createAudienceKeyMessage({
293
+ protocol,
294
+ role: 'chat/member',
295
+ contextId: 'chat1',
296
+ epoch: 1,
297
+ publicKeyJwk: rolePublicKey,
298
+ recipient: bob.did,
299
+ signer: Jws.createSigner(alice),
300
+ });
301
+ const encryptionProtocol = new EncryptionProtocol();
302
+ await encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({
303
+ audienceEpochs: [audienceEpoch],
304
+ hasMatchingRole: true,
305
+ protocolDefinition,
306
+ }));
307
+ });
308
+ it('should reject audienceKey records that are not encrypted', async () => {
309
+ const alice = await TestDataGenerator.generatePersona();
310
+ const bob = await TestDataGenerator.generatePersona();
311
+ const protocol = 'https://example.com/protocol/chat';
312
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
313
+ const message = await createAudienceKeyMessage({
314
+ protocol,
315
+ role: 'chat/member',
316
+ contextId: 'chat1',
317
+ epoch: 1,
318
+ publicKeyJwk: rolePublicKey,
319
+ recipient: bob.did,
320
+ signer: Jws.createSigner(alice),
321
+ encrypted: false,
322
+ });
323
+ const encryptionProtocol = new EncryptionProtocol();
324
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({}))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateEncryptedDeliveryMissingEncryption);
325
+ });
326
+ it('should reject audienceKey records without a recipient', async () => {
327
+ const alice = await TestDataGenerator.generatePersona();
328
+ const protocol = 'https://example.com/protocol/chat';
329
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
330
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
331
+ const audienceEpoch = await createAudienceEpochMessage({
332
+ protocol,
333
+ role: 'chat/member',
334
+ contextId: 'chat1',
335
+ epoch: 1,
336
+ publicKeyJwk: rolePublicKey,
337
+ signer: Jws.createSigner(alice),
338
+ });
339
+ const message = await createAudienceKeyMessage({
340
+ protocol,
341
+ role: 'chat/member',
342
+ contextId: 'chat1',
343
+ epoch: 1,
344
+ publicKeyJwk: rolePublicKey,
345
+ signer: Jws.createSigner(alice),
346
+ });
347
+ const encryptionProtocol = new EncryptionProtocol();
348
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({
349
+ audienceEpochs: [audienceEpoch],
350
+ protocolDefinition,
351
+ }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceKeyRecipientMissing);
352
+ });
353
+ it('should reject audienceKey records for recipients without the referenced role', async () => {
354
+ const alice = await TestDataGenerator.generatePersona();
355
+ const bob = await TestDataGenerator.generatePersona();
356
+ const protocol = 'https://example.com/protocol/chat';
357
+ const rolePublicKey = alice.encryptionKeyPair.publicJwk;
358
+ const protocolDefinition = createRoleProtocolDefinition(protocol, rolePublicKey);
359
+ const audienceEpoch = await createAudienceEpochMessage({
360
+ protocol,
361
+ role: 'chat/member',
362
+ contextId: 'chat1',
363
+ epoch: 1,
364
+ publicKeyJwk: rolePublicKey,
365
+ signer: Jws.createSigner(alice),
366
+ });
367
+ const message = await createAudienceKeyMessage({
368
+ protocol,
369
+ role: 'chat/member',
370
+ contextId: 'chat1',
371
+ epoch: 1,
372
+ publicKeyJwk: rolePublicKey,
373
+ recipient: bob.did,
374
+ signer: Jws.createSigner(alice),
375
+ });
376
+ const encryptionProtocol = new EncryptionProtocol();
377
+ await expect(encryptionProtocol.preProcessWrite(alice.did, message, createValidationStateReader({
378
+ audienceEpochs: [audienceEpoch],
379
+ hasMatchingRole: false,
380
+ protocolDefinition,
381
+ }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceKeyRoleRecordMissing);
382
+ });
383
+ it('should accept grantKey records covered by an ancestor protocolPath read grant', async () => {
384
+ const alice = await TestDataGenerator.generatePersona();
385
+ const bob = await TestDataGenerator.generatePersona();
386
+ const protocol = 'https://example.com/protocol';
387
+ const grant = createGrant({
388
+ grantor: alice.did,
389
+ grantee: bob.did,
390
+ id: 'grant1',
391
+ protocol,
392
+ protocolPath: 'message',
393
+ });
394
+ const message = await createGrantKeyMessage({
395
+ grant,
396
+ protocol,
397
+ protocolPath: 'message/reply',
398
+ recipient: bob.did,
399
+ signer: Jws.createSigner(alice),
400
+ });
401
+ const encryptionProtocol = new EncryptionProtocol();
402
+ await encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }));
403
+ });
404
+ it('should accept grantKey records covered by a Messages read grant', async () => {
405
+ const alice = await TestDataGenerator.generatePersona();
406
+ const bob = await TestDataGenerator.generatePersona();
407
+ const protocol = 'https://example.com/protocol';
408
+ const grant = createGrant({
409
+ grantor: alice.did,
410
+ grantee: bob.did,
411
+ id: 'grant1',
412
+ interface: DwnInterfaceName.Messages,
413
+ protocol,
414
+ });
415
+ const message = await createGrantKeyMessage({
416
+ grant,
417
+ protocol,
418
+ recipient: bob.did,
419
+ signer: Jws.createSigner(alice),
420
+ });
421
+ const encryptionProtocol = new EncryptionProtocol();
422
+ await encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }));
423
+ });
424
+ it('should reject protocol-scoped grantKey records for protocolPath-scoped read grants', async () => {
425
+ const alice = await TestDataGenerator.generatePersona();
426
+ const bob = await TestDataGenerator.generatePersona();
427
+ const protocol = 'https://example.com/protocol';
428
+ const grant = createGrant({
429
+ grantor: alice.did,
430
+ grantee: bob.did,
431
+ id: 'grant1',
432
+ protocol,
433
+ protocolPath: 'message',
434
+ });
435
+ const message = await createGrantKeyMessage({
436
+ grant,
437
+ protocol,
438
+ recipient: bob.did,
439
+ signer: Jws.createSigner(alice),
440
+ });
441
+ const encryptionProtocol = new EncryptionProtocol();
442
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyGrantScopeMismatch);
443
+ });
444
+ it('should reject grantKey records outside the grant protocol', async () => {
445
+ const alice = await TestDataGenerator.generatePersona();
446
+ const bob = await TestDataGenerator.generatePersona();
447
+ const grant = createGrant({
448
+ grantor: alice.did,
449
+ grantee: bob.did,
450
+ id: 'grant1',
451
+ protocol: 'https://example.com/protocol',
452
+ });
453
+ const message = await createGrantKeyMessage({
454
+ grant,
455
+ protocol: 'https://example.com/other',
456
+ recipient: bob.did,
457
+ signer: Jws.createSigner(alice),
458
+ });
459
+ const encryptionProtocol = new EncryptionProtocol();
460
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyGrantScopeMismatch);
461
+ });
462
+ it('should reject grantKey records outside the grant protocolPath subtree', async () => {
463
+ const alice = await TestDataGenerator.generatePersona();
464
+ const bob = await TestDataGenerator.generatePersona();
465
+ const protocol = 'https://example.com/protocol';
466
+ const grant = createGrant({
467
+ grantor: alice.did,
468
+ grantee: bob.did,
469
+ id: 'grant1',
470
+ protocol,
471
+ protocolPath: 'message',
472
+ });
473
+ const message = await createGrantKeyMessage({
474
+ grant,
475
+ protocol,
476
+ protocolPath: 'profile',
477
+ recipient: bob.did,
478
+ signer: Jws.createSigner(alice),
479
+ });
480
+ const encryptionProtocol = new EncryptionProtocol();
481
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyGrantScopeMismatch);
482
+ });
483
+ it('should reject grantKey records whose recipient is not the grant grantee', async () => {
484
+ const alice = await TestDataGenerator.generatePersona();
485
+ const bob = await TestDataGenerator.generatePersona();
486
+ const carol = await TestDataGenerator.generatePersona();
487
+ const protocol = 'https://example.com/protocol';
488
+ const grant = createGrant({
489
+ grantor: alice.did,
490
+ grantee: bob.did,
491
+ id: 'grant1',
492
+ protocol,
493
+ });
494
+ const message = await createGrantKeyMessage({
495
+ grant,
496
+ protocol,
497
+ recipient: carol.did,
498
+ signer: Jws.createSigner(alice),
499
+ });
500
+ const encryptionProtocol = new EncryptionProtocol();
501
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyRecipientMismatch);
502
+ });
503
+ it('should reject grantKey records not authored by the grantor', async () => {
504
+ const alice = await TestDataGenerator.generatePersona();
505
+ const bob = await TestDataGenerator.generatePersona();
506
+ const carol = await TestDataGenerator.generatePersona();
507
+ const protocol = 'https://example.com/protocol';
508
+ const grant = createGrant({
509
+ grantor: alice.did,
510
+ grantee: bob.did,
511
+ id: 'grant1',
512
+ protocol,
513
+ });
514
+ const message = await createGrantKeyMessage({
515
+ grant,
516
+ protocol,
517
+ recipient: bob.did,
518
+ signer: Jws.createSigner(carol),
519
+ });
520
+ const encryptionProtocol = new EncryptionProtocol();
521
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyAuthorMismatch);
522
+ });
523
+ it('should reject grantKey records with malformed optional protocolPath tags', async () => {
524
+ const alice = await TestDataGenerator.generatePersona();
525
+ const bob = await TestDataGenerator.generatePersona();
526
+ const protocol = 'https://example.com/protocol';
527
+ const grant = createGrant({
528
+ grantor: alice.did,
529
+ grantee: bob.did,
530
+ id: 'grant1',
531
+ protocol,
532
+ });
533
+ const message = await createGrantKeyMessage({
534
+ grant,
535
+ protocol,
536
+ recipient: bob.did,
537
+ signer: Jws.createSigner(alice),
538
+ });
539
+ message.descriptor.tags.protocolPath = 1;
540
+ const encryptionProtocol = new EncryptionProtocol();
541
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyMissingRequiredTag);
542
+ });
543
+ it('should reject grantKey records for grants that are not yet active', async () => {
544
+ const alice = await TestDataGenerator.generatePersona();
545
+ const bob = await TestDataGenerator.generatePersona();
546
+ const protocol = 'https://example.com/protocol';
547
+ const grant = createGrant({
548
+ dateGranted: Time.createOffsetTimestamp({ seconds: 3600 }),
549
+ grantor: alice.did,
550
+ grantee: bob.did,
551
+ id: 'grant1',
552
+ protocol,
553
+ });
554
+ const message = await createGrantKeyMessage({
555
+ grant,
556
+ protocol,
557
+ recipient: bob.did,
558
+ signer: Jws.createSigner(alice),
559
+ });
560
+ const encryptionProtocol = new EncryptionProtocol();
561
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.GrantAuthorizationGrantNotYetActive);
562
+ });
563
+ it('should reject grantKey records for expired grants', async () => {
564
+ const alice = await TestDataGenerator.generatePersona();
565
+ const bob = await TestDataGenerator.generatePersona();
566
+ const protocol = 'https://example.com/protocol';
567
+ const grant = createGrant({
568
+ dateExpires: Time.createOffsetTimestamp({ seconds: -1 }),
569
+ grantor: alice.did,
570
+ grantee: bob.did,
571
+ id: 'grant1',
572
+ protocol,
573
+ });
574
+ const message = await createGrantKeyMessage({
575
+ grant,
576
+ protocol,
577
+ recipient: bob.did,
578
+ signer: Jws.createSigner(alice),
579
+ });
580
+ const encryptionProtocol = new EncryptionProtocol();
581
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.GrantAuthorizationGrantExpired);
582
+ });
583
+ it('should reject grantKey records for revoked grants', async () => {
584
+ const alice = await TestDataGenerator.generatePersona();
585
+ const bob = await TestDataGenerator.generatePersona();
586
+ const protocol = 'https://example.com/protocol';
587
+ const grant = createGrant({
588
+ grantor: alice.did,
589
+ grantee: bob.did,
590
+ id: 'grant1',
591
+ protocol,
592
+ });
593
+ const message = await createGrantKeyMessage({
594
+ grant,
595
+ protocol,
596
+ recipient: bob.did,
597
+ signer: Jws.createSigner(alice),
598
+ });
599
+ const encryptionProtocol = new EncryptionProtocol();
600
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({
601
+ grant,
602
+ revocation: {
603
+ descriptor: {
604
+ messageTimestamp: Time.createOffsetTimestamp({ seconds: -30 }),
605
+ },
606
+ },
607
+ }))).rejects.toThrow(DwnErrorCode.GrantAuthorizationGrantRevoked);
608
+ });
609
+ it('should reject grantKey records that are not encrypted', async () => {
610
+ const alice = await TestDataGenerator.generatePersona();
611
+ const bob = await TestDataGenerator.generatePersona();
612
+ const protocol = 'https://example.com/protocol';
613
+ const grant = createGrant({
614
+ grantor: alice.did,
615
+ grantee: bob.did,
616
+ id: 'grant1',
617
+ protocol,
618
+ });
619
+ const message = await createGrantKeyMessage({
620
+ encrypted: false,
621
+ grant,
622
+ protocol,
623
+ recipient: bob.did,
624
+ signer: Jws.createSigner(alice),
625
+ });
626
+ const encryptionProtocol = new EncryptionProtocol();
627
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateEncryptedDeliveryMissingEncryption);
628
+ });
629
+ it('should reject grantKey records for non-read permission grants', async () => {
630
+ const alice = await TestDataGenerator.generatePersona();
631
+ const bob = await TestDataGenerator.generatePersona();
632
+ const protocol = 'https://example.com/protocol';
633
+ const grant = createGrant({
634
+ grantor: alice.did,
635
+ grantee: bob.did,
636
+ id: 'grant1',
637
+ method: DwnMethodName.Write,
638
+ protocol,
639
+ });
640
+ const message = await createGrantKeyMessage({
641
+ grant,
642
+ protocol,
643
+ recipient: bob.did,
644
+ signer: Jws.createSigner(alice),
645
+ });
646
+ const encryptionProtocol = new EncryptionProtocol();
647
+ await expect(encryptionProtocol.preProcessWrite('did:example:tenant', message, createValidationStateReader({ grant }))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateGrantKeyGrantScopeMismatch);
648
+ });
649
+ });
650
+ describe('validateRecord()', () => {
651
+ it('should reject records outside the encryption protocol schema paths', async () => {
652
+ const alice = await TestDataGenerator.generatePersona();
653
+ const recordsWrite = await RecordsWrite.create({
654
+ data: TestDataGenerator.randomBytes(32),
655
+ dataFormat: 'application/json',
656
+ protocol: EncryptionProtocol.uri,
657
+ protocolPath: 'other',
658
+ signer: Jws.createSigner(alice),
659
+ });
660
+ const encryptionProtocol = new EncryptionProtocol();
661
+ await expect(encryptionProtocol.validateRecord(recordsWrite.message, TestDataGenerator.randomBytes(32))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateSchemaUnexpectedRecord);
662
+ });
663
+ it('should reject audienceEpoch records whose keyId does not match the public key', async () => {
664
+ const alice = await TestDataGenerator.generatePersona();
665
+ const other = await TestDataGenerator.generatePersona();
666
+ const protocol = 'https://example.com/protocol/chat';
667
+ const message = await createAudienceEpochMessage({
668
+ protocol,
669
+ role: 'chat/member',
670
+ contextId: 'chat1',
671
+ epoch: 1,
672
+ publicKeyJwk: alice.encryptionKeyPair.publicJwk,
673
+ keyId: await Encryption.getKeyId(other.encryptionKeyPair.publicJwk),
674
+ signer: Jws.createSigner(alice),
675
+ });
676
+ const encryptionProtocol = new EncryptionProtocol();
677
+ await expect(encryptionProtocol.validateRecord(message, Encoder.base64UrlToBytes(message.encodedData))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceEpochKeyIdMismatch);
678
+ });
679
+ it('should reject audienceEpoch records whose payload does not match tags', async () => {
680
+ const alice = await TestDataGenerator.generatePersona();
681
+ const protocol = 'https://example.com/protocol/chat';
682
+ const keyId = await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk);
683
+ const message = await createAudienceEpochMessage({
684
+ protocol,
685
+ role: 'chat/member',
686
+ contextId: 'chat1',
687
+ epoch: 1,
688
+ publicKeyJwk: alice.encryptionKeyPair.publicJwk,
689
+ keyId,
690
+ signer: Jws.createSigner(alice),
691
+ });
692
+ const mismatchingPayload = Encoder.objectToBytes({
693
+ protocol,
694
+ contextId: 'chat2',
695
+ role: 'chat/member',
696
+ epoch: 1,
697
+ keyId,
698
+ publicKeyJwk: alice.encryptionKeyPair.publicJwk,
699
+ });
700
+ const encryptionProtocol = new EncryptionProtocol();
701
+ await expect(encryptionProtocol.validateRecord(message, mismatchingPayload)).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceTagsMismatch);
702
+ });
703
+ it('should reject encrypted audienceEpoch records', async () => {
704
+ const alice = await TestDataGenerator.generatePersona();
705
+ const protocol = 'https://example.com/protocol/chat';
706
+ const message = await createAudienceEpochMessage({
707
+ protocol,
708
+ role: 'chat/member',
709
+ contextId: 'chat1',
710
+ epoch: 1,
711
+ publicKeyJwk: alice.encryptionKeyPair.publicJwk,
712
+ signer: Jws.createSigner(alice),
713
+ });
714
+ message.encryption = createTestEncryptionProperty();
715
+ const encryptionProtocol = new EncryptionProtocol();
716
+ await expect(encryptionProtocol.validateRecord(message, Encoder.base64UrlToBytes(message.encodedData))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateAudienceEpochEncrypted);
717
+ });
718
+ it('should reject unencrypted delivery records', async () => {
719
+ const alice = await TestDataGenerator.generatePersona();
720
+ const bob = await TestDataGenerator.generatePersona();
721
+ const protocol = 'https://example.com/protocol/chat';
722
+ const message = await createAudienceKeyMessage({
723
+ protocol,
724
+ role: 'chat/member',
725
+ contextId: 'chat1',
726
+ epoch: 1,
727
+ publicKeyJwk: alice.encryptionKeyPair.publicJwk,
728
+ recipient: bob.did,
729
+ signer: Jws.createSigner(alice),
730
+ encrypted: false,
731
+ });
732
+ const encryptionProtocol = new EncryptionProtocol();
733
+ await expect(encryptionProtocol.validateRecord(message, TestDataGenerator.randomBytes(32))).rejects.toThrow(DwnErrorCode.EncryptionProtocolValidateEncryptedDeliveryMissingEncryption);
734
+ });
735
+ });
736
+ describe('mapErrorToStatusCode()', () => {
737
+ it('should map encryption validation errors to bad request', () => {
738
+ const encryptionProtocol = new EncryptionProtocol();
739
+ expect(encryptionProtocol.mapErrorToStatusCode(DwnErrorCode.EncryptionProtocolValidateAudienceEpochMissing)).toBe(400);
740
+ expect(encryptionProtocol.mapErrorToStatusCode(DwnErrorCode.GrantAuthorizationGrantExpired)).toBeUndefined();
741
+ });
742
+ });
743
+ describe('constructAdditionalMessageFilter()', () => {
744
+ it('should construct encryption protocol filters for application protocol queries', () => {
745
+ const encryptionProtocol = new EncryptionProtocol();
746
+ expect(encryptionProtocol.constructAdditionalMessageFilter({})).toBeUndefined();
747
+ expect(encryptionProtocol.constructAdditionalMessageFilter({
748
+ protocol: 'https://example.com/protocol/chat',
749
+ messageTimestamp: { from: '2026-01-01T00:00:00.000000Z' },
750
+ })).toEqual({
751
+ protocol: EncryptionProtocol.uri,
752
+ 'tag.protocol': 'https://example.com/protocol/chat',
753
+ messageTimestamp: { gte: '2026-01-01T00:00:00.000000Z' },
754
+ });
755
+ });
756
+ });
757
+ });
758
+ function createGrant(input) {
759
+ return {
760
+ conditions: undefined,
761
+ dateExpires: input.dateExpires ?? Time.createOffsetTimestamp({ seconds: 3600 }),
762
+ dateGranted: input.dateGranted ?? Time.createOffsetTimestamp({ seconds: -60 }),
763
+ delegated: undefined,
764
+ description: undefined,
765
+ grantor: input.grantor,
766
+ grantee: input.grantee,
767
+ id: input.id,
768
+ requestId: undefined,
769
+ scope: {
770
+ interface: input.interface ?? DwnInterfaceName.Records,
771
+ method: input.method ?? DwnMethodName.Read,
772
+ protocol: input.protocol,
773
+ protocolPath: input.protocolPath,
774
+ },
775
+ };
776
+ }
777
+ function createValidationStateReader(input) {
778
+ return {
779
+ constructRecordChain: async () => [],
780
+ fetchGrant: async () => input.grant,
781
+ fetchOldestGrantRevocation: async () => input.revocation,
782
+ fetchProtocolDefinition: async () => input.protocolDefinition,
783
+ hasMatchingRoleRecord: async (roleInput) => {
784
+ return input.hasMatchingRoleRecord?.(roleInput) ?? input.hasMatchingRole ?? true;
785
+ },
786
+ queryAudienceEpochs: async () => input.audienceEpochs ?? [],
787
+ };
788
+ }
789
+ function createRoleProtocolDefinition(protocol, publicKeyJwk, options = {}) {
790
+ const adminRuleSet = {
791
+ $actions: [{ can: ['create'], who: 'anyone' }],
792
+ $role: true,
793
+ };
794
+ const memberRuleSet = {
795
+ $actions: options.memberActions ?? [{ can: ['create'], who: 'anyone' }],
796
+ $role: true,
797
+ };
798
+ if (options.keyAgreement !== false) {
799
+ adminRuleSet.$keyAgreement = { publicKeyJwk };
800
+ memberRuleSet.$keyAgreement = { publicKeyJwk };
801
+ }
802
+ return {
803
+ published: true,
804
+ protocol,
805
+ uses: options.uses,
806
+ types: {
807
+ admin: { dataFormats: ['application/json'] },
808
+ chat: { dataFormats: ['application/json'] },
809
+ member: { dataFormats: ['application/json'] },
810
+ },
811
+ structure: {
812
+ chat: {
813
+ admin: adminRuleSet,
814
+ member: memberRuleSet,
815
+ },
816
+ },
817
+ };
818
+ }
819
+ async function createAudienceEpochMessage(input) {
820
+ const keyId = input.keyId ?? await Encryption.getKeyId(input.publicKeyJwk);
821
+ const data = Encoder.objectToBytes({
822
+ protocol: input.protocol,
823
+ contextId: input.contextId,
824
+ role: input.role,
825
+ epoch: input.epoch,
826
+ keyId,
827
+ publicKeyJwk: input.publicKeyJwk,
828
+ });
829
+ const recordsWrite = await RecordsWrite.create({
830
+ data,
831
+ dataFormat: 'application/json',
832
+ protocol: EncryptionProtocol.uri,
833
+ protocolPath: EncryptionProtocol.audienceEpochPath,
834
+ protocolRole: input.protocolRole,
835
+ signer: input.signer,
836
+ tags: {
837
+ protocol: input.protocol,
838
+ contextId: input.contextId,
839
+ role: input.role,
840
+ epoch: input.epoch,
841
+ keyId,
842
+ },
843
+ });
844
+ return {
845
+ ...recordsWrite.message,
846
+ encodedData: Encoder.bytesToBase64Url(data),
847
+ };
848
+ }
849
+ async function createAudienceKeyMessage(input) {
850
+ const keyId = await Encryption.getKeyId(input.publicKeyJwk);
851
+ const recordsWrite = await RecordsWrite.create({
852
+ data: TestDataGenerator.randomBytes(32),
853
+ dataFormat: 'application/json',
854
+ encryptionInput: input.encrypted === false ? undefined : {
855
+ initializationVector: TestDataGenerator.randomBytes(16),
856
+ key: TestDataGenerator.randomBytes(32),
857
+ keyEncryptionInputs: [{
858
+ derivationScheme: KeyDerivationScheme.ProtocolPath,
859
+ keyId,
860
+ publicKey: input.publicKeyJwk,
861
+ }],
862
+ },
863
+ protocol: EncryptionProtocol.uri,
864
+ protocolPath: EncryptionProtocol.audienceKeyPath,
865
+ recipient: input.recipient,
866
+ signer: input.signer,
867
+ tags: {
868
+ protocol: input.protocol,
869
+ contextId: input.contextId,
870
+ role: input.role,
871
+ epoch: input.epoch,
872
+ keyId,
873
+ },
874
+ });
875
+ return recordsWrite.message;
876
+ }
877
+ async function createGrantKeyMessage(input) {
878
+ const publicKey = (await TestDataGenerator.generatePersona()).encryptionKeyPair.publicJwk;
879
+ const tags = {
880
+ grantId: input.grant.id,
881
+ keyId: await Encryption.getKeyId(publicKey),
882
+ protocol: input.protocol,
883
+ };
884
+ if (input.protocolPath !== undefined) {
885
+ tags.protocolPath = input.protocolPath;
886
+ }
887
+ const recordsWrite = await RecordsWrite.create({
888
+ data: TestDataGenerator.randomBytes(32),
889
+ dataFormat: 'application/json',
890
+ encryptionInput: input.encrypted === false ? undefined : {
891
+ initializationVector: TestDataGenerator.randomBytes(16),
892
+ key: TestDataGenerator.randomBytes(32),
893
+ keyEncryptionInputs: [{
894
+ derivationScheme: KeyDerivationScheme.ProtocolPath,
895
+ keyId: await Encryption.getKeyId(publicKey),
896
+ publicKey,
897
+ }],
898
+ },
899
+ protocol: EncryptionProtocol.uri,
900
+ protocolPath: EncryptionProtocol.grantKeyPath,
901
+ recipient: input.recipient,
902
+ signer: input.signer,
903
+ tags: tags,
904
+ });
905
+ return recordsWrite.message;
906
+ }
907
+ function createTestEncryptionProperty() {
908
+ return {
909
+ algorithm: ContentEncryptionAlgorithm.A256CTR,
910
+ initializationVector: Encoder.bytesToBase64Url(TestDataGenerator.randomBytes(16)),
911
+ keyEncryption: [],
912
+ };
913
+ }
914
+ //# sourceMappingURL=encryption.spec.js.map