Class: Bitcoin::Script

Inherits:
Object
  • Object
show all
Includes:
HexConverter, Opcodes
Defined in:
lib/bitcoin/script/script.rb

Overview

bitcoin script

Constant Summary collapse

P2A_PROGRAM =
'4e73'.htb

Constants included from Opcodes

Opcodes::DUPLICATE_KEY, Opcodes::NAME_MAP, Opcodes::OPCODES_MAP, 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_CHECKMULTISIG, Opcodes::OP_CHECKMULTISIGVERIFY, Opcodes::OP_CHECKSIG, Opcodes::OP_CHECKSIGADD, 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_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_PUBKEY, Opcodes::OP_PUBKEYHASH, 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_SUCCESSES, Opcodes::OP_SWAP, Opcodes::OP_TOALTSTACK, Opcodes::OP_TUCK, Opcodes::OP_VER, Opcodes::OP_VERIF, Opcodes::OP_VERIFY, Opcodes::OP_VERNOTIF, Opcodes::OP_WITHIN, Opcodes::OP_XOR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HexConverter

#to_hex

Methods included from Opcodes

defined?, name_to_opcode, op_success?, opcode_to_name, opcode_to_small_int, small_int_to_opcode

Constructor Details

#initializeScript

Returns a new instance of Script.



15
16
17
# File 'lib/bitcoin/script/script.rb', line 15

def initialize
  @chunks = []
end

Instance Attribute Details

#chunksObject

Returns the value of attribute chunks.



13
14
15
# File 'lib/bitcoin/script/script.rb', line 13

def chunks
  @chunks
end

Class Method Details

.decode_number(s) ⇒ Object

decode script number hex to int value



489
490
491
492
493
494
495
496
497
# File 'lib/bitcoin/script/script.rb', line 489

def self.decode_number(s)
  v = s.htb.reverse
  return 0 if v.length.zero?
  mbs = v[0].unpack1('C')
  v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
  result = v.bth.to_i(16)
  result = -result unless (mbs & 0x80) == 0
  result
end

.encode_number(i) ⇒ Object

encode int value to script number hex. The stacks hold byte vectors. When used as numbers, byte vectors are interpreted as little-endian variable-length integers with the most significant bit determining the sign of the integer. Thus 0x81 represents -1. 0x80 is another representation of zero (so called negative 0). Positive 0 is represented by a null-length vector. Byte vectors are interpreted as Booleans where False is represented by any representation of zero, and True is represented by any representation of non-zero.



475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/bitcoin/script/script.rb', line 475

def self.encode_number(i)
  return '' if i == 0
  negative = i < 0

  hex = i.abs.to_even_length_hex
  hex = '0' + hex unless (hex.length % 2).zero?
  v = hex.htb.reverse # change endian

  v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
  v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
  v.bth
end

.from_string(string) ⇒ Object

generate script from string.



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/bitcoin/script/script.rb', line 96

def self.from_string(string)
  script = new
  string.split(' ').each do |v|
    opcode = Opcodes.name_to_opcode(v)
    if opcode
      script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
    else
      script << (number_token?(v) ? v.to_i : v)
    end
  end
  script
end

.number_token?(token) ⇒ Boolean

Whether token is a script number rather than pushed data with hex format. A digit only token is ambiguous since #to_s emits pushed data as hex. #to_s never emits a number with more than 10 digits, so a longer even-length token must be pushed data.

Parameters:

  • token (String)

    a token of the script string.

Returns:

  • (Boolean)

    whether token should be interpreted as a script number.



114
115
116
117
# File 'lib/bitcoin/script/script.rb', line 114

def self.number_token?(token)
  return false unless token =~ /^[0-9]+$/
  token.length < 12 || token.length.odd?
end

.pack_pushdata(data) ⇒ Object

binary data convert pushdata which contains data length and append PUSHDATA opcode if necessary.



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/bitcoin/script/script.rb', line 500

def self.pack_pushdata(data)
  size = data.bytesize
  header = if size < OP_PUSHDATA1
             [size].pack('C')
           elsif size <= 0xff
             [OP_PUSHDATA1, size].pack('CC')
           elsif size <= 0xffff
             [OP_PUSHDATA2, size].pack('Cv')
           elsif size <= 0xffffffff
             [OP_PUSHDATA4, size].pack('CV')
           else
             raise ArgumentError, 'data size is too big.'
           end
  header + data
end

.parse_from_addr(addr) ⇒ Bitcoin::Script

generate script from addr.

Parameters:

Returns:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/bitcoin/script/script.rb', line 122

def self.parse_from_addr(addr)
  begin
    segwit_addr = Bech32::SegwitAddr.new(addr)
    raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
    Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
  rescue Exception => e
    begin
    hex, addr_version = Bitcoin.decode_base58_address(addr)
    rescue
      raise ArgumentError, 'Invalid address.'
    end
    case addr_version
    when Bitcoin.chain_params.address_version
      Bitcoin::Script.to_p2pkh(hex)
    when Bitcoin.chain_params.p2sh_version
      Bitcoin::Script.to_p2sh(hex)
    else
      raise ArgumentError, 'Invalid address.'
    end
  end
end

.parse_from_payload(payload) ⇒ Object



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
# File 'lib/bitcoin/script/script.rb', line 144

def self.parse_from_payload(payload)
  s = new
  buf = StringIO.new(payload)
  until buf.eof?
    opcode = buf.read(1)
    if opcode.pushdata?
      pushcode = opcode.ord
      packed_size = nil
      if buf.eof?
        s.chunks << opcode
        return s
      end
      len = case pushcode
              when OP_PUSHDATA1
                packed_size = buf.read(1)
                packed_size.unpack1('C')
              when OP_PUSHDATA2
                packed_size = buf.read(2)
                packed_size.unpack1('v')
              when OP_PUSHDATA4
                packed_size = buf.read(4)
                packed_size.unpack1('V')
              else
                pushcode < OP_PUSHDATA1 ? pushcode : 0
            end
      if buf.eof?
        s.chunks << [len].pack('C')
      else
        chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
        s.chunks << chunk
      end
    else
      if Opcodes.defined?(opcode.ord)
        s << opcode.ord
      else
        s.chunks << (opcode + buf.read) # If opcode is invalid, put all remaining data in last chunk.
      end
    end
  end
  s
end

.to_multisig_script(m, pubkeys, sort: false) ⇒ Script

generate m of n multisig script

Parameters:

  • m (Integer)

    the number of signatures required for multisig

  • pubkeys (Array)

    array of public keys that compose multisig

Returns:

  • (Script)

    multisig script.



81
82
83
84
# File 'lib/bitcoin/script/script.rb', line 81

def self.to_multisig_script(m, pubkeys, sort: false)
  pubkeys = pubkeys.sort if sort
  new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
end

.to_p2aBitcoin::Script

Generate P2TR script

Returns:



44
45
46
# File 'lib/bitcoin/script/script.rb', line 44

def self.to_p2a
  new << OP_1 << P2A_PROGRAM
end

.to_p2pkh(pubkey_hash) ⇒ Object

generate P2PKH script

Parameters:

  • pubkey_hash (String)

    public key hash with hex format



21
22
23
# File 'lib/bitcoin/script/script.rb', line 21

def self.to_p2pkh(pubkey_hash)
  new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
end

.to_p2sh(script_hash) ⇒ Script

generate p2sh script.

Parameters:

  • script_hash (String)

    script hash for P2SH

Returns:



60
61
62
# File 'lib/bitcoin/script/script.rb', line 60

def self.to_p2sh(script_hash)
  Script.new << OP_HASH160 << script_hash << OP_EQUAL
end

.to_p2sh_multisig_script(m, pubkeys) ⇒ Script

generate m of n multisig p2sh script

Parameters:

  • m (String)

    the number of signatures required for multisig

  • pubkeys (Array)

    array of public keys that compose multisig

Returns:

  • (Script, Script)

    first element is p2sh script, second one is redeem script.



52
53
54
55
# File 'lib/bitcoin/script/script.rb', line 52

def self.to_p2sh_multisig_script(m, pubkeys)
  redeem_script = to_multisig_script(m, pubkeys)
  [redeem_script.to_p2sh, redeem_script]
end

.to_p2tr(xonly_pubkey) ⇒ Bitcoin::Script

Generate P2TR script

Parameters:

Returns:

Raises:

  • ArgumentError



36
37
38
39
40
# File 'lib/bitcoin/script/script.rb', line 36

def self.to_p2tr(xonly_pubkey)
  xonly_pubkey = xonly_pubkey.xonly_pubkey if xonly_pubkey.is_a?(Bitcoin::Key)
  raise ArgumentError, 'Invalid public key size' unless xonly_pubkey.htb.bytesize == Bitcoin::WITNESS_V1_TAPROOT_SIZE
  new << OP_1 << xonly_pubkey
end

.to_p2wpkh(pubkey_hash) ⇒ Bitcoin::Script

generate P2WPKH script

Parameters:

  • pubkey_hash (String)

    public key hash with hex format

Returns:



28
29
30
# File 'lib/bitcoin/script/script.rb', line 28

def self.to_p2wpkh(pubkey_hash)
  new << WITNESS_VERSION_V0 << pubkey_hash
end

.to_p2wsh(redeem_script) ⇒ Bitcoin::Script

generate p2wsh script for redeem_script

Parameters:

Returns:

Raises:

  • (ArgumentError)

    If the script size exceeds 10,000 bytes



90
91
92
93
# File 'lib/bitcoin/script/script.rb', line 90

def self.to_p2wsh(redeem_script)
  raise ArgumentError, 'P2WSH witness script must be 10,000 bytes or less.' if redeem_script.size > Bitcoin::MAX_SCRIPT_SIZE
  new << WITNESS_VERSION_V0 << redeem_script.to_sha256
end

Instance Method Details

#<<(obj) ⇒ Object

append object to payload



364
365
366
367
368
369
370
371
372
373
# File 'lib/bitcoin/script/script.rb', line 364

def <<(obj)
  if obj.is_a?(Integer)
    push_int(obj)
  elsif obj.is_a?(String)
    append_data(obj)
  elsif obj.is_a?(Array)
    obj.each { |o| self.<< o}
    self
  end
end

#==(other) ⇒ Object



576
577
578
579
# File 'lib/bitcoin/script/script.rb', line 576

def ==(other)
  return false unless other.is_a?(Script)
  chunks == other.chunks
end

#addressesObject

Deprecated.


199
200
201
202
203
204
205
206
# File 'lib/bitcoin/script/script.rb', line 199

def addresses
  puts "WARNING: Bitcoin::Script#addresses is deprecated. Use Bitcoin::Script#to_addr instead."
  return [p2pkh_addr] if p2pkh?
  return [p2sh_addr] if p2sh?
  return [bech32_addr] if witness_program?
  return get_multisig_pubkeys.map{|pubkey| Bitcoin::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
  []
end

#append_data(data) ⇒ Script

append data to payload with pushdata opcode

Parameters:

  • data (String)

    append data. this data is not binary

Returns:



398
399
400
401
402
# File 'lib/bitcoin/script/script.rb', line 398

def append_data(data)
  data = Encoding::ASCII_8BIT == data.encoding ? data : data.htb
  chunks << Bitcoin::Script.pack_pushdata(data)
  self
end

#append_opcode(opcode) ⇒ Script

append opcode to payload

Parameters:

  • opcode (Integer)

    append opcode which defined by Bitcoin::Opcodes

Returns:

Raises:

  • (ArgumentError)


388
389
390
391
392
393
# File 'lib/bitcoin/script/script.rb', line 388

def append_opcode(opcode)
  opcode = Opcodes.small_int_to_opcode(opcode) if -1 <= opcode && opcode <= 16
  raise ArgumentError, "specified invalid opcode #{opcode}." unless Opcodes.defined?(opcode)
  chunks << opcode.chr
  self
end

#delete_opcode(opcode) ⇒ Object

remove all occurences of opcode. Typically it's OP_CODESEPARATOR.



558
559
560
561
# File 'lib/bitcoin/script/script.rb', line 558

def delete_opcode(opcode)
  @chunks = chunks.select{|chunk| chunk.ord != opcode}
  self
end

#empty?Boolean

Returns:

  • (Boolean)


194
195
196
# File 'lib/bitcoin/script/script.rb', line 194

def empty?
  chunks.size == 0
end

#find_and_delete(subscript) ⇒ Object

removes chunks matching subscript byte-for-byte and returns as a new object.

Raises:

  • (ArgumentError)


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
# File 'lib/bitcoin/script/script.rb', line 524

def find_and_delete(subscript)
  raise ArgumentError, 'subscript must be Bitcoin::Script' unless subscript.is_a?(Script)
  return self if subscript.chunks.empty?
  buf = []
  i = 0
  result = Script.new
  chunks.each do |chunk|
    sub_chunk = subscript.chunks[i]
    if chunk.start_with?(sub_chunk)
      if chunk == sub_chunk
        buf << chunk
        i += 1
        (i = 0; buf.clear) if i == subscript.chunks.size # matched the whole subscript
      else # matched the part of head
        i = 0
        tmp = chunk.dup
        tmp.slice!(sub_chunk)
        result.chunks << tmp
      end
    else
      result.chunks << buf.join unless buf.empty?
      if buf.first == chunk
        i = 1
        buf = [chunk]
      else
        i = 0
        result.chunks << chunk
      end
    end
  end
  result
end

#get_multisig_pubkeysObject



72
73
74
75
# File 'lib/bitcoin/script/script.rb', line 72

def get_multisig_pubkeys
  num = Bitcoin::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
  (1..num).map{ |i| chunks[i].pushed_data }
end

#get_pubkeysObject

get public keys in the stack. @return[Array[String]] an array of the pubkeys with hex format.



315
316
317
# File 'lib/bitcoin/script/script.rb', line 315

def get_pubkeys
  chunks.select{|c|c.pushdata? && [33, 65].include?(c.pushed_data.bytesize) && [2, 3, 4, 6, 7].include?(c.pushed_data[0].bth.to_i(16))}.map{|c|c.pushed_data.bth}
end

#include?(item) ⇒ Boolean

Check the item is in the chunk of the script.

Returns:

  • (Boolean)


405
406
407
408
409
410
411
412
413
414
# File 'lib/bitcoin/script/script.rb', line 405

def include?(item)
  chunk_item = if item.is_a?(Integer)
                 item.chr
               elsif item.is_a?(String)
                 data = Encoding::ASCII_8BIT == item.encoding ? item : item.htb
                 Bitcoin::Script.pack_pushdata(data)
               end
  return false unless chunk_item
  chunks.include?(chunk_item)
end

#multisig?Boolean

Returns:

  • (Boolean)


282
283
284
285
286
287
288
# File 'lib/bitcoin/script/script.rb', line 282

def multisig?
  return false if chunks.size < 4 || chunks.last.ord != OP_CHECKMULTISIG
  pubkey_count = Opcodes.opcode_to_small_int(chunks[-2].opcode)
  sig_count = Opcodes.opcode_to_small_int(chunks[0].opcode)
  return false if pubkey_count.nil? || sig_count.nil?
  sig_count <= pubkey_count
end

#op_return?Boolean

Returns:

  • (Boolean)


290
291
292
# File 'lib/bitcoin/script/script.rb', line 290

def op_return?
  chunks.size >= 1 && chunks[0].ord == OP_RETURN
end

#op_return_dataObject



299
300
301
302
303
# File 'lib/bitcoin/script/script.rb', line 299

def op_return_data
  return nil unless op_return?
  return nil if chunks.size == 1
  chunks[1].pushed_data
end

#p2a?Boolean

Check whether this script is a Pay to Anchor format script.

Returns:

  • (Boolean)

    if P2A return true, otherwise false



253
254
255
256
# File 'lib/bitcoin/script/script.rb', line 253

def p2a?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].pushed_data == P2A_PROGRAM
end

#p2pk?Boolean

Check whether this script is a P2PK format script.

Returns:

  • (Boolean)

    if P2PK return true, otherwise false



267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/bitcoin/script/script.rb', line 267

def p2pk?
  return false unless chunks.size == 2
  return false unless chunks[0].pushdata?
  key_type = chunks[0].pushed_data[0].ord
  case key_type
  when 0x02, 0x03
    return false unless chunks[0].pushed_data.bytesize == 33
  when 0x04
    return false unless chunks[0].pushed_data.bytesize == 65
  else
    return false
  end
  chunks[1].ord == OP_CHECKSIG
end

#p2pkh?Boolean

Check whether this script is a P2PKH format script.

Returns:

  • (Boolean)

    if P2PKH return true, otherwise false



224
225
226
227
228
# File 'lib/bitcoin/script/script.rb', line 224

def p2pkh?
  return false unless chunks.size == 5
  [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
      (chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
end

#p2sh?Boolean

Check whether this script is a P2SH format script.

Returns:

  • (Boolean)

    if P2SH return true, otherwise false



260
261
262
263
# File 'lib/bitcoin/script/script.rb', line 260

def p2sh?
  return false unless chunks.size == 3
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
end

#p2tr?Boolean

Check whether this script is a P2TR format script.

Returns:

  • (Boolean)

    if P2TR return true, otherwise false



246
247
248
249
# File 'lib/bitcoin/script/script.rb', line 246

def p2tr?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
end

#p2wpkh?Boolean

Check whether this script is a P2WPKH format script.

Returns:

  • (Boolean)

    if P2WPKH return true, otherwise false



232
233
234
235
# File 'lib/bitcoin/script/script.rb', line 232

def p2wpkh?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
end

#p2wsh?Boolean

Check whether this script is a P2WPSH format script.

Returns:

  • (Boolean)

    if P2WPSH return true, otherwise false



239
240
241
242
# File 'lib/bitcoin/script/script.rb', line 239

def p2wsh?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
end

#push_int(n) ⇒ Object

push integer to stack.



376
377
378
379
380
381
382
383
# File 'lib/bitcoin/script/script.rb', line 376

def push_int(n)
  begin
    append_opcode(n)
  rescue ArgumentError
    append_data(Script.encode_number(n))
  end
  self
end

#push_only?Boolean

whether data push only script which dose not include other opcode

Returns:

  • (Boolean)


306
307
308
309
310
311
# File 'lib/bitcoin/script/script.rb', line 306

def push_only?
  chunks.each do |c|
    return false if !c.opcode.nil? && c.opcode > OP_16
  end
  true
end

#runObject

execute script interpreter using this script for development.



463
464
465
# File 'lib/bitcoin/script/script.rb', line 463

def run
  Bitcoin::ScriptInterpreter.eval(Bitcoin::Script.new, self.dup)
end

#sizeObject

script size



458
459
460
# File 'lib/bitcoin/script/script.rb', line 458

def size
  to_payload.bytesize
end

#standard?Boolean

check whether standard script.

Returns:

  • (Boolean)


218
219
220
# File 'lib/bitcoin/script/script.rb', line 218

def standard?
  p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return? || p2a?
end

#standard_op_return?Boolean

Returns:

  • (Boolean)


294
295
296
297
# File 'lib/bitcoin/script/script.rb', line 294

def standard_op_return?
  op_return? && size <= MAX_OP_RETURN_RELAY &&
      (chunks.size == 1 || chunks[1].opcode <= OP_16)
end

#subscript(*args) ⇒ Object

subscript this script to the specified range.



517
518
519
520
521
# File 'lib/bitcoin/script/script.rb', line 517

def subscript(*args)
  s = self.class.new
  s.chunks = chunks[*args]
  s
end

#subscript_codeseparator(separator_index) ⇒ Object

Returns a script that deleted the script before the index specified by separator_index.



564
565
566
567
568
569
570
571
572
573
574
# File 'lib/bitcoin/script/script.rb', line 564

def subscript_codeseparator(separator_index)
  buf = []
  process_separator_index = 0
  chunks.each{|chunk|
    buf << chunk if process_separator_index == separator_index
    if chunk.ord == OP_CODESEPARATOR && process_separator_index < separator_index
      process_separator_index += 1
    end
  }
  buf.join
end

#to_addrString

convert to address

Returns:

  • (String)

    if script type is p2pkh or p2sh or witness program, return address, otherwise nil.



210
211
212
213
214
215
# File 'lib/bitcoin/script/script.rb', line 210

def to_addr
  return p2pkh_addr if p2pkh?
  return p2sh_addr if p2sh?
  return bech32_addr if witness_program?
  nil
end

#to_hObject



592
593
594
595
596
597
# File 'lib/bitcoin/script/script.rb', line 592

def to_h
  h = {asm: to_s, hex: to_hex, type: type}
  addr = to_addr
  h[:address] = addr if addr
  h
end

#to_hash160Object

generate hash160 hash for payload



453
454
455
# File 'lib/bitcoin/script/script.rb', line 453

def to_hash160
  Bitcoin.hash160(to_hex)
end

#to_p2shScript

generate p2sh script with this as a redeem script

Returns:

Raises:

  • (RuntimeError)

    If the script size exceeds 520 bytes



67
68
69
70
# File 'lib/bitcoin/script/script.rb', line 67

def to_p2sh
  raise RuntimeError, "P2SH redeem script must be 520 bytes or less." if size > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
  Script.to_p2sh(to_hash160)
end

#to_payload(length_prefixed = false) ⇒ String

Output script payload.

Parameters:

  • length_prefixed (Boolean) (defaults to: false)

    Flag whether the length of the payload should be given at the beginning.(default: false)

Returns:



189
190
191
192
# File 'lib/bitcoin/script/script.rb', line 189

def to_payload(length_prefixed = false)
  p = chunks.join
  length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
end

#to_sObject



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
# File 'lib/bitcoin/script/script.rb', line 416

def to_s
  chunks.map { |c|
    case c
    when Integer
      opcode_to_name(c)
    when String
      return c if c.empty?
      if c.pushdata?
        v = Opcodes.opcode_to_small_int(c.ord)
        if v
          v
        else
          data = c.pushed_data
          if data
            if data.bytesize <= 4
              Script.decode_number(data.bth) # for scriptnum
            else
              data.bth
            end
          else
            c.bth
          end
        end
      else
        opcode = Opcodes.opcode_to_name(c.ord)
        opcode ? opcode : 'OP_UNKNOWN [error]'
      end
    end
  }.join(' ')
end

#to_script_code(skip_separator_index = 0) ⇒ Object

If this script is witness program, return its script code, otherwise returns the self payload. ScriptInterpreter does not use this.



346
347
348
349
350
351
352
353
354
# File 'lib/bitcoin/script/script.rb', line 346

def to_script_code(skip_separator_index = 0)
  payload = to_payload
  if p2wpkh?
    payload = Script.to_p2pkh(chunks[1].pushed_data.bth).to_payload
  elsif skip_separator_index > 0
    payload = subscript_codeseparator(skip_separator_index)
  end
  Bitcoin.pack_var_string(payload)
end

#to_sha256Object

generate sha-256 hash for payload



448
449
450
# File 'lib/bitcoin/script/script.rb', line 448

def to_sha256
  Bitcoin.sha256(to_payload).bth
end

#typeObject



581
582
583
584
585
586
587
588
589
590
# File 'lib/bitcoin/script/script.rb', line 581

def type
  return 'pubkeyhash' if p2pkh?
  return 'scripthash' if p2sh?
  return 'multisig' if multisig?
  return 'witness_v0_keyhash' if p2wpkh?
  return 'witness_v0_scripthash' if p2wsh?
  return 'witness_v1_taproot' if p2tr?
  return 'anchor' if p2a?
  'nonstandard'
end

#unspendable?Boolean

Returns whether the script is guaranteed to fail at execution, regardless of the initial stack. This allows outputs to be pruned instantly when entering the UTXO set.

Returns:

  • (Boolean)

    whether the script is guaranteed to fail at execution



602
603
604
# File 'lib/bitcoin/script/script.rb', line 602

def unspendable?
  (size > 0 && op_return?) || size > Bitcoin::MAX_SCRIPT_SIZE
end

#witness_commitmentObject

get witness commitment



337
338
339
340
341
342
# File 'lib/bitcoin/script/script.rb', line 337

def witness_commitment
  return nil if !op_return? || op_return_data.bytesize < 36
  buf = StringIO.new(op_return_data)
  return nil unless buf.read(4).bth == WITNESS_COMMITMENT_HEADER
  buf.read(32).bth
end

#witness_dataObject

get witness version and witness program



357
358
359
360
361
# File 'lib/bitcoin/script/script.rb', line 357

def witness_data
  version = opcode_to_small_int(chunks[0].opcode)
  program = chunks[1].pushed_data
  [version, program]
end

#witness_program?Boolean

A witness program is any valid Script that consists of a 1-byte push opcode followed by a data push between 2 and 40 bytes.

Returns:

  • (Boolean)


320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/bitcoin/script/script.rb', line 320

def witness_program?
  return false if size < 4 || size > 42 || chunks.size < 2

  opcode = chunks[0].opcode

  return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
  return false unless chunks[1].pushdata?

  if size == (chunks[1][0].unpack1('C') + 2)
    program_size = chunks[1].pushed_data.bytesize
    return program_size >= 2 && program_size <= 40
  end

  false
end