class RawTx

This class is mostly used to decode json transactions from raw transactions much of this implementation was inspired from: gist.github.com/shesek/5835695

Constants

BASE64_CHARS

This is a map of offset to characters used for decoding base64 strings:

MAX_32BIT_INTEGER

Public Class Methods

bytes_to_base64_s(input_bytes) click to toggle source

Convert an array of bytes to a base64 string.

# File lib/counterparty/raw_tx.rb, line 73
def self.bytes_to_base64_s(input_bytes)
  input_bytes.each_slice(3).collect.with_index{ |bytes,i|
    # This is the base offset of this 3-byte slice in the sequence:
    i_triplet = i*3 

    # Here we compose a triplet by or'ing the shifted bytes:
    triplet = bytes.collect.with_index{|b,j| b << 8*(2-j) }.reduce(:|)

    # And here we convert to chars, unless with equals as nil-padding:
    0.upto(3).collect do |j| 
      (i_triplet * 8 + j * 6 <= input_bytes.length * 8) ? 
        BASE64_CHARS[((triplet >> 6 * (3 - j)) & 0x3F)] : '=' 
    end
  }.join
end
bytes_to_ui(bytes) click to toggle source

Convert an array of 8 bit numbers into an unsigned int, Remember that for each input byte, the most significant nibble comes last.

# File lib/counterparty/raw_tx.rb, line 67
def self.bytes_to_ui(bytes)
  nibbles = bytes.collect{|b| [b & 0x0f, (b & 0xf0) >> 4]}.flatten
  nibbles.each_with_index.inject(0){|sum,(b,i)| sum += b * 16**i}
end
new(as_hexstring) click to toggle source

Creates a raw transaction from a hexstring

# File lib/counterparty/raw_tx.rb, line 11
def initialize(as_hexstring)
  @hexstring = as_hexstring.downcase
  raise ArgumentError, "Unsupported hex" unless /\A[0-9a-f]+\Z/.match @hexstring
end
nibbles_to_ui(nibbles) click to toggle source

Convert an array of 4 bit numbers into an unsigned int, works for numbers up to 32-bit only

# File lib/counterparty/raw_tx.rb, line 58
def self.nibbles_to_ui(nibbles)
  raise ArgumentError if nibbles.length > 4

  nibbles.each_with_index.inject(0){ |sum, (b,i)|
    sum += (b.to_i & 0xff) << (4 * i) }
end

Public Instance Methods

to_hash(options = {}) click to toggle source

Returns this transaction in a standard json format

# File lib/counterparty/raw_tx.rb, line 17
def to_hash(options = {})
  bytes = @hexstring.scan(/../).collect(&:hex)
  size = bytes.length

  # Now we start shift elements off the byte stack:
  version = shift_u32(bytes)

  raise ArgumentError, "Unsupported Version/Tx" unless version == 0x01

  # Parse the inputs:
  ins = (0...shift_varint(bytes)).collect do
    hash = bytes.slice!(0,32).reverse.collect{|n| '%02x' % n}.join
    index,script,seq = shift_u32(bytes),shift_varchar(bytes),shift_u32(bytes)

    # NOTE: We may want to base64 encode the hash, or support this via an
    # option : self.class.bytes_to_base64_s(hash).reverse),

    # NOTE: The :seq field isnt actually used right now, so some rawtx decoders
    # return the varint (like decoder), and some return UINT_MAX (4294967295)
    { 'txid' => hash, 'vout' => index, 'sequence' => MAX_32BIT_INTEGER,
      'scriptSig' => {'hex' => hex_stringify(script), 
        'asm' => disassemble_script(script) } }
  end

  # Parse outputs:
  outs = (0...shift_varint(bytes)).collect do |i|
    value, script = shift_u64(bytes), shift_varchar(bytes)

    { 'value' => self.class.bytes_to_ui(value).to_f/1e8, 'n' => i, 
      'scriptPubKey' => {'hex' => hex_stringify(script), 
        'asm' => disassemble_script(script) } }
  end

  lock_time = shift_u32 bytes

  {'vin' => ins, 'vout' => outs, 'lock_time' => lock_time, 'ver' => version, 
    'vin_sz' => ins.length, 'vout_sz' => outs.length, 'size' => size}
end

Private Instance Methods

disassemble_script(bytes) click to toggle source
# File lib/counterparty/raw_tx.rb, line 91
def disassemble_script(bytes)
  # We don't actually need to reference a hash argument to achieve disassembly:
  btc_script = Bitcoin::Script.new String.new
  chunks = btc_script.parse bytes.pack('C*')
  btc_script.to_string chunks
end
hex_stringify(nibbles) click to toggle source
# File lib/counterparty/raw_tx.rb, line 98
def hex_stringify(nibbles)
  nibbles.collect{|c| '%02x' % c}.join
end
shift_u64(bytes) click to toggle source

64 bit requests are an exception, and kept as 8-byte arrays:

# File lib/counterparty/raw_tx.rb, line 127
def shift_u64(bytes)
  bytes.slice!(0,8)
end
shift_varchar(bytes) click to toggle source

en.bitcoin.it/wiki/Protocol_specification#Variable_length_string

# File lib/counterparty/raw_tx.rb, line 103
def shift_varchar(bytes)
  bytes.slice! 0, shift_varint(bytes)
end
shift_varint(bytes) click to toggle source

en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer

# File lib/counterparty/raw_tx.rb, line 108
def shift_varint(bytes)
  n = shift_u8 bytes
  case n
    when 0xfd then shift_u16 bytes
    when 0xfe then shift_u32 bytes
    when 0xff then shift_u64 bytes
    else n
  end
end