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
6 changes: 3 additions & 3 deletions lib/bsv/ext_key.ex
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ defmodule BSV.ExtKey do
}}
"""
@spec from_string(xprv() | xpub()) :: {:ok, t()} | {:error, term()}
def from_string(<<"xprv", _::binary>> = xprv) do
def from_string(<<prefix::binary-4, _::binary>> = xprv) when prefix in ["xprv", "tprv"] do
<<version_byte, prefix::binary>> = version = @privkey_version_bytes[BSV.network()]

with {:ok, {data, <<^version_byte>>}} when byte_size(data) == 77 <- B58.decode58_check(xprv) do
Expand Down Expand Up @@ -222,10 +222,10 @@ defmodule BSV.ExtKey do
end
end

def from_string(<<"xpub", _::binary>> = xprv) do
def from_string(<<prefix::binary-4, _::binary>> = xpub) when prefix in ["xpub", "tpub"] do
<<version_byte, prefix::binary>> = version = @pubkey_version_bytes[BSV.network()]

with {:ok, {data, <<^version_byte>>}} when byte_size(data) == 77 <- B58.decode58_check(xprv) do
with {:ok, {data, <<^version_byte>>}} when byte_size(data) == 77 <- B58.decode58_check(xpub) do
<<
^prefix::binary-3,
depth::8,
Expand Down
76 changes: 76 additions & 0 deletions test/bsv/ext_key_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule BSV.ExtKeyTest do
@test_seed "5bd995f07cbaeeb8c1fb4d52db5884471ae80b82f7c07094bfc77b2f4742a76a1d72d25ad58d011ecff16b1b9b0ae225e2fc084cad91a176527b4bca50047025"
@test_xprv "xprv9s21ZrQH143K3qcbMJpvTQQQ1zRCPaZjXUD1zPouMDtKY9QQQ9DskzrZ3Cx38GnYXpgY2awCmJfz2QXkpxLN3Pp2PmUddbnrXziFtArpZ5v"
@test_xpub "xpub661MyMwAqRbcGKh4TLMvpYM8a2Fgo3Hath8cnnDWuZRJQwjYwgY8JoB2tTgiTDdwf4rdGvgUpGhGNH54Ycb8vegrhHVVpdfYCdBBii94CLF"
@test_tprv "tprv8ZgxMBicQKsPeer81sgRd42PL7qQd6bjs288rpEMqCNoKk9VPWZdGkDzxP7h8eAruGDK2gYxvfFnVG5VxAgJrT5cvQgwHxWuT6TgKvGc1DZ"
@test_tpub "tpubD6NzVbkrYhZ4Y7suuXM22TgVu9MLnRneSKiv9LGfFUBCAEQG1uPDTEqs8VmVyibBSyPXtWCMMNXK4TZfwUSNp699ktEoWAKmnambUgCsghP"
@extkey %BSV.ExtKey{
chain_code: <<178, 208, 232, 46, 183, 65, 27, 66, 14, 172, 46, 66, 222, 84, 220, 98, 70, 249, 25, 3, 50, 209, 218, 236, 96, 142, 211, 79, 59, 166, 41, 106>>,
child_index: 0,
Expand Down Expand Up @@ -121,6 +123,80 @@ defmodule BSV.ExtKeyTest do
end
end

describe "Testnet (tprv/tpub)" do
# switch to testnet for this describe block, and restore afterward
setup do
prev = BSV.network()
Application.put_env(:bsv, :network, :test)
on_exit(fn -> Application.put_env(:bsv, :network, prev) end)
:ok
end

test "from_seed/2 produces tprv" do
assert {:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)

tprv = ExtKey.to_string(extkey)
assert String.starts_with?(tprv, "tprv")
assert tprv == @test_tprv
end

test "to_public produces tpub" do
assert {:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)

tpub = extkey |> ExtKey.to_public() |> ExtKey.to_string()
assert String.starts_with?(tpub, "tpub")
assert tpub == @test_tpub
end

test "from_string/1 decodes tprv " do
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
tprv = ExtKey.to_string(extkey)

assert String.starts_with?(tprv, "tprv")
assert {:ok, %ExtKey{} = parsed} = ExtKey.from_string(tprv)
assert tprv == @test_tprv

# round-trip equality on the serialized form
assert ExtKey.to_string(parsed) == tprv

# public conversion is also consistent
assert ExtKey.to_public(parsed) |> ExtKey.to_string()
== ExtKey.to_public(extkey) |> ExtKey.to_string()
end

test "from_string/1 decodes tpub and round-trips" do
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
tpub = extkey |> ExtKey.to_public() |> ExtKey.to_string()

assert String.starts_with?(tpub, "tpub")
assert {:ok, %ExtKey{} = parsed_pub} = ExtKey.from_string(tpub)
assert is_nil(parsed_pub.privkey)

# round-trip equality on the serialized form
assert ExtKey.to_public(parsed_pub) |> ExtKey.to_string() == tpub
end

test "to_public/1 swaps version bytes to testnet pub (tpub)" do
# testnet pub version bytes per module under :test
expected_pub_version = <<4, 53, 135, 207>>

{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
pub = ExtKey.to_public(extkey)

assert is_nil(pub.privkey)
assert pub.version == expected_pub_version

assert ExtKey.to_string(pub) |> String.starts_with?("tpub")
end

test "to_string/1 emits tprv for private and tpub for public on testnet" do
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)

assert ExtKey.to_string(extkey) |> String.starts_with?("tprv")
assert ExtKey.to_public(extkey) |> ExtKey.to_string() |> String.starts_with?("tpub")
end
end

describe "ExtKey.to_public/1" do
test "converts private extkey to public extkey" do
assert %ExtKey{} = extkey = ExtKey.from_string!(@test_xprv) |> ExtKey.to_public()
Expand Down
Loading