btcruby 1.1.3 → 1.1.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.
- checksums.yaml +4 -4
- data/RELEASE_NOTES.md +8 -0
- data/lib/btcruby.rb +1 -0
- data/lib/btcruby/script/opcode.rb +163 -156
- data/lib/btcruby/script/script.rb +8 -201
- data/lib/btcruby/script/script_chunk.rb +216 -0
- data/lib/btcruby/version.rb +1 -1
- data/spec/script_spec.rb +7 -1
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 496c31ea05b95ca3fe1971a17eea51f43a2c00b6
         | 
| 4 | 
            +
              data.tar.gz: 6a8cbbe43cd39b004f188f32c90e6c492d7f78d2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 5de016754bc4fdec389d62e4fbce3b3f104a00eb698fb1647c02c7531223cfb41066e3c13eb0a286cb91ba59950493af8edd9c061f427c0317cc424b699b3e43
         | 
| 7 | 
            +
              data.tar.gz: 4514a56b1c78db5e1e3beaf3fc5983b964451bf4a007adeb359d2c16b05e0e4b291ecdf06917b689446f33ec22ef5bf9a36cfd6496d2bc37a955896a01606e23
         | 
    
        data/RELEASE_NOTES.md
    CHANGED
    
    | @@ -2,6 +2,14 @@ | |
| 2 2 | 
             
            BTCRuby Release Notes
         | 
| 3 3 | 
             
            =====================
         | 
| 4 4 |  | 
| 5 | 
            +
            1.1.4 (August 18, 2015)
         | 
| 6 | 
            +
            -----------------------
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            * Public API for `ScriptChunk` instances.
         | 
| 9 | 
            +
            * Added `ScriptChunk#data_only?`.
         | 
| 10 | 
            +
            * Added `ScriptChunk#interpreted_data`.
         | 
| 11 | 
            +
             | 
| 12 | 
            +
             | 
| 5 13 | 
             
            1.1.3 (August 17, 2015)
         | 
| 6 14 | 
             
            -----------------------
         | 
| 7 15 |  | 
    
        data/lib/btcruby.rb
    CHANGED
    
    | @@ -32,6 +32,7 @@ require_relative 'btcruby/validation.rb' | |
| 32 32 | 
             
            require_relative 'btcruby/script/script_error.rb'
         | 
| 33 33 | 
             
            require_relative 'btcruby/script/script_flags.rb'
         | 
| 34 34 | 
             
            require_relative 'btcruby/script/script_number.rb'
         | 
| 35 | 
            +
            require_relative 'btcruby/script/script_chunk.rb'
         | 
| 35 36 | 
             
            require_relative 'btcruby/script/script.rb'
         | 
| 36 37 | 
             
            require_relative 'btcruby/script/script_interpreter.rb'
         | 
| 37 38 | 
             
            require_relative 'btcruby/script/opcode.rb'
         | 
| @@ -38,161 +38,168 @@ module BTC | |
| 38 38 | 
             
                  return nil
         | 
| 39 39 | 
             
                end
         | 
| 40 40 | 
             
              end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
              #  | 
| 43 | 
            -
             | 
| 44 | 
            -
               | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
               | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 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 | 
            -
               | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 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 | 
            -
             | 
| 41 | 
            +
              
         | 
| 42 | 
            +
              # All opcodes are defined in Opcodes module so they can be included in
         | 
| 43 | 
            +
              # a different namespace without the baggage of other BTC identifiers.
         | 
| 44 | 
            +
              module Opcodes
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # 1. Operators pushing data on stack.
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # Push 1 byte 0x00 on the stack
         | 
| 49 | 
            +
                OP_FALSE = 0x00
         | 
| 50 | 
            +
                OP_0     = 0x00
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                # Any opcode with value < PUSHDATA1 is a length of the string to be pushed on the stack.
         | 
| 53 | 
            +
                # So opcode 0x01 is followed by 1 byte of data, 0x09 by 9 bytes and so on up to 0x4b (75 bytes)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # PUSHDATA<N> opcode is followed by N-byte length of the string that follows.
         | 
| 56 | 
            +
                OP_PUSHDATA1 = 0x4c # followed by a 1-byte length of the string to push (allows pushing 0..255 bytes).
         | 
| 57 | 
            +
                OP_PUSHDATA2 = 0x4d # followed by a 2-byte length of the string to push (allows pushing 0..65535 bytes).
         | 
| 58 | 
            +
                OP_PUSHDATA4 = 0x4e # followed by a 4-byte length of the string to push (allows pushing 0..4294967295 bytes).
         | 
| 59 | 
            +
                OP_1NEGATE   = 0x4f # pushes -1 number on the stack
         | 
| 60 | 
            +
                OP_RESERVED  = 0x50 # Not assigned. If executed, transaction is invalid.
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # OP_<N> pushes number <N> on the stack
         | 
| 63 | 
            +
                OP_TRUE = 0x51
         | 
| 64 | 
            +
                OP_1    = 0x51
         | 
| 65 | 
            +
                OP_2    = 0x52
         | 
| 66 | 
            +
                OP_3    = 0x53
         | 
| 67 | 
            +
                OP_4    = 0x54
         | 
| 68 | 
            +
                OP_5    = 0x55
         | 
| 69 | 
            +
                OP_6    = 0x56
         | 
| 70 | 
            +
                OP_7    = 0x57
         | 
| 71 | 
            +
                OP_8    = 0x58
         | 
| 72 | 
            +
                OP_9    = 0x59
         | 
| 73 | 
            +
                OP_10   = 0x5a
         | 
| 74 | 
            +
                OP_11   = 0x5b
         | 
| 75 | 
            +
                OP_12   = 0x5c
         | 
| 76 | 
            +
                OP_13   = 0x5d
         | 
| 77 | 
            +
                OP_14   = 0x5e
         | 
| 78 | 
            +
                OP_15   = 0x5f
         | 
| 79 | 
            +
                OP_16   = 0x60
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # 2. Control flow operators
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                OP_NOP      = 0x61 # Does nothing
         | 
| 84 | 
            +
                OP_VER      = 0x62 # Not assigned. If executed, transaction is invalid.
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # BitcoinQT executes all operators from OP_IF to OP_ENDIF even inside "non-executed" branch (to keep track of nesting).
         | 
| 87 | 
            +
                # Since OP_VERIF and OP_VERNOTIF are not assigned, even inside a non-executed branch they will fall in "default:" switch case
         | 
| 88 | 
            +
                # and cause the script to fail. Some other ops like OP_VER can be present inside non-executed branch because they'll be skipped.
         | 
| 89 | 
            +
                OP_IF       = 0x63 # If the top stack value is not 0, the statements are executed. The top stack value is removed.
         | 
| 90 | 
            +
                OP_NOTIF    = 0x64 # If the top stack value is 0, the statements are executed. The top stack value is removed.
         | 
| 91 | 
            +
                OP_VERIF    = 0x65 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
         | 
| 92 | 
            +
                OP_VERNOTIF = 0x66 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
         | 
| 93 | 
            +
                OP_ELSE     = 0x67 # Executes code if the previous OP_IF or OP_NOTIF was not executed.
         | 
| 94 | 
            +
                OP_ENDIF    = 0x68 # Finishes if/else block
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                OP_VERIFY   = 0x69 # Removes item from the stack if it's not 0x00 or 0x80 (negative zero). Otherwise, marks script as invalid.
         | 
| 97 | 
            +
                OP_RETURN   = 0x6a # Marks transaction as invalid.
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                # Stack ops
         | 
| 100 | 
            +
                OP_TOALTSTACK   = 0x6b # Moves item from the stack to altstack
         | 
| 101 | 
            +
                OP_FROMALTSTACK = 0x6c # Moves item from the altstack to stack
         | 
| 102 | 
            +
                OP_2DROP = 0x6d
         | 
| 103 | 
            +
                OP_2DUP  = 0x6e
         | 
| 104 | 
            +
                OP_3DUP  = 0x6f
         | 
| 105 | 
            +
                OP_2OVER = 0x70
         | 
| 106 | 
            +
                OP_2ROT  = 0x71
         | 
| 107 | 
            +
                OP_2SWAP = 0x72
         | 
| 108 | 
            +
                OP_IFDUP = 0x73
         | 
| 109 | 
            +
                OP_DEPTH = 0x74
         | 
| 110 | 
            +
                OP_DROP  = 0x75
         | 
| 111 | 
            +
                OP_DUP   = 0x76
         | 
| 112 | 
            +
                OP_NIP   = 0x77
         | 
| 113 | 
            +
                OP_OVER  = 0x78
         | 
| 114 | 
            +
                OP_PICK  = 0x79
         | 
| 115 | 
            +
                OP_ROLL  = 0x7a
         | 
| 116 | 
            +
                OP_ROT   = 0x7b
         | 
| 117 | 
            +
                OP_SWAP  = 0x7c
         | 
| 118 | 
            +
                OP_TUCK  = 0x7d
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                # Splice ops
         | 
| 121 | 
            +
                OP_CAT    = 0x7e # Disabled opcode. If executed, transaction is invalid.
         | 
| 122 | 
            +
                OP_SUBSTR = 0x7f # Disabled opcode. If executed, transaction is invalid.
         | 
| 123 | 
            +
                OP_LEFT   = 0x80 # Disabled opcode. If executed, transaction is invalid.
         | 
| 124 | 
            +
                OP_RIGHT  = 0x81 # Disabled opcode. If executed, transaction is invalid.
         | 
| 125 | 
            +
                OP_SIZE   = 0x82
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                # Bit logic
         | 
| 128 | 
            +
                OP_INVERT = 0x83 # Disabled opcode. If executed, transaction is invalid.
         | 
| 129 | 
            +
                OP_AND    = 0x84 # Disabled opcode. If executed, transaction is invalid.
         | 
| 130 | 
            +
                OP_OR     = 0x85 # Disabled opcode. If executed, transaction is invalid.
         | 
| 131 | 
            +
                OP_XOR    = 0x86 # Disabled opcode. If executed, transaction is invalid.
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                OP_EQUAL = 0x87        # Last two items are removed from the stack and compared. Result (true or false) is pushed to the stack.
         | 
| 134 | 
            +
                OP_EQUALVERIFY = 0x88  # Same as OP_EQUAL, but removes the result from the stack if it's true or marks script as invalid.
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                OP_RESERVED1 = 0x89 # Disabled opcode. If executed, transaction is invalid.
         | 
| 137 | 
            +
                OP_RESERVED2 = 0x8a # Disabled opcode. If executed, transaction is invalid.
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                # Numeric
         | 
| 140 | 
            +
                OP_1ADD      = 0x8b  # adds 1 to last item, pops it from stack and pushes result.
         | 
| 141 | 
            +
                OP_1SUB      = 0x8c  # substracts 1 to last item, pops it from stack and pushes result.
         | 
| 142 | 
            +
                OP_2MUL      = 0x8d  # Disabled opcode. If executed, transaction is invalid.
         | 
| 143 | 
            +
                OP_2DIV      = 0x8e  # Disabled opcode. If executed, transaction is invalid.
         | 
| 144 | 
            +
                OP_NEGATE    = 0x8f  # negates the number, pops it from stack and pushes result.
         | 
| 145 | 
            +
                OP_ABS       = 0x90  # replaces number with its absolute value
         | 
| 146 | 
            +
                OP_NOT       = 0x91  # replaces number with True if it's zero, False otherwise.
         | 
| 147 | 
            +
                OP_0NOTEQUAL = 0x92  # replaces number with True if it's not zero, False otherwise.
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                OP_ADD    = 0x93  # (x y -- x+y)
         | 
| 150 | 
            +
                OP_SUB    = 0x94  # (x y -- x-y)
         | 
| 151 | 
            +
                OP_MUL    = 0x95  # Disabled opcode. If executed, transaction is invalid.
         | 
| 152 | 
            +
                OP_DIV    = 0x96  # Disabled opcode. If executed, transaction is invalid.
         | 
| 153 | 
            +
                OP_MOD    = 0x97  # Disabled opcode. If executed, transaction is invalid.
         | 
| 154 | 
            +
                OP_LSHIFT = 0x98  # Disabled opcode. If executed, transaction is invalid.
         | 
| 155 | 
            +
                OP_RSHIFT = 0x99  # Disabled opcode. If executed, transaction is invalid.
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                OP_BOOLAND            = 0x9a
         | 
| 158 | 
            +
                OP_BOOLOR             = 0x9b
         | 
| 159 | 
            +
                OP_NUMEQUAL           = 0x9c
         | 
| 160 | 
            +
                OP_NUMEQUALVERIFY     = 0x9d
         | 
| 161 | 
            +
                OP_NUMNOTEQUAL        = 0x9e
         | 
| 162 | 
            +
                OP_LESSTHAN           = 0x9f
         | 
| 163 | 
            +
                OP_GREATERTHAN        = 0xa0
         | 
| 164 | 
            +
                OP_LESSTHANOREQUAL    = 0xa1
         | 
| 165 | 
            +
                OP_GREATERTHANOREQUAL = 0xa2
         | 
| 166 | 
            +
                OP_MIN                = 0xa3
         | 
| 167 | 
            +
                OP_MAX                = 0xa4
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                OP_WITHIN = 0xa5
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                # Crypto
         | 
| 172 | 
            +
                OP_RIPEMD160      = 0xa6
         | 
| 173 | 
            +
                OP_SHA1           = 0xa7
         | 
| 174 | 
            +
                OP_SHA256         = 0xa8
         | 
| 175 | 
            +
                OP_HASH160        = 0xa9
         | 
| 176 | 
            +
                OP_HASH256        = 0xaa
         | 
| 177 | 
            +
                OP_CODESEPARATOR  = 0xab # This opcode is rarely used because it's useless, but we need to support it anyway.
         | 
| 178 | 
            +
                OP_CHECKSIG       = 0xac
         | 
| 179 | 
            +
                OP_CHECKSIGVERIFY = 0xad
         | 
| 180 | 
            +
                OP_CHECKMULTISIG  = 0xae
         | 
| 181 | 
            +
                OP_CHECKMULTISIGVERIFY = 0xaf
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                # Expansion
         | 
| 184 | 
            +
                OP_NOP1  = 0xb0
         | 
| 185 | 
            +
                OP_NOP2  = 0xb1
         | 
| 186 | 
            +
                OP_CHECKLOCKTIMEVERIFY = OP_NOP2 # See BIP65
         | 
| 187 | 
            +
                OP_NOP3  = 0xb2
         | 
| 188 | 
            +
                OP_NOP4  = 0xb3
         | 
| 189 | 
            +
                OP_NOP5  = 0xb4
         | 
| 190 | 
            +
                OP_NOP6  = 0xb5
         | 
| 191 | 
            +
                OP_NOP7  = 0xb6
         | 
| 192 | 
            +
                OP_NOP8  = 0xb7
         | 
| 193 | 
            +
                OP_NOP9  = 0xb8
         | 
| 194 | 
            +
                OP_NOP10 = 0xb9
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                OP_INVALIDOPCODE = 0xff
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                OPCODE_NAME_TO_VALUE = Hash[*constants.grep(/^OP_/).map{|c| [c.to_s, const_get(c)] }.flatten]
         | 
| 199 | 
            +
                OPCODE_VALUE_TO_NAME = Hash[*constants.grep(/^OP_/).map{|c| [const_get(c), c.to_s] }.flatten]
         | 
| 200 | 
            +
              end
         | 
| 201 | 
            +
              
         | 
| 202 | 
            +
              # Make all opcodes available as BTC::OP_...
         | 
| 203 | 
            +
              include Opcodes
         | 
| 197 204 | 
             
            end
         | 
| 198 205 |  | 
| @@ -13,6 +13,9 @@ module BTC | |
| 13 13 | 
             
                # If it is not a multisig script, returns nil.
         | 
| 14 14 | 
             
                # See also #multisig_script?
         | 
| 15 15 | 
             
                attr_reader :multisig_signatures_required
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                # Returns an array of chunks that constitute the script.
         | 
| 18 | 
            +
                attr_reader :chunks
         | 
| 16 19 |  | 
| 17 20 | 
             
                def initialize(hex: nil, # raw script data in hex
         | 
| 18 21 | 
             
                               data: nil, # raw script data in binary
         | 
| @@ -25,7 +28,7 @@ module BTC | |
| 25 28 | 
             
                    @chunks = []
         | 
| 26 29 | 
             
                    offset = 0
         | 
| 27 30 | 
             
                    while offset < data.bytesize
         | 
| 28 | 
            -
                      chunk =  | 
| 31 | 
            +
                      chunk = ScriptChunk.with_data(data, offset: offset)
         | 
| 29 32 | 
             
                      if !chunk.canonical?
         | 
| 30 33 | 
             
                        Diagnostics.current.add_message("BTC::Script: decoded non-canonical chunk at offset #{offset}: #{chunk.to_s}")
         | 
| 31 34 | 
             
                      end
         | 
| @@ -249,7 +252,7 @@ module BTC | |
| 249 252 | 
             
                def data_only?
         | 
| 250 253 | 
             
                  # Include PUSHDATA ops and OP_0..OP_16 literals.
         | 
| 251 254 | 
             
                  @chunks.each do |chunk|
         | 
| 252 | 
            -
                    return false if chunk. | 
| 255 | 
            +
                    return false if !chunk.data_only?
         | 
| 253 256 | 
             
                  end
         | 
| 254 257 | 
             
                  return true
         | 
| 255 258 | 
             
                end
         | 
| @@ -377,7 +380,7 @@ module BTC | |
| 377 380 | 
             
                  if opcode > 0 && opcode <= OP_PUSHDATA4
         | 
| 378 381 | 
             
                    raise ArgumentError, "Cannot add pushdata opcode without data"
         | 
| 379 382 | 
             
                  end
         | 
| 380 | 
            -
                  @chunks <<  | 
| 383 | 
            +
                  @chunks << ScriptChunk.new(opcode.chr)
         | 
| 381 384 | 
             
                  return self
         | 
| 382 385 | 
             
                end
         | 
| 383 386 |  | 
| @@ -390,7 +393,7 @@ module BTC | |
| 390 393 | 
             
                  if !encoded_pushdata
         | 
| 391 394 | 
             
                    raise ArgumentError, "Cannot encode pushdata with opcode #{opcode}"
         | 
| 392 395 | 
             
                  end
         | 
| 393 | 
            -
                  @chunks <<  | 
| 396 | 
            +
                  @chunks << ScriptChunk.new(encoded_pushdata)
         | 
| 394 397 | 
             
                  return self
         | 
| 395 398 | 
             
                end
         | 
| 396 399 |  | 
| @@ -434,7 +437,7 @@ module BTC | |
| 434 437 | 
             
                    object.each do |element|
         | 
| 435 438 | 
             
                      self << element
         | 
| 436 439 | 
             
                    end
         | 
| 437 | 
            -
                  elsif object.is_a?( | 
| 440 | 
            +
                  elsif object.is_a?(ScriptChunk)
         | 
| 438 441 | 
             
                    @chunks << object
         | 
| 439 442 | 
             
                  else
         | 
| 440 443 | 
             
                    raise ArgumentError, "Operand must be an integer, a string a BTC::Script instance or an array of any of those."
         | 
| @@ -490,8 +493,6 @@ module BTC | |
| 490 493 | 
             
                # Private API
         | 
| 491 494 | 
             
                # -----------
         | 
| 492 495 |  | 
| 493 | 
            -
                attr_reader :chunks
         | 
| 494 | 
            -
             | 
| 495 496 | 
             
                # If opcode is nil, then the most compact encoding will be chosen.
         | 
| 496 497 | 
             
                # Returns nil if opcode can't be used for data, or data is nil or too big.
         | 
| 497 498 | 
             
                def self.encode_pushdata(pushdata, opcode: nil)
         | 
| @@ -561,199 +562,5 @@ module BTC | |
| 561 562 | 
             
                  return true
         | 
| 562 563 | 
             
                end
         | 
| 563 564 |  | 
| 564 | 
            -
                # Script::Chunk represents either an opcode or a pushdata command.
         | 
| 565 | 
            -
                class Chunk
         | 
| 566 | 
            -
                  # Raw data for this chunk.
         | 
| 567 | 
            -
                  # 1 byte for regular opcode, 1 or more bytes for pushdata command.
         | 
| 568 | 
            -
                  # We do not call it 'data' to avoid confusion with `pushdata` (see below).
         | 
| 569 | 
            -
                  # The encoding is guaranteed to be binary.
         | 
| 570 | 
            -
                  attr_reader :raw_data
         | 
| 571 | 
            -
             | 
| 572 | 
            -
                  # Opcode for this chunk (first byte of the raw_data).
         | 
| 573 | 
            -
                  attr_reader :opcode
         | 
| 574 | 
            -
             | 
| 575 | 
            -
                  # If opcode is OP_PUSHDATA*, contains pure data being pushed.
         | 
| 576 | 
            -
                  # If opcode is OP_0, returns an empty string.
         | 
| 577 | 
            -
                  # If opcode is not pushdata, returns nil.
         | 
| 578 | 
            -
                  attr_reader :pushdata
         | 
| 579 | 
            -
             | 
| 580 | 
            -
                  # Length of raw_data in bytes.
         | 
| 581 | 
            -
                  attr_reader :size
         | 
| 582 | 
            -
                  alias :length :size
         | 
| 583 | 
            -
             | 
| 584 | 
            -
                  # Returns true if this is a non-pushdata (also not OP_0) opcode.
         | 
| 585 | 
            -
                  def opcode?
         | 
| 586 | 
            -
                    !pushdata?
         | 
| 587 | 
            -
                  end
         | 
| 588 | 
            -
             | 
| 589 | 
            -
                  # Returns true if this is a pushdata chunk (or OP_0 opcode).
         | 
| 590 | 
            -
                  def pushdata?
         | 
| 591 | 
            -
                    # Compact pushdata opcodes are "virtual", just length prefixes.
         | 
| 592 | 
            -
                    # Attention: OP_0 is also "pushdata" code that pushes empty data.
         | 
| 593 | 
            -
                    self.opcode <= OP_PUSHDATA4
         | 
| 594 | 
            -
                  end
         | 
| 595 | 
            -
             | 
| 596 | 
            -
                  # Returns true if this chunk is in canonical form (the most compact one).
         | 
| 597 | 
            -
                  # Returns false if it contains pushdata with too big length prefix.
         | 
| 598 | 
            -
                  # Example of non-canonical chunk: 75 bytes pushed with OP_PUSHDATA1 instead
         | 
| 599 | 
            -
                  # of simple 0x4b prefix.
         | 
| 600 | 
            -
                  def canonical?
         | 
| 601 | 
            -
                    opcode = self.opcode
         | 
| 602 | 
            -
                    if opcode < OP_PUSHDATA1
         | 
| 603 | 
            -
                      return true # most compact pushdata is always canonical.
         | 
| 604 | 
            -
                    elsif opcode == OP_PUSHDATA1
         | 
| 605 | 
            -
                      return (self.raw_data.bytesize - (1+1)) >= OP_PUSHDATA1
         | 
| 606 | 
            -
                    elsif opcode == OP_PUSHDATA2
         | 
| 607 | 
            -
                      return (self.raw_data.bytesize - (1+2)) > 0xff
         | 
| 608 | 
            -
                    elsif opcode == OP_PUSHDATA4
         | 
| 609 | 
            -
                      return (self.raw_data.bytesize - (1+4)) > 0xffff
         | 
| 610 | 
            -
                    else
         | 
| 611 | 
            -
                      return true # all other opcodes are canonical (just 1 byte code)
         | 
| 612 | 
            -
                    end
         | 
| 613 | 
            -
                  end
         | 
| 614 | 
            -
             | 
| 615 | 
            -
                  def opcode
         | 
| 616 | 
            -
                    # raises StopIteration if raw_data is empty,
         | 
| 617 | 
            -
                    # but we don't allow empty raw_data for chunks.
         | 
| 618 | 
            -
                    raw_data.each_byte.next
         | 
| 619 | 
            -
                  end
         | 
| 620 | 
            -
             | 
| 621 | 
            -
                  def pushdata
         | 
| 622 | 
            -
                    return nil if !pushdata?
         | 
| 623 | 
            -
                    opcode = self.opcode
         | 
| 624 | 
            -
                    offset = 1 # by default, opcode is just a length prefix.
         | 
| 625 | 
            -
                    if opcode == OP_PUSHDATA1
         | 
| 626 | 
            -
                      offset += 1
         | 
| 627 | 
            -
                    elsif opcode == OP_PUSHDATA2
         | 
| 628 | 
            -
                      offset += 2
         | 
| 629 | 
            -
                    elsif opcode == OP_PUSHDATA4
         | 
| 630 | 
            -
                      offset += 4
         | 
| 631 | 
            -
                    end
         | 
| 632 | 
            -
                    self.raw_data[offset, self.raw_data.size - offset]
         | 
| 633 | 
            -
                  end
         | 
| 634 | 
            -
             | 
| 635 | 
            -
                  def size
         | 
| 636 | 
            -
                    self.raw_data.bytesize
         | 
| 637 | 
            -
                  end
         | 
| 638 | 
            -
             | 
| 639 | 
            -
                  def to_s
         | 
| 640 | 
            -
                    opcode = self.opcode
         | 
| 641 | 
            -
             | 
| 642 | 
            -
                    if self.opcode?
         | 
| 643 | 
            -
                      return "OP_0"       if opcode == OP_0
         | 
| 644 | 
            -
                      return "OP_1NEGATE" if opcode == OP_1NEGATE
         | 
| 645 | 
            -
                      if opcode >= OP_1 && opcode <= OP_16
         | 
| 646 | 
            -
                        return "OP_#{opcode + 1 - OP_1}"
         | 
| 647 | 
            -
                      end
         | 
| 648 | 
            -
                      return Opcode.name_for_opcode(opcode)
         | 
| 649 | 
            -
                    end
         | 
| 650 | 
            -
             | 
| 651 | 
            -
                    pushdata = self.pushdata
         | 
| 652 | 
            -
                    return "OP_0" if pushdata.bytesize == 0 # Empty data is encoded as OP_0.
         | 
| 653 | 
            -
             | 
| 654 | 
            -
                    string = ""
         | 
| 655 | 
            -
             | 
| 656 | 
            -
                    # If it's some weird readable string, show it as a readable string.
         | 
| 657 | 
            -
                    if data_is_ascii_printable?(pushdata) && (pushdata.bytesize < 20 || pushdata.bytesize > 65)
         | 
| 658 | 
            -
                      string = pushdata.encode(Encoding::ASCII)
         | 
| 659 | 
            -
                      # Escape escapes & single quote characters.
         | 
| 660 | 
            -
                      string.gsub!("\\", "\\\\")
         | 
| 661 | 
            -
                      string.gsub!("'", "\\'")
         | 
| 662 | 
            -
                      # Wrap in single quotes. Why not double? Because they are already used in JSON and we don't want to multiply the mess.
         | 
| 663 | 
            -
                      string = "'#{string}'"
         | 
| 664 | 
            -
                    else
         | 
| 665 | 
            -
                      string = BTC.to_hex(pushdata)
         | 
| 666 | 
            -
                      # Shorter than 128-bit chunks are wrapped in square brackets to avoid ambiguity with big all-decimal numbers.
         | 
| 667 | 
            -
                      if (pushdata.bytesize < 16)
         | 
| 668 | 
            -
                          string = "[#{string}]"
         | 
| 669 | 
            -
                      end
         | 
| 670 | 
            -
                    end
         | 
| 671 | 
            -
             | 
| 672 | 
            -
                    # Pushdata with non-compact encoding will have explicit length prefix (1 for OP_PUSHDATA1, 2 for OP_PUSHDATA2 and 4 for OP_PUSHDATA4).
         | 
| 673 | 
            -
                    if !canonical?
         | 
| 674 | 
            -
                      prefix = 1
         | 
| 675 | 
            -
                      prefix = 2 if opcode == OP_PUSHDATA2
         | 
| 676 | 
            -
                      prefix = 4 if opcode == OP_PUSHDATA4
         | 
| 677 | 
            -
                      string = "#{prefix}:#{string}"
         | 
| 678 | 
            -
                    end
         | 
| 679 | 
            -
             | 
| 680 | 
            -
                    return string
         | 
| 681 | 
            -
                  end
         | 
| 682 | 
            -
             | 
| 683 | 
            -
                  # Parses the chunk with binary data. Assumes the encoding is binary.
         | 
| 684 | 
            -
                  def self.with_data(data, offset: 0)
         | 
| 685 | 
            -
                    raise ArgumentError, "Data is missing" if !data
         | 
| 686 | 
            -
             | 
| 687 | 
            -
                    opcode, _ = BTC::WireFormat.read_uint8(data: data, offset: offset)
         | 
| 688 | 
            -
             | 
| 689 | 
            -
                    raise ArgumentError, "Failed to read opcode of the script chunk" if !opcode
         | 
| 690 | 
            -
             | 
| 691 | 
            -
                    # push data opcode
         | 
| 692 | 
            -
                    if opcode <= OP_PUSHDATA4
         | 
| 693 | 
            -
             | 
| 694 | 
            -
                      length = data.bytesize
         | 
| 695 | 
            -
             | 
| 696 | 
            -
                      if opcode < OP_PUSHDATA1
         | 
| 697 | 
            -
                        pushdata_length = opcode
         | 
| 698 | 
            -
                        chunk_length = 1 + pushdata_length
         | 
| 699 | 
            -
                        if offset + chunk_length > length
         | 
| 700 | 
            -
                          raise ArgumentError, "PUSHDATA is longer than we have bytes available"
         | 
| 701 | 
            -
                        end
         | 
| 702 | 
            -
                        return self.new(data[offset, chunk_length])
         | 
| 703 | 
            -
                      elsif opcode == OP_PUSHDATA1
         | 
| 704 | 
            -
                        pushdata_length, _ = BTC::WireFormat.read_uint8(data: data, offset: offset + 1)
         | 
| 705 | 
            -
                        if !pushdata_length
         | 
| 706 | 
            -
                          raise ArgumentError, "Failed to read length for PUSHDATA1"
         | 
| 707 | 
            -
                        end
         | 
| 708 | 
            -
                        chunk_length = 1 + 1 + pushdata_length
         | 
| 709 | 
            -
                        if offset + chunk_length > length
         | 
| 710 | 
            -
                          raise ArgumentError, "PUSHDATA1 is longer than we have bytes available"
         | 
| 711 | 
            -
                        end
         | 
| 712 | 
            -
                        return self.new(data[offset, chunk_length])
         | 
| 713 | 
            -
                      elsif (opcode == OP_PUSHDATA2)
         | 
| 714 | 
            -
                        pushdata_length, _ = BTC::WireFormat.read_uint16le(data: data, offset: offset + 1)
         | 
| 715 | 
            -
                        if !pushdata_length
         | 
| 716 | 
            -
                          raise ArgumentError, "Failed to read length for PUSHDATA2"
         | 
| 717 | 
            -
                        end
         | 
| 718 | 
            -
                        chunk_length = 1 + 2 + pushdata_length
         | 
| 719 | 
            -
                        if offset + chunk_length > length
         | 
| 720 | 
            -
                          raise ArgumentError, "PUSHDATA2 is longer than we have bytes available"
         | 
| 721 | 
            -
                        end
         | 
| 722 | 
            -
                        return self.new(data[offset, chunk_length])
         | 
| 723 | 
            -
                      elsif (opcode == OP_PUSHDATA4)
         | 
| 724 | 
            -
                        pushdata_length, _ = BTC::WireFormat.read_uint32le(data: data, offset: offset + 1)
         | 
| 725 | 
            -
                        if !pushdata_length
         | 
| 726 | 
            -
                          raise ArgumentError, "Failed to read length for PUSHDATA4"
         | 
| 727 | 
            -
                        end
         | 
| 728 | 
            -
                        chunk_length = 1 + 4 + pushdata_length
         | 
| 729 | 
            -
                        if offset + chunk_length > length
         | 
| 730 | 
            -
                          raise ArgumentError, "PUSHDATA4 is longer than we have bytes available"
         | 
| 731 | 
            -
                        end
         | 
| 732 | 
            -
                        return self.new(data[offset, chunk_length])
         | 
| 733 | 
            -
                      end
         | 
| 734 | 
            -
                    else
         | 
| 735 | 
            -
                      # simple opcode - 1 byte
         | 
| 736 | 
            -
                      return self.new(data[offset, 1])
         | 
| 737 | 
            -
                    end
         | 
| 738 | 
            -
                  end
         | 
| 739 | 
            -
             | 
| 740 | 
            -
                  def initialize(raw_data)
         | 
| 741 | 
            -
                    @raw_data = raw_data
         | 
| 742 | 
            -
                  end
         | 
| 743 | 
            -
             | 
| 744 | 
            -
                  def ==(other)
         | 
| 745 | 
            -
                    @raw_data == other.raw_data
         | 
| 746 | 
            -
                  end
         | 
| 747 | 
            -
             | 
| 748 | 
            -
                  protected
         | 
| 749 | 
            -
             | 
| 750 | 
            -
                  def data_is_ascii_printable?(data)
         | 
| 751 | 
            -
                    data.each_byte do |byte|
         | 
| 752 | 
            -
                      return false if !(byte >= 0x20 && byte <= 0x7E)
         | 
| 753 | 
            -
                    end
         | 
| 754 | 
            -
                    return true
         | 
| 755 | 
            -
                  end
         | 
| 756 | 
            -
             | 
| 757 | 
            -
                end # Chunk
         | 
| 758 565 | 
             
              end # Script
         | 
| 759 566 | 
             
            end # BTC
         | 
| @@ -0,0 +1,216 @@ | |
| 1 | 
            +
            module BTC
         | 
| 2 | 
            +
              # ScriptChunk represents either an opcode or a pushdata command.
         | 
| 3 | 
            +
              class ScriptChunk
         | 
| 4 | 
            +
                # Raw data for this chunk.
         | 
| 5 | 
            +
                # 1 byte for regular opcode, 1 or more bytes for pushdata command.
         | 
| 6 | 
            +
                # We do not call it 'data' to avoid confusion with `pushdata` (see below).
         | 
| 7 | 
            +
                # The encoding is guaranteed to be binary.
         | 
| 8 | 
            +
                attr_reader :raw_data
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # Opcode for this chunk (first byte of the raw_data).
         | 
| 11 | 
            +
                attr_reader :opcode
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # If opcode is OP_PUSHDATA*, contains pure data being pushed.
         | 
| 14 | 
            +
                # If opcode is OP_0, returns an empty string.
         | 
| 15 | 
            +
                # If opcode is not pushdata, returns nil.
         | 
| 16 | 
            +
                attr_reader :pushdata
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Length of raw_data in bytes.
         | 
| 19 | 
            +
                attr_reader :size
         | 
| 20 | 
            +
                alias :length :size
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # Returns true if this is a non-pushdata (also not OP_0) opcode.
         | 
| 23 | 
            +
                def opcode?
         | 
| 24 | 
            +
                  !pushdata?
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # Returns true if this is a pushdata chunk (or OP_0 opcode).
         | 
| 28 | 
            +
                def pushdata?
         | 
| 29 | 
            +
                  # Compact pushdata opcodes are "virtual", just length prefixes.
         | 
| 30 | 
            +
                  # Attention: OP_0 is also "pushdata" code that pushes empty data.
         | 
| 31 | 
            +
                  opcode <= OP_PUSHDATA4
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def data_only?
         | 
| 35 | 
            +
                  opcode <= OP_16
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # Returns true if this chunk is in canonical form (the most compact one).
         | 
| 39 | 
            +
                # Returns false if it contains pushdata with too big length prefix.
         | 
| 40 | 
            +
                # Example of non-canonical chunk: 75 bytes pushed with OP_PUSHDATA1 instead
         | 
| 41 | 
            +
                # of simple 0x4b prefix.
         | 
| 42 | 
            +
                # Note: this is not as strict as `check_minimal_push` in ScriptInterpreter.
         | 
| 43 | 
            +
                def canonical?
         | 
| 44 | 
            +
                  opcode = self.opcode
         | 
| 45 | 
            +
                  if opcode < OP_PUSHDATA1
         | 
| 46 | 
            +
                    return true # most compact pushdata is always canonical.
         | 
| 47 | 
            +
                  elsif opcode == OP_PUSHDATA1
         | 
| 48 | 
            +
                    return (self.raw_data.bytesize - (1+1)) >= OP_PUSHDATA1
         | 
| 49 | 
            +
                  elsif opcode == OP_PUSHDATA2
         | 
| 50 | 
            +
                    return (self.raw_data.bytesize - (1+2)) > 0xff
         | 
| 51 | 
            +
                  elsif opcode == OP_PUSHDATA4
         | 
| 52 | 
            +
                    return (self.raw_data.bytesize - (1+4)) > 0xffff
         | 
| 53 | 
            +
                  else
         | 
| 54 | 
            +
                    return true # all other opcodes are canonical (just 1 byte code)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def opcode
         | 
| 59 | 
            +
                  # raises StopIteration if raw_data is empty,
         | 
| 60 | 
            +
                  # but we don't allow empty raw_data for chunks.
         | 
| 61 | 
            +
                  raw_data.each_byte.next
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def pushdata
         | 
| 65 | 
            +
                  return nil if !pushdata?
         | 
| 66 | 
            +
                  opcode = self.opcode
         | 
| 67 | 
            +
                  offset = 1 # by default, opcode is just a length prefix.
         | 
| 68 | 
            +
                  if opcode == OP_PUSHDATA1
         | 
| 69 | 
            +
                    offset += 1
         | 
| 70 | 
            +
                  elsif opcode == OP_PUSHDATA2
         | 
| 71 | 
            +
                    offset += 2
         | 
| 72 | 
            +
                  elsif opcode == OP_PUSHDATA4
         | 
| 73 | 
            +
                    offset += 4
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  self.raw_data[offset, self.raw_data.size - offset]
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
                
         | 
| 78 | 
            +
                # Returns corresponding data for data_only opcodes:
         | 
| 79 | 
            +
                # pushdata or OP_N-encoded numbers.
         | 
| 80 | 
            +
                # For all other opcodes returns nil.
         | 
| 81 | 
            +
                def interpreted_data
         | 
| 82 | 
            +
                  if d = pushdata
         | 
| 83 | 
            +
                    return d
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                  opcode = self.opcode
         | 
| 86 | 
            +
                  if opcode == OP_1NEGATE || (opcode >= OP_1 && opcode <= OP_16)
         | 
| 87 | 
            +
                    ScriptNumber.new(integer: opcode - OP_1 + 1).data
         | 
| 88 | 
            +
                  else
         | 
| 89 | 
            +
                    nil
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def size
         | 
| 94 | 
            +
                  self.raw_data.bytesize
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def to_s
         | 
| 98 | 
            +
                  opcode = self.opcode
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  if self.opcode?
         | 
| 101 | 
            +
                    return "OP_0"       if opcode == OP_0
         | 
| 102 | 
            +
                    return "OP_1NEGATE" if opcode == OP_1NEGATE
         | 
| 103 | 
            +
                    if opcode >= OP_1 && opcode <= OP_16
         | 
| 104 | 
            +
                      return "OP_#{opcode + 1 - OP_1}"
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                    return Opcode.name_for_opcode(opcode)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  pushdata = self.pushdata
         | 
| 110 | 
            +
                  return "OP_0" if pushdata.bytesize == 0 # Empty data is encoded as OP_0.
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  string = ""
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  # If it's some weird readable string, show it as a readable string.
         | 
| 115 | 
            +
                  if data_is_ascii_printable?(pushdata) && (pushdata.bytesize < 20 || pushdata.bytesize > 65)
         | 
| 116 | 
            +
                    string = pushdata.encode(Encoding::ASCII)
         | 
| 117 | 
            +
                    # Escape escapes & single quote characters.
         | 
| 118 | 
            +
                    string.gsub!("\\", "\\\\")
         | 
| 119 | 
            +
                    string.gsub!("'", "\\'")
         | 
| 120 | 
            +
                    # Wrap in single quotes. Why not double? Because they are already used in JSON and we don't want to multiply the mess.
         | 
| 121 | 
            +
                    string = "'#{string}'"
         | 
| 122 | 
            +
                  else
         | 
| 123 | 
            +
                    string = BTC.to_hex(pushdata)
         | 
| 124 | 
            +
                    # Shorter than 128-bit chunks are wrapped in square brackets to avoid ambiguity with big all-decimal numbers.
         | 
| 125 | 
            +
                    if (pushdata.bytesize < 16)
         | 
| 126 | 
            +
                        string = "[#{string}]"
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  # Pushdata with non-compact encoding will have explicit length prefix (1 for OP_PUSHDATA1, 2 for OP_PUSHDATA2 and 4 for OP_PUSHDATA4).
         | 
| 131 | 
            +
                  if !canonical?
         | 
| 132 | 
            +
                    prefix = 1
         | 
| 133 | 
            +
                    prefix = 2 if opcode == OP_PUSHDATA2
         | 
| 134 | 
            +
                    prefix = 4 if opcode == OP_PUSHDATA4
         | 
| 135 | 
            +
                    string = "#{prefix}:#{string}"
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  return string
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                # Parses the chunk with binary data. Assumes the encoding is binary.
         | 
| 142 | 
            +
                def self.with_data(data, offset: 0)
         | 
| 143 | 
            +
                  raise ArgumentError, "Data is missing" if !data
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  opcode, _ = BTC::WireFormat.read_uint8(data: data, offset: offset)
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  raise ArgumentError, "Failed to read opcode of the script chunk" if !opcode
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  # push data opcode
         | 
| 150 | 
            +
                  if opcode <= OP_PUSHDATA4
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    length = data.bytesize
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    if opcode < OP_PUSHDATA1
         | 
| 155 | 
            +
                      pushdata_length = opcode
         | 
| 156 | 
            +
                      chunk_length = 1 + pushdata_length
         | 
| 157 | 
            +
                      if offset + chunk_length > length
         | 
| 158 | 
            +
                        raise ArgumentError, "PUSHDATA is longer than we have bytes available"
         | 
| 159 | 
            +
                      end
         | 
| 160 | 
            +
                      return self.new(data[offset, chunk_length])
         | 
| 161 | 
            +
                    elsif opcode == OP_PUSHDATA1
         | 
| 162 | 
            +
                      pushdata_length, _ = BTC::WireFormat.read_uint8(data: data, offset: offset + 1)
         | 
| 163 | 
            +
                      if !pushdata_length
         | 
| 164 | 
            +
                        raise ArgumentError, "Failed to read length for PUSHDATA1"
         | 
| 165 | 
            +
                      end
         | 
| 166 | 
            +
                      chunk_length = 1 + 1 + pushdata_length
         | 
| 167 | 
            +
                      if offset + chunk_length > length
         | 
| 168 | 
            +
                        raise ArgumentError, "PUSHDATA1 is longer than we have bytes available"
         | 
| 169 | 
            +
                      end
         | 
| 170 | 
            +
                      return self.new(data[offset, chunk_length])
         | 
| 171 | 
            +
                    elsif (opcode == OP_PUSHDATA2)
         | 
| 172 | 
            +
                      pushdata_length, _ = BTC::WireFormat.read_uint16le(data: data, offset: offset + 1)
         | 
| 173 | 
            +
                      if !pushdata_length
         | 
| 174 | 
            +
                        raise ArgumentError, "Failed to read length for PUSHDATA2"
         | 
| 175 | 
            +
                      end
         | 
| 176 | 
            +
                      chunk_length = 1 + 2 + pushdata_length
         | 
| 177 | 
            +
                      if offset + chunk_length > length
         | 
| 178 | 
            +
                        raise ArgumentError, "PUSHDATA2 is longer than we have bytes available"
         | 
| 179 | 
            +
                      end
         | 
| 180 | 
            +
                      return self.new(data[offset, chunk_length])
         | 
| 181 | 
            +
                    elsif (opcode == OP_PUSHDATA4)
         | 
| 182 | 
            +
                      pushdata_length, _ = BTC::WireFormat.read_uint32le(data: data, offset: offset + 1)
         | 
| 183 | 
            +
                      if !pushdata_length
         | 
| 184 | 
            +
                        raise ArgumentError, "Failed to read length for PUSHDATA4"
         | 
| 185 | 
            +
                      end
         | 
| 186 | 
            +
                      chunk_length = 1 + 4 + pushdata_length
         | 
| 187 | 
            +
                      if offset + chunk_length > length
         | 
| 188 | 
            +
                        raise ArgumentError, "PUSHDATA4 is longer than we have bytes available"
         | 
| 189 | 
            +
                      end
         | 
| 190 | 
            +
                      return self.new(data[offset, chunk_length])
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
                  else
         | 
| 193 | 
            +
                    # simple opcode - 1 byte
         | 
| 194 | 
            +
                    return self.new(data[offset, 1])
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                def initialize(raw_data)
         | 
| 199 | 
            +
                  @raw_data = raw_data
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                def ==(other)
         | 
| 203 | 
            +
                  @raw_data == other.raw_data
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                protected
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                def data_is_ascii_printable?(data)
         | 
| 209 | 
            +
                  data.each_byte do |byte|
         | 
| 210 | 
            +
                    return false if !(byte >= 0x20 && byte <= 0x7E)
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                  return true
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
              end # ScriptChunk
         | 
| 216 | 
            +
            end
         | 
    
        data/lib/btcruby/version.rb
    CHANGED
    
    
    
        data/spec/script_spec.rb
    CHANGED
    
    | @@ -71,7 +71,7 @@ describe BTC::Script do | |
| 71 71 | 
             
              end
         | 
| 72 72 |  | 
| 73 73 |  | 
| 74 | 
            -
              it "removing subscript does not modify receiver" do
         | 
| 74 | 
            +
              it "removing subscript does not modify the receiver" do
         | 
| 75 75 | 
             
                s = Script.new << OP_DUP << OP_HASH160 << OP_CODESEPARATOR << "some data" << OP_EQUALVERIFY << OP_CHECKSIG
         | 
| 76 76 | 
             
                s1 = s.dup
         | 
| 77 77 | 
             
                s2 = s1.find_and_delete(Script.new << OP_HASH160)
         | 
| @@ -91,4 +91,10 @@ describe BTC::Script do | |
| 91 91 | 
             
                s2.must_equal(Script.new.append_pushdata("foo", opcode:OP_PUSHDATA1))
         | 
| 92 92 | 
             
              end
         | 
| 93 93 |  | 
| 94 | 
            +
              it "should parse interpreted data and pushdata correctly" do
         | 
| 95 | 
            +
                script = Script.new << OP_0 << OP_1NEGATE << OP_NOP << OP_RESERVED << OP_1 << OP_16 << "1" << "2" << "chancellor"
         | 
| 96 | 
            +
                script.chunks.map{|c| c.interpreted_data }.must_equal ["", "\x81".b, nil, nil, "\x01".b, "\x10".b, "1", "2", "chancellor"]
         | 
| 97 | 
            +
                script.chunks.map{|c| c.pushdata }.must_equal ["", nil, nil, nil, nil, nil, "1", "2", "chancellor"]
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 94 100 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: btcruby
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.1. | 
| 4 | 
            +
              version: 1.1.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Oleg Andreev
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2015-08- | 
| 12 | 
            +
            date: 2015-08-18 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: ffi
         | 
| @@ -108,6 +108,7 @@ files: | |
| 108 108 | 
             
            - lib/btcruby/script/opcode.rb
         | 
| 109 109 | 
             
            - lib/btcruby/script/p2sh_plugin.rb
         | 
| 110 110 | 
             
            - lib/btcruby/script/script.rb
         | 
| 111 | 
            +
            - lib/btcruby/script/script_chunk.rb
         | 
| 111 112 | 
             
            - lib/btcruby/script/script_error.rb
         | 
| 112 113 | 
             
            - lib/btcruby/script/script_flags.rb
         | 
| 113 114 | 
             
            - lib/btcruby/script/script_interpreter.rb
         |