@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,692 @@
1
+ import {
2
+ Command,
3
+ CommandBlock,
4
+ CommandType,
5
+ AddMember,
6
+ Derive,
7
+ EditMember,
8
+ PublishKey,
9
+ Seed,
10
+ } from "./CommandBlock";
11
+ import { Device } from "./Device";
12
+ import { PublicKey } from "./PublicKey";
13
+ import Transport from "@ledgerhq/hw-transport";
14
+ import { CommandStreamEncoder } from "./CommandStreamEncoder";
15
+ import { KeyPair, crypto } from "./Crypto";
16
+ import { StreamTree } from "./StreamTree";
17
+ import { TLV, TLVField } from "./tlv";
18
+ import { SeedIdResult, parseSeedIdResult } from "./SeedId";
19
+
20
+ export const TRUSTCHAIN_APP_NAME = "Ledger Sync";
21
+
22
+ enum ParseStreamMode {
23
+ BlockHeader = 0x00,
24
+ Command = 0x01,
25
+ Signature = 0x02,
26
+ Empty = 0x03,
27
+ }
28
+
29
+ enum OutputDataMode {
30
+ None = 0x00,
31
+ TrustedParam = 0x01,
32
+ }
33
+
34
+ const TP_ENCRYPT = 1 << 7;
35
+
36
+ enum TrustedPropertiesTLV {
37
+ IV = 0x00,
38
+ IssuerPublicKey = 0x01 | TP_ENCRYPT,
39
+ Xpriv = 0x02 | TP_ENCRYPT,
40
+ EphemeralPublicKey = 0x03,
41
+ CommandIV = 0x04,
42
+ GroupKey = 0x05,
43
+ TrustedMember = 0x06 | TP_ENCRYPT,
44
+ }
45
+
46
+ interface TrustedMember {
47
+ iv: Uint8Array;
48
+ data: Uint8Array;
49
+ }
50
+
51
+ interface TrustedParams {
52
+ members: Map<string, TrustedMember>;
53
+ lastTrustedMember: string | undefined;
54
+ }
55
+
56
+ interface SignatureResponse {
57
+ signature: Uint8Array;
58
+ sessionKey: Uint8Array;
59
+ }
60
+
61
+ interface SignBlockHeaderResponse {
62
+ iv: Uint8Array;
63
+ issuer: Uint8Array;
64
+ }
65
+
66
+ interface SeedCommandResponse {
67
+ iv: Uint8Array;
68
+ xpriv: Uint8Array;
69
+ commandIv: Uint8Array;
70
+ ephemeralPublicKey: Uint8Array;
71
+ groupKey: Uint8Array;
72
+ trustedMember: Uint8Array | null;
73
+ }
74
+
75
+ interface EmptyCommandResponse {}
76
+
77
+ interface AddMemberCommandResponse {
78
+ iv: Uint8Array;
79
+ trustedMember: Uint8Array;
80
+ }
81
+
82
+ interface PublishKeyCommandResponse {
83
+ trustedMember: Uint8Array | null;
84
+ iv: Uint8Array;
85
+ xpriv: Uint8Array;
86
+ commandIv: Uint8Array;
87
+ ephemeralPublicKey: Uint8Array;
88
+ }
89
+
90
+ type CommandResponse =
91
+ | SeedCommandResponse
92
+ | AddMemberCommandResponse
93
+ | PublishKeyCommandResponse
94
+ | EmptyCommandResponse;
95
+
96
+ /**
97
+ *
98
+ */
99
+ export class APDU {
100
+ static CLA = 0xe0;
101
+
102
+ static INS_GET_PUBLIC_KEY = 0x05;
103
+ static INS_PARSE_STREAM = 0x08;
104
+ static INS_SIGN_BLOCK = 0x07;
105
+ static INS_INIT = 0x06;
106
+ static INS_SET_TRUSTED_MEMBER = 0x09;
107
+
108
+ static async setTrustedMember(transport: Transport, member: TrustedMember): Promise<void> {
109
+ const payload = new Uint8Array([
110
+ TrustedPropertiesTLV.IV,
111
+ member.iv.length,
112
+ ...member.iv,
113
+ TrustedPropertiesTLV.TrustedMember,
114
+ member.data.length,
115
+ ...member.data,
116
+ ]);
117
+ await transport.send(APDU.CLA, APDU.INS_SET_TRUSTED_MEMBER, 0, 0, Buffer.from(payload));
118
+ }
119
+
120
+ static async parseBlockHeader(transport: Transport, header: Uint8Array) {
121
+ const result = await transport.send(
122
+ APDU.CLA,
123
+ APDU.INS_PARSE_STREAM,
124
+ ParseStreamMode.BlockHeader,
125
+ OutputDataMode.None,
126
+ Buffer.from(header),
127
+ );
128
+ return APDU.getResponseData(result);
129
+ }
130
+
131
+ static async parseCommand(
132
+ transport: Transport,
133
+ command: Uint8Array,
134
+ outputTrustedParam: boolean = false,
135
+ ) {
136
+ return await transport.send(
137
+ APDU.CLA,
138
+ APDU.INS_PARSE_STREAM,
139
+ ParseStreamMode.Command,
140
+ outputTrustedParam ? OutputDataMode.TrustedParam : OutputDataMode.None,
141
+ Buffer.from(command),
142
+ );
143
+ }
144
+
145
+ static async parseSignature(transport: Transport, signature: Uint8Array) {
146
+ return await transport.send(
147
+ APDU.CLA,
148
+ APDU.INS_PARSE_STREAM,
149
+ ParseStreamMode.Signature,
150
+ OutputDataMode.None,
151
+ Buffer.from(signature),
152
+ );
153
+ }
154
+
155
+ static async initFlow(transport: Transport, sessionKey: Uint8Array): Promise<void> {
156
+ await transport.send(APDU.CLA, APDU.INS_INIT, 0x00, 0x00, Buffer.from(sessionKey));
157
+ }
158
+
159
+ static async parseEmptyStream(transport: Transport): Promise<void> {
160
+ await transport.send(
161
+ APDU.CLA,
162
+ APDU.INS_PARSE_STREAM,
163
+ ParseStreamMode.Empty,
164
+ OutputDataMode.None,
165
+ Buffer.alloc(0),
166
+ );
167
+ }
168
+
169
+ static async signBlockHeader(
170
+ transport: Transport,
171
+ header: Uint8Array,
172
+ ): Promise<SignBlockHeaderResponse> {
173
+ const data = await transport.send(
174
+ APDU.CLA,
175
+ APDU.INS_SIGN_BLOCK,
176
+ ParseStreamMode.BlockHeader,
177
+ OutputDataMode.None,
178
+ Buffer.from(header),
179
+ );
180
+ const resp = APDU.getResponseData(data);
181
+ const tlvs = TLV.readAllTLV(resp, 0);
182
+ let iv: Uint8Array | null = null;
183
+ let issuer: Uint8Array | null = null;
184
+ for (const tlv of tlvs) {
185
+ if (tlv.type === TrustedPropertiesTLV.IV) {
186
+ iv = tlv.value;
187
+ }
188
+ if (tlv.type === TrustedPropertiesTLV.IssuerPublicKey) {
189
+ issuer = tlv.value;
190
+ }
191
+ }
192
+ if (iv === null) {
193
+ throw new Error("No IV in response");
194
+ }
195
+ if (issuer === null) {
196
+ throw new Error("No issuer in response");
197
+ }
198
+ return { iv, issuer };
199
+ }
200
+
201
+ static async signCommand(transport: Transport, command: Uint8Array): Promise<Uint8Array> {
202
+ const data = await transport.send(
203
+ APDU.CLA,
204
+ APDU.INS_SIGN_BLOCK,
205
+ ParseStreamMode.Command,
206
+ OutputDataMode.None,
207
+ Buffer.from(command),
208
+ );
209
+ return APDU.getResponseData(data);
210
+ }
211
+
212
+ static async finalizeSignature(transport: Transport): Promise<SignatureResponse> {
213
+ const response = await transport.send(
214
+ APDU.CLA,
215
+ APDU.INS_SIGN_BLOCK,
216
+ ParseStreamMode.Signature,
217
+ OutputDataMode.None,
218
+ Buffer.alloc(0),
219
+ );
220
+ const data = APDU.getResponseData(response);
221
+ const sigLen = data[0];
222
+ const signature = data.slice(1, sigLen + 1);
223
+ const sessionKey = data.slice(sigLen + 2);
224
+
225
+ return { signature, sessionKey };
226
+ }
227
+
228
+ static async getPublicKey(transport: Transport): Promise<Uint8Array> {
229
+ const response = await transport.send(
230
+ APDU.CLA,
231
+ APDU.INS_GET_PUBLIC_KEY,
232
+ 0x00,
233
+ 0x00,
234
+ Buffer.alloc(0),
235
+ );
236
+ return APDU.getResponseData(response);
237
+ }
238
+
239
+ /**
240
+ * allows to sign a challenge and get the seed id
241
+ */
242
+ static async getSeedId(transport: Transport, challenge: Uint8Array): Promise<SeedIdResult> {
243
+ const response = await transport.send(
244
+ APDU.CLA,
245
+ APDU.INS_GET_PUBLIC_KEY,
246
+ 0x00,
247
+ 0x00,
248
+ Buffer.from(challenge),
249
+ );
250
+ const result = parseSeedIdResult(APDU.getResponseData(response));
251
+ return result;
252
+ }
253
+
254
+ static getResponseData(response: Buffer): Uint8Array {
255
+ return Uint8Array.prototype.slice.call(response, 0, response.length - 2);
256
+ }
257
+
258
+ static getStatusWord(response: Buffer): number {
259
+ return response.readUInt16BE(response.length - 2);
260
+ }
261
+
262
+ static parseTrustedSeed(tlvs: TLVField[]): SeedCommandResponse {
263
+ let iv: Uint8Array | null = null;
264
+ let xpriv: Uint8Array | null = null;
265
+ let ephemeralPublicKey: Uint8Array | null = null;
266
+ let commandIv: Uint8Array | null = null;
267
+ let groupKey: Uint8Array | null = null;
268
+ let trustedMember: Uint8Array | null = null;
269
+
270
+ for (const tlv of tlvs) {
271
+ switch (tlv.type) {
272
+ case TrustedPropertiesTLV.IV:
273
+ iv = tlv.value;
274
+ break;
275
+ case TrustedPropertiesTLV.Xpriv:
276
+ xpriv = tlv.value;
277
+ break;
278
+ case TrustedPropertiesTLV.EphemeralPublicKey:
279
+ ephemeralPublicKey = tlv.value;
280
+ break;
281
+ case TrustedPropertiesTLV.CommandIV:
282
+ commandIv = tlv.value;
283
+ break;
284
+ case TrustedPropertiesTLV.GroupKey:
285
+ groupKey = tlv.value;
286
+ break;
287
+ case TrustedPropertiesTLV.TrustedMember:
288
+ trustedMember = tlv.value;
289
+ break;
290
+ default:
291
+ throw new Error("Unknown trusted property");
292
+ }
293
+ }
294
+ if (iv === null) {
295
+ throw new Error("No IV in response");
296
+ }
297
+ if (xpriv === null) {
298
+ throw new Error("No xpriv in response");
299
+ }
300
+ if (ephemeralPublicKey === null) {
301
+ throw new Error("No ephemeral public key in response");
302
+ }
303
+ if (commandIv === null) {
304
+ throw new Error("No command IV in response");
305
+ }
306
+ if (groupKey === null) {
307
+ throw new Error("No group key in response");
308
+ }
309
+ return { iv, xpriv, ephemeralPublicKey, commandIv, groupKey, trustedMember };
310
+ }
311
+
312
+ static parseTrustedAddMember(tlvs: TLVField[]): AddMemberCommandResponse {
313
+ let iv: Uint8Array | null = null;
314
+ let trustedMember: Uint8Array | null = null;
315
+ for (const tlv of tlvs) {
316
+ if (tlv.type === TrustedPropertiesTLV.TrustedMember) {
317
+ trustedMember = tlv.value;
318
+ } else if (tlv.type === TrustedPropertiesTLV.IV) {
319
+ iv = tlv.value;
320
+ }
321
+ }
322
+ if (iv === null) {
323
+ throw new Error("No IV in response");
324
+ }
325
+ if (trustedMember === null) {
326
+ throw new Error("No trusted member in response");
327
+ }
328
+ return { trustedMember, iv };
329
+ }
330
+
331
+ static parseTrustedPublishKey(tlvs: TLVField[]): PublishKeyCommandResponse {
332
+ let iv: Uint8Array | null = null;
333
+ let ephemeralPublicKey: Uint8Array | null = null;
334
+ let commandIv: Uint8Array | null = null;
335
+ let trustedMember: Uint8Array | null = null;
336
+ let xpriv: Uint8Array | null = null;
337
+
338
+ for (const tlv of tlvs) {
339
+ switch (tlv.type) {
340
+ case TrustedPropertiesTLV.IV:
341
+ iv = tlv.value;
342
+ break;
343
+ case TrustedPropertiesTLV.EphemeralPublicKey:
344
+ ephemeralPublicKey = tlv.value;
345
+ break;
346
+ case TrustedPropertiesTLV.CommandIV:
347
+ commandIv = tlv.value;
348
+ break;
349
+ case TrustedPropertiesTLV.TrustedMember:
350
+ trustedMember = tlv.value;
351
+ break;
352
+ case TrustedPropertiesTLV.Xpriv:
353
+ xpriv = tlv.value;
354
+ break;
355
+ default:
356
+ continue;
357
+ }
358
+ }
359
+ if (iv === null) {
360
+ throw new Error("No IV in response");
361
+ }
362
+ if (ephemeralPublicKey === null) {
363
+ throw new Error("No ephemeral public key in response");
364
+ }
365
+ if (commandIv === null) {
366
+ throw new Error("No command IV in response");
367
+ }
368
+ if (xpriv === null) {
369
+ throw new Error("No xpriv in response");
370
+ }
371
+ return { iv, ephemeralPublicKey, commandIv, trustedMember, xpriv };
372
+ }
373
+
374
+ static parseTrustedProperties(command: Command, rawProperties: Uint8Array): CommandResponse {
375
+ const tlvs = TLV.readAllTLV(rawProperties, 0);
376
+ switch (command.getType()) {
377
+ case CommandType.Derive:
378
+ case CommandType.Seed:
379
+ return APDU.parseTrustedSeed(tlvs);
380
+ case CommandType.AddMember:
381
+ return APDU.parseTrustedAddMember(tlvs);
382
+ case CommandType.PublishKey:
383
+ return APDU.parseTrustedPublishKey(tlvs);
384
+ case CommandType.CloseStream:
385
+ return {};
386
+ default:
387
+ throw new Error("Unsupported command type");
388
+ }
389
+ }
390
+ }
391
+
392
+ async function injectTrustedProperties(
393
+ command: Command,
394
+ properties: CommandResponse,
395
+ secret: Uint8Array,
396
+ ): Promise<Command> {
397
+ switch (command.getType()) {
398
+ case CommandType.Seed: {
399
+ const seedCommand = command as Seed;
400
+ const seedProperties = properties as SeedCommandResponse;
401
+ seedCommand.encryptedXpriv = await crypto.decrypt(
402
+ secret,
403
+ seedProperties.iv,
404
+ seedProperties.xpriv,
405
+ );
406
+ seedCommand.ephemeralPublicKey = seedProperties.ephemeralPublicKey;
407
+ seedCommand.initializationVector = seedProperties.commandIv;
408
+ seedCommand.groupKey = seedProperties.groupKey;
409
+ return seedCommand;
410
+ }
411
+ case CommandType.Derive: {
412
+ const deriveCommand = command as Derive;
413
+ const deriveProperties = properties as SeedCommandResponse;
414
+ deriveCommand.encryptedXpriv = await crypto.decrypt(
415
+ secret,
416
+ deriveProperties.iv,
417
+ deriveProperties.xpriv,
418
+ );
419
+ deriveCommand.ephemeralPublicKey = deriveProperties.ephemeralPublicKey;
420
+ deriveCommand.initializationVector = deriveProperties.commandIv;
421
+ deriveCommand.groupKey = deriveProperties.groupKey;
422
+ return deriveCommand;
423
+ }
424
+ case CommandType.AddMember:
425
+ return command; // No properties to inject
426
+ case CommandType.PublishKey: {
427
+ const publishKeyCommand = command as PublishKey;
428
+ const publishKeyProperties = properties as PublishKeyCommandResponse;
429
+ publishKeyCommand.ephemeralPublicKey = publishKeyProperties.ephemeralPublicKey;
430
+ publishKeyCommand.initializationVector = publishKeyProperties.commandIv;
431
+ publishKeyCommand.encryptedXpriv = await crypto.decrypt(
432
+ secret,
433
+ publishKeyProperties.iv,
434
+ publishKeyProperties.xpriv,
435
+ );
436
+ return publishKeyCommand;
437
+ }
438
+ case CommandType.CloseStream:
439
+ return command; // No properties to inject
440
+ default:
441
+ throw new Error("Unsupported command type");
442
+ }
443
+ }
444
+
445
+ function findTrustedMember(params: TrustedParams, member: Uint8Array): TrustedMember | null {
446
+ return params.members.get(crypto.to_hex(member)) || null;
447
+ }
448
+ findTrustedMember;
449
+
450
+ export class ApduDevice implements Device {
451
+ private transport: Transport;
452
+ private sessionKeyPair: Promise<KeyPair>;
453
+
454
+ constructor(transport: Transport) {
455
+ this.transport = transport;
456
+ this.sessionKeyPair = crypto.randomKeypair();
457
+ }
458
+
459
+ isPublicKeyAvailable(): boolean {
460
+ return false;
461
+ }
462
+
463
+ async getPublicKey(): Promise<PublicKey> {
464
+ const publicKey = await APDU.getPublicKey(this.transport);
465
+ return new PublicKey(publicKey);
466
+ }
467
+
468
+ async getSeedId(data: Uint8Array): Promise<SeedIdResult> {
469
+ return APDU.getSeedId(this.transport, data);
470
+ }
471
+
472
+ private assertStreamIsValid(stream: CommandBlock[]) {
473
+ const blockToSign = stream.filter(block => block.signature.length == 0).length;
474
+ if (blockToSign !== 1)
475
+ throw new Error(
476
+ "Stream must contain exactly one block to sign. Found " + blockToSign + " blocks to sign.",
477
+ );
478
+ }
479
+
480
+ private recordTrustedMember(
481
+ trustedParams: TrustedParams,
482
+ publicKey: Uint8Array,
483
+ responseData: Uint8Array,
484
+ ) {
485
+ // Parse an APDU result as TLV and find IV and trusted member data.
486
+ // The data is then assigned to a public key. The parsing must set the
487
+ // public key depending on the current step in the flow (e.g add member
488
+ // will issue a trusted member for the added member)
489
+ const tlvs = TLV.readAllTLV(responseData, 0);
490
+ let member: Uint8Array | null = null;
491
+ let iv: Uint8Array | null = null;
492
+
493
+ if (publicKey.length == 0 || (publicKey[0] != 0x02 && publicKey[0] != 0x03)) {
494
+ // The public key is not set if it's the device itself
495
+ return;
496
+ }
497
+
498
+ for (const tlv of tlvs) {
499
+ if (tlv.type == TrustedPropertiesTLV.TrustedMember) {
500
+ member = tlv.value;
501
+ }
502
+ if (tlv.type == TrustedPropertiesTLV.IV) {
503
+ iv = tlv.value;
504
+ }
505
+ }
506
+ if (member === null || iv === null) {
507
+ return; // Do nothing trusted member is optional in some cases
508
+ // (e.g. if the trusted member is the device itself)
509
+ }
510
+ trustedParams.members.set(crypto.to_hex(publicKey), { iv, data: member });
511
+ // Set the last trusted member. This is used to prevent sending the same current trusted member
512
+ // to the device again.
513
+ trustedParams.lastTrustedMember = crypto.to_hex(publicKey);
514
+ }
515
+
516
+ private hasTrustedMember(trustedParams: TrustedParams, publicKey: Uint8Array): boolean {
517
+ return trustedParams.members.has(crypto.to_hex(publicKey));
518
+ }
519
+
520
+ // Throws if the trusted member is not found
521
+ private getTrustedMember(trustedParams: TrustedParams, publicKey: Uint8Array): TrustedMember {
522
+ const member = trustedParams.members.get(crypto.to_hex(publicKey));
523
+ if (member === undefined) {
524
+ throw new Error("Trusted member not found");
525
+ }
526
+ return member;
527
+ }
528
+
529
+ // Set the trusted member on the device if it's not already set
530
+ private async setTrustedMember(params: TrustedParams, publicKey: Uint8Array): Promise<void> {
531
+ // Check if the trusted member is already set on device
532
+ if (params.lastTrustedMember == crypto.to_hex(publicKey)) {
533
+ return;
534
+ }
535
+
536
+ // Verify we actually have the trusted member
537
+ if (this.hasTrustedMember(params, publicKey) == false) {
538
+ return;
539
+ }
540
+ // console.log("Setting trusted member: ", crypto.to_hex(publicKey));
541
+ return APDU.setTrustedMember(this.transport, this.getTrustedMember(params, publicKey));
542
+ }
543
+
544
+ private async parseBlock(block: CommandBlock, trustedParams: TrustedParams): Promise<void> {
545
+ let result: Uint8Array;
546
+
547
+ // Parse the block header
548
+ await this.setTrustedMember(trustedParams, block.issuer);
549
+ result = await APDU.parseBlockHeader(
550
+ this.transport,
551
+ CommandStreamEncoder.encodeBlockHeader(block),
552
+ );
553
+ // Record potential trusted member
554
+ this.recordTrustedMember(trustedParams, block.issuer, result);
555
+ for (const index in block.commands) {
556
+ // Parse the command
557
+ const command = block.commands[index];
558
+
559
+ // Set the trusted member depending on the command
560
+ switch (command.getType()) {
561
+ case CommandType.AddMember:
562
+ await this.setTrustedMember(trustedParams, block.issuer);
563
+ break;
564
+ case CommandType.PublishKey:
565
+ await this.setTrustedMember(trustedParams, (command as PublishKey).recipient);
566
+ break;
567
+ case CommandType.EditMember:
568
+ await this.setTrustedMember(trustedParams, (command as EditMember).member);
569
+ break;
570
+ default:
571
+ // Do nothing
572
+ break;
573
+ }
574
+ result = await APDU.parseCommand(
575
+ this.transport,
576
+ CommandStreamEncoder.encodeCommand(block, parseInt(index)),
577
+ true,
578
+ );
579
+ // Record potential trusted member
580
+ switch (command.getType()) {
581
+ case CommandType.Seed:
582
+ this.recordTrustedMember(trustedParams, block.issuer, result);
583
+ break;
584
+ case CommandType.AddMember:
585
+ this.recordTrustedMember(trustedParams, (command as AddMember).publicKey, result);
586
+ break;
587
+ case CommandType.PublishKey:
588
+ this.recordTrustedMember(trustedParams, (command as PublishKey).recipient, result);
589
+ break;
590
+ case CommandType.Derive:
591
+ this.recordTrustedMember(trustedParams, block.issuer, result);
592
+ break;
593
+ case CommandType.EditMember:
594
+ this.recordTrustedMember(trustedParams, (command as EditMember).member, result);
595
+ break;
596
+ }
597
+ }
598
+ // Parse the block signature
599
+ await APDU.parseSignature(this.transport, CommandStreamEncoder.encodeSignature(block));
600
+ }
601
+
602
+ private async parseStream(stream: CommandBlock[]): Promise<TrustedParams> {
603
+ const trustedParams: TrustedParams = {
604
+ members: new Map<string, TrustedMember>(),
605
+ lastTrustedMember: undefined,
606
+ };
607
+ if (stream.length == 0) {
608
+ await APDU.parseEmptyStream(this.transport);
609
+ }
610
+ for (const block of stream.slice(0, stream.length - 1)) {
611
+ await this.parseBlock(block, trustedParams);
612
+ }
613
+ return trustedParams;
614
+ }
615
+
616
+ async sign(stream: CommandBlock[]): Promise<CommandBlock> {
617
+ const sessionKey = await this.sessionKeyPair;
618
+ const trustedProperties: CommandResponse[] = [];
619
+
620
+ // We expect the stream to have a single block to sign (the last one)
621
+ this.assertStreamIsValid(stream);
622
+
623
+ // Init signature flow
624
+ await APDU.initFlow(this.transport, sessionKey.publicKey);
625
+
626
+ // Before signing we need to parse the stream on device and get trusted params
627
+ const trustedParams = await this.parseStream(stream);
628
+ trustedParams;
629
+
630
+ // Create the new block to sign
631
+ const blockToSign = stream[stream.length - 1];
632
+ const trustedIssuer = await APDU.signBlockHeader(
633
+ this.transport,
634
+ CommandStreamEncoder.encodeBlockHeader(blockToSign),
635
+ );
636
+
637
+ // Pass all commands to device
638
+ for (let commandIndex = 0; commandIndex < blockToSign.commands.length; commandIndex++) {
639
+ // Pass the trusted param allowing the command to the device
640
+ // If we have no trusted param we need an explicit approval
641
+ const tp = await APDU.signCommand(
642
+ this.transport,
643
+ CommandStreamEncoder.encodeCommand(blockToSign, commandIndex),
644
+ );
645
+ trustedProperties.push(APDU.parseTrustedProperties(blockToSign.commands[commandIndex], tp));
646
+ }
647
+
648
+ // Finalize block signature
649
+ const signature = await APDU.finalizeSignature(this.transport);
650
+
651
+ // Decrypt and inject trusted issuer
652
+ const secret = await crypto.ecdh(sessionKey, signature.sessionKey);
653
+ const issuer = await crypto.decrypt(secret, trustedIssuer.iv, trustedIssuer.issuer);
654
+
655
+ // Inject trusted properties for commands
656
+ for (let commandIndex = 0; commandIndex < blockToSign.commands.length; commandIndex++) {
657
+ blockToSign.commands[commandIndex] = await injectTrustedProperties(
658
+ blockToSign.commands[commandIndex],
659
+ trustedProperties[commandIndex],
660
+ secret,
661
+ );
662
+ }
663
+ blockToSign.issuer = issuer;
664
+ blockToSign.signature = signature.signature;
665
+ return blockToSign;
666
+ }
667
+
668
+ async readKey(tree: StreamTree, path: number[]): Promise<Uint8Array> {
669
+ tree as StreamTree;
670
+ path as number[];
671
+ throw new Error("readKey is not supported on hardware devices");
672
+ }
673
+
674
+ async isConnected(): Promise<boolean> {
675
+ const response = await this.transport.send(0xe0, 0x04, 0x00, 0x00);
676
+ const sw = response.readUInt16BE(response.length - 2);
677
+ if (sw !== 0x9000) return false;
678
+ const appName = response.subarray(0, response.length - 2).toString();
679
+ return appName === TRUSTCHAIN_APP_NAME;
680
+ }
681
+
682
+ async close(): Promise<void> {
683
+ await this.transport.close();
684
+ }
685
+ }
686
+
687
+ /**
688
+ *
689
+ */
690
+ export function createApduDevice(transport: Transport): ApduDevice {
691
+ return new ApduDevice(transport);
692
+ }
@@ -0,0 +1,25 @@
1
+ export default class BigEndian {
2
+ public static shortToArray(n: number): Uint8Array {
3
+ const array = new Uint8Array(2);
4
+ const view = new DataView(array.buffer);
5
+ view.setUint16(0, n, false);
6
+ return array;
7
+ }
8
+
9
+ public static arrayToShort(array: Uint8Array): number {
10
+ const view = new DataView(array.buffer);
11
+ return view.getUint16(0, false);
12
+ }
13
+
14
+ public static numberToArray(n: number): Uint8Array {
15
+ const array = new Uint8Array(4);
16
+ const view = new DataView(array.buffer);
17
+ view.setUint32(0, n, false);
18
+ return array;
19
+ }
20
+
21
+ public static arrayToNumber(array: Uint8Array): number {
22
+ const view = new DataView(array.buffer);
23
+ return view.getUint32(0, false);
24
+ }
25
+ }