@keetanetwork/anchor 0.0.13 → 0.0.15
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/lib/certificates.d.ts +83 -21
- package/lib/certificates.d.ts.map +1 -1
- package/lib/certificates.generated.d.ts +3 -0
- package/lib/certificates.generated.d.ts.map +1 -0
- package/lib/certificates.generated.js +120 -0
- package/lib/certificates.generated.js.map +1 -0
- package/lib/certificates.js +508 -117
- package/lib/certificates.js.map +1 -1
- package/lib/http-server.d.ts +2 -1
- package/lib/http-server.d.ts.map +1 -1
- package/lib/http-server.js +5 -5
- package/lib/http-server.js.map +1 -1
- package/lib/log/index.d.ts +6 -57
- package/lib/log/index.d.ts.map +1 -1
- package/lib/log/index.js +5 -205
- package/lib/log/index.js.map +1 -1
- package/lib/log/target_console.d.ts +3 -11
- package/lib/log/target_console.d.ts.map +1 -1
- package/lib/log/target_console.js +3 -42
- package/lib/log/target_console.js.map +1 -1
- package/lib/resolver.d.ts +2 -1
- package/lib/resolver.d.ts.map +1 -1
- package/lib/resolver.js +22 -22
- package/lib/resolver.js.map +1 -1
- package/lib/utils/asn1.d.ts +10 -1
- package/lib/utils/asn1.d.ts.map +1 -1
- package/lib/utils/asn1.js +1413 -0
- package/lib/utils/asn1.js.map +1 -1
- package/lib/utils/buffer.d.ts +3 -0
- package/lib/utils/buffer.d.ts.map +1 -1
- package/lib/utils/buffer.js +32 -0
- package/lib/utils/buffer.js.map +1 -1
- package/lib/utils/external.d.ts +43 -0
- package/lib/utils/external.d.ts.map +1 -0
- package/lib/utils/external.js +115 -0
- package/lib/utils/external.js.map +1 -0
- package/lib/utils/guards.d.ts +14 -0
- package/lib/utils/guards.d.ts.map +1 -0
- package/lib/utils/guards.js +31 -0
- package/lib/utils/guards.js.map +1 -0
- package/lib/utils/index.d.ts +3 -1
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +3 -1
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/oid.d.ts +7 -0
- package/lib/utils/oid.d.ts.map +1 -0
- package/lib/utils/oid.js +22 -0
- package/lib/utils/oid.js.map +1 -0
- package/lib/utils/signing.d.ts.map +1 -1
- package/lib/utils/signing.js +26 -2
- package/lib/utils/signing.js.map +1 -1
- package/npm-shrinkwrap.json +76 -216
- package/package.json +2 -2
- package/services/asset-movement/common.js +7 -7
- package/services/fx/server.js +1 -1
- package/services/fx/server.js.map +1 -1
- package/services/kyc/client.d.ts.map +1 -1
- package/services/kyc/client.js +1 -8
- package/services/kyc/client.js.map +1 -1
- package/services/kyc/common.d.ts +39 -3
- package/services/kyc/common.d.ts.map +1 -1
- package/services/kyc/common.generated.d.ts +5 -0
- package/services/kyc/common.generated.d.ts.map +1 -0
- package/services/kyc/common.generated.js +241 -0
- package/services/kyc/common.generated.js.map +1 -0
- package/services/kyc/common.js +68 -1
- package/services/kyc/common.js.map +1 -1
- package/services/kyc/iso20022.generated.d.ts +336 -0
- package/services/kyc/iso20022.generated.d.ts.map +1 -0
- package/services/kyc/iso20022.generated.js +287 -0
- package/services/kyc/iso20022.generated.js.map +1 -0
- package/services/kyc/oids.generated.d.ts +104 -0
- package/services/kyc/oids.generated.d.ts.map +1 -0
- package/services/kyc/oids.generated.js +163 -0
- package/services/kyc/oids.generated.js.map +1 -0
- package/services/kyc/server.d.ts +141 -0
- package/services/kyc/server.d.ts.map +1 -0
- package/services/kyc/server.js +183 -0
- package/services/kyc/server.js.map +1 -0
- package/services/kyc/utils/generate-kyc-schema.d.ts +3 -0
- package/services/kyc/utils/generate-kyc-schema.d.ts.map +1 -0
- package/services/kyc/utils/generate-kyc-schema.js +1342 -0
- package/services/kyc/utils/generate-kyc-schema.js.map +1 -0
- package/lib/log/common.d.ts +0 -35
- package/lib/log/common.d.ts.map +0 -1
- package/lib/log/common.js +0 -19
- package/lib/log/common.js.map +0 -1
package/lib/certificates.js
CHANGED
|
@@ -1,69 +1,36 @@
|
|
|
1
1
|
import * as KeetaNetClient from '@keetanetwork/keetanet-client';
|
|
2
|
+
import * as oids from '../services/kyc/oids.generated.js';
|
|
2
3
|
import * as ASN1 from './utils/asn1.js';
|
|
3
|
-
import {
|
|
4
|
+
import { ASN1toJS, contextualizeStructSchema, encodeValueBySchema, normalizeDecodedASN1 } from './utils/asn1.js';
|
|
5
|
+
import { arrayBufferLikeToBuffer, arrayBufferToBuffer, Buffer, bufferToArrayBuffer } from './utils/buffer.js';
|
|
4
6
|
import crypto from './utils/crypto.js';
|
|
5
7
|
import { assertNever } from './utils/never.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
8
|
+
import { CertificateAttributeOIDDB, CertificateAttributeSchema, ReferenceSchema } from '../services/kyc/iso20022.generated.js';
|
|
9
|
+
import { getOID, lookupByOID } from './utils/oid.js';
|
|
10
|
+
import { convertToJSON as convertToJSONUtil } from './utils/json.js';
|
|
11
|
+
import { EncryptedContainer } from './encrypted-container.js';
|
|
12
|
+
import { assertSharableCertificateAttributesContentsSchema } from './certificates.generated.js';
|
|
13
|
+
/**
|
|
14
|
+
* Short alias for printing a debug representation of an object
|
|
15
|
+
*/
|
|
16
|
+
const DPO = KeetaNetClient.lib.Utils.Helper.debugPrintableObject.bind(KeetaNetClient.lib.Utils.Helper);
|
|
17
|
+
const KeetaNetAccount = KeetaNetClient.lib.Account;
|
|
18
|
+
function toJSON(data) {
|
|
19
|
+
return (convertToJSONUtil(data));
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (value === oid) {
|
|
26
|
-
return (key);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
throw (new Error(`Unknown OID: ${oid}`));
|
|
21
|
+
// Generic type guard to align decoded values with generated attribute types
|
|
22
|
+
function isAttributeValue(_name, _v) {
|
|
23
|
+
// Runtime schema validation is already performed by BufferStorageASN1; this guard
|
|
24
|
+
// serves to inform TypeScript of the precise type tied to the attribute name.
|
|
25
|
+
return (true);
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if ('publicKeyString' in value && typeof value.publicKeyString === 'object' && value.publicKeyString !== null) {
|
|
38
|
-
if ('get' in value.publicKeyString && typeof value.publicKeyString.get === 'function') {
|
|
39
|
-
/*
|
|
40
|
-
* If the value has a publicKeyString property that is an
|
|
41
|
-
* object with a get method, we assume it is a KeetaNetAccount
|
|
42
|
-
* or similar object and we return the public key string
|
|
43
|
-
*/
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
45
|
-
const publicKeyString = value.publicKeyString.get();
|
|
46
|
-
if (typeof publicKeyString === 'string') {
|
|
47
|
-
return (publicKeyString);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (Buffer.isBuffer(value)) {
|
|
53
|
-
return (value.toString('base64'));
|
|
54
|
-
}
|
|
55
|
-
if (typeof value === 'bigint') {
|
|
56
|
-
return (value.toString());
|
|
57
|
-
}
|
|
58
|
-
return (convertedValue);
|
|
59
|
-
}));
|
|
60
|
-
return (retval);
|
|
27
|
+
// Helper to apply type guard once and return the properly typed value
|
|
28
|
+
function asAttributeValue(name, v) {
|
|
29
|
+
if (!isAttributeValue(name, v)) {
|
|
30
|
+
throw (new Error('internal error: decoded value did not match expected type'));
|
|
31
|
+
}
|
|
32
|
+
return (v);
|
|
61
33
|
}
|
|
62
|
-
/*
|
|
63
|
-
* Because our public interfaces are ArrayBuffers we often need to convert
|
|
64
|
-
* Buffers to ArrayBuffers -- an alias to the Node function to do that
|
|
65
|
-
*/
|
|
66
|
-
const bufferToArrayBuffer = KeetaNetClient.lib.Utils.Helper.bufferToArrayBuffer.bind(KeetaNetClient.lib.Utils.Helper);
|
|
67
34
|
/**
|
|
68
35
|
* Sensitive Attribute Schema
|
|
69
36
|
*
|
|
@@ -107,30 +74,88 @@ const SensitiveAttributeSchemaInternal = [
|
|
|
107
74
|
* Database of permitted algorithms and their OIDs
|
|
108
75
|
*/
|
|
109
76
|
const sensitiveAttributeOIDDB = {
|
|
110
|
-
'aes-256-gcm':
|
|
111
|
-
'aes-256-cbc':
|
|
112
|
-
'sha2-256':
|
|
113
|
-
'sha3-256':
|
|
77
|
+
'aes-256-gcm': oids.AES_256_GCM,
|
|
78
|
+
'aes-256-cbc': oids.AES_256_CBC,
|
|
79
|
+
'sha2-256': oids.SHA2_256,
|
|
80
|
+
'sha3-256': oids.SHA3_256,
|
|
81
|
+
'sha256': oids.SHA2_256,
|
|
82
|
+
'aes256-gcm': oids.AES_256_GCM,
|
|
83
|
+
'aes256-cbc': oids.AES_256_CBC
|
|
114
84
|
};
|
|
85
|
+
function assertCertificateAttributeNames(name) {
|
|
86
|
+
if (!(name in CertificateAttributeOIDDB)) {
|
|
87
|
+
throw (new Error(`Unknown attribute name: ${name}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function asCertificateAttributeNames(name) {
|
|
91
|
+
assertCertificateAttributeNames(name);
|
|
92
|
+
return (name);
|
|
93
|
+
}
|
|
94
|
+
const DOCUMENT_SCHEMA_ATTRIBUTES = new Set([
|
|
95
|
+
'document',
|
|
96
|
+
'documentDriversLicenseFront',
|
|
97
|
+
'documentDriversLicenseBack',
|
|
98
|
+
'documentPassport'
|
|
99
|
+
]);
|
|
100
|
+
function resolveSchema(name, schema) {
|
|
101
|
+
if (DOCUMENT_SCHEMA_ATTRIBUTES.has(name)) {
|
|
102
|
+
return (contextualizeStructSchema(ReferenceSchema));
|
|
103
|
+
}
|
|
104
|
+
return (contextualizeStructSchema(schema));
|
|
105
|
+
}
|
|
106
|
+
function encodeAttribute(name, value) {
|
|
107
|
+
const schema = resolveSchema(name, CertificateAttributeSchema[name]);
|
|
108
|
+
const encodedJS = encodeValueBySchema(schema, value, { attributeName: name });
|
|
109
|
+
if (encodedJS === undefined) {
|
|
110
|
+
throw (new Error(`Unsupported attribute value for encoding: ${JSON.stringify(DPO(value))}`));
|
|
111
|
+
}
|
|
112
|
+
const asn1Object = ASN1.JStoASN1(encodedJS);
|
|
113
|
+
if (!asn1Object) {
|
|
114
|
+
throw (new Error(`Failed to encode value for attribute ${name}`));
|
|
115
|
+
}
|
|
116
|
+
return (asn1Object.toBER(false));
|
|
117
|
+
}
|
|
118
|
+
// Prepare a value for inclusion in a SensitiveAttribute: pre-encode complex and date types
|
|
119
|
+
function encodeForSensitive(name, value) {
|
|
120
|
+
if (Buffer.isBuffer(value)) {
|
|
121
|
+
return (value);
|
|
122
|
+
}
|
|
123
|
+
if (value instanceof ArrayBuffer) {
|
|
124
|
+
return (arrayBufferToBuffer(value));
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === 'string') {
|
|
127
|
+
const asn1 = ASN1.JStoASN1({ type: 'string', kind: 'utf8', value });
|
|
128
|
+
return (arrayBufferToBuffer(asn1.toBER(false)));
|
|
129
|
+
}
|
|
130
|
+
if (value instanceof Date) {
|
|
131
|
+
const asn1 = ASN1.JStoASN1(value);
|
|
132
|
+
return (arrayBufferToBuffer(asn1.toBER(false)));
|
|
133
|
+
}
|
|
134
|
+
if (typeof value === 'object' && value !== null) {
|
|
135
|
+
if (!name) {
|
|
136
|
+
throw (new Error('attributeName required for complex types'));
|
|
137
|
+
}
|
|
138
|
+
const encoded = encodeAttribute(name, value);
|
|
139
|
+
return (arrayBufferToBuffer(encoded));
|
|
140
|
+
}
|
|
141
|
+
return (Buffer.from(String(value), 'utf-8'));
|
|
142
|
+
}
|
|
143
|
+
async function decodeAttribute(name, value) {
|
|
144
|
+
const schema = resolveSchema(name, CertificateAttributeSchema[name]);
|
|
145
|
+
// XXX:TODO Fix depth issue
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
const decodedUnknown = new ASN1.BufferStorageASN1(value, schema).getASN1();
|
|
148
|
+
const candidate = normalizeDecodedASN1(decodedUnknown);
|
|
149
|
+
return (asAttributeValue(name, candidate));
|
|
150
|
+
}
|
|
115
151
|
class SensitiveAttributeBuilder {
|
|
116
152
|
#account;
|
|
117
153
|
#value;
|
|
118
|
-
constructor(account
|
|
154
|
+
constructor(account) {
|
|
119
155
|
this.#account = account;
|
|
120
|
-
if (value) {
|
|
121
|
-
this.set(value);
|
|
122
|
-
}
|
|
123
156
|
}
|
|
124
157
|
set(value) {
|
|
125
|
-
|
|
126
|
-
this.#value = value;
|
|
127
|
-
}
|
|
128
|
-
else if (typeof value === 'string') {
|
|
129
|
-
this.#value = Buffer.from(value, 'utf-8');
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
this.#value = Buffer.from(value);
|
|
133
|
-
}
|
|
158
|
+
this.#value = Buffer.isBuffer(value) ? value : arrayBufferLikeToBuffer(value);
|
|
134
159
|
return (this);
|
|
135
160
|
}
|
|
136
161
|
async build() {
|
|
@@ -143,15 +168,31 @@ class SensitiveAttributeBuilder {
|
|
|
143
168
|
const cipher = 'aes-256-gcm';
|
|
144
169
|
const key = crypto.randomBytes(32);
|
|
145
170
|
const nonce = crypto.randomBytes(12);
|
|
146
|
-
const encryptedKey = await this.#account.encrypt(key);
|
|
171
|
+
const encryptedKey = await this.#account.encrypt(bufferToArrayBuffer(key));
|
|
147
172
|
function encrypt(value) {
|
|
148
173
|
const cipherObject = crypto.createCipheriv(cipher, key, nonce);
|
|
149
174
|
let retval = cipherObject.update(value);
|
|
150
175
|
retval = Buffer.concat([retval, cipherObject.final()]);
|
|
176
|
+
/*
|
|
177
|
+
* For AES-GCM, the last 16 bytes are the authentication tag
|
|
178
|
+
*/
|
|
179
|
+
if (cipher === 'aes-256-gcm') {
|
|
180
|
+
const getAuthTagFn = Reflect.get(cipherObject, 'getAuthTag');
|
|
181
|
+
if (typeof getAuthTagFn === 'function') {
|
|
182
|
+
const tag = getAuthTagFn.call(cipherObject);
|
|
183
|
+
if (!Buffer.isBuffer(tag)) {
|
|
184
|
+
throw (new Error('getAuthTag did not return a Buffer'));
|
|
185
|
+
}
|
|
186
|
+
retval = Buffer.concat([retval, tag]);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
throw (new Error('getAuthTag is not available on cipherObject'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
151
192
|
return (retval);
|
|
152
193
|
}
|
|
153
194
|
const encryptedValue = encrypt(this.#value);
|
|
154
|
-
const encryptedSalt = encrypt(salt);
|
|
195
|
+
const encryptedSalt = encrypt(arrayBufferLikeToBuffer(salt));
|
|
155
196
|
const saltedValue = Buffer.concat([salt, publicKey, encryptedValue, this.#value]);
|
|
156
197
|
const hashedAndSaltedValue = KeetaNetClient.lib.Utils.Hash.Hash(saltedValue);
|
|
157
198
|
const attributeStructure = [
|
|
@@ -179,23 +220,35 @@ class SensitiveAttributeBuilder {
|
|
|
179
220
|
encryptedValue
|
|
180
221
|
];
|
|
181
222
|
const encodedAttributeObject = ASN1.JStoASN1(attributeStructure);
|
|
182
|
-
|
|
223
|
+
// Produce canonical DER as ArrayBuffer
|
|
224
|
+
const retval = encodedAttributeObject.toBER(false);
|
|
183
225
|
return (retval);
|
|
184
226
|
}
|
|
185
227
|
}
|
|
186
228
|
class SensitiveAttribute {
|
|
187
229
|
#account;
|
|
188
230
|
#info;
|
|
189
|
-
|
|
231
|
+
#decoder;
|
|
232
|
+
constructor(account, data, decoder) {
|
|
190
233
|
this.#account = account;
|
|
191
234
|
this.#info = this.decode(data);
|
|
235
|
+
if (decoder) {
|
|
236
|
+
this.#decoder = decoder;
|
|
237
|
+
}
|
|
192
238
|
}
|
|
193
239
|
decode(data) {
|
|
194
240
|
if (Buffer.isBuffer(data)) {
|
|
195
241
|
data = bufferToArrayBuffer(data);
|
|
196
242
|
}
|
|
197
|
-
|
|
198
|
-
|
|
243
|
+
let decodedAttribute;
|
|
244
|
+
try {
|
|
245
|
+
const dataObject = new ASN1.BufferStorageASN1(data, SensitiveAttributeSchemaInternal);
|
|
246
|
+
decodedAttribute = dataObject.getASN1();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
const js = ASN1toJS(data);
|
|
250
|
+
throw (new Error(`SensitiveAttribute.decode: unexpected DER shape ${JSON.stringify(DPO(js))}`));
|
|
251
|
+
}
|
|
199
252
|
const decodedVersion = decodedAttribute[0] + 1n;
|
|
200
253
|
if (decodedVersion !== 1n) {
|
|
201
254
|
throw (new Error(`Unsupported Sensitive Attribute version (${decodedVersion})`));
|
|
@@ -217,11 +270,31 @@ class SensitiveAttribute {
|
|
|
217
270
|
});
|
|
218
271
|
}
|
|
219
272
|
async #decryptValue(value) {
|
|
220
|
-
const decryptedKey = await this.#account.decrypt(this.#info.cipher.key);
|
|
273
|
+
const decryptedKey = await this.#account.decrypt(bufferToArrayBuffer(this.#info.cipher.key));
|
|
221
274
|
const algorithm = this.#info.cipher.algorithm;
|
|
222
275
|
const iv = this.#info.cipher.iv;
|
|
223
276
|
const cipher = crypto.createDecipheriv(algorithm, Buffer.from(decryptedKey), iv);
|
|
277
|
+
// For AES-GCM, the last 16 bytes are the authentication tag
|
|
278
|
+
if (algorithm === 'aes-256-gcm') {
|
|
279
|
+
const authTag = value.subarray(value.length - 16);
|
|
280
|
+
const ciphertext = value.subarray(0, value.length - 16);
|
|
281
|
+
// XXX:TODO Fix typescript unsafe calls
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
283
|
+
const setAuthTagFn = Reflect.get(cipher, 'setAuthTag');
|
|
284
|
+
if (typeof setAuthTagFn === 'function') {
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
286
|
+
setAuthTagFn.call(cipher, authTag);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
throw (new Error('setAuthTag is not available on cipher'));
|
|
290
|
+
}
|
|
291
|
+
const decrypted = cipher.update(ciphertext);
|
|
292
|
+
cipher.final(); // Verify auth tag
|
|
293
|
+
return (decrypted);
|
|
294
|
+
}
|
|
295
|
+
// For other algorithms (like CBC), just decrypt normally
|
|
224
296
|
const decryptedValue = cipher.update(value);
|
|
297
|
+
cipher.final();
|
|
225
298
|
return (decryptedValue);
|
|
226
299
|
}
|
|
227
300
|
/**
|
|
@@ -234,25 +307,33 @@ class SensitiveAttribute {
|
|
|
234
307
|
* ArrayBuffer
|
|
235
308
|
*/
|
|
236
309
|
async get() {
|
|
237
|
-
const decryptedValue = await this.#decryptValue(this.#info.encryptedValue);
|
|
310
|
+
const decryptedValue = await this.#decryptValue(arrayBufferLikeToBuffer(this.#info.encryptedValue));
|
|
238
311
|
return (bufferToArrayBuffer(decryptedValue));
|
|
239
312
|
}
|
|
240
|
-
|
|
241
|
-
* Get the value of the sensitive attribute as a string after being
|
|
242
|
-
* interpreted as UTF-8 ( @see SensitiveAttribute.get for more information)
|
|
243
|
-
*/
|
|
244
|
-
async getString() {
|
|
313
|
+
async getValue() {
|
|
245
314
|
const value = await this.get();
|
|
246
|
-
|
|
315
|
+
if (!this.#decoder) {
|
|
316
|
+
/**
|
|
317
|
+
* TypeScript complains that T may not be the correct
|
|
318
|
+
* type here, but gives us no tools to enforce that it
|
|
319
|
+
* is -- it should always be ArrayBuffer if no decoder
|
|
320
|
+
* is provided, but someone could always specify a
|
|
321
|
+
* type parameter in that case and we cannot check
|
|
322
|
+
* that at runtime since T is only a compile-time type.
|
|
323
|
+
*/
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
return (this.#decoder(value));
|
|
247
328
|
}
|
|
248
329
|
/**
|
|
249
330
|
* Generate a proof that a sensitive attribute is a given value,
|
|
250
331
|
* which can be validated by a third party using the certificate
|
|
251
332
|
* and the `validateProof` method
|
|
252
333
|
*/
|
|
253
|
-
async
|
|
334
|
+
async getProof() {
|
|
254
335
|
const value = await this.get();
|
|
255
|
-
const salt = await this.#decryptValue(this.#info.hashedValue.encryptedSalt);
|
|
336
|
+
const salt = await this.#decryptValue(arrayBufferLikeToBuffer(this.#info.hashedValue.encryptedSalt));
|
|
256
337
|
return ({
|
|
257
338
|
value: Buffer.from(value).toString('base64'),
|
|
258
339
|
hash: {
|
|
@@ -268,7 +349,8 @@ class SensitiveAttribute {
|
|
|
268
349
|
const proofSaltBuffer = Buffer.from(proof.hash.salt, 'base64');
|
|
269
350
|
const publicKeyBuffer = Buffer.from(this.#account.publicKey.get());
|
|
270
351
|
const encryptedValue = this.#info.encryptedValue;
|
|
271
|
-
const
|
|
352
|
+
const hashInput = Buffer.concat([proofSaltBuffer, publicKeyBuffer, encryptedValue, plaintextValue]);
|
|
353
|
+
const hashedAndSaltedValue = KeetaNetClient.lib.Utils.Hash.Hash(hashInput);
|
|
272
354
|
const hashedAndSaltedValueBuffer = Buffer.from(hashedAndSaltedValue);
|
|
273
355
|
return (this.#info.hashedValue.value.equals(hashedAndSaltedValueBuffer));
|
|
274
356
|
}
|
|
@@ -276,16 +358,6 @@ class SensitiveAttribute {
|
|
|
276
358
|
return (toJSON(this.#info));
|
|
277
359
|
}
|
|
278
360
|
}
|
|
279
|
-
/**
|
|
280
|
-
* Database of attributes
|
|
281
|
-
*/
|
|
282
|
-
const CertificateAttributeOIDDB = {
|
|
283
|
-
'fullName': '1.3.6.1.4.1.62675.1.0',
|
|
284
|
-
'dateOfBirth': '1.3.6.1.4.1.62675.1.1',
|
|
285
|
-
'address': '1.3.6.1.4.1.62675.1.2',
|
|
286
|
-
'email': '1.3.6.1.4.1.62675.1.3',
|
|
287
|
-
'phoneNumber': '1.3.6.1.4.1.62675.1.4'
|
|
288
|
-
};
|
|
289
361
|
/**
|
|
290
362
|
* ASN.1 Schema for Certificate KYC Attributes Extension
|
|
291
363
|
*
|
|
@@ -338,10 +410,40 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
338
410
|
super(CertificateBuilder.mapParams(params));
|
|
339
411
|
}
|
|
340
412
|
/**
|
|
341
|
-
* Set a KYC Attribute to a given value
|
|
413
|
+
* Set a KYC Attribute to a given value.
|
|
414
|
+
* The sensitive flag is required.
|
|
415
|
+
*
|
|
416
|
+
* If an attribute is marked sensitive, the value is encoded
|
|
417
|
+
* into the certificate using a commitment scheme so that the
|
|
418
|
+
* value can be proven later without revealing it.
|
|
342
419
|
*/
|
|
343
420
|
setAttribute(name, sensitive, value) {
|
|
344
|
-
|
|
421
|
+
// Non-sensitive path: only primitive schema (string/date) allowed
|
|
422
|
+
const schemaValidator = CertificateAttributeSchema[name];
|
|
423
|
+
let encoded;
|
|
424
|
+
if (value instanceof ArrayBuffer) {
|
|
425
|
+
encoded = value;
|
|
426
|
+
}
|
|
427
|
+
else if (name in CertificateAttributeSchema) {
|
|
428
|
+
/* XXX: Why do we have two encoding methods ? */
|
|
429
|
+
encoded = bufferToArrayBuffer(encodeForSensitive(name, value));
|
|
430
|
+
}
|
|
431
|
+
else if (schemaValidator === ASN1.ValidateASN1.IsDate) {
|
|
432
|
+
if (!(value instanceof Date)) {
|
|
433
|
+
throw (new Error('Expected Date value'));
|
|
434
|
+
}
|
|
435
|
+
encoded = encodeAttribute(name, value);
|
|
436
|
+
}
|
|
437
|
+
else if (schemaValidator === ASN1.ValidateASN1.IsString && typeof value === 'string') {
|
|
438
|
+
encoded = encodeAttribute(name, value);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
throw (new Error('Unsupported non-sensitive value type'));
|
|
442
|
+
}
|
|
443
|
+
this.#attributes[name] = {
|
|
444
|
+
sensitive: sensitive,
|
|
445
|
+
value: encoded
|
|
446
|
+
};
|
|
345
447
|
}
|
|
346
448
|
async addExtensions(...args) {
|
|
347
449
|
const retval = await super.addExtensions(...args);
|
|
@@ -357,19 +459,20 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
357
459
|
* can assume that the attribute is always present in
|
|
358
460
|
* the object
|
|
359
461
|
*/
|
|
360
|
-
|
|
462
|
+
assertCertificateAttributeNames(name);
|
|
361
463
|
const nameOID = CertificateAttributeOIDDB[name];
|
|
362
464
|
let value;
|
|
363
465
|
if (attribute.sensitive) {
|
|
364
|
-
const
|
|
365
|
-
|
|
466
|
+
const builder = new SensitiveAttributeBuilder(subject);
|
|
467
|
+
builder.set(attribute.value);
|
|
468
|
+
value = arrayBufferToBuffer(await builder.build());
|
|
366
469
|
}
|
|
367
470
|
else {
|
|
368
471
|
if (typeof attribute.value === 'string') {
|
|
369
472
|
value = Buffer.from(attribute.value, 'utf-8');
|
|
370
473
|
}
|
|
371
474
|
else {
|
|
372
|
-
value =
|
|
475
|
+
value = arrayBufferToBuffer(attribute.value);
|
|
373
476
|
}
|
|
374
477
|
}
|
|
375
478
|
certAttributes.push([{
|
|
@@ -383,7 +486,7 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
383
486
|
}]);
|
|
384
487
|
}
|
|
385
488
|
if (certAttributes.length > 0) {
|
|
386
|
-
retval.push(KeetaNetClient.lib.Utils.Certificate.CertificateBuilder.extension(
|
|
489
|
+
retval.push(KeetaNetClient.lib.Utils.Certificate.CertificateBuilder.extension(oids.keeta.KYC_ATTRIBUTES, certAttributes));
|
|
387
490
|
}
|
|
388
491
|
return (retval);
|
|
389
492
|
}
|
|
@@ -412,6 +515,7 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
412
515
|
export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificate {
|
|
413
516
|
subjectKey;
|
|
414
517
|
static Builder = CertificateBuilder;
|
|
518
|
+
static SharableAttributes;
|
|
415
519
|
/**
|
|
416
520
|
* User KYC Attributes
|
|
417
521
|
*/
|
|
@@ -424,27 +528,60 @@ export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificat
|
|
|
424
528
|
finalizeConstruction() {
|
|
425
529
|
/* Do nothing, we call the super method in the constructor */
|
|
426
530
|
}
|
|
531
|
+
setPlainAttribute(name, value) {
|
|
532
|
+
// @ts-ignore
|
|
533
|
+
this.attributes[name] = { sensitive: false, value };
|
|
534
|
+
}
|
|
535
|
+
setSensitiveAttribute(name, value) {
|
|
536
|
+
const decodeForSensitive = async (data) => {
|
|
537
|
+
const bufferInput = Buffer.isBuffer(data) ? bufferToArrayBuffer(data) : data;
|
|
538
|
+
return (await decodeAttribute(name, bufferInput));
|
|
539
|
+
};
|
|
540
|
+
this.attributes[name] = {
|
|
541
|
+
sensitive: true,
|
|
542
|
+
value: new SensitiveAttribute(this.subjectKey, value, decodeForSensitive)
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get the underlying value for an attribute.
|
|
547
|
+
*
|
|
548
|
+
* If the attribute is sensitive, this will decrypt it using the
|
|
549
|
+
* subject's private key, otherwise it will return the value.
|
|
550
|
+
*/
|
|
551
|
+
async getAttributeValue(attributeName) {
|
|
552
|
+
const attr = this.attributes[attributeName]?.value;
|
|
553
|
+
if (!attr) {
|
|
554
|
+
throw (new Error(`Attribute ${attributeName} is not available`));
|
|
555
|
+
}
|
|
556
|
+
if (attr instanceof SensitiveAttribute) {
|
|
557
|
+
const raw = await attr.get();
|
|
558
|
+
return (await decodeAttribute(attributeName, raw));
|
|
559
|
+
}
|
|
560
|
+
// Non-sensitive: ArrayBuffer or Buffer
|
|
561
|
+
if (attr instanceof ArrayBuffer || Buffer.isBuffer(attr)) {
|
|
562
|
+
return (await decodeAttribute(attributeName, attr));
|
|
563
|
+
}
|
|
564
|
+
throw (new Error(`Attribute ${attributeName} is not a supported type`));
|
|
565
|
+
}
|
|
427
566
|
processExtension(id, value) {
|
|
428
567
|
if (super.processExtension(id, value)) {
|
|
429
568
|
return (true);
|
|
430
569
|
}
|
|
431
|
-
if (id ===
|
|
570
|
+
if (id === oids.keeta.KYC_ATTRIBUTES) {
|
|
432
571
|
const attributesRaw = new ASN1.BufferStorageASN1(value, CertificateKYCAttributeSchemaValidation).getASN1();
|
|
433
572
|
for (const attribute of attributesRaw) {
|
|
434
|
-
const
|
|
573
|
+
const nameString = lookupByOID(attribute[0].oid, CertificateAttributeOIDDB);
|
|
574
|
+
const name = asCertificateAttributeNames(nameString);
|
|
435
575
|
const valueKind = attribute[1].value;
|
|
436
576
|
const value = bufferToArrayBuffer(attribute[1].contains);
|
|
437
577
|
switch (valueKind) {
|
|
438
578
|
case 0:
|
|
439
579
|
/* Plain Value */
|
|
440
|
-
this.
|
|
580
|
+
this.setPlainAttribute(name, value);
|
|
441
581
|
break;
|
|
442
582
|
case 1:
|
|
443
583
|
/* Sensitive Value */
|
|
444
|
-
this.
|
|
445
|
-
sensitive: true,
|
|
446
|
-
value: new SensitiveAttribute(this.subjectKey, value)
|
|
447
|
-
};
|
|
584
|
+
this.setSensitiveAttribute(name, value);
|
|
448
585
|
break;
|
|
449
586
|
default:
|
|
450
587
|
assertNever(valueKind);
|
|
@@ -455,6 +592,260 @@ export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificat
|
|
|
455
592
|
return (false);
|
|
456
593
|
}
|
|
457
594
|
}
|
|
595
|
+
;
|
|
596
|
+
export class SharableCertificateAttributes {
|
|
597
|
+
#certificate;
|
|
598
|
+
#attributes = {};
|
|
599
|
+
container;
|
|
600
|
+
populatedFromInit = false;
|
|
601
|
+
static assertCertificateAttributeName = assertCertificateAttributeNames;
|
|
602
|
+
constructor(input, options) {
|
|
603
|
+
let containerBuffer;
|
|
604
|
+
if (typeof input === 'string') {
|
|
605
|
+
/*
|
|
606
|
+
* Attempt to decode as PEM, but also if not PEM, then return
|
|
607
|
+
* the lines as-is (base64) after removing whitespace
|
|
608
|
+
*/
|
|
609
|
+
const inputLines = input.split(/\r?\n/);
|
|
610
|
+
let base64Lines;
|
|
611
|
+
for (let beginOffset = 0; beginOffset < inputLines.length; beginOffset++) {
|
|
612
|
+
const line = inputLines[beginOffset]?.trim();
|
|
613
|
+
if (line?.startsWith('-----BEGIN ')) {
|
|
614
|
+
let endIndex = -1;
|
|
615
|
+
const matchingEndLine = line.replace('BEGIN', 'END');
|
|
616
|
+
for (let endOffset = beginOffset + 1; endOffset < inputLines.length; endOffset++) {
|
|
617
|
+
const checkEndLine = inputLines[endOffset]?.trim();
|
|
618
|
+
if (checkEndLine === matchingEndLine) {
|
|
619
|
+
endIndex = endOffset;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (endIndex === -1) {
|
|
624
|
+
throw (new Error('Invalid PEM format: missing END line'));
|
|
625
|
+
}
|
|
626
|
+
base64Lines = inputLines.slice(beginOffset + 1, endIndex);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (base64Lines === undefined) {
|
|
631
|
+
base64Lines = inputLines;
|
|
632
|
+
}
|
|
633
|
+
base64Lines = base64Lines.map(function (line) {
|
|
634
|
+
return (line.trim());
|
|
635
|
+
}).filter(function (line) {
|
|
636
|
+
return (line.length > 0);
|
|
637
|
+
});
|
|
638
|
+
const base64Content = base64Lines.join('');
|
|
639
|
+
containerBuffer = Buffer.from(base64Content, 'base64');
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
containerBuffer = arrayBufferToBuffer(input);
|
|
643
|
+
}
|
|
644
|
+
let principals = options?.principals;
|
|
645
|
+
if (KeetaNetAccount.isInstance(principals)) {
|
|
646
|
+
principals = [principals];
|
|
647
|
+
}
|
|
648
|
+
else if (principals instanceof Set) {
|
|
649
|
+
principals = Array.from(principals);
|
|
650
|
+
}
|
|
651
|
+
else if (principals === undefined) {
|
|
652
|
+
principals = null;
|
|
653
|
+
}
|
|
654
|
+
this.container = EncryptedContainer.fromEncodedBuffer(containerBuffer, principals);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Create a SharableCertificateAttributes from a Certificate
|
|
658
|
+
* and a list of attribute names to include -- if no list is
|
|
659
|
+
* provided, all attributes are included.
|
|
660
|
+
*/
|
|
661
|
+
static async fromCertificate(certificate, attributeNames) {
|
|
662
|
+
if (attributeNames === undefined) {
|
|
663
|
+
/*
|
|
664
|
+
* We know the keys are whatever the Certificate says they are, so
|
|
665
|
+
* we can cast here safely
|
|
666
|
+
*/
|
|
667
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
668
|
+
attributeNames = Object.keys(certificate.attributes);
|
|
669
|
+
}
|
|
670
|
+
const attributes = {};
|
|
671
|
+
for (const name of attributeNames) {
|
|
672
|
+
const attr = certificate.attributes[name];
|
|
673
|
+
/**
|
|
674
|
+
* Skip missing attributes
|
|
675
|
+
*/
|
|
676
|
+
if (!attr) {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (attr.sensitive) {
|
|
680
|
+
attributes[name] = {
|
|
681
|
+
sensitive: true,
|
|
682
|
+
value: await attr.value.getProof()
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
attributes[name] = {
|
|
687
|
+
sensitive: false,
|
|
688
|
+
value: arrayBufferToBuffer(attr.value).toString('base64')
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const contentsString = JSON.stringify({
|
|
693
|
+
certificate: certificate.toPEM(),
|
|
694
|
+
attributes: attributes
|
|
695
|
+
});
|
|
696
|
+
const temporaryUser = KeetaNetAccount.fromSeed(KeetaNetAccount.generateRandomSeed(), 0);
|
|
697
|
+
const contentsBuffer = Buffer.from(contentsString, 'utf-8');
|
|
698
|
+
const contentsBufferCompressed = await KeetaNetClient.lib.Utils.Buffer.ZlibDeflateAsync(bufferToArrayBuffer(contentsBuffer));
|
|
699
|
+
const container = EncryptedContainer.fromPlaintext(arrayBufferToBuffer(contentsBufferCompressed), [temporaryUser], true);
|
|
700
|
+
const containerBuffer = await container.getEncodedBuffer();
|
|
701
|
+
const retval = new SharableCertificateAttributes(bufferToArrayBuffer(containerBuffer), { principals: temporaryUser });
|
|
702
|
+
await retval.revokeAccess(temporaryUser);
|
|
703
|
+
return (retval);
|
|
704
|
+
}
|
|
705
|
+
async grantAccess(principal) {
|
|
706
|
+
await this.container.grantAccess(principal);
|
|
707
|
+
return (this);
|
|
708
|
+
}
|
|
709
|
+
async revokeAccess(principal) {
|
|
710
|
+
await this.container.revokeAccess(principal);
|
|
711
|
+
return (this);
|
|
712
|
+
}
|
|
713
|
+
get principals() {
|
|
714
|
+
return (this.container.principals);
|
|
715
|
+
}
|
|
716
|
+
async #populate() {
|
|
717
|
+
if (this.populatedFromInit) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
this.populatedFromInit = true;
|
|
721
|
+
const contentsBuffer = await this.container.getPlaintext();
|
|
722
|
+
const contentsBufferDecompressed = await KeetaNetClient.lib.Utils.Buffer.ZlibInflateAsync(bufferToArrayBuffer(contentsBuffer));
|
|
723
|
+
const contentsString = Buffer.from(contentsBufferDecompressed).toString('utf-8');
|
|
724
|
+
const contentsJSON = JSON.parse(contentsString);
|
|
725
|
+
const contents = assertSharableCertificateAttributesContentsSchema(contentsJSON);
|
|
726
|
+
this.#certificate = new Certificate(contents.certificate);
|
|
727
|
+
const attributePromises = Object.entries(contents.attributes).map(async ([name, attr]) => {
|
|
728
|
+
/*
|
|
729
|
+
* Get the corresponding attribute from the certificate
|
|
730
|
+
*
|
|
731
|
+
* We actually do not care if `name` is a known attribute
|
|
732
|
+
* because we are not decoding it here, we are just
|
|
733
|
+
* verifying it matches the certificate
|
|
734
|
+
*/
|
|
735
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
736
|
+
const certAttribute = this.#certificate?.attributes[name];
|
|
737
|
+
if (!certAttribute) {
|
|
738
|
+
throw (new Error(`Attribute ${name} not found in certificate`));
|
|
739
|
+
}
|
|
740
|
+
if (certAttribute.sensitive !== attr.sensitive) {
|
|
741
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
742
|
+
}
|
|
743
|
+
if (!attr.sensitive) {
|
|
744
|
+
if (certAttribute.sensitive) {
|
|
745
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
746
|
+
}
|
|
747
|
+
const certValue = certAttribute.value;
|
|
748
|
+
const sharedValue = bufferToArrayBuffer(Buffer.from(attr.value, 'base64'));
|
|
749
|
+
if (sharedValue.byteLength !== certValue.byteLength || !Buffer.from(sharedValue).equals(Buffer.from(certValue))) {
|
|
750
|
+
throw (new Error(`Attribute ${name} value mismatch with certificate`));
|
|
751
|
+
}
|
|
752
|
+
return ([name, {
|
|
753
|
+
sensitive: false,
|
|
754
|
+
value: sharedValue
|
|
755
|
+
}]);
|
|
756
|
+
}
|
|
757
|
+
if (!certAttribute.sensitive) {
|
|
758
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
759
|
+
}
|
|
760
|
+
if (!(await certAttribute.value.validateProof(attr.value))) {
|
|
761
|
+
throw (new Error(`Attribute ${name} proof validation failed`));
|
|
762
|
+
}
|
|
763
|
+
const attrValue = bufferToArrayBuffer(Buffer.from(attr.value.value, 'base64'));
|
|
764
|
+
return ([name, {
|
|
765
|
+
sensitive: true,
|
|
766
|
+
value: attrValue
|
|
767
|
+
}]);
|
|
768
|
+
});
|
|
769
|
+
const resolvedAttributes = await Promise.all(attributePromises);
|
|
770
|
+
this.#attributes = Object.fromEntries(resolvedAttributes);
|
|
771
|
+
}
|
|
772
|
+
async getCertificate() {
|
|
773
|
+
await this.#populate();
|
|
774
|
+
if (!this.#certificate) {
|
|
775
|
+
throw (new Error('internal error: certificate not populated'));
|
|
776
|
+
}
|
|
777
|
+
return (this.#certificate);
|
|
778
|
+
}
|
|
779
|
+
async getAttributeBuffer(name) {
|
|
780
|
+
await this.#populate();
|
|
781
|
+
const attr = this.#attributes[name];
|
|
782
|
+
return (attr?.value);
|
|
783
|
+
}
|
|
784
|
+
async getAttribute(name) {
|
|
785
|
+
const buffer = await this.getAttributeBuffer(name);
|
|
786
|
+
if (buffer === undefined) {
|
|
787
|
+
return (undefined);
|
|
788
|
+
}
|
|
789
|
+
const retval = await decodeAttribute(name, buffer);
|
|
790
|
+
/* XXX:TODO: Here is where we would look at a reference value
|
|
791
|
+
* (e.g., URL+hash) and fetch it, and verify it the hash matches
|
|
792
|
+
* the fetched value
|
|
793
|
+
*
|
|
794
|
+
* The schema for references is not yet defined, so this is
|
|
795
|
+
* left as a TODO for now.
|
|
796
|
+
*
|
|
797
|
+
* The return type would also need to be updated to reflect
|
|
798
|
+
* that we would map referenced types to something like
|
|
799
|
+
* { data: ArrayBuffer, contentType: string, source: <url>,
|
|
800
|
+
* hash: <hash> } (where source and hash should be named
|
|
801
|
+
* after whatever the actual schema is)
|
|
802
|
+
*/
|
|
803
|
+
return (retval);
|
|
804
|
+
}
|
|
805
|
+
async getAttributeNames(includeUnknown) {
|
|
806
|
+
await this.#populate();
|
|
807
|
+
const names = Object.keys(this.#attributes);
|
|
808
|
+
if (includeUnknown) {
|
|
809
|
+
return (names);
|
|
810
|
+
}
|
|
811
|
+
const knownNames = names.filter(function (name) {
|
|
812
|
+
return (name in CertificateAttributeOIDDB);
|
|
813
|
+
});
|
|
814
|
+
return (knownNames);
|
|
815
|
+
}
|
|
816
|
+
async export(options) {
|
|
817
|
+
options = {
|
|
818
|
+
format: 'arraybuffer',
|
|
819
|
+
...options
|
|
820
|
+
};
|
|
821
|
+
let principals;
|
|
822
|
+
try {
|
|
823
|
+
principals = this.container.principals;
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
principals = [];
|
|
827
|
+
}
|
|
828
|
+
if (principals.length === 0) {
|
|
829
|
+
throw (new Error('This container has no authorized users (principals); cannot export'));
|
|
830
|
+
}
|
|
831
|
+
const retvalBuffer = await this.container.getEncodedBuffer();
|
|
832
|
+
if (options.format === 'string') {
|
|
833
|
+
const retvalBase64 = retvalBuffer.toString('base64');
|
|
834
|
+
const retvalLines = ['-----BEGIN KYC CERTIFICATE PROOF-----'];
|
|
835
|
+
retvalLines.push(...retvalBase64.match(/.{1,64}/g) ?? []);
|
|
836
|
+
retvalLines.push('-----END KYC CERTIFICATE PROOF-----');
|
|
837
|
+
return (retvalLines.join('\n'));
|
|
838
|
+
}
|
|
839
|
+
else if (options.format === 'arraybuffer') {
|
|
840
|
+
return (bufferToArrayBuffer(retvalBuffer));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
throw (new Error(`Unsupported export format: ${String(options.format)}`));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
// @ts-ignore
|
|
848
|
+
Certificate.SharableAttributes = SharableCertificateAttributes;
|
|
458
849
|
/** @internal */
|
|
459
850
|
export const _Testing = {
|
|
460
851
|
SensitiveAttributeBuilder,
|