@idos-network/enclave 1.1.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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/base-0xSNl4rW.d.mts +140 -0
- package/dist/base-0xSNl4rW.d.mts.map +1 -0
- package/dist/client-BSkUuHYm.mjs +1196 -0
- package/dist/client-BSkUuHYm.mjs.map +1 -0
- package/dist/client-Bu0aO5sT.d.mts +38 -0
- package/dist/client-Bu0aO5sT.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/keys-RuC1M-_K.mjs +187 -0
- package/dist/keys-RuC1M-_K.mjs.map +1 -0
- package/dist/local.d.mts +96 -0
- package/dist/local.d.mts.map +1 -0
- package/dist/local.mjs +253 -0
- package/dist/local.mjs.map +1 -0
- package/dist/mpc/index.d.mts +3 -0
- package/dist/mpc/index.mjs +2 -0
- package/dist/types-DKsOwtWL.d.mts +73 -0
- package/dist/types-DKsOwtWL.d.mts.map +1 -0
- package/package.json +76 -0
- package/src/base.test.ts +137 -0
- package/src/base.ts +306 -0
- package/src/index.ts +3 -0
- package/src/keys.ts +10 -0
- package/src/local.test.ts +113 -0
- package/src/local.ts +495 -0
- package/src/mpc/api.ts +122 -0
- package/src/mpc/client.ts +411 -0
- package/src/mpc/engine-client.ts +144 -0
- package/src/mpc/generated/IdosContract.ts +92 -0
- package/src/mpc/index.ts +2 -0
- package/src/mpc/secretsharing/f256.ts +122 -0
- package/src/mpc/secretsharing/finite-field-element.ts +50 -0
- package/src/mpc/secretsharing/lagrange.ts +163 -0
- package/src/mpc/secretsharing/polynomial.ts +84 -0
- package/src/mpc/secretsharing/secret-share-factory.ts +30 -0
- package/src/mpc/secretsharing/secret-shares.ts +20 -0
- package/src/mpc/secretsharing/shamir-secret-shares.ts +164 -0
- package/src/mpc/secretsharing/xor-secret-shares.ts +68 -0
- package/src/mpc/types.ts +117 -0
- package/src/types.ts +37 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { WALLET_TYPES, type WalletType } from "@idos-network/kwil-infra/actions";
|
|
2
|
+
import {
|
|
3
|
+
ChainControllerApi,
|
|
4
|
+
Configuration,
|
|
5
|
+
} from "@partisiablockchain/blockchain-api-transaction-client";
|
|
6
|
+
import { ethers, type TypedDataDomain } from "ethers";
|
|
7
|
+
import { EngineClient } from "./engine-client";
|
|
8
|
+
import { deserializeState } from "./generated/IdosContract";
|
|
9
|
+
import { getRandomBytes, ShamirFactory } from "./secretsharing/shamir-secret-shares";
|
|
10
|
+
import {
|
|
11
|
+
ADD_ADDRESS_TYPES,
|
|
12
|
+
type AddAddressMessageToSign,
|
|
13
|
+
type AddAddressSignatureMessage,
|
|
14
|
+
type Bytes,
|
|
15
|
+
DOWNLOAD_TYPES,
|
|
16
|
+
type DownloadMessageToSign,
|
|
17
|
+
type DownloadSignatureMessage,
|
|
18
|
+
type PbcAddress,
|
|
19
|
+
REMOVE_ADDRESS_TYPES,
|
|
20
|
+
type RemoveAddressMessageToSign,
|
|
21
|
+
type RemoveAddressSignatureMessage,
|
|
22
|
+
type Sharing,
|
|
23
|
+
UPLOAD_TYPES,
|
|
24
|
+
type UploadMessageToSign,
|
|
25
|
+
type UploadSignatureMessage,
|
|
26
|
+
} from "./types";
|
|
27
|
+
import invariant from "tiny-invariant";
|
|
28
|
+
|
|
29
|
+
export class Client {
|
|
30
|
+
private readonly baseUrl: string;
|
|
31
|
+
private readonly contractAddress: PbcAddress;
|
|
32
|
+
private engines: EngineClient[] | undefined;
|
|
33
|
+
private walletType!: WalletType;
|
|
34
|
+
private signerAddress!: string;
|
|
35
|
+
private signerPublicKey: string | undefined;
|
|
36
|
+
private factory: ShamirFactory;
|
|
37
|
+
private numNodes: number;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
baseUrl: string,
|
|
41
|
+
contractAddress: PbcAddress,
|
|
42
|
+
numMalicious: number,
|
|
43
|
+
numNodes: number,
|
|
44
|
+
numToReconstruct: number,
|
|
45
|
+
walletType: WalletType,
|
|
46
|
+
signerAddress: string,
|
|
47
|
+
signerPublicKey?: string,
|
|
48
|
+
) {
|
|
49
|
+
this.baseUrl = baseUrl;
|
|
50
|
+
this.contractAddress = contractAddress;
|
|
51
|
+
|
|
52
|
+
this.reconfigure(walletType, signerAddress, signerPublicKey);
|
|
53
|
+
|
|
54
|
+
this.numNodes = numNodes;
|
|
55
|
+
// TODO: Make these configurable from env variables
|
|
56
|
+
this.factory = new ShamirFactory({ numMalicious, numNodes, numToReconstruct });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public reconfigure(
|
|
60
|
+
walletType: WalletType,
|
|
61
|
+
signerAddress: string,
|
|
62
|
+
signerPublicKey?: string,
|
|
63
|
+
): void {
|
|
64
|
+
// @deprecated
|
|
65
|
+
// Remove this method after a while
|
|
66
|
+
if (["evm", "xrpl", "near", "facesign", "stellar"].includes(walletType as string)) {
|
|
67
|
+
console.warn("Deprecated wallet type", walletType, ", please upgrade to latest SDK version");
|
|
68
|
+
switch (walletType as string) {
|
|
69
|
+
case "evm":
|
|
70
|
+
walletType = "EVM";
|
|
71
|
+
break;
|
|
72
|
+
case "xrpl":
|
|
73
|
+
walletType = "XRPL";
|
|
74
|
+
break;
|
|
75
|
+
case "near":
|
|
76
|
+
walletType = "NEAR";
|
|
77
|
+
break;
|
|
78
|
+
case "stellar":
|
|
79
|
+
walletType = "Stellar";
|
|
80
|
+
break;
|
|
81
|
+
case "facesign":
|
|
82
|
+
walletType = "FaceSign";
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
invariant(WALLET_TYPES.includes(walletType), `Invalid signer type: ${walletType}`);
|
|
88
|
+
invariant(
|
|
89
|
+
// XRPL, NEAR and FaceSign require a public key, EVM does not
|
|
90
|
+
(["XRPL", "NEAR", "FaceSign"].includes(walletType) && signerPublicKey) || walletType === "EVM",
|
|
91
|
+
"Signer public key is required for XRPL, NEAR and FaceSign",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
this.walletType = walletType;
|
|
95
|
+
// Reset engines to force re-creation of them since they are using old signer information
|
|
96
|
+
this.engines = undefined;
|
|
97
|
+
this.signerAddress = signerAddress;
|
|
98
|
+
if (signerPublicKey) this.signerPublicKey = signerPublicKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async uploadSecret(
|
|
102
|
+
id: string,
|
|
103
|
+
uploadSignature: UploadSignatureMessage,
|
|
104
|
+
signature: Bytes,
|
|
105
|
+
blindedShares: Buffer[],
|
|
106
|
+
): Promise<{ status: string }> {
|
|
107
|
+
const engineClients = await this.getEngines();
|
|
108
|
+
const promises = [];
|
|
109
|
+
for (let i = 0; i < engineClients.length; i++) {
|
|
110
|
+
const engineClient = engineClients[i];
|
|
111
|
+
const uploadRequest: Sharing = {
|
|
112
|
+
...uploadSignature,
|
|
113
|
+
share_data: ethers.hexlify(blindedShares[i]),
|
|
114
|
+
};
|
|
115
|
+
promises.push(engineClient.sendUpload(id, uploadRequest, signature));
|
|
116
|
+
}
|
|
117
|
+
const statuses = await Promise.all(promises);
|
|
118
|
+
|
|
119
|
+
if (statuses.every((item) => item === "201")) {
|
|
120
|
+
return { status: "success" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (statuses.filter((item) => item === "201").length > 0) {
|
|
124
|
+
return { status: "partial-success" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { status: "failure" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public getBlindedShares(secret: Buffer): Buffer[] {
|
|
131
|
+
// TODO: Make the number of nodes configurable from env variables
|
|
132
|
+
const secretShares = this.factory.fromPlainText(this.numNodes, secret);
|
|
133
|
+
var blindedShares: Buffer[] = [];
|
|
134
|
+
for (let i = 0; i < secretShares.numShares(); i++) {
|
|
135
|
+
blindedShares.push(Client.blindShare(secretShares.getShareBytes(i)));
|
|
136
|
+
}
|
|
137
|
+
return blindedShares;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public uploadMessageToSign(uploadRequest: UploadSignatureMessage): UploadMessageToSign {
|
|
141
|
+
return {
|
|
142
|
+
domain: this.getTypedDomain(),
|
|
143
|
+
types: UPLOAD_TYPES,
|
|
144
|
+
value: uploadRequest,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public uploadRequest(blindedShares: Buffer[]): UploadSignatureMessage {
|
|
149
|
+
console.log({ mpc: "uploading to MPC", walletType: this.walletType, address: this.signerAddress, publicKey: this.signerPublicKey });
|
|
150
|
+
|
|
151
|
+
var address = "";
|
|
152
|
+
switch (this.walletType) {
|
|
153
|
+
case "EVM":
|
|
154
|
+
address = `eip712:${this.signerAddress}`;
|
|
155
|
+
break;
|
|
156
|
+
case "XRPL":
|
|
157
|
+
address = `XRPL:${this.signerPublicKey}`;
|
|
158
|
+
break;
|
|
159
|
+
case "NEAR":
|
|
160
|
+
address = `NEAR:${this.signerPublicKey?.replace("ed25519:", "")}`;
|
|
161
|
+
break;
|
|
162
|
+
case "FaceSign":
|
|
163
|
+
address = `FACESIGN:${this.signerPublicKey}`;
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
throw new Error("Invalid signer type");
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
share_commitments: blindedShares.map((b) => ethers.keccak256(b)),
|
|
170
|
+
recovering_addresses: [address],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public downloadMessageToSign(downloadRequest: DownloadSignatureMessage): DownloadMessageToSign {
|
|
175
|
+
return {
|
|
176
|
+
domain: this.getTypedDomain(),
|
|
177
|
+
types: DOWNLOAD_TYPES,
|
|
178
|
+
value: downloadRequest,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public downloadRequest(publicKey: Uint8Array): DownloadSignatureMessage {
|
|
183
|
+
var address = "";
|
|
184
|
+
switch (this.walletType) {
|
|
185
|
+
case "EVM":
|
|
186
|
+
address = `eip712:${this.signerAddress}`;
|
|
187
|
+
break;
|
|
188
|
+
case "XRPL":
|
|
189
|
+
address = `XRPL:${this.signerPublicKey}`;
|
|
190
|
+
break;
|
|
191
|
+
case "NEAR":
|
|
192
|
+
address = `NEAR:${this.signerPublicKey?.replace("ed25519:", "")}`;
|
|
193
|
+
break;
|
|
194
|
+
case "FaceSign":
|
|
195
|
+
address = `FACESIGN:${this.signerPublicKey}`;
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
throw new Error("Invalid signer type");
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
recovering_address: address,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
public_key: ethers.hexlify(publicKey),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public async downloadSecret(
|
|
208
|
+
id: string,
|
|
209
|
+
downloadRequest: DownloadSignatureMessage,
|
|
210
|
+
signature: Bytes,
|
|
211
|
+
secretKey: Uint8Array,
|
|
212
|
+
): Promise<{ status: string; secret: Buffer | undefined }> {
|
|
213
|
+
const shares = [];
|
|
214
|
+
const engineClients = await this.getEngines();
|
|
215
|
+
for (let i = 0; i < engineClients.length; i++) {
|
|
216
|
+
const engineClient = engineClients[i];
|
|
217
|
+
shares.push(engineClient.downloadAndDecrypt(id, downloadRequest, signature, secretKey));
|
|
218
|
+
}
|
|
219
|
+
const secretShares = await Promise.all(shares);
|
|
220
|
+
|
|
221
|
+
console.log({ secretSharesDownloadStatuses: secretShares.map((item) => item.status) });
|
|
222
|
+
|
|
223
|
+
if (secretShares.every((item) => item.status === "404")) {
|
|
224
|
+
return { status: "not-stored", secret: undefined };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var secret: Buffer;
|
|
228
|
+
try {
|
|
229
|
+
secret = this.factory
|
|
230
|
+
.fromSharesBytes(secretShares.map((item) => item.share))
|
|
231
|
+
.reconstructPlainText();
|
|
232
|
+
return { status: "ok", secret };
|
|
233
|
+
} catch (_e) {
|
|
234
|
+
return { status: "error", secret: undefined };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public addAddressMessageToSign(
|
|
239
|
+
addressToAdd: string,
|
|
240
|
+
publicKey: string | undefined,
|
|
241
|
+
addressToAddType: string,
|
|
242
|
+
): AddAddressMessageToSign {
|
|
243
|
+
var address = "";
|
|
244
|
+
switch (this.walletType) {
|
|
245
|
+
case "EVM":
|
|
246
|
+
address = `eip712:${this.signerAddress}`;
|
|
247
|
+
break;
|
|
248
|
+
case "XRPL":
|
|
249
|
+
address = `XRPL:${this.signerPublicKey}`;
|
|
250
|
+
break;
|
|
251
|
+
case "NEAR":
|
|
252
|
+
address = `NEAR:${this.signerPublicKey?.replace("ed25519:", "")}`;
|
|
253
|
+
break;
|
|
254
|
+
case "FaceSign":
|
|
255
|
+
address = `FACESIGN:${this.signerPublicKey}`;
|
|
256
|
+
break;
|
|
257
|
+
default:
|
|
258
|
+
throw new Error("Invalid signer type");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
var addressToAddFormatted = "";
|
|
262
|
+
// @deprecated toLowerCase remove when we have updated the SDK
|
|
263
|
+
switch (addressToAddType.toLowerCase()) {
|
|
264
|
+
case "evm":
|
|
265
|
+
addressToAddFormatted = `eip712:${addressToAdd}`;
|
|
266
|
+
break;
|
|
267
|
+
case "xrpl":
|
|
268
|
+
addressToAddFormatted = `XRPL:${publicKey}`;
|
|
269
|
+
break;
|
|
270
|
+
case "near":
|
|
271
|
+
addressToAddFormatted = `NEAR:${publicKey?.replace("ed25519:", "")}`;
|
|
272
|
+
break;
|
|
273
|
+
case "facesign":
|
|
274
|
+
addressToAddFormatted = `FACESIGN:${publicKey}`;
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
throw new Error("Invalid address to add type");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const value = {
|
|
281
|
+
recovering_address: address,
|
|
282
|
+
address_to_add: addressToAddFormatted,
|
|
283
|
+
timestamp: Date.now(),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
domain: this.getTypedDomain(),
|
|
288
|
+
types: ADD_ADDRESS_TYPES,
|
|
289
|
+
value,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
public removeAddressMessageToSign(
|
|
294
|
+
addressToRemove: string,
|
|
295
|
+
publicKey: string | undefined,
|
|
296
|
+
addressToRemoveType: string,
|
|
297
|
+
): RemoveAddressMessageToSign {
|
|
298
|
+
var address = "";
|
|
299
|
+
switch (this.walletType) {
|
|
300
|
+
case "EVM":
|
|
301
|
+
address = `eip712:${this.signerAddress}`;
|
|
302
|
+
break;
|
|
303
|
+
case "XRPL":
|
|
304
|
+
address = `XRPL:${this.signerPublicKey}`;
|
|
305
|
+
break;
|
|
306
|
+
case "NEAR":
|
|
307
|
+
address = `NEAR:${this.signerPublicKey?.replace("ed25519:", "")}`;
|
|
308
|
+
break;
|
|
309
|
+
case "FaceSign":
|
|
310
|
+
address = `FACESIGN:${this.signerPublicKey}`;
|
|
311
|
+
break;
|
|
312
|
+
default:
|
|
313
|
+
throw new Error("Invalid signer type");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
var addressToRemoveFormatted = "";
|
|
317
|
+
// @deprecated toLowerCase remove when we have updated the SDK
|
|
318
|
+
switch (addressToRemoveType.toLowerCase()) {
|
|
319
|
+
case "evm":
|
|
320
|
+
addressToRemoveFormatted = `eip712:${addressToRemove}`;
|
|
321
|
+
break;
|
|
322
|
+
case "xrpl":
|
|
323
|
+
addressToRemoveFormatted = `XRPL:${publicKey}`;
|
|
324
|
+
break;
|
|
325
|
+
case "near":
|
|
326
|
+
addressToRemoveFormatted = `NEAR:${publicKey?.replace("ed25519:", "")}`;
|
|
327
|
+
break;
|
|
328
|
+
case "facesign":
|
|
329
|
+
addressToRemoveFormatted = `FACESIGN:${publicKey}`;
|
|
330
|
+
break;
|
|
331
|
+
default:
|
|
332
|
+
throw new Error("Invalid address to remove type");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const value = {
|
|
336
|
+
recovering_address: address,
|
|
337
|
+
address_to_remove: addressToRemoveFormatted,
|
|
338
|
+
timestamp: Date.now(),
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
domain: this.getTypedDomain(),
|
|
342
|
+
types: REMOVE_ADDRESS_TYPES,
|
|
343
|
+
value,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public async addAddress(
|
|
348
|
+
userId: string,
|
|
349
|
+
message: AddAddressSignatureMessage,
|
|
350
|
+
signature: string,
|
|
351
|
+
): Promise<string> {
|
|
352
|
+
const engineClients = await this.getEngines();
|
|
353
|
+
const promises = [];
|
|
354
|
+
for (let i = 0; i < engineClients.length; i++) {
|
|
355
|
+
const engineClient = engineClients[i];
|
|
356
|
+
promises.push(engineClient.sendAddAddress(userId, message, signature));
|
|
357
|
+
}
|
|
358
|
+
const statuses = await Promise.all(promises);
|
|
359
|
+
|
|
360
|
+
if (statuses.every((item) => item === "200")) {
|
|
361
|
+
return "success";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return "failure";
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
public async removeAddress(
|
|
368
|
+
userId: string,
|
|
369
|
+
message: RemoveAddressSignatureMessage,
|
|
370
|
+
signature: string,
|
|
371
|
+
): Promise<string> {
|
|
372
|
+
const engineClients = await this.getEngines();
|
|
373
|
+
const promises = [];
|
|
374
|
+
for (let i = 0; i < engineClients.length; i++) {
|
|
375
|
+
const engineClient = engineClients[i];
|
|
376
|
+
promises.push(engineClient.sendRemoveAddress(userId, message, signature));
|
|
377
|
+
}
|
|
378
|
+
const statuses = await Promise.all(promises);
|
|
379
|
+
|
|
380
|
+
if (statuses.every((item) => item === "200")) {
|
|
381
|
+
return "success";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return "failure";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private getTypedDomain(): TypedDataDomain {
|
|
388
|
+
return {
|
|
389
|
+
name: "idOS secret store contract",
|
|
390
|
+
version: "1",
|
|
391
|
+
verifyingContract: `0x${this.contractAddress.substring(2)}`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private async getEngines(): Promise<EngineClient[]> {
|
|
396
|
+
if (this.engines === undefined) {
|
|
397
|
+
const chainController = new ChainControllerApi(new Configuration({ basePath: this.baseUrl }));
|
|
398
|
+
const rawState = await chainController.getContract({ address: this.contractAddress });
|
|
399
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- we know serializedContract is present here
|
|
400
|
+
const state = deserializeState(Buffer.from(rawState.serializedContract!, "base64"));
|
|
401
|
+
this.engines = state.nodes.map(
|
|
402
|
+
(value) => new EngineClient(value.endpoint, this.contractAddress, this.walletType),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return this.engines;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private static blindShare(share: Buffer): Buffer {
|
|
409
|
+
return Buffer.concat([getRandomBytes(32), share]);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { WalletType } from "@idos-network/kwil-infra/actions";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import nacl from "tweetnacl";
|
|
4
|
+
import { patchRequest, postRequest, putRequest } from "./api";
|
|
5
|
+
import type {
|
|
6
|
+
AddAddressRequest,
|
|
7
|
+
Bytes,
|
|
8
|
+
DownloadRequest,
|
|
9
|
+
EncryptedShare,
|
|
10
|
+
PbcAddress,
|
|
11
|
+
RemoveAddressRequest,
|
|
12
|
+
Sharing,
|
|
13
|
+
UpdateWalletsRequest,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
export class EngineClient {
|
|
17
|
+
private readonly baseUrl: string;
|
|
18
|
+
private readonly contractAddress: PbcAddress;
|
|
19
|
+
private readonly walletType: WalletType;
|
|
20
|
+
|
|
21
|
+
constructor(baseUrl: string, contractAddress: PbcAddress, walletType: WalletType) {
|
|
22
|
+
this.contractAddress = contractAddress;
|
|
23
|
+
this.baseUrl = baseUrl;
|
|
24
|
+
this.walletType = walletType;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private getAuthHeader(signature: string): Record<string, string> {
|
|
28
|
+
let prefix: string;
|
|
29
|
+
switch (this.walletType) {
|
|
30
|
+
case "EVM":
|
|
31
|
+
prefix = "eip712";
|
|
32
|
+
break;
|
|
33
|
+
case "NEAR":
|
|
34
|
+
prefix = "NEAR";
|
|
35
|
+
break;
|
|
36
|
+
case "XRPL":
|
|
37
|
+
prefix = "XRPL";
|
|
38
|
+
break;
|
|
39
|
+
case "FaceSign":
|
|
40
|
+
prefix = "FACESIGN";
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
prefix = "eip712"; // fallback to eip712 for unknown types
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
Authorization: `${prefix} ${signature}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public async sendUpload(id: string, uploadRequest: Sharing, signature: Bytes): Promise<string> {
|
|
52
|
+
const authHeader = this.getAuthHeader(signature);
|
|
53
|
+
const url = `${this.baseUrl}/offchain/${this.contractAddress}/shares/${id}`;
|
|
54
|
+
const status = await putRequest(url, uploadRequest, authHeader);
|
|
55
|
+
if (status !== "201") {
|
|
56
|
+
throw new Error(`Error uploading share to ${this.contractAddress} at ${url}`);
|
|
57
|
+
}
|
|
58
|
+
return status;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async sendDownload(
|
|
62
|
+
id: string,
|
|
63
|
+
downloadRequest: DownloadRequest,
|
|
64
|
+
signature: Bytes,
|
|
65
|
+
): Promise<{ status: string; body: EncryptedShare | undefined }> {
|
|
66
|
+
const authHeader = this.getAuthHeader(signature);
|
|
67
|
+
const url = `${this.baseUrl}/offchain/${this.contractAddress}/shares/${id}`;
|
|
68
|
+
return await postRequest(url, downloadRequest, authHeader);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async downloadAndDecrypt(
|
|
72
|
+
id: string,
|
|
73
|
+
downloadRequest: DownloadRequest,
|
|
74
|
+
signature: Bytes,
|
|
75
|
+
secretKey: Uint8Array,
|
|
76
|
+
): Promise<{ share: Buffer | undefined; status: string }> {
|
|
77
|
+
const { status, body: encryptedShare } = await this.sendDownload(
|
|
78
|
+
id,
|
|
79
|
+
downloadRequest,
|
|
80
|
+
signature,
|
|
81
|
+
);
|
|
82
|
+
if (encryptedShare === undefined) {
|
|
83
|
+
return { share: undefined, status };
|
|
84
|
+
}
|
|
85
|
+
const encryptedBuffer = ethers.getBytes(encryptedShare.encrypted_share);
|
|
86
|
+
const publicKeyBuffer = ethers.getBytes(encryptedShare.public_key);
|
|
87
|
+
const nonceBuffer = ethers.getBytes(encryptedShare.nonce);
|
|
88
|
+
const open = nacl.box.open(encryptedBuffer, nonceBuffer, publicKeyBuffer, secretKey);
|
|
89
|
+
if (open == null) {
|
|
90
|
+
return { share: undefined, status: "box-open-error" };
|
|
91
|
+
}
|
|
92
|
+
// Remove blinding
|
|
93
|
+
return { share: Buffer.from(open.subarray(32)), status };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public async sendUpdate(
|
|
97
|
+
id: string,
|
|
98
|
+
updateRequest: UpdateWalletsRequest,
|
|
99
|
+
signature: string,
|
|
100
|
+
): Promise<string> {
|
|
101
|
+
const authHeader: RequestInit["headers"] = {
|
|
102
|
+
Authorization: `eip712 ${signature}`,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const url = `${this.baseUrl}/offchain/${this.contractAddress}/shares/${id}`;
|
|
106
|
+
const ok = await patchRequest(url, updateRequest, authHeader);
|
|
107
|
+
|
|
108
|
+
if (!ok) {
|
|
109
|
+
throw new Error(`Error updating wallets to ${this.contractAddress} at ${url}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return ok;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public async sendAddAddress(
|
|
116
|
+
id: string,
|
|
117
|
+
addRequest: AddAddressRequest,
|
|
118
|
+
signature: string,
|
|
119
|
+
): Promise<string> {
|
|
120
|
+
const authHeader = this.getAuthHeader(signature);
|
|
121
|
+
const url = `${this.baseUrl}/offchain/${this.contractAddress}/shares/${id}/add_address`;
|
|
122
|
+
const status = await patchRequest(url, addRequest, authHeader);
|
|
123
|
+
if (status !== "200") {
|
|
124
|
+
throw new Error(`Error adding a wallet to ${id} at ${url}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return status;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public async sendRemoveAddress(
|
|
131
|
+
id: string,
|
|
132
|
+
removeRequest: RemoveAddressRequest,
|
|
133
|
+
signature: string,
|
|
134
|
+
): Promise<string> {
|
|
135
|
+
const authHeader = this.getAuthHeader(signature);
|
|
136
|
+
const url = `${this.baseUrl}/offchain/${this.contractAddress}/shares/${id}/remove_address`;
|
|
137
|
+
const status = await patchRequest(url, removeRequest, authHeader);
|
|
138
|
+
if (status !== "200") {
|
|
139
|
+
throw new Error(`Error removing address from ${id} at ${url}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return status;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// This file is auto-generated from an abi-file using AbiCodegen.
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
// noinspection ES6UnusedImports
|
|
5
|
+
import {
|
|
6
|
+
AbiByteInput,
|
|
7
|
+
AbiByteOutput,
|
|
8
|
+
type AbiInput,
|
|
9
|
+
type AbiOutput,
|
|
10
|
+
type BlockchainAddress,
|
|
11
|
+
type BlockchainStateClient,
|
|
12
|
+
type StateWithClient,
|
|
13
|
+
} from "@partisiablockchain/abi-client";
|
|
14
|
+
|
|
15
|
+
type Option<K> = K | undefined;
|
|
16
|
+
export class IdosContract {
|
|
17
|
+
private readonly _client: BlockchainStateClient | undefined;
|
|
18
|
+
private readonly _address: BlockchainAddress | undefined;
|
|
19
|
+
|
|
20
|
+
public constructor(
|
|
21
|
+
client: BlockchainStateClient | undefined,
|
|
22
|
+
address: BlockchainAddress | undefined,
|
|
23
|
+
) {
|
|
24
|
+
this._address = address;
|
|
25
|
+
this._client = client;
|
|
26
|
+
}
|
|
27
|
+
public deserializeContractState(_input: AbiInput): ContractState {
|
|
28
|
+
const nodes_vecLength = _input.readI32();
|
|
29
|
+
const nodes: NodeConfig[] = [];
|
|
30
|
+
for (let nodes_i = 0; nodes_i < nodes_vecLength; nodes_i++) {
|
|
31
|
+
const nodes_elem: NodeConfig = this.deserializeNodeConfig(_input);
|
|
32
|
+
nodes.push(nodes_elem);
|
|
33
|
+
}
|
|
34
|
+
return { nodes };
|
|
35
|
+
}
|
|
36
|
+
public deserializeNodeConfig(_input: AbiInput): NodeConfig {
|
|
37
|
+
const address: BlockchainAddress = _input.readAddress();
|
|
38
|
+
const endpoint: string = _input.readString();
|
|
39
|
+
return { address, endpoint };
|
|
40
|
+
}
|
|
41
|
+
public async getState(): Promise<ContractState> {
|
|
42
|
+
const bytes = await this._client?.getContractStateBinary(this._address!);
|
|
43
|
+
if (bytes === undefined) {
|
|
44
|
+
throw new Error("Unable to get state bytes");
|
|
45
|
+
}
|
|
46
|
+
const input = AbiByteInput.createLittleEndian(bytes);
|
|
47
|
+
return this.deserializeContractState(input);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export interface ContractState {
|
|
51
|
+
nodes: NodeConfig[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface NodeConfig {
|
|
55
|
+
address: BlockchainAddress;
|
|
56
|
+
endpoint: string;
|
|
57
|
+
}
|
|
58
|
+
function serializeNodeConfig(_out: AbiOutput, _value: NodeConfig): void {
|
|
59
|
+
const { address, endpoint } = _value;
|
|
60
|
+
_out.writeAddress(address);
|
|
61
|
+
_out.writeString(endpoint);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function initialize(nodes: NodeConfig[]): Buffer {
|
|
65
|
+
return AbiByteOutput.serializeBigEndian((_out) => {
|
|
66
|
+
_out.writeBytes(Buffer.from("ffffffff0f", "hex"));
|
|
67
|
+
_out.writeI32(nodes.length);
|
|
68
|
+
for (const nodes_vec of nodes) {
|
|
69
|
+
serializeNodeConfig(_out, nodes_vec);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function deserializeState(state: StateWithClient): ContractState;
|
|
75
|
+
export function deserializeState(bytes: Buffer): ContractState;
|
|
76
|
+
export function deserializeState(
|
|
77
|
+
bytes: Buffer,
|
|
78
|
+
client: BlockchainStateClient,
|
|
79
|
+
address: BlockchainAddress,
|
|
80
|
+
): ContractState;
|
|
81
|
+
export function deserializeState(
|
|
82
|
+
state: Buffer | StateWithClient,
|
|
83
|
+
client?: BlockchainStateClient,
|
|
84
|
+
address?: BlockchainAddress,
|
|
85
|
+
): ContractState {
|
|
86
|
+
if (Buffer.isBuffer(state)) {
|
|
87
|
+
const input = AbiByteInput.createLittleEndian(state);
|
|
88
|
+
return new IdosContract(client, address).deserializeContractState(input);
|
|
89
|
+
}
|
|
90
|
+
const input = AbiByteInput.createLittleEndian(state.bytes);
|
|
91
|
+
return new IdosContract(state.client, state.address).deserializeContractState(input);
|
|
92
|
+
}
|
package/src/mpc/index.ts
ADDED