bsv-bap 0.0.1
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.
- package/.babelrc +20 -0
- package/.eslintrc +46 -0
- package/LICENSE +25 -0
- package/README.md +819 -0
- package/babel.config.js +6 -0
- package/bun.lockb +0 -0
- package/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/constants.ts.html +113 -0
- package/coverage/lcov-report/src/id.ts.html +2207 -0
- package/coverage/lcov-report/src/index.html +156 -0
- package/coverage/lcov-report/src/index.ts.html +1877 -0
- package/coverage/lcov-report/src/utils.ts.html +404 -0
- package/coverage/lcov-report/tests/data/index.html +111 -0
- package/coverage/lcov-report/tests/data/keys.js.html +86 -0
- package/coverage/lcov.info +0 -0
- package/dist/jest.config.d.ts +8 -0
- package/dist/src/constants.d.ts +8 -0
- package/dist/src/id.d.ts +295 -0
- package/dist/src/index.d.ts +238 -0
- package/dist/src/interface.d.ts +23 -0
- package/dist/src/poa.d.ts +6 -0
- package/dist/src/utils.d.ts +54 -0
- package/dist/typescript-npm-package.cjs.d.ts +554 -0
- package/dist/typescript-npm-package.cjs.js +1320 -0
- package/dist/typescript-npm-package.cjs.js.map +1 -0
- package/dist/typescript-npm-package.esm.d.ts +554 -0
- package/dist/typescript-npm-package.esm.js +1312 -0
- package/dist/typescript-npm-package.esm.js.map +1 -0
- package/dist/typescript-npm-package.umd.d.ts +554 -0
- package/dist/typescript-npm-package.umd.js +110193 -0
- package/dist/typescript-npm-package.umd.js.map +1 -0
- package/jest.config.ts +196 -0
- package/jsdoc.json +16 -0
- package/package.json +80 -0
- package/rollup.config.js +64 -0
- package/setup-jest.js +1 -0
- package/src/README.md +80 -0
- package/src/attributes.json +119 -0
- package/src/constants.ts +11 -0
- package/src/id.ts +783 -0
- package/src/index.ts +631 -0
- package/src/interface.ts +26 -0
- package/src/poa.ts +9 -0
- package/src/utils.ts +111 -0
- package/tests/data/ids.json +30 -0
- package/tests/data/keys.js +2 -0
- package/tests/data/old-ids.json +25 -0
- package/tests/data/test-vectors.json +122 -0
- package/tests/id.test.js +286 -0
- package/tests/index.test.js +335 -0
- package/tests/regression.test.js +28 -0
- package/tests/utils.test.js +27 -0
- package/tsconfig.json +17 -0
package/src/id.ts
ADDED
@@ -0,0 +1,783 @@
|
|
1
|
+
import { BSM, Hash, type HD, ECIES, PublicKey } from "@bsv/sdk";
|
2
|
+
import {
|
3
|
+
MAX_INT,
|
4
|
+
SIGNING_PATH_PREFIX,
|
5
|
+
BAP_SERVER,
|
6
|
+
BAP_BITCOM_ADDRESS,
|
7
|
+
AIP_BITCOM_ADDRESS,
|
8
|
+
ENCRYPTION_PATH,
|
9
|
+
} from "./constants";
|
10
|
+
import { Utils } from "./utils";
|
11
|
+
import type { Identity } from "./interface";
|
12
|
+
import { Utils as BSVUtils } from "@bsv/sdk";
|
13
|
+
const { toArray, toHex, toBase58, toUTF8, toBase64 } = BSVUtils;
|
14
|
+
const { bitcoreDecrypt, bitcoreEncrypt } = ECIES;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* BAP_ID class
|
18
|
+
*
|
19
|
+
* This class should be used in conjunction with the BAP class
|
20
|
+
*
|
21
|
+
* @type {BAP_ID}
|
22
|
+
*/
|
23
|
+
class BAP_ID {
|
24
|
+
#HDPrivateKey: HD;
|
25
|
+
#BAP_SERVER: string = BAP_SERVER;
|
26
|
+
#BAP_TOKEN = "";
|
27
|
+
#rootPath: string;
|
28
|
+
#previousPath: string;
|
29
|
+
#currentPath: string;
|
30
|
+
#idSeed: string;
|
31
|
+
|
32
|
+
idName: string;
|
33
|
+
description: string;
|
34
|
+
|
35
|
+
rootAddress: string;
|
36
|
+
identityKey: string;
|
37
|
+
identityAttributes: { [key: string]: any };
|
38
|
+
|
39
|
+
constructor(
|
40
|
+
HDPrivateKey: HD,
|
41
|
+
identityAttributes: { [key: string]: any } = {},
|
42
|
+
idSeed = "",
|
43
|
+
) {
|
44
|
+
this.#idSeed = idSeed;
|
45
|
+
if (idSeed) {
|
46
|
+
// create a new HDPrivateKey based on the seed
|
47
|
+
const seedHex = toHex(Hash.sha256(idSeed, "utf8"));
|
48
|
+
const seedPath = Utils.getSigningPathFromHex(seedHex);
|
49
|
+
this.#HDPrivateKey = HDPrivateKey.derive(seedPath);
|
50
|
+
} else {
|
51
|
+
this.#HDPrivateKey = HDPrivateKey;
|
52
|
+
}
|
53
|
+
|
54
|
+
this.idName = "ID 1";
|
55
|
+
this.description = "";
|
56
|
+
|
57
|
+
this.#rootPath = `${SIGNING_PATH_PREFIX}/0/0/0`;
|
58
|
+
this.#previousPath = `${SIGNING_PATH_PREFIX}/0/0/0`;
|
59
|
+
this.#currentPath = `${SIGNING_PATH_PREFIX}/0/0/1`;
|
60
|
+
|
61
|
+
const rootChild = this.#HDPrivateKey.derive(this.#rootPath);
|
62
|
+
this.rootAddress = rootChild.privKey.toPublicKey().toAddress();
|
63
|
+
this.identityKey = this.deriveIdentityKey(this.rootAddress);
|
64
|
+
|
65
|
+
// unlink the object
|
66
|
+
identityAttributes = { ...identityAttributes };
|
67
|
+
this.identityAttributes = this.parseAttributes(identityAttributes);
|
68
|
+
}
|
69
|
+
|
70
|
+
set BAP_SERVER(bapServer) {
|
71
|
+
this.#BAP_SERVER = bapServer;
|
72
|
+
}
|
73
|
+
|
74
|
+
get BAP_SERVER(): string {
|
75
|
+
return this.#BAP_SERVER;
|
76
|
+
}
|
77
|
+
|
78
|
+
set BAP_TOKEN(token) {
|
79
|
+
this.#BAP_TOKEN = token;
|
80
|
+
}
|
81
|
+
|
82
|
+
get BAP_TOKEN(): string {
|
83
|
+
return this.#BAP_TOKEN;
|
84
|
+
}
|
85
|
+
|
86
|
+
deriveIdentityKey(address: string): string {
|
87
|
+
// base58( ripemd160 ( sha256 ( rootAddress ) ) )
|
88
|
+
const rootAddressHash = toHex(Hash.sha256(address, "utf8"));
|
89
|
+
|
90
|
+
return toBase58(Hash.ripemd160(rootAddressHash, "hex"));
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Helper function to parse identity attributes
|
95
|
+
*
|
96
|
+
* @param identityAttributes
|
97
|
+
* @returns {{}}
|
98
|
+
*/
|
99
|
+
parseAttributes(identityAttributes: { [key: string]: any } | string): {
|
100
|
+
[key: string]: any;
|
101
|
+
} {
|
102
|
+
if (typeof identityAttributes === "string") {
|
103
|
+
return this.parseStringUrns(identityAttributes);
|
104
|
+
}
|
105
|
+
|
106
|
+
for (const key in identityAttributes) {
|
107
|
+
if (!identityAttributes[key].value || !identityAttributes[key].nonce) {
|
108
|
+
throw new Error("Invalid identity attribute");
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
return identityAttributes || {};
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Parse a text of urn string into identity attributes
|
117
|
+
*
|
118
|
+
* urn:bap:id:name:John Doe:e2c6fb4063cc04af58935737eaffc938011dff546d47b7fbb18ed346f8c4d4fa
|
119
|
+
* urn:bap:id:birthday:1990-05-22:e61f23cbbb2284842d77965e2b0e32f0ca890b1894ca4ce652831347ee3596d9
|
120
|
+
* urn:bap:id:over18:1:480ca17ccaacd671b28dc811332525f2f2cd594d8e8e7825de515ce5d52d30e8
|
121
|
+
*
|
122
|
+
* @param urnIdentityAttributes
|
123
|
+
*/
|
124
|
+
parseStringUrns(urnIdentityAttributes: string): { [key: string]: any } {
|
125
|
+
const identityAttributes: { [key: string]: any } = {};
|
126
|
+
// avoid forEach
|
127
|
+
|
128
|
+
const attributesRaw = urnIdentityAttributes
|
129
|
+
.replace(/^\s+/g, "")
|
130
|
+
.replace(/\r/gm, "")
|
131
|
+
.split("\n");
|
132
|
+
|
133
|
+
for (const line of attributesRaw) {
|
134
|
+
// remove any whitespace from the string (trim)
|
135
|
+
const attribute = line.replace(/^\s+/g, "").replace(/\s+$/g, "");
|
136
|
+
const urn = attribute.split(":");
|
137
|
+
if (
|
138
|
+
urn[0] === "urn" &&
|
139
|
+
urn[1] === "bap" &&
|
140
|
+
urn[2] === "id" &&
|
141
|
+
urn[3] &&
|
142
|
+
urn[4] &&
|
143
|
+
urn[5]
|
144
|
+
) {
|
145
|
+
identityAttributes[urn[3]] = {
|
146
|
+
value: urn[4],
|
147
|
+
nonce: urn[5],
|
148
|
+
};
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
return identityAttributes;
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Returns the identity key
|
157
|
+
*
|
158
|
+
* @returns {*|string}
|
159
|
+
*/
|
160
|
+
getIdentityKey(): string {
|
161
|
+
return this.identityKey;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Returns all the attributes in the identity
|
166
|
+
*
|
167
|
+
* @returns {*}
|
168
|
+
*/
|
169
|
+
getAttributes(): { [key: string]: any } {
|
170
|
+
return this.identityAttributes;
|
171
|
+
}
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Get the value of the given attribute
|
175
|
+
*
|
176
|
+
* @param attributeName
|
177
|
+
* @returns {{}|null}
|
178
|
+
*/
|
179
|
+
getAttribute(attributeName: string): any {
|
180
|
+
if (this.identityAttributes[attributeName]) {
|
181
|
+
return this.identityAttributes[attributeName];
|
182
|
+
}
|
183
|
+
|
184
|
+
return null;
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Set the value of the given attribute
|
189
|
+
*
|
190
|
+
* If an empty value ('' || null || false) is given, the attribute is removed from the ID
|
191
|
+
*
|
192
|
+
* @param attributeName string
|
193
|
+
* @param attributeValue any
|
194
|
+
* @returns {{}|null}
|
195
|
+
*/
|
196
|
+
setAttribute(attributeName: string, attributeValue: any): void {
|
197
|
+
if (attributeValue) {
|
198
|
+
if (this.identityAttributes[attributeName]) {
|
199
|
+
this.identityAttributes[attributeName].value = attributeValue;
|
200
|
+
} else {
|
201
|
+
this.addAttribute(attributeName, attributeValue);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
/**
|
207
|
+
* Unset the given attribute from the ID
|
208
|
+
*
|
209
|
+
* @param attributeName
|
210
|
+
* @returns {{}|null}
|
211
|
+
*/
|
212
|
+
unsetAttribute(attributeName: string): void {
|
213
|
+
delete this.identityAttributes[attributeName];
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Get all attribute urn's for this id
|
218
|
+
*
|
219
|
+
* @returns {string}
|
220
|
+
*/
|
221
|
+
getAttributeUrns(): string {
|
222
|
+
let urns = "";
|
223
|
+
for (const key in this.identityAttributes) {
|
224
|
+
const urn = this.getAttributeUrn(key);
|
225
|
+
if (urn) {
|
226
|
+
urns += `${urn}\n`;
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
return urns;
|
231
|
+
}
|
232
|
+
|
233
|
+
/**
|
234
|
+
* Create an return the attribute urn for the given attribute
|
235
|
+
*
|
236
|
+
* @param attributeName
|
237
|
+
* @returns {string|null}
|
238
|
+
*/
|
239
|
+
getAttributeUrn(attributeName: string) {
|
240
|
+
const attribute = this.identityAttributes[attributeName];
|
241
|
+
if (attribute) {
|
242
|
+
return `urn:bap:id:${attributeName}:${attribute.value}:${attribute.nonce}`;
|
243
|
+
}
|
244
|
+
|
245
|
+
return null;
|
246
|
+
}
|
247
|
+
|
248
|
+
/**
|
249
|
+
* Add an attribute to this identity
|
250
|
+
*
|
251
|
+
* @param attributeName
|
252
|
+
* @param value
|
253
|
+
* @param nonce
|
254
|
+
*/
|
255
|
+
addAttribute(attributeName: string, value: any, nonce = ""): void {
|
256
|
+
if (!nonce) {
|
257
|
+
nonce = Utils.getRandomString();
|
258
|
+
}
|
259
|
+
|
260
|
+
this.identityAttributes[attributeName] = {
|
261
|
+
value,
|
262
|
+
nonce,
|
263
|
+
};
|
264
|
+
}
|
265
|
+
|
266
|
+
/**
|
267
|
+
* This should be called with the last part of the signing path (/.../.../...)
|
268
|
+
* This library assumes the first part is m/424150'/0'/0' as defined at the top of this file
|
269
|
+
*
|
270
|
+
* @param path The second path of the signing path in the format [0-9]{0,9}/[0-9]{0,9}/[0-9]{0,9}
|
271
|
+
*/
|
272
|
+
set rootPath(path) {
|
273
|
+
if (this.#HDPrivateKey) {
|
274
|
+
if (path.split("/").length < 5) {
|
275
|
+
path = `${SIGNING_PATH_PREFIX}${path}`;
|
276
|
+
}
|
277
|
+
|
278
|
+
if (!this.validatePath(path)) {
|
279
|
+
throw new Error(`invalid signing path given ${path}`);
|
280
|
+
}
|
281
|
+
|
282
|
+
this.#rootPath = path;
|
283
|
+
|
284
|
+
const derivedChild = this.#HDPrivateKey.derive(path);
|
285
|
+
this.rootAddress = derivedChild.pubKey.toAddress();
|
286
|
+
// Identity keys should be derivatives of the root address - this allows checking
|
287
|
+
// of the creation transaction
|
288
|
+
this.identityKey = this.deriveIdentityKey(this.rootAddress);
|
289
|
+
|
290
|
+
// we also set this previousPath / currentPath to the root as we seem to be (re)setting this ID
|
291
|
+
this.#previousPath = path;
|
292
|
+
this.#currentPath = path;
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
get rootPath(): string {
|
297
|
+
return this.#rootPath;
|
298
|
+
}
|
299
|
+
|
300
|
+
getRootPath(): string {
|
301
|
+
return this.#rootPath;
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
* This should be called with the last part of the signing path (/.../.../...)
|
306
|
+
* This library assumes the first part is m/424150'/0'/0' as defined at the top of this file
|
307
|
+
*
|
308
|
+
* @param path The second path of the signing path in the format [0-9]{0,9}/[0-9]{0,9}/[0-9]{0,9}
|
309
|
+
*/
|
310
|
+
set currentPath(path) {
|
311
|
+
if (path.split("/").length < 5) {
|
312
|
+
path = `${SIGNING_PATH_PREFIX}${path}`;
|
313
|
+
}
|
314
|
+
|
315
|
+
if (!this.validatePath(path)) {
|
316
|
+
throw new Error("invalid signing path given");
|
317
|
+
}
|
318
|
+
|
319
|
+
this.#previousPath = this.#currentPath;
|
320
|
+
this.#currentPath = path;
|
321
|
+
}
|
322
|
+
|
323
|
+
get currentPath(): string {
|
324
|
+
return this.#currentPath;
|
325
|
+
}
|
326
|
+
|
327
|
+
get previousPath(): string {
|
328
|
+
return this.#previousPath;
|
329
|
+
}
|
330
|
+
|
331
|
+
/**
|
332
|
+
* This can be used to break the deterministic way child keys are created to make it harder for
|
333
|
+
* an attacker to steal the identites when the root key is compromised. This does however require
|
334
|
+
* the seeds to be stored at all times. If the seed is lost, the identity will not be recoverable.
|
335
|
+
*/
|
336
|
+
get idSeed(): string {
|
337
|
+
return this.#idSeed;
|
338
|
+
}
|
339
|
+
|
340
|
+
/**
|
341
|
+
* Increment current path to a new path
|
342
|
+
*
|
343
|
+
* @returns {*}
|
344
|
+
*/
|
345
|
+
incrementPath(): void {
|
346
|
+
this.currentPath = Utils.getNextPath(this.currentPath);
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* Check whether the given path is a valid path for use with this class
|
351
|
+
* The signing paths used here always have a length of 3
|
352
|
+
*
|
353
|
+
* @param path The last part of the signing path (example "/0/0/1")
|
354
|
+
* @returns {boolean}
|
355
|
+
*/
|
356
|
+
validatePath(path: string) {
|
357
|
+
/* eslint-disable max-len */
|
358
|
+
if (
|
359
|
+
path.match(
|
360
|
+
/\/[0-9]{1,10}'?\/[0-9]{1,10}'?\/[0-9]{1,10}'?\/[0-9]{1,10}'?\/[0-9]{1,10}'?\/[0-9]{1,10}'?/,
|
361
|
+
)
|
362
|
+
) {
|
363
|
+
const pathValues = path.split("/");
|
364
|
+
if (
|
365
|
+
pathValues.length === 7 &&
|
366
|
+
Number(pathValues[1].replace("'", "")) <= MAX_INT &&
|
367
|
+
Number(pathValues[2].replace("'", "")) <= MAX_INT &&
|
368
|
+
Number(pathValues[3].replace("'", "")) <= MAX_INT &&
|
369
|
+
Number(pathValues[4].replace("'", "")) <= MAX_INT &&
|
370
|
+
Number(pathValues[5].replace("'", "")) <= MAX_INT &&
|
371
|
+
Number(pathValues[6].replace("'", "")) <= MAX_INT
|
372
|
+
) {
|
373
|
+
return true;
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
return false;
|
378
|
+
}
|
379
|
+
|
380
|
+
/**
|
381
|
+
* Get the OP_RETURN for the initial ID transaction (signed with root address)
|
382
|
+
*
|
383
|
+
* @returns {[]}
|
384
|
+
*/
|
385
|
+
getInitialIdTransaction() {
|
386
|
+
return this.getIdTransaction(this.#rootPath);
|
387
|
+
}
|
388
|
+
|
389
|
+
/**
|
390
|
+
* Get the OP_RETURN for the ID transaction of the current address / path
|
391
|
+
*
|
392
|
+
* @returns {[]}
|
393
|
+
*/
|
394
|
+
getIdTransaction(previousPath = "") {
|
395
|
+
if (this.#currentPath === this.#rootPath) {
|
396
|
+
throw new Error(
|
397
|
+
"Current path equals rootPath. ID was probably not initialized properly",
|
398
|
+
);
|
399
|
+
}
|
400
|
+
|
401
|
+
const opReturn = [
|
402
|
+
Buffer.from(BAP_BITCOM_ADDRESS).toString("hex"),
|
403
|
+
Buffer.from("ID").toString("hex"),
|
404
|
+
Buffer.from(this.identityKey).toString("hex"),
|
405
|
+
Buffer.from(this.getCurrentAddress()).toString("hex"),
|
406
|
+
];
|
407
|
+
|
408
|
+
previousPath = previousPath || this.#previousPath;
|
409
|
+
|
410
|
+
return this.signOpReturnWithAIP(opReturn, previousPath);
|
411
|
+
}
|
412
|
+
|
413
|
+
/**
|
414
|
+
* Get address for given path
|
415
|
+
*
|
416
|
+
* @param path
|
417
|
+
* @returns {*}
|
418
|
+
*/
|
419
|
+
getAddress(path: string): string {
|
420
|
+
const derivedChild = this.#HDPrivateKey.derive(path);
|
421
|
+
return derivedChild.privKey.toPublicKey().toAddress();
|
422
|
+
}
|
423
|
+
|
424
|
+
/**
|
425
|
+
* Get current signing address
|
426
|
+
*
|
427
|
+
* @returns {*}
|
428
|
+
*/
|
429
|
+
getCurrentAddress(): string {
|
430
|
+
return this.getAddress(this.#currentPath);
|
431
|
+
}
|
432
|
+
|
433
|
+
/**
|
434
|
+
* Get the public key for encrypting data for this identity
|
435
|
+
*/
|
436
|
+
getEncryptionPublicKey(): string {
|
437
|
+
const HDPrivateKey = this.#HDPrivateKey.derive(this.#rootPath);
|
438
|
+
const encryptionKey = HDPrivateKey.derive(ENCRYPTION_PATH).privKey;
|
439
|
+
// @ts-ignore
|
440
|
+
return encryptionKey.toPublicKey().toString();
|
441
|
+
}
|
442
|
+
|
443
|
+
/**
|
444
|
+
* Get the public key for encrypting data for this identity, using a seed for the encryption
|
445
|
+
*/
|
446
|
+
getEncryptionPublicKeyWithSeed(seed: string): string {
|
447
|
+
const encryptionKey = this.getEncryptionPrivateKeyWithSeed(seed);
|
448
|
+
// @ts-ignore
|
449
|
+
return encryptionKey.toPublicKey().toString("hex");
|
450
|
+
}
|
451
|
+
|
452
|
+
/**
|
453
|
+
* Encrypt the given string data with the identity encryption key
|
454
|
+
* @param stringData
|
455
|
+
* @param counterPartyPublicKey Optional public key of the counterparty
|
456
|
+
* @return string Base64
|
457
|
+
*/
|
458
|
+
encrypt(stringData: string, counterPartyPublicKey?: string): string {
|
459
|
+
const HDPrivateKey = this.#HDPrivateKey.derive(this.#rootPath);
|
460
|
+
const encryptionKey = HDPrivateKey.derive(ENCRYPTION_PATH).privKey;
|
461
|
+
const publicKey = encryptionKey.toPublicKey();
|
462
|
+
const pubKey = counterPartyPublicKey
|
463
|
+
? PublicKey.fromString(counterPartyPublicKey)
|
464
|
+
: publicKey;
|
465
|
+
return toBase64(bitcoreEncrypt(toArray(stringData), pubKey));
|
466
|
+
}
|
467
|
+
|
468
|
+
/**
|
469
|
+
* Decrypt the given ciphertext with the identity encryption key
|
470
|
+
* @param ciphertext
|
471
|
+
* @param counterPartyPublicKey Optional public key of the counterparty
|
472
|
+
*/
|
473
|
+
decrypt(ciphertext: string, counterPartyPublicKey?: string): string {
|
474
|
+
const HDPrivateKey = this.#HDPrivateKey.derive(this.#rootPath);
|
475
|
+
const encryptionKey = HDPrivateKey.derive(ENCRYPTION_PATH).privKey;
|
476
|
+
// const ecies = new ECIES();
|
477
|
+
|
478
|
+
// if (counterPartyPublicKey) {
|
479
|
+
// return toUTF8(bitcoreDecrypt(toArray(Buffer.from(ciphertext, 'base64'), 'base64'), encryptionKey))
|
480
|
+
// }
|
481
|
+
|
482
|
+
// TODO: It seems the counterPartyPublicKey is not being used here
|
483
|
+
return toUTF8(
|
484
|
+
bitcoreDecrypt(
|
485
|
+
toArray(Buffer.from(ciphertext, "base64"), "base64"),
|
486
|
+
encryptionKey,
|
487
|
+
),
|
488
|
+
);
|
489
|
+
// ecies.privateKey(encryptionKey);
|
490
|
+
// if (counterPartyPublicKey) {
|
491
|
+
// ecies.publicKey(counterPartyPublicKey);
|
492
|
+
// }
|
493
|
+
// return ecies.decrypt(Buffer.from(ciphertext, 'base64')).toString();
|
494
|
+
}
|
495
|
+
|
496
|
+
/**
|
497
|
+
* Encrypt the given string data with the identity encryption key
|
498
|
+
* @param stringData
|
499
|
+
* @param seed String seed
|
500
|
+
* @param counterPartyPublicKey Optional public key of the counterparty
|
501
|
+
* @return string Base64
|
502
|
+
*/
|
503
|
+
encryptWithSeed(
|
504
|
+
stringData: string,
|
505
|
+
seed: string,
|
506
|
+
counterPartyPublicKey?: string,
|
507
|
+
): string {
|
508
|
+
const encryptionKey = this.getEncryptionPrivateKeyWithSeed(seed);
|
509
|
+
const publicKey = encryptionKey.toPublicKey();
|
510
|
+
|
511
|
+
// const ecies = new ECIES();
|
512
|
+
if (counterPartyPublicKey) {
|
513
|
+
// ecies.privateKey(encryptionKey);
|
514
|
+
// ecies.publicKey(counterPartyPublicKey);
|
515
|
+
return toBase64(
|
516
|
+
bitcoreEncrypt(
|
517
|
+
toArray(stringData),
|
518
|
+
PublicKey.fromString(counterPartyPublicKey),
|
519
|
+
),
|
520
|
+
);
|
521
|
+
}
|
522
|
+
// ecies.publicKey(publicKey);
|
523
|
+
return toBase64(bitcoreEncrypt(toArray(stringData), publicKey));
|
524
|
+
// return ecies.encrypt(stringData).toString('base64');
|
525
|
+
}
|
526
|
+
|
527
|
+
/**
|
528
|
+
* Decrypt the given ciphertext with the identity encryption key
|
529
|
+
* @param ciphertext
|
530
|
+
* @param seed String seed
|
531
|
+
* @param counterPartyPublicKey Public key of the counterparty
|
532
|
+
*/
|
533
|
+
decryptWithSeed(
|
534
|
+
ciphertext: string,
|
535
|
+
seed: string,
|
536
|
+
counterPartyPublicKey?: string,
|
537
|
+
): string {
|
538
|
+
const encryptionKey = this.getEncryptionPrivateKeyWithSeed(seed);
|
539
|
+
// const ecies = new ECIES();
|
540
|
+
// ecies.privateKey(encryptionKey);
|
541
|
+
// if (counterPartyPublicKey) {
|
542
|
+
// ecies.publicKey(counterPartyPublicKey);
|
543
|
+
// TODOL: It seems the counterPartyPublicKey is not being used here
|
544
|
+
return toUTF8(
|
545
|
+
bitcoreDecrypt(
|
546
|
+
toArray(Buffer.from(ciphertext, "base64"), "base64"),
|
547
|
+
encryptionKey,
|
548
|
+
),
|
549
|
+
);
|
550
|
+
// }
|
551
|
+
|
552
|
+
// return ecies.decrypt(Buffer.from(ciphertext, 'base64')).toString();
|
553
|
+
}
|
554
|
+
|
555
|
+
private getEncryptionPrivateKeyWithSeed(seed: string) {
|
556
|
+
const pathHex = toHex(Hash.sha256(seed, "utf8"));
|
557
|
+
const path = Utils.getSigningPathFromHex(pathHex);
|
558
|
+
|
559
|
+
const HDPrivateKey = this.#HDPrivateKey.derive(this.#rootPath);
|
560
|
+
return HDPrivateKey.derive(path).privKey;
|
561
|
+
}
|
562
|
+
|
563
|
+
/**
|
564
|
+
* Get an attestation string for the given urn for this identity
|
565
|
+
*
|
566
|
+
* @param urn
|
567
|
+
* @returns {string}
|
568
|
+
*/
|
569
|
+
getAttestation(urn: string) {
|
570
|
+
const urnHash = Hash.sha256(urn, "utf8");
|
571
|
+
return `bap:attest:${toHex(urnHash)}:${this.getIdentityKey()}`;
|
572
|
+
}
|
573
|
+
|
574
|
+
/**
|
575
|
+
* Generate and return the attestation hash for the given attribute of this identity
|
576
|
+
*
|
577
|
+
* @param attribute Attribute name (name, email etc.)
|
578
|
+
* @returns {string}
|
579
|
+
*/
|
580
|
+
getAttestationHash(attribute: string) {
|
581
|
+
const urn = this.getAttributeUrn(attribute);
|
582
|
+
if (!urn) return null;
|
583
|
+
|
584
|
+
const attestation = this.getAttestation(urn);
|
585
|
+
const attestationHash = Hash.sha256(attestation, "utf8");
|
586
|
+
|
587
|
+
return toHex(attestationHash);
|
588
|
+
}
|
589
|
+
|
590
|
+
/**
|
591
|
+
* Sign a message with the current signing address of this identity
|
592
|
+
*
|
593
|
+
* @param message
|
594
|
+
* @param signingPath
|
595
|
+
* @returns {{address, signature}}
|
596
|
+
*/
|
597
|
+
signMessage(message: string | Buffer, signingPath = "") {
|
598
|
+
let msg: Buffer;
|
599
|
+
if (!(message instanceof Buffer)) {
|
600
|
+
msg = Buffer.from(message);
|
601
|
+
} else {
|
602
|
+
msg = message;
|
603
|
+
}
|
604
|
+
|
605
|
+
signingPath = signingPath || this.#currentPath;
|
606
|
+
const childPk = this.#HDPrivateKey.derive(signingPath).privKey;
|
607
|
+
const address = childPk.toAddress();
|
608
|
+
const signature = BSM.sign(toArray(msg), childPk).toCompact(
|
609
|
+
0,
|
610
|
+
true,
|
611
|
+
"base64",
|
612
|
+
) as string;
|
613
|
+
|
614
|
+
return { address, signature };
|
615
|
+
}
|
616
|
+
|
617
|
+
/**
|
618
|
+
* Sign a message using a key based on the given string seed
|
619
|
+
*
|
620
|
+
* This works by creating a private key from the root key of this identity. It will always
|
621
|
+
* work with the rootPath / rootKey, to be deterministic. It will not change even if the keys
|
622
|
+
* are rotated for this ID.
|
623
|
+
*
|
624
|
+
* This is used in for instance deterministic login systems, that do not support BAP.
|
625
|
+
*
|
626
|
+
* @param message
|
627
|
+
* @param seed {string} String seed that will be used to generate a path
|
628
|
+
*/
|
629
|
+
signMessageWithSeed(
|
630
|
+
message: string,
|
631
|
+
seed: string,
|
632
|
+
): { address: string; signature: string } {
|
633
|
+
const pathHex = toHex(Hash.sha256(seed, "utf8"));
|
634
|
+
const path = Utils.getSigningPathFromHex(pathHex);
|
635
|
+
|
636
|
+
const HDPrivateKey = this.#HDPrivateKey.derive(this.#rootPath);
|
637
|
+
const derivedChild = HDPrivateKey.derive(path);
|
638
|
+
const address = derivedChild.privKey.toPublicKey().toAddress();
|
639
|
+
const signature = BSM.sign(
|
640
|
+
toArray(Buffer.from(message)),
|
641
|
+
derivedChild.privKey,
|
642
|
+
).toCompact(0, true, "base64") as string;
|
643
|
+
|
644
|
+
return { address, signature };
|
645
|
+
}
|
646
|
+
|
647
|
+
/**
|
648
|
+
* Sign an op_return hex array with AIP
|
649
|
+
* @param opReturn {array}
|
650
|
+
* @param signingPath {string}
|
651
|
+
* @param outputType {string}
|
652
|
+
* @return {[]}
|
653
|
+
*/
|
654
|
+
signOpReturnWithAIP(
|
655
|
+
opReturn: string[],
|
656
|
+
signingPath = "",
|
657
|
+
outputType: BufferEncoding = "hex",
|
658
|
+
): string[] {
|
659
|
+
const aipMessageBuffer = this.getAIPMessageBuffer(opReturn);
|
660
|
+
const { address, signature } = this.signMessage(
|
661
|
+
aipMessageBuffer,
|
662
|
+
signingPath,
|
663
|
+
);
|
664
|
+
|
665
|
+
return opReturn.concat([
|
666
|
+
Buffer.from("|").toString(outputType),
|
667
|
+
Buffer.from(AIP_BITCOM_ADDRESS).toString(outputType),
|
668
|
+
Buffer.from("BITCOIN_ECDSA").toString(outputType),
|
669
|
+
Buffer.from(address).toString(outputType),
|
670
|
+
Buffer.from(signature, "base64").toString(outputType),
|
671
|
+
]);
|
672
|
+
}
|
673
|
+
|
674
|
+
/**
|
675
|
+
* Construct an AIP buffer from the op return data
|
676
|
+
* @param opReturn
|
677
|
+
* @returns {Buffer}
|
678
|
+
*/
|
679
|
+
getAIPMessageBuffer(opReturn: string[]): Buffer {
|
680
|
+
const buffers = [];
|
681
|
+
if (opReturn[0].replace("0x", "") !== "6a") {
|
682
|
+
// include OP_RETURN in constructing the signature buffer
|
683
|
+
buffers.push(Buffer.from("6a", "hex"));
|
684
|
+
}
|
685
|
+
for (const op of opReturn) {
|
686
|
+
buffers.push(Buffer.from(op.replace("0x", ""), "hex"));
|
687
|
+
}
|
688
|
+
// add a trailing "|" - this is the AIP way
|
689
|
+
buffers.push(Buffer.from("|"));
|
690
|
+
|
691
|
+
return Buffer.concat([...buffers]);
|
692
|
+
}
|
693
|
+
|
694
|
+
/**
|
695
|
+
* Get all signing keys for this identity
|
696
|
+
*/
|
697
|
+
async getIdSigningKeys(): Promise<any> {
|
698
|
+
const signingKeys = await this.getApiData("/signing-keys", {
|
699
|
+
idKey: this.identityKey,
|
700
|
+
});
|
701
|
+
console.log("getIdSigningKeys", signingKeys);
|
702
|
+
|
703
|
+
return signingKeys;
|
704
|
+
}
|
705
|
+
|
706
|
+
/**
|
707
|
+
* Get all attestations for the given attribute
|
708
|
+
*
|
709
|
+
* @param attribute
|
710
|
+
*/
|
711
|
+
async getAttributeAttestations(attribute: string): Promise<any> {
|
712
|
+
// This function needs to make a call to a BAP server to get all the attestations for this
|
713
|
+
// identity for the given attribute
|
714
|
+
const attestationHash = this.getAttestationHash(attribute);
|
715
|
+
|
716
|
+
// get all BAP ATTEST records for the given attestationHash
|
717
|
+
const attestations = await this.getApiData("/attestations", {
|
718
|
+
hash: attestationHash,
|
719
|
+
});
|
720
|
+
console.log("getAttestations", attribute, attestationHash, attestations);
|
721
|
+
|
722
|
+
return attestations;
|
723
|
+
}
|
724
|
+
|
725
|
+
/**
|
726
|
+
* Helper function to get attestation from a BAP API server
|
727
|
+
*
|
728
|
+
* @param apiUrl
|
729
|
+
* @param apiData
|
730
|
+
* @returns {Promise<any>}
|
731
|
+
*/
|
732
|
+
async getApiData(apiUrl: string, apiData: any): Promise<any> {
|
733
|
+
const url = `${this.#BAP_SERVER}${apiUrl}`;
|
734
|
+
const response = await fetch(url, {
|
735
|
+
method: "post",
|
736
|
+
headers: {
|
737
|
+
"Content-type": "application/json; charset=utf-8",
|
738
|
+
token: this.#BAP_TOKEN,
|
739
|
+
format: "json",
|
740
|
+
},
|
741
|
+
body: JSON.stringify(apiData),
|
742
|
+
});
|
743
|
+
return response.json();
|
744
|
+
}
|
745
|
+
|
746
|
+
/**
|
747
|
+
* Import an identity from a JSON object
|
748
|
+
*
|
749
|
+
* @param identity{{}}
|
750
|
+
*/
|
751
|
+
import(identity: Identity): void {
|
752
|
+
this.idName = identity.name;
|
753
|
+
this.description = identity.description || "";
|
754
|
+
this.identityKey = identity.identityKey;
|
755
|
+
this.#rootPath = identity.rootPath;
|
756
|
+
this.rootAddress = identity.rootAddress;
|
757
|
+
this.#previousPath = identity.previousPath;
|
758
|
+
this.#currentPath = identity.currentPath;
|
759
|
+
this.#idSeed = identity.idSeed || "";
|
760
|
+
this.identityAttributes = this.parseAttributes(identity.identityAttributes);
|
761
|
+
}
|
762
|
+
|
763
|
+
/**
|
764
|
+
* Export this identity to a JSON object
|
765
|
+
* @returns {{}}
|
766
|
+
*/
|
767
|
+
export(): Identity {
|
768
|
+
return {
|
769
|
+
name: this.idName,
|
770
|
+
description: this.description,
|
771
|
+
identityKey: this.identityKey,
|
772
|
+
rootPath: this.#rootPath,
|
773
|
+
rootAddress: this.rootAddress,
|
774
|
+
previousPath: this.#previousPath,
|
775
|
+
currentPath: this.#currentPath,
|
776
|
+
idSeed: this.#idSeed,
|
777
|
+
identityAttributes: this.getAttributes(),
|
778
|
+
lastIdPath: "",
|
779
|
+
};
|
780
|
+
}
|
781
|
+
}
|
782
|
+
|
783
|
+
export { BAP_ID };
|