@ledgerhq/hw-ledger-key-ring-protocol 0.2.1-nightly.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.eslintrc.js +33 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.unimportedrc.json +4 -0
  4. package/CHANGELOG.md +126 -0
  5. package/LICENSE.txt +21 -0
  6. package/README.md +3 -0
  7. package/jest.config.js +13 -0
  8. package/lib/ApduDevice.d.ts +99 -0
  9. package/lib/ApduDevice.d.ts.map +1 -0
  10. package/lib/ApduDevice.js +532 -0
  11. package/lib/ApduDevice.js.map +1 -0
  12. package/lib/BigEndian.d.ts +7 -0
  13. package/lib/BigEndian.d.ts.map +1 -0
  14. package/lib/BigEndian.js +26 -0
  15. package/lib/BigEndian.js.map +1 -0
  16. package/lib/CommandBlock.d.ts +114 -0
  17. package/lib/CommandBlock.d.ts.map +1 -0
  18. package/lib/CommandBlock.js +173 -0
  19. package/lib/CommandBlock.js.map +1 -0
  20. package/lib/CommandStream.d.ts +38 -0
  21. package/lib/CommandStream.d.ts.map +1 -0
  22. package/lib/CommandStream.js +197 -0
  23. package/lib/CommandStream.js.map +1 -0
  24. package/lib/CommandStreamDecoder.d.ts +15 -0
  25. package/lib/CommandStreamDecoder.d.ts.map +1 -0
  26. package/lib/CommandStreamDecoder.js +101 -0
  27. package/lib/CommandStreamDecoder.js.map +1 -0
  28. package/lib/CommandStreamEncoder.d.ts +16 -0
  29. package/lib/CommandStreamEncoder.d.ts.map +1 -0
  30. package/lib/CommandStreamEncoder.js +131 -0
  31. package/lib/CommandStreamEncoder.js.map +1 -0
  32. package/lib/CommandStreamJsonifier.d.ts +6 -0
  33. package/lib/CommandStreamJsonifier.d.ts.map +1 -0
  34. package/lib/CommandStreamJsonifier.js +75 -0
  35. package/lib/CommandStreamJsonifier.js.map +1 -0
  36. package/lib/CommandStreamResolver.d.ts +53 -0
  37. package/lib/CommandStreamResolver.d.ts.map +1 -0
  38. package/lib/CommandStreamResolver.js +221 -0
  39. package/lib/CommandStreamResolver.js.map +1 -0
  40. package/lib/Crypto.d.ts +38 -0
  41. package/lib/Crypto.d.ts.map +1 -0
  42. package/lib/Crypto.js +47 -0
  43. package/lib/Crypto.js.map +1 -0
  44. package/lib/Device.d.ts +43 -0
  45. package/lib/Device.d.ts.map +1 -0
  46. package/lib/Device.js +203 -0
  47. package/lib/Device.js.map +1 -0
  48. package/lib/IndexedTree.d.ts +13 -0
  49. package/lib/IndexedTree.d.ts.map +1 -0
  50. package/lib/IndexedTree.js +75 -0
  51. package/lib/IndexedTree.js.map +1 -0
  52. package/lib/NobleCrypto.d.ts +41 -0
  53. package/lib/NobleCrypto.d.ts.map +1 -0
  54. package/lib/NobleCrypto.js +298 -0
  55. package/lib/NobleCrypto.js.map +1 -0
  56. package/lib/PublicKey.d.ts +5 -0
  57. package/lib/PublicKey.d.ts.map +1 -0
  58. package/lib/PublicKey.js +10 -0
  59. package/lib/PublicKey.js.map +1 -0
  60. package/lib/SeedId.d.ts +80 -0
  61. package/lib/SeedId.d.ts.map +1 -0
  62. package/lib/SeedId.js +244 -0
  63. package/lib/SeedId.js.map +1 -0
  64. package/lib/StreamTree.d.ts +50 -0
  65. package/lib/StreamTree.d.ts.map +1 -0
  66. package/lib/StreamTree.js +169 -0
  67. package/lib/StreamTree.js.map +1 -0
  68. package/lib/StreamTreeCipher.d.ts +46 -0
  69. package/lib/StreamTreeCipher.d.ts.map +1 -0
  70. package/lib/StreamTreeCipher.js +179 -0
  71. package/lib/StreamTreeCipher.js.map +1 -0
  72. package/lib/__tests__/codec.d.ts +2 -0
  73. package/lib/__tests__/codec.d.ts.map +1 -0
  74. package/lib/__tests__/codec.js +108 -0
  75. package/lib/__tests__/codec.js.map +1 -0
  76. package/lib/__tests__/crypto.d.ts +2 -0
  77. package/lib/__tests__/crypto.d.ts.map +1 -0
  78. package/lib/__tests__/crypto.js +46 -0
  79. package/lib/__tests__/crypto.js.map +1 -0
  80. package/lib/__tests__/indexed_tree.d.ts +2 -0
  81. package/lib/__tests__/indexed_tree.d.ts.map +1 -0
  82. package/lib/__tests__/indexed_tree.js +45 -0
  83. package/lib/__tests__/indexed_tree.js.map +1 -0
  84. package/lib/__tests__/key_exchange.d.ts +2 -0
  85. package/lib/__tests__/key_exchange.d.ts.map +1 -0
  86. package/lib/__tests__/key_exchange.js +129 -0
  87. package/lib/__tests__/key_exchange.js.map +1 -0
  88. package/lib/__tests__/seedId.d.ts +2 -0
  89. package/lib/__tests__/seedId.d.ts.map +1 -0
  90. package/lib/__tests__/seedId.js +92 -0
  91. package/lib/__tests__/seedId.js.map +1 -0
  92. package/lib/__tests__/shared_object.d.ts +2 -0
  93. package/lib/__tests__/shared_object.d.ts.map +1 -0
  94. package/lib/__tests__/shared_object.js +78 -0
  95. package/lib/__tests__/shared_object.js.map +1 -0
  96. package/lib/index.d.ts +35 -0
  97. package/lib/index.d.ts.map +1 -0
  98. package/lib/index.js +81 -0
  99. package/lib/index.js.map +1 -0
  100. package/lib/tlv.d.ts +99 -0
  101. package/lib/tlv.d.ts.map +1 -0
  102. package/lib/tlv.js +150 -0
  103. package/lib/tlv.js.map +1 -0
  104. package/lib-es/ApduDevice.d.ts +99 -0
  105. package/lib-es/ApduDevice.d.ts.map +1 -0
  106. package/lib-es/ApduDevice.js +526 -0
  107. package/lib-es/ApduDevice.js.map +1 -0
  108. package/lib-es/BigEndian.d.ts +7 -0
  109. package/lib-es/BigEndian.d.ts.map +1 -0
  110. package/lib-es/BigEndian.js +23 -0
  111. package/lib-es/BigEndian.js.map +1 -0
  112. package/lib-es/CommandBlock.d.ts +114 -0
  113. package/lib-es/CommandBlock.d.ts.map +1 -0
  114. package/lib-es/CommandBlock.js +160 -0
  115. package/lib-es/CommandBlock.js.map +1 -0
  116. package/lib-es/CommandStream.d.ts +38 -0
  117. package/lib-es/CommandStream.d.ts.map +1 -0
  118. package/lib-es/CommandStream.js +189 -0
  119. package/lib-es/CommandStream.js.map +1 -0
  120. package/lib-es/CommandStreamDecoder.d.ts +15 -0
  121. package/lib-es/CommandStreamDecoder.d.ts.map +1 -0
  122. package/lib-es/CommandStreamDecoder.js +97 -0
  123. package/lib-es/CommandStreamDecoder.js.map +1 -0
  124. package/lib-es/CommandStreamEncoder.d.ts +16 -0
  125. package/lib-es/CommandStreamEncoder.d.ts.map +1 -0
  126. package/lib-es/CommandStreamEncoder.js +127 -0
  127. package/lib-es/CommandStreamEncoder.js.map +1 -0
  128. package/lib-es/CommandStreamJsonifier.d.ts +6 -0
  129. package/lib-es/CommandStreamJsonifier.d.ts.map +1 -0
  130. package/lib-es/CommandStreamJsonifier.js +72 -0
  131. package/lib-es/CommandStreamJsonifier.js.map +1 -0
  132. package/lib-es/CommandStreamResolver.d.ts +53 -0
  133. package/lib-es/CommandStreamResolver.d.ts.map +1 -0
  134. package/lib-es/CommandStreamResolver.js +216 -0
  135. package/lib-es/CommandStreamResolver.js.map +1 -0
  136. package/lib-es/Crypto.d.ts +38 -0
  137. package/lib-es/Crypto.d.ts.map +1 -0
  138. package/lib-es/Crypto.js +43 -0
  139. package/lib-es/Crypto.js.map +1 -0
  140. package/lib-es/Device.d.ts +43 -0
  141. package/lib-es/Device.d.ts.map +1 -0
  142. package/lib-es/Device.js +195 -0
  143. package/lib-es/Device.js.map +1 -0
  144. package/lib-es/IndexedTree.d.ts +13 -0
  145. package/lib-es/IndexedTree.d.ts.map +1 -0
  146. package/lib-es/IndexedTree.js +71 -0
  147. package/lib-es/IndexedTree.js.map +1 -0
  148. package/lib-es/NobleCrypto.d.ts +41 -0
  149. package/lib-es/NobleCrypto.d.ts.map +1 -0
  150. package/lib-es/NobleCrypto.js +267 -0
  151. package/lib-es/NobleCrypto.js.map +1 -0
  152. package/lib-es/PublicKey.d.ts +5 -0
  153. package/lib-es/PublicKey.d.ts.map +1 -0
  154. package/lib-es/PublicKey.js +6 -0
  155. package/lib-es/PublicKey.js.map +1 -0
  156. package/lib-es/SeedId.d.ts +80 -0
  157. package/lib-es/SeedId.d.ts.map +1 -0
  158. package/lib-es/SeedId.js +235 -0
  159. package/lib-es/SeedId.js.map +1 -0
  160. package/lib-es/StreamTree.d.ts +50 -0
  161. package/lib-es/StreamTree.d.ts.map +1 -0
  162. package/lib-es/StreamTree.js +165 -0
  163. package/lib-es/StreamTree.js.map +1 -0
  164. package/lib-es/StreamTreeCipher.d.ts +46 -0
  165. package/lib-es/StreamTreeCipher.d.ts.map +1 -0
  166. package/lib-es/StreamTreeCipher.js +175 -0
  167. package/lib-es/StreamTreeCipher.js.map +1 -0
  168. package/lib-es/__tests__/codec.d.ts +2 -0
  169. package/lib-es/__tests__/codec.d.ts.map +1 -0
  170. package/lib-es/__tests__/codec.js +106 -0
  171. package/lib-es/__tests__/codec.js.map +1 -0
  172. package/lib-es/__tests__/crypto.d.ts +2 -0
  173. package/lib-es/__tests__/crypto.d.ts.map +1 -0
  174. package/lib-es/__tests__/crypto.js +44 -0
  175. package/lib-es/__tests__/crypto.js.map +1 -0
  176. package/lib-es/__tests__/indexed_tree.d.ts +2 -0
  177. package/lib-es/__tests__/indexed_tree.d.ts.map +1 -0
  178. package/lib-es/__tests__/indexed_tree.js +43 -0
  179. package/lib-es/__tests__/indexed_tree.js.map +1 -0
  180. package/lib-es/__tests__/key_exchange.d.ts +2 -0
  181. package/lib-es/__tests__/key_exchange.d.ts.map +1 -0
  182. package/lib-es/__tests__/key_exchange.js +124 -0
  183. package/lib-es/__tests__/key_exchange.js.map +1 -0
  184. package/lib-es/__tests__/seedId.d.ts +2 -0
  185. package/lib-es/__tests__/seedId.d.ts.map +1 -0
  186. package/lib-es/__tests__/seedId.js +90 -0
  187. package/lib-es/__tests__/seedId.js.map +1 -0
  188. package/lib-es/__tests__/shared_object.d.ts +2 -0
  189. package/lib-es/__tests__/shared_object.d.ts.map +1 -0
  190. package/lib-es/__tests__/shared_object.js +76 -0
  191. package/lib-es/__tests__/shared_object.js.map +1 -0
  192. package/lib-es/index.d.ts +35 -0
  193. package/lib-es/index.d.ts.map +1 -0
  194. package/lib-es/index.js +32 -0
  195. package/lib-es/index.js.map +1 -0
  196. package/lib-es/tlv.d.ts +99 -0
  197. package/lib-es/tlv.d.ts.map +1 -0
  198. package/lib-es/tlv.js +144 -0
  199. package/lib-es/tlv.js.map +1 -0
  200. package/package.json +63 -0
  201. package/src/ApduDevice.ts +692 -0
  202. package/src/BigEndian.ts +25 -0
  203. package/src/CommandBlock.ts +247 -0
  204. package/src/CommandStream.ts +262 -0
  205. package/src/CommandStreamDecoder.ts +142 -0
  206. package/src/CommandStreamEncoder.ts +144 -0
  207. package/src/CommandStreamJsonifier.ts +82 -0
  208. package/src/CommandStreamResolver.ts +284 -0
  209. package/src/Crypto.ts +78 -0
  210. package/src/Device.ts +254 -0
  211. package/src/IndexedTree.ts +80 -0
  212. package/src/NobleCrypto.ts +294 -0
  213. package/src/PublicKey.ts +6 -0
  214. package/src/SeedId.ts +338 -0
  215. package/src/StreamTree.ts +212 -0
  216. package/src/StreamTreeCipher.ts +207 -0
  217. package/src/__tests__/codec.ts +146 -0
  218. package/src/__tests__/crypto.ts +44 -0
  219. package/src/__tests__/indexed_tree.ts +51 -0
  220. package/src/__tests__/key_exchange.ts +167 -0
  221. package/src/__tests__/seedId.ts +120 -0
  222. package/src/__tests__/shared_object.ts +118 -0
  223. package/src/index.ts +43 -0
  224. package/src/tlv.ts +210 -0
  225. package/tsconfig.json +14 -0
@@ -0,0 +1,207 @@
1
+ import { Device, crypto } from ".";
2
+ import { StreamTree } from "./StreamTree";
3
+
4
+ /**
5
+ *
6
+ */
7
+ export enum StreamTreeCipherMode {
8
+ AES_256_CBC = 0x00,
9
+ AES_256_GCM = 0x01,
10
+ }
11
+
12
+ const TAG_LENGTH = 16;
13
+
14
+ interface DecodedPayload {
15
+ version: number;
16
+ ephemeralPublicKey: Uint8Array;
17
+ nonce: Uint8Array;
18
+ encrypted: Uint8Array;
19
+ checksum: Uint8Array;
20
+ }
21
+
22
+ /**
23
+ *
24
+ */
25
+ export class StreamTreeCipher {
26
+ private _mode: StreamTreeCipherMode;
27
+ private _device: Device;
28
+
29
+ constructor(mode: StreamTreeCipherMode, device: Device) {
30
+ this._mode = mode;
31
+ this._device = device;
32
+ }
33
+
34
+ get mode(): StreamTreeCipherMode {
35
+ return this._mode;
36
+ }
37
+
38
+ /**
39
+ * Encrypts a message for a given path in the tree
40
+ * @param tree
41
+ * @param path
42
+ * @param message
43
+ * @param nonce optional nonce to use for the encryption (if null a random nonce will be generated)
44
+ * @returns the encrypted message with the nonce prepended (1 byte for the nonce length + nonce + 33 bytes ephemeral key + encrypted message + 4 bytes checksum)
45
+ * @throws Error if the cipher mode is not implemented
46
+ * @throws Error if the path is not found in the tree and can't be derived from the device
47
+ */
48
+ async encrypt(
49
+ tree: StreamTree,
50
+ path: number[],
51
+ message: Uint8Array,
52
+ nonce: Uint8Array | null = null,
53
+ ): Promise<Uint8Array> {
54
+ if (nonce === null) {
55
+ nonce = await crypto.randomBytes(16);
56
+ }
57
+
58
+ // Generate ephemeral key pair
59
+ const ephemeralKeyPair = await crypto.randomKeypair();
60
+
61
+ // Get the group public key
62
+ const groupKeypair = await this.getGroupKeypair(tree, path);
63
+
64
+ // Compute the secret via ECDH
65
+ const secret = await crypto.ecdh(ephemeralKeyPair, groupKeypair.publicKey);
66
+
67
+ let encrypted: Uint8Array = new Uint8Array(0);
68
+ switch (this._mode) {
69
+ case StreamTreeCipherMode.AES_256_CBC: {
70
+ encrypted = await crypto.encrypt(secret, nonce, message);
71
+ break;
72
+ }
73
+ case StreamTreeCipherMode.AES_256_GCM: {
74
+ encrypted = await crypto.encrypt(secret, nonce, message);
75
+ break;
76
+ }
77
+ default:
78
+ throw new Error("Unknown cipher mode");
79
+ }
80
+
81
+ // Serialize encrypted data
82
+ return this.encodeData(ephemeralKeyPair.publicKey, nonce, encrypted, message);
83
+ }
84
+
85
+ private async getGroupKeypair(
86
+ tree: StreamTree,
87
+ path: number[],
88
+ ): Promise<{ privateKey: Uint8Array; publicKey: Uint8Array }> {
89
+ if (!this._device.isPublicKeyAvailable()) {
90
+ throw new Error("Stream tree cipher is only available for software devices");
91
+ }
92
+ const member = await this._device.getPublicKey();
93
+ const event = await tree.getPublishKeyEvent(member.publicKey, path);
94
+ if (!event) {
95
+ throw new Error("Cannot find key in the tree for the current device");
96
+ }
97
+
98
+ // Compute the relative path from the event to the path parameter
99
+ const privateKey = (await this._device.readKey(tree, path)).slice(0, 32);
100
+ const publicKey = (await crypto.keypairFromSecretKey(privateKey)).publicKey;
101
+ return { privateKey, publicKey };
102
+ }
103
+
104
+ private async encodeData(
105
+ ephemeralPublicKey: Uint8Array,
106
+ nonce: Uint8Array,
107
+ data: Uint8Array,
108
+ message: Uint8Array,
109
+ ): Promise<Uint8Array> {
110
+ const result = new Uint8Array(1 + 33 + nonce.length + TAG_LENGTH + data.length);
111
+ let offset = 0;
112
+ // Version
113
+ result[offset] = this._mode;
114
+ offset += 1;
115
+ // Ephemeral public key
116
+ result.set(ephemeralPublicKey, offset);
117
+ offset += ephemeralPublicKey.length;
118
+ // Nonce/IV
119
+ result.set(nonce, offset);
120
+ offset += nonce.length;
121
+ // Checksum
122
+ if (this._mode == StreamTreeCipherMode.AES_256_CBC) {
123
+ const checksum = await this.computeChecksum(message);
124
+ result.set(checksum, offset);
125
+ offset += checksum.length;
126
+ }
127
+ // Encrypted data
128
+ result.set(data, offset);
129
+ return result;
130
+ }
131
+
132
+ private decodeData(payload: Uint8Array): DecodedPayload {
133
+ const version = payload[0];
134
+ let offset = 1;
135
+ const ephemeralPublicKey = payload.slice(offset, offset + 33);
136
+ offset += 33;
137
+ const nonce = payload.slice(offset, offset + 16);
138
+ offset += 16;
139
+ const checksum = payload.slice(payload.length - 16, payload.length);
140
+ const encrypted = payload.slice(offset, payload.length - 16);
141
+ return {
142
+ version,
143
+ ephemeralPublicKey,
144
+ nonce,
145
+ encrypted,
146
+ checksum,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Decrypts a message for a given path in the tree
152
+ * @param tree
153
+ * @param path
154
+ * @param encrytedPayload
155
+ * @returns the decrypted message
156
+ * @throws Error if the cipher mode is not implemented
157
+ * @throws Error if the path is not found in the tree and can't be derived from the device
158
+ * @throws Error if the checksum is invalid
159
+ */
160
+ async decrypt(
161
+ tree: StreamTree,
162
+ path: number[],
163
+ encrytedPayload: Uint8Array,
164
+ ): Promise<Uint8Array> {
165
+ const decodedPayload = this.decodeData(encrytedPayload);
166
+
167
+ const ephemeralKey = decodedPayload.ephemeralPublicKey;
168
+ const nonce = decodedPayload.nonce;
169
+ const encryptedMessage = decodedPayload.encrypted;
170
+ const checksum = decodedPayload.checksum;
171
+
172
+ const sharedKeyPair = await this.getGroupKeypair(tree, path);
173
+ const secret = await crypto.ecdh(sharedKeyPair, ephemeralKey);
174
+
175
+ let decrypted = new Uint8Array(0);
176
+ switch (this._mode) {
177
+ case StreamTreeCipherMode.AES_256_CBC: {
178
+ decrypted = await crypto.decrypt(secret, nonce, encryptedMessage);
179
+ const computedChecksum = await this.computeChecksum(decrypted);
180
+ if (crypto.to_hex(computedChecksum) !== crypto.to_hex(checksum)) {
181
+ throw new Error("Invalid checksum");
182
+ }
183
+ break;
184
+ }
185
+ case StreamTreeCipherMode.AES_256_GCM: {
186
+ decrypted = await crypto.decrypt(secret, nonce, encryptedMessage);
187
+ break;
188
+ }
189
+ default:
190
+ throw new Error("Unknown cipher mode");
191
+ }
192
+
193
+ return decrypted;
194
+ }
195
+
196
+ private async computeChecksum(message: Uint8Array): Promise<Uint8Array> {
197
+ const hash = await crypto.hash(message);
198
+ return hash.slice(0, 16);
199
+ }
200
+
201
+ static create(
202
+ device: Device,
203
+ mode: StreamTreeCipherMode = StreamTreeCipherMode.AES_256_GCM,
204
+ ): StreamTreeCipher {
205
+ return new StreamTreeCipher(mode, device);
206
+ }
207
+ }
@@ -0,0 +1,146 @@
1
+ import {
2
+ AddMember,
3
+ createCommandBlock,
4
+ Permissions,
5
+ PublishKey,
6
+ Seed,
7
+ signCommandBlock,
8
+ } from "../CommandBlock";
9
+ import { TLV } from "../tlv";
10
+ import { CommandStreamDecoder } from "../CommandStreamDecoder";
11
+ import { CommandStreamEncoder } from "../CommandStreamEncoder";
12
+ import { crypto } from "../Crypto";
13
+ import { CommandStream } from "..";
14
+
15
+ describe("Encode/Decode command stream tester", () => {
16
+ it("should encode and decode a byte", async () => {
17
+ const byte = 42;
18
+ let buffer = new Uint8Array();
19
+ buffer = TLV.pushByte(buffer, 42);
20
+ const decoded = TLV.readVarInt(TLV.readTLV(buffer, 0));
21
+ expect(decoded.value).toBe(byte);
22
+ });
23
+
24
+ it("should encode and decode a Int32", async () => {
25
+ const varint = 0xdeadbeef;
26
+ let buffer = new Uint8Array();
27
+ buffer = TLV.pushInt32(buffer, varint);
28
+ const decoded = TLV.readVarInt(TLV.readTLV(buffer, 0));
29
+ expect(decoded.value).toBe(varint);
30
+ });
31
+
32
+ it("should encode and decode a string", async () => {
33
+ const str = "Hello World";
34
+ let buffer = new Uint8Array();
35
+ buffer = TLV.pushString(buffer, str);
36
+ const decoded = TLV.readString(TLV.readTLV(buffer, 0));
37
+ expect(decoded.value).toBe(str);
38
+ });
39
+
40
+ it("should encode and decode a hash", async () => {
41
+ const hash = await crypto.hash(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
42
+ let buffer = new Uint8Array();
43
+ buffer = TLV.pushHash(buffer, hash);
44
+ const decoded = TLV.readHash(TLV.readTLV(buffer, 0));
45
+ expect(crypto.to_hex(decoded.value)).toEqual(crypto.to_hex(hash));
46
+ });
47
+
48
+ it("should encode and decode bytes", async () => {
49
+ const bytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
50
+ let buffer = new Uint8Array();
51
+ buffer = TLV.pushBytes(buffer, bytes);
52
+ const decoded = TLV.readBytes(TLV.readTLV(buffer, 0));
53
+ expect(decoded.value).toEqual(bytes);
54
+ });
55
+
56
+ it("should encode and decode a signature", async () => {
57
+ const alice = await crypto.randomKeypair();
58
+ const block = await signCommandBlock(
59
+ await createCommandBlock(alice.publicKey, []),
60
+ alice.publicKey,
61
+ alice.privateKey,
62
+ );
63
+ let buffer = new Uint8Array();
64
+ buffer = TLV.pushSignature(buffer, block.signature);
65
+ const decoded = TLV.readSignature(TLV.readTLV(buffer, 0));
66
+ expect(decoded.value).toEqual(block.signature);
67
+ });
68
+
69
+ it("should encode and decode a public key", async () => {
70
+ const alice = await crypto.randomKeypair();
71
+ let buffer = new Uint8Array();
72
+ buffer = TLV.pushPublicKey(buffer, alice.publicKey);
73
+ const decoded = TLV.readPublicKey(TLV.readTLV(buffer, 0));
74
+ expect(decoded.value).toEqual(alice.publicKey);
75
+ });
76
+
77
+ it("should encode and decode a stream. Encoding/Decoding should not alter the stream", async () => {
78
+ const alice = await crypto.randomKeypair();
79
+ const groupPk = await crypto.randomKeypair();
80
+ const groupChainCode = await crypto.randomBytes(32);
81
+ const xpriv = new Uint8Array(64);
82
+ const initializationVector = await crypto.randomBytes(16);
83
+ xpriv.set(groupPk.privateKey);
84
+ xpriv.set(groupChainCode, 32);
85
+ const ephemeralPk = await crypto.randomKeypair();
86
+
87
+ const block1 = await signCommandBlock(
88
+ await createCommandBlock(alice.publicKey, [
89
+ new Seed(
90
+ await crypto.randomBytes(16),
91
+ 0,
92
+ groupPk.publicKey,
93
+ initializationVector,
94
+ xpriv,
95
+ ephemeralPk.publicKey,
96
+ ),
97
+ ]),
98
+ alice.publicKey,
99
+ alice.privateKey,
100
+ );
101
+
102
+ const block2 = await signCommandBlock(
103
+ await createCommandBlock(alice.publicKey, [
104
+ new AddMember("Alice", await crypto.randomBytes(32), Permissions.OWNER),
105
+ new PublishKey(
106
+ await crypto.randomBytes(16),
107
+ await crypto.randomBytes(32),
108
+ await crypto.randomBytes(32),
109
+ await crypto.randomBytes(32),
110
+ ),
111
+ ]),
112
+ alice.publicKey,
113
+ alice.privateKey,
114
+ );
115
+ const block3 = await signCommandBlock(
116
+ await createCommandBlock(alice.publicKey, []),
117
+ alice.publicKey,
118
+ alice.privateKey,
119
+ );
120
+
121
+ const stream = [block1, block2, block3];
122
+
123
+ const encoded = CommandStreamEncoder.encode(stream);
124
+ const digestEncoded = await crypto.hash(encoded);
125
+
126
+ const decoded = CommandStreamDecoder.decode(encoded);
127
+ const reencoded = CommandStreamEncoder.encode(decoded);
128
+ const digestReencoded = await crypto.hash(reencoded);
129
+ expect(digestEncoded).toEqual(digestReencoded);
130
+ });
131
+
132
+ it("decodes a specific command stream", async () => {
133
+ const tlv =
134
+ "0101010220824b3168c79e8b61b599751c107117b5c9b647f2b6859de8a245952559707692062102a13e82cd0d2f77d1ab1434d8bd799571e54cd32e1121c5cf82217f8b0b713b6b01010315a8050c800000008000001080000000062103ccf74aa7775b3d39d6cbb0236acee7a7f980b9f6a556a4d814d44b0bd56cb77b05108c51eda6be26623ca919ed17333afcdb054019c0b60ede1692479cc04ce69eae6a0bd51941bab6f044f3dec10c11cf11e6253504d1df6b0aab7dc1996e4eaa7c6f92c29153c59534578901cd7ff4efcea1ae06210268abdb3d49ba4a274ce8660cde0d1eeaf1fea00e281218be775f6b3aefc39756113a040f7765622d746f6f6c732d6563626638062103a270456b0f95714cc61a6473e6b6d8db354a3c377281096bdd2439a5475ecbf80104ffffffff129a05100e5205b4a616b2a4d79b07b4a4932f560540669e741f38fee07956fb0dc0ea9978d55bd5d8424b0d0f66a2c5a45788f92d0ddc283138c7ba62c521de1d604ee7f847c5aed40a11536bbe742af0be8cfd4132062103a270456b0f95714cc61a6473e6b6d8db354a3c377281096bdd2439a5475ecbf80621027003755248202ea8a67d1fcdcd82d7f7022248f3af892fa5307d3ea250dc81050346304402204422a779fd08723d8cba19c0cc11ef7a24f6f1f459cb01598ff1a26f27ea8976022053a554d4f509223f2d08faa5de796fed13a9762f35da08e94884edd1f7c0d015";
135
+ const decoded = CommandStreamDecoder.decode(crypto.from_hex(tlv));
136
+ const stream = new CommandStream(decoded);
137
+ const resolved = await stream.resolve();
138
+ expect(resolved.getMembersData()).toEqual([
139
+ {
140
+ id: "03a270456b0f95714cc61a6473e6b6d8db354a3c377281096bdd2439a5475ecbf8",
141
+ name: "web-tools-ecbf8",
142
+ permissions: 4294967295,
143
+ },
144
+ ]);
145
+ });
146
+ });
@@ -0,0 +1,44 @@
1
+ import { crypto } from "../Crypto";
2
+
3
+ describe("Sodium wrapper tester", () => {
4
+ it("should computes the same symetric key with ECDH using two parties", async () => {
5
+ const kp1 = await crypto.randomKeypair();
6
+ const kp2 = await crypto.randomKeypair();
7
+ const shared1 = await crypto.ecdh(kp1, kp2.publicKey);
8
+ const shared2 = await crypto.ecdh(kp2, kp1.publicKey);
9
+
10
+ expect(crypto.to_hex(shared1)).toBe(crypto.to_hex(shared2));
11
+ });
12
+
13
+ it("should encrypt a message and decrypt using the same symmetric key", async () => {
14
+ //const message = "Hello world!"
15
+ const message = await crypto.randomBytes(64);
16
+ const key = await crypto.randomBytes(32);
17
+ const nonce = await crypto.randomBytes(16);
18
+ const encrypted = await crypto.encrypt(key, nonce, message);
19
+ const decrypted = await crypto.decrypt(key, nonce, encrypted);
20
+ expect(crypto.to_hex(decrypted)).toBe(crypto.to_hex(message));
21
+ });
22
+
23
+ it("should encrypt user data", async () => {
24
+ const keypair = await crypto.randomKeypair();
25
+ const data = await crypto.randomBytes(64);
26
+ const encrypted = await crypto.encryptUserData(keypair.privateKey, data);
27
+ const decrypted = await crypto.decryptUserData(keypair.privateKey, encrypted);
28
+ expect(crypto.to_hex(decrypted)).toBe(crypto.to_hex(data));
29
+ });
30
+
31
+ it("should verify truncated signature by padding 0s from the start", async () => {
32
+ const hash = crypto.from_hex(
33
+ "19514a2e50bfad4a6de397ebde776191cbb720e8bbfcc3c165385c3664c03341",
34
+ );
35
+ const signature = crypto.from_hex(
36
+ // Here the "S" part of the signature is only 31 bytes long it should be padded with a 0
37
+ "3043022052a82876fcd4d9d8383ce12a7e4d96bb4c1d9e71e857cd087c092b87cec6baeb021f6b86b9a3bab1e7794ca6ef081c66cb6e6dff06cceddbd23e1f25089e311784",
38
+ );
39
+ const issuer = crypto.from_hex(
40
+ "026e7bf1e015da491674be5796b15d6fabd1f454aad478a6a223934e5a872719e0",
41
+ );
42
+ expect(await crypto.verify(hash, signature, issuer)).toBe(true);
43
+ });
44
+ });
@@ -0,0 +1,51 @@
1
+ import { IndexedTree } from "../IndexedTree";
2
+
3
+ describe("Indexed tree unit test suite", () => {
4
+ it("should create a new tree with a root", () => {
5
+ const tree = new IndexedTree(0);
6
+ expect(tree.getValue()).toBe(0);
7
+ });
8
+
9
+ it("should create a new tree with a root and update the root", () => {
10
+ let tree = new IndexedTree(0);
11
+ tree = tree.updateChild([], 1);
12
+ expect(tree.getValue()).toBe(1);
13
+ });
14
+
15
+ it("should create a new tree with a root and update a child", () => {
16
+ let tree = new IndexedTree(0);
17
+ tree = tree.updateChild([1], 1);
18
+ expect(tree.getChild(1)!.getValue()).toBe(1);
19
+ });
20
+
21
+ it("should create a tree with multiple children and update a child which had no value before", () => {
22
+ let tree = new IndexedTree(0);
23
+ tree = tree.updateChild([0, 1], 1);
24
+ tree = tree.updateChild([0, 2], 2);
25
+
26
+ expect(tree.findChild([0, 1])!.getValue()).toBe(1);
27
+ expect(tree.findChild([0, 2])!.getValue()).toBe(2);
28
+ expect(tree.getChild(0)?.getValue()).toBe(null);
29
+
30
+ tree = tree.updateChild([0], 42);
31
+ expect(tree.getChild(0)?.getValue()).toBe(42);
32
+ });
33
+
34
+ it("should add a subtree to the tree", () => {
35
+ let tree = new IndexedTree(0);
36
+ tree = tree.addChild([0, 1], new IndexedTree(1));
37
+ tree = tree.addChild([0, 2], new IndexedTree(2));
38
+
39
+ let subtree = new IndexedTree(42);
40
+ subtree = subtree.updateChild([0], 43);
41
+ subtree = subtree.updateChild([1], 44);
42
+
43
+ tree = tree.addChild([0, 3], subtree);
44
+
45
+ expect(tree.findChild([0, 1])!.getValue()).toBe(1);
46
+ expect(tree.findChild([0, 2])!.getValue()).toBe(2);
47
+ expect(tree.findChild([0, 3])!.getValue()).toBe(42);
48
+ expect(tree.findChild([0, 3, 0])!.getValue()).toBe(43);
49
+ expect(tree.findChild([0, 3, 1])!.getValue()).toBe(44);
50
+ });
51
+ });
@@ -0,0 +1,167 @@
1
+ import { createDevice } from "../Device";
2
+
3
+ import CommandStream from "../CommandStream";
4
+ import { Permissions } from "../CommandBlock";
5
+ import { crypto } from "../Crypto";
6
+ import { StreamTreeCipher } from "../StreamTreeCipher";
7
+ import { StreamTree } from "../StreamTree";
8
+
9
+ const DEFAULT_TOPIC = "c96d450545ff2836204c29af291428a5bf740304978f5dfb0b4a261474192851";
10
+
11
+ describe("Symmetric key exchange scenarii", () => {
12
+ it("create a new group with 1 member", async () => {
13
+ const alice = await createDevice();
14
+ const topic = crypto.from_hex(DEFAULT_TOPIC);
15
+ let stream = new CommandStream([]);
16
+ stream = await stream.edit().seed(topic).issue(alice);
17
+ const resolved = await stream.resolve();
18
+ expect(resolved.isCreated()).toBe(true);
19
+ expect(resolved.getMembers().length).toBe(1);
20
+ expect(resolved.getMembersData().length).toBe(0);
21
+ expect(crypto.to_hex(resolved.getTopic()!)).toBe(crypto.to_hex(topic));
22
+ //const parsed = CommandStreamDecoder.decode(CommandStreamEncoder.encode(stream.blocks));
23
+ //console.log(JSON.stringify(CommandStreamJsonifier.jsonify(parsed), null, 2));
24
+ //const raw = CommandStreamEncoder.encode(stream.blocks);
25
+ //console.log(crypto.to_hex(raw));
26
+ });
27
+
28
+ it("create a group owned by Alice, Alice adds Bob and publish a key for Bob.", async () => {
29
+ const alice = await createDevice();
30
+ const bob = await createDevice();
31
+
32
+ let stream = new CommandStream();
33
+ stream = await stream.edit().seed(crypto.from_hex(DEFAULT_TOPIC)).issue(alice);
34
+ stream = await stream
35
+ .edit()
36
+ .addMember("Bob", (await bob.getPublicKey()).publicKey, Permissions.KEY_READER)
37
+ .issue(alice);
38
+ const tree = StreamTree.from(stream);
39
+
40
+ // Encrypt a message from Alice
41
+ const originalMessage = new TextEncoder().encode("Hello World!");
42
+ const encryptedMessage = await StreamTreeCipher.create(alice).encrypt(
43
+ tree,
44
+ [],
45
+ originalMessage,
46
+ );
47
+
48
+ // The message should be different from the original
49
+ expect(crypto.to_hex(encryptedMessage)).not.toEqual(crypto.to_hex(originalMessage));
50
+
51
+ // Decrypt the message from Bob
52
+ const decryptedMessage = await StreamTreeCipher.create(bob).decrypt(tree, [], encryptedMessage);
53
+ // The message should be the same as the original
54
+ expect(crypto.to_hex(decryptedMessage)).toBe(crypto.to_hex(originalMessage));
55
+ });
56
+
57
+ it("create a group owned by Alice, Alice adds Bob and publish a key for Bob, Alice adds Charlie and Bob publishes a key for Charlie", async () => {
58
+ const alice = await createDevice();
59
+ const bob = await createDevice();
60
+ const charlie = await createDevice();
61
+
62
+ let stream = new CommandStream();
63
+ stream = await stream.edit().seed(crypto.from_hex(DEFAULT_TOPIC)).issue(alice);
64
+ stream = await stream
65
+ .edit()
66
+ .addMember("Bob", (await bob.getPublicKey()).publicKey, Permissions.KEY_READER)
67
+ .issue(alice);
68
+ stream = await stream
69
+ .edit()
70
+ .addMember("Charlie", (await charlie.getPublicKey()).publicKey, Permissions.KEY_READER)
71
+ .issue(alice);
72
+
73
+ const tree = StreamTree.from(stream);
74
+
75
+ // Encrypt a message from Alice
76
+ const originalMessage = new TextEncoder().encode("Hello World!");
77
+ const encryptedMessage = await StreamTreeCipher.create(alice).encrypt(
78
+ tree,
79
+ [],
80
+ originalMessage,
81
+ );
82
+ // The message should be different from the original
83
+ expect(crypto.to_hex(encryptedMessage)).not.toBe(crypto.to_hex(originalMessage));
84
+
85
+ {
86
+ // Decrypt the message from Bob
87
+ const decryptedMessage = await StreamTreeCipher.create(bob).decrypt(
88
+ tree,
89
+ [],
90
+ encryptedMessage,
91
+ );
92
+ // The message should be the same as the original
93
+ expect(crypto.to_hex(decryptedMessage)).toBe(crypto.to_hex(originalMessage));
94
+ }
95
+
96
+ {
97
+ // Decrypt the message from Charlie
98
+ const decryptedMessage = await StreamTreeCipher.create(charlie).decrypt(
99
+ tree,
100
+ [],
101
+ encryptedMessage,
102
+ );
103
+ // The message should be the same as the original
104
+ expect(crypto.to_hex(decryptedMessage)).toBe(crypto.to_hex(originalMessage));
105
+ }
106
+
107
+ //console.log(crypto.to_hex(CommandStreamEncoder.encode(stream.blocks)));
108
+ //const parsed = CommandStreamDecoder.decode(CommandStreamEncoder.encode(stream.blocks));
109
+ //console.log(JSON.stringify(CommandStreamJsonifier.jsonify(parsed), null, 2));
110
+ });
111
+
112
+ it("creates group owned by Alice, Alice adds Bob as an admin, Bob adds Charlie", async () => {
113
+ const alice = await createDevice();
114
+ const bob = await createDevice();
115
+ const charlie = await createDevice();
116
+
117
+ let stream = new CommandStream();
118
+ stream = await stream.edit().seed(crypto.from_hex(DEFAULT_TOPIC)).issue(alice);
119
+ stream = await stream
120
+ .edit()
121
+ .addMember(
122
+ "Bob",
123
+ (await bob.getPublicKey()).publicKey,
124
+ Permissions.ADD_MEMBER | Permissions.KEY_READER,
125
+ )
126
+ .issue(alice);
127
+ stream = await stream
128
+ .edit()
129
+ .addMember("Charlie", (await charlie.getPublicKey()).publicKey, Permissions.KEY_READER)
130
+ .issue(bob);
131
+
132
+ const tree = StreamTree.from(stream);
133
+
134
+ // Encrypt a message from Alice
135
+ const originalMessage = new TextEncoder().encode("Hello World!");
136
+ const encryptedMessage = await StreamTreeCipher.create(alice).encrypt(
137
+ tree,
138
+ [],
139
+ originalMessage,
140
+ );
141
+ // The message should be different from the original
142
+ expect(crypto.to_hex(encryptedMessage)).not.toBe(crypto.to_hex(originalMessage));
143
+
144
+ {
145
+ // Decrypt the message from Bob
146
+ const decryptedMessage = await StreamTreeCipher.create(bob).decrypt(
147
+ tree,
148
+ [],
149
+ encryptedMessage,
150
+ );
151
+ // The message should be the same as the original
152
+ expect(crypto.to_hex(decryptedMessage)).toBe(crypto.to_hex(originalMessage));
153
+ }
154
+
155
+ {
156
+ // Decrypt the message from Charlie
157
+ const decryptedMessage = await StreamTreeCipher.create(charlie).decrypt(
158
+ tree,
159
+ [],
160
+ encryptedMessage,
161
+ );
162
+ // The message should be the same as the original
163
+ expect(crypto.to_hex(decryptedMessage)).toBe(crypto.to_hex(originalMessage));
164
+ }
165
+ //console.log(JSON.stringify(CommandStreamJsonifier.jsonify(stream.blocks), null, 2));
166
+ });
167
+ });