Skip to content
4 changes: 2 additions & 2 deletions lib/bitcoin/block.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
43 changes: 43 additions & 0 deletions lib/bitcoin/bloom_filter.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/bitcoin/fetcher/uri_fetcher.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require_relative './fetcher'
require_relative '../tx'
require_relative '../../encoding_helper'
require_relative '../../helpers/encoding_helper'
require 'net/http'
require 'stringio'

Expand Down
4 changes: 2 additions & 2 deletions lib/bitcoin/network/envelope.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/bitcoin/network/messages/base_message.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# encoding: ascii-8bit
require_relative '../../../encoding_helper'
require_relative '../../../helpers/encoding_helper'

module Bitcoin
module Network
Expand Down
22 changes: 22 additions & 0 deletions lib/bitcoin/network/messages/generic.rb
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions lib/bitcoin/network/messages/get_data.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions lib/bitcoin/op.rb
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
2 changes: 1 addition & 1 deletion lib/bitcoin/script.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require_relative '../bitcoin_data_io'
require_relative '../encoding_helper'
require_relative '../helpers/encoding_helper'
require_relative './op'

module Bitcoin
Expand Down
4 changes: 2 additions & 2 deletions lib/bitcoin/tx.rb
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
2 changes: 1 addition & 1 deletion lib/ecc/private_key.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/ecc/s256_point.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/ecc/signature.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require_relative '../encoding_helper'
require_relative '../helpers/encoding_helper'
module ECC
class Signature
include EncodingHelper
Expand Down
13 changes: 0 additions & 13 deletions lib/hash_helper.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/address_helper.rb → lib/helpers/address_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# encoding: ascii-8bit

require 'encoding_helper'
require_relative 'encoding_helper'

module AddressHelper
include EncodingHelper
Expand Down
23 changes: 23 additions & 0 deletions lib/encoding_helper.rb → lib/helpers/encoding_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ 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

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)
Expand Down
68 changes: 68 additions & 0 deletions lib/helpers/hash_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'digest'

module HashHelper
MASK32 = 0xFFFFFFFF

def self.hash160(string)
first_round = Digest::SHA256.digest(string)
Digest::RMD160.digest(first_round)
end

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
4 changes: 4 additions & 0 deletions lib/script_helper.rb → lib/helpers/script_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
require_relative 'encoding_helper'

module ScriptHelper
include EncodingHelper

def encode_num(num)
return '' if num.zero?

Expand Down
2 changes: 1 addition & 1 deletion spec/bitcoin/block_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require 'bitcoin/block'
require 'encoding_helper'
require 'helpers/encoding_helper'

RSpec.describe Bitcoin::Block do
include EncodingHelper
Expand Down
43 changes: 43 additions & 0 deletions spec/bitcoin/bloom_filter.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion spec/bitcoin/network/envelope_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading