From a06728bd4b8643671ac61512e8323dfe0c92a769 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 12:23:38 -0300 Subject: [PATCH 1/9] refactor(helpers): organize helpers in dir --- lib/bitcoin/block.rb | 4 ++-- lib/bitcoin/fetcher/uri_fetcher.rb | 2 +- lib/bitcoin/network/envelope.rb | 4 ++-- lib/bitcoin/network/messages/base_message.rb | 2 +- lib/bitcoin/op.rb | 6 +++--- lib/bitcoin/script.rb | 2 +- lib/bitcoin/tx.rb | 4 ++-- lib/ecc/private_key.rb | 2 +- lib/ecc/s256_point.rb | 6 +++--- lib/ecc/signature.rb | 2 +- lib/{ => helpers}/address_helper.rb | 2 +- lib/{ => helpers}/encoding_helper.rb | 0 lib/{ => helpers}/hash_helper.rb | 0 lib/{ => helpers}/script_helper.rb | 4 ++++ spec/bitcoin/block_spec.rb | 2 +- spec/bitcoin/network/envelope_spec.rb | 2 +- spec/bitcoin/network/messages/get_headers_spec.rb | 2 +- spec/bitcoin/network/messages/headers_spec.rb | 2 +- spec/bitcoin/network/messages/version_spec.rb | 2 +- spec/bitcoin/network/simple_node_spec.rb | 2 +- spec/bitcoin/op_spec.rb | 4 ++-- spec/bitcoin/script_spec.rb | 2 +- spec/bitcoin_data_io_spec.rb | 2 +- spec/ecc/s256_point_spec.rb | 4 ++-- spec/{ => helpers}/address_helper_spec.rb | 2 +- spec/{ => helpers}/encoding_helper_spec.rb | 2 +- spec/{ => helpers}/hash_helper_spec.rb | 2 ++ spec/{script_helper.rb => helpers/script_helper_spec.rb} | 2 ++ 28 files changed, 40 insertions(+), 32 deletions(-) rename lib/{ => helpers}/address_helper.rb (91%) rename lib/{ => helpers}/encoding_helper.rb (100%) rename lib/{ => helpers}/hash_helper.rb (100%) rename lib/{ => helpers}/script_helper.rb (77%) rename spec/{ => helpers}/address_helper_spec.rb (97%) rename spec/{ => helpers}/encoding_helper_spec.rb (99%) rename spec/{ => helpers}/hash_helper_spec.rb (94%) rename spec/{script_helper.rb => helpers/script_helper_spec.rb} (96%) diff --git a/lib/bitcoin/block.rb b/lib/bitcoin/block.rb index 5297bf5..a9de39a 100644 --- a/lib/bitcoin/block.rb +++ b/lib/bitcoin/block.rb @@ -1,6 +1,6 @@ require_relative '../bitcoin_data_io' -require_relative '../encoding_helper' -require_relative '../hash_helper' +require_relative '../helpers/encoding_helper' +require_relative '../helpers/hash_helper' module Bitcoin class Block diff --git a/lib/bitcoin/fetcher/uri_fetcher.rb b/lib/bitcoin/fetcher/uri_fetcher.rb index 94c3491..5dcc6a0 100644 --- a/lib/bitcoin/fetcher/uri_fetcher.rb +++ b/lib/bitcoin/fetcher/uri_fetcher.rb @@ -1,6 +1,6 @@ require_relative './fetcher' require_relative '../tx' -require_relative '../../encoding_helper' +require_relative '../../helpers/encoding_helper' require 'net/http' require 'stringio' diff --git a/lib/bitcoin/network/envelope.rb b/lib/bitcoin/network/envelope.rb index 3569be1..2c98145 100644 --- a/lib/bitcoin/network/envelope.rb +++ b/lib/bitcoin/network/envelope.rb @@ -1,8 +1,8 @@ # encoding: ascii-8bit require_relative '../../bitcoin_data_io' -require_relative '../../encoding_helper' -require_relative '../../hash_helper' +require_relative '../../helpers/encoding_helper' +require_relative '../../helpers/hash_helper' module Bitcoin module Network diff --git a/lib/bitcoin/network/messages/base_message.rb b/lib/bitcoin/network/messages/base_message.rb index bed5a4c..6f6316d 100644 --- a/lib/bitcoin/network/messages/base_message.rb +++ b/lib/bitcoin/network/messages/base_message.rb @@ -1,5 +1,5 @@ # encoding: ascii-8bit -require_relative '../../../encoding_helper' +require_relative '../../../helpers/encoding_helper' module Bitcoin module Network diff --git a/lib/bitcoin/op.rb b/lib/bitcoin/op.rb index 0cc6a0b..71e3034 100644 --- a/lib/bitcoin/op.rb +++ b/lib/bitcoin/op.rb @@ -1,6 +1,6 @@ -require_relative '../hash_helper' -require_relative '../encoding_helper' -require_relative '../script_helper' +require_relative '../helpers/hash_helper' +require_relative '../helpers/encoding_helper' +require_relative '../helpers/script_helper' require_relative '../ecc/signature' require_relative '../ecc/s256_point' diff --git a/lib/bitcoin/script.rb b/lib/bitcoin/script.rb index 08aafa5..ddb5ad0 100644 --- a/lib/bitcoin/script.rb +++ b/lib/bitcoin/script.rb @@ -1,5 +1,5 @@ require_relative '../bitcoin_data_io' -require_relative '../encoding_helper' +require_relative '../helpers/encoding_helper' require_relative './op' module Bitcoin diff --git a/lib/bitcoin/tx.rb b/lib/bitcoin/tx.rb index 83ba2a9..dc13d16 100644 --- a/lib/bitcoin/tx.rb +++ b/lib/bitcoin/tx.rb @@ -1,6 +1,6 @@ require_relative '../bitcoin_data_io' -require_relative '../encoding_helper' -require_relative '../hash_helper' +require_relative '../helpers/encoding_helper' +require_relative '../helpers/hash_helper' require_relative './fetcher/uri_fetcher' require_relative 'script' require 'net/http' diff --git a/lib/ecc/private_key.rb b/lib/ecc/private_key.rb index aa3d5f4..18f1e7f 100644 --- a/lib/ecc/private_key.rb +++ b/lib/ecc/private_key.rb @@ -1,7 +1,7 @@ require_relative 's256_point' require_relative 'signature' require_relative 'secp256k1_constants' -require_relative '../encoding_helper' +require_relative '../helpers/encoding_helper' require 'openssl' module ECC diff --git a/lib/ecc/s256_point.rb b/lib/ecc/s256_point.rb index a501bcd..3f73e0c 100644 --- a/lib/ecc/s256_point.rb +++ b/lib/ecc/s256_point.rb @@ -1,9 +1,9 @@ require_relative 's256_field' require_relative 'point' require_relative 'secp256k1_constants' -require_relative '../encoding_helper' -require_relative '../hash_helper' -require_relative '../address_helper' +require_relative '../helpers/encoding_helper' +require_relative '../helpers/hash_helper' +require_relative '../helpers/address_helper' module ECC class S256Point < Point include EncodingHelper diff --git a/lib/ecc/signature.rb b/lib/ecc/signature.rb index 2bb761d..a9c59a8 100644 --- a/lib/ecc/signature.rb +++ b/lib/ecc/signature.rb @@ -1,4 +1,4 @@ -require_relative '../encoding_helper' +require_relative '../helpers/encoding_helper' module ECC class Signature include EncodingHelper diff --git a/lib/address_helper.rb b/lib/helpers/address_helper.rb similarity index 91% rename from lib/address_helper.rb rename to lib/helpers/address_helper.rb index 3bd1dd0..bf9289e 100644 --- a/lib/address_helper.rb +++ b/lib/helpers/address_helper.rb @@ -1,6 +1,6 @@ # encoding: ascii-8bit -require 'encoding_helper' +require_relative 'encoding_helper' module AddressHelper include EncodingHelper diff --git a/lib/encoding_helper.rb b/lib/helpers/encoding_helper.rb similarity index 100% rename from lib/encoding_helper.rb rename to lib/helpers/encoding_helper.rb diff --git a/lib/hash_helper.rb b/lib/helpers/hash_helper.rb similarity index 100% rename from lib/hash_helper.rb rename to lib/helpers/hash_helper.rb diff --git a/lib/script_helper.rb b/lib/helpers/script_helper.rb similarity index 77% rename from lib/script_helper.rb rename to lib/helpers/script_helper.rb index 1d7140b..3dca396 100644 --- a/lib/script_helper.rb +++ b/lib/helpers/script_helper.rb @@ -1,4 +1,8 @@ +require_relative 'encoding_helper' + module ScriptHelper + include EncodingHelper + def encode_num(num) return '' if num.zero? diff --git a/spec/bitcoin/block_spec.rb b/spec/bitcoin/block_spec.rb index 3bd9458..d225432 100644 --- a/spec/bitcoin/block_spec.rb +++ b/spec/bitcoin/block_spec.rb @@ -1,5 +1,5 @@ require 'bitcoin/block' -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Block do include EncodingHelper diff --git a/spec/bitcoin/network/envelope_spec.rb b/spec/bitcoin/network/envelope_spec.rb index c95dfa5..1b9231f 100644 --- a/spec/bitcoin/network/envelope_spec.rb +++ b/spec/bitcoin/network/envelope_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit require 'bitcoin/network/envelope' -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Network::Envelope do include EncodingHelper diff --git a/spec/bitcoin/network/messages/get_headers_spec.rb b/spec/bitcoin/network/messages/get_headers_spec.rb index 3f0e22a..d9a0eb3 100644 --- a/spec/bitcoin/network/messages/get_headers_spec.rb +++ b/spec/bitcoin/network/messages/get_headers_spec.rb @@ -1,6 +1,6 @@ require 'bitcoin/network/messages/get_headers' require 'bitcoin/block' -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Network::Messages::GetHeaders do include EncodingHelper diff --git a/spec/bitcoin/network/messages/headers_spec.rb b/spec/bitcoin/network/messages/headers_spec.rb index 35501d1..30ad6fd 100644 --- a/spec/bitcoin/network/messages/headers_spec.rb +++ b/spec/bitcoin/network/messages/headers_spec.rb @@ -1,6 +1,6 @@ require 'bitcoin/network/messages/headers' require 'bitcoin/block' -require 'encoding_helper' +require 'helpers/encoding_helper' require 'stringio' RSpec.describe Bitcoin::Network::Messages::Headers do diff --git a/spec/bitcoin/network/messages/version_spec.rb b/spec/bitcoin/network/messages/version_spec.rb index e3c6528..3e34e5d 100644 --- a/spec/bitcoin/network/messages/version_spec.rb +++ b/spec/bitcoin/network/messages/version_spec.rb @@ -1,5 +1,5 @@ require 'bitcoin/network/messages/version' -require 'encoding_helper' +require 'helpers/encoding_helper' require 'timecop' RSpec.describe Bitcoin::Network::Messages::Version do diff --git a/spec/bitcoin/network/simple_node_spec.rb b/spec/bitcoin/network/simple_node_spec.rb index 5ffd190..1c454df 100644 --- a/spec/bitcoin/network/simple_node_spec.rb +++ b/spec/bitcoin/network/simple_node_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit require 'bitcoin/network/simple_node' -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Network::SimpleNode do include EncodingHelper diff --git a/spec/bitcoin/op_spec.rb b/spec/bitcoin/op_spec.rb index 964fe71..b9cd733 100644 --- a/spec/bitcoin/op_spec.rb +++ b/spec/bitcoin/op_spec.rb @@ -2,8 +2,8 @@ require 'bitcoin/op' require 'ecc/s256_point' require 'ecc/signature' -require 'hash_helper' -require 'encoding_helper' +require 'helpers/hash_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Op do include EncodingHelper diff --git a/spec/bitcoin/script_spec.rb b/spec/bitcoin/script_spec.rb index b6065ef..9ac714f 100644 --- a/spec/bitcoin/script_spec.rb +++ b/spec/bitcoin/script_spec.rb @@ -1,6 +1,6 @@ require 'bitcoin/script' require 'bitcoin/op' -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe Bitcoin::Script do def _raw_script(hex_script) diff --git a/spec/bitcoin_data_io_spec.rb b/spec/bitcoin_data_io_spec.rb index 448d307..efc94d5 100644 --- a/spec/bitcoin_data_io_spec.rb +++ b/spec/bitcoin_data_io_spec.rb @@ -1,4 +1,4 @@ -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe BitcoinDataIO do def io(_hex_data) diff --git a/spec/ecc/s256_point_spec.rb b/spec/ecc/s256_point_spec.rb index b94d85c..643083c 100644 --- a/spec/ecc/s256_point_spec.rb +++ b/spec/ecc/s256_point_spec.rb @@ -4,8 +4,8 @@ require 'ecc/signature' require 'ecc/secp256k1_constants' require 'ecc/private_key' -require 'encoding_helper' -require 'hash_helper' +require 'helpers/encoding_helper' +require 'helpers/hash_helper' RSpec.describe ECC::S256Point do describe 'init' do diff --git a/spec/address_helper_spec.rb b/spec/helpers/address_helper_spec.rb similarity index 97% rename from spec/address_helper_spec.rb rename to spec/helpers/address_helper_spec.rb index 2851ce6..193c590 100644 --- a/spec/address_helper_spec.rb +++ b/spec/helpers/address_helper_spec.rb @@ -1,4 +1,4 @@ -require 'address_helper' +require 'helpers/address_helper' RSpec.describe AddressHelper do let(:described_module) { Object.new.extend described_class } diff --git a/spec/encoding_helper_spec.rb b/spec/helpers/encoding_helper_spec.rb similarity index 99% rename from spec/encoding_helper_spec.rb rename to spec/helpers/encoding_helper_spec.rb index 809059b..f01302c 100644 --- a/spec/encoding_helper_spec.rb +++ b/spec/helpers/encoding_helper_spec.rb @@ -1,5 +1,5 @@ # encoding: ascii-8bit -require 'encoding_helper' +require 'helpers/encoding_helper' RSpec.describe EncodingHelper do let(:described_module) { Object.new.extend described_class } diff --git a/spec/hash_helper_spec.rb b/spec/helpers/hash_helper_spec.rb similarity index 94% rename from spec/hash_helper_spec.rb rename to spec/helpers/hash_helper_spec.rb index 5b8c819..b61a0e2 100644 --- a/spec/hash_helper_spec.rb +++ b/spec/helpers/hash_helper_spec.rb @@ -1,3 +1,5 @@ +require 'helpers/hash_helper' + RSpec.describe HashHelper do describe '#hash160' do it 'computes the hash by sha256 followed by ripemd160' do diff --git a/spec/script_helper.rb b/spec/helpers/script_helper_spec.rb similarity index 96% rename from spec/script_helper.rb rename to spec/helpers/script_helper_spec.rb index 6bdfa4c..9b0ce1d 100644 --- a/spec/script_helper.rb +++ b/spec/helpers/script_helper_spec.rb @@ -1,3 +1,5 @@ +require 'helpers/script_helper' + RSpec.describe ScriptHelper do let(:described_module) { Object.new.extend described_class } From 53f943838e53cae07e5bb05ff8e7498015b11778 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 16:23:39 -0300 Subject: [PATCH 2/9] feat(helpers/hash_helper): add murmur3 implementation --- lib/helpers/hash_helper.rb | 55 ++++++++++++++++++++++++++++++++ spec/helpers/hash_helper_spec.rb | 16 ++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/helpers/hash_helper.rb b/lib/helpers/hash_helper.rb index 311396b..ec4ebb5 100644 --- a/lib/helpers/hash_helper.rb +++ b/lib/helpers/hash_helper.rb @@ -1,6 +1,8 @@ require 'digest' module HashHelper + MASK32 = 0xFFFFFFFF + def self.hash160(string) first_round = Digest::SHA256.digest(string) Digest::RMD160.digest(first_round) @@ -10,4 +12,57 @@ def self.hash256(string) first_round = Digest::SHA256.digest(string) Digest::SHA256.digest(first_round) end + + # rubocop:disable Metrics/MethodLength + def self.murmur3(string, seed: 0) # rubocop:disable Metrics/AbcSize + key_bytes = string.bytes + result_hash = seed + + rounded_end = (key_bytes.length & 0xfffffffc) + (0...rounded_end).step(4) do |i| + aux = block32(key_bytes, i) + result_hash ^= scramble32(aux) + result_hash = rotl32(result_hash, 13) + result_hash = result_hash * 5 + 0xe6546b64 + end + + val = key_bytes.length & 3 + + aux = 0 + (0...3).reverse_each do |i| + aux |= (key_bytes[rounded_end + i] & 0xff) << (8 * i) if val >= (i + 1) + end + + result_hash ^= scramble32(aux) + finalization_mix(result_hash ^ key_bytes.length) + end + # rubocop:enable Metrics/MethodLength + + private + + def self.finalization_mix(result_hash) + result_hash ^= ((result_hash & MASK32) >> 16) + result_hash *= 0x85ebca6b + result_hash ^= ((result_hash & MASK32) >> 13) + result_hash *= 0xc2b2ae35 + (result_hash ^ ((result_hash & MASK32) >> 16)) & MASK32 + end + + def self.block32(key_bytes, index) + (1..3).map do |i| + key_bytes[index + i] << (8 * i) + end.reduce(key_bytes[index], :|) + end + + def self.rotl32(item, bits_to_rotate) + ((item << bits_to_rotate) | ((item & MASK32) >> (32 - bits_to_rotate))) & MASK32 + end + + def self.scramble32(aux) + aux = (aux * 0xcc9e2d51) & MASK32 + aux = rotl32(aux, 15) + (aux * 0x1b873593) & MASK32 + end + + private_class_method :finalization_mix, :block32, :rotl32, :scramble32 end diff --git a/spec/helpers/hash_helper_spec.rb b/spec/helpers/hash_helper_spec.rb index b61a0e2..bfc1901 100644 --- a/spec/helpers/hash_helper_spec.rb +++ b/spec/helpers/hash_helper_spec.rb @@ -1,17 +1,29 @@ require 'helpers/hash_helper' RSpec.describe HashHelper do - describe '#hash160' do + describe '.hash160' do it 'computes the hash by sha256 followed by ripemd160' do hash = '77bc43ce98ed7de19a42b6f2b8978df300890a2d' expect(described_class.hash160('Bitcoin Guild rocks!').unpack1('H*')).to eq(hash) end end - describe '#hash256' do + describe '.hash256' do it 'computes the hash by two passes of sha256' do hash = 'a63898c9855b802d6db18886928affbc22b928c3ccd683e21d62da8f0af00a42' expect(described_class.hash256('Bitcoin Guild rocks!').unpack1('H*')).to eq(hash) end end + + describe '.murmur3' do + it 'computes the correct murmur3 hash for different message and seeds' do + [1203516251, 669393163, 819509628, 3765971536].each_with_index do |expected_hash, seed| + expect(described_class.murmur3('Bitcoin Guild rocks!', seed: seed)).to eq(expected_hash) + end + expect(described_class.murmur3('Goodbye!', seed: 8443760525)).to eq(468028502) + [1411415842, 2371772749, 4164319582, 2164673664].each_with_index do |expected_hash, seed| + expect(described_class.murmur3("Bitcoin Guild rocks! #{seed}")).to eq(expected_hash) + end + end + end end From 6b7bce05749e21333e35484ba44081e4eb85e1be Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 16:58:39 -0300 Subject: [PATCH 3/9] feat(helpers/encoding_helper): add bit_field_to_bytes --- lib/helpers/encoding_helper.rb | 11 +++++++++++ spec/helpers/encoding_helper_spec.rb | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/helpers/encoding_helper.rb b/lib/helpers/encoding_helper.rb index 1ca63ee..1134d28 100644 --- a/lib/helpers/encoding_helper.rb +++ b/lib/helpers/encoding_helper.rb @@ -83,6 +83,17 @@ def encode_varint(integer) end end + def bit_field_to_bytes(bit_field) + raise EncodingError.new("bit_field's length is not divisible by 8") if bit_field.length % 8 != 0 + + result = [0] * (bit_field.length / 8) + bit_field.each_with_index do |bit, index| + byte_index, bit_index = index.divmod(8) + result[byte_index] |= 1 << bit_index unless bit.zero? + end + result.pack('c*') + end + private def base58_to_num(base58_string) diff --git a/spec/helpers/encoding_helper_spec.rb b/spec/helpers/encoding_helper_spec.rb index f01302c..606926e 100644 --- a/spec/helpers/encoding_helper_spec.rb +++ b/spec/helpers/encoding_helper_spec.rb @@ -133,4 +133,14 @@ expect(described_module.bytes_to_hex("\xA0/")).to eq "a02f" end end + + describe '#bit_field_to_bytes' do + it 'produces the proper bytes' do + bit_field = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] + expect(described_module.bit_field_to_bytes(bit_field)) + .to eq "@\x00`\n\b\x00\x00\x01\t@" + end + end end From f2da1851962aa69382bf477979e23bea1024b557 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 17:02:12 -0300 Subject: [PATCH 4/9] style(spec/ecc/ecc): demove extra space --- spec/ecc/ecc_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/ecc/ecc_spec.rb b/spec/ecc/ecc_spec.rb index f719ae1..e7df776 100644 --- a/spec/ecc/ecc_spec.rb +++ b/spec/ecc/ecc_spec.rb @@ -43,11 +43,11 @@ let(:solution) { ECC::Point.new(x, y, a, b) } it 'returns the point scalar product (scalar on the right side)' do - expect(point1 * scalar ).to eq solution + expect(point1 * scalar).to eq solution end it 'returns the point scalar product (scalar on the left side)' do - expect(scalar * point1 ).to eq solution + expect(scalar * point1).to eq solution end end end From 9a966f81c5baeefc12868096d9549361dd205bcb Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 17:07:11 -0300 Subject: [PATCH 5/9] fix(spec/bitcoin_data_io): add require to be able to run single test --- spec/bitcoin_data_io_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bitcoin_data_io_spec.rb b/spec/bitcoin_data_io_spec.rb index efc94d5..cdfb2a2 100644 --- a/spec/bitcoin_data_io_spec.rb +++ b/spec/bitcoin_data_io_spec.rb @@ -1,4 +1,5 @@ require 'helpers/encoding_helper' +require 'bitcoin_data_io' RSpec.describe BitcoinDataIO do def io(_hex_data) From 18ff9d72d8e836f1aab6031e59093c8ce5525ef4 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 17:31:53 -0300 Subject: [PATCH 6/9] feat(helpers/encoding_helper): add bytes_to_bit_field --- lib/helpers/encoding_helper.rb | 12 ++++++++++++ spec/helpers/encoding_helper_spec.rb | 14 +++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/helpers/encoding_helper.rb b/lib/helpers/encoding_helper.rb index 1134d28..a653254 100644 --- a/lib/helpers/encoding_helper.rb +++ b/lib/helpers/encoding_helper.rb @@ -94,6 +94,18 @@ def bit_field_to_bytes(bit_field) result.pack('c*') end + def bytes_to_bit_field(bytes) + bytes = bytes.unpack('C*') + bit_field = [] + bytes.each do |byte| + 8.times do + bit_field << (byte & 1) + byte >>= 1 + end + end + bit_field + end + private def base58_to_num(base58_string) diff --git a/spec/helpers/encoding_helper_spec.rb b/spec/helpers/encoding_helper_spec.rb index 606926e..27ddf8e 100644 --- a/spec/helpers/encoding_helper_spec.rb +++ b/spec/helpers/encoding_helper_spec.rb @@ -139,8 +139,20 @@ bit_field = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] + bytes = "@\x00`\n\b\x00\x00\x01\t@" expect(described_module.bit_field_to_bytes(bit_field)) - .to eq "@\x00`\n\b\x00\x00\x01\t@" + .to eq bytes + end + end + + describe '#bytes_to_bit_field' do + it 'produces the proper bit_field' do + bit_field = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] + bytes = "@\x00`\n\b\x00\x00\x01\t@" + + expect(described_module.bytes_to_bit_field(bytes)).to eq bit_field end end end From d8137e510209316434c808b9f5b89f756540ca29 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 19:12:03 -0300 Subject: [PATCH 7/9] feat(bitcoin/network/messages/generic): add generic message --- lib/bitcoin/network/messages/generic.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/bitcoin/network/messages/generic.rb diff --git a/lib/bitcoin/network/messages/generic.rb b/lib/bitcoin/network/messages/generic.rb new file mode 100644 index 0000000..8e9d46f --- /dev/null +++ b/lib/bitcoin/network/messages/generic.rb @@ -0,0 +1,22 @@ +require_relative './base_message' + +module Bitcoin + module Network + module Messages + class Generic < BaseMessage + def initialize(command, payload) + @command = command + @payload = payload + end + + def command + @command + end + + def serialize + @payload + end + end + end + end +end From 6c4ee6603d2faefed00cbb28cd6d9fd6e55048b6 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 19:13:57 -0300 Subject: [PATCH 8/9] feat(bitcoin/bloom_filter): add bloom filter implementation --- lib/bitcoin/bloom_filter.rb | 43 ++++++++++++++++++++++++++++++++++++ spec/bitcoin/bloom_filter.rb | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 lib/bitcoin/bloom_filter.rb create mode 100644 spec/bitcoin/bloom_filter.rb diff --git a/lib/bitcoin/bloom_filter.rb b/lib/bitcoin/bloom_filter.rb new file mode 100644 index 0000000..1e4ff42 --- /dev/null +++ b/lib/bitcoin/bloom_filter.rb @@ -0,0 +1,43 @@ +require_relative '../helpers/hash_helper' +require_relative '../helpers/encoding_helper' +require_relative 'network/messages/generic' + +module Bitcoin + class BloomFilter + include EncodingHelper + BIP37_CONSTANT = 0xfba4c795 + + def initialize(size, function_count, tweak) + @size = size + @bit_field = [0] * (size * 8) + @function_count = function_count + @tweak = tweak + end + + attr_reader :bit_field + + def add(item) + @function_count.times do |i| + seed = i * BIP37_CONSTANT + @tweak + + hash_result = HashHelper.murmur3(item, seed: seed) + bit = hash_result % (@size * 8) + @bit_field[bit] = 1 + end + end + + def filter_bytes + bit_field_to_bytes(@bit_field) + end + + def filterload(flag: 1) + result = encode_varint(@size) + result += filter_bytes + result += int_to_little_endian(@function_count, 4) + result += int_to_little_endian(@tweak, 4) + result += int_to_little_endian(flag, 1) + + Bitcoin::Network::Messages::Generic.new('filterload', result) + end + end +end diff --git a/spec/bitcoin/bloom_filter.rb b/spec/bitcoin/bloom_filter.rb new file mode 100644 index 0000000..cf2c1be --- /dev/null +++ b/spec/bitcoin/bloom_filter.rb @@ -0,0 +1,43 @@ +require_relative '../../lib/bitcoin/bloom_filter' +require_relative '../../lib/helpers/encoding_helper' + +RSpec.describe Bitcoin::BloomFilter do + include EncodingHelper + + describe '.new' do + it 'initializes the bit_field appropiately' do + expect(described_class.new(1, 2, 3).bit_field).to eq [0] * 8 + end + end + + describe '#add' do + it 'mutates the bit_field correctly' do + expected_bit_field = bytes_to_bit_field(from_hex_to_bytes('0000000a080000000140')) + bloom_filter = described_class.new(10, 5, 99) + expect { bloom_filter.add('Hello World') } + .to((change { bloom_filter.bit_field }.to expected_bit_field)) + end + end + + describe '#filter_bytes' do + it 'returns the proper filter bytes' do + bloom_filter = described_class.new(10, 5, 99) + expect(bloom_filter.filter_bytes).to eq bit_field_to_bytes([0] * 10 * 8) + bloom_filter.add('Hello World') + expect(bloom_filter.filter_bytes).to eq from_hex_to_bytes('0000000a080000000140') + bloom_filter.add('Goodbye!') + expect(bloom_filter.filter_bytes).to eq from_hex_to_bytes('4000600a080000010940') + end + end + + describe '#filterload' do + it 'returns the proper generic filterload message' do + bloom_filter = described_class.new(10, 5, 99) + bloom_filter.add('Hello World') + bloom_filter.add('Goodbye!') + expect(bloom_filter.filterload.command).to eq 'filterload' + expect(bloom_filter.filterload.serialize) + .to eq from_hex_to_bytes('0a4000600a080000010940050000006300000001') + end + end +end From adb2c73891f4d458ee01cb55eb166c20374754f7 Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Thu, 24 Nov 2022 19:49:19 -0300 Subject: [PATCH 9/9] feat(bitcoin/network/messages/get_data): add GetData message --- lib/bitcoin/network/messages/get_data.rb | 32 +++++++++++++++++++ .../bitcoin/network/messages/get_data_spec.rb | 23 +++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 lib/bitcoin/network/messages/get_data.rb create mode 100644 spec/bitcoin/network/messages/get_data_spec.rb diff --git a/lib/bitcoin/network/messages/get_data.rb b/lib/bitcoin/network/messages/get_data.rb new file mode 100644 index 0000000..c4f6908 --- /dev/null +++ b/lib/bitcoin/network/messages/get_data.rb @@ -0,0 +1,32 @@ +require_relative './base_message' + +module Bitcoin + module Network + module Messages + class GetData < BaseMessage + TX_DATA_TYPE = 1 + BLOCK_DATA_TYPE = 2 + FILTERED_BLOCK_DATA_TYPE = 3 + COMPACT_BLOCK_DATA_TYPE = 4 + COMMAND = "getdata" + + def initialize + @data = [] + end + + def add_data(data_type:, identifier:) + @data << [data_type, identifier] + end + + def serialize + result = encode_varint(@data.length) + @data.each do |data_type, identifier| + result += int_to_little_endian(data_type, 4) + result += identifier.reverse + end + result + end + end + end + end +end diff --git a/spec/bitcoin/network/messages/get_data_spec.rb b/spec/bitcoin/network/messages/get_data_spec.rb new file mode 100644 index 0000000..ac4a77c --- /dev/null +++ b/spec/bitcoin/network/messages/get_data_spec.rb @@ -0,0 +1,23 @@ +require 'bitcoin/network/messages/get_data' +require 'helpers/encoding_helper' + +RSpec.describe Bitcoin::Network::Messages::GetData do + include EncodingHelper + + def serialized_message_hex + get_data = described_class.new + block1 = from_hex_to_bytes('00000000000000cac712b726e4326e596170574c01a16001692510c44025eb30') + get_data.add_data(data_type: described_class::FILTERED_BLOCK_DATA_TYPE, identifier: block1) + block2 = from_hex_to_bytes('00000000000000beb88910c46f6b442312361c6693a7fb52065b583979844910') + get_data.add_data(data_type: described_class::FILTERED_BLOCK_DATA_TYPE, identifier: block2) + bytes_to_hex(get_data.serialize) + end + + describe "#serialize" do + it "serializes version" do + expected_hex = "020300000030eb2540c41025690160a1014c577061596e32e426b712c7ca000000000000000\ +30000001049847939585b0652fba793661c361223446b6fc41089b8be00000000000000" + expect(serialized_message_hex).to eq expected_hex + end + end +end