class Bitcoin::Script

bitcoin script

Attributes

chunks[RW]

Public Class Methods

decode_number(s) click to toggle source

decode script number hex to int value

# File lib/bitcoin/script/script.rb, line 430
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) click to toggle source

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.

# File lib/bitcoin/script/script.rb, line 416
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) click to toggle source

generate script from string.

# File lib/bitcoin/script/script.rb, line 71
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 << (v =~ /^[0-9]+$/ ? v.to_i : v)
    end
  end
  script
end
new() click to toggle source
# File lib/bitcoin/script/script.rb, line 13
def initialize
  @chunks = []
end
pack_pushdata(data) click to toggle source

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

# File lib/bitcoin/script/script.rb, line 441
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) click to toggle source

generate script from addr. @param [String] addr address. @return [Bitcoin::Script] parsed script.

# File lib/bitcoin/script/script.rb, line 87
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) click to toggle source
# File lib/bitcoin/script/script.rb, line 109
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 buf.eof?
        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) click to toggle source

generate m of n multisig script @param [Integer] m the number of signatures required for multisig @param [Array] pubkeys array of public keys that compose multisig @return [Script] multisig script.

# File lib/bitcoin/script/script.rb, line 58
def self.to_multisig_script(m, pubkeys, sort: false)
  pubkeys = pubkeys.sort if sort
  new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
end
to_p2pkh(pubkey_hash) click to toggle source

generate P2PKH script

# File lib/bitcoin/script/script.rb, line 18
def self.to_p2pkh(pubkey_hash)
  new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
end
to_p2sh(script_hash) click to toggle source

generate p2sh script. @param [String] script_hash script hash for P2SH @return [Script] P2SH script

# File lib/bitcoin/script/script.rb, line 39
def self.to_p2sh(script_hash)
  Script.new << OP_HASH160 << script_hash << OP_EQUAL
end
to_p2sh_multisig_script(m, pubkeys) click to toggle source

generate m of n multisig p2sh script @param [String] m the number of signatures required for multisig @param [Array] pubkeys array of public keys that compose multisig @return [Script, Script] first element is p2sh script, second one is redeem script.

# File lib/bitcoin/script/script.rb, line 31
def self.to_p2sh_multisig_script(m, pubkeys)
  redeem_script = to_multisig_script(m, pubkeys)
  [redeem_script.to_p2sh, redeem_script]
end
to_p2wpkh(pubkey_hash) click to toggle source

generate P2WPKH script

# File lib/bitcoin/script/script.rb, line 23
def self.to_p2wpkh(pubkey_hash)
  new << WITNESS_VERSION_V0 << pubkey_hash
end
to_p2wsh(redeem_script) click to toggle source

generate p2wsh script for redeem_script @param [Script] redeem_script target redeem script @param [Script] p2wsh script

# File lib/bitcoin/script/script.rb, line 66
def self.to_p2wsh(redeem_script)
  new << WITNESS_VERSION_V0 << redeem_script.to_sha256
end

Public Instance Methods

<<(obj) click to toggle source

append object to payload

# File lib/bitcoin/script/script.rb, line 305
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) click to toggle source
# File lib/bitcoin/script/script.rb, line 517
def ==(other)
  return false unless other.is_a?(Script)
  chunks == other.chunks
end
addresses() click to toggle source

@deprecated

# File lib/bitcoin/script/script.rb, line 164
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) click to toggle source

append data to payload with pushdata opcode @param [String] data append data. this data is not binary @return [Script] return self

# File lib/bitcoin/script/script.rb, line 339
def append_data(data)
  data = Encoding::ASCII_8BIT == data.encoding ? data : data.htb
  chunks << Bitcoin::Script.pack_pushdata(data)
  self
end
append_opcode(opcode) click to toggle source

append opcode to payload @param [Integer] opcode append opcode which defined by Bitcoin::Opcodes @return [Script] return self

# File lib/bitcoin/script/script.rb, line 329
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) click to toggle source

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

# File lib/bitcoin/script/script.rb, line 499
def delete_opcode(opcode)
  @chunks = chunks.select{|chunk| chunk.ord != opcode}
  self
end
empty?() click to toggle source
# File lib/bitcoin/script/script.rb, line 159
def empty?
  chunks.size == 0
end
find_and_delete(subscript) click to toggle source

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

# File lib/bitcoin/script/script.rb, line 465
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_pubkeys() click to toggle source
# File lib/bitcoin/script/script.rb, line 49
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_pubkeys() click to toggle source

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

# File lib/bitcoin/script/script.rb, line 256
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) click to toggle source

Check the item is in the chunk of the script.

# File lib/bitcoin/script/script.rb, line 346
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?() click to toggle source
# File lib/bitcoin/script/script.rb, line 223
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 unless pubkey_count || sig_count
  sig_count <= pubkey_count
end
op_return?() click to toggle source
# File lib/bitcoin/script/script.rb, line 231
def op_return?
  chunks.size >= 1 && chunks[0].ord == OP_RETURN
end
op_return_data() click to toggle source
# File lib/bitcoin/script/script.rb, line 240
def op_return_data
  return nil unless op_return?
  return nil if chunks.size == 1
  chunks[1].pushed_data
end
p2pkh?() click to toggle source

Check whether this script is a P2PKH format script. @return [Boolean] if P2PKH return true, otherwise false

# File lib/bitcoin/script/script.rb, line 189
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?() click to toggle source

Check whether this script is a P2SH format script. @return [Boolean] if P2SH return true, otherwise false

# File lib/bitcoin/script/script.rb, line 218
def p2sh?
  return false unless chunks.size == 3
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
end
p2tr?() click to toggle source

Check whether this script is a P2TR format script. @return [Boolean] if P2TR return true, otherwise false

# File lib/bitcoin/script/script.rb, line 211
def p2tr?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
end
p2wpkh?() click to toggle source

Check whether this script is a P2WPKH format script. @return [Boolean] if P2WPKH return true, otherwise false

# File lib/bitcoin/script/script.rb, line 197
def p2wpkh?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
end
p2wsh?() click to toggle source

Check whether this script is a P2WPSH format script. @return [Boolean] if P2WPSH return true, otherwise false

# File lib/bitcoin/script/script.rb, line 204
def p2wsh?
  return false unless chunks.size == 2
  chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
end
push_int(n) click to toggle source

push integer to stack.

# File lib/bitcoin/script/script.rb, line 317
def push_int(n)
  begin
    append_opcode(n)
  rescue ArgumentError
    append_data(Script.encode_number(n))
  end
  self
end
push_only?() click to toggle source

whether data push only script which dose not include other opcode

# File lib/bitcoin/script/script.rb, line 247
def push_only?
  chunks.each do |c|
    return false if !c.opcode.nil? && c.opcode > OP_16
  end
  true
end
run() click to toggle source

execute script interpreter using this script for development.

# File lib/bitcoin/script/script.rb, line 404
def run
  Bitcoin::ScriptInterpreter.eval(Bitcoin::Script.new, self.dup)
end
size() click to toggle source

script size

# File lib/bitcoin/script/script.rb, line 399
def size
  to_payload.bytesize
end
standard?() click to toggle source

check whether standard script.

# File lib/bitcoin/script/script.rb, line 183
def standard?
  p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return?
end
standard_op_return?() click to toggle source
# File lib/bitcoin/script/script.rb, line 235
def standard_op_return?
  op_return? && size <= MAX_OP_RETURN_RELAY &&
      (chunks.size == 1 || chunks[1].opcode <= OP_16)
end
subscript(*args) click to toggle source

subscript this script to the specified range.

# File lib/bitcoin/script/script.rb, line 458
def subscript(*args)
  s = self.class.new
  s.chunks = chunks[*args]
  s
end
subscript_codeseparator(separator_index) click to toggle source

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

# File lib/bitcoin/script/script.rb, line 505
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_addr() click to toggle source

convert to address @return [String] if script type is p2pkh or p2sh or witness program, return address, otherwise nil.

# File lib/bitcoin/script/script.rb, line 175
def to_addr
  return p2pkh_addr if p2pkh?
  return p2sh_addr if p2sh?
  return bech32_addr if witness_program?
  nil
end
to_h() click to toggle source
# File lib/bitcoin/script/script.rb, line 531
def to_h
  h = {asm: to_s, hex: to_hex, type: type}
  addrs = addresses
  unless addrs.empty?
    h[:req_sigs] = multisig? ? Bitcoin::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
    h[:addresses] = addrs
  end
  h
end
to_hash160() click to toggle source

generate hash160 hash for payload

# File lib/bitcoin/script/script.rb, line 394
def to_hash160
  Bitcoin.hash160(to_hex)
end
to_p2sh() click to toggle source

generate p2sh script with this as a redeem script @return [Script] P2SH script

# File lib/bitcoin/script/script.rb, line 45
def to_p2sh
  Script.to_p2sh(to_hash160)
end
to_payload(length_prefixed = false) click to toggle source

Output script payload. @param [Boolean] length_prefixed Flag whether the length of the payload should be given at the beginning.(default: false) @return [String] payload

# File lib/bitcoin/script/script.rb, line 154
def to_payload(length_prefixed = false)
  p = chunks.join
  length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
end
to_s() click to toggle source
# File lib/bitcoin/script/script.rb, line 357
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) click to toggle source

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

# File lib/bitcoin/script/script.rb, line 287
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_sha256() click to toggle source

generate sha-256 hash for payload

# File lib/bitcoin/script/script.rb, line 389
def to_sha256
  Bitcoin.sha256(to_payload).bth
end
type() click to toggle source
# File lib/bitcoin/script/script.rb, line 522
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?
  'nonstandard'
end
unspendable?() click to toggle source

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. @return [Boolean] whether the script is guaranteed to fail at execution

# File lib/bitcoin/script/script.rb, line 544
def unspendable?
  (size > 0 && op_return?) || size > Bitcoin::MAX_SCRIPT_SIZE
end
witness_commitment() click to toggle source

get witness commitment

# File lib/bitcoin/script/script.rb, line 278
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_data() click to toggle source

get witness version and witness program

# File lib/bitcoin/script/script.rb, line 298
def witness_data
  version = opcode_to_small_int(chunks[0].opcode)
  program = chunks[1].pushed_data
  [version, program]
end
witness_program?() click to toggle source

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.

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

Private Instance Methods

bech32_addr() click to toggle source

return bech32 address for payload

# File lib/bitcoin/script/script.rb, line 577
def bech32_addr
  segwit_addr = Bech32::SegwitAddr.new
  segwit_addr.hrp = Bitcoin.chain_params.bech32_hrp
  segwit_addr.script_pubkey = to_hex
  segwit_addr.addr
end
p2pkh_addr() click to toggle source

generate p2pkh address. if script dose not p2pkh, return nil.

# File lib/bitcoin/script/script.rb, line 551
def p2pkh_addr
  return nil unless p2pkh?
  hash160 = chunks[2].pushed_data.bth
  return nil unless hash160.htb.bytesize == 20
  Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.address_version)
end
p2sh_addr() click to toggle source

generate p2sh address. if script dose not p2sh, return nil.

# File lib/bitcoin/script/script.rb, line 564
def p2sh_addr
  return nil unless p2sh?
  hash160 = chunks[1].pushed_data.bth
  return nil unless hash160.htb.bytesize == 20
  Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.p2sh_version)
end
p2wpkh_addr() click to toggle source

generate p2wpkh address. if script dose not p2wpkh, return nil.

# File lib/bitcoin/script/script.rb, line 559
def p2wpkh_addr
  p2wpkh? ? bech32_addr : nil
end
p2wsh_addr() click to toggle source

generate p2wsh address. if script dose not p2wsh, return nil.

# File lib/bitcoin/script/script.rb, line 572
def p2wsh_addr
  p2wsh? ? bech32_addr : nil
end
valid_pushdata_length?(chunk) click to toggle source

Check whether push data length is valid. @return [Boolean] if valid return true, otherwise false.

# File lib/bitcoin/script/script.rb, line 586
def valid_pushdata_length?(chunk)
  buf = StringIO.new(chunk)
  opcode = buf.read(1).ord
  offset = 1
  len = case opcode
        when OP_PUSHDATA1
          offset += 1
          buf.read(1).unpack1('C')
        when OP_PUSHDATA2
          offset += 2
          buf.read(2).unpack1('v')
        when OP_PUSHDATA4
          offset += 4
          buf.read(4).unpack1('V')
        else
          opcode
        end
  chunk.bytesize == len + offset
end