Class: BTC::ScriptInterpreter
- Inherits:
-
Object
- Object
- BTC::ScriptInterpreter
- Includes:
- Opcodes, ScriptErrors, ScriptFlags
- Defined in:
- lib/btcruby/script/script_interpreter.rb
Overview
Script is a stack machine (like Forth) that evaluates a predicate returning a bool indicating valid or not. There are no loops.
Constant Summary
Constants included from Opcodes
Opcodes::OPCODE_NAME_TO_VALUE, Opcodes::OPCODE_VALUE_TO_NAME, Opcodes::OP_0, Opcodes::OP_0NOTEQUAL, Opcodes::OP_1, Opcodes::OP_10, Opcodes::OP_11, Opcodes::OP_12, Opcodes::OP_13, Opcodes::OP_14, Opcodes::OP_15, Opcodes::OP_16, Opcodes::OP_1ADD, Opcodes::OP_1NEGATE, Opcodes::OP_1SUB, Opcodes::OP_2, Opcodes::OP_2DIV, Opcodes::OP_2DROP, Opcodes::OP_2DUP, Opcodes::OP_2MUL, Opcodes::OP_2OVER, Opcodes::OP_2ROT, Opcodes::OP_2SWAP, Opcodes::OP_3, Opcodes::OP_3DUP, Opcodes::OP_4, Opcodes::OP_5, Opcodes::OP_6, Opcodes::OP_7, Opcodes::OP_8, Opcodes::OP_9, Opcodes::OP_ABS, Opcodes::OP_ADD, Opcodes::OP_AND, Opcodes::OP_BOOLAND, Opcodes::OP_BOOLOR, Opcodes::OP_CAT, Opcodes::OP_CHECKLOCKTIMEVERIFY, Opcodes::OP_CHECKMULTISIG, Opcodes::OP_CHECKMULTISIGVERIFY, Opcodes::OP_CHECKSIG, Opcodes::OP_CHECKSIGVERIFY, Opcodes::OP_CODESEPARATOR, Opcodes::OP_DEPTH, Opcodes::OP_DIV, Opcodes::OP_DROP, Opcodes::OP_DUP, Opcodes::OP_ELSE, Opcodes::OP_ENDIF, Opcodes::OP_EQUAL, Opcodes::OP_EQUALVERIFY, Opcodes::OP_FALSE, Opcodes::OP_FROMALTSTACK, Opcodes::OP_GREATERTHAN, Opcodes::OP_GREATERTHANOREQUAL, Opcodes::OP_HASH160, Opcodes::OP_HASH256, Opcodes::OP_IF, Opcodes::OP_IFDUP, Opcodes::OP_INVALIDOPCODE, Opcodes::OP_INVERT, Opcodes::OP_LEFT, Opcodes::OP_LESSTHAN, Opcodes::OP_LESSTHANOREQUAL, Opcodes::OP_LSHIFT, Opcodes::OP_MAX, Opcodes::OP_MIN, Opcodes::OP_MOD, Opcodes::OP_MUL, Opcodes::OP_NEGATE, Opcodes::OP_NIP, Opcodes::OP_NOP, Opcodes::OP_NOP1, Opcodes::OP_NOP10, Opcodes::OP_NOP2, Opcodes::OP_NOP3, Opcodes::OP_NOP4, Opcodes::OP_NOP5, Opcodes::OP_NOP6, Opcodes::OP_NOP7, Opcodes::OP_NOP8, Opcodes::OP_NOP9, Opcodes::OP_NOT, Opcodes::OP_NOTIF, Opcodes::OP_NUMEQUAL, Opcodes::OP_NUMEQUALVERIFY, Opcodes::OP_NUMNOTEQUAL, Opcodes::OP_OR, Opcodes::OP_OVER, Opcodes::OP_PICK, Opcodes::OP_PUSHDATA1, Opcodes::OP_PUSHDATA2, Opcodes::OP_PUSHDATA4, Opcodes::OP_RESERVED, Opcodes::OP_RESERVED1, Opcodes::OP_RESERVED2, Opcodes::OP_RETURN, Opcodes::OP_RIGHT, Opcodes::OP_RIPEMD160, Opcodes::OP_ROLL, Opcodes::OP_ROT, Opcodes::OP_RSHIFT, Opcodes::OP_SHA1, Opcodes::OP_SHA256, Opcodes::OP_SIZE, Opcodes::OP_SUB, Opcodes::OP_SUBSTR, Opcodes::OP_SWAP, Opcodes::OP_TOALTSTACK, Opcodes::OP_TRUE, Opcodes::OP_TUCK, Opcodes::OP_VER, Opcodes::OP_VERIF, Opcodes::OP_VERIFY, Opcodes::OP_VERNOTIF, Opcodes::OP_WITHIN, Opcodes::OP_XOR
Constants included from ScriptErrors
BTC::ScriptErrors::SCRIPT_ERR_BAD_OPCODE, BTC::ScriptErrors::SCRIPT_ERR_CHECKMULTISIGVERIFY, BTC::ScriptErrors::SCRIPT_ERR_CHECKSIGVERIFY, BTC::ScriptErrors::SCRIPT_ERR_CLEANSTACK, BTC::ScriptErrors::SCRIPT_ERR_DISABLED_OPCODE, BTC::ScriptErrors::SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, BTC::ScriptErrors::SCRIPT_ERR_EQUALVERIFY, BTC::ScriptErrors::SCRIPT_ERR_EVAL_FALSE, BTC::ScriptErrors::SCRIPT_ERR_INVALID_ALTSTACK_OPERATION, BTC::ScriptErrors::SCRIPT_ERR_INVALID_STACK_OPERATION, BTC::ScriptErrors::SCRIPT_ERR_MINIMALDATA, BTC::ScriptErrors::SCRIPT_ERR_NEGATIVE_LOCKTIME, BTC::ScriptErrors::SCRIPT_ERR_NUMEQUALVERIFY, BTC::ScriptErrors::SCRIPT_ERR_OK, BTC::ScriptErrors::SCRIPT_ERR_OP_COUNT, BTC::ScriptErrors::SCRIPT_ERR_OP_RETURN, BTC::ScriptErrors::SCRIPT_ERR_PUBKEYTYPE, BTC::ScriptErrors::SCRIPT_ERR_PUBKEY_COUNT, BTC::ScriptErrors::SCRIPT_ERR_PUSH_SIZE, BTC::ScriptErrors::SCRIPT_ERR_SCRIPT_SIZE, BTC::ScriptErrors::SCRIPT_ERR_SIG_COUNT, BTC::ScriptErrors::SCRIPT_ERR_SIG_DER, BTC::ScriptErrors::SCRIPT_ERR_SIG_HASHTYPE, BTC::ScriptErrors::SCRIPT_ERR_SIG_HIGH_S, BTC::ScriptErrors::SCRIPT_ERR_SIG_NULLDUMMY, BTC::ScriptErrors::SCRIPT_ERR_SIG_PUSHONLY, BTC::ScriptErrors::SCRIPT_ERR_STACK_SIZE, BTC::ScriptErrors::SCRIPT_ERR_UNBALANCED_CONDITIONAL, BTC::ScriptErrors::SCRIPT_ERR_UNKNOWN_ERROR, BTC::ScriptErrors::SCRIPT_ERR_UNSATISFIED_LOCKTIME, BTC::ScriptErrors::SCRIPT_ERR_VERIFY
Constants included from ScriptFlags
BTC::ScriptFlags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, BTC::ScriptFlags::SCRIPT_VERIFY_CLEANSTACK, BTC::ScriptFlags::SCRIPT_VERIFY_DERSIG, BTC::ScriptFlags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS, BTC::ScriptFlags::SCRIPT_VERIFY_LOW_S, BTC::ScriptFlags::SCRIPT_VERIFY_MINIMALDATA, BTC::ScriptFlags::SCRIPT_VERIFY_NONE, BTC::ScriptFlags::SCRIPT_VERIFY_NULLDUMMY, BTC::ScriptFlags::SCRIPT_VERIFY_P2SH, BTC::ScriptFlags::SCRIPT_VERIFY_SIGPUSHONLY, BTC::ScriptFlags::SCRIPT_VERIFY_STRICTENC
Instance Attribute Summary collapse
-
#altstack ⇒ Object
readonly
Returns the value of attribute altstack.
-
#error ⇒ Object
ScriptError instance.
-
#extensions ⇒ Object
Returns the value of attribute extensions.
-
#flags ⇒ Object
Flags specified for this interpreter, not including flags added by extensions.
-
#integer_max_size ⇒ Object
Returns the value of attribute integer_max_size.
-
#locktime_max_size ⇒ Object
Returns the value of attribute locktime_max_size.
-
#max_op_count ⇒ Object
Returns the value of attribute max_op_count.
-
#max_pushdata_size ⇒ Object
Returns the value of attribute max_pushdata_size.
-
#max_script_size ⇒ Object
Returns the value of attribute max_script_size.
-
#max_stack_size ⇒ Object
Returns the value of attribute max_stack_size.
-
#raise_on_failure ⇒ Object
Returns the value of attribute raise_on_failure.
-
#signature_checker ⇒ Object
Returns the value of attribute signature_checker.
-
#stack ⇒ Object
Execution state.
Instance Method Summary collapse
- #all_flags ⇒ Object
- #cast_to_bool(data) ⇒ Object
- #cast_to_number(data, require_minimal: flag?(SCRIPT_VERIFY_MINIMALDATA), max_size: @integer_max_size) ⇒ Object
-
#check_minimal_push(data, opcode) ⇒ Object
aka CheckMinimalPush(const valtype& data, opcodetype opcode).
- #check_pubkey_encoding(pubkey) ⇒ Object
- #check_signature_encoding(sig) ⇒ Object
- #compressed_or_uncompressed_pubkey(pubkey) ⇒ Object
- #defined_hashtype_signature(sig) ⇒ Object
-
#flag?(flags) ⇒ Boolean
If multiple flags are provided, returns true if any of them are present.
-
#initialize(flags: SCRIPT_VERIFY_NONE, extensions: nil, signature_checker: nil, raise_on_failure: false, max_pushdata_size: MAX_SCRIPT_ELEMENT_SIZE, max_op_count: MAX_OPS_PER_SCRIPT, max_stack_size: MAX_STACK_SIZE, max_script_size: MAX_SCRIPT_SIZE, integer_max_size: 4, locktime_max_size: 5) ⇒ ScriptInterpreter
constructor
Instantiates interpreter with validation flags and an optional checker (required if the scripts use signature-checking opcodes).
- #low_der_signature(sig) ⇒ Object
-
#run_script(script) ⇒ Object
Returns true if succeeded or false in case of failure.
- #set_error(code, message = nil) ⇒ Object
- #stack_pop ⇒ Object
- #stack_push(x) ⇒ Object
- #valid_signature_encoding(sig) ⇒ Object
-
#verify_script(signature_script: nil, output_script: nil) ⇒ Object
Returns true if succeeded or false in case of failure.
Constructor Details
#initialize(flags: SCRIPT_VERIFY_NONE, extensions: nil, signature_checker: nil, raise_on_failure: false, max_pushdata_size: MAX_SCRIPT_ELEMENT_SIZE, max_op_count: MAX_OPS_PER_SCRIPT, max_stack_size: MAX_STACK_SIZE, max_script_size: MAX_SCRIPT_SIZE, integer_max_size: 4, locktime_max_size: 5) ⇒ ScriptInterpreter
Instantiates interpreter with validation flags and an optional checker (required if the scripts use signature-checking opcodes). Checker can be transaction checker or block checker
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/btcruby/script/script_interpreter.rb', line 38 def initialize(flags: SCRIPT_VERIFY_NONE, extensions: nil, signature_checker: nil, raise_on_failure: false, max_pushdata_size: MAX_SCRIPT_ELEMENT_SIZE, max_op_count: MAX_OPS_PER_SCRIPT, max_stack_size: MAX_STACK_SIZE, max_script_size: MAX_SCRIPT_SIZE, integer_max_size: 4, locktime_max_size: 5) @flags = flags @extensions = extensions || [] @signature_checker = signature_checker @raise_on_failure = raise_on_failure @max_pushdata_size = max_pushdata_size @max_op_count = max_op_count @max_stack_size = max_stack_size @max_script_size = max_script_size @integer_max_size = integer_max_size @locktime_max_size = locktime_max_size end |
Instance Attribute Details
#altstack ⇒ Object (readonly)
Returns the value of attribute altstack.
32 33 34 |
# File 'lib/btcruby/script/script_interpreter.rb', line 32 def altstack @altstack end |
#error ⇒ Object
ScriptError instance
33 34 35 |
# File 'lib/btcruby/script/script_interpreter.rb', line 33 def error @error end |
#extensions ⇒ Object
Returns the value of attribute extensions.
20 21 22 |
# File 'lib/btcruby/script/script_interpreter.rb', line 20 def extensions @extensions end |
#flags ⇒ Object
Flags specified for this interpreter, not including flags added by extensions.
19 20 21 |
# File 'lib/btcruby/script/script_interpreter.rb', line 19 def flags @flags end |
#integer_max_size ⇒ Object
Returns the value of attribute integer_max_size.
27 28 29 |
# File 'lib/btcruby/script/script_interpreter.rb', line 27 def integer_max_size @integer_max_size end |
#locktime_max_size ⇒ Object
Returns the value of attribute locktime_max_size.
28 29 30 |
# File 'lib/btcruby/script/script_interpreter.rb', line 28 def locktime_max_size @locktime_max_size end |
#max_op_count ⇒ Object
Returns the value of attribute max_op_count.
24 25 26 |
# File 'lib/btcruby/script/script_interpreter.rb', line 24 def max_op_count @max_op_count end |
#max_pushdata_size ⇒ Object
Returns the value of attribute max_pushdata_size.
23 24 25 |
# File 'lib/btcruby/script/script_interpreter.rb', line 23 def max_pushdata_size @max_pushdata_size end |
#max_script_size ⇒ Object
Returns the value of attribute max_script_size.
26 27 28 |
# File 'lib/btcruby/script/script_interpreter.rb', line 26 def max_script_size @max_script_size end |
#max_stack_size ⇒ Object
Returns the value of attribute max_stack_size.
25 26 27 |
# File 'lib/btcruby/script/script_interpreter.rb', line 25 def max_stack_size @max_stack_size end |
#raise_on_failure ⇒ Object
Returns the value of attribute raise_on_failure.
22 23 24 |
# File 'lib/btcruby/script/script_interpreter.rb', line 22 def raise_on_failure @raise_on_failure end |
#signature_checker ⇒ Object
Returns the value of attribute signature_checker.
21 22 23 |
# File 'lib/btcruby/script/script_interpreter.rb', line 21 def signature_checker @signature_checker end |
#stack ⇒ Object
Execution state
31 32 33 |
# File 'lib/btcruby/script/script_interpreter.rb', line 31 def stack @stack end |
Instance Method Details
#all_flags ⇒ Object
898 899 900 901 902 |
# File 'lib/btcruby/script/script_interpreter.rb', line 898 def all_flags @extensions.inject(@flags) do |f, p| f | p.extra_flags end end |
#cast_to_bool(data) ⇒ Object
910 911 912 913 914 915 916 917 918 919 920 921 |
# File 'lib/btcruby/script/script_interpreter.rb', line 910 def cast_to_bool(data) data.bytes.each_with_index do |byte, i| if byte != 0 # Can be negative zero if byte == 0x80 && i == (data.bytesize - 1) return false end return true end end return false end |
#cast_to_number(data, require_minimal: flag?(SCRIPT_VERIFY_MINIMALDATA), max_size: @integer_max_size) ⇒ Object
904 905 906 907 908 |
# File 'lib/btcruby/script/script_interpreter.rb', line 904 def cast_to_number(data, require_minimal: flag?(SCRIPT_VERIFY_MINIMALDATA), max_size: @integer_max_size) ScriptNumber.new(data: data, require_minimal: require_minimal, max_size: max_size) end |
#check_minimal_push(data, opcode) ⇒ Object
aka CheckMinimalPush(const valtype& data, opcodetype opcode)
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 |
# File 'lib/btcruby/script/script_interpreter.rb', line 831 def check_minimal_push(data, opcode) if data.bytesize == 0 # Could have used OP_0. return opcode == OP_0 elsif data.bytesize == 1 && data.bytes[0] >= 1 && data.bytes[0] <= 16 # Could have used OP_1 .. OP_16. return opcode == OP_1 + (data.bytes[0] - 1) elsif data.bytesize == 1 && data.bytes[0] == 0x81 # Could have used OP_1NEGATE. return opcode == OP_1NEGATE elsif data.bytesize <= 75 # Could have used a direct push (opcode indicating number of bytes pushed + those bytes). return opcode == data.bytesize elsif data.bytesize <= 255 # Could have used OP_PUSHDATA. return opcode == OP_PUSHDATA1 elsif (data.bytesize <= 65535) # Could have used OP_PUSHDATA2. return opcode == OP_PUSHDATA2 end return true end |
#check_pubkey_encoding(pubkey) ⇒ Object
870 871 872 873 874 875 |
# File 'lib/btcruby/script/script_interpreter.rb', line 870 def check_pubkey_encoding(pubkey) if flag?(SCRIPT_VERIFY_STRICTENC) && !compressed_or_uncompressed_pubkey(pubkey) return set_error(SCRIPT_ERR_PUBKEYTYPE) end return true end |
#check_signature_encoding(sig) ⇒ Object
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 |
# File 'lib/btcruby/script/script_interpreter.rb', line 854 def check_signature_encoding(sig) # Empty signature. Not strictly DER encoded, but allowed to provide a # compact way to provide an invalid signature for use with CHECK(MULTI)SIG if sig.size == 0 return true end if flag?(SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC) && !valid_signature_encoding(sig) return set_error(SCRIPT_ERR_SIG_DER) elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature(sig) return set_error(SCRIPT_ERR_SIG_HIGH_S) elsif flag?(SCRIPT_VERIFY_STRICTENC) && !defined_hashtype_signature(sig) return set_error(SCRIPT_ERR_SIG_HASHTYPE) end return true end |
#compressed_or_uncompressed_pubkey(pubkey) ⇒ Object
889 890 891 |
# File 'lib/btcruby/script/script_interpreter.rb', line 889 def compressed_or_uncompressed_pubkey(pubkey) BTC::Key.validate_public_key(pubkey) end |
#defined_hashtype_signature(sig) ⇒ Object
885 886 887 |
# File 'lib/btcruby/script/script_interpreter.rb', line 885 def defined_hashtype_signature(sig) BTC::Key.validate_script_signature(sig, verify_lower_s: false, verify_hashtype: true) end |
#flag?(flags) ⇒ Boolean
If multiple flags are provided, returns true if any of them are present
894 895 896 |
# File 'lib/btcruby/script/script_interpreter.rb', line 894 def flag?(flags) (all_flags & flags) != 0 end |
#low_der_signature(sig) ⇒ Object
881 882 883 |
# File 'lib/btcruby/script/script_interpreter.rb', line 881 def low_der_signature(sig) BTC::Key.validate_script_signature(sig, verify_lower_s: true, verify_hashtype: false) end |
#run_script(script) ⇒ Object
Returns true if succeeded or false in case of failure. If fails, sets the error attribute. Used internally in ‘verify_script` and also in unit tests.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 |
# File 'lib/btcruby/script/script_interpreter.rb', line 137 def run_script(script) if script.data.bytesize > @max_script_size return set_error(SCRIPT_ERR_SCRIPT_SIZE) end # Altstack is not shared between individual runs, but we still store it in ivar to make available to the extensions. @altstack = [] number_zero = ScriptNumber.new(integer: 0) number_one = ScriptNumber.new(integer: 1) zero_value = "".b false_value = "".b true_value = "\x01".b opcount = 0 require_minimal = flag?(SCRIPT_VERIFY_MINIMALDATA) condition_flags = [] index_after_codeseparator = 0 script.chunks.each_with_index do |chunk, chunk_index| opcode = chunk.opcode should_execute = !condition_flags.include?(false) if chunk.pushdata? && chunk.pushdata.bytesize > @max_pushdata_size return set_error(SCRIPT_ERR_PUSH_SIZE) end # Note how OP_RESERVED does not count towards the opcode limit. if opcode > OP_16 if (opcount += 1) > @max_op_count return set_error(SCRIPT_ERR_OP_COUNT) end end # Check if there is a extension for this opcode before we check for disabled opcodes. opcode_extension = extension_to_handle_opcode(opcode) if !opcode_extension if opcode == OP_CAT || opcode == OP_SUBSTR || opcode == OP_LEFT || opcode == OP_RIGHT || opcode == OP_INVERT || opcode == OP_AND || opcode == OP_OR || opcode == OP_XOR || opcode == OP_2MUL || opcode == OP_2DIV || opcode == OP_MUL || opcode == OP_DIV || opcode == OP_MOD || opcode == OP_LSHIFT || opcode == OP_RSHIFT return set_error(SCRIPT_ERR_DISABLED_OPCODE) end end if should_execute && 0 <= opcode && opcode <= OP_PUSHDATA4 # Pushdata (including OP_0). if require_minimal && !check_minimal_push(chunk.pushdata, opcode) return set_error(SCRIPT_ERR_MINIMALDATA) end stack_push(chunk.pushdata) elsif should_execute && opcode_extension if !opcode_extension.handle_opcode(interpreter: self, opcode: opcode) # error is set already return false end elsif should_execute || (OP_IF <= opcode && opcode <= OP_ENDIF) case opcode when OP_1NEGATE, OP_1..OP_16 # ( -- value) num = ScriptNumber.new(integer: opcode - (OP_1 - 1)) stack_push(num.data) # The result of these opcodes should always be the minimal way to push the data # they push, so no need for a CheckMinimalPush here. # Control Operators # ----------------- when OP_NOP # nothing # See CLTVExtension # when OP_CHECKLOCKTIMEVERIFY # begin # if !flag?(SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) # # not enabled; treat as a NOP2 # if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) # return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS) # end # break # breaks out of begin ... end while false # end # # if @stack.size < 1 # return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) # end # # # Note that elsewhere numeric opcodes are limited to # # operands in the range -2**31+1 to 2**31-1, however it is # # legal for opcodes to produce results exceeding that # # range. This limitation is implemented by CScriptNum's # # default 4-byte limit. # # # # If we kept to that limit we'd have a year 2038 problem, # # even though the nLockTime field in transactions # # themselves is uint32 which only becomes meaningless # # after the year 2106. # # # # Thus as a special case we tell CScriptNum to accept up # # to 5-byte bignums, which are good until 2**39-1, well # # beyond the 2**32-1 limit of the nLockTime field itself. # locktime = cast_to_number(@stack.last, max_size: @locktime_max_size) # # # In the rare event that the argument may be < 0 due to # # some arithmetic being done first, you can always use # # 0 MAX CHECKLOCKTIMEVERIFY. # if locktime < 0 # return set_error(SCRIPT_ERR_NEGATIVE_LOCKTIME) # end # # # Actually compare the specified lock time with the transaction. # if !signature_checker.check_lock_time(locktime) # return set_error(SCRIPT_ERR_UNSATISFIED_LOCKTIME) # end # end while false when OP_NOP1..OP_NOP10 if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS) end when OP_IF, OP_NOTIF # <expression> if [statements] [else [statements]] endif flag = false if should_execute if @stack.size < 1 return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) end flag = cast_to_bool(@stack.last) if opcode == OP_NOTIF flag = !flag end stack_pop end condition_flags.push(flag) when OP_ELSE if condition_flags.empty? return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) end condition_flags[-1] = !condition_flags.last when OP_ENDIF if condition_flags.empty? return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) end condition_flags.pop when OP_VERIFY # (true -- ) or # (false -- false) and return if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end flag = cast_to_bool(@stack.last) if flag stack_pop else return set_error(SCRIPT_ERR_VERIFY) end when OP_RETURN return set_error(SCRIPT_ERR_OP_RETURN) # Stack Operations # ---------------- when OP_TOALTSTACK if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end @altstack.push(stack_pop) when OP_FROMALTSTACK if @altstack.size < 1 return set_error(SCRIPT_ERR_INVALID_ALTSTACK_OPERATION) end stack_push(@altstack.pop) when OP_2DROP # (x1 x2 -- ) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end stack_pop stack_pop when OP_2DUP # (x1 x2 -- x1 x2 x1 x2) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end a = @stack[-2] b = @stack[-1] stack_push(a) stack_push(b) when OP_3DUP # (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) if @stack.size < 3 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end a = @stack[-3] b = @stack[-2] c = @stack[-1] stack_push(a) stack_push(b) stack_push(c) when OP_2OVER # (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) if @stack.size < 4 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end a = @stack[-4] b = @stack[-3] stack_push(a) stack_push(b) when OP_2ROT # (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) if @stack.size < 6 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end a = @stack[-6] b = @stack[-5] @stack[-6...-4] = [] stack_push(a) stack_push(b) when OP_2SWAP # (x1 x2 x3 x4 -- x3 x4 x1 x2) if @stack.size < 4 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end x1x2 = @stack[-4...-2] @stack[-4...-2] = [] @stack += x1x2 when OP_IFDUP # (x - 0 | x x) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end item = @stack.last if cast_to_bool(item) stack_push(item) end when OP_DEPTH # -- stacksize sn = ScriptNumber.new(integer: @stack.size) stack_push(sn.data) when OP_DROP # (x -- ) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end stack_pop when OP_DUP # (x -- x x) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end stack_push(@stack.last) when OP_NIP # (x1 x2 -- x2) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end @stack.delete_at(-2) when OP_OVER # (x1 x2 -- x1 x2 x1) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end stack_push(@stack[-2]) when OP_PICK, OP_ROLL # (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) # (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end n = cast_to_number(@stack.last).to_i stack_pop if n < 0 || n >= @stack.size return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end item = @stack[-n-1] if opcode == OP_ROLL @stack.delete_at(-n-1) end stack_push(item) when OP_ROT # (x1 x2 x3 -- x2 x3 x1) # x2 x1 x3 after first swap # x2 x3 x1 after second swap if @stack.size < 3 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end x1 = @stack[-3] @stack.delete_at(-3) stack_push(x1) when OP_SWAP # (x1 x2 -- x2 x1) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end x1 = @stack[-2] @stack.delete_at(-2) stack_push(x1) when OP_TUCK # (x1 x2 -- x2 x1 x2) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end item = @stack[-1] @stack.insert(-3, item) # -1 inserts in the end, -2 before the last item. when OP_SIZE # (in -- in size) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end sn = ScriptNumber.new(integer: @stack.last.size) stack_push(sn.data) # Bitwise Logic # ------------- when OP_EQUAL, OP_EQUALVERIFY # there is no OP_NOTEQUAL, use OP_NUMNOTEQUAL instead # (x1 x2 - bool) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end a = @stack[-2] b = @stack[-1] equal = (a == b) # OP_NOTEQUAL is disabled because it would be too easy to say # something like n != 1 and have some wiseguy pass in 1 with extra # zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) # if opcode == OP_NOTEQUAL # equal = !equal # end stack_pop stack_pop stack_push(equal ? true_value : false_value) if opcode == OP_EQUALVERIFY if equal stack_pop else return set_error(SCRIPT_ERR_EQUALVERIFY) end end # Numeric # ------- when OP_1ADD, OP_1SUB, OP_NEGATE, OP_ABS, OP_NOT, OP_0NOTEQUAL # (in -- out) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end bn = cast_to_number(@stack.last) case opcode when OP_1ADD bn += 1 when OP_1SUB bn -= 1 when OP_NEGATE bn = -bn when OP_ABS bn = -bn if bn < 0 when OP_NOT bn = ScriptNumber.new(boolean: (bn == 0)) when OP_0NOTEQUAL bn = ScriptNumber.new(boolean: (bn != 0)) else raise "invalid opcode" end stack_pop stack_push(bn.data); when OP_ADD, OP_SUB, OP_BOOLAND..OP_MAX # (x1 x2 -- out) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end bn1 = cast_to_number(@stack[-2]) bn2 = cast_to_number(@stack[-1]) bn = ScriptNumber.new(integer: 0) case opcode when OP_ADD bn = bn1 + bn2 when OP_SUB bn = bn1 - bn2 when OP_BOOLAND bn = ScriptNumber.new(boolean: ((bn1 != 0) && (bn2 != 0))) when OP_BOOLOR bn = ScriptNumber.new(boolean: ((bn1 != 0) || (bn2 != 0))) when OP_NUMEQUAL, OP_NUMEQUALVERIFY bn = ScriptNumber.new(boolean: (bn1 == bn2)) when OP_NUMNOTEQUAL bn = ScriptNumber.new(boolean: (bn1 != bn2)) when OP_LESSTHAN bn = ScriptNumber.new(boolean: (bn1 < bn2)) when OP_GREATERTHAN bn = ScriptNumber.new(boolean: (bn1 > bn2)) when OP_LESSTHANOREQUAL bn = ScriptNumber.new(boolean: (bn1 <= bn2)) when OP_GREATERTHANOREQUAL bn = ScriptNumber.new(boolean: (bn1 >= bn2)) when OP_MIN bn = (bn1 < bn2 ? bn1 : bn2) when OP_MAX bn = (bn1 > bn2 ? bn1 : bn2) else raise "Invalid opcode" end stack_pop stack_pop stack_push(bn.data) if opcode == OP_NUMEQUALVERIFY if cast_to_bool(@stack[-1]) stack_pop else return set_error(SCRIPT_ERR_NUMEQUALVERIFY) end end when OP_WITHIN # (x min max -- out) if @stack.size < 3 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end bn1 = cast_to_number(@stack[-3]) bn2 = cast_to_number(@stack[-2]) bn3 = cast_to_number(@stack[-1]) flag = ((bn2 <= bn1) && (bn1 < bn3)) stack_pop stack_pop stack_pop stack_push(flag ? true_value : false_value) # Crypto # ------ when OP_RIPEMD160..OP_HASH256 # (in -- hash) if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end item = @stack[-1] hash = case opcode when OP_RIPEMD160 BTC.ripemd160(item) when OP_SHA1 BTC.sha1(item) when OP_SHA256 BTC.sha256(item) when OP_HASH160 BTC.hash160(item) when OP_HASH256 BTC.hash256(item) end stack_pop stack_push(hash) when OP_CODESEPARATOR # Hash starts after the code separator index_after_codeseparator = chunk_index + 1 when OP_CHECKSIG, OP_CHECKSIGVERIFY # (sig pubkey -- bool) if @stack.size < 2 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end sig = @stack[-2] pubkey = @stack[-1] # Subset of script starting at the most recent codeseparator subscript = script.subscript(index_after_codeseparator..-1) # Drop the signature, since there's no way for a signature to sign itself # Consensus-critical code: must replace sig with minimal encoding. subscript = subscript.find_and_delete(BTC::Script.new << sig) if !check_signature_encoding(sig) || !check_pubkey_encoding(pubkey) # error is set already return false end success = signature_checker.check_signature( script_signature: sig, public_key: pubkey, script: subscript ) stack_pop stack_pop stack_push(success ? true_value : false_value) if opcode == OP_CHECKSIGVERIFY if success stack_pop else return set_error(SCRIPT_ERR_CHECKSIGVERIFY) end end when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY # ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) i = 1 if @stack.size < i return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end keys_count = cast_to_number(@stack[-i]).to_i if keys_count < 0 || keys_count > 20 return set_error(SCRIPT_ERR_PUBKEY_COUNT) end opcount += keys_count if opcount > @max_op_count return set_error(SCRIPT_ERR_OP_COUNT) end ikey = (i+=1) i += keys_count if @stack.size < i return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end sigs_count = cast_to_number(@stack[-i]).to_i if sigs_count < 0 || sigs_count > keys_count return set_error(SCRIPT_ERR_SIG_COUNT) end isig = (i+=1) i += sigs_count if @stack.size < i return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end # Subset of script starting at the most recent codeseparator # Subset of script starting at the most recent codeseparator subscript = script.subscript(index_after_codeseparator..-1) # Drop the signatures, since there's no way for a signature to sign itself # Consensus-critical code: must replace sig with minimal encoding. sigs_count.times do |k| sig = @stack[-isig-k] subscript = subscript.find_and_delete(BTC::Script.new << sig) end success = true while success && sigs_count > 0 sig = @stack[-isig] pubkey = @stack[-ikey] # Note how this makes the exact order of pubkey/signature evaluation # distinguishable by CHECKMULTISIG NOT if the STRICTENC flag is set. # See the script_(in)valid tests for details. if !check_signature_encoding(sig) || !check_pubkey_encoding(pubkey) # error is set already return false end # Check signature ok = signature_checker.check_signature( script_signature: sig, public_key: pubkey, script: subscript ) if ok isig += 1 sigs_count -= 1 end ikey += 1 keys_count -= 1 # If there are more signatures left than keys left, # then too many signatures have failed. Exit early, # without checking any further signatures. if sigs_count > keys_count success = false end end # Clean up stack of actual arguments while (i-=1) > 0 # Bitcoin Core: while (i-- > 1) stack_pop end # A bug causes CHECKMULTISIG to consume one extra argument # whose contents were not checked in any way. # # Unfortunately this is a potential source of mutability, # so optionally verify it is exactly equal to zero prior # to removing it from the stack. if @stack.size < 1 return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) end if flag?(SCRIPT_VERIFY_NULLDUMMY) && @stack[-1].size > 0 return set_error(SCRIPT_ERR_SIG_NULLDUMMY) end stack_pop stack_push(success ? true_value : false_value) if opcode == OP_CHECKMULTISIGVERIFY if success stack_pop else return set_error(SCRIPT_ERR_CHECKMULTISIGVERIFY) end end else # unknown opcode return set_error(SCRIPT_ERR_BAD_OPCODE, "Unknown opcode 0x#{opcode.to_s(16)}") end # case opcode end # within IF scope # Size limits if @stack.size + @altstack.size > @max_stack_size return set_error(SCRIPT_ERR_STACK_SIZE) end end # each chunk if !condition_flags.empty? return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) end return true rescue ScriptNumberError => e return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.) end |
#set_error(code, message = nil) ⇒ Object
923 924 925 926 927 928 |
# File 'lib/btcruby/script/script_interpreter.rb', line 923 def set_error(code, = nil) error = ScriptError.new(code, ) raise "#{error.description} (#{code.inspect})" if @raise_on_failure @error = error false end |
#stack_pop ⇒ Object
819 820 821 822 823 |
# File 'lib/btcruby/script/script_interpreter.rb', line 819 def stack_pop r = @stack.pop #puts "POPPED FROM STACK: #{@stack.map{|s|s.to_hex}.join(' ')}" r end |
#stack_push(x) ⇒ Object
825 826 827 828 |
# File 'lib/btcruby/script/script_interpreter.rb', line 825 def stack_push(x) @stack.push(x) #puts "PUSHED TO STACK: #{@stack.map{|s|s.to_hex}.join(' ')}" end |
#valid_signature_encoding(sig) ⇒ Object
877 878 879 |
# File 'lib/btcruby/script/script_interpreter.rb', line 877 def valid_signature_encoding(sig) BTC::Key.validate_script_signature(sig, verify_lower_s: false, verify_hashtype: false) end |
#verify_script(signature_script: nil, output_script: nil) ⇒ Object
Returns true if succeeded or false in case of failure. If fails, sets the error attribute.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/btcruby/script/script_interpreter.rb', line 63 def verify_script(signature_script: nil, output_script: nil) @stack = [] @altstack = [] if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !signature_script.data_only? return set_error(SCRIPT_ERR_SIG_PUSHONLY) end if extension = extension_to_handle_scripts(signature_script, output_script) return extension.handle_scripts( interpreter: self, signature_script: signature_script, output_script: output_script ) && verify_clean_stack_if_needed end if !run_script(signature_script) # error is set in run_script return false end if !did_execute_signature_script(signature_script) # error is set already return false end # This is implemented in P2SHExtension # stack_copy = if flag?(SCRIPT_VERIFY_P2SH) # @stack.dup # end if extension = extension_to_handle_output_script(output_script) return extension.handle_output_script( interpreter: self, output_script: output_script ) && verify_clean_stack_if_needed end if !run_script(output_script) # error is set in run_script return false end if @stack.empty? return set_error(SCRIPT_ERR_EVAL_FALSE) end if cast_to_bool(@stack.last) == false return set_error(SCRIPT_ERR_EVAL_FALSE) end if !did_execute_output_script(output_script) # error is set already return false end # Additional validation for pay-to-script-hash (P2SH) transactions: # See P2SHExtension#did_execute_output_script. return verify_clean_stack_if_needed end |