Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
785df11
use derivation paths in PSBT
SachinMeier Nov 2, 2021
f66784a
rename parse/serialize/display functions
SachinMeier Nov 2, 2021
7845135
use xpubs in PSBT
SachinMeier Nov 2, 2021
0fdbd74
rename prv functions
SachinMeier Nov 3, 2021
a40c8dd
simplify func signatures
SachinMeier Oct 21, 2022
f479ccb
first draft adaptor signatures
SachinMeier Oct 22, 2022
2ccff2e
fmt
SachinMeier Oct 22, 2022
40372d5
fix typo
SachinMeier Oct 22, 2022
4baa1f2
simplify code & add extract_sig from adaptor
SachinMeier Oct 22, 2022
a04f43e
make code consistent across sign & adaptor sign
SachinMeier Oct 22, 2022
47d2a01
temp: try to fix R+T not even bug
SachinMeier Oct 27, 2022
25eef34
make ExtendedKey.serialize elixir-idiomatic
SachinMeier Jan 17, 2023
35cdda1
Merge branch 'master' into sachin--psbt-use-deriv-and-xpub
SachinMeier Jan 17, 2023
05f5edd
temp fix adaptors
SachinMeier Jan 28, 2023
ab82799
Merge branch 'master' into sachin--adaptor-signatures
SachinMeier Jan 28, 2023
b7d5739
fix schnorr adaptor signatures
SachinMeier Jan 30, 2023
ce1f869
finish adaptor signatures!
SachinMeier Jan 31, 2023
6a4e775
add comments, make tests realistic by force even tweak
SachinMeier Jan 31, 2023
8bccf20
fmt
SachinMeier Jan 31, 2023
f4dc730
do not assume even tweak
SachinMeier Feb 1, 2023
932687b
do not assume tweak is even in tests
SachinMeier Feb 1, 2023
92ae2b4
add key-only taproot script creation
SachinMeier Feb 1, 2023
f9054d2
add Taproot.build_control_block and merkle tree handling
SachinMeier Feb 4, 2023
7f34d80
improve create_p2tr signature
SachinMeier Feb 4, 2023
ad34bf6
finish bip341 sighash & tx signing
SachinMeier Feb 5, 2023
02a7fbd
add scriptpath spend, op_checksigadd, scripts for examples
SachinMeier Feb 6, 2023
11fe186
add tx hex to taproot multi-spend ex
SachinMeier Feb 6, 2023
8792568
cleanup
SachinMeier Feb 6, 2023
406e73f
rm tapscript.ex
SachinMeier Feb 6, 2023
a65df93
Merge branch 'sachin--adaptor-signatures' into sachin--add-key-only-t…
SachinMeier Feb 6, 2023
17e28df
add full DLC example
SachinMeier Feb 9, 2023
0d18d7a
syntax
SachinMeier Feb 9, 2023
511c8d2
add taproot multisig script construction
SachinMeier Feb 9, 2023
45ea0b3
add spec for function
SachinMeier Feb 12, 2023
e9fadf4
Merge branch 'master' into sachin--psbt-use-deriv-and-xpub
SachinMeier Feb 13, 2023
b977596
add psbt new fields
SachinMeier Feb 13, 2023
f4d7a3d
psbt v2 serialization funcs
SachinMeier Feb 17, 2023
41fd7d5
fix lints
SachinMeier Feb 17, 2023
31dedf7
rm duplicate test cases & add serialization & improve logic for handl…
SachinMeier Feb 21, 2023
5820c75
add psbt test vectors
SachinMeier Feb 21, 2023
ed4f6b6
add all fields to psbt global serializer
SachinMeier Feb 21, 2023
5eba54e
add BIP370 & 371 test vectors for psbt
SachinMeier Feb 24, 2023
d534c26
add todo
SachinMeier Feb 24, 2023
289e49c
lint
SachinMeier Feb 24, 2023
5bed706
Merge branch 'sachin--adaptor-signatures' into sachin--add-key-only-t…
SachinMeier Feb 24, 2023
5cc1ef6
lint
SachinMeier Feb 24, 2023
e659a67
lint
SachinMeier Feb 24, 2023
581403f
fmt
SachinMeier Feb 24, 2023
02ab1cf
fmt
SachinMeier Feb 24, 2023
9bd20fa
lint
SachinMeier Feb 24, 2023
afadef2
fmt
SachinMeier Feb 24, 2023
e133c5c
fix aliases
SachinMeier Feb 24, 2023
72a89d0
lint
SachinMeier Feb 24, 2023
3a48dbd
lint
SachinMeier Feb 24, 2023
73b7500
lint
SachinMeier Feb 24, 2023
e2cf4a0
Merge pull request #11 from SachinMeier/sachin--psbt-use-deriv-and-xpub
SachinMeier Feb 24, 2023
2043883
Merge pull request #12 from SachinMeier/sachin--adaptor-signatures
SachinMeier Feb 24, 2023
fa7df7f
lint
SachinMeier Feb 24, 2023
d81cd49
Merge branch 'master' into sachin--add-key-only-taproot-script-creation
SachinMeier Feb 24, 2023
3294369
Merge pull request #13 from SachinMeier/sachin--add-key-only-taproot-…
SachinMeier Feb 24, 2023
eb0b3eb
Add PSBT fields functionality (#14)
SachinMeier Mar 4, 2023
c711203
taproot_multisig -> tapscript_multisig
SachinMeier Mar 5, 2023
6b9810c
Merge branch 'master' into sachin--add-taproot-multisig
SachinMeier Mar 5, 2023
4774678
implement bip67
SachinMeier Mar 5, 2023
e5c7554
implement bip69 and add testing
SachinMeier Mar 5, 2023
de6b5bb
fmt and add second bip69 test
SachinMeier Mar 5, 2023
3c2599d
fix spec
SachinMeier Mar 5, 2023
ad4f19d
refactor sorting for multisig
SachinMeier Mar 7, 2023
6f42ab9
rm excess
SachinMeier Mar 7, 2023
d065db9
fmt
SachinMeier Mar 7, 2023
e64c0e4
Add tapscript multisig helpers & BIP 67 & BIP 69 (#15)
SachinMeier Mar 8, 2023
059cee5
fix bip69 bug
SachinMeier Mar 9, 2023
7904b9c
fmt
SachinMeier Mar 9, 2023
1a9c6da
Merge branch 'sachin--fix-bip69-comparison-bug' into sachin--add-tapr…
SachinMeier Mar 9, 2023
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
186 changes: 152 additions & 34 deletions lib/extendedkey.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,21 @@ defmodule Bitcoinex.ExtendedKey do

def new(), do: %__MODULE__{child_nums: []}

@spec serialize(t()) :: {:ok, binary} | {:error, String.t()}
def serialize(dp = %__MODULE__{}), do: to_bin(dp)

@spec to_string(t()) :: {:ok, String.t()} | {:error, String.t()}
def to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "")
def to_string(%__MODULE__{child_nums: path}), do: path_to_string(path, "")

defp tto_string([], path_acc), do: {:ok, path_acc}
defp path_to_string([], path_acc), do: {:ok, path_acc}

defp tto_string([l | rest], path_acc) do
defp path_to_string([l | rest], path_acc) do
cond do
l == :any ->
tto_string(rest, path_acc <> "*/")
path_to_string(rest, path_acc <> "*/")

l == :anyh ->
tto_string(rest, path_acc <> "*'/")
path_to_string(rest, path_acc <> "*'/")

l > @max_hardened_child_num ->
{:error, "index cannot be greater than #{@max_hardened_child_num}"}
Expand All @@ -63,7 +66,7 @@ defmodule Bitcoinex.ExtendedKey do

# hardened
l >= @min_hardened_child_num ->
tto_string(
path_to_string(
rest,
path_acc <>
(l
Expand All @@ -74,31 +77,108 @@ defmodule Bitcoinex.ExtendedKey do

# unhardened
true ->
tto_string(rest, path_acc <> Integer.to_string(l) <> "/")
path_to_string(rest, path_acc <> Integer.to_string(l) <> "/")
end
end

@spec to_bin(t()) :: {:ok, binary} | {:error, String.t()}
def to_bin(%__MODULE__{child_nums: child_nums}) do
try do
{:ok, to_bin(child_nums, <<>>)}
rescue
e in ArgumentError -> {:error, e.message}
end
end

defp to_bin([], path_acc), do: path_acc

defp to_bin([lvl | rest], path_acc) do
cond do
lvl == :any or lvl == :anyh ->
raise(ArgumentError,
message: "Derivation Path with wildcard cannot be encoded to binary."
)

lvl > @max_hardened_child_num ->
raise(ArgumentError, message: "index cannot be greater than #{@max_hardened_child_num}")

lvl < @min_non_hardened_child_num ->
raise(ArgumentError, message: "index cannot be less than #{@min_non_hardened_child_num}")

true ->
lvlbin =
lvl
|> :binary.encode_unsigned(:little)
|> Bitcoinex.Utils.pad(4, :trailing)

to_bin(rest, path_acc <> lvlbin)
end
end

@spec parse(binary) :: {:ok, t()} | {:error, String.t()}
def parse(dp), do: from_bin(dp)

@spec from_string(String.t()) :: {:ok, t()} | {:error, String.t()}
def from_string(pathstr) do
try do
{:ok, %__MODULE__{child_nums: tfrom_string(String.split(pathstr, "/"))}}
{:ok,
%__MODULE__{
child_nums:
pathstr
|> String.split("/")
|> path_from_string([])
|> Enum.reverse()
}}
rescue
e in ArgumentError -> {:error, e.message}
end
end

defp tfrom_string(path_list) do
defp path_from_string(path_list, child_nums) do
case path_list do
[] -> []
[""] -> []
["m" | rest] -> tfrom_string(rest)
["*" | rest] -> [:any | tfrom_string(rest)]
["*'" | rest] -> [:anyh | tfrom_string(rest)]
["*h" | rest] -> [:anyh | tfrom_string(rest)]
[i | rest] -> [str_to_level(i) | tfrom_string(rest)]
[] ->
child_nums

[""] ->
child_nums

["m" | rest] ->
if child_nums != [] do
raise(ArgumentError,
message: "m can only be present at the begining of a derivation path."
)
else
path_from_string(rest, child_nums)
end

["*" | rest] ->
path_from_string(rest, [:any | child_nums])

["*'" | rest] ->
path_from_string(rest, [:anyh | child_nums])

["*h" | rest] ->
path_from_string(rest, [:anyh | child_nums])

[i | rest] ->
path_from_string(rest, [str_to_level(i) | child_nums])
end
end

@spec from_bin(binary) :: {:ok, t()} | {:error, String.t()}
def from_bin(bin) do
try do
{:ok, %__MODULE__{child_nums: Enum.reverse(from_bin(bin, []))}}
rescue
_e in ArgumentError -> {:error, "invalid binary encoding of derivation path"}
end
end

defp from_bin(<<>>, child_nums), do: child_nums

defp from_bin(<<level::little-unsigned-32, bin::binary>>, child_nums),
do: from_bin(bin, [level | child_nums])

defp str_to_level(level) do
{num, is_hardened} =
case String.split(level, ["'", "h"]) do
Expand All @@ -111,6 +191,7 @@ defmodule Bitcoinex.ExtendedKey do

nnum = String.to_integer(num)

# TODO benchmark and make this two comparisons
if nnum in @min_non_hardened_child_num..@max_non_hardened_child_num do
if is_hardened do
nnum + @min_hardened_child_num
Expand All @@ -124,6 +205,8 @@ defmodule Bitcoinex.ExtendedKey do

def add(%__MODULE__{child_nums: path1}, %__MODULE__{child_nums: path2}),
do: %__MODULE__{child_nums: path1 ++ path2}

def depth(%__MODULE__{child_nums: child_nums}), do: length(child_nums)
end

@type t :: %__MODULE__{
Expand Down Expand Up @@ -245,11 +328,25 @@ defmodule Bitcoinex.ExtendedKey do
# PARSE & SERIALIZE

@doc """
parse_extended_key takes binary or string representation
parse! calls parse, which takes binary or string representation
of an extended key and parses it to an extended key object.
parse! raises ArgumentError on failure.
"""
@spec parse!(binary) :: t()
def parse!(xpub) do
case parse(xpub) do
{:ok, res} -> res
{:error, msg} -> raise(ArgumentError, message: msg)
end
end

@doc """
parse takes binary or string representation
of an extended key and parses it to an extended key object
returns {:error, msg} on failure
"""
@spec parse_extended_key(binary) :: {:ok, t()} | {:error, String.t()}
def parse_extended_key(
@spec parse(binary) :: {:ok, t()} | {:error, String.t()}
def parse(
xkey =
<<prefix::binary-size(4), depth::binary-size(1), parent_fingerprint::binary-size(4),
child_num::binary-size(4), chaincode::binary-size(32), key::binary-size(33),
Expand Down Expand Up @@ -283,16 +380,27 @@ defmodule Bitcoinex.ExtendedKey do
end
end

# parse without checksum (used for PSBT encodings)
def parse(
xkey =
<<_prefix::binary-size(4), _depth::binary-size(1), _parent_fingerprint::binary-size(4),
_child_num::binary-size(4), _chaincode::binary-size(32), _key::binary-size(33)>>
) do
xkey
|> Base58.append_checksum()
|> parse()
end

# parse from string
def parse_extended_key(xkey) do
def parse(xkey) do
case Base58.decode(xkey) do
{:error, _} ->
{:error, "error parsing key"}

{:ok, xkey} ->
xkey
|> Base58.append_checksum()
|> parse_extended_key()
|> parse()
end
end

Expand All @@ -303,23 +411,33 @@ defmodule Bitcoinex.ExtendedKey do
end

@doc """
serialize_extended_key takes an extended key
serialize takes an extended key
and returns the binary
"""
@spec serialize_extended_key(t()) :: binary
def serialize_extended_key(xkey) do
(xkey.prefix <>
xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key)
|> Base58.append_checksum()
@spec serialize(t(), list({:with_checksum?, boolean})) :: binary
def serialize(xkey, opts \\ []) do
with_checksum? = Keyword.get(opts, :with_checksum?, true)

extended_key_without_checksum_bin =
xkey.prefix <>
xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key

case with_checksum? do
true ->
Base58.append_checksum(extended_key_without_checksum_bin)

false ->
extended_key_without_checksum_bin
end
end

@doc """
display returns the extended key as a string
"""
@spec display_extended_key(t()) :: String.t()
def display_extended_key(xkey) do
@spec display(t()) :: String.t()
def display(xkey) do
xkey
|> serialize_extended_key()
|> serialize()
|> Base58.encode_base()
end

Expand All @@ -339,7 +457,7 @@ defmodule Bitcoinex.ExtendedKey do

(prefix <> depth_fingerprint_childnum <> chaincode <> <<0>> <> key)
|> Base58.append_checksum()
|> parse_extended_key()
|> parse()
else
{:error, "invalid extended private key prefix"}
end
Expand Down Expand Up @@ -368,7 +486,7 @@ defmodule Bitcoinex.ExtendedKey do
|> Kernel.<>(xprv.chaincode)
|> Kernel.<>(pubkey)
|> Base58.append_checksum()
|> parse_extended_key()
|> parse()
rescue
_ in MatchError -> {:error, "invalid private key"}
end
Expand Down Expand Up @@ -475,7 +593,7 @@ defmodule Bitcoinex.ExtendedKey do
(xkey.prefix <>
child_depth <> fingerprint <> i <> child_chaincode <> Point.sec(pubkey))
|> Base58.append_checksum()
|> parse_extended_key()
|> parse()
end
end
end
Expand Down Expand Up @@ -526,7 +644,7 @@ defmodule Bitcoinex.ExtendedKey do

(xkey.prefix <> child_depth <> fingerprint <> i <> child_chaincode <> <<0>> <> child_key)
|> Base58.append_checksum()
|> parse_extended_key()
|> parse()
rescue
_ in MatchError -> {:error, "invalid private key in extended private key"}
end
Expand Down
2 changes: 1 addition & 1 deletion lib/lightning_network/invoice.ex
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ defmodule Bitcoinex.LightningNetwork.Invoice do

# TODO if destination exist from tagged field, we dun need to recover but to verify it with signature
# but that require convert lg sig before using secp256k1 to verify it
# TODO refactor too nested
# TODO refactor to nested
case Bitcoinex.Secp256k1.Ecdsa.ecdsa_recover_compact(hash, signature, recoveryId) do
{:ok, pubkey} ->
if is_nil(destination) or destination == pubkey do
Expand Down
3 changes: 3 additions & 0 deletions lib/opcode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ defmodule Bitcoinex.Opcode do
op_codeseparator: 0xAB,
op_checksig: 0xAC,
op_checksigverify: 0xAD,
# disabled in tapscript
op_checkmultisig: 0xAE,
# disabled in tapscript
op_checkmultisigverify: 0xAF,
op_nop1: 0xB0,
op_nop2: 0xB1,
Expand All @@ -240,6 +242,7 @@ defmodule Bitcoinex.Opcode do
op_nop8: 0xB7,
op_nop9: 0xB8,
op_nop10: 0xB9,
op_checksigadd: 0xBA,
op_smallinteger: 0xFA,
op_pubkeys: 0xFB,
op_pubkeyhash: 0xFD,
Expand Down
Loading