Line data Source code
1 : import 'dart:typed_data';
2 :
3 : import 'package:walletkit_dart/src/crypto/utxo/entities/op_codes.dart';
4 : import 'package:walletkit_dart/src/domain/constants.dart';
5 : import 'package:walletkit_dart/src/utils/int.dart';
6 : import 'package:walletkit_dart/src/utils/var_uint.dart';
7 :
8 : class Script {
9 : final Uint8List bytes;
10 :
11 2 : const Script(this.bytes);
12 :
13 2 : List<ScriptChunk> get chunks {
14 2 : List<ScriptChunk> chunks = [];
15 : int offset = 0;
16 :
17 6 : while (offset < bytes.length) {
18 4 : int opcode = bytes[offset];
19 2 : offset += 1;
20 : int? dataToRead;
21 :
22 4 : if (opcode >= 0 && opcode <= OP_PUSHDATA1) {
23 : // The opcode value itself is length of data
24 : dataToRead = opcode;
25 2 : } else if (opcode == OP_PUSHDATA1) {
26 : // Next byte is length of data
27 0 : if (bytes.length - offset < 1) throw Exception('Unexpected end of script');
28 0 : dataToRead = bytes.bytes.readUint8(offset).$1;
29 0 : offset += 1;
30 2 : } else if (opcode == OP_PUSHDATA2) {
31 : // Next two bytes are length of data
32 0 : if (bytes.length - offset < 2) throw Exception('Unexpected end of script');
33 0 : dataToRead = bytes.bytes.readUint16(offset).$1;
34 0 : offset += 2;
35 2 : } else if (opcode == OP_PUSHDATA4) {
36 : // Next four bytes are length of data
37 0 : if (bytes.length - offset < 4) throw Exception('Unexpected end of script');
38 0 : dataToRead = bytes.bytes.readUint32(offset).$1;
39 0 : offset += 4;
40 : }
41 :
42 : final ScriptChunk chunk;
43 : if (dataToRead == null) {
44 2 : chunk = ScriptChunk(opcode, null);
45 : } else {
46 8 : if (dataToRead > bytes.length - offset)
47 0 : throw Exception(
48 0 : 'Push of data element that is larger than remaining data: $dataToRead vs ${bytes.length - offset} ');
49 6 : final data = bytes.sublist(offset, offset + dataToRead);
50 2 : chunk = ScriptChunk(opcode, data);
51 2 : offset += dataToRead;
52 : }
53 :
54 2 : chunks.add(chunk);
55 : }
56 6 : if (offset != bytes.length) throw Exception('Script parsing error');
57 : return chunks;
58 : }
59 : }
60 :
61 : class ScriptChunk {
62 : final int opcode;
63 : final Uint8List? data;
64 :
65 2 : const ScriptChunk(this.opcode, this.data);
66 : }
67 :
68 2 : BigInt getScriptWeight(Uint8List scriptBytes) {
69 4 : final scriptChunks = Script(scriptBytes).chunks;
70 :
71 2 : BigInt w = BigInt.zero;
72 : bool dataMode = false;
73 2 : int lastOpcode = -1; // OP_INVALIDOPCODE
74 4 : for (ScriptChunk scriptChunk in scriptChunks) {
75 10 : if (scriptChunk.data != null) w += scriptChunk.data!.length.toBI;
76 :
77 : if (dataMode) {
78 0 : w += 5.toBI;
79 : continue;
80 : }
81 :
82 2 : final opcode = scriptChunk.opcode;
83 :
84 : switch (opcode) {
85 2 : case OP_RETURN:
86 : dataMode = true;
87 0 : w += 5.toBI;
88 : break;
89 2 : case OP_RIPEMD160:
90 2 : case OP_SHA1:
91 2 : case OP_SHA256:
92 2 : case OP_HASH160:
93 2 : case OP_HASH256:
94 4 : w += 10.toBI;
95 : break;
96 2 : case OP_CODESEPARATOR:
97 0 : w += 10.toBI;
98 : break;
99 2 : case OP_CHECKSIG:
100 2 : case OP_CHECKSIGVERIFY:
101 4 : w += 100.toBI;
102 : break;
103 2 : case OP_CHECKMULTISIG:
104 2 : case OP_CHECKMULTISIGVERIFY:
105 0 : if (lastOpcode >= OP_1 && lastOpcode <= OP_16)
106 0 : w += (100 * decodeOP_N(lastOpcode)).toBI;
107 : else
108 0 : w += (100 * MAX_PUBKEYS_PER_MULTISIG).toBI;
109 : break;
110 2 : case OP_RESERVED1:
111 2 : case OP_RESERVED2:
112 2 : case OP_NOP1:
113 2 : case OP_NOP2:
114 2 : case OP_NOP3:
115 2 : case OP_NOP4:
116 2 : case OP_NOP5:
117 2 : case OP_NOP6:
118 2 : case OP_NOP7:
119 2 : case OP_NOP8:
120 2 : case OP_NOP9:
121 2 : case OP_NOP10:
122 0 : w += 10000.toBI;
123 : break;
124 : // case OP_INVALIDOPCODE:
125 : // w += 100000.toBI;
126 : // break;
127 :
128 : default:
129 4 : w += 5.toBI;
130 : break;
131 : }
132 : lastOpcode = opcode;
133 : }
134 :
135 0 : if (dataMode) w *= 20.toBI;
136 :
137 : return w;
138 : }
139 :
140 0 : int decodeOP_N(int opcode) {
141 : // Subtracting opcode by (OpCode.OP_1 - 1) to get the numeric value
142 0 : return opcode - (OPCODE.OP_1.hex - 1);
143 : }
|