1 | 'use strict';
|
2 |
|
3 | var Address = require('../address');
|
4 | var BufferReader = require('../encoding/bufferreader');
|
5 | var BufferWriter = require('../encoding/bufferwriter');
|
6 | var Hash = require('../crypto/hash');
|
7 | var Opcode = require('../opcode');
|
8 | var PublicKey = require('../publickey');
|
9 | var Signature = require('../crypto/signature');
|
10 | var Networks = require('../networks');
|
11 | var $ = require('../util/preconditions');
|
12 | var _ = require('lodash');
|
13 | var errors = require('../errors');
|
14 | var buffer = require('buffer');
|
15 | var BufferUtil = require('../util/buffer');
|
16 | var JSUtil = require('../util/js');
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | var Script = function Script(from) {
|
28 | if (!(this instanceof Script)) {
|
29 | return new Script(from);
|
30 | }
|
31 | this.chunks = [];
|
32 |
|
33 | if (BufferUtil.isBuffer(from)) {
|
34 | return Script.fromBuffer(from);
|
35 | } else if (from instanceof Address) {
|
36 | return Script.fromAddress(from);
|
37 | } else if (from instanceof Script) {
|
38 | return Script.fromBuffer(from.toBuffer());
|
39 | } else if (_.isString(from)) {
|
40 | return Script.fromString(from);
|
41 | } else if (_.isObject(from) && _.isArray(from.chunks)) {
|
42 | this.set(from);
|
43 | }
|
44 | };
|
45 |
|
46 | Script.VERIFY_TAPROOT = (1 << 17);
|
47 |
|
48 |
|
49 | Script.prototype.set = function(obj) {
|
50 | $.checkArgument(_.isObject(obj));
|
51 | $.checkArgument(_.isArray(obj.chunks));
|
52 | this.chunks = obj.chunks;
|
53 | return this;
|
54 | };
|
55 |
|
56 | Script.fromBuffer = function(buffer) {
|
57 | var script = new Script();
|
58 | script.chunks = [];
|
59 |
|
60 | var br = new BufferReader(buffer);
|
61 | while (!br.finished()) {
|
62 | try {
|
63 | var opcodenum = br.readUInt8();
|
64 |
|
65 | var len, buf;
|
66 | if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) {
|
67 | len = opcodenum;
|
68 | script.chunks.push({
|
69 | buf: br.read(len),
|
70 | len: len,
|
71 | opcodenum: opcodenum
|
72 | });
|
73 | } else if (opcodenum === Opcode.OP_PUSHDATA1) {
|
74 | len = br.readUInt8();
|
75 | buf = br.read(len);
|
76 | script.chunks.push({
|
77 | buf: buf,
|
78 | len: len,
|
79 | opcodenum: opcodenum
|
80 | });
|
81 | } else if (opcodenum === Opcode.OP_PUSHDATA2) {
|
82 | len = br.readUInt16LE();
|
83 | buf = br.read(len);
|
84 | script.chunks.push({
|
85 | buf: buf,
|
86 | len: len,
|
87 | opcodenum: opcodenum
|
88 | });
|
89 | } else if (opcodenum === Opcode.OP_PUSHDATA4) {
|
90 | len = br.readUInt32LE();
|
91 | buf = br.read(len);
|
92 | script.chunks.push({
|
93 | buf: buf,
|
94 | len: len,
|
95 | opcodenum: opcodenum
|
96 | });
|
97 | } else {
|
98 | script.chunks.push({
|
99 | opcodenum: opcodenum
|
100 | });
|
101 | }
|
102 | } catch (e) {
|
103 | if (e instanceof RangeError) {
|
104 | throw new errors.Script.InvalidBuffer(buffer.toString('hex'));
|
105 | }
|
106 | throw e;
|
107 | }
|
108 | }
|
109 |
|
110 | return script;
|
111 | };
|
112 |
|
113 | Script.prototype.toBuffer = function() {
|
114 | var bw = new BufferWriter();
|
115 |
|
116 | for (var i = 0; i < this.chunks.length; i++) {
|
117 | var chunk = this.chunks[i];
|
118 | var opcodenum = chunk.opcodenum;
|
119 | bw.writeUInt8(chunk.opcodenum);
|
120 | if (chunk.buf) {
|
121 | if (opcodenum < Opcode.OP_PUSHDATA1) {
|
122 | bw.write(chunk.buf);
|
123 | } else if (opcodenum === Opcode.OP_PUSHDATA1) {
|
124 | bw.writeUInt8(chunk.len);
|
125 | bw.write(chunk.buf);
|
126 | } else if (opcodenum === Opcode.OP_PUSHDATA2) {
|
127 | bw.writeUInt16LE(chunk.len);
|
128 | bw.write(chunk.buf);
|
129 | } else if (opcodenum === Opcode.OP_PUSHDATA4) {
|
130 | bw.writeUInt32LE(chunk.len);
|
131 | bw.write(chunk.buf);
|
132 | }
|
133 | }
|
134 | }
|
135 |
|
136 | return bw.concat();
|
137 | };
|
138 |
|
139 | Script.fromASM = function(str) {
|
140 | var script = new Script();
|
141 | script.chunks = [];
|
142 |
|
143 | var tokens = str.split(' ');
|
144 | var i = 0;
|
145 | while (i < tokens.length) {
|
146 | var token = tokens[i];
|
147 | var opcode = Opcode(token);
|
148 | var opcodenum = opcode.toNumber();
|
149 |
|
150 | if (_.isUndefined(opcodenum)) {
|
151 | var buf = Buffer.from(tokens[i], 'hex');
|
152 | script.chunks.push({
|
153 | buf: buf,
|
154 | len: buf.length,
|
155 | opcodenum: buf.length
|
156 | });
|
157 | i = i + 1;
|
158 | } else if (opcodenum === Opcode.OP_PUSHDATA1 ||
|
159 | opcodenum === Opcode.OP_PUSHDATA2 ||
|
160 | opcodenum === Opcode.OP_PUSHDATA4) {
|
161 | script.chunks.push({
|
162 | buf: Buffer.from(tokens[i + 2], 'hex'),
|
163 | len: parseInt(tokens[i + 1]),
|
164 | opcodenum: opcodenum
|
165 | });
|
166 | i = i + 3;
|
167 | } else {
|
168 | script.chunks.push({
|
169 | opcodenum: opcodenum
|
170 | });
|
171 | i = i + 1;
|
172 | }
|
173 | }
|
174 | return script;
|
175 | };
|
176 |
|
177 | Script.fromHex = function(str) {
|
178 | return new Script(Buffer.from(str, 'hex'));
|
179 | };
|
180 |
|
181 | Script.fromString = function(str) {
|
182 | if (JSUtil.isHexa(str) || str.length === 0) {
|
183 | return new Script(Buffer.from(str, 'hex'));
|
184 | }
|
185 | var script = new Script();
|
186 | script.chunks = [];
|
187 |
|
188 | var tokens = str.split(' ');
|
189 | var i = 0;
|
190 | while (i < tokens.length) {
|
191 | var token = tokens[i];
|
192 | var opcode = Opcode(token);
|
193 | var opcodenum = opcode.toNumber();
|
194 |
|
195 | if (_.isUndefined(opcodenum)) {
|
196 | opcodenum = parseInt(token);
|
197 | if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) {
|
198 | script.chunks.push({
|
199 | buf: Buffer.from(tokens[i + 1].slice(2), 'hex'),
|
200 | len: opcodenum,
|
201 | opcodenum: opcodenum
|
202 | });
|
203 | i = i + 2;
|
204 | } else {
|
205 | throw new Error('Invalid script: ' + JSON.stringify(str));
|
206 | }
|
207 | } else if (opcodenum === Opcode.OP_PUSHDATA1 ||
|
208 | opcodenum === Opcode.OP_PUSHDATA2 ||
|
209 | opcodenum === Opcode.OP_PUSHDATA4) {
|
210 | if (tokens[i + 2].slice(0, 2) !== '0x') {
|
211 | throw new Error('Pushdata data must start with 0x');
|
212 | }
|
213 | script.chunks.push({
|
214 | buf: Buffer.from(tokens[i + 2].slice(2), 'hex'),
|
215 | len: parseInt(tokens[i + 1]),
|
216 | opcodenum: opcodenum
|
217 | });
|
218 | i = i + 3;
|
219 | } else {
|
220 | script.chunks.push({
|
221 | opcodenum: opcodenum
|
222 | });
|
223 | i = i + 1;
|
224 | }
|
225 | }
|
226 | return script;
|
227 | };
|
228 |
|
229 | Script.prototype._chunkToString = function(chunk, type) {
|
230 | var opcodenum = chunk.opcodenum;
|
231 | var asm = (type === 'asm');
|
232 | var str = '';
|
233 | if (!chunk.buf) {
|
234 |
|
235 | if (typeof Opcode.reverseMap[opcodenum] !== 'undefined') {
|
236 | if (asm) {
|
237 |
|
238 |
|
239 | if (opcodenum === 0) {
|
240 |
|
241 | str = str + ' 0';
|
242 | } else if(opcodenum === 79) {
|
243 |
|
244 | str = str + ' -1';
|
245 | } else {
|
246 | str = str + ' ' + Opcode(opcodenum).toString();
|
247 | }
|
248 | } else {
|
249 | str = str + ' ' + Opcode(opcodenum).toString();
|
250 | }
|
251 | } else {
|
252 | var numstr = opcodenum.toString(16);
|
253 | if (numstr.length % 2 !== 0) {
|
254 | numstr = '0' + numstr;
|
255 | }
|
256 | if (asm) {
|
257 | str = str + ' ' + numstr;
|
258 | } else {
|
259 | str = str + ' ' + '0x' + numstr;
|
260 | }
|
261 | }
|
262 | } else {
|
263 |
|
264 | if (!asm && opcodenum === Opcode.OP_PUSHDATA1 ||
|
265 | opcodenum === Opcode.OP_PUSHDATA2 ||
|
266 | opcodenum === Opcode.OP_PUSHDATA4) {
|
267 | str = str + ' ' + Opcode(opcodenum).toString();
|
268 | }
|
269 | if (chunk.len > 0) {
|
270 | if (asm) {
|
271 | str = str + ' ' + chunk.buf.toString('hex');
|
272 | } else {
|
273 | str = str + ' ' + chunk.len + ' ' + '0x' + chunk.buf.toString('hex');
|
274 | }
|
275 | }
|
276 | }
|
277 | return str;
|
278 | };
|
279 |
|
280 | Script.prototype.toASM = function() {
|
281 | var str = '';
|
282 | for (var i = 0; i < this.chunks.length; i++) {
|
283 | var chunk = this.chunks[i];
|
284 | str += this._chunkToString(chunk, 'asm');
|
285 | }
|
286 |
|
287 | return str.substr(1);
|
288 | };
|
289 |
|
290 | Script.prototype.toString = function() {
|
291 | var str = '';
|
292 | for (var i = 0; i < this.chunks.length; i++) {
|
293 | var chunk = this.chunks[i];
|
294 | str += this._chunkToString(chunk);
|
295 | }
|
296 |
|
297 | return str.substr(1);
|
298 | };
|
299 |
|
300 | Script.prototype.toHex = function() {
|
301 | return this.toBuffer().toString('hex');
|
302 | };
|
303 |
|
304 | Script.prototype.inspect = function() {
|
305 | return '<Script: ' + this.toString() + '>';
|
306 | };
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 | Script.prototype.isPublicKeyHashOut = function() {
|
314 | return !!(this.chunks.length === 5 &&
|
315 | this.chunks[0].opcodenum === Opcode.OP_DUP &&
|
316 | this.chunks[1].opcodenum === Opcode.OP_HASH160 &&
|
317 | this.chunks[2].buf &&
|
318 | this.chunks[2].buf.length === 20 &&
|
319 | this.chunks[3].opcodenum === Opcode.OP_EQUALVERIFY &&
|
320 | this.chunks[4].opcodenum === Opcode.OP_CHECKSIG);
|
321 | };
|
322 |
|
323 |
|
324 |
|
325 |
|
326 | Script.prototype.isPublicKeyHashIn = function() {
|
327 | if (this.chunks.length === 2) {
|
328 | var signatureBuf = this.chunks[0].buf;
|
329 | var pubkeyBuf = this.chunks[1].buf;
|
330 | if (signatureBuf &&
|
331 | signatureBuf.length &&
|
332 | signatureBuf[0] === 0x30 &&
|
333 | pubkeyBuf &&
|
334 | pubkeyBuf.length
|
335 | ) {
|
336 | var version = pubkeyBuf[0];
|
337 | if ((version === 0x04 ||
|
338 | version === 0x06 ||
|
339 | version === 0x07) && pubkeyBuf.length === 65) {
|
340 | return true;
|
341 | } else if ((version === 0x03 || version === 0x02) && pubkeyBuf.length === 33) {
|
342 | return true;
|
343 | }
|
344 | }
|
345 | }
|
346 | return false;
|
347 | };
|
348 |
|
349 | Script.prototype.getPublicKey = function() {
|
350 | $.checkState(this.isPublicKeyOut(), 'Can\'t retrieve PublicKey from a non-PK output');
|
351 | return this.chunks[0].buf;
|
352 | };
|
353 |
|
354 | Script.prototype.getPublicKeyHash = function() {
|
355 | if (this.isPublicKeyHashOut()) {
|
356 | return this.chunks[2].buf;
|
357 | } else if (this.isWitnessPublicKeyHashOut()) {
|
358 | return this.chunks[1].buf;
|
359 | } else {
|
360 | throw new Error('Can\'t retrieve PublicKeyHash from a non-PKH output');
|
361 | }
|
362 | };
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | Script.prototype.isPublicKeyOut = function() {
|
368 | if (this.chunks.length === 2 &&
|
369 | this.chunks[0].buf &&
|
370 | this.chunks[0].buf.length &&
|
371 | this.chunks[1].opcodenum === Opcode.OP_CHECKSIG) {
|
372 | var pubkeyBuf = this.chunks[0].buf;
|
373 | var version = pubkeyBuf[0];
|
374 | var isVersion = false;
|
375 | if ((version === 0x04 ||
|
376 | version === 0x06 ||
|
377 | version === 0x07) && pubkeyBuf.length === 65) {
|
378 | isVersion = true;
|
379 | } else if ((version === 0x03 || version === 0x02) && pubkeyBuf.length === 33) {
|
380 | isVersion = true;
|
381 | }
|
382 | if (isVersion) {
|
383 | return PublicKey.isValid(pubkeyBuf);
|
384 | }
|
385 | }
|
386 | return false;
|
387 | };
|
388 |
|
389 |
|
390 |
|
391 |
|
392 | Script.prototype.isPublicKeyIn = function() {
|
393 | if (this.chunks.length === 1) {
|
394 | var signatureBuf = this.chunks[0].buf;
|
395 | if (signatureBuf &&
|
396 | signatureBuf.length &&
|
397 | signatureBuf[0] === 0x30) {
|
398 | return true;
|
399 | }
|
400 | }
|
401 | return false;
|
402 | };
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | Script.prototype.isScriptHashOut = function() {
|
408 | var buf = this.toBuffer();
|
409 | return (buf.length === 23 &&
|
410 | buf[0] === Opcode.OP_HASH160 &&
|
411 | buf[1] === 0x14 &&
|
412 | buf[buf.length - 1] === Opcode.OP_EQUAL);
|
413 | };
|
414 |
|
415 |
|
416 |
|
417 |
|
418 | Script.prototype.isWitnessScriptHashOut = function() {
|
419 | var buf = this.toBuffer();
|
420 | return (buf.length === 34 && buf[0] === Opcode.OP_0 && buf[1] === 32);
|
421 | };
|
422 |
|
423 |
|
424 |
|
425 |
|
426 | Script.prototype.isWitnessPublicKeyHashOut = function() {
|
427 | var buf = this.toBuffer();
|
428 | return (buf.length === 22 && buf[0] === Opcode.OP_0 && buf[1] === 20);
|
429 | };
|
430 |
|
431 |
|
432 |
|
433 |
|
434 | Script.prototype.isTaproot = function() {
|
435 | var buf = this.toBuffer();
|
436 | return (buf.length === 34 && buf[0] === Opcode.OP_1 && buf[1] === 32);
|
437 | }
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 | Script.prototype.isWitnessProgram = function(values) {
|
446 | if (!values) {
|
447 | values = {};
|
448 | }
|
449 | var buf = this.toBuffer();
|
450 | if (buf.length < 4 || buf.length > 42) {
|
451 | return false;
|
452 | }
|
453 | if (buf[0] !== Opcode.OP_0 && !(buf[0] >= Opcode.OP_1 && buf[0] <= Opcode.OP_16)) {
|
454 | return false;
|
455 | }
|
456 |
|
457 | if (buf.length === buf[1] + 2) {
|
458 | values.version = buf[0];
|
459 | values.program = buf.slice(2, buf.length);
|
460 | return true;
|
461 | }
|
462 |
|
463 | return false;
|
464 | };
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 | Script.prototype.isScriptHashIn = function() {
|
471 | if (this.chunks.length <= 1) {
|
472 | return false;
|
473 | }
|
474 | var redeemChunk = this.chunks[this.chunks.length - 1];
|
475 | var redeemBuf = redeemChunk.buf;
|
476 | if (!redeemBuf) {
|
477 | return false;
|
478 | }
|
479 |
|
480 | var redeemScript;
|
481 | try {
|
482 | redeemScript = Script.fromBuffer(redeemBuf);
|
483 | } catch (e) {
|
484 | if (e instanceof errors.Script.InvalidBuffer) {
|
485 | return false;
|
486 | }
|
487 | throw e;
|
488 | }
|
489 | var type = redeemScript.classify();
|
490 | return type !== Script.types.UNKNOWN;
|
491 | };
|
492 |
|
493 |
|
494 |
|
495 |
|
496 | Script.prototype.isMultisigOut = function() {
|
497 | return (this.chunks.length > 3 &&
|
498 | Opcode.isSmallIntOp(this.chunks[0].opcodenum) &&
|
499 | this.chunks.slice(1, this.chunks.length - 2).every(function(obj) {
|
500 | return obj.buf && BufferUtil.isBuffer(obj.buf);
|
501 | }) &&
|
502 | Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2].opcodenum) &&
|
503 | this.chunks[this.chunks.length - 1].opcodenum === Opcode.OP_CHECKMULTISIG);
|
504 | };
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 | Script.prototype.isMultisigIn = function() {
|
511 | return this.chunks.length >= 2 &&
|
512 | this.chunks[0].opcodenum === 0 &&
|
513 | this.chunks.slice(1, this.chunks.length).every(function(obj) {
|
514 | return obj.buf &&
|
515 | BufferUtil.isBuffer(obj.buf) &&
|
516 | Signature.isTxDER(obj.buf);
|
517 | });
|
518 | };
|
519 |
|
520 |
|
521 |
|
522 |
|
523 | Script.prototype.isDataOut = function() {
|
524 | return this.chunks.length >= 1 &&
|
525 | this.chunks[0].opcodenum === Opcode.OP_RETURN &&
|
526 | (this.chunks.length === 1 ||
|
527 | (this.chunks.length === 2 &&
|
528 | this.chunks[1].buf &&
|
529 | this.chunks[1].buf.length <= Script.OP_RETURN_STANDARD_SIZE &&
|
530 | this.chunks[1].length === this.chunks.len));
|
531 | };
|
532 |
|
533 |
|
534 |
|
535 |
|
536 |
|
537 |
|
538 |
|
539 | Script.prototype.getData = function() {
|
540 | if (this.isDataOut() || this.isScriptHashOut() || this.isWitnessScriptHashOut() || this.isWitnessPublicKeyHashOut() || this.isTaproot()) {
|
541 | if (_.isUndefined(this.chunks[1])) {
|
542 | return Buffer.alloc(0);
|
543 | } else {
|
544 | return Buffer.from(this.chunks[1].buf);
|
545 | }
|
546 | }
|
547 | if (this.isPublicKeyHashOut()) {
|
548 | return Buffer.from(this.chunks[2].buf);
|
549 | }
|
550 | throw new Error('Unrecognized script type to get data from');
|
551 | };
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 | Script.prototype.isPushOnly = function() {
|
558 | return _.every(this.chunks, function(chunk) {
|
559 | return chunk.opcodenum <= Opcode.OP_16;
|
560 | });
|
561 | };
|
562 |
|
563 |
|
564 | Script.types = {};
|
565 | Script.types.UNKNOWN = 'Unknown';
|
566 | Script.types.PUBKEY_OUT = 'Pay to public key';
|
567 | Script.types.PUBKEY_IN = 'Spend from public key';
|
568 | Script.types.PUBKEYHASH_OUT = 'Pay to public key hash';
|
569 | Script.types.PUBKEYHASH_IN = 'Spend from public key hash';
|
570 | Script.types.SCRIPTHASH_OUT = 'Pay to script hash';
|
571 | Script.types.SCRIPTHASH_IN = 'Spend from script hash';
|
572 | Script.types.MULTISIG_OUT = 'Pay to multisig';
|
573 | Script.types.MULTISIG_IN = 'Spend from multisig';
|
574 | Script.types.DATA_OUT = 'Data push';
|
575 |
|
576 | Script.OP_RETURN_STANDARD_SIZE = 80;
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 | Script.prototype.classify = function() {
|
583 | if (this._isInput) {
|
584 | return this.classifyInput();
|
585 | } else if (this._isOutput) {
|
586 | return this.classifyOutput();
|
587 | } else {
|
588 | var outputType = this.classifyOutput();
|
589 | return outputType != Script.types.UNKNOWN ? outputType : this.classifyInput();
|
590 | }
|
591 | };
|
592 |
|
593 | Script.outputIdentifiers = {};
|
594 | Script.outputIdentifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut;
|
595 | Script.outputIdentifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut;
|
596 | Script.outputIdentifiers.MULTISIG_OUT = Script.prototype.isMultisigOut;
|
597 | Script.outputIdentifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut;
|
598 | Script.outputIdentifiers.DATA_OUT = Script.prototype.isDataOut;
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 | Script.prototype.classifyOutput = function() {
|
605 | for (var type in Script.outputIdentifiers) {
|
606 | if (Script.outputIdentifiers[type].bind(this)()) {
|
607 | return Script.types[type];
|
608 | }
|
609 | }
|
610 | return Script.types.UNKNOWN;
|
611 | };
|
612 |
|
613 | Script.inputIdentifiers = {};
|
614 | Script.inputIdentifiers.PUBKEY_IN = Script.prototype.isPublicKeyIn;
|
615 | Script.inputIdentifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn;
|
616 | Script.inputIdentifiers.MULTISIG_IN = Script.prototype.isMultisigIn;
|
617 | Script.inputIdentifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn;
|
618 |
|
619 |
|
620 |
|
621 |
|
622 |
|
623 | Script.prototype.classifyInput = function() {
|
624 | for (var type in Script.inputIdentifiers) {
|
625 | if (Script.inputIdentifiers[type].bind(this)()) {
|
626 | return Script.types[type];
|
627 | }
|
628 | }
|
629 | return Script.types.UNKNOWN;
|
630 | };
|
631 |
|
632 |
|
633 |
|
634 |
|
635 |
|
636 | Script.prototype.isStandard = function() {
|
637 |
|
638 | return this.classify() !== Script.types.UNKNOWN;
|
639 | };
|
640 |
|
641 |
|
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 |
|
648 |
|
649 | Script.prototype.prepend = function(obj) {
|
650 | this._addByType(obj, true);
|
651 | return this;
|
652 | };
|
653 |
|
654 |
|
655 |
|
656 |
|
657 | Script.prototype.equals = function(script) {
|
658 | $.checkState(script instanceof Script, 'Must provide another script');
|
659 | if (this.chunks.length !== script.chunks.length) {
|
660 | return false;
|
661 | }
|
662 | var i;
|
663 | for (i = 0; i < this.chunks.length; i++) {
|
664 | if (BufferUtil.isBuffer(this.chunks[i].buf) && !BufferUtil.isBuffer(script.chunks[i].buf)) {
|
665 | return false;
|
666 | }
|
667 | if (BufferUtil.isBuffer(this.chunks[i].buf) && !BufferUtil.equals(this.chunks[i].buf, script.chunks[i].buf)) {
|
668 | return false;
|
669 | } else if (this.chunks[i].opcodenum !== script.chunks[i].opcodenum) {
|
670 | return false;
|
671 | }
|
672 | }
|
673 | return true;
|
674 | };
|
675 |
|
676 |
|
677 |
|
678 |
|
679 |
|
680 |
|
681 |
|
682 |
|
683 | Script.prototype.add = function(obj) {
|
684 | this._addByType(obj, false);
|
685 | return this;
|
686 | };
|
687 |
|
688 | Script.prototype._addByType = function(obj, prepend) {
|
689 | if (typeof obj === 'string') {
|
690 | this._addOpcode(obj, prepend);
|
691 | } else if (typeof obj === 'number') {
|
692 | this._addOpcode(obj, prepend);
|
693 | } else if (obj instanceof Opcode) {
|
694 | this._addOpcode(obj, prepend);
|
695 | } else if (BufferUtil.isBuffer(obj)) {
|
696 | this._addBuffer(obj, prepend);
|
697 | } else if (obj instanceof Script) {
|
698 | this.chunks = this.chunks.concat(obj.chunks);
|
699 | } else if (typeof obj === 'object') {
|
700 | this._insertAtPosition(obj, prepend);
|
701 | } else {
|
702 | throw new Error('Invalid script chunk');
|
703 | }
|
704 | };
|
705 |
|
706 | Script.prototype._insertAtPosition = function(op, prepend) {
|
707 | if (prepend) {
|
708 | this.chunks.unshift(op);
|
709 | } else {
|
710 | this.chunks.push(op);
|
711 | }
|
712 | };
|
713 |
|
714 | Script.prototype._addOpcode = function(opcode, prepend) {
|
715 | var op;
|
716 | if (typeof opcode === 'number') {
|
717 | op = opcode;
|
718 | } else if (opcode instanceof Opcode) {
|
719 | op = opcode.toNumber();
|
720 | } else {
|
721 | op = Opcode(opcode).toNumber();
|
722 | }
|
723 | this._insertAtPosition({
|
724 | opcodenum: op
|
725 | }, prepend);
|
726 | return this;
|
727 | };
|
728 |
|
729 | Script.prototype._addBuffer = function(buf, prepend) {
|
730 | var opcodenum;
|
731 | var len = buf.length;
|
732 | if (len >= 0 && len < Opcode.OP_PUSHDATA1) {
|
733 | opcodenum = len;
|
734 | } else if (len < Math.pow(2, 8)) {
|
735 | opcodenum = Opcode.OP_PUSHDATA1;
|
736 | } else if (len < Math.pow(2, 16)) {
|
737 | opcodenum = Opcode.OP_PUSHDATA2;
|
738 | } else if (len < Math.pow(2, 32)) {
|
739 | opcodenum = Opcode.OP_PUSHDATA4;
|
740 | } else {
|
741 | throw new Error('You can\'t push that much data');
|
742 | }
|
743 | this._insertAtPosition({
|
744 | buf: buf,
|
745 | len: len,
|
746 | opcodenum: opcodenum
|
747 | }, prepend);
|
748 | return this;
|
749 | };
|
750 |
|
751 | Script.prototype.hasCodeseparators = function() {
|
752 | for (var i = 0; i < this.chunks.length; i++) {
|
753 | if (this.chunks[i].opcodenum === Opcode.OP_CODESEPARATOR) {
|
754 | return true;
|
755 | }
|
756 | }
|
757 | return false;
|
758 | };
|
759 |
|
760 | Script.prototype.removeCodeseparators = function() {
|
761 | var chunks = [];
|
762 | for (var i = 0; i < this.chunks.length; i++) {
|
763 | if (this.chunks[i].opcodenum !== Opcode.OP_CODESEPARATOR) {
|
764 | chunks.push(this.chunks[i]);
|
765 | }
|
766 | }
|
767 | this.chunks = chunks;
|
768 | return this;
|
769 | };
|
770 |
|
771 |
|
772 |
|
773 |
|
774 |
|
775 |
|
776 |
|
777 |
|
778 |
|
779 |
|
780 |
|
781 |
|
782 | Script.buildMultisigOut = function(publicKeys, threshold, opts) {
|
783 | $.checkArgument(threshold <= publicKeys.length,
|
784 | 'Number of required signatures must be less than or equal to the number of public keys');
|
785 | opts = opts || {};
|
786 | var script = new Script();
|
787 | script.add(Opcode.smallInt(threshold));
|
788 | publicKeys = _.map(publicKeys, PublicKey);
|
789 | var sorted = publicKeys;
|
790 | if (!opts.noSorting) {
|
791 | sorted = _.sortBy(publicKeys, function(publicKey) {
|
792 | return publicKey.toString('hex');
|
793 | });
|
794 | }
|
795 | for (var i = 0; i < sorted.length; i++) {
|
796 | var publicKey = sorted[i];
|
797 | script.add(publicKey.toBuffer());
|
798 | }
|
799 | script.add(Opcode.smallInt(publicKeys.length));
|
800 | script.add(Opcode.OP_CHECKMULTISIG);
|
801 | return script;
|
802 | };
|
803 |
|
804 | Script.buildWitnessMultisigOutFromScript = function(script) {
|
805 | if (script instanceof Script) {
|
806 | var s = new Script();
|
807 | s.add(Opcode.OP_0);
|
808 | s.add(Hash.sha256(script.toBuffer()));
|
809 | return s;
|
810 | } else {
|
811 | throw new TypeError('First argument is expected to be a p2sh script');
|
812 | }
|
813 | };
|
814 |
|
815 |
|
816 |
|
817 |
|
818 |
|
819 |
|
820 |
|
821 |
|
822 |
|
823 |
|
824 |
|
825 |
|
826 |
|
827 | Script.buildMultisigIn = function(pubkeys, threshold, signatures, opts) {
|
828 | $.checkArgument(_.isArray(pubkeys));
|
829 | $.checkArgument(_.isNumber(threshold));
|
830 | $.checkArgument(_.isArray(signatures));
|
831 | opts = opts || {};
|
832 | var s = new Script();
|
833 | s.add(Opcode.OP_0);
|
834 | _.each(signatures, function(signature) {
|
835 | $.checkArgument(BufferUtil.isBuffer(signature), 'Signatures must be an array of Buffers');
|
836 |
|
837 | s.add(signature);
|
838 | });
|
839 | return s;
|
840 | };
|
841 |
|
842 |
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 | Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) {
|
855 | $.checkArgument(_.isArray(pubkeys));
|
856 | $.checkArgument(_.isNumber(threshold));
|
857 | $.checkArgument(_.isArray(signatures));
|
858 | opts = opts || {};
|
859 | var s = new Script();
|
860 | s.add(Opcode.OP_0);
|
861 | _.each(signatures, function(signature) {
|
862 | $.checkArgument(BufferUtil.isBuffer(signature), 'Signatures must be an array of Buffers');
|
863 |
|
864 | s.add(signature);
|
865 | });
|
866 | s.add((opts.cachedMultisig || Script.buildMultisigOut(pubkeys, threshold, opts)).toBuffer());
|
867 | return s;
|
868 | };
|
869 |
|
870 |
|
871 |
|
872 |
|
873 |
|
874 |
|
875 | Script.buildPublicKeyHashOut = function(to) {
|
876 | $.checkArgument(!_.isUndefined(to));
|
877 | $.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to));
|
878 | if (to instanceof PublicKey) {
|
879 | to = to.toAddress();
|
880 | } else if (_.isString(to)) {
|
881 | to = new Address(to);
|
882 | }
|
883 | var s = new Script();
|
884 | s.add(Opcode.OP_DUP)
|
885 | .add(Opcode.OP_HASH160)
|
886 | .add(to.hashBuffer)
|
887 | .add(Opcode.OP_EQUALVERIFY)
|
888 | .add(Opcode.OP_CHECKSIG);
|
889 | s._network = to.network;
|
890 | return s;
|
891 | };
|
892 |
|
893 |
|
894 |
|
895 |
|
896 |
|
897 |
|
898 | Script.buildWitnessV0Out = function(to) {
|
899 | $.checkArgument(!_.isUndefined(to));
|
900 | $.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to));
|
901 | if (to instanceof PublicKey) {
|
902 | to = to.toAddress(null, Address.PayToWitnessPublicKeyHash);
|
903 | } else if (_.isString(to)) {
|
904 | to = new Address(to);
|
905 | }
|
906 | var s = new Script();
|
907 | s.add(Opcode.OP_0)
|
908 | .add(to.hashBuffer);
|
909 | s._network = to.network;
|
910 | return s;
|
911 | };
|
912 |
|
913 |
|
914 |
|
915 |
|
916 |
|
917 | Script.buildPublicKeyOut = function(pubkey) {
|
918 | $.checkArgument(pubkey instanceof PublicKey);
|
919 | var s = new Script();
|
920 | s.add(pubkey.toBuffer())
|
921 | .add(Opcode.OP_CHECKSIG);
|
922 | return s;
|
923 | };
|
924 |
|
925 |
|
926 |
|
927 |
|
928 |
|
929 |
|
930 | Script.buildDataOut = function(data, encoding) {
|
931 | $.checkArgument(_.isUndefined(data) || _.isString(data) || BufferUtil.isBuffer(data));
|
932 | if (_.isString(data)) {
|
933 | data = Buffer.from(data, encoding);
|
934 | }
|
935 | var s = new Script();
|
936 | s.add(Opcode.OP_RETURN);
|
937 | if (!_.isUndefined(data)) {
|
938 | s.add(data);
|
939 | }
|
940 | return s;
|
941 | };
|
942 |
|
943 |
|
944 |
|
945 |
|
946 |
|
947 |
|
948 | Script.buildScriptHashOut = function(script) {
|
949 | $.checkArgument(script instanceof Script ||
|
950 | (script instanceof Address && script.isPayToScriptHash()));
|
951 | var s = new Script();
|
952 | s.add(Opcode.OP_HASH160)
|
953 | .add(script instanceof Address ? script.hashBuffer : Hash.sha256ripemd160(script.toBuffer()))
|
954 | .add(Opcode.OP_EQUAL);
|
955 |
|
956 | s._network = script._network || script.network;
|
957 | return s;
|
958 | };
|
959 |
|
960 |
|
961 |
|
962 |
|
963 |
|
964 |
|
965 |
|
966 | Script.buildPublicKeyIn = function(signature, sigtype) {
|
967 | $.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature));
|
968 | $.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype));
|
969 | if (signature instanceof Signature) {
|
970 | signature = signature.toBuffer();
|
971 | }
|
972 | var script = new Script();
|
973 | script.add(BufferUtil.concat([
|
974 | signature,
|
975 | BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL)
|
976 | ]));
|
977 | return script;
|
978 | };
|
979 |
|
980 |
|
981 |
|
982 |
|
983 |
|
984 |
|
985 |
|
986 |
|
987 |
|
988 | Script.buildPublicKeyHashIn = function(publicKey, signature, sigtype) {
|
989 | $.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature));
|
990 | $.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype));
|
991 | if (signature instanceof Signature) {
|
992 | signature = signature.toBuffer();
|
993 | }
|
994 | var script = new Script()
|
995 | .add(BufferUtil.concat([
|
996 | signature,
|
997 | BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL)
|
998 | ]))
|
999 | .add(new PublicKey(publicKey).toBuffer());
|
1000 | return script;
|
1001 | };
|
1002 |
|
1003 |
|
1004 |
|
1005 |
|
1006 | Script.empty = function() {
|
1007 | return new Script();
|
1008 | };
|
1009 |
|
1010 |
|
1011 |
|
1012 |
|
1013 | Script.prototype.toScriptHashOut = function() {
|
1014 | return Script.buildScriptHashOut(this);
|
1015 | };
|
1016 |
|
1017 |
|
1018 |
|
1019 |
|
1020 | Script.fromAddress = function(address) {
|
1021 | address = Address(address);
|
1022 | if (address.isPayToScriptHash()) {
|
1023 | return Script.buildScriptHashOut(address);
|
1024 | } else if (address.isPayToPublicKeyHash()) {
|
1025 | return Script.buildPublicKeyHashOut(address);
|
1026 | } else if (address.isPayToWitnessPublicKeyHash()) {
|
1027 | return Script.buildWitnessV0Out(address);
|
1028 | } else if (address.isPayToWitnessScriptHash()) {
|
1029 | return Script.buildWitnessV0Out(address);
|
1030 | }
|
1031 | throw new errors.Script.UnrecognizedAddress(address);
|
1032 | };
|
1033 |
|
1034 |
|
1035 |
|
1036 |
|
1037 |
|
1038 | Script.prototype.getAddressInfo = function(opts) {
|
1039 | if (this._isInput) {
|
1040 | return this._getInputAddressInfo();
|
1041 | } else if (this._isOutput) {
|
1042 | return this._getOutputAddressInfo();
|
1043 | } else {
|
1044 | var info = this._getOutputAddressInfo();
|
1045 | if (!info) {
|
1046 | return this._getInputAddressInfo();
|
1047 | }
|
1048 | return info;
|
1049 | }
|
1050 | };
|
1051 |
|
1052 |
|
1053 |
|
1054 |
|
1055 |
|
1056 |
|
1057 | Script.prototype._getOutputAddressInfo = function() {
|
1058 | var info = {};
|
1059 | if (this.isScriptHashOut()) {
|
1060 | info.hashBuffer = this.getData();
|
1061 | info.type = Address.PayToScriptHash;
|
1062 | } else if (this.isPublicKeyHashOut()) {
|
1063 | info.hashBuffer = this.getData();
|
1064 | info.type = Address.PayToPublicKeyHash;
|
1065 | } else if (this.isWitnessScriptHashOut()) {
|
1066 | info.hashBuffer = this.getData();
|
1067 | info.type = Address.PayToWitnessScriptHash;
|
1068 | } else if (this.isWitnessPublicKeyHashOut()) {
|
1069 | info.hashBuffer = this.getData();
|
1070 | info.type = Address.PayToWitnessPublicKeyHash;
|
1071 | } else if (this.isTaproot()) {
|
1072 | info.hashBuffer = this.getData();
|
1073 | info.type = Address.PayToTaproot;
|
1074 | } else {
|
1075 | return false;
|
1076 | }
|
1077 | return info;
|
1078 | };
|
1079 |
|
1080 |
|
1081 |
|
1082 |
|
1083 |
|
1084 |
|
1085 | Script.prototype._getInputAddressInfo = function() {
|
1086 | var info = {};
|
1087 | if (this.isPublicKeyHashIn()) {
|
1088 |
|
1089 | info.hashBuffer = Hash.sha256ripemd160(this.chunks[1].buf);
|
1090 | info.type = Address.PayToPublicKeyHash;
|
1091 | } else if (this.isScriptHashIn()) {
|
1092 |
|
1093 | info.hashBuffer = Hash.sha256ripemd160(this.chunks[this.chunks.length - 1].buf);
|
1094 | info.type = Address.PayToScriptHash;
|
1095 | } else {
|
1096 | return false;
|
1097 | }
|
1098 | return info;
|
1099 | };
|
1100 |
|
1101 |
|
1102 |
|
1103 |
|
1104 |
|
1105 | Script.prototype.toAddress = function(network) {
|
1106 | var info = this.getAddressInfo();
|
1107 | if (!info) {
|
1108 | return false;
|
1109 | }
|
1110 | info.network = Networks.get(network) || this._network || Networks.defaultNetwork;
|
1111 | return new Address(info);
|
1112 | };
|
1113 |
|
1114 |
|
1115 |
|
1116 |
|
1117 |
|
1118 |
|
1119 |
|
1120 |
|
1121 |
|
1122 | Script.prototype.findAndDelete = function(script) {
|
1123 | var buf = script.toBuffer();
|
1124 | var hex = buf.toString('hex');
|
1125 | for (var i = 0; i < this.chunks.length; i++) {
|
1126 | var script2 = Script({
|
1127 | chunks: [this.chunks[i]]
|
1128 | });
|
1129 | var buf2 = script2.toBuffer();
|
1130 | var hex2 = buf2.toString('hex');
|
1131 | if (hex === hex2) {
|
1132 | this.chunks.splice(i, 1);
|
1133 | }
|
1134 | }
|
1135 | return this;
|
1136 | };
|
1137 |
|
1138 |
|
1139 |
|
1140 |
|
1141 |
|
1142 | Script.prototype.checkMinimalPush = function(i) {
|
1143 | var chunk = this.chunks[i];
|
1144 | var buf = chunk.buf;
|
1145 | var opcodenum = chunk.opcodenum;
|
1146 | if (!buf) {
|
1147 | return true;
|
1148 | }
|
1149 | if (buf.length === 0) {
|
1150 |
|
1151 | return opcodenum === Opcode.OP_0;
|
1152 | } else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) {
|
1153 |
|
1154 | return opcodenum === Opcode.OP_1 + (buf[0] - 1);
|
1155 | } else if (buf.length === 1 && buf[0] === 0x81) {
|
1156 |
|
1157 | return opcodenum === Opcode.OP_1NEGATE;
|
1158 | } else if (buf.length <= 75) {
|
1159 |
|
1160 | return opcodenum === buf.length;
|
1161 | } else if (buf.length <= 255) {
|
1162 |
|
1163 | return opcodenum === Opcode.OP_PUSHDATA1;
|
1164 | } else if (buf.length <= 65535) {
|
1165 |
|
1166 | return opcodenum === Opcode.OP_PUSHDATA2;
|
1167 | }
|
1168 | return true;
|
1169 | };
|
1170 |
|
1171 |
|
1172 |
|
1173 |
|
1174 |
|
1175 |
|
1176 | Script.prototype._decodeOP_N = function(opcode) {
|
1177 | if (opcode === Opcode.OP_0) {
|
1178 | return 0;
|
1179 | } else if (opcode >= Opcode.OP_1 && opcode <= Opcode.OP_16) {
|
1180 | return opcode - (Opcode.OP_1 - 1);
|
1181 | } else {
|
1182 | throw new Error('Invalid opcode: ' + JSON.stringify(opcode));
|
1183 | }
|
1184 | };
|
1185 |
|
1186 |
|
1187 |
|
1188 |
|
1189 |
|
1190 |
|
1191 | Script.prototype.getSignatureOperationsCount = function(accurate) {
|
1192 | accurate = (_.isUndefined(accurate) ? true : accurate);
|
1193 | var self = this;
|
1194 | var n = 0;
|
1195 | var lastOpcode = Opcode.OP_INVALIDOPCODE;
|
1196 | _.each(self.chunks, function getChunk(chunk) {
|
1197 | var opcode = chunk.opcodenum;
|
1198 | if (opcode == Opcode.OP_CHECKSIG || opcode == Opcode.OP_CHECKSIGVERIFY) {
|
1199 | n++;
|
1200 | } else if (opcode == Opcode.OP_CHECKMULTISIG || opcode == Opcode.OP_CHECKMULTISIGVERIFY) {
|
1201 | if (accurate && lastOpcode >= Opcode.OP_1 && lastOpcode <= Opcode.OP_16) {
|
1202 | n += self._decodeOP_N(lastOpcode);
|
1203 | } else {
|
1204 | n += 20;
|
1205 | }
|
1206 | }
|
1207 | lastOpcode = opcode;
|
1208 | });
|
1209 | return n;
|
1210 | };
|
1211 |
|
1212 | module.exports = Script;
|