Skip to content

Commit bf25b94

Browse files
committed
support testnet keys
1 parent 95070d6 commit bf25b94

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

lib/bsv/ext_key.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ defmodule BSV.ExtKey do
190190
}}
191191
"""
192192
@spec from_string(xprv() | xpub()) :: {:ok, t()} | {:error, term()}
193-
def from_string(<<"xprv", _::binary>> = xprv) do
193+
def from_string(<<prefix::binary-4, _::binary>> = xprv) when prefix in ["xprv", "tprv"] do
194194
<<version_byte, prefix::binary>> = version = @privkey_version_bytes[BSV.network()]
195195

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

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

228-
with {:ok, {data, <<^version_byte>>}} when byte_size(data) == 77 <- B58.decode58_check(xprv) do
228+
with {:ok, {data, <<^version_byte>>}} when byte_size(data) == 77 <- B58.decode58_check(xpub) do
229229
<<
230230
^prefix::binary-3,
231231
depth::8,

test/bsv/ext_key_test.exs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ defmodule BSV.ExtKeyTest do
77
@test_seed "5bd995f07cbaeeb8c1fb4d52db5884471ae80b82f7c07094bfc77b2f4742a76a1d72d25ad58d011ecff16b1b9b0ae225e2fc084cad91a176527b4bca50047025"
88
@test_xprv "xprv9s21ZrQH143K3qcbMJpvTQQQ1zRCPaZjXUD1zPouMDtKY9QQQ9DskzrZ3Cx38GnYXpgY2awCmJfz2QXkpxLN3Pp2PmUddbnrXziFtArpZ5v"
99
@test_xpub "xpub661MyMwAqRbcGKh4TLMvpYM8a2Fgo3Hath8cnnDWuZRJQwjYwgY8JoB2tTgiTDdwf4rdGvgUpGhGNH54Ycb8vegrhHVVpdfYCdBBii94CLF"
10+
@test_tprv "tprv8ZgxMBicQKsPeer81sgRd42PL7qQd6bjs288rpEMqCNoKk9VPWZdGkDzxP7h8eAruGDK2gYxvfFnVG5VxAgJrT5cvQgwHxWuT6TgKvGc1DZ"
11+
@test_tpub "tpubD6NzVbkrYhZ4Y7suuXM22TgVu9MLnRneSKiv9LGfFUBCAEQG1uPDTEqs8VmVyibBSyPXtWCMMNXK4TZfwUSNp699ktEoWAKmnambUgCsghP"
1012
@extkey %BSV.ExtKey{
1113
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>>,
1214
child_index: 0,
@@ -121,6 +123,80 @@ defmodule BSV.ExtKeyTest do
121123
end
122124
end
123125

126+
describe "Testnet (tprv/tpub)" do
127+
# switch to testnet for this describe block, and restore afterward
128+
setup do
129+
prev = BSV.network()
130+
Application.put_env(:bsv, :network, :test)
131+
on_exit(fn -> Application.put_env(:bsv, :network, prev) end)
132+
:ok
133+
end
134+
135+
test "from_seed/2 produces tprv" do
136+
assert {:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
137+
138+
tprv = ExtKey.to_string(extkey)
139+
assert String.starts_with?(tprv, "tprv")
140+
assert tprv == @test_tprv
141+
end
142+
143+
test "to_public produces tpub" do
144+
assert {:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
145+
146+
tpub = extkey |> ExtKey.to_public() |> ExtKey.to_string()
147+
assert String.starts_with?(tpub, "tpub")
148+
assert tpub == @test_tpub
149+
end
150+
151+
test "from_string/1 decodes tprv " do
152+
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
153+
tprv = ExtKey.to_string(extkey)
154+
155+
assert String.starts_with?(tprv, "tprv")
156+
assert {:ok, %ExtKey{} = parsed} = ExtKey.from_string(tprv)
157+
assert tprv == @@test_tprv
158+
159+
# round-trip equality on the serialized form
160+
assert ExtKey.to_string(parsed) == tprv
161+
162+
# public conversion is also consistent
163+
assert ExtKey.to_public(parsed) |> ExtKey.to_string()
164+
== ExtKey.to_public(extkey) |> ExtKey.to_string()
165+
end
166+
167+
test "from_string/1 decodes tpub and round-trips" do
168+
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
169+
tpub = extkey |> ExtKey.to_public() |> ExtKey.to_string()
170+
171+
assert String.starts_with?(tpub, "tpub")
172+
assert {:ok, %ExtKey{} = parsed_pub} = ExtKey.from_string(tpub)
173+
assert is_nil(parsed_pub.privkey)
174+
175+
# round-trip equality on the serialized form
176+
assert ExtKey.to_public(parsed_pub) |> ExtKey.to_string() == tpub
177+
end
178+
179+
test "to_public/1 swaps version bytes to testnet pub (tpub)" do
180+
# testnet pub version bytes per module under :test
181+
expected_pub_version = <<4, 53, 135, 207>>
182+
183+
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
184+
pub = ExtKey.to_public(extkey)
185+
186+
assert is_nil(pub.privkey)
187+
assert pub.version == expected_pub_version
188+
189+
assert ExtKey.to_string(pub) |> String.starts_with?("tpub")
190+
end
191+
192+
test "to_string/1 emits tprv for private and tpub for public on testnet" do
193+
{:ok, %ExtKey{} = extkey} = ExtKey.from_seed(@test_seed, encoding: :hex)
194+
195+
assert ExtKey.to_string(extkey) |> String.starts_with?("tprv")
196+
assert ExtKey.to_public(extkey) |> ExtKey.to_string() |> String.starts_with?("tpub")
197+
end
198+
end
199+
124200
describe "ExtKey.to_public/1" do
125201
test "converts private extkey to public extkey" do
126202
assert %ExtKey{} = extkey = ExtKey.from_string!(@test_xprv) |> ExtKey.to_public()

0 commit comments

Comments
 (0)