koilib 5.5.3 → 5.5.5
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/Contract.d.ts +1 -1
- package/lib/browser/Contract.d.ts +1 -1
- package/package.json +3 -2
- package/src/Contract.ts +614 -0
- package/src/Provider.ts +512 -0
- package/src/Serializer.ts +366 -0
- package/src/Signer.ts +827 -0
- package/src/Transaction.ts +241 -0
- package/src/index.ts +9 -0
- package/src/index2.ts +17 -0
- package/src/indexUtils.ts +2 -0
- package/src/interface.ts +767 -0
- package/src/jsonDescriptors/chain-proto.json +726 -0
- package/src/jsonDescriptors/pow-proto.json +68 -0
- package/src/jsonDescriptors/token-proto.json +255 -0
- package/src/jsonDescriptors/value-proto.json +161 -0
- package/src/protoModules/protocol-proto.js +8138 -0
- package/src/protoModules/value-proto.js +1736 -0
- package/src/utils.ts +533 -0
- package/src/utilsNode.ts +378 -0
package/src/Signer.ts
ADDED
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
3
|
+
import * as secp from "@noble/secp256k1";
|
|
4
|
+
import { Provider } from "./Provider";
|
|
5
|
+
import {
|
|
6
|
+
TransactionJson,
|
|
7
|
+
TransactionJsonWait,
|
|
8
|
+
BlockJson,
|
|
9
|
+
RecoverPublicKeyOptions,
|
|
10
|
+
Abi,
|
|
11
|
+
TypeField,
|
|
12
|
+
TransactionReceipt,
|
|
13
|
+
SendTransactionOptions,
|
|
14
|
+
} from "./interface";
|
|
15
|
+
import {
|
|
16
|
+
bitcoinAddress,
|
|
17
|
+
bitcoinDecode,
|
|
18
|
+
bitcoinEncode,
|
|
19
|
+
btypeDecode,
|
|
20
|
+
calculateMerkleRoot,
|
|
21
|
+
decodeBase64url,
|
|
22
|
+
encodeBase64url,
|
|
23
|
+
toHexString,
|
|
24
|
+
toUint8Array,
|
|
25
|
+
} from "./utils";
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
import { koinos } from "./protoModules/protocol-proto.js";
|
|
29
|
+
|
|
30
|
+
export interface SignerInterface {
|
|
31
|
+
provider?: Provider;
|
|
32
|
+
getAddress: (compressed?: boolean) => string;
|
|
33
|
+
getPrivateKey: (format: "wif" | "hex", compressed?: boolean) => string;
|
|
34
|
+
signHash: (hash: Uint8Array) => Promise<Uint8Array>;
|
|
35
|
+
signMessage: (message: string | Uint8Array) => Promise<Uint8Array>;
|
|
36
|
+
|
|
37
|
+
// Transaction
|
|
38
|
+
prepareTransaction: (tx: TransactionJson) => Promise<TransactionJson>;
|
|
39
|
+
signTransaction: (
|
|
40
|
+
transaction: TransactionJson | TransactionJsonWait,
|
|
41
|
+
abis?: Record<string, Abi>
|
|
42
|
+
) => Promise<TransactionJson>;
|
|
43
|
+
sendTransaction: (
|
|
44
|
+
transaction: TransactionJson | TransactionJsonWait,
|
|
45
|
+
options?: SendTransactionOptions
|
|
46
|
+
) => Promise<{
|
|
47
|
+
receipt: TransactionReceipt;
|
|
48
|
+
transaction: TransactionJsonWait;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
// Block
|
|
52
|
+
prepareBlock: (block: BlockJson) => Promise<BlockJson>;
|
|
53
|
+
signBlock: (block: BlockJson) => Promise<BlockJson>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const btypeBlockHeader: TypeField["subtypes"] = {
|
|
57
|
+
previous: { type: "bytes", btype: "BLOCK_ID" },
|
|
58
|
+
height: { type: "uint64" },
|
|
59
|
+
timestamp: { type: "uint64" },
|
|
60
|
+
previous_state_merkle_root: { type: "bytes" },
|
|
61
|
+
transaction_merkle_root: { type: "bytes" },
|
|
62
|
+
signer: { type: "bytes", btype: "ADDRESS" },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const btypeTransactionHeader: TypeField["subtypes"] = {
|
|
66
|
+
chain_id: { type: "bytes" },
|
|
67
|
+
rc_limit: { type: "uint64" },
|
|
68
|
+
nonce: { type: "bytes" },
|
|
69
|
+
operation_merkle_root: { type: "bytes" },
|
|
70
|
+
payer: { type: "bytes", btype: "ADDRESS" },
|
|
71
|
+
payee: { type: "bytes", btype: "ADDRESS" },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const btypesOperation: TypeField["subtypes"] = {
|
|
75
|
+
upload_contract: {
|
|
76
|
+
type: "object",
|
|
77
|
+
subtypes: {
|
|
78
|
+
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
|
|
79
|
+
bytecode: { type: "bytes" },
|
|
80
|
+
abi: { type: "string" },
|
|
81
|
+
authorizes_call_contract: { type: "bool" },
|
|
82
|
+
authorizes_transaction_application: { type: "bool" },
|
|
83
|
+
authorizes_upload_contract: { type: "bool" },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
call_contract: {
|
|
87
|
+
type: "object",
|
|
88
|
+
subtypes: {
|
|
89
|
+
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
|
|
90
|
+
entry_point: { type: "uint32" },
|
|
91
|
+
args: { type: "bytes" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
set_system_call: {
|
|
95
|
+
type: "object",
|
|
96
|
+
subtypes: {
|
|
97
|
+
call_id: { type: "uint32" },
|
|
98
|
+
target: {
|
|
99
|
+
type: "object",
|
|
100
|
+
subtypes: {
|
|
101
|
+
thunk_id: { type: "uint32" },
|
|
102
|
+
system_call_bundle: {
|
|
103
|
+
type: "object",
|
|
104
|
+
subtypes: {
|
|
105
|
+
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
|
|
106
|
+
entry_point: { type: "uint32" },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
set_system_contract: {
|
|
114
|
+
type: "object",
|
|
115
|
+
subtypes: {
|
|
116
|
+
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
|
|
117
|
+
system_contract: { type: "bool" },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The Signer Class contains the private key needed to sign transactions.
|
|
124
|
+
* It can be created using the seed, wif, or private key
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* using private key as hex string
|
|
128
|
+
* ```ts
|
|
129
|
+
* var privateKey = "ec8601a24f81decd57f4b611b5ac6eb801cb3780bb02c0f9cdfe9d09daaddf9c";
|
|
130
|
+
* var signer = new Signer({ privateKey });
|
|
131
|
+
* ```
|
|
132
|
+
* <br>
|
|
133
|
+
*
|
|
134
|
+
* using private key as Uint8Array
|
|
135
|
+
* ```ts
|
|
136
|
+
* var buffer = new Uint8Array([
|
|
137
|
+
* 236, 134, 1, 162, 79, 129, 222, 205,
|
|
138
|
+
* 87, 244, 182, 17, 181, 172, 110, 184,
|
|
139
|
+
* 1, 203, 55, 128, 187, 2, 192, 249,
|
|
140
|
+
* 205, 254, 157, 9, 218, 173, 223, 156
|
|
141
|
+
* ]);
|
|
142
|
+
* var signer = new Signer({ privateKey: buffer });
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* <br>
|
|
146
|
+
*
|
|
147
|
+
* using private key as bigint
|
|
148
|
+
* ```ts
|
|
149
|
+
* var privateKey = 106982601049961974618234078204952280507266494766432547312316920283818886029212n;
|
|
150
|
+
* var signer = new Signer({ privateKey });
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* <br>
|
|
154
|
+
*
|
|
155
|
+
* using the seed
|
|
156
|
+
* ```ts
|
|
157
|
+
* var signer = Signer.fromSeed("my seed");
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* <br>
|
|
161
|
+
*
|
|
162
|
+
* using private key in WIF format
|
|
163
|
+
* ```ts
|
|
164
|
+
* var signer = Signer.fromWif("L59UtJcTdNBnrH2QSBA5beSUhRufRu3g6tScDTite6Msuj7U93tM");
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* <br>
|
|
168
|
+
*
|
|
169
|
+
* defining a provider
|
|
170
|
+
* ```ts
|
|
171
|
+
* var provider = new Provider(["https://example.com/jsonrpc"]);
|
|
172
|
+
* var privateKey = "ec8601a24f81decd57f4b611b5ac6eb801cb3780bb02c0f9cdfe9d09daaddf9c";
|
|
173
|
+
* var signer = new Signer({ privateKey, provider });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export class Signer implements SignerInterface {
|
|
177
|
+
/**
|
|
178
|
+
* Boolean determining if the public/private key
|
|
179
|
+
* is using the compressed format
|
|
180
|
+
*/
|
|
181
|
+
compressed: boolean;
|
|
182
|
+
|
|
183
|
+
private privateKey: string | number | bigint | Uint8Array;
|
|
184
|
+
|
|
185
|
+
publicKey: string | Uint8Array;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Account address
|
|
189
|
+
*/
|
|
190
|
+
address: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Chain id
|
|
194
|
+
*/
|
|
195
|
+
chainId = "";
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Provider to connect with the blockchain
|
|
199
|
+
*/
|
|
200
|
+
provider?: Provider;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Options to apply when sending a transaction.
|
|
204
|
+
* By default broadcast is true and the other fields
|
|
205
|
+
* are undefined
|
|
206
|
+
*/
|
|
207
|
+
sendOptions?: SendTransactionOptions;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The constructor receives de private key as hexstring, bigint or Uint8Array.
|
|
211
|
+
* See also the functions [[Signer.fromWif]] and [[Signer.fromSeed]]
|
|
212
|
+
* to create the signer from the WIF or Seed respectively.
|
|
213
|
+
*
|
|
214
|
+
* @param privateKey - Private key as hexstring, bigint or Uint8Array
|
|
215
|
+
* @param compressed - compressed format is true by default
|
|
216
|
+
* @param provider - provider to connect with the blockchain
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* const privateKey = "ec8601a24f81decd57f4b611b5ac6eb801cb3780bb02c0f9cdfe9d09daaddf9c";
|
|
220
|
+
* cons signer = new Signer({ privateKey });
|
|
221
|
+
* console.log(signer.getAddress());
|
|
222
|
+
* // 1MbL6mG8ASAvSYdoMnGUfG3ZXkmQ2dpL5b
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
constructor(c: {
|
|
226
|
+
privateKey: string | number | bigint | Uint8Array;
|
|
227
|
+
compressed?: boolean;
|
|
228
|
+
chainId?: string;
|
|
229
|
+
provider?: Provider;
|
|
230
|
+
sendOptions?: SendTransactionOptions;
|
|
231
|
+
}) {
|
|
232
|
+
this.compressed = typeof c.compressed === "undefined" ? true : c.compressed;
|
|
233
|
+
this.privateKey = c.privateKey;
|
|
234
|
+
this.provider = c.provider;
|
|
235
|
+
if (typeof c.privateKey === "string") {
|
|
236
|
+
this.publicKey = secp.getPublicKey(c.privateKey, this.compressed);
|
|
237
|
+
this.address = bitcoinAddress(this.publicKey);
|
|
238
|
+
} else {
|
|
239
|
+
this.publicKey = secp.getPublicKey(c.privateKey, this.compressed);
|
|
240
|
+
this.address = bitcoinAddress(this.publicKey);
|
|
241
|
+
}
|
|
242
|
+
if (c.chainId) this.chainId = c.chainId;
|
|
243
|
+
this.sendOptions = {
|
|
244
|
+
broadcast: true,
|
|
245
|
+
...c.sendOptions,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Function to import a private key from the WIF
|
|
251
|
+
* @param wif - Private key in WIF format
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* const signer = Signer.fromWif("L59UtJcTdNBnrH2QSBA5beSUhRufRu3g6tScDTite6Msuj7U93tM")
|
|
255
|
+
* console.log(signer.getAddress());
|
|
256
|
+
* // 1MbL6mG8ASAvSYdoMnGUfG3ZXkmQ2dpL5b
|
|
257
|
+
* ```
|
|
258
|
+
* @returns Signer object
|
|
259
|
+
*/
|
|
260
|
+
static fromWif(wif: string, compressed = true): Signer {
|
|
261
|
+
const comp = compressed === undefined ? wif[0] !== "5" : compressed;
|
|
262
|
+
const privateKey = bitcoinDecode(wif);
|
|
263
|
+
return new Signer({
|
|
264
|
+
privateKey: toHexString(privateKey),
|
|
265
|
+
compressed: comp,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Function to import a private key from the seed
|
|
271
|
+
* @param seed - Seed words
|
|
272
|
+
* @param compressed -
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* const signer = Signer.fromSeed("my seed");
|
|
276
|
+
* console.log(signer.getAddress());
|
|
277
|
+
* // 1BqtgWBcqm9cSZ97avLGZGJdgso7wx6pCA
|
|
278
|
+
* ```
|
|
279
|
+
* @returns Signer object
|
|
280
|
+
*/
|
|
281
|
+
static fromSeed(seed: string, compressed = true): Signer {
|
|
282
|
+
const privateKey = sha256(seed);
|
|
283
|
+
return new Signer({ privateKey, compressed });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @param compressed - determines if the address should be
|
|
288
|
+
* derived from the compressed public key (default) or the public key
|
|
289
|
+
* @returns Signer address
|
|
290
|
+
*/
|
|
291
|
+
getAddress(compressed = true): string {
|
|
292
|
+
if (typeof this.privateKey === "string") {
|
|
293
|
+
const publicKey = secp.getPublicKey(this.privateKey, compressed);
|
|
294
|
+
return bitcoinAddress(publicKey);
|
|
295
|
+
}
|
|
296
|
+
const publicKey = secp.getPublicKey(this.privateKey, compressed);
|
|
297
|
+
return bitcoinAddress(publicKey);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Function to get the private key in hex format or wif format
|
|
302
|
+
* @param format - The format must be "hex" (default) or "wif"
|
|
303
|
+
* @param compressed - Optional arg when using WIF format. By default it
|
|
304
|
+
* uses the compressed value defined in the signer
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const signer = Signer.fromSeed("one two three four five six");
|
|
308
|
+
* console.log(signer.getPrivateKey());
|
|
309
|
+
* // bab7fd6e5bd624f4ea0c33f7e7219262a6fa93a945a8964d9f110148286b7b37
|
|
310
|
+
*
|
|
311
|
+
* console.log(signer.getPrivateKey("wif"));
|
|
312
|
+
* // L3UfgFJWmbVziGB1uZBjkG1UjKkF7hhpXWY7mbTUdmycmvXCVtiL
|
|
313
|
+
*
|
|
314
|
+
* console.log(signer.getPrivateKey("wif", false));
|
|
315
|
+
* // 5KEX4TMHG66fT7cM9HMZLmdp4hVq4LC4X2Fkg6zeypM5UteWmtd
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
getPrivateKey(format: "wif" | "hex" = "hex", compressed = false): string {
|
|
319
|
+
let stringPrivateKey: string;
|
|
320
|
+
if (this.privateKey instanceof Uint8Array) {
|
|
321
|
+
stringPrivateKey = toHexString(this.privateKey);
|
|
322
|
+
} else if (typeof this.privateKey === "string") {
|
|
323
|
+
stringPrivateKey = this.privateKey;
|
|
324
|
+
} else {
|
|
325
|
+
stringPrivateKey = BigInt(this.privateKey).toString(16).padStart(64, "0");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const comp = compressed === undefined ? this.compressed : compressed;
|
|
329
|
+
|
|
330
|
+
switch (format) {
|
|
331
|
+
case "hex":
|
|
332
|
+
return stringPrivateKey;
|
|
333
|
+
case "wif":
|
|
334
|
+
return bitcoinEncode(toUint8Array(stringPrivateKey), "private", comp);
|
|
335
|
+
default:
|
|
336
|
+
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
337
|
+
throw new Error(`Invalid format ${format}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Function to sign a hash value. It returns the bytes signature.
|
|
343
|
+
* The signature is in compact format with the recovery byte
|
|
344
|
+
* @param hash - Hash value. Also known as digest
|
|
345
|
+
*/
|
|
346
|
+
async signHash(hash: Uint8Array): Promise<Uint8Array> {
|
|
347
|
+
const [compSignature, recovery] = await secp.sign(hash, this.privateKey, {
|
|
348
|
+
recovered: true,
|
|
349
|
+
canonical: true,
|
|
350
|
+
der: false, // compact signature
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const compactSignature = new Uint8Array(65);
|
|
354
|
+
compactSignature.set([recovery + 31], 0);
|
|
355
|
+
compactSignature.set(compSignature, 1);
|
|
356
|
+
return compactSignature;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Function to sign a message, which could be a string or a Uint8Array
|
|
361
|
+
*/
|
|
362
|
+
async signMessage(message: string | Uint8Array): Promise<Uint8Array> {
|
|
363
|
+
return this.signHash(sha256(message));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Function to sign a transaction. It's important to remark that
|
|
368
|
+
* the transaction parameter is modified inside this function.
|
|
369
|
+
* @param tx - Unsigned transaction
|
|
370
|
+
*/
|
|
371
|
+
async signTransaction(
|
|
372
|
+
tx: TransactionJson | TransactionJsonWait,
|
|
373
|
+
_abis?: Record<string, Abi>
|
|
374
|
+
): Promise<TransactionJson> {
|
|
375
|
+
if (!tx.id) throw new Error("Missing transaction id");
|
|
376
|
+
|
|
377
|
+
// multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
|
|
378
|
+
// tx id is a stringified multihash, need to extract the hash digest only
|
|
379
|
+
const hash = toUint8Array(tx.id.slice(6));
|
|
380
|
+
|
|
381
|
+
const signature = await this.signHash(hash);
|
|
382
|
+
if (!tx.signatures) tx.signatures = [];
|
|
383
|
+
tx.signatures.push(encodeBase64url(signature));
|
|
384
|
+
|
|
385
|
+
return tx;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Function to sign a block for federated consensus. That is,
|
|
390
|
+
* just the ecdsa signature. For other algorithms, like PoW,
|
|
391
|
+
* you have to sign the block and then process the signature
|
|
392
|
+
* to add the extra data (nonce in the case of PoW).
|
|
393
|
+
* @param block - Unsigned block
|
|
394
|
+
*/
|
|
395
|
+
async signBlock(block: BlockJson): Promise<BlockJson> {
|
|
396
|
+
if (!block.id) throw new Error("Missing block id");
|
|
397
|
+
|
|
398
|
+
// multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
|
|
399
|
+
// block id is a stringified multihash, need to extract the hash digest only
|
|
400
|
+
const hash = toUint8Array(block.id.slice(6));
|
|
401
|
+
|
|
402
|
+
const signature = await this.signHash(hash);
|
|
403
|
+
|
|
404
|
+
block.signature = encodeBase64url(signature);
|
|
405
|
+
|
|
406
|
+
return block;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Function to sign and send a transaction. It internally uses
|
|
411
|
+
* [[Provider.sendTransaction]]
|
|
412
|
+
* @param transaction - Transaction to send. It will be signed inside this
|
|
413
|
+
* function if it is not signed yet
|
|
414
|
+
* @param options - Options for sending the transaction
|
|
415
|
+
*/
|
|
416
|
+
async sendTransaction(
|
|
417
|
+
transaction: TransactionJson | TransactionJsonWait,
|
|
418
|
+
options?: SendTransactionOptions
|
|
419
|
+
): Promise<{
|
|
420
|
+
receipt: TransactionReceipt;
|
|
421
|
+
transaction: TransactionJsonWait;
|
|
422
|
+
}> {
|
|
423
|
+
if (!transaction.signatures || !transaction.signatures?.length)
|
|
424
|
+
transaction = await this.signTransaction(
|
|
425
|
+
transaction,
|
|
426
|
+
options?.sendAbis ? options.abis : undefined
|
|
427
|
+
);
|
|
428
|
+
if (!this.provider) throw new Error("provider is undefined");
|
|
429
|
+
const opts: SendTransactionOptions = {
|
|
430
|
+
...this.sendOptions,
|
|
431
|
+
...options,
|
|
432
|
+
};
|
|
433
|
+
if (opts.beforeSend) {
|
|
434
|
+
await opts.beforeSend(transaction, options);
|
|
435
|
+
}
|
|
436
|
+
return this.provider.sendTransaction(transaction, opts.broadcast);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Function to recover the public key from hash and signature
|
|
441
|
+
* @param hash - hash sha256
|
|
442
|
+
* @param signature - compact signature
|
|
443
|
+
* @param compressed - default true
|
|
444
|
+
*/
|
|
445
|
+
static recoverPublicKey(
|
|
446
|
+
hash: Uint8Array,
|
|
447
|
+
signature: Uint8Array,
|
|
448
|
+
compressed = true
|
|
449
|
+
): string {
|
|
450
|
+
const compactSignatureHex = toHexString(signature);
|
|
451
|
+
const recovery = Number(`0x${compactSignatureHex.slice(0, 2)}`) - 31;
|
|
452
|
+
const rHex = compactSignatureHex.slice(2, 66);
|
|
453
|
+
const sHex = compactSignatureHex.slice(66);
|
|
454
|
+
const r = BigInt(`0x${rHex}`);
|
|
455
|
+
const s = BigInt(`0x${sHex}`);
|
|
456
|
+
const sig = new secp.Signature(r, s);
|
|
457
|
+
const publicKey = secp.recoverPublicKey(
|
|
458
|
+
toHexString(hash),
|
|
459
|
+
sig.toHex(),
|
|
460
|
+
recovery
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
if (!publicKey) throw new Error("Public key cannot be recovered");
|
|
464
|
+
if (!compressed) {
|
|
465
|
+
return toHexString(publicKey);
|
|
466
|
+
} else {
|
|
467
|
+
return secp.Point.fromHex(publicKey).toHex(true);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static recoverAddress(
|
|
472
|
+
hash: Uint8Array,
|
|
473
|
+
signature: Uint8Array,
|
|
474
|
+
compressed = true
|
|
475
|
+
): string {
|
|
476
|
+
return bitcoinAddress(
|
|
477
|
+
toUint8Array(Signer.recoverPublicKey(hash, signature, compressed))
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Function to recover the publics keys from a signed
|
|
483
|
+
* transaction or block.
|
|
484
|
+
* The output format can be compressed (default) or uncompressed.
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```ts
|
|
488
|
+
* const publicKeys = await Signer.recoverPublicKeys(tx);
|
|
489
|
+
* ```
|
|
490
|
+
*
|
|
491
|
+
* If the signature data contains more data, like in the
|
|
492
|
+
* blocks for PoW consensus, use the "transformSignature"
|
|
493
|
+
* function to extract the signature.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```ts
|
|
497
|
+
* const powDescriptorJson = {
|
|
498
|
+
* nested: {
|
|
499
|
+
* mypackage: {
|
|
500
|
+
* nested: {
|
|
501
|
+
* pow_signature_data: {
|
|
502
|
+
* fields: {
|
|
503
|
+
* nonce: {
|
|
504
|
+
* type: "bytes",
|
|
505
|
+
* id: 1,
|
|
506
|
+
* },
|
|
507
|
+
* recoverable_signature: {
|
|
508
|
+
* type: "bytes",
|
|
509
|
+
* id: 2,
|
|
510
|
+
* },
|
|
511
|
+
* },
|
|
512
|
+
* },
|
|
513
|
+
* },
|
|
514
|
+
* },
|
|
515
|
+
* },
|
|
516
|
+
* };
|
|
517
|
+
*
|
|
518
|
+
* const serializer = new Serializer(powDescriptorJson, {
|
|
519
|
+
* defaultTypeName: "pow_signature_data",
|
|
520
|
+
* });
|
|
521
|
+
*
|
|
522
|
+
* const publicKeys = await signer.recoverPublicKeys(block, {
|
|
523
|
+
* transformSignature: async (signatureData) => {
|
|
524
|
+
* const powSignatureData = await serializer.deserialize(signatureData);
|
|
525
|
+
* return powSignatureData.recoverable_signature;
|
|
526
|
+
* },
|
|
527
|
+
* });
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
async recoverPublicKeys(
|
|
531
|
+
txOrBlock: TransactionJson | BlockJson,
|
|
532
|
+
opts?: RecoverPublicKeyOptions
|
|
533
|
+
): Promise<string[]> {
|
|
534
|
+
let compressed = true;
|
|
535
|
+
|
|
536
|
+
if (opts && opts.compressed !== undefined) {
|
|
537
|
+
compressed = opts.compressed;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
let signatures: string[] = [];
|
|
541
|
+
let headerBytes: Uint8Array;
|
|
542
|
+
|
|
543
|
+
const block = txOrBlock as BlockJson;
|
|
544
|
+
if (block.signature) {
|
|
545
|
+
if (!block.header) throw new Error("Missing block header");
|
|
546
|
+
if (!block.signature) throw new Error("Missing block signature");
|
|
547
|
+
signatures = [block.signature];
|
|
548
|
+
const headerDecoded = btypeDecode(block.header, btypeBlockHeader!, false);
|
|
549
|
+
const message = koinos.protocol.block_header.create(headerDecoded);
|
|
550
|
+
headerBytes = koinos.protocol.block_header.encode(message).finish();
|
|
551
|
+
} else {
|
|
552
|
+
const transaction = txOrBlock as TransactionJson;
|
|
553
|
+
if (!transaction.header) throw new Error("Missing transaction header");
|
|
554
|
+
if (!transaction.signatures)
|
|
555
|
+
throw new Error("Missing transaction signatures");
|
|
556
|
+
signatures = transaction.signatures;
|
|
557
|
+
const headerDecoded = btypeDecode(
|
|
558
|
+
transaction.header,
|
|
559
|
+
btypeTransactionHeader!,
|
|
560
|
+
false
|
|
561
|
+
);
|
|
562
|
+
const message = koinos.protocol.transaction_header.create(headerDecoded);
|
|
563
|
+
headerBytes = koinos.protocol.transaction_header.encode(message).finish();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const hash = sha256(headerBytes);
|
|
567
|
+
|
|
568
|
+
return Promise.all(
|
|
569
|
+
signatures.map(async (signature) => {
|
|
570
|
+
if (opts && typeof opts.transformSignature === "function") {
|
|
571
|
+
signature = await opts.transformSignature(signature);
|
|
572
|
+
}
|
|
573
|
+
return Signer.recoverPublicKey(
|
|
574
|
+
hash,
|
|
575
|
+
decodeBase64url(signature),
|
|
576
|
+
compressed
|
|
577
|
+
);
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Function to recover the signer addresses from a signed
|
|
584
|
+
* transaction or block.
|
|
585
|
+
* The output format can be compressed (default) or uncompressed.
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* const addresses = await signer.recoverAddress(tx);
|
|
589
|
+
* ```
|
|
590
|
+
*
|
|
591
|
+
* If the signature data contains more data, like in the
|
|
592
|
+
* blocks for PoW consensus, use the "transformSignature"
|
|
593
|
+
* function to extract the signature.
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```ts
|
|
597
|
+
* const powDescriptorJson = {
|
|
598
|
+
* nested: {
|
|
599
|
+
* mypackage: {
|
|
600
|
+
* nested: {
|
|
601
|
+
* pow_signature_data: {
|
|
602
|
+
* fields: {
|
|
603
|
+
* nonce: {
|
|
604
|
+
* type: "bytes",
|
|
605
|
+
* id: 1,
|
|
606
|
+
* },
|
|
607
|
+
* recoverable_signature: {
|
|
608
|
+
* type: "bytes",
|
|
609
|
+
* id: 2,
|
|
610
|
+
* },
|
|
611
|
+
* },
|
|
612
|
+
* },
|
|
613
|
+
* },
|
|
614
|
+
* },
|
|
615
|
+
* },
|
|
616
|
+
* };
|
|
617
|
+
*
|
|
618
|
+
* const serializer = new Serializer(powDescriptorJson, {
|
|
619
|
+
* defaultTypeName: "pow_signature_data",
|
|
620
|
+
* });
|
|
621
|
+
*
|
|
622
|
+
* const addresses = await signer.recoverAddress(block, {
|
|
623
|
+
* transformSignature: async (signatureData) => {
|
|
624
|
+
* const powSignatureData = await serializer.deserialize(signatureData);
|
|
625
|
+
* return powSignatureData.recoverable_signature;
|
|
626
|
+
* },
|
|
627
|
+
* });
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
630
|
+
async recoverAddresses(
|
|
631
|
+
txOrBlock: TransactionJson | BlockJson,
|
|
632
|
+
opts?: RecoverPublicKeyOptions
|
|
633
|
+
): Promise<string[]> {
|
|
634
|
+
const publicKeys = await this.recoverPublicKeys(txOrBlock, opts);
|
|
635
|
+
return publicKeys.map((publicKey) =>
|
|
636
|
+
bitcoinAddress(toUint8Array(publicKey))
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Function to prepare a transaction
|
|
642
|
+
* @param tx - Do not set the nonce to get it from the blockchain
|
|
643
|
+
* using the provider. The rc_limit is 1e8 by default.
|
|
644
|
+
* @returns A prepared transaction. ()
|
|
645
|
+
*/
|
|
646
|
+
async prepareTransaction(tx: TransactionJson): Promise<TransactionJson> {
|
|
647
|
+
if (!tx.header) {
|
|
648
|
+
tx.header = {};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const payer = tx.header.payer ?? this.address;
|
|
652
|
+
const { payee } = tx.header;
|
|
653
|
+
|
|
654
|
+
let nonce;
|
|
655
|
+
if (tx.header.nonce === undefined) {
|
|
656
|
+
if (!this.provider)
|
|
657
|
+
throw new Error(
|
|
658
|
+
"Cannot get the nonce because provider is undefined. To skip this call set a nonce in the transaction header"
|
|
659
|
+
);
|
|
660
|
+
nonce = await this.provider.getNextNonce(payee || payer);
|
|
661
|
+
} else {
|
|
662
|
+
nonce = tx.header.nonce;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
let rcLimit;
|
|
666
|
+
if (tx.header.rc_limit === undefined) {
|
|
667
|
+
if (!this.provider)
|
|
668
|
+
throw new Error(
|
|
669
|
+
"Cannot get the rc_limit because provider is undefined. To skip this call set a rc_limit in the transaction header"
|
|
670
|
+
);
|
|
671
|
+
rcLimit = await this.provider.getAccountRc(payer);
|
|
672
|
+
} else {
|
|
673
|
+
rcLimit = tx.header.rc_limit;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
let chainId = tx.header.chain_id || this.chainId;
|
|
677
|
+
if (!chainId) {
|
|
678
|
+
if (!this.provider)
|
|
679
|
+
throw new Error(
|
|
680
|
+
"Cannot get the chain_id because provider is undefined. To skip this call set a chain_id in the Signer"
|
|
681
|
+
);
|
|
682
|
+
chainId = await this.provider.getChainId();
|
|
683
|
+
this.chainId = chainId;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const operationsHashes: Uint8Array[] = [];
|
|
687
|
+
|
|
688
|
+
if (tx.operations) {
|
|
689
|
+
for (let index = 0; index < tx.operations?.length; index += 1) {
|
|
690
|
+
const operationDecoded = btypeDecode(
|
|
691
|
+
tx.operations[index],
|
|
692
|
+
btypesOperation!,
|
|
693
|
+
false
|
|
694
|
+
);
|
|
695
|
+
const message = koinos.protocol.operation.create(operationDecoded);
|
|
696
|
+
const operationEncoded = koinos.protocol.operation
|
|
697
|
+
.encode(message)
|
|
698
|
+
.finish() as Uint8Array;
|
|
699
|
+
operationsHashes.push(sha256(operationEncoded));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const operationMerkleRoot = encodeBase64url(
|
|
703
|
+
new Uint8Array([
|
|
704
|
+
// multihash sha256: 18, 32
|
|
705
|
+
18,
|
|
706
|
+
32,
|
|
707
|
+
...calculateMerkleRoot(operationsHashes),
|
|
708
|
+
])
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
tx.header = {
|
|
712
|
+
chain_id: chainId,
|
|
713
|
+
rc_limit: rcLimit,
|
|
714
|
+
nonce,
|
|
715
|
+
operation_merkle_root: operationMerkleRoot,
|
|
716
|
+
payer,
|
|
717
|
+
...(payee && { payee }),
|
|
718
|
+
// TODO: Option to resolve names (payer, payee)
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const headerDecoded = btypeDecode(
|
|
722
|
+
tx.header,
|
|
723
|
+
btypeTransactionHeader!,
|
|
724
|
+
false
|
|
725
|
+
);
|
|
726
|
+
const message = koinos.protocol.transaction_header.create(headerDecoded);
|
|
727
|
+
const headerBytes = koinos.protocol.transaction_header
|
|
728
|
+
.encode(message)
|
|
729
|
+
.finish() as Uint8Array;
|
|
730
|
+
|
|
731
|
+
const hash = sha256(headerBytes);
|
|
732
|
+
|
|
733
|
+
// multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
|
|
734
|
+
tx.id = `0x1220${toHexString(hash)}`;
|
|
735
|
+
return tx;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Function to prepare a block
|
|
740
|
+
* @param block -
|
|
741
|
+
* @returns A prepared block. ()
|
|
742
|
+
*/
|
|
743
|
+
async prepareBlock(block: BlockJson): Promise<BlockJson> {
|
|
744
|
+
if (!block.header) {
|
|
745
|
+
block.header = {};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const hashes: Uint8Array[] = [];
|
|
749
|
+
|
|
750
|
+
if (block.transactions) {
|
|
751
|
+
for (let index = 0; index < block.transactions.length; index++) {
|
|
752
|
+
const tx = block.transactions[index];
|
|
753
|
+
const headerDecoded = btypeDecode(
|
|
754
|
+
tx.header!,
|
|
755
|
+
btypeTransactionHeader!,
|
|
756
|
+
false
|
|
757
|
+
);
|
|
758
|
+
const message =
|
|
759
|
+
koinos.protocol.transaction_header.create(headerDecoded);
|
|
760
|
+
const headerBytes = koinos.protocol.transaction_header
|
|
761
|
+
.encode(message)
|
|
762
|
+
.finish() as Uint8Array;
|
|
763
|
+
|
|
764
|
+
hashes.push(sha256(headerBytes));
|
|
765
|
+
|
|
766
|
+
let signaturesBytes = new Uint8Array();
|
|
767
|
+
tx.signatures?.forEach((sig) => {
|
|
768
|
+
signaturesBytes = new Uint8Array([
|
|
769
|
+
...signaturesBytes,
|
|
770
|
+
...decodeBase64url(sig),
|
|
771
|
+
]);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
hashes.push(sha256(signaturesBytes));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// retrieve head info if not provided
|
|
779
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
780
|
+
let { height, previous, previous_state_merkle_root } = block.header;
|
|
781
|
+
|
|
782
|
+
if (!height || !previous || !previous_state_merkle_root) {
|
|
783
|
+
if (!this.provider) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
"Cannot get the head info because provider is undefined."
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const headInfo = await this.provider.getHeadInfo();
|
|
790
|
+
|
|
791
|
+
height = height || `${Number(headInfo.head_topology.height) + 1}`;
|
|
792
|
+
previous = previous || headInfo.head_topology.id;
|
|
793
|
+
previous_state_merkle_root =
|
|
794
|
+
previous_state_merkle_root || headInfo.head_state_merkle_root;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
block.header = {
|
|
798
|
+
height,
|
|
799
|
+
previous,
|
|
800
|
+
previous_state_merkle_root,
|
|
801
|
+
timestamp: block.header.timestamp || `${Date.now()}`,
|
|
802
|
+
transaction_merkle_root: encodeBase64url(
|
|
803
|
+
new Uint8Array([
|
|
804
|
+
// multihash sha256: 18, 32
|
|
805
|
+
18,
|
|
806
|
+
32,
|
|
807
|
+
...calculateMerkleRoot(hashes),
|
|
808
|
+
])
|
|
809
|
+
),
|
|
810
|
+
signer: this.address,
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const headerDecoded = btypeDecode(block.header, btypeBlockHeader!, false);
|
|
814
|
+
const message = koinos.protocol.block_header.create(headerDecoded);
|
|
815
|
+
const headerBytes = koinos.protocol.block_header
|
|
816
|
+
.encode(message)
|
|
817
|
+
.finish() as Uint8Array;
|
|
818
|
+
|
|
819
|
+
const hash = sha256(headerBytes);
|
|
820
|
+
|
|
821
|
+
// multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
|
|
822
|
+
block.id = `0x1220${toHexString(hash)}`;
|
|
823
|
+
return block;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export default Signer;
|