koilib 5.5.2 → 5.5.4

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/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;