Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions lib/money-tree/networks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,48 @@ module MoneyTree
p2sh_char: "3",
privkey_version: "80",
privkey_compression_flag: "01",
extended_privkey_version: "0488ade4",
extended_pubkey_version: "0488b21e",
extended_version: {
public: {
bip_44: '0x0488b21e',
bip_49: '0x049d7cb2',
bip_84: '0x04b24746'
},
private: {
bip_44: '0x0488ade4',
bip_49: '0x049d7878',
bip_84: '0x04b2430c'
}
},
compressed_wif_chars: %w(K L),
uncompressed_wif_chars: %w(5),
protocol_version: 70001,
human_readable_part: "bc",
human_readable_part: "bc"
},
bitcoin_testnet: {
address_version: "6f",
p2sh_version: "c4",
p2sh_char: "2",
privkey_version: "ef",
privkey_compression_flag: "01",
extended_privkey_version: "04358394",
extended_pubkey_version: "043587cf",
extended_version: {
public: {
bip_44: '0x043587cf',
bip_49: '0x044a5262',
bip_84: '0x045f1cf6'
},
private: {
bip_44: '0x04358394',
bip_49: '0x044a4e28',
bip_84: '0x045f18bc'
}
},
compressed_wif_chars: %w(c),
uncompressed_wif_chars: %w(9),
protocol_version: 70001,
human_readable_part: "tb",
human_readable_part: "tb"
},
)
hsh[:testnet3] = hsh[:bitcoin_testnet]
hsh[:testnet3] = hsh[:testnet4] = hsh[:regtest] = hsh[:bitcoin_testnet]
hsh
end
end
23 changes: 18 additions & 5 deletions lib/money-tree/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class Node
attr_reader :parent

PRIVATE_RANGE_LIMIT = 0x80000000
DEFAULT_BIP = 44

class PublicDerivationFailure < StandardError; end
class InvalidKeyForIndex < StandardError; end
class ImportError < StandardError; end
class PrivatePublicMismatch < StandardError; end
class VersionNotImplemented < StandardError; end

def initialize(opts = {})
opts.each { |k, v| instance_variable_set "@#{k}", v }
Expand Down Expand Up @@ -123,20 +125,31 @@ def right_from_hash(hash)
bytes_to_int hash.bytes.to_a[32..-1]
end

def to_serialized_hex(type = :public, network: :bitcoin)
def version_bytes(type:, network:, bip:)
sym_bip = "bip_#{bip}".to_sym
version_bytes = NETWORKS.dig(network, :extended_version, type, sym_bip)
raise VersionNotImplemented unless version_bytes
version_bytes.gsub('0x','')
end

def to_serialized_hex(type = :public, network: :bitcoin, bip: DEFAULT_BIP)
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
hex = NETWORKS[network][version_key] # version (4 bytes)
hex = version_bytes(type: type, network: network, bip: bip) # version (4 bytes)
hex += depth_hex(depth) # depth (1 byte)
hex += parent_fingerprint # fingerprint of key (4 bytes)
hex += index_hex(index) # child number i (4 bytes)
hex += chain_code_hex
hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.compressed.to_hex
end

def to_bip32(type = :public, network: :bitcoin)
def to_bip(bip, type: :public, network: :bitcoin)
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
to_serialized_base58 to_serialized_hex(type, network: network)
to_serialized_base58 to_serialized_hex(type, network: network, bip: bip)
end

# keep old way
def to_bip32(type = :public, network: :bitcoin)
to_bip(DEFAULT_BIP, type: type, network: network)
end

def to_identifier(compressed = true)
Expand Down
69 changes: 69 additions & 0 deletions spec/money-tree/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,61 @@
expect(@master.to_serialized_hex).to eql("0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2")
expect(@master.to_bip32).to eql("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8")
end

context 'with version prefix' do
it 'contains version prefix' do
expect(@master.to_bip(49).include?('ypub')).to be(true)
expect(@master.to_bip(84).include?('zpub')).to be(true)
end
end
end

describe "m/44'/0'/0'/0 address" do
before do
@node = @master.node_for_path "m/44'/0'/0'/0"
end

it 'generate serialized private key' do
expect(@node.to_bip(44, type: :private))
.to eql('xprvA2EqnHNMfq9w1nDwQanUKHtfZcn4xvGAbc2ugxHMbnDdQbQUm9w6EWF7ZKmXG4NhQyFF7vp6AtoAFjxtc56osAtRKA1T9KJYwfiesFD8wiT')
end

it 'generate serialized public key' do
expect(@node.to_bip(44))
.to eql('xpub6FECBnuFWCiEEGJQWcKUgRqQ7ecZNNz1xpxWVLgyA7kcHPjdJhFLnJZbQbvQSPVr2R9xVWXjoVgGUom21dw9AkQkiKKz2YYGYGUdj7RaiNA')
end
end

describe "m/49'/0'/0'/0 address" do
before do
@node = @master.node_for_path "m/49'/0'/0'/0"
end

it 'generate serialized private key' do
expect(@node.to_bip(49, type: :private))
.to eql('yprvALM988hxmYs98gPgnPQa2G7rg8Yuemm2m9Q31pyqhsdQSzpMfQnDgVMUjjNvyWvYcUwmZ8m6hFoBEd75MLygRxXws2Wvw2Rpbi3usvgSRmZ')
end

it 'generate serialized public key' do
expect(@node.to_bip(49))
.to eql('ypub6ZLVXeErbvRSMAU9tQwaPQ4bEAPQ4EUt8NKdpDPTGDAPKo9WCx6UEHfxb2WH83jfwcouCGZqwW8L9f1KbUm7MJSDCQbQWiARmwKJ5Pv5J9q')
end
end

describe "m/84'/0'/0'/0 address" do
before do
@node = @master.node_for_path "m/84'/0'/0'/0"
end

it 'generate serialized private key' do
expect(@node.to_bip(84, type: :private))
.to eql('zprvAfvDLyVt5SKubEEs54xybXAXUetAttxXHqXow4nxv3b2QkBBu2Wqe7PuBk1LzedwcEfXQzpBK661EtRWDDbiKNjgv3KhNAzVJ1WxF9z2VV8')
end

it 'generate serialized public key' do
expect(@node.to_bip(84))
.to eql('zpub6tuZkV2muotCoiKLB6Vyxf7G2gifJMgNf4TQjTCaUP81HYWLSZq6BuiP2zb225YP35YkdF8wzFsDy51Z2fx14tyVCKuNt35XdNB27M2AZwU')
end
end

describe "m/2147483648" do
Expand Down Expand Up @@ -1247,6 +1302,20 @@
end
end

describe '#version_bytes' do
before do
@master = MoneyTree::Master.new seed_hex: "000102030405060708090a0b0c0d0e0f"
end

context 'when version is not implemented' do
it 'should raise an error' do
expect {
@master.version_bytes(type: :public, network: :bitcoin, bip: :unknown)
}.to raise_error(MoneyTree::Node::VersionNotImplemented)
end
end
end

describe "negative index" do
before do
@master = MoneyTree::Master.new seed_hex: "000102030405060708090a0b0c0d0e0f"
Expand Down
Loading