bitcoinrb 0.5.0 → 0.9.0
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/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +11 -1
- data/bitcoinrb.gemspec +7 -6
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
    
        data/lib/bitcoin/key.rb
    CHANGED
    
    | @@ -10,6 +10,7 @@ module Bitcoin | |
| 10 10 | 
             
                COMPRESSED_PUBLIC_KEY_SIZE = 33
         | 
| 11 11 | 
             
                SIGNATURE_SIZE = 72
         | 
| 12 12 | 
             
                COMPACT_SIGNATURE_SIZE = 65
         | 
| 13 | 
            +
                COMPACT_SIG_HEADER_BYTE = 0x1b
         | 
| 13 14 |  | 
| 14 15 | 
             
                attr_accessor :priv_key
         | 
| 15 16 | 
             
                attr_accessor :pubkey
         | 
| @@ -28,8 +29,7 @@ module Bitcoin | |
| 28 29 | 
             
                # @param [Integer] key_type a key type which determine address type.
         | 
| 29 30 | 
             
                # @param [Boolean] compressed [Deprecated] whether public key is compressed.
         | 
| 30 31 | 
             
                # @return [Bitcoin::Key] a key object.
         | 
| 31 | 
            -
                def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
         | 
| 32 | 
            -
                  puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
         | 
| 32 | 
            +
                def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
         | 
| 33 33 | 
             
                  if key_type
         | 
| 34 34 | 
             
                    @key_type = key_type
         | 
| 35 35 | 
             
                    compressed = @key_type != TYPES[:uncompressed]
         | 
| @@ -39,13 +39,14 @@ module Bitcoin | |
| 39 39 | 
             
                  @secp256k1_module =  Bitcoin.secp_impl
         | 
| 40 40 | 
             
                  @priv_key = priv_key
         | 
| 41 41 | 
             
                  if @priv_key
         | 
| 42 | 
            -
                    raise ArgumentError,  | 
| 42 | 
            +
                    raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
         | 
| 43 43 | 
             
                  end
         | 
| 44 44 | 
             
                  if pubkey
         | 
| 45 45 | 
             
                    @pubkey = pubkey
         | 
| 46 46 | 
             
                  else
         | 
| 47 47 | 
             
                    @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
         | 
| 48 48 | 
             
                  end
         | 
| 49 | 
            +
                  raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
         | 
| 49 50 | 
             
                end
         | 
| 50 51 |  | 
| 51 52 | 
             
                # generate key pair
         | 
| @@ -63,9 +64,9 @@ module Bitcoin | |
| 63 64 | 
             
                  data = hex[2...-8].htb
         | 
| 64 65 | 
             
                  checksum = hex[-8..-1]
         | 
| 65 66 | 
             
                  raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
         | 
| 66 | 
            -
                  raise ArgumentError,  | 
| 67 | 
            +
                  raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum
         | 
| 67 68 | 
             
                  key_len = data.bytesize
         | 
| 68 | 
            -
                  if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1]. | 
| 69 | 
            +
                  if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1
         | 
| 69 70 | 
             
                    key_type = TYPES[:compressed]
         | 
| 70 71 | 
             
                    data = data[0..-2]
         | 
| 71 72 | 
             
                  elsif key_len == 32
         | 
| @@ -76,6 +77,23 @@ module Bitcoin | |
| 76 77 | 
             
                  new(priv_key: data.bth, key_type: key_type)
         | 
| 77 78 | 
             
                end
         | 
| 78 79 |  | 
| 80 | 
            +
                # Generate from xonly public key.
         | 
| 81 | 
            +
                # @param [String] xonly_pubkey xonly public key with hex format.
         | 
| 82 | 
            +
                # @return [Bitcoin::Key] key object has public key.
         | 
| 83 | 
            +
                def self.from_xonly_pubkey(xonly_pubkey)
         | 
| 84 | 
            +
                  raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
         | 
| 85 | 
            +
                  Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                # Generate from public key point.
         | 
| 89 | 
            +
                # @param [ECDSA::Point] point Public key point.
         | 
| 90 | 
            +
                # @param [Boolean] compressed whether compressed or not.
         | 
| 91 | 
            +
                # @return [Bitcoin::Key]
         | 
| 92 | 
            +
                def self.from_point(point, compressed: true)
         | 
| 93 | 
            +
                  pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
         | 
| 94 | 
            +
                  Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 79 97 | 
             
                # export private key with wif format
         | 
| 80 98 | 
             
                def to_wif
         | 
| 81 99 | 
             
                  version = Bitcoin.chain_params.privkey_version
         | 
| @@ -88,30 +106,69 @@ module Bitcoin | |
| 88 106 | 
             
                # sign +data+ with private key
         | 
| 89 107 | 
             
                # @param [String] data a data to be signed with binary format
         | 
| 90 108 | 
             
                # @param [Boolean] low_r flag to apply low-R.
         | 
| 91 | 
            -
                # @param [String] extra_entropy the extra entropy for rfc6979.
         | 
| 109 | 
            +
                # @param [String] extra_entropy the extra entropy with binary format for rfc6979.
         | 
| 110 | 
            +
                # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
         | 
| 92 111 | 
             
                # @return [String] signature data with binary format
         | 
| 93 | 
            -
                def sign(data, low_r = true, extra_entropy = nil)
         | 
| 94 | 
            -
                   | 
| 95 | 
            -
                   | 
| 96 | 
            -
                     | 
| 97 | 
            -
                     | 
| 98 | 
            -
                       | 
| 99 | 
            -
                       | 
| 100 | 
            -
             | 
| 112 | 
            +
                def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
         | 
| 113 | 
            +
                  case algo
         | 
| 114 | 
            +
                  when :ecdsa
         | 
| 115 | 
            +
                    sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
         | 
| 116 | 
            +
                    if low_r && !sig_has_low_r?(sig)
         | 
| 117 | 
            +
                      counter = 1
         | 
| 118 | 
            +
                      until sig_has_low_r?(sig)
         | 
| 119 | 
            +
                        extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
         | 
| 120 | 
            +
                        sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
         | 
| 121 | 
            +
                        counter += 1
         | 
| 122 | 
            +
                      end
         | 
| 101 123 | 
             
                    end
         | 
| 124 | 
            +
                    sig
         | 
| 125 | 
            +
                  when :schnorr
         | 
| 126 | 
            +
                    secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr)
         | 
| 127 | 
            +
                  else
         | 
| 128 | 
            +
                    raise ArgumentError "Unsupported algo specified: #{algo}"
         | 
| 102 129 | 
             
                  end
         | 
| 103 | 
            -
             | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                # Sign compact signature.
         | 
| 133 | 
            +
                # @param [String] data message digest to be signed.
         | 
| 134 | 
            +
                # @return [String] compact signature with binary format.
         | 
| 135 | 
            +
                def sign_compact(data)
         | 
| 136 | 
            +
                  signature, rec = secp256k1_module.sign_compact(data, priv_key)
         | 
| 137 | 
            +
                  rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
         | 
| 138 | 
            +
                  [rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
         | 
| 139 | 
            +
                    ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                # Recover public key from compact signature.
         | 
| 143 | 
            +
                # @param [String] data message digest using signature.
         | 
| 144 | 
            +
                # @param [String] signature signature with binary format.
         | 
| 145 | 
            +
                # @return [Bitcoin::Key] Recovered public key.
         | 
| 146 | 
            +
                def self.recover_compact(data, signature)
         | 
| 147 | 
            +
                  rec_id = signature.unpack1('C')
         | 
| 148 | 
            +
                  rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
         | 
| 149 | 
            +
                  raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
         | 
| 150 | 
            +
                  rec = rec & 3
         | 
| 151 | 
            +
                  compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
         | 
| 152 | 
            +
                  Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
         | 
| 104 153 | 
             
                end
         | 
| 105 154 |  | 
| 106 155 | 
             
                # verify signature using public key
         | 
| 107 156 | 
             
                # @param [String] sig signature data with binary format
         | 
| 108 | 
            -
                # @param [String]  | 
| 157 | 
            +
                # @param [String] data original message
         | 
| 158 | 
            +
                # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
         | 
| 109 159 | 
             
                # @return [Boolean] verify result
         | 
| 110 | 
            -
                def verify(sig,  | 
| 160 | 
            +
                def verify(sig, data, algo: :ecdsa)
         | 
| 111 161 | 
             
                  return false unless valid_pubkey?
         | 
| 112 162 | 
             
                  begin
         | 
| 113 | 
            -
                     | 
| 114 | 
            -
                     | 
| 163 | 
            +
                    case algo
         | 
| 164 | 
            +
                    when :ecdsa
         | 
| 165 | 
            +
                      sig = ecdsa_signature_parse_der_lax(sig)
         | 
| 166 | 
            +
                      secp256k1_module.verify_sig(data, sig, pubkey)
         | 
| 167 | 
            +
                    when :schnorr
         | 
| 168 | 
            +
                      secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
         | 
| 169 | 
            +
                    else
         | 
| 170 | 
            +
                      false
         | 
| 171 | 
            +
                    end
         | 
| 115 172 | 
             
                  rescue Exception
         | 
| 116 173 | 
             
                    false
         | 
| 117 174 | 
             
                  end
         | 
| @@ -148,10 +205,16 @@ module Bitcoin | |
| 148 205 | 
             
                # @return [ECDSA::Point]
         | 
| 149 206 | 
             
                def to_point
         | 
| 150 207 | 
             
                  p = pubkey
         | 
| 151 | 
            -
                  p ||= generate_pubkey(priv_key, compressed: compressed)
         | 
| 208 | 
            +
                  p ||= generate_pubkey(priv_key, compressed: compressed?)
         | 
| 152 209 | 
             
                  ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
         | 
| 153 210 | 
             
                end
         | 
| 154 211 |  | 
| 212 | 
            +
                # get xonly public key (32 bytes).
         | 
| 213 | 
            +
                # @return [String] xonly public key with hex format
         | 
| 214 | 
            +
                def xonly_pubkey
         | 
| 215 | 
            +
                  pubkey[2..65]
         | 
| 216 | 
            +
                end
         | 
| 217 | 
            +
             | 
| 155 218 | 
             
                # check +pubkey+ (hex) is compress or uncompress pubkey.
         | 
| 156 219 | 
             
                def self.compress_or_uncompress_pubkey?(pubkey)
         | 
| 157 220 | 
             
                  p = pubkey.htb
         | 
| @@ -225,14 +288,8 @@ module Bitcoin | |
| 225 288 | 
             
                end
         | 
| 226 289 |  | 
| 227 290 | 
             
                # fully validate whether this is a valid public key (more expensive than IsValid())
         | 
| 228 | 
            -
                def fully_valid_pubkey?
         | 
| 229 | 
            -
                   | 
| 230 | 
            -
                  begin
         | 
| 231 | 
            -
                    point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
         | 
| 232 | 
            -
                    ECDSA::Group::Secp256k1.valid_public_key?(point)
         | 
| 233 | 
            -
                  rescue ECDSA::Format::DecodeError
         | 
| 234 | 
            -
                    false
         | 
| 235 | 
            -
                  end
         | 
| 291 | 
            +
                def fully_valid_pubkey?(allow_hybrid = false)
         | 
| 292 | 
            +
                  valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
         | 
| 236 293 | 
             
                end
         | 
| 237 294 |  | 
| 238 295 | 
             
                private
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Bitcoin
         | 
| 2 | 
            +
              module Message
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                # addrv2 message class.
         | 
| 5 | 
            +
                # https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
         | 
| 6 | 
            +
                class AddrV2 < Base
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  COMMAND = 'addrv2'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  attr_reader :addrs
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def initialize(addrs = [])
         | 
| 13 | 
            +
                    @addrs = addrs
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.parse_from_payload(payload)
         | 
| 17 | 
            +
                    buf = StringIO.new(payload)
         | 
| 18 | 
            +
                    addr_count = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 19 | 
            +
                    v2 = new
         | 
| 20 | 
            +
                    addr_count.times do
         | 
| 21 | 
            +
                      v2.addrs << NetworkAddr.parse_from_payload(buf, type: NetworkAddr::TYPE[:addr_v2])
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                    v2
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def to_payload
         | 
| 27 | 
            +
                    buf = Bitcoin.pack_var_int(addrs.size)
         | 
| 28 | 
            +
                    buf << (addrs.map { |a| a.to_payload(type: NetworkAddr::TYPE[:addr_v2])}.join)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
    
        data/lib/bitcoin/message/base.rb
    CHANGED
    
    | @@ -23,6 +23,22 @@ module Bitcoin | |
| 23 23 | 
             
                    raise 'to_payload must be implemented in a child class.'
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 | 
            +
                  # Decode message data to message object.
         | 
| 27 | 
            +
                  # @param [String] message with binary format.
         | 
| 28 | 
            +
                  # @return [Bitcoin::Message::XXX] An instance of a class that inherits Bitcoin::Message::Base
         | 
| 29 | 
            +
                  # @raise [ArgumentError] Occurs for data that cannot be decoded.
         | 
| 30 | 
            +
                  def self.from_pkt(message)
         | 
| 31 | 
            +
                    buf = StringIO.new(message)
         | 
| 32 | 
            +
                    magic = buf.read(4)
         | 
| 33 | 
            +
                    raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
         | 
| 34 | 
            +
                    command = buf.read(12).delete("\x00")
         | 
| 35 | 
            +
                    length = buf.read(4).unpack1('V')
         | 
| 36 | 
            +
                    checksum = buf.read(4)
         | 
| 37 | 
            +
                    payload = buf.read(length)
         | 
| 38 | 
            +
                    raise ArgumentError, 'Checksum do not match.' unless checksum == Bitcoin.double_sha256(payload)[0...4]
         | 
| 39 | 
            +
                    Bitcoin::Message.decode(command, payload&.bth)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 26 42 | 
             
                end
         | 
| 27 43 |  | 
| 28 44 | 
             
              end
         | 
| @@ -19,8 +19,8 @@ module Bitcoin | |
| 19 19 |  | 
| 20 20 | 
             
                  def self.parse_from_payload(payload)
         | 
| 21 21 | 
             
                    buf = StringIO.new(payload)
         | 
| 22 | 
            -
                    type = buf.read(1). | 
| 23 | 
            -
                    hash = buf.read(32). | 
| 22 | 
            +
                    type = buf.read(1).unpack1('C')
         | 
| 23 | 
            +
                    hash = buf.read(32).unpack1('H*')
         | 
| 24 24 | 
             
                    count = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 25 25 | 
             
                    headers = count.times.map{buf.read(32).bth}
         | 
| 26 26 | 
             
                    self.new(type, hash, headers)
         | 
| @@ -21,7 +21,7 @@ module Bitcoin | |
| 21 21 |  | 
| 22 22 | 
             
                  def self.parse_from_payload(payload)
         | 
| 23 23 | 
             
                    buf = StringIO.new(payload)
         | 
| 24 | 
            -
                    type = buf.read(1). | 
| 24 | 
            +
                    type = buf.read(1).unpack1("C")
         | 
| 25 25 | 
             
                    hash = buf.read(32).bth
         | 
| 26 26 | 
             
                    header = buf.read(32).bth
         | 
| 27 27 | 
             
                    count = Bitcoin.unpack_var_int_from_io(buf)
         | 
| @@ -19,7 +19,7 @@ module Bitcoin | |
| 19 19 |  | 
| 20 20 | 
             
                  def self.parse_from_payload(payload)
         | 
| 21 21 | 
             
                    buf = StringIO.new(payload)
         | 
| 22 | 
            -
                    type = buf.read(1). | 
| 22 | 
            +
                    type = buf.read(1).unpack1("C")
         | 
| 23 23 | 
             
                    hash = buf.read(32).bth
         | 
| 24 24 | 
             
                    len = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 25 25 | 
             
                    filter = buf.read(len).bth
         | 
| @@ -23,9 +23,9 @@ module Bitcoin | |
| 23 23 | 
             
                    buf = StringIO.new(payload)
         | 
| 24 24 | 
             
                    filter_count = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 25 25 | 
             
                    filter = buf.read(filter_count).unpack('C*')
         | 
| 26 | 
            -
                    func_count = buf.read(4). | 
| 27 | 
            -
                    tweak = buf.read(4). | 
| 28 | 
            -
                    flag = buf.read(1). | 
| 26 | 
            +
                    func_count = buf.read(4).unpack1('V')
         | 
| 27 | 
            +
                    tweak = buf.read(4).unpack1('V')
         | 
| 28 | 
            +
                    flag = buf.read(1).unpack1('C')
         | 
| 29 29 | 
             
                    FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| @@ -22,7 +22,7 @@ module Bitcoin | |
| 22 22 | 
             
                  def self.parse_from_payload(payload)
         | 
| 23 23 | 
             
                    buf = StringIO.new(payload)
         | 
| 24 24 | 
             
                    header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
         | 
| 25 | 
            -
                    nonce = buf.read(8). | 
| 25 | 
            +
                    nonce = buf.read(8).unpack1('q*')
         | 
| 26 26 | 
             
                    short_ids_len = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 27 27 | 
             
                    short_ids = short_ids_len.times.map do
         | 
| 28 28 | 
             
                       buf.read(6).reverse.bth.to_i(16)
         | 
| @@ -26,7 +26,7 @@ module Bitcoin | |
| 26 26 | 
             
                  # parse inventory payload
         | 
| 27 27 | 
             
                  def self.parse_from_payload(payload)
         | 
| 28 28 | 
             
                    raise Error, 'invalid inventory size.' if payload.bytesize != 36
         | 
| 29 | 
            -
                    identifier = payload[0..4]. | 
| 29 | 
            +
                    identifier = payload[0..4].unpack1('V')
         | 
| 30 30 | 
             
                    hash = payload[4..-1].bth # internal byte order
         | 
| 31 31 | 
             
                    new(identifier, hash)
         | 
| 32 32 | 
             
                  end
         | 
| @@ -20,7 +20,7 @@ module Bitcoin | |
| 20 20 | 
             
                    m = new
         | 
| 21 21 | 
             
                    buf = StringIO.new(payload)
         | 
| 22 22 | 
             
                    m.header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
         | 
| 23 | 
            -
                    m.tx_count = buf.read(4). | 
| 23 | 
            +
                    m.tx_count = buf.read(4).unpack1('V')
         | 
| 24 24 | 
             
                    hash_count = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 25 25 | 
             
                    hash_count.times do
         | 
| 26 26 | 
             
                      m.hashes << buf.read(32).bth
         | 
| @@ -1,10 +1,16 @@ | |
| 1 1 | 
             
            require 'ipaddr'
         | 
| 2 | 
            +
            require 'base32'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module Bitcoin
         | 
| 4 5 | 
             
              module Message
         | 
| 5 6 |  | 
| 7 | 
            +
                NETWORK_ID = {ipv4: 0x01, ipv6: 0x02, tor_v2: 0x03, tor_v3: 0x04, i2p: 0x05, cjdns: 0x06}
         | 
| 8 | 
            +
                INTERNAL_IN_IPV6_PREFIX = "fd6b:88c0:8724"
         | 
| 9 | 
            +
             | 
| 6 10 | 
             
                class NetworkAddr
         | 
| 7 11 |  | 
| 12 | 
            +
                  TYPE = {legacy: 0x01, addr_v2: 0x02}
         | 
| 13 | 
            +
             | 
| 8 14 | 
             
                  # unix time.
         | 
| 9 15 | 
             
                  # Nodes advertising their own IP address set this to the current time.
         | 
| 10 16 | 
             
                  # Nodes advertising IP addresses they’ve connected to set this to the last time they connected to that node.
         | 
| @@ -14,47 +20,164 @@ module Bitcoin | |
| 14 20 | 
             
                  # The services the node advertised in its version message.
         | 
| 15 21 | 
             
                  attr_accessor :services
         | 
| 16 22 |  | 
| 17 | 
            -
                  attr_accessor : | 
| 23 | 
            +
                  attr_accessor :net # network ID that defined by BIP-155
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  # Network address. The interpretation depends on networkID.
         | 
| 26 | 
            +
                  # If ipv4 or ipv6 this field is a IPAddr object, otherwise hex string.
         | 
| 27 | 
            +
                  attr_accessor :addr
         | 
| 18 28 |  | 
| 19 29 | 
             
                  attr_accessor :port
         | 
| 20 30 |  | 
| 21 31 | 
             
                  attr_reader :skip_time
         | 
| 22 32 |  | 
| 23 | 
            -
                  def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port, | 
| 33 | 
            +
                  def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port,
         | 
| 34 | 
            +
                                 services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i, net: NETWORK_ID[:ipv4])
         | 
| 24 35 | 
             
                    @time = time
         | 
| 25 | 
            -
                    @ip_addr = IPAddr.new(ip)
         | 
| 26 36 | 
             
                    @port = port
         | 
| 27 37 | 
             
                    @services = services
         | 
| 38 | 
            +
                    @net = net
         | 
| 39 | 
            +
                    case net
         | 
| 40 | 
            +
                    when NETWORK_ID[:ipv4], NETWORK_ID[:ipv6]
         | 
| 41 | 
            +
                      @addr = IPAddr.new(ip) if ip
         | 
| 42 | 
            +
                    end
         | 
| 28 43 | 
             
                  end
         | 
| 29 44 |  | 
| 30 | 
            -
                   | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                     | 
| 36 | 
            -
                     | 
| 37 | 
            -
             | 
| 38 | 
            -
                     | 
| 45 | 
            +
                  # Parse addr payload
         | 
| 46 | 
            +
                  # @param [String] payload payload of addr
         | 
| 47 | 
            +
                  # @param [Integer] type Address format type
         | 
| 48 | 
            +
                  # @return [NetworkAddr]
         | 
| 49 | 
            +
                  def self.parse_from_payload(payload, type: TYPE[:legacy])
         | 
| 50 | 
            +
                    case type
         | 
| 51 | 
            +
                    when TYPE[:legacy]
         | 
| 52 | 
            +
                      load_legacy_payload(payload)
         | 
| 53 | 
            +
                    when TYPE[:addr_v2]
         | 
| 54 | 
            +
                      load_addr_v2_payload(payload)
         | 
| 55 | 
            +
                    else
         | 
| 56 | 
            +
                      raise Bitcoin::Message::Error, "Unknown type: #{type}."
         | 
| 57 | 
            +
                    end
         | 
| 39 58 | 
             
                  end
         | 
| 40 59 |  | 
| 41 60 | 
             
                  def self.local_addr
         | 
| 42 61 | 
             
                    addr = new
         | 
| 43 | 
            -
                    addr. | 
| 62 | 
            +
                    addr.addr = IPAddr.new('127.0.0.1')
         | 
| 44 63 | 
             
                    addr.port = Bitcoin.chain_params.default_port
         | 
| 45 64 | 
             
                    addr.services = DEFAULT_SERVICE_FLAGS
         | 
| 46 65 | 
             
                    addr
         | 
| 47 66 | 
             
                  end
         | 
| 48 67 |  | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 68 | 
            +
                  # Show addr string. e.g 127.0.0.1
         | 
| 69 | 
            +
                  def addr_string
         | 
| 70 | 
            +
                    case net
         | 
| 71 | 
            +
                    when NETWORK_ID[:ipv4]
         | 
| 72 | 
            +
                      addr.native
         | 
| 73 | 
            +
                    when NETWORK_ID[:ipv6]
         | 
| 74 | 
            +
                      if addr.to_s.start_with?(INTERNAL_IN_IPV6_PREFIX)
         | 
| 75 | 
            +
                        Base32.encode(addr.hton[6..-1]).downcase.delete('=') + ".internal"
         | 
| 76 | 
            +
                      else
         | 
| 77 | 
            +
                        addr.to_s
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    when NETWORK_ID[:tor_v2]
         | 
| 80 | 
            +
                      Base32.encode(addr.htb).downcase + ".onion"
         | 
| 81 | 
            +
                    when NETWORK_ID[:tor_v3]
         | 
| 82 | 
            +
                      # TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
         | 
| 83 | 
            +
                      pubkey = addr.htb
         | 
| 84 | 
            +
                      checksum = OpenSSL::Digest.new('SHA3-256').digest('.onion checksum' + pubkey + "\x03")
         | 
| 85 | 
            +
                      Base32.encode(pubkey + checksum[0...2] + "\x03").downcase + ".onion"
         | 
| 86 | 
            +
                    when NETWORK_ID[:i2p]
         | 
| 87 | 
            +
                      Base32.encode(addr.htb).downcase.delete('=') + ".b32.i2p"
         | 
| 88 | 
            +
                    when NETWORK_ID[:cjdns]
         | 
| 89 | 
            +
                      addr.to_s
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def to_payload(skip_time = false, type: TYPE[:legacy])
         | 
| 94 | 
            +
                    case type
         | 
| 95 | 
            +
                    when TYPE[:legacy]
         | 
| 96 | 
            +
                      legacy_payload(skip_time)
         | 
| 97 | 
            +
                    when TYPE[:addr_v2]
         | 
| 98 | 
            +
                      v2_payload
         | 
| 99 | 
            +
                    else
         | 
| 100 | 
            +
                      raise Bitcoin::Message::Error, "Unknown type: #{type}."
         | 
| 101 | 
            +
                    end
         | 
| 51 102 | 
             
                  end
         | 
| 52 103 |  | 
| 53 | 
            -
                   | 
| 104 | 
            +
                  # Load addr payload with legacy format.
         | 
| 105 | 
            +
                  def self.load_legacy_payload(payload)
         | 
| 106 | 
            +
                    buf = payload.is_a?(String) ? StringIO.new(payload) : payload
         | 
| 107 | 
            +
                    has_time = buf.size > 26
         | 
| 108 | 
            +
                    addr = NetworkAddr.new(time: nil)
         | 
| 109 | 
            +
                    addr.time = buf.read(4).unpack1('V') if has_time
         | 
| 110 | 
            +
                    addr.services = buf.read(8).unpack1('Q')
         | 
| 111 | 
            +
                    addr.addr = IPAddr::new_ntoh(buf.read(16))
         | 
| 112 | 
            +
                    addr.port = buf.read(2).unpack1('n')
         | 
| 113 | 
            +
                    addr
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  # Load addr payload with addr v2 format.
         | 
| 117 | 
            +
                  def self.load_addr_v2_payload(payload)
         | 
| 118 | 
            +
                    buf = payload.is_a?(String) ? StringIO.new(payload) : payload
         | 
| 119 | 
            +
                    addr = NetworkAddr.new(time: buf.read(4).unpack1('V'))
         | 
| 120 | 
            +
                    addr.services = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 121 | 
            +
                    addr.net = buf.read(1).unpack1('C')
         | 
| 122 | 
            +
                    raise Bitcoin::Message::Error, "Unknown network id: #{addr.net}" unless NETWORK_ID.value?(addr.net)
         | 
| 123 | 
            +
                    addr_len = Bitcoin.unpack_var_int_from_io(buf)
         | 
| 124 | 
            +
                    addr.addr = case addr.net 
         | 
| 125 | 
            +
                                when NETWORK_ID[:ipv4]
         | 
| 126 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid IPv4 address." unless addr_len == 4
         | 
| 127 | 
            +
                                  IPAddr::new_ntoh(buf.read(addr_len))
         | 
| 128 | 
            +
                                when NETWORK_ID[:ipv6]
         | 
| 129 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid IPv6 address." unless addr_len == 16
         | 
| 130 | 
            +
                                  a = IPAddr::new_ntoh(buf.read(addr_len))
         | 
| 131 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid IPv6 address." if a.ipv4_mapped?
         | 
| 132 | 
            +
                                  a
         | 
| 133 | 
            +
                                when NETWORK_ID[:tor_v2]
         | 
| 134 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid Tor v2 address." unless addr_len == 10
         | 
| 135 | 
            +
                                  buf.read(addr_len).bth
         | 
| 136 | 
            +
                                when NETWORK_ID[:tor_v3]
         | 
| 137 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid Tor v3 address." unless addr_len == 32
         | 
| 138 | 
            +
                                  buf.read(addr_len).bth
         | 
| 139 | 
            +
                                when NETWORK_ID[:i2p]
         | 
| 140 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid I2P address." unless addr_len == 32
         | 
| 141 | 
            +
                                  buf.read(addr_len).bth
         | 
| 142 | 
            +
                                when NETWORK_ID[:cjdns]
         | 
| 143 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid CJDNS address." unless addr_len == 16
         | 
| 144 | 
            +
                                  a = IPAddr::new_ntoh(buf.read(addr_len))
         | 
| 145 | 
            +
                                  raise Bitcoin::Message::Error, "Invalid CJDNS address." unless a.to_s.start_with?('fc00:')
         | 
| 146 | 
            +
                                  a
         | 
| 147 | 
            +
                                end
         | 
| 148 | 
            +
                    addr.port = buf.read(2).unpack1('n')
         | 
| 149 | 
            +
                    addr
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  def legacy_payload(skip_time)
         | 
| 54 153 | 
             
                    p = ''
         | 
| 55 154 | 
             
                    p << [time].pack('V') unless skip_time
         | 
| 56 | 
            -
                     | 
| 57 | 
            -
                    p << [services].pack('Q') <<  | 
| 155 | 
            +
                    ip = addr.ipv4? ? addr.ipv4_mapped : addr
         | 
| 156 | 
            +
                    p << [services].pack('Q') << ip.hton << [port].pack('n')
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  def v2_payload
         | 
| 160 | 
            +
                    p = [time].pack('V')
         | 
| 161 | 
            +
                    p << Bitcoin.pack_var_int(services)
         | 
| 162 | 
            +
                    p << [net].pack('C')
         | 
| 163 | 
            +
                    case net
         | 
| 164 | 
            +
                    when NETWORK_ID[:ipv4]
         | 
| 165 | 
            +
                      p << Bitcoin.pack_var_int(4)
         | 
| 166 | 
            +
                      p << addr.to_i.to_s(16).htb
         | 
| 167 | 
            +
                    when NETWORK_ID[:ipv6]
         | 
| 168 | 
            +
                      p << Bitcoin.pack_var_int(16)
         | 
| 169 | 
            +
                      p << addr.hton
         | 
| 170 | 
            +
                    when NETWORK_ID[:tor_v2]
         | 
| 171 | 
            +
                      p << Bitcoin.pack_var_int(10)
         | 
| 172 | 
            +
                    when NETWORK_ID[:tor_v3]
         | 
| 173 | 
            +
                      p << Bitcoin.pack_var_int(32)
         | 
| 174 | 
            +
                    when NETWORK_ID[:i2p]
         | 
| 175 | 
            +
                      p << Bitcoin.pack_var_int(32)
         | 
| 176 | 
            +
                    when NETWORK_ID[:cjdns]
         | 
| 177 | 
            +
                      p << Bitcoin.pack_var_int(16)
         | 
| 178 | 
            +
                    end
         | 
| 179 | 
            +
                    p << [port].pack('n')
         | 
| 180 | 
            +
                    p
         | 
| 58 181 | 
             
                  end
         | 
| 59 182 |  | 
| 60 183 | 
             
                end
         | 
    
        data/lib/bitcoin/message/ping.rb
    CHANGED
    
    
    
        data/lib/bitcoin/message/pong.rb
    CHANGED
    
    
| @@ -21,8 +21,8 @@ module Bitcoin | |
| 21 21 |  | 
| 22 22 | 
             
                  def self.parse_from_payload(payload)
         | 
| 23 23 | 
             
                    buf = StringIO.new(payload)
         | 
| 24 | 
            -
                    mode = buf.read(1). | 
| 25 | 
            -
                    version = buf.read(8). | 
| 24 | 
            +
                    mode = buf.read(1).unpack1('c')
         | 
| 25 | 
            +
                    version = buf.read(8).unpack1('Q')
         | 
| 26 26 | 
             
                    new(mode, version)
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  |