module Cashaddress
Constants
- ALPHABET
ALPHABET
is missing some confusing characters like O and I- ALPHABET_LOOKUP
- CHARSET
- CHARSET_LOOKUP
- MAINNET_PREFIX
- TESTNET_PREFIX
- VERSION
Public Class Methods
convert_bits(data, from, to, pad)
click to toggle source
# File lib/cashaddress.rb, line 160 def self.convert_bits(data, from, to, pad) converted = data .map{|i| '%0*b' % [from, i] } # Get chunks <from> bits long .join('') # In a long row of zeros and ones. .chars.each_slice(to) # Then in bits that are < .map{|bits| bits.join('').ljust(to, '0').to_i(2) } converted = converted[0..-2] if converted.last == 0 && !pad converted end
encode_base58(bytes)
click to toggle source
# File lib/cashaddress.rb, line 52 def self.encode_base58(bytes) digits = [0] bytes.each do |byte| carry = byte digits.each_with_index do |digit, index| carry += digit << 8 digits[index] = carry % 58 carry = ( carry / 58 ) | 0 end while carry > 0 digits << carry % 58 carry = (carry / 58) | 0 end end encoded = '' bytes.each do |byte| break if byte != 0 encoded << "1" end encoded << digits.reverse.map{|d| ALPHABET[d] }.join('') encoded end
from_legacy(legacy_address)
click to toggle source
# File lib/cashaddress.rb, line 79 def self.from_legacy(legacy_address) bytes = [0] characters = legacy_address.split('') characters.each_with_index do |char, i| unless value = ALPHABET_LOOKUP[char] raise Error.new("Invalid char #{char}") end carry = value bytes.each_with_index do |byte, j| carry += byte * 58 bytes[j] = carry & 0xff carry = carry >> 8 end while carry > 0 bytes << ( carry & 0xff ) carry = carry >> 8 end end characters.each do |char| break if char != "1" bytes << 0 end if bytes.size < 5 raise Error.new("Decoded less than 5 bytes from #{legacy_address}") end bytes = bytes.reverse version = bytes.first digest = Digest::SHA256.digest(Digest::SHA256.digest(bytes[0..-5].pack("C*"))).unpack('C*') if digest[0..3] != bytes[-4..-1] raise Error.new("Invalid checksum") end kind, is_main_net = case version when 0x00, 0x1c then [0, true] when 0x05, 0x28 then [1, true] when 0x6f then [0, false] when 0xc4 then [1, false] else raise Error.new("Unexpected version #{version}") end make_cashaddress(kind, bytes[1..-5], is_main_net) end
make_cashaddress(kind, hash, main_net)
click to toggle source
# File lib/cashaddress.rb, line 130 def self.make_cashaddress(kind, hash, main_net) packed = convert_bits( [ kind << 3] + hash, 8, 5, true) raise Error.new("Can't pack Cashaddress") unless packed prefix = main_net ? MAINNET_PREFIX.clone : TESTNET_PREFIX.clone encoded = prefix.concat(packed) moduled = polymod(encoded + [0] * 8) checksum = 8.times.map do |i| shifted = ('%0*b' % [ 5 * (i+1), moduled >> ( 5 * (7-i))])[-5..-1] .split('').map(&:to_i) to_five_bit_array(shifted).first end address = main_net ? "bitcoincash:" : "bchtest:" address << (packed + checksum).map{|b| CHARSET[b] }.join('') if address.size != 54 && address.size != 50 raise Error.new("Address is not 50 or 54 characters long #{address}") end address end
make_old_address(payload, version)
click to toggle source
# File lib/cashaddress.rb, line 46 def self.make_old_address(payload, version) raw = [version] + payload digest = Digest::SHA256.digest(Digest::SHA256.digest(raw.pack("C*"))).unpack('C*') encode_base58( raw + digest[0..3] ) end
polymod(vector)
click to toggle source
Polymod checksum is defined in the cashaddr spec github.com/Bitcoin-UAHF/spec/blob/master/cashaddr.md
# File lib/cashaddress.rb, line 173 def self.polymod(vector) accum = 1 vector.each do |byte| pivot = accum >> 35 accum = ((accum & 0x07ffffffff) << 5) ^ byte [ [0x01, 0x98f2bc8e61], [0x02, 0x79b76d99e2], [0x04, 0xf33e5fb3c4], [0x08, 0xae2eabe2a8], [0x10, 0x1e4f43e470], ].each do |bit, mask| accum ^= mask if (pivot & bit) != 0 end end accum ^ 1 end
to_five_bit_array(array)
click to toggle source
# File lib/cashaddress.rb, line 154 def self.to_five_bit_array(array) array.each_slice(5).collect do |slice| slice.zip([16, 8, 4, 2, 1]).map{|a,b| a * b}.sum end end
to_legacy(cashaddress)
click to toggle source
# File lib/cashaddress.rb, line 19 def self.to_legacy(cashaddress) unless match = /(.+?):(.+)/.match(cashaddress) raise Error.new("Malformed cashaddress: #{cashaddress}") end is_mainnet = match.captures.first == 'bitcoincash' prefix = is_mainnet ? MAINNET_PREFIX.clone : TESTNET_PREFIX.clone raw_payload = match.captures.last.split('').map{|m| CHARSET_LOOKUP[m] } if polymod(prefix + raw_payload) != 0 raise Error.new("Address checksum invalid: #{cashaddress}") end payload = convert_bits(raw_payload[0..-9], 5, 8, false) if payload.empty? raise Error.new("Converted payload was empty") end version = if (payload.first >> 3) == 0 is_mainnet ? 0x00 : 0x6f else is_mainnet ? 0x05 : 0xc4 end make_old_address(payload[1..21], version) end
to_lookup(array)
click to toggle source
# File lib/cashaddress.rb, line 5 def self.to_lookup(array) Hash[*array.each_with_index.to_a.flatten] end