From f479ccbe1dc750fbdd6d0a85651606d19412e0d2 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 00:46:24 -0700 Subject: [PATCH 01/40] first draft adaptor signatures --- lib/secp256k1/schnorr.ex | 147 +++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 12 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 0d21f4b..fac1ed8 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -41,24 +41,72 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) - e = - tagged_hash_challenge(Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) + e = calculate_e(r_point, d_point, z_bytes) - sig_s = - (k.d + d.d * e) - |> Math.modulo(@n) + sig_s = calculate_s(k, d, e) {:ok, %Signature{r: r_point.x, s: sig_s}} end end end + @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} + def tweaked_sign(privkey, z, aux, pubtweak) do + case PrivateKey.validate(privkey) do + {:error, msg} -> + {:error, msg} + + {:ok, privkey} -> + z_bytes = Utils.int_to_big(z, 32) + aux_bytes = Utils.int_to_big(aux, 32) + d_point = PrivateKey.to_point(privkey) + d = Secp256k1.force_even_y(privkey) + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + + {:ok, k0} = + tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + |> PrivateKey.new() + + if k0.d == 0 do + {:error, "invalid aux randomness"} + else + r_point = PrivateKey.to_point(k0) + k = Secp256k1.force_even_y(k0) + + tweaked_r_point = Math.add(r_point, pubtweak) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, pubtweak} + end + end + end + defp tagged_hash_aux(aux), do: Utils.tagged_hash("BIP0340/aux", aux) defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_s(k, d, e) do + (k.d + d.d * e) + |> Math.modulo(@n) + end + + defp calculate_e(nonce_bytes, pubkey_bytes, msg_bytes) do + tagged_hash_challenge(nonce_bytes <> pubkey_bytes <> msg_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + end + + def validate_r(r_point, rx) do + !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx + end + @doc """ verify whether the schnorr signature is valid for the given message hash and public key """ @@ -70,16 +118,91 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) - e = - tagged_hash_challenge(r_bytes <> Point.x_bytes(pubkey) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) + e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) r_point = @generator_point |> Math.multiply(s) |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) - !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == r + validate_r(r_point, r) + end + + @spec verify_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + boolean | {:error, String.t()} + def verify_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do + if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} + + case Point.lift_x(r) do + {:error, err} -> + {:error, err} + + {:ok, given_r_point} -> + tweaked_point = Math.add(given_r_point, pubtweak) + + z_bytes = Utils.int_to_big(z, 32) + + e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) + + r_point = + @generator_point + |> Math.multiply(s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + validate_r(r_point, r) + end + end + + + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: {:ok, non_neg_integer} | {:error, String.t()} + def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do + tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) + + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(tweaked_r_bytes, Point.x_bytes(pubkey), z_bytes) + + tweaked_r_point = + @generator_point + |> Math.multiply(tweaked_s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + if validate_r(tweaked_r_point, tweaked_r) do + if tweaked_s < s do + {:error, "invalid tweak"} + else + {:ok, tweaked_s - s} + end + else + {:error, "failed to extract tweak due to invalid signature"} + end + end + + @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: {:ok, Signature.t()} | {:error, String.t()} + def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do + case Point.lift_x(r) do + {:error, err} -> + {:error, err} + + {:ok, r_point} -> + tweak_point = PrivateKey.to_point(tweak) + + tweaked_r = Math.add(r_point, tweak_point).x + tweaked_s = tweak+s + + r_bytes = Utils.int_to_big(r, 32) + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) + + tweaked_r_point = + @generator_point + |> Math.multiply(tweaked_s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + if validate_r(tweaked_r_point, tweaked_r) do + {:ok, %Signature{r: tweaked_r, s: tweaked_s}} + else + {:error, "tweak does not produce valid signature"} + end + end end end From 2ccff2e66c93d5454bafd39fd6834f3611482f61 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 00:48:07 -0700 Subject: [PATCH 02/40] fmt --- lib/secp256k1/privatekey.ex | 1 + lib/secp256k1/schnorr.ex | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/secp256k1/privatekey.ex b/lib/secp256k1/privatekey.ex index 77b0a2a..734feb7 100644 --- a/lib/secp256k1/privatekey.ex +++ b/lib/secp256k1/privatekey.ex @@ -54,6 +54,7 @@ defmodule Bitcoinex.Secp256k1.PrivateKey do case new(d) do {:ok, sk} -> to_point(sk) + {:error, msg} -> {:error, msg} end diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index fac1ed8..021dade 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -50,7 +50,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} + @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: + {:ok, Signature.t(), Point.t()} | {:error, String.t()} def tweaked_sign(privkey, z, aux, pubtweak) do case PrivateKey.validate(privkey) do {:error, msg} -> @@ -153,8 +154,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: {:ok, non_neg_integer} | {:error, String.t()} + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: + {:ok, non_neg_integer} | {:error, String.t()} def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) @@ -177,7 +178,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: {:ok, Signature.t()} | {:error, String.t()} + @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: + {:ok, Signature.t()} | {:error, String.t()} def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do case Point.lift_x(r) do {:error, err} -> @@ -187,7 +189,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tweak_point = PrivateKey.to_point(tweak) tweaked_r = Math.add(r_point, tweak_point).x - tweaked_s = tweak+s + tweaked_s = tweak + s r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) From 40372d54aefd47962ab3f0848f9b596928a12d5d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 01:03:36 -0700 Subject: [PATCH 03/40] fix typo --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 021dade..811c488 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -41,7 +41,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) - e = calculate_e(r_point, d_point, z_bytes) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) sig_s = calculate_s(k, d, e) From 4baa1f227e84da5191da923776bbdfc3e6272959 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 02:48:51 -0700 Subject: [PATCH 04/40] simplify code & add extract_sig from adaptor --- lib/secp256k1/schnorr.ex | 90 +++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 811c488..907697d 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -50,9 +50,9 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: + @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} - def tweaked_sign(privkey, z, aux, pubtweak) do + def sign_for_tweak(privkey, z, aux, pubtweak) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -75,8 +75,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k) tweaked_r_point = Math.add(r_point, pubtweak) @@ -93,6 +93,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_r(pubkey, s, e) do + @generator_point + |> Math.multiply(s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + end + defp calculate_s(k, d, e) do (k.d + d.d * e) |> Math.modulo(@n) @@ -104,7 +110,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - def validate_r(r_point, rx) do + defp validate_r(r_point, rx) do !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx end @@ -118,20 +124,16 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) - r_point = - @generator_point - |> Math.multiply(s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + r_point = calculate_r(pubkey, s, e) validate_r(r_point, r) end - @spec verify_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: boolean | {:error, String.t()} - def verify_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} case Point.lift_x(r) do @@ -140,34 +142,33 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, given_r_point} -> tweaked_point = Math.add(given_r_point, pubtweak) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - r_point = - @generator_point - |> Math.multiply(s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + r_point = calculate_r(pubkey, s, e) validate_r(r_point, r) end end - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: - {:ok, non_neg_integer} | {:error, String.t()} - def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do - tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) + @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() + def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(tweaked_r_bytes, Point.x_bytes(pubkey), z_bytes) - - tweaked_r_point = - @generator_point - |> Math.multiply(tweaked_s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + def tweak_signature(%Signature{r: r, s: s}, tweak) do + {:ok, t} = PrivateKey.new(tweak) + t_point = PrivateKey.to_point(t) + {:ok, r_point} = Point.lift_x(r) + %Signature{r: Math.add(r_point, t_point).x, s: tweak + s} + end - if validate_r(tweaked_r_point, tweaked_r) do + @doc """ + extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), + and a completed signature, verifies the signature, and then returns the revealed tweak secret + """ + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: + {:ok, non_neg_integer} | {:error, String.t()} + def extract_tweak(pubkey, z, %Signature{s: s}, tweaked_sig = %Signature{s: tweaked_s}) do + if verify_signature(pubkey, z, tweaked_sig) do if tweaked_s < s do {:error, "invalid tweak"} else @@ -178,30 +179,35 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: + @doc """ + extract_tweaked_signature takes a signer pubkey, message hash z, untweaked signature (adaptor signature), + and a tweak secret, and uses it to verify the adaptor signature, and returns the complete signature + """ + @spec extract_tweaked_signature( + Point.t(), + non_neg_integer, + Signature.t(), + non_neg_integer | PrivateKey.t() + ) :: {:ok, Signature.t()} | {:error, String.t()} + def extract_tweaked_signature(pubkey, z, sig, t = %PrivateKey{}), + do: extract_tweaked_signature(pubkey, z, sig, t.d) + def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, r_point} -> - tweak_point = PrivateKey.to_point(tweak) - - tweaked_r = Math.add(r_point, tweak_point).x tweaked_s = tweak + s - r_bytes = Utils.int_to_big(r, 32) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) + tweak_point = PrivateKey.to_point(tweak) + tweaked_r_point = Math.add(r_point, tweak_point) - tweaked_r_point = - @generator_point - |> Math.multiply(tweaked_s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + tweaked_sig = %Signature{r: tweaked_r_point.x, s: tweaked_s} - if validate_r(tweaked_r_point, tweaked_r) do - {:ok, %Signature{r: tweaked_r, s: tweaked_s}} + if verify_signature(pubkey, z, tweaked_sig) do + {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} else {:error, "tweak does not produce valid signature"} end From a04f43e583661dc262fbebba8bf91a4a353c974e Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 02:53:19 -0700 Subject: [PATCH 05/40] make code consistent across sign & adaptor sign --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 907697d..868d711 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -38,8 +38,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k) e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) From 47d2a012d799b89d34de241ac79bb0143ae710d2 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 27 Oct 2022 14:11:18 -0700 Subject: [PATCH 06/40] temp: try to fix R+T not even bug --- lib/secp256k1/point.ex | 2 +- lib/secp256k1/privatekey.ex | 5 + lib/secp256k1/schnorr.ex | 101 +++++++++++++------- lib/secp256k1/secp256k1.ex | 9 +- scripts/gen_test_vectors.exs | 58 ++++++++++++ scripts/schnorr_adaptor.exs | 40 ++++++++ scripts/test_broken_adaptor.exs | 30 ++++++ test/secp256k1/adaptor_test.exs | 151 ++++++++++++++++++++++++++++++ test/secp256k1/schnorr_test.exs | 8 +- test/secp256k1/secp256k1_test.exs | 17 +++- 10 files changed, 383 insertions(+), 38 deletions(-) create mode 100644 scripts/gen_test_vectors.exs create mode 100644 scripts/schnorr_adaptor.exs create mode 100644 scripts/test_broken_adaptor.exs create mode 100644 test/secp256k1/adaptor_test.exs diff --git a/lib/secp256k1/point.ex b/lib/secp256k1/point.ex index 2de8ddc..1bb6b7a 100644 --- a/lib/secp256k1/point.ex +++ b/lib/secp256k1/point.ex @@ -124,7 +124,7 @@ defmodule Bitcoinex.Secp256k1.Point do """ @spec x_bytes(t()) :: binary def x_bytes(%__MODULE__{x: x}) do - Bitcoinex.Utils.pad(:binary.encode_unsigned(x), 32, :leading) + Utils.int_to_big(x, 32) end @doc """ diff --git a/lib/secp256k1/privatekey.ex b/lib/secp256k1/privatekey.ex index 734feb7..1f01bf4 100644 --- a/lib/secp256k1/privatekey.ex +++ b/lib/secp256k1/privatekey.ex @@ -60,6 +60,11 @@ defmodule Bitcoinex.Secp256k1.PrivateKey do end end + @spec negate(t()) :: t() + def negate(%__MODULE__{d: d}) do + %__MODULE__{d: @n - d} + end + @doc """ serialize_private_key serializes a private key into hex """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 868d711..513d561 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -7,6 +7,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do alias Bitcoinex.Utils @n Params.curve().n + @p Params.curve().p @generator_point %Point{ x: Params.curve().g_x, @@ -29,30 +30,18 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = - tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) - |> PrivateKey.new() + {:ok, k} = calculate_k(t, d_point, z_bytes) + r_point = PrivateKey.to_point(k) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - k = Secp256k1.force_even_y(k0) - r_point = PrivateKey.to_point(k) - - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, %Signature{r: r_point.x, s: sig_s}} end end @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} - def sign_for_tweak(privkey, z, aux, pubtweak) do + def sign_for_tweak(privkey, z, aux, tweak_point) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -75,16 +64,28 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k0) + tweaked_r_point = Math.add(r_point, tweak_point) + IO.puts("!---\n") + IO.puts("k0: " <> to_string(k0.d)) + IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") + # we must ensure that R+T, the final signature's nonce point has even y + k = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) + IO.puts("k1: " <> to_string(k.d)) r_point = PrivateKey.to_point(k) + tweaked_r_point = Math.add(r_point, tweak_point) + IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") + IO.puts("---!\n") + if Point.has_even_y(tweaked_r_point) do + sameK = to_string(k == k0) + {:error, "what is going on: sameK: #{sameK}"} + else + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point} + end - tweaked_r_point = Math.add(r_point, pubtweak) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, pubtweak} end end end @@ -93,6 +94,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_r(pubkey, s, e) do @generator_point |> Math.multiply(s) @@ -104,6 +106,30 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + defp get_k_for_even_tweaked_nonce(k, tweaked_point) do + if Point.has_even_y(tweaked_point) do + IO.puts("k unchanged\n") + k + else + IO.puts("k changed\n") + PrivateKey.negate(k) + end + end + + defp calculate_k(t, d_point, z_bytes) do + {:ok, k0} = + tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + |> PrivateKey.new() + + if k0.d == 0 do + {:error, "invalid aux randomness"} + else + {:ok, Secp256k1.force_even_y(k0)} + end + end + defp calculate_e(nonce_bytes, pubkey_bytes, msg_bytes) do tagged_hash_challenge(nonce_bytes <> pubkey_bytes <> msg_bytes) |> :binary.decode_unsigned() @@ -111,7 +137,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end defp validate_r(r_point, rx) do - !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx + cond do + Point.is_inf(r_point) -> + {:error, "R point is infinite"} + !Point.has_even_y(r_point) -> + {:error, "R point is not even"} + r_point.x != rx -> + {:error, "x's do not match #{r_point.x} vs #{rx}"} + true -> true + end end @doc """ @@ -120,7 +154,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec verify_signature(Point.t(), non_neg_integer, Signature.t()) :: boolean | {:error, String.t()} def verify_signature(pubkey, z, %Signature{r: r, s: s}) do - if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} + if r >= @p || s >= @n, do: {:error, "invalid signature"} r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) @@ -133,15 +167,14 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: boolean | {:error, String.t()} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do - if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} - + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point) do + if r >= @p || s >= @n, do: {:error, "invalid signature"} case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, given_r_point} -> - tweaked_point = Math.add(given_r_point, pubtweak) + tweaked_point = Math.add(given_r_point, tweak_point) z_bytes = Utils.int_to_big(z, 32) e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) @@ -153,12 +186,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak) do {:ok, t} = PrivateKey.new(tweak) t_point = PrivateKey.to_point(t) {:ok, r_point} = Point.lift_x(r) - %Signature{r: Math.add(r_point, t_point).x, s: tweak + s} + tw_s = Math.modulo(tweak+s, @n) + %Signature{r: Math.add(r_point, t_point).x, s: tw_s} end @doc """ diff --git a/lib/secp256k1/secp256k1.ex b/lib/secp256k1/secp256k1.ex index e99cbc2..641b01f 100644 --- a/lib/secp256k1/secp256k1.ex +++ b/lib/secp256k1/secp256k1.ex @@ -113,7 +113,14 @@ defmodule Bitcoinex.Secp256k1 do @spec serialize_signature(t()) :: binary def serialize_signature(%__MODULE__{r: r, s: s}) do - :binary.encode_unsigned(r) <> :binary.encode_unsigned(s) + Utils.int_to_big(r, 32) <> Utils.int_to_big(s, 32) + end + + @spec to_hex(t()) :: binary + def to_hex(sig) do + sig + |> serialize_signature() + |> Base.encode16(case: :lower) end @doc """ diff --git a/scripts/gen_test_vectors.exs b/scripts/gen_test_vectors.exs new file mode 100644 index 0000000..43ed9ca --- /dev/null +++ b/scripts/gen_test_vectors.exs @@ -0,0 +1,58 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Math, PrivateKey, Point, Schnorr, Signature} +alias Bitcoinex.Utils + +to_hex = fn i -> "0x" <> Integer.to_string(i, 16) end + +write_row = fn file, sk, pk, tw, t_point, z, aux, ut_sig, tw_sig, err, is_tweaked_s_even, is_tweaked_s_ooo -> IO.binwrite(file, +to_hex.(sk.d) <> "," <> Point.x_hex(pk) <> "," <> to_hex.(tw.d) <> "," +<> Point.x_hex(t_point) <> "," <> to_hex.(z) <> "," <> to_hex.(aux) <> "," +<> Signature.to_hex(ut_sig) <> "," <> Signature.to_hex(tw_sig) <> "," +<> err <> "," <> to_string(is_tweaked_s_even) <> "," <> to_string(is_tweaked_s_ooo) <> "\n") +end + +order_n = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 + +{:ok, good_file} = File.open("schnorr_adaptor_test_vectors-good.csv", [:write]) +{:ok, bad_file} = File.open("schnorr_adaptor_test_vectors-bad.csv", [:write]) + +IO.binwrite(good_file, "private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,is_tweaked_s_even\n") +IO.binwrite(bad_file, "private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,is_tweaked_s_even\n") + +for _ <- 1..50 do + ski = :rand.uniform(order_n-1) + {:ok, sk0} = PrivateKey.new(ski) + sk = Secp256k1.force_even_y(sk0) + pk = PrivateKey.to_point(sk) + + # tweak + ti = :rand.uniform(order_n-1) + {:ok, tw} = PrivateKey.new(ti) + tw = Secp256k1.force_even_y(tw) + tw_point = PrivateKey.to_point(tw) + + msg = + :rand.uniform(order_n-1) + |> :binary.encode_unsigned() + z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + + aux = :rand.uniform(order_n-1) + + # create adaptor sig + {:ok, ut_sig, _tw_point} = Schnorr.sign_for_tweak(sk, z, aux, tw_point) + tw_sig = Schnorr.tweak_signature(ut_sig, tw.d) + + # checks + tweaked_s = tw.d+ut_sig.s + is_tweaked_s_ooo = tweaked_s > order_n + {:ok, tweaked_s} = PrivateKey.new(Math.modulo(tweaked_s, order_n)) + tweaked_forced_s = Secp256k1.force_even_y(tweaked_s) + is_tweaked_s_even = tweaked_forced_s == tweaked_s + + case Schnorr.verify_signature(pk, z, tw_sig) do + true -> + write_row.(good_file, sk, pk, tw, tw_point, z, aux, ut_sig, tw_sig, "", is_tweaked_s_even, is_tweaked_s_ooo) + {:error, err} -> + write_row.(bad_file, sk, pk, tw, tw_point, z, aux, ut_sig, tw_sig, err, is_tweaked_s_even, is_tweaked_s_ooo) + end +end diff --git a/scripts/schnorr_adaptor.exs b/scripts/schnorr_adaptor.exs new file mode 100644 index 0000000..932deb5 --- /dev/null +++ b/scripts/schnorr_adaptor.exs @@ -0,0 +1,40 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{PrivateKey, Point, Schnorr, Signature} +alias Bitcoinex.Utils +# private key +{:ok, sk} = PrivateKey.new(1234123412341234123412341234) +pk = PrivateKey.to_point(sk) + +# tweak +{:ok, t} = PrivateKey.new(658393766392737484910002828395) +t = Secp256k1.force_even_y(t) +t_point = PrivateKey.to_point(t) + +msg = "tweakin" +z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + +aux = 1203948712823749283 + +# create adaptor sig +{:ok, ut_sig, t_point_} = Schnorr.sign_for_tweak(sk, z, aux, t_point) +t_point_ == t_point + +# adaptor sig is not a valid schnorr sig +!Schnorr.verify_signature(pk, z, ut_sig) + +# verify adaptor signature +Schnorr.verify_untweaked_signature(pk, z, ut_sig, t_point) + +# complete adaptor sig +tw_sig = Schnorr.tweak_signature(ut_sig, t.d) + +# complete sig must be valid schnorr sig +Schnorr.verify_signature(pk, z, tw_sig) + +# extract tweak +{:ok, tweak} = Schnorr.extract_tweak(pk, z, ut_sig, tw_sig) +tweak == t.d + +# extract signature given tweak +{:ok, sig} = Schnorr.extract_tweaked_signature(pk, z, ut_sig, t.d) +sig == tw_sig diff --git a/scripts/test_broken_adaptor.exs b/scripts/test_broken_adaptor.exs new file mode 100644 index 0000000..c9919b3 --- /dev/null +++ b/scripts/test_broken_adaptor.exs @@ -0,0 +1,30 @@ + +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} + +test = %{ + privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, + pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, + tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, + tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, + message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, + aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, + untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", + tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" +} + +z = test.message_hash +aux = test.aux_rand + +{:ok, t} = PrivateKey.new(test.tweak_secret) +t2 = Secp256k1.force_even_y(t) +t_point = PrivateKey.to_point(t) + +{:ok, sk} = PrivateKey.new(test.privkey) +sk2 = Secp256k1.force_even_y(sk) + +# use sk2 +pk = PrivateKey.to_point(sk2) +{:ok, ut_sig, t_point_} = Schnorr.sign_for_tweak(sk2, z, aux, t_point) +tw_sig = Schnorr.tweak_signature(ut_sig, t.d) +Schnorr.verify_signature(pk, z, tw_sig) diff --git a/test/secp256k1/adaptor_test.exs b/test/secp256k1/adaptor_test.exs new file mode 100644 index 0000000..127cc6b --- /dev/null +++ b/test/secp256k1/adaptor_test.exs @@ -0,0 +1,151 @@ +defmodule Bitcoinex.Secp256k1.SchnorrTest do + use ExUnit.Case + + alias Bitcoinex.Secp256k1 + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} + + + @schnorr_adaptor_signatures [ + %{ + privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, + pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, + tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, + tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, + message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, + aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, + untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", + tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" + }, + %{ + privkey: 0x6F0B8C685BEB787F3121851AC13DAD9549C235217C5F578D9E7BE3B3845D8773, + pubkey: 0x825fa6ec886022247b7b98d24d47a6f03d3a47f2fa3916a244eba7114ea94fa1, + tweak_secret: 0x76D2C1FE7E575063C8888D935645973AC3AE1FFD1C97F25D14F7567657D7F748, + tweak_point: 0xb99378d7cf782cf6ff265ef8e3ba447b36da89224c0682ca61cc9916a4fec4bf, + message_hash: 0xA193926FAFF8CE363937D8655892E60D45661C5FC510BC711E4B5049A6B4DD30, + aux_rand: 0x76C66B394932DC5704EC0FB4CE4D76D388707396AC64D9C35BFC081F11D51124, + untweaked_sig: "864897cd95c22dda7fce0435fe365c658118e52d578ba77c25d1f1e63ff3cd0916b38c325759c310e467a0ec026d8362b2f5155adb19a17eb06a5c661b039be1", + tweaked_sig: "e277ad505af9e82abcb122c3df410459494af86315988d4596bf0fb71f64bf338d864e30d5b11374acf02e7f58b31a9d76a33557f7b193dbc561b2dc72db9329" + }, + %{ + privkey: 0x624FB68C6C2474358EF57A9810BA5A17855D0FF8676D5D80E93DF314D5C3D277, + pubkey: 0x4af83421073f24e51235c43ffb85aa94dc3139af958a2532c6b412832fbbacc4, + tweak_secret: 0xF1997F60060268C14B81744869AEB04098A93704C3BDA2A640A1B26F74E246F4, + tweak_point: 0xb9dc16baaf262b3a60a1979833f9ca350e2219121fdb43d3fd2045d3142bb71f, + message_hash: 0x97FF15503ECA5167E871E04998F3C90976E8FAFF9AD1AF54F045153BA41E1663, + aux_rand: 0x71C1FE1F3A27B04EEC4142AC1612A7C6419B1740716E2C29CEE319E718950A04, + untweaked_sig: "08365373469000bca3fa28a95469f6331a20d4f03429372573b3c2d3d3d5b0f6801f053ece6e34cdf9fa8f43ec2ed3b730c388c0e444dd45f9cc1f920adac25f", + tweaked_sig: "9e3deb23f27730ce1be854b0145cc9bf99e3ee10491d28308160c7b65b0a56e871b8849ed4709d8f457c038c55dd83f90ebde2def8b9dfb07a9b7374af86c812" + }, + %{ + privkey: 0xB5196100AAC42311C9B33B1746CA0B38AC4547936DDC4AFFF547A862A4BF9890, + pubkey: 0x5b279f79dcf2414b66160515de53ac471a043d6d9f62fd007c09ad90704ffd28, + tweak_secret: 0xA69690763B4034FCF0F269E80464643E521475BFE08162632971AE0BDAA7AE8E, + tweak_point: 0xe9f219bb7c0a390a7df96a4f33b7f7d6179a9a4aa216ed1a750ddba1719072f0, + message_hash: 0xAD6F06747576C8EC210113A2C070A7A5688616EE841FA61026D2FF1CF9F7970F, + aux_rand: 0x63241AA5C78B99A849025EB3056E84DB011769DC8BBE782FAFA6E89D2BEE0C16, + untweaked_sig: "ef3766f37e9235e9652418bd60c54cd8e9c2db06f7effc542415fc76fac9c5cb75cc36cc9d5c20d303c30e43c2be82708141b8f721f75c7f889ebb24da81c818", + tweaked_sig: "aa9788a104f497f788bf4979a791a04c8408fd2f82075d5810f5f85394d567761c62c742d89c55cff4b5782bc722e6b018a751d053301ea6f23e0aa3e4f33565" + }, + %{ + privkey: 0x45AC71188B8B3B180D806ABB0289AB23357BF7CE866BFDB13509C68BAC5C88C4, + pubkey: 0x8b688d7ce32ab13bed3f0c5f119791f8b76913809c22983c8b6713d6fdc6f5a3, + tweak_secret: 0x6D2B06A08C0F20A64FA06DADF35F992F4DA441A29541268FB5F9AA9B7977A886, + tweak_point: 0x4d01a75b79ad34ce9d1dec59c581881db6a05865807705558a31e5b19047b024, + message_hash: 0x1E3D05CC94304EB6CEF384D5ADF36827A6CA4299D70B821B60240995F75BB6CB, + aux_rand: 0x7EFC5AD98D6AA0542A9534FA8637AB87A1B8E48FBAFD89AC6EA9348B44EEA049, + untweaked_sig: "655244da978cb1fa37690f3fe98c0e78b58390207781dae0d57f74dde06c0c3f6c8463e2c5f1e4dcabdfa050d9f18e3dfdd4bd70b55a024f3beab45fecb6e255", + tweaked_sig: "29abdd275d062b0bef51113fabaf3a70652387e7fa227c54b968b4943a7f4312d9af6a8352010582fb800dfecd51276d4b78ff134a9b28def1e45efb662e8adb" + }, + %{ + privkey: 0x24D807AB16645D504C75AA9B6355444FC291298AABEB4DAC5EB070898AD645B0, + pubkey: 0xf7609d85839d360fdba6a7bcde848807baeb6417d5ed4258c1c8bc4650543c7a, + tweak_secret: 0x4ABE441BF15DBB0378C2763FCA1E967EFA2636C6292A80711DA55AC7810CAA2A, + tweak_point: 0x6690f7703df1a815d8b4391889c47d8d8837369c6f2e00d5eb92abf1b90a206f, + message_hash: 0x9AE6F80954DA7A3D615987FC0CE97CC4E4567F5425A733DBD4C0BD496A3D5622, + aux_rand: 0xA421B1D6C0DA447395A19B0CF5BBB9879DAE8303CFD33C46F001DBE7EFE9853, + untweaked_sig: "96b4d6d0a19e7f42eea9ee4ad8d36a19e039623eeea47a6fb1658df2e20f7e23cbc02c5c59d7961cf88402a346584af229cb795295c5ea108440bd7de5ea01f0", + tweaked_sig: "a57c39c39b18fd5e6f572253e19227252a4a81a4ac3dc1ef804e6b306b142b10167e70784b355120714678e31076e1726942d3320fa7ca45e213b9b896c06ad9" + }, + %{ + privkey: 0x7EDA1030020279FA24CC33B79A665B73A9E715EB1EB2C6DBEBEE21AD7BCF1E2C, + pubkey: 0x65c57e92bd052d623ac708c34a9a73851e37c81cb763b9fe1ff88437ce42245f, + tweak_secret: 0x1DE275F6F723416882A18E0F4CCD398B7AA9C2A701BFD16D448A118BF82D3027, + tweak_point: 0xcbc6ed86f4442c6403d5c0a3b8157489c5cc55d5137bbd4648c58301979b6f6c, + message_hash: 0xA744D50F65508DDE9278AECAD6A2759BE3A83709877B427E4098085DB27B9826, + aux_rand: 0x12F582D5FA0B2F6245985FBC7C7220C55D441D027B64BA2A7289B9A15E5106F7, + untweaked_sig: "454d9f3fe892e1f31638a258b135ddb74beaed682936830b1d233432e7a2a575d9953241a92d0172bf8df6e198dc7a9052638337483e0881ccb96ba38ea973d3", + tweaked_sig: "db076ae6615ccd083a0d8e6d9e78c28cba696f4c4d3fbfb5c5a55b23d4de0d0af777a838a05042db422f84f0e5a9b41bcd0d45de49fdd9ef11437d2f86d6a3fa" + }, + %{ + privkey: 0x95896701D713F8AA383176C2BCD61F8EC55A29C5169AEBE9A8A32AC9803F8E7D, + pubkey: 0xe7d2b5e311e4f7e81d05c5c56a94f0e5438da78b2e512f78b540114dcf931080, + tweak_secret: 0x7038F53CA5C13C1534A595BC74799657805CB864A21AC78618E661E2A27F6DE4, + tweak_point: 0xe76f186a03ef4ca41b55ff18e2de2f713cf9a1f97f23f627681a2a47bea24a95, + message_hash: 0x4928F4C94434C570F12CF548F4B2F4C9124F5E6D1371020D55EC3D508DA49D61, + aux_rand: 0x51DDDD197BF29EE74235C80CECBE06C8E012BFFC0E227E87A966AD9BB6E6A07B, + untweaked_sig: "46d4e627dc2a8d3864f4e6738f88ab891e5e51da7b08251218d11b0f56f047b4d91271d08e0ba2ecde0169ce02307e49bca6cc749e3c6869dad5ace39f09c943", + tweaked_sig: "83ba54b75d4516cb376b51fc8fb9cd560aae652a2ff8cded96cb052ed3b1e12e494b670d33ccdf0212a6ff8a76aa14a28254a7f2910e8fb433e9b0397152f5e6" + }, + %{ + privkey: 0x695563958BE07A20700F6B624C2EBA9F336C47ADF7B8E202388600132D2E7CF3, + pubkey: 0x97826ac90a2e8ebf9c2f94c5f23863aed915b4c4e77701b9ef7b43e9c501d0f3, + tweak_secret: 0x6FDDBC57B407184C1D1C0B9040A14A19EF8824222431F39CB89FED2C9D67CE41, + tweak_point: 0xe772ca746c6f40e607167f6d95414dab603fb36e0a4e473ab69d6e1036c209ea, + message_hash: 0x9A6BBFEF82261C8EFA9CD7F3481728D68554B2E698187797EAF60A734B6307D9, + aux_rand: 0x1ED0360C5241E28038735B43C6705AE2A1A971CDD94FCED6817C005ED2DF8237, + untweaked_sig: "d44e4a61254b90b671a278ee8ae73add71c5cb89b9fda0611cabb3ab46c4bb5d2511324a297a4027f3bef12a79e07a16d1419922e0d6c205066b538efc655dfe", + tweaked_sig: "840a95a2d67b1fe09584a2b9f353277c318d4b21774a777a2dd2814de4e9d22094eeeea1dd81587410dafcbaba81c430c0c9bd450508b5a1bf0b40bb99cd2c3f" + } + ] + + describe "adaptor signature tests" do + test "test adaptor signatures" do + for t <- @schnorr_adaptor_signatures do + {:ok, sk} = PrivateKey.new(t.privkey) + sk = Secp256k1.force_even_y(sk) + pk = PrivateKey.to_point(sk) + # check pubkey is correct + assert t.pubkey == pk.x + + {:ok, tw} = PrivateKey.new(t.tweak_secret) + tw = Secp256k1.force_even_y(tw) + tw_point = PrivateKey.to_point(tw) + # check tweak point is correct + assert t.tweak_point == tw_point.x + + # parse sigs + {:ok, c_ut_sig} = Signature.parse_signature(t.untweaked_sig) + {:ok, c_tw_sig} = Signature.parse_signature(t.tweaked_sig) + + {:ok, ut_sig, t_point} = Schnorr.sign_for_tweak(sk, t.message_hash, t.aux_rand, tw_point) + # check tweak point hasn't changed + assert t_point == tw_point + # check untweaked sig + assert c_ut_sig == ut_sig + # adaptor sig is not a valid schnorr sig, must fail + assert !Schnorr.verify_signature(pk, t.message_hash, ut_sig) + # verify adaptor signature + assert Schnorr.verify_untweaked_signature(pk, t.message_hash, ut_sig, tw_point) + # complete adaptor sig + tw_sig = Schnorr.tweak_signature(ut_sig, tw) + # check sig + assert c_tw_sig == tw_sig + # ensure tweaked sig is same when using tweak private key and integer + tw_sig0 = Schnorr.tweak_signature(ut_sig, tw.d) + assert tw_sig == tw_sig0 + + # complete sig must be valid schnorr sig + assert Schnorr.verify_signature(pk, t.message_hash, tw_sig) + + # extract tweak secret + {:ok, tweak} = Schnorr.extract_tweak(pk, t.message_hash, ut_sig, tw_sig) + assert tweak == tw.d + assert t.tweak_secret == tweak + + # extract signature given tweak + {:ok, sig} = Schnorr.extract_tweaked_signature(pk, t.message_hash, ut_sig, t.tweak_secret) + assert sig == tw_sig + end + end + end +end diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index d5aa295..522d940 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -5,6 +5,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do alias Bitcoinex.Utils alias Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1.{Point, PrivateKey, Schnorr, Signature} + # alias Bitcoinex.Secp256k1.{PrivateKey} # BIP340 official test vectors: # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv @@ -153,6 +154,9 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do } ] + + + describe "sign/3" do test "sign" do for t <- @schnorr_signatures_with_secrets do @@ -260,7 +264,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do for _ <- 1..1000 do secret = - 32 + 31 |> :crypto.strong_rand_bytes() |> :binary.decode_unsigned() @@ -271,4 +275,6 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end end end + + end diff --git a/test/secp256k1/secp256k1_test.exs b/test/secp256k1/secp256k1_test.exs index a723d01..d03547d 100644 --- a/test/secp256k1/secp256k1_test.exs +++ b/test/secp256k1/secp256k1_test.exs @@ -3,7 +3,7 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do doctest Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{Signature} + alias Bitcoinex.Secp256k1.{Signature, PrivateKey} @valid_der_signatures [ %{ @@ -165,4 +165,19 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do end end end + + describe "force_even_y/1" do + test "force_even_y" do + for _ <- 1..1000 do + secret = + 31 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + + privkey = Secp256k1.force_even_y(%PrivateKey{d: secret}) + pubkey = PrivateKey.to_point(privkey) + assert rem(pubkey.y,2) == 0 + end + end + end end From 05f5edd06dc9ff0416a4ba57f69386fd93e0112a Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 27 Jan 2023 22:43:31 -0800 Subject: [PATCH 07/40] temp fix adaptors --- .tool-versions | 4 +- lib/secp256k1/schnorr.ex | 94 +++++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/.tool-versions b/.tool-versions index 7ffb442..440d747 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.10.4-otp-22 -erlang 22.3.4.1 +elixir 1.14.2-otp-25 +erlang 25.1.2 diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 513d561..5dfa7d2 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -40,7 +40,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: - {:ok, Signature.t(), Point.t()} | {:error, String.t()} + {:ok, Signature.t(), Point.t(), bool} | {:error, String.t()} def sign_for_tweak(privkey, z, aux, tweak_point) do case PrivateKey.validate(privkey) do {:error, msg} -> @@ -63,29 +63,19 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} - else + + r_point = PrivateKey.to_point(k0) tweaked_r_point = Math.add(r_point, tweak_point) - IO.puts("!---\n") - IO.puts("k0: " <> to_string(k0.d)) - IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") # we must ensure that R+T, the final signature's nonce point has even y - k = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) - IO.puts("k1: " <> to_string(k.d)) + {k, was_negated} = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) r_point = PrivateKey.to_point(k) tweaked_r_point = Math.add(r_point, tweak_point) - IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") - IO.puts("---!\n") - if Point.has_even_y(tweaked_r_point) do - sameK = to_string(k == k0) - {:error, "what is going on: sameK: #{sameK}"} - else - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point} - end + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point, was_negated} end end end @@ -106,13 +96,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + # returns new_k, was_negated defp get_k_for_even_tweaked_nonce(k, tweaked_point) do if Point.has_even_y(tweaked_point) do IO.puts("k unchanged\n") - k - else + {k, false} + + IO.puts("k changed\n") - PrivateKey.negate(k) + {PrivateKey.negate(k), true} end end @@ -125,7 +117,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} - else + + {:ok, Secp256k1.force_even_y(k0)} end end @@ -165,35 +158,61 @@ defmodule Bitcoinex.Secp256k1.Schnorr do validate_r(r_point, r) end - @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t(), boolean) :: boolean | {:error, String.t()} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point) do - if r >= @p || s >= @n, do: {:error, "invalid signature"} + def verify_untweaked_signature(_, _, %Signature{r: r, s: s}, _, _) when r >= @p || s >= @n, do: {:error, "invalid signature"} + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point, was_negated) do + z_bytes = Utils.int_to_big(z, 32) case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, given_r_point} -> - tweaked_point = Math.add(given_r_point, tweak_point) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - - r_point = calculate_r(pubkey, s, e) + case get_even_tweaked_point(given_r_point, tweak_point, was_negated) do + {:error, err} -> + {:error, err} + + {:ok, tweaked_point} -> + e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) + r_point = calculate_r(pubkey, s, e) + validate_r(r_point, r) + end + end + end - validate_r(r_point, r) + # bool is was_negated. + defp get_even_tweaked_point(r_point, tweak_point, false) do + Math.add(given_r_point, tweak_point) + end + defp get_even_tweaked_point(r_point, tweak_point, true) do + # force tweaked_point to have even y + tweaked_point = get_even_tweaked_point(r_point, tweak_point, false) + case Point.lift_x(tweaked_point.x) do + {:ok, tweaked_point} -> {:ok, tweaked_point} + {:error, err} -> {:error, err} end end @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak) do - {:ok, t} = PrivateKey.new(tweak) + def tweak_signature(%Signature{r: r, s: s}, tweak, was_negated) do + t = get_even_tweak(tweak, was_negated) t_point = PrivateKey.to_point(t) {:ok, r_point} = Point.lift_x(r) tw_s = Math.modulo(tweak+s, @n) %Signature{r: Math.add(r_point, t_point).x, s: tw_s} end + def get_even_tweak(tweak, false) do + {:ok, t} = PrivateKey.new(tweak) + t + end + def get_even_tweak(tweak, true) do + tweak + |> get_even_tweak(false) + |> PrivateKey.negate() + end + @doc """ extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), and a completed signature, verifies the signature, and then returns the revealed tweak secret @@ -204,10 +223,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if verify_signature(pubkey, z, tweaked_sig) do if tweaked_s < s do {:error, "invalid tweak"} - else + + {:ok, tweaked_s - s} end - else + + {:error, "failed to extract tweak due to invalid signature"} end end @@ -241,7 +262,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if verify_signature(pubkey, z, tweaked_sig) do {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} - else + + {:error, "tweak does not produce valid signature"} end end From b7d57390805c3fe43b3f837a1e6f7c4654e7317b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 29 Jan 2023 22:54:55 -0800 Subject: [PATCH 08/40] fix schnorr adaptor signatures --- lib/secp256k1/point.ex | 10 ++ lib/secp256k1/schnorr.ex | 260 +++++++++++++----------------- test/secp256k1/adaptor_test.exs | 151 ----------------- test/secp256k1/schnorr_test.exs | 63 +++++++- test/secp256k1/secp256k1_test.exs | 2 +- 5 files changed, 183 insertions(+), 303 deletions(-) delete mode 100644 test/secp256k1/adaptor_test.exs diff --git a/lib/secp256k1/point.ex b/lib/secp256k1/point.ex index 8793347..50259bd 100644 --- a/lib/secp256k1/point.ex +++ b/lib/secp256k1/point.ex @@ -105,6 +105,16 @@ defmodule Bitcoinex.Secp256k1.Point do end end + @doc """ + negate returns the pubkey with the same x but the other y. + It does this by passing y % 2 == 0 as y_is_odd to Secp256k1.get_y. + """ + @spec negate(t()) :: t() + def negate(%__MODULE__{x: x, y: y}) do + {:ok, y} = Secp256k1.get_y(x, (y &&& 1) == 0) + %__MODULE__{x: x, y: y} + end + @doc """ sec serializes a compressed public key to binary """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 5de2132..7e612cc 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -36,6 +36,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do t = Utils.xor_bytes(d_bytes, tagged_aux_hash) {:ok, k0} = calculate_k(t, d_point, z_bytes) + if k0.d == 0 do {:error, "invalid aux randomness"} else @@ -46,8 +47,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:error, msg} k -> - e = calculate_e(Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes) - sig_s = calculate_s(k,d,e) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) {:ok, %Signature{r: r_point.x, s: sig_s}} end @@ -56,51 +57,10 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: - {:ok, Signature.t(), Point.t(), bool} | {:error, String.t()} - def sign_for_tweak(privkey, z, aux, tweak_point) do - case PrivateKey.validate(privkey) do - {:error, msg} -> - {:error, msg} - - {:ok, privkey} -> - z_bytes = Utils.int_to_big(z, 32) - aux_bytes = Utils.int_to_big(aux, 32) - d_point = PrivateKey.to_point(privkey) - d = Secp256k1.force_even_y(privkey) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - - {:ok, k0} = - tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) - |> PrivateKey.new() - - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - r_point = PrivateKey.to_point(k0) - tweaked_r_point = Math.add(r_point, tweak_point) - # we must ensure that R+T, the final signature's nonce point has even y - {k, was_negated} = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) - r_point = PrivateKey.to_point(k) - tweaked_r_point = Math.add(r_point, tweak_point) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point, was_negated} - end - end - end - defp tagged_hash_aux(aux), do: Utils.tagged_hash("BIP0340/aux", aux) defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) - defp calculate_r(pubkey, s, e) do @generator_point |> Math.multiply(s) @@ -112,17 +72,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - # returns new_k, was_negated - defp get_k_for_even_tweaked_nonce(k, tweaked_point) do - if Point.has_even_y(tweaked_point) do - IO.puts("k unchanged\n") - {k, false} - else - IO.puts("k changed\n") - {PrivateKey.negate(k), true} - end - end - defp calculate_k(t, d_point, z_bytes) do {:ok, k0} = tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) @@ -143,20 +92,39 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - defp validate_r(r_point, rx) do + defp partial_validate_r(r_point, rx) do cond do Point.is_inf(r_point) -> {:error, "R point is infinite"} - !Point.has_even_y(r_point) -> - {:error, "R point is not even"} + r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} - true -> true + # TODO ensure encrypted signatures are allwoed to have odd Y values + # !Point.has_even_y(r_point) -> + # {:error, "R point is not even"} + true -> + true + end + end + + defp validate_r(r_point, rx) do + cond do + Point.is_inf(r_point) -> + # {:error, "R point is infinite"} + false + !Point.has_even_y(r_point) -> + # {:error, "R point is not even"} + false + r_point.x != rx -> + # {:error, "x's do not match #{r_point.x} vs #{rx}"} + false + true -> + true end end @doc """ - verify whether the schnorr signature is valid for the given message hash and public key + verify_signature verifies whether the Schnorr signature is valid for the given message hash and public key """ @spec verify_signature(Point.t(), non_neg_integer, Signature.t()) :: boolean | {:error, String.t()} @@ -174,113 +142,105 @@ defmodule Bitcoinex.Secp256k1.Schnorr do validate_r(r_point, r) end - @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t(), boolean) :: - boolean | {:error, String.t()} - def verify_untweaked_signature(_, _, %Signature{r: r, s: s}, _, _) when r >= @p || s >= @n, do: {:error, "invalid signature"} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point, was_negated) do + # negate a secret + defp conditional_negate(d, true), do: %PrivateKey{d: d} |> PrivateKey.negate() + defp conditional_negate(d, false), do: %PrivateKey{d: d} + + # negate a point (switches parity of P.y) + defp conditional_negate_point(point, true), do: Point.negate(point) + defp conditional_negate_point(point, false), do: point + + # Adaptor/Encrypted Signatures + + @doc """ + encrypted_sign signs a message hash z with Private Key sk but encrypts the signature using the tweak_point + as the encryption key. The signer need not know the decryption key / tweak itself, which can later be used + to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. + """ + @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} + def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) - case Point.lift_x(r) do - {:error, err} -> - {:error, err} - - {:ok, given_r_point} -> - case get_even_tweaked_point(given_r_point, tweak_point, was_negated) do - {:error, err} -> - {:error, err} - - {:ok, tweaked_point} -> - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - r_point = calculate_r(pubkey, s, e) - validate_r(r_point, r) - end - end - end + aux_bytes = Utils.int_to_big(aux, 32) + d_point = PrivateKey.to_point(sk) - # bool is was_negated. - defp get_even_tweaked_point(r_point, tweak_point, false) do - Math.add(given_r_point, tweak_point) - end - defp get_even_tweaked_point(r_point, tweak_point, true) do - # force tweaked_point to have even y - tweaked_point = get_even_tweaked_point(r_point, tweak_point, false) - case Point.lift_x(tweaked_point.x) do - {:ok, tweaked_point} -> {:ok, tweaked_point} - {:error, err} -> {:error, err} - end - end + d = Secp256k1.force_even_y(sk) + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + {:ok, k0} = calculate_k(t, d_point, z_bytes) - @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() - def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak, was_negated) do - t = get_even_tweak(tweak, was_negated) - t_point = PrivateKey.to_point(t) - {:ok, r_point} = Point.lift_x(r) - tw_s = Math.modulo(tweak+s, @n) - %Signature{r: Math.add(r_point, t_point).x, s: tw_s} - end + r_point = PrivateKey.to_point(k0) + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) - def get_even_tweak(tweak, false) do - {:ok, t} = PrivateKey.new(tweak) - t - end - def get_even_tweak(tweak, true) do - tweak - |> get_even_tweak(false) - |> PrivateKey.negate() + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} end @doc """ - extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), - and a completed signature, verifies the signature, and then returns the revealed tweak secret + verify_encrypted_signature verifies that an encrypted signature commits to a tweak_point / encryption key. + This is different from a regular Schnorr signature verification, as encrypted signatures are not valid Schnorr Signatures. """ - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: - {:ok, non_neg_integer} | {:error, String.t()} - def extract_tweak(pubkey, z, %Signature{s: s}, tweaked_sig = %Signature{s: tweaked_s}) do - if verify_signature(pubkey, z, tweaked_sig) do - if tweaked_s < s do - {:error, "invalid tweak"} + @spec verify_encrypted_signature(Signature.t(), Point.t(), non_neg_integer(), Point.t(), boolean) :: boolean + def verify_encrypted_signature( + %Signature{r: tweaked_r, s: s}, + pk = %Point{}, + z, + tweak_point = %Point{}, + was_negated + ) do + z_bytes = Utils.int_to_big(z, 32) - else - {:ok, tweaked_s - s} - end + {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) + # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R + tweak_point = conditional_negate_point(tweak_point, !was_negated) + r_point = Math.add(tweaked_r_point, tweak_point) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(pk), z_bytes) + r_point2 = calculate_r(pk, s, e) + partial_validate_r(r_point, r_point2.x) + end + defp make_point_even(point) do + if Point.has_even_y(point) do + {point, false} else - {:error, "failed to extract tweak due to invalid signature"} + {Point.negate(point), true} end end @doc """ - extract_tweaked_signature takes a signer pubkey, message hash z, untweaked signature (adaptor signature), - and a tweak secret, and uses it to verify the adaptor signature, and returns the complete signature + decrypt_signature uses the tweak/decryption key to transform an + adaptor/encrypted signature into a final, valid Schnorr signature. """ - @spec extract_tweaked_signature( - Point.t(), - non_neg_integer, - Signature.t(), - non_neg_integer | PrivateKey.t() - ) :: - {:ok, Signature.t()} | {:error, String.t()} - def extract_tweaked_signature(pubkey, z, sig, t = %PrivateKey{}), - do: extract_tweaked_signature(pubkey, z, sig, t.d) - - def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do - case Point.lift_x(r) do - {:error, err} -> - {:error, err} - - {:ok, r_point} -> - tweaked_s = tweak + s - - tweak_point = PrivateKey.to_point(tweak) - tweaked_r_point = Math.add(r_point, tweak_point) - - tweaked_sig = %Signature{r: tweaked_r_point.x, s: tweaked_s} + @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() + def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do + tweak = conditional_negate(tweak, was_negated) + final_s = Math.modulo(tweak.d + s, @n) + %Signature{r: r, s: final_s} + end - if verify_signature(pubkey, z, tweaked_sig) do - {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} - else - {:error, "tweak does not produce valid signature"} - end - end + @doc """ + recover_decryption_key recovers the tweak or decryption key by + subtracting final_sig.s - encrypted_sig.s (mod n). The tweak is + negated if the original R+T point was negated during signing. + """ + @spec recover_decryption_key(Signature.t(), Signature.t(), boolean) :: + PrivateKey.t() | {:error, String.t()} + def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _, _) when enc_r != r, + do: {:error, "invalid signature pair"} + + def recover_decryption_key( + _encrypted_sig = %Signature{s: enc_s}, + _sig = %Signature{s: s}, + was_negated + ) do + t = Math.modulo(s - enc_s, @n) + conditional_negate(t, was_negated) end end diff --git a/test/secp256k1/adaptor_test.exs b/test/secp256k1/adaptor_test.exs deleted file mode 100644 index 127cc6b..0000000 --- a/test/secp256k1/adaptor_test.exs +++ /dev/null @@ -1,151 +0,0 @@ -defmodule Bitcoinex.Secp256k1.SchnorrTest do - use ExUnit.Case - - alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} - - - @schnorr_adaptor_signatures [ - %{ - privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, - pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, - tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, - tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, - message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, - aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, - untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", - tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" - }, - %{ - privkey: 0x6F0B8C685BEB787F3121851AC13DAD9549C235217C5F578D9E7BE3B3845D8773, - pubkey: 0x825fa6ec886022247b7b98d24d47a6f03d3a47f2fa3916a244eba7114ea94fa1, - tweak_secret: 0x76D2C1FE7E575063C8888D935645973AC3AE1FFD1C97F25D14F7567657D7F748, - tweak_point: 0xb99378d7cf782cf6ff265ef8e3ba447b36da89224c0682ca61cc9916a4fec4bf, - message_hash: 0xA193926FAFF8CE363937D8655892E60D45661C5FC510BC711E4B5049A6B4DD30, - aux_rand: 0x76C66B394932DC5704EC0FB4CE4D76D388707396AC64D9C35BFC081F11D51124, - untweaked_sig: "864897cd95c22dda7fce0435fe365c658118e52d578ba77c25d1f1e63ff3cd0916b38c325759c310e467a0ec026d8362b2f5155adb19a17eb06a5c661b039be1", - tweaked_sig: "e277ad505af9e82abcb122c3df410459494af86315988d4596bf0fb71f64bf338d864e30d5b11374acf02e7f58b31a9d76a33557f7b193dbc561b2dc72db9329" - }, - %{ - privkey: 0x624FB68C6C2474358EF57A9810BA5A17855D0FF8676D5D80E93DF314D5C3D277, - pubkey: 0x4af83421073f24e51235c43ffb85aa94dc3139af958a2532c6b412832fbbacc4, - tweak_secret: 0xF1997F60060268C14B81744869AEB04098A93704C3BDA2A640A1B26F74E246F4, - tweak_point: 0xb9dc16baaf262b3a60a1979833f9ca350e2219121fdb43d3fd2045d3142bb71f, - message_hash: 0x97FF15503ECA5167E871E04998F3C90976E8FAFF9AD1AF54F045153BA41E1663, - aux_rand: 0x71C1FE1F3A27B04EEC4142AC1612A7C6419B1740716E2C29CEE319E718950A04, - untweaked_sig: "08365373469000bca3fa28a95469f6331a20d4f03429372573b3c2d3d3d5b0f6801f053ece6e34cdf9fa8f43ec2ed3b730c388c0e444dd45f9cc1f920adac25f", - tweaked_sig: "9e3deb23f27730ce1be854b0145cc9bf99e3ee10491d28308160c7b65b0a56e871b8849ed4709d8f457c038c55dd83f90ebde2def8b9dfb07a9b7374af86c812" - }, - %{ - privkey: 0xB5196100AAC42311C9B33B1746CA0B38AC4547936DDC4AFFF547A862A4BF9890, - pubkey: 0x5b279f79dcf2414b66160515de53ac471a043d6d9f62fd007c09ad90704ffd28, - tweak_secret: 0xA69690763B4034FCF0F269E80464643E521475BFE08162632971AE0BDAA7AE8E, - tweak_point: 0xe9f219bb7c0a390a7df96a4f33b7f7d6179a9a4aa216ed1a750ddba1719072f0, - message_hash: 0xAD6F06747576C8EC210113A2C070A7A5688616EE841FA61026D2FF1CF9F7970F, - aux_rand: 0x63241AA5C78B99A849025EB3056E84DB011769DC8BBE782FAFA6E89D2BEE0C16, - untweaked_sig: "ef3766f37e9235e9652418bd60c54cd8e9c2db06f7effc542415fc76fac9c5cb75cc36cc9d5c20d303c30e43c2be82708141b8f721f75c7f889ebb24da81c818", - tweaked_sig: "aa9788a104f497f788bf4979a791a04c8408fd2f82075d5810f5f85394d567761c62c742d89c55cff4b5782bc722e6b018a751d053301ea6f23e0aa3e4f33565" - }, - %{ - privkey: 0x45AC71188B8B3B180D806ABB0289AB23357BF7CE866BFDB13509C68BAC5C88C4, - pubkey: 0x8b688d7ce32ab13bed3f0c5f119791f8b76913809c22983c8b6713d6fdc6f5a3, - tweak_secret: 0x6D2B06A08C0F20A64FA06DADF35F992F4DA441A29541268FB5F9AA9B7977A886, - tweak_point: 0x4d01a75b79ad34ce9d1dec59c581881db6a05865807705558a31e5b19047b024, - message_hash: 0x1E3D05CC94304EB6CEF384D5ADF36827A6CA4299D70B821B60240995F75BB6CB, - aux_rand: 0x7EFC5AD98D6AA0542A9534FA8637AB87A1B8E48FBAFD89AC6EA9348B44EEA049, - untweaked_sig: "655244da978cb1fa37690f3fe98c0e78b58390207781dae0d57f74dde06c0c3f6c8463e2c5f1e4dcabdfa050d9f18e3dfdd4bd70b55a024f3beab45fecb6e255", - tweaked_sig: "29abdd275d062b0bef51113fabaf3a70652387e7fa227c54b968b4943a7f4312d9af6a8352010582fb800dfecd51276d4b78ff134a9b28def1e45efb662e8adb" - }, - %{ - privkey: 0x24D807AB16645D504C75AA9B6355444FC291298AABEB4DAC5EB070898AD645B0, - pubkey: 0xf7609d85839d360fdba6a7bcde848807baeb6417d5ed4258c1c8bc4650543c7a, - tweak_secret: 0x4ABE441BF15DBB0378C2763FCA1E967EFA2636C6292A80711DA55AC7810CAA2A, - tweak_point: 0x6690f7703df1a815d8b4391889c47d8d8837369c6f2e00d5eb92abf1b90a206f, - message_hash: 0x9AE6F80954DA7A3D615987FC0CE97CC4E4567F5425A733DBD4C0BD496A3D5622, - aux_rand: 0xA421B1D6C0DA447395A19B0CF5BBB9879DAE8303CFD33C46F001DBE7EFE9853, - untweaked_sig: "96b4d6d0a19e7f42eea9ee4ad8d36a19e039623eeea47a6fb1658df2e20f7e23cbc02c5c59d7961cf88402a346584af229cb795295c5ea108440bd7de5ea01f0", - tweaked_sig: "a57c39c39b18fd5e6f572253e19227252a4a81a4ac3dc1ef804e6b306b142b10167e70784b355120714678e31076e1726942d3320fa7ca45e213b9b896c06ad9" - }, - %{ - privkey: 0x7EDA1030020279FA24CC33B79A665B73A9E715EB1EB2C6DBEBEE21AD7BCF1E2C, - pubkey: 0x65c57e92bd052d623ac708c34a9a73851e37c81cb763b9fe1ff88437ce42245f, - tweak_secret: 0x1DE275F6F723416882A18E0F4CCD398B7AA9C2A701BFD16D448A118BF82D3027, - tweak_point: 0xcbc6ed86f4442c6403d5c0a3b8157489c5cc55d5137bbd4648c58301979b6f6c, - message_hash: 0xA744D50F65508DDE9278AECAD6A2759BE3A83709877B427E4098085DB27B9826, - aux_rand: 0x12F582D5FA0B2F6245985FBC7C7220C55D441D027B64BA2A7289B9A15E5106F7, - untweaked_sig: "454d9f3fe892e1f31638a258b135ddb74beaed682936830b1d233432e7a2a575d9953241a92d0172bf8df6e198dc7a9052638337483e0881ccb96ba38ea973d3", - tweaked_sig: "db076ae6615ccd083a0d8e6d9e78c28cba696f4c4d3fbfb5c5a55b23d4de0d0af777a838a05042db422f84f0e5a9b41bcd0d45de49fdd9ef11437d2f86d6a3fa" - }, - %{ - privkey: 0x95896701D713F8AA383176C2BCD61F8EC55A29C5169AEBE9A8A32AC9803F8E7D, - pubkey: 0xe7d2b5e311e4f7e81d05c5c56a94f0e5438da78b2e512f78b540114dcf931080, - tweak_secret: 0x7038F53CA5C13C1534A595BC74799657805CB864A21AC78618E661E2A27F6DE4, - tweak_point: 0xe76f186a03ef4ca41b55ff18e2de2f713cf9a1f97f23f627681a2a47bea24a95, - message_hash: 0x4928F4C94434C570F12CF548F4B2F4C9124F5E6D1371020D55EC3D508DA49D61, - aux_rand: 0x51DDDD197BF29EE74235C80CECBE06C8E012BFFC0E227E87A966AD9BB6E6A07B, - untweaked_sig: "46d4e627dc2a8d3864f4e6738f88ab891e5e51da7b08251218d11b0f56f047b4d91271d08e0ba2ecde0169ce02307e49bca6cc749e3c6869dad5ace39f09c943", - tweaked_sig: "83ba54b75d4516cb376b51fc8fb9cd560aae652a2ff8cded96cb052ed3b1e12e494b670d33ccdf0212a6ff8a76aa14a28254a7f2910e8fb433e9b0397152f5e6" - }, - %{ - privkey: 0x695563958BE07A20700F6B624C2EBA9F336C47ADF7B8E202388600132D2E7CF3, - pubkey: 0x97826ac90a2e8ebf9c2f94c5f23863aed915b4c4e77701b9ef7b43e9c501d0f3, - tweak_secret: 0x6FDDBC57B407184C1D1C0B9040A14A19EF8824222431F39CB89FED2C9D67CE41, - tweak_point: 0xe772ca746c6f40e607167f6d95414dab603fb36e0a4e473ab69d6e1036c209ea, - message_hash: 0x9A6BBFEF82261C8EFA9CD7F3481728D68554B2E698187797EAF60A734B6307D9, - aux_rand: 0x1ED0360C5241E28038735B43C6705AE2A1A971CDD94FCED6817C005ED2DF8237, - untweaked_sig: "d44e4a61254b90b671a278ee8ae73add71c5cb89b9fda0611cabb3ab46c4bb5d2511324a297a4027f3bef12a79e07a16d1419922e0d6c205066b538efc655dfe", - tweaked_sig: "840a95a2d67b1fe09584a2b9f353277c318d4b21774a777a2dd2814de4e9d22094eeeea1dd81587410dafcbaba81c430c0c9bd450508b5a1bf0b40bb99cd2c3f" - } - ] - - describe "adaptor signature tests" do - test "test adaptor signatures" do - for t <- @schnorr_adaptor_signatures do - {:ok, sk} = PrivateKey.new(t.privkey) - sk = Secp256k1.force_even_y(sk) - pk = PrivateKey.to_point(sk) - # check pubkey is correct - assert t.pubkey == pk.x - - {:ok, tw} = PrivateKey.new(t.tweak_secret) - tw = Secp256k1.force_even_y(tw) - tw_point = PrivateKey.to_point(tw) - # check tweak point is correct - assert t.tweak_point == tw_point.x - - # parse sigs - {:ok, c_ut_sig} = Signature.parse_signature(t.untweaked_sig) - {:ok, c_tw_sig} = Signature.parse_signature(t.tweaked_sig) - - {:ok, ut_sig, t_point} = Schnorr.sign_for_tweak(sk, t.message_hash, t.aux_rand, tw_point) - # check tweak point hasn't changed - assert t_point == tw_point - # check untweaked sig - assert c_ut_sig == ut_sig - # adaptor sig is not a valid schnorr sig, must fail - assert !Schnorr.verify_signature(pk, t.message_hash, ut_sig) - # verify adaptor signature - assert Schnorr.verify_untweaked_signature(pk, t.message_hash, ut_sig, tw_point) - # complete adaptor sig - tw_sig = Schnorr.tweak_signature(ut_sig, tw) - # check sig - assert c_tw_sig == tw_sig - # ensure tweaked sig is same when using tweak private key and integer - tw_sig0 = Schnorr.tweak_signature(ut_sig, tw.d) - assert tw_sig == tw_sig0 - - # complete sig must be valid schnorr sig - assert Schnorr.verify_signature(pk, t.message_hash, tw_sig) - - # extract tweak secret - {:ok, tweak} = Schnorr.extract_tweak(pk, t.message_hash, ut_sig, tw_sig) - assert tweak == tw.d - assert t.tweak_secret == tweak - - # extract signature given tweak - {:ok, sig} = Schnorr.extract_tweaked_signature(pk, t.message_hash, ut_sig, t.tweak_secret) - assert sig == tw_sig - end - end - end -end diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 522d940..454178e 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -4,9 +4,11 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do alias Bitcoinex.Utils alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{Point, PrivateKey, Schnorr, Signature} + alias Bitcoinex.Secp256k1.{Params, Point, PrivateKey, Schnorr, Signature} # alias Bitcoinex.Secp256k1.{PrivateKey} + @n Params.curve().n + # BIP340 official test vectors: # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv @schnorr_signatures_with_secrets [ @@ -154,8 +156,24 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do } ] + def get_rand_values_for_encrypted_sig() do + sk_int = :rand.uniform(@n - 1) + {:ok, sk} = PrivateKey.new(sk_int) + sk = Secp256k1.force_even_y(sk) + pk = PrivateKey.to_point(sk) + + # tweak + tweak_int = :rand.uniform(@n - 1) + {:ok, tweak} = PrivateKey.new(tweak_int) + tweak_point = PrivateKey.to_point(tweak) + msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() + z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + aux = :rand.uniform(@n - 1) + + {sk, pk, tweak, tweak_point, z, aux} + end describe "sign/3" do test "sign" do @@ -276,5 +294,48 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end end + describe "encrypted signature testing" do + test "encrypted_sign/4 and verify_encrypted_signature/5" do + for _ <- 1..100 do + {sk, pk, _tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + end + end + + test "encrypt & decrypt signature" do + for _ <- 1..100 do + {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + # decrypt to real Schnorr Signature using tweak + sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + # ensure valid Schnorr signature + assert Schnorr.verify_signature(pk, z, sig) + end + end + + test "encrypt & recover descryption key" do + for _ <- 1..100 do + {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + + # decrypt to real Schnorr Signature using tweak + sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + # ensure valid Schnorr signature + assert Schnorr.verify_signature(pk, z, sig) + + recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) + assert recovered_tweak == tweak + end + end + end end diff --git a/test/secp256k1/secp256k1_test.exs b/test/secp256k1/secp256k1_test.exs index d03547d..4a6c4f5 100644 --- a/test/secp256k1/secp256k1_test.exs +++ b/test/secp256k1/secp256k1_test.exs @@ -176,7 +176,7 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do privkey = Secp256k1.force_even_y(%PrivateKey{d: secret}) pubkey = PrivateKey.to_point(privkey) - assert rem(pubkey.y,2) == 0 + assert rem(pubkey.y, 2) == 0 end end end From ce1f86926ca76d9d004da15405a9c1bed378398d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:07:03 -0800 Subject: [PATCH 09/40] finish adaptor signatures! --- lib/secp256k1/schnorr.ex | 13 ++++++------ .../schnorr_adaptor_test_vectors.csv | 21 +++++++++++++++++++ test/secp256k1/schnorr_test.exs | 12 +++++------ 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 test/secp256k1/schnorr_adaptor_test_vectors.csv diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 7e612cc..0534fee 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -92,6 +92,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + # this is just like validate_r but without the R.y evenness check defp partial_validate_r(r_point, rx) do cond do Point.is_inf(r_point) -> @@ -99,9 +100,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} - # TODO ensure encrypted signatures are allwoed to have odd Y values - # !Point.has_even_y(r_point) -> - # {:error, "R point is not even"} true -> true end @@ -158,7 +156,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) @@ -171,6 +169,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, k0} = calculate_k(t, d_point, z_bytes) r_point = PrivateKey.to_point(k0) + {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) @@ -191,11 +190,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do %Signature{r: tweaked_r, s: s}, pk = %Point{}, z, - tweak_point = %Point{}, + %Point{x: tweak_point_x}, was_negated ) do z_bytes = Utils.int_to_big(z, 32) + {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R tweak_point = conditional_negate_point(tweak_point, !was_negated) @@ -220,7 +220,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do - tweak = conditional_negate(tweak, was_negated) + tweak = Secp256k1.force_even_y(tweak) + tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) %Signature{r: r, s: final_s} end diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv new file mode 100644 index 0000000..32d1f7b --- /dev/null +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -0,0 +1,21 @@ +private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated +"64d399f840a0153ae7573fcd736b2b6dc8d31417514ab67622eff2354ee22241","03077b0ff8da086bbd941887ea62d90f0ecdac8acb093254fb4d69dc97d65f56","44f76c9909398b59a6fc09f9cd446476ca91e00cffe8499c1c326e2f8b9289ca","c8d33563fe4ae990f75cc0baa7526df881d95759f1dd31fa10ea3d614b8d6bef","69d1666105256708b242da64ac91b8d455119375ddaf588487f5dfd31c843f9e","f1a3588373fe6a90cf4b8d91295f41693b606f46d41f0d221bd6ebff75817f23","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f3588e3fec4d6f7f890d943e8d5b3acc39799c94d925a62e3fda2291cb9bf2b537e6a","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f358828f6317001321c32eae4dfad7a07fc11d9309580b383a702857cc961e6afc6f3",false +"332b18ac4de2ee1b125bfa35e1b0ae692c900219ae36cdc94de0de9b7b82ec35","57270515af6b0cfc28cc621c18522ce7c490bff838d6fb3e057a9d518d975464","ddbfb38f862619928f205a925ad5d7f17fffefd2526d8b37a83b863d090f8ded","9bd47e739d691b7c81c83b08678b38afc4199060ceae0e1e61fa013dc2eff161","44d013b587c4b9982cefacf9e4014e96f52485cc7648815f519e5c7acf817b95","5a238ecfc487b6a1f52e9fda6047a3a6db0c5a4a6c581671908f81c35f744c34","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331d894ef7dd5635592f2de62c9e456ad757658dfd5f6b3a3d08e23755c04981f905","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331dab8f444dd00f3f9c9ec5d20bea94ff64a03cea73c815520cf9ce2e1010a8ac59",true +"a6f9e49b8e4b714d760dbd8f01abfe04e2108f5accd7ce81091d8f8a04d4e3ef","60e3b83eec196e0ea1195ae64580d3345654e162266800f1c5507bd7acc166ef","a78f44c328c5506fe347688640de9b085560d399be6ecb23dbf06ef3239d9117","e8314bb4e986ab44904e66adc3b272a626b5804cc89bf8a9338296bd93eee97c","7a14183e12079562ea17b59a9156b1a74bb7fdd6bb16413c4a6ac4599b2c3818","4d1860c32ea63d2d59856108d3eb55e14d962cb106daf333bf721cbeaf1de6b1","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559a55545812984531783a3b9cd6fca829fb14c8dc7731242c1fbe916d2c3c9f965c","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559aadc5134f6f7fe10856f43450bbc98ef17a16e5c421fe0137a2735cc5e9384686",true +"3ecdc22e7f8f527ba66ef75754ff386db60739ad691e8713b00c7d0e18650365","e3047a46759d8e47b22dd384cf8fe8631fefa466ea41b75030cea308c3360d45","8263cfdf5b3034958b7e503907ea95722deff04f0fdbbf76adbae6d79774f2de","95e028af7e8890ba89705fdd05e26f6c68050eea5f17c65095cccb903da76f8d","6c1ea354705a73aad3006aedab07ee311d343b1421b7f4daa4380c389a793180","eea79caadc04e2df90c29494e002bb1cf096c11996eecd8ff9c4733ff133c12c","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccb47a6a2a1a3a2dafe60ea9e54b1ba83e99b0125d89cea8d516b9b5a6fa85bfb19","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccbca0a7280fed30f93ec68ee8db9a5195bc8f11627acc64cc8195641473fd0edf7",false +"7789778cfe9d60aa50c339a11b36ffbbc5d350c345ab14e56f207f430deafca5","031551b96dfef020553900283837e36f1f669dc1b7857129a66a23dc3cef7b04","19db69599941098c1f375b8608094dbad22d72e1fc62cd65285321a029b63de6","4ee81875e67869acd8d49b53dcdfeea2c64360b4470d548cd0a66916e354e565","db7aa4a8785c8cab4d42f6c1f282982818f5b528b3d6beb5b99486de031bf4fd","03ea829059d5d7d36ff9554b3718a0177a04de8e5ff33730c72e480477840042","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410d8824b1197afe7727177b54d6378c6e3dbc60fabdfc24b60f572ce0637b02089b","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410da2001a73143f80b336b2b05c3f95bbf88e8e6d9ff88783747f800203a4b84681",false +"2c42d0167ee5cfa88a0cc1c16fa0c9dc38c01d47f30e4c5423c707aecdd365aa","b97f1f0ce685329fce080aff4c9a17f3dfc6a14bfe9689290e9b89ec7f24bb5d","f06324b650ec674ccefa36c4e19c4e700fa09bedd1f7129c44745479dffab65d","0f638142959e8e6dc979685bb4292b646389db2ec418f87629bac97dc6c21478","f72df7b15f31c5abb0db5af07efc53199afe533af041512b47276d7ab90336be","6b117aabe372c146924929aa00990d7392eca59fc31b416a833a7e2a56ab73a8","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dedade54f20ed0f0345cc22a4374971ce64f4bb7cfd0e03574e0bab7f4dd1a051","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dfd4ac098d000a7b676d1ebdf55ad235d1002fc75da5f90f6c969b5923e0d2b35",true +"0570c938ab1e31af3244c1c8de80bfac01da36cb00708506aa9d6294b1020275","a162076078883fe9ce67f32a28eb637796fe5a20c3ee5a4dcf5795d54142152a","cb97db136151e7fd66b36622e2f6ac9f1244e951b5bf15f9a3f38aee73be4ce3","6ba7a5b30ac5bd0e5b7641830cb74367b41136823f8be7100e3c14d80b3eebcd","78ca0adc4d4d892c4bb8060c10b69d3b05630a5b5189877b9f20691897988174","4b8d63dc59a313c09548593d2002a5af4ca601b37a98bb299d8605062ea8c88a","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d4538757ed880d9f67d61947202c7d6ba6e82ad5bf99f51b6c318009f26c59acf5","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d41f1f3300e95f87653cccad430f7418473fc0e22aa06b912a15a136540fe1b897",false +"cfeb57aa91b08f7df563c54cd5c37fe9354c34c7a3a0942a73a1bd28d01ca785","6379d1dcf2da0e5c90e05d02b32a5c969dd21ea986f2df5a329ce2154c2f54fb","6c8f2a3b7bae9cd68637fbaac2fc1b62d5ecacd24ad97163659e7016ed79ad35","bcd420b56d103b29f50376335b959b76ff1102f319cc0e8c791615cac203c0fe","91d16e79b8869ae5d5cabfb4818405613bb9a89b552370309e44dc923c5071ae","fea792688415e4ea1927a8287dbf938aff1795c590cd89fc83a6190820f4a619","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0427bf317bebab7ded50ebf1b253845b43da1ad71cb5d0bd93f8fc895a7f2ebc3d5","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0420f63ed833f0941fe88871f7a754998dacbc0c4f9123267dc2a2a2591057216a0",true +"94c063c368f720e36575be96aa0075d73e669b9a0cb4188306d8bb4cb1cfb1e1","637cf87ec03dd68084474aa9f22bcc94a0985048d0a38b24e12b821f492ff4b5","c9fb6ba3efe3064bfd7625a53f254e90c59f35969ee3a65e110d6437fd24b435","c33096b024f6871aa9958e620501df9adc43760b6213e0a0783d143e1f36e8d7","68d398b891283b42e4bda770d3cb644be617e82a93cbd281ceae542d66e3bf55","54c1eec0ece9844fd5de9512e72b4e3452cf5472dc4934f0a7ef59323468d42a","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9afd56205207bda7af8933d3ac71f5ef475aa03bbe8fe78733ba42f1271106a174d","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9af9f5d70c46bbd814490b3606c5e844307b4f41498ee13795df56a181c3d588a41",false +"9e0278e0e6f8a0767dd81ecea025adf55dfaf913f9b4ff347115956fd0fb327d","535b5a59e5879920b490835ac9dbf23a82435bd083b2cc66be5be9997e2ce681","c47f351884757e03385b7c5a60d43895fa4b88dc9388d20c773c0aa6704b7ee0","93373b7570b569d3945cdc306b18b29b5f71a502f2636accb4a0b34be6d73203","8d96a34cf601b53ea3f0f1b0a5ca315f89f8d968f148c071560fca35ce97d1d4","e57adc1b2b869f8e07d97d2725fcffe19338c737a78c6d9112b34cbc5fa7c1b0","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b74b08551ea14588ec3a29e1d2d88d629721bbfbc7af3f5785dd7e41ca03f7d857","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b70f878a3725bb06ef72855e2d39619b2e6158a7bd937f895694e7ede3a40d15f6",false +"e29f4867a64d1199a19777f91c9ec2f9d15dd08a93409144ea0064cb60c76a3e","85524318955cb8c63933e6e0c06b8fe561861628548539bf760c2f1840cf8ae7","3b415bb6c72bce59fc88681de981ae96875d5cccb98755e195b3e4399340171c","f86e55a60819bc5b9ba93746753f4fc0db87c10de5b7d25f1fe242b3c1bb6ebd","1ec2865fb83a22cddf06a22491d861b685e987d115e89c4d49b93672944553df","fd6e924750d9359ff32feacc6e61991d6da690d312514b5b896bcb3365f05bdb","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf69bee8aad31fb077fc38eeef227519f5a1ceb06692d362c11ab34351c8a15381","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf2e7d8cf40bf3e21dffb086d138f36b5f1a715399d94c0cdf84ff5f1835613c65",true +"5745cbbd4a3f91aace64d6d5dfd81787b40c11f9c6a934bdf3fc80b2c8e28fb8","c99af68ee4b762410875e5bda87af74683541ed597966c64c96f8c486ff1f19a","627977f34498f34ed896fb5019fb9ffa2dc7dd238bf3777a3701186df35299c7","0a801b75ed3ae344de1b255f53930fc82fade6a0ba0e0b07d8b109a2b9cc3701","47406b77cca9234a06bdb4fc65ac76649e392ea780bdb81a6fc6cf3221f1354c","652839026578293d7ce1969b4d457739e722a2f5952857889da87a7d1b6f0f89","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef74495faca5ac0354f0a837a66214ea462f7f4e7212218776ca7ea37eb8b77ce53cd7e","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef744959850e2ccf0b61734a1cf25fe8a6757fab95944f4eb79307000ea7309db0133b7",true +"0c097d304c27a23e91e639407341eb33bfc90f74184130251bb0553dbe2de854","2b0868c00a5799f5686afe3550ca15d94ee2a6a81b1ecbda18e0735ca7ff7a56","6dc0f73722daa9eef9d0dcf931841a07f53c844b30a08c3f89898e619bc855f3","5c44f4f8428117f9ee1ad3711ec1120ba5a8545cc7d5aace491a215b49d617ec","52f98f0d574cae4db1cd30cb67a3e65c44dedb16f07c7bd728bf0fb83d0ad408","460d47e19edb129b81d62e6b59f76dd7bd4c61a725da3338d706c0e755ac2ae9","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e9c67dee427981c760ab8c6b90ce3d9f22d07007d16845eb9f80fe1dc948efbf7","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e0a28d61b4a72c6650489a3b23e67f3fb6794a7e197dc4abdc1c711b1602110a9",false +"b1b904d991387680de13c606565643e4930d2bf79b721db4530401c3b92cf3cf","63cdee00d9f07194399fe6518512073190b71dc2a94084681e49a172158f90ff","b6928d5994fa41ebd66a22a826cd6ad4015a23feab9ae45f1eaac309007fca73","38388ce16e2c5e1eab89b4e56233dacb58d4aa286ae2806d5debd117c9c529f9","6eab75f6bf9de09d363e4fc2447d0fc03feb78a5fb06ccb39daf648e13fd3045","41f2e0be320908f40428a195e471e425864ab7acbafdb11e642a68be1598c2c3","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cb97acb68f70542aa1f6641f228d0df53b3a3930e82cac62d5cd466b89ca9049e4","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cbe11a2935db59e8b61ff9fc7a66408a65f38de9d0305a1eb26e6e070d9a46c0b2",true +"7a444d39de9cf6852cf431a622af15acb852dce5034735166bdc4cccf6d984d3","52853e8d893caeb43c6ab322625641ccece988667bf9d64670a4df4750b83d7c","bf611892850c5e814215dfecd9629ed0de56376bab46fe361db5c0ba4fe916d8","103dd130009f424fdc25eeb5a1392a36fe4e008290267722ac24eb492db00ead","49a5fbb6bdb0cd167e5fb7b4b8544fcd87a3433e66292173c101efcd97756349","7666bd5dce4e4ad0df76672bcf6f8123adf4f5314894b808fbf739e3e3e687bd","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9abd8f6cb88cc6090f789d6726cda594f72d0335653a5b18c27655fb38dee47d56","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9afe2e542607b9aa8e36878739f442f625095bdae03e5cbac81872990b5f31a7bf",true +"c19d1686e76d5b777af0924e6154615abb7db030a22e407a397ce7e301d87742","0ba3c133ab7dee2839552fc9e43601ab3cc501c21891f2fd34fce78e43b63e67","2e5b908a7466490f23b30f5b75bbf10ba0d9c901a8fa1188f5ba6b1d07277174","5fba25cf7f5fb85e24900d14f789604dd0eca2eafa53ef2a41e55a79740709b0","ca543d2652c3ff13ac98641cb65df9dd0f719706363dcfa74592b373a638e308","da2cde0c16fd3cebf8c84ec28dbf1a951f43ff930795b610df8cec40749a25e2","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e39584c6b03ef20b2edf449b5f6fb7a00a42eadc9aba1f3652cd143d37651700a","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e0afcbbe08f88d7a3ca413a5a813f88f50354e4c802a7e1dc3716d8b66f29fe96",true +"29e362a7bfef5fb7f4918ec19e25befa8a4a43a23c8ec7c6028f97226773a7bd","2102f0eed266ed80ec7568cb20eeead902f0c35309f5ba7c1567b819a8e1bbb7","6c82609f56f6c11866cc83ee2498d551c17ae68d72904b4af73ff8e5f45c3284","f9f9be4fc4d63060a3c3c0656679f3d757775f8f8641a71f1525e53b57a1d99b","c78fc24af3b81ec075e1e1764b4d67ea07b5a9dda2e1504e159eb427025fb80b","15968a700ee7598db61ab47d210a27f7d0d82c5d25739b6141b095de9e00942d","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39afe63ee86c0240d1f2bda706dcb386ab6b1a3d73cb44250f9fd409b764a4e2bc9","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39a6ae64f26171ace3792a6f45befd14009b86fe0e37789fc0934ae35cf6e741d0c",false +"b01defc1ec85fdd6381f62ecb596239e91245b940b18489071526064a538baef","baab11fd4992c19e6a7692944ff665c08e1fb06c7a26b0f2ee2253fc9f7227e8","a0dfd48fa85f67b08b3fdbb9a6bb71c74b4239148f65f35caa37b1d1800fce44","025342259c0aa8df863a2f151ff0c2b1d6064997d1c54c42e5b62d033e83c7af","f5032f3bb2f6767a6f95b3559b74e14edde45ab47f8ff5c48542d748374c5144","08a35c8959bf4a10b85c75b7ef260d39f865876f4bdb66511b53dccf3f1a9353","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d9933033dc488afdcf7a9ac18515f96a4ac4e73db690bde3f49e2ce53141f437f0a8","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d993d113b0d8335d372b260160cfa025bc8c327fefa54d49e7fad71ce3137447beec",false +"dea7c5fe87a447632a53dcf40716ad99bd04d983405d851f44ff2671f8f8a6ea","07f04f60aa3367725401b8a813c0575007438714f3b6afa9150acdae4867f2f8","840da049a59cc5b8830e54aee306788b2b6104655f0a23a9404671f821fe9573","4ba30d52534bbed6bb67fa5e1bdfb1bdb983843a56887e43e652e89ccaae519e","d568dee1f5d4d1b400641fee5afd68e176e234acda24b0eb2516f739a8e87dcf","cab1f1f1229e93ad2efb76649c8091717ace3ba01461ac559295c5e0e006d07d","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef0153370f1f321db528ee4f907e8b66903f080575efd5d11bb2518d9ec021e6b4c0e1156","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef01533706de58191acf21f4084da61ba20ea07cc339c58ac5c1af530abbbac732a0f7be3",true +"17936333370f03b826196c144d2051b3da132aa00f8bb5c12482a577cad7c31f","54dbd2074d2787dc5a3e64f197bc7beb59987a4d3ed70fc818cd48ff08efeff1","059bcae22f27b9d0b66912406950cd96ac21c89b859b5f0fa401dc4ef9c6ff4d","951bf3b6eec23b6670b605a98998da6e38664d9f64cdd4f3573bc2b6f443b98e","28592ae85cb8d559e3bbe91793ad9c1463f03d7070d2e97d33b3c26d69d2fc82","2386074d572d93c77e096f6fc448521cc180f9257d9f3fb5347b7abaeb08c00c","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c38510e3df3375f4789be40145260d0aab790cc40e6c567e8face06b61ef0f7d","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c920dbc60e5b2fc52f04f641ae76daa1579ad55f9407b58e33aebcba5bb60eca",false diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 454178e..167ab0b 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -296,7 +296,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do describe "encrypted signature testing" do test "encrypted_sign/4 and verify_encrypted_signature/5" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, _tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -306,7 +306,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end test "encrypt & decrypt signature" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -314,14 +314,14 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) # decrypt to real Schnorr Signature using tweak - sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + sig = Schnorr.decrypt_signature(ut_sig, tweak, was_negated) # ensure valid Schnorr signature assert Schnorr.verify_signature(pk, z, sig) end end test "encrypt & recover descryption key" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -329,12 +329,12 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) # decrypt to real Schnorr Signature using tweak - sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + sig = Schnorr.decrypt_signature(ut_sig, tweak, was_negated) # ensure valid Schnorr signature assert Schnorr.verify_signature(pk, z, sig) recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) - assert recovered_tweak == tweak + assert recovered_tweak == Secp256k1.force_even_y(tweak) end end end From 6a4e775f9c19350f3094bea1511705a0685193a1 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:14:39 -0800 Subject: [PATCH 10/40] add comments, make tests realistic by force even tweak --- lib/secp256k1/schnorr.ex | 3 +++ test/secp256k1/schnorr_test.exs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 0534fee..9a991bb 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -169,6 +169,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, k0} = calculate_k(t, d_point, z_bytes) r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true @@ -195,6 +196,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do ) do z_bytes = Utils.int_to_big(z, 32) + # ensure that tweak_point has even Y {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R @@ -220,6 +222,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do + # force even on tweak is a backup. the passed tweak should already be properly negated tweak = Secp256k1.force_even_y(tweak) tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 167ab0b..228d9c1 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -165,6 +165,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do # tweak tweak_int = :rand.uniform(@n - 1) {:ok, tweak} = PrivateKey.new(tweak_int) + tweak = Secp256k1.force_even_y(tweak) tweak_point = PrivateKey.to_point(tweak) msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() @@ -334,7 +335,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_signature(pk, z, sig) recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) - assert recovered_tweak == Secp256k1.force_even_y(tweak) + assert recovered_tweak == tweak end end end From 8bccf204fb38b39c3a3cbc52ac66f747b1cd6037 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:16:26 -0800 Subject: [PATCH 11/40] fmt --- lib/secp256k1/schnorr.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 9a991bb..c7a6f44 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -100,6 +100,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} + true -> true end @@ -110,12 +111,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do Point.is_inf(r_point) -> # {:error, "R point is infinite"} false + !Point.has_even_y(r_point) -> # {:error, "R point is not even"} false + r_point.x != rx -> # {:error, "x's do not match #{r_point.x} vs #{rx}"} false + true -> true end @@ -155,7 +159,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do as the encryption key. The signer need not know the decryption key / tweak itself, which can later be used to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. """ - @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} + @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: + {:ok, Signature.t(), boolean} def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) @@ -186,7 +191,13 @@ defmodule Bitcoinex.Secp256k1.Schnorr do verify_encrypted_signature verifies that an encrypted signature commits to a tweak_point / encryption key. This is different from a regular Schnorr signature verification, as encrypted signatures are not valid Schnorr Signatures. """ - @spec verify_encrypted_signature(Signature.t(), Point.t(), non_neg_integer(), Point.t(), boolean) :: boolean + @spec verify_encrypted_signature( + Signature.t(), + Point.t(), + non_neg_integer(), + Point.t(), + boolean + ) :: boolean def verify_encrypted_signature( %Signature{r: tweaked_r, s: s}, pk = %Point{}, From f4dc7300f91ae8547fcd7bb6f975e126a3e5925b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 23:01:37 -0800 Subject: [PATCH 12/40] do not assume even tweak --- lib/secp256k1/schnorr.ex | 8 +--- .../schnorr_adaptor_test_vectors.csv | 41 +++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index c7a6f44..824d286 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -161,7 +161,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux,tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) @@ -175,7 +175,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) # ensure that tweak_point has even Y - {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) @@ -202,13 +201,11 @@ defmodule Bitcoinex.Secp256k1.Schnorr do %Signature{r: tweaked_r, s: s}, pk = %Point{}, z, - %Point{x: tweak_point_x}, + tweak_point = %Point{}, was_negated ) do z_bytes = Utils.int_to_big(z, 32) - # ensure that tweak_point has even Y - {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R tweak_point = conditional_negate_point(tweak_point, !was_negated) @@ -234,7 +231,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do # force even on tweak is a backup. the passed tweak should already be properly negated - tweak = Secp256k1.force_even_y(tweak) tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) %Signature{r: r, s: final_s} diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv index 32d1f7b..2fae775 100644 --- a/test/secp256k1/schnorr_adaptor_test_vectors.csv +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -1,21 +1,20 @@ -private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated -"64d399f840a0153ae7573fcd736b2b6dc8d31417514ab67622eff2354ee22241","03077b0ff8da086bbd941887ea62d90f0ecdac8acb093254fb4d69dc97d65f56","44f76c9909398b59a6fc09f9cd446476ca91e00cffe8499c1c326e2f8b9289ca","c8d33563fe4ae990f75cc0baa7526df881d95759f1dd31fa10ea3d614b8d6bef","69d1666105256708b242da64ac91b8d455119375ddaf588487f5dfd31c843f9e","f1a3588373fe6a90cf4b8d91295f41693b606f46d41f0d221bd6ebff75817f23","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f3588e3fec4d6f7f890d943e8d5b3acc39799c94d925a62e3fda2291cb9bf2b537e6a","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f358828f6317001321c32eae4dfad7a07fc11d9309580b383a702857cc961e6afc6f3",false -"332b18ac4de2ee1b125bfa35e1b0ae692c900219ae36cdc94de0de9b7b82ec35","57270515af6b0cfc28cc621c18522ce7c490bff838d6fb3e057a9d518d975464","ddbfb38f862619928f205a925ad5d7f17fffefd2526d8b37a83b863d090f8ded","9bd47e739d691b7c81c83b08678b38afc4199060ceae0e1e61fa013dc2eff161","44d013b587c4b9982cefacf9e4014e96f52485cc7648815f519e5c7acf817b95","5a238ecfc487b6a1f52e9fda6047a3a6db0c5a4a6c581671908f81c35f744c34","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331d894ef7dd5635592f2de62c9e456ad757658dfd5f6b3a3d08e23755c04981f905","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331dab8f444dd00f3f9c9ec5d20bea94ff64a03cea73c815520cf9ce2e1010a8ac59",true -"a6f9e49b8e4b714d760dbd8f01abfe04e2108f5accd7ce81091d8f8a04d4e3ef","60e3b83eec196e0ea1195ae64580d3345654e162266800f1c5507bd7acc166ef","a78f44c328c5506fe347688640de9b085560d399be6ecb23dbf06ef3239d9117","e8314bb4e986ab44904e66adc3b272a626b5804cc89bf8a9338296bd93eee97c","7a14183e12079562ea17b59a9156b1a74bb7fdd6bb16413c4a6ac4599b2c3818","4d1860c32ea63d2d59856108d3eb55e14d962cb106daf333bf721cbeaf1de6b1","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559a55545812984531783a3b9cd6fca829fb14c8dc7731242c1fbe916d2c3c9f965c","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559aadc5134f6f7fe10856f43450bbc98ef17a16e5c421fe0137a2735cc5e9384686",true -"3ecdc22e7f8f527ba66ef75754ff386db60739ad691e8713b00c7d0e18650365","e3047a46759d8e47b22dd384cf8fe8631fefa466ea41b75030cea308c3360d45","8263cfdf5b3034958b7e503907ea95722deff04f0fdbbf76adbae6d79774f2de","95e028af7e8890ba89705fdd05e26f6c68050eea5f17c65095cccb903da76f8d","6c1ea354705a73aad3006aedab07ee311d343b1421b7f4daa4380c389a793180","eea79caadc04e2df90c29494e002bb1cf096c11996eecd8ff9c4733ff133c12c","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccb47a6a2a1a3a2dafe60ea9e54b1ba83e99b0125d89cea8d516b9b5a6fa85bfb19","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccbca0a7280fed30f93ec68ee8db9a5195bc8f11627acc64cc8195641473fd0edf7",false -"7789778cfe9d60aa50c339a11b36ffbbc5d350c345ab14e56f207f430deafca5","031551b96dfef020553900283837e36f1f669dc1b7857129a66a23dc3cef7b04","19db69599941098c1f375b8608094dbad22d72e1fc62cd65285321a029b63de6","4ee81875e67869acd8d49b53dcdfeea2c64360b4470d548cd0a66916e354e565","db7aa4a8785c8cab4d42f6c1f282982818f5b528b3d6beb5b99486de031bf4fd","03ea829059d5d7d36ff9554b3718a0177a04de8e5ff33730c72e480477840042","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410d8824b1197afe7727177b54d6378c6e3dbc60fabdfc24b60f572ce0637b02089b","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410da2001a73143f80b336b2b05c3f95bbf88e8e6d9ff88783747f800203a4b84681",false -"2c42d0167ee5cfa88a0cc1c16fa0c9dc38c01d47f30e4c5423c707aecdd365aa","b97f1f0ce685329fce080aff4c9a17f3dfc6a14bfe9689290e9b89ec7f24bb5d","f06324b650ec674ccefa36c4e19c4e700fa09bedd1f7129c44745479dffab65d","0f638142959e8e6dc979685bb4292b646389db2ec418f87629bac97dc6c21478","f72df7b15f31c5abb0db5af07efc53199afe533af041512b47276d7ab90336be","6b117aabe372c146924929aa00990d7392eca59fc31b416a833a7e2a56ab73a8","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dedade54f20ed0f0345cc22a4374971ce64f4bb7cfd0e03574e0bab7f4dd1a051","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dfd4ac098d000a7b676d1ebdf55ad235d1002fc75da5f90f6c969b5923e0d2b35",true -"0570c938ab1e31af3244c1c8de80bfac01da36cb00708506aa9d6294b1020275","a162076078883fe9ce67f32a28eb637796fe5a20c3ee5a4dcf5795d54142152a","cb97db136151e7fd66b36622e2f6ac9f1244e951b5bf15f9a3f38aee73be4ce3","6ba7a5b30ac5bd0e5b7641830cb74367b41136823f8be7100e3c14d80b3eebcd","78ca0adc4d4d892c4bb8060c10b69d3b05630a5b5189877b9f20691897988174","4b8d63dc59a313c09548593d2002a5af4ca601b37a98bb299d8605062ea8c88a","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d4538757ed880d9f67d61947202c7d6ba6e82ad5bf99f51b6c318009f26c59acf5","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d41f1f3300e95f87653cccad430f7418473fc0e22aa06b912a15a136540fe1b897",false -"cfeb57aa91b08f7df563c54cd5c37fe9354c34c7a3a0942a73a1bd28d01ca785","6379d1dcf2da0e5c90e05d02b32a5c969dd21ea986f2df5a329ce2154c2f54fb","6c8f2a3b7bae9cd68637fbaac2fc1b62d5ecacd24ad97163659e7016ed79ad35","bcd420b56d103b29f50376335b959b76ff1102f319cc0e8c791615cac203c0fe","91d16e79b8869ae5d5cabfb4818405613bb9a89b552370309e44dc923c5071ae","fea792688415e4ea1927a8287dbf938aff1795c590cd89fc83a6190820f4a619","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0427bf317bebab7ded50ebf1b253845b43da1ad71cb5d0bd93f8fc895a7f2ebc3d5","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0420f63ed833f0941fe88871f7a754998dacbc0c4f9123267dc2a2a2591057216a0",true -"94c063c368f720e36575be96aa0075d73e669b9a0cb4188306d8bb4cb1cfb1e1","637cf87ec03dd68084474aa9f22bcc94a0985048d0a38b24e12b821f492ff4b5","c9fb6ba3efe3064bfd7625a53f254e90c59f35969ee3a65e110d6437fd24b435","c33096b024f6871aa9958e620501df9adc43760b6213e0a0783d143e1f36e8d7","68d398b891283b42e4bda770d3cb644be617e82a93cbd281ceae542d66e3bf55","54c1eec0ece9844fd5de9512e72b4e3452cf5472dc4934f0a7ef59323468d42a","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9afd56205207bda7af8933d3ac71f5ef475aa03bbe8fe78733ba42f1271106a174d","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9af9f5d70c46bbd814490b3606c5e844307b4f41498ee13795df56a181c3d588a41",false -"9e0278e0e6f8a0767dd81ecea025adf55dfaf913f9b4ff347115956fd0fb327d","535b5a59e5879920b490835ac9dbf23a82435bd083b2cc66be5be9997e2ce681","c47f351884757e03385b7c5a60d43895fa4b88dc9388d20c773c0aa6704b7ee0","93373b7570b569d3945cdc306b18b29b5f71a502f2636accb4a0b34be6d73203","8d96a34cf601b53ea3f0f1b0a5ca315f89f8d968f148c071560fca35ce97d1d4","e57adc1b2b869f8e07d97d2725fcffe19338c737a78c6d9112b34cbc5fa7c1b0","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b74b08551ea14588ec3a29e1d2d88d629721bbfbc7af3f5785dd7e41ca03f7d857","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b70f878a3725bb06ef72855e2d39619b2e6158a7bd937f895694e7ede3a40d15f6",false -"e29f4867a64d1199a19777f91c9ec2f9d15dd08a93409144ea0064cb60c76a3e","85524318955cb8c63933e6e0c06b8fe561861628548539bf760c2f1840cf8ae7","3b415bb6c72bce59fc88681de981ae96875d5cccb98755e195b3e4399340171c","f86e55a60819bc5b9ba93746753f4fc0db87c10de5b7d25f1fe242b3c1bb6ebd","1ec2865fb83a22cddf06a22491d861b685e987d115e89c4d49b93672944553df","fd6e924750d9359ff32feacc6e61991d6da690d312514b5b896bcb3365f05bdb","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf69bee8aad31fb077fc38eeef227519f5a1ceb06692d362c11ab34351c8a15381","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf2e7d8cf40bf3e21dffb086d138f36b5f1a715399d94c0cdf84ff5f1835613c65",true -"5745cbbd4a3f91aace64d6d5dfd81787b40c11f9c6a934bdf3fc80b2c8e28fb8","c99af68ee4b762410875e5bda87af74683541ed597966c64c96f8c486ff1f19a","627977f34498f34ed896fb5019fb9ffa2dc7dd238bf3777a3701186df35299c7","0a801b75ed3ae344de1b255f53930fc82fade6a0ba0e0b07d8b109a2b9cc3701","47406b77cca9234a06bdb4fc65ac76649e392ea780bdb81a6fc6cf3221f1354c","652839026578293d7ce1969b4d457739e722a2f5952857889da87a7d1b6f0f89","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef74495faca5ac0354f0a837a66214ea462f7f4e7212218776ca7ea37eb8b77ce53cd7e","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef744959850e2ccf0b61734a1cf25fe8a6757fab95944f4eb79307000ea7309db0133b7",true -"0c097d304c27a23e91e639407341eb33bfc90f74184130251bb0553dbe2de854","2b0868c00a5799f5686afe3550ca15d94ee2a6a81b1ecbda18e0735ca7ff7a56","6dc0f73722daa9eef9d0dcf931841a07f53c844b30a08c3f89898e619bc855f3","5c44f4f8428117f9ee1ad3711ec1120ba5a8545cc7d5aace491a215b49d617ec","52f98f0d574cae4db1cd30cb67a3e65c44dedb16f07c7bd728bf0fb83d0ad408","460d47e19edb129b81d62e6b59f76dd7bd4c61a725da3338d706c0e755ac2ae9","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e9c67dee427981c760ab8c6b90ce3d9f22d07007d16845eb9f80fe1dc948efbf7","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e0a28d61b4a72c6650489a3b23e67f3fb6794a7e197dc4abdc1c711b1602110a9",false -"b1b904d991387680de13c606565643e4930d2bf79b721db4530401c3b92cf3cf","63cdee00d9f07194399fe6518512073190b71dc2a94084681e49a172158f90ff","b6928d5994fa41ebd66a22a826cd6ad4015a23feab9ae45f1eaac309007fca73","38388ce16e2c5e1eab89b4e56233dacb58d4aa286ae2806d5debd117c9c529f9","6eab75f6bf9de09d363e4fc2447d0fc03feb78a5fb06ccb39daf648e13fd3045","41f2e0be320908f40428a195e471e425864ab7acbafdb11e642a68be1598c2c3","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cb97acb68f70542aa1f6641f228d0df53b3a3930e82cac62d5cd466b89ca9049e4","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cbe11a2935db59e8b61ff9fc7a66408a65f38de9d0305a1eb26e6e070d9a46c0b2",true -"7a444d39de9cf6852cf431a622af15acb852dce5034735166bdc4cccf6d984d3","52853e8d893caeb43c6ab322625641ccece988667bf9d64670a4df4750b83d7c","bf611892850c5e814215dfecd9629ed0de56376bab46fe361db5c0ba4fe916d8","103dd130009f424fdc25eeb5a1392a36fe4e008290267722ac24eb492db00ead","49a5fbb6bdb0cd167e5fb7b4b8544fcd87a3433e66292173c101efcd97756349","7666bd5dce4e4ad0df76672bcf6f8123adf4f5314894b808fbf739e3e3e687bd","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9abd8f6cb88cc6090f789d6726cda594f72d0335653a5b18c27655fb38dee47d56","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9afe2e542607b9aa8e36878739f442f625095bdae03e5cbac81872990b5f31a7bf",true -"c19d1686e76d5b777af0924e6154615abb7db030a22e407a397ce7e301d87742","0ba3c133ab7dee2839552fc9e43601ab3cc501c21891f2fd34fce78e43b63e67","2e5b908a7466490f23b30f5b75bbf10ba0d9c901a8fa1188f5ba6b1d07277174","5fba25cf7f5fb85e24900d14f789604dd0eca2eafa53ef2a41e55a79740709b0","ca543d2652c3ff13ac98641cb65df9dd0f719706363dcfa74592b373a638e308","da2cde0c16fd3cebf8c84ec28dbf1a951f43ff930795b610df8cec40749a25e2","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e39584c6b03ef20b2edf449b5f6fb7a00a42eadc9aba1f3652cd143d37651700a","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e0afcbbe08f88d7a3ca413a5a813f88f50354e4c802a7e1dc3716d8b66f29fe96",true -"29e362a7bfef5fb7f4918ec19e25befa8a4a43a23c8ec7c6028f97226773a7bd","2102f0eed266ed80ec7568cb20eeead902f0c35309f5ba7c1567b819a8e1bbb7","6c82609f56f6c11866cc83ee2498d551c17ae68d72904b4af73ff8e5f45c3284","f9f9be4fc4d63060a3c3c0656679f3d757775f8f8641a71f1525e53b57a1d99b","c78fc24af3b81ec075e1e1764b4d67ea07b5a9dda2e1504e159eb427025fb80b","15968a700ee7598db61ab47d210a27f7d0d82c5d25739b6141b095de9e00942d","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39afe63ee86c0240d1f2bda706dcb386ab6b1a3d73cb44250f9fd409b764a4e2bc9","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39a6ae64f26171ace3792a6f45befd14009b86fe0e37789fc0934ae35cf6e741d0c",false -"b01defc1ec85fdd6381f62ecb596239e91245b940b18489071526064a538baef","baab11fd4992c19e6a7692944ff665c08e1fb06c7a26b0f2ee2253fc9f7227e8","a0dfd48fa85f67b08b3fdbb9a6bb71c74b4239148f65f35caa37b1d1800fce44","025342259c0aa8df863a2f151ff0c2b1d6064997d1c54c42e5b62d033e83c7af","f5032f3bb2f6767a6f95b3559b74e14edde45ab47f8ff5c48542d748374c5144","08a35c8959bf4a10b85c75b7ef260d39f865876f4bdb66511b53dccf3f1a9353","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d9933033dc488afdcf7a9ac18515f96a4ac4e73db690bde3f49e2ce53141f437f0a8","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d993d113b0d8335d372b260160cfa025bc8c327fefa54d49e7fad71ce3137447beec",false -"dea7c5fe87a447632a53dcf40716ad99bd04d983405d851f44ff2671f8f8a6ea","07f04f60aa3367725401b8a813c0575007438714f3b6afa9150acdae4867f2f8","840da049a59cc5b8830e54aee306788b2b6104655f0a23a9404671f821fe9573","4ba30d52534bbed6bb67fa5e1bdfb1bdb983843a56887e43e652e89ccaae519e","d568dee1f5d4d1b400641fee5afd68e176e234acda24b0eb2516f739a8e87dcf","cab1f1f1229e93ad2efb76649c8091717ace3ba01461ac559295c5e0e006d07d","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef0153370f1f321db528ee4f907e8b66903f080575efd5d11bb2518d9ec021e6b4c0e1156","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef01533706de58191acf21f4084da61ba20ea07cc339c58ac5c1af530abbbac732a0f7be3",true -"17936333370f03b826196c144d2051b3da132aa00f8bb5c12482a577cad7c31f","54dbd2074d2787dc5a3e64f197bc7beb59987a4d3ed70fc818cd48ff08efeff1","059bcae22f27b9d0b66912406950cd96ac21c89b859b5f0fa401dc4ef9c6ff4d","951bf3b6eec23b6670b605a98998da6e38664d9f64cdd4f3573bc2b6f443b98e","28592ae85cb8d559e3bbe91793ad9c1463f03d7070d2e97d33b3c26d69d2fc82","2386074d572d93c77e096f6fc448521cc180f9257d9f3fb5347b7abaeb08c00c","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c38510e3df3375f4789be40145260d0aab790cc40e6c567e8face06b61ef0f7d","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c920dbc60e5b2fc52f04f641ae76daa1579ad55f9407b58e33aebcba5bb60eca",false +"01138d3785033d091a3fdb849d9a1291f5a6d1d24a81bdddf8881b10204e6136","02a13ad24a8a69cb200f25fbb73c2d3f403a0718ca4b02864b4c26b033cacfe2ff","665900ff1ca9e0bd80c12815122c6d928505e2b23e0d2121341fb1d9bf474f3c","03911b512d73c3602d8ae4455bee5cc82d442c00d9754b403f5b3eb83e98ed2ed9","a10616fb1ec1e3e212c30ccc0fbb5f8a3e37c9a821e35aed3e7431776ffd7c8b","eb41b70f96364551f2a8dea5177d8f7b0456dde9b21df232c9cfe1801b5d7646","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d10098fa3daffb44ed51687508d3e64825f8aaf7a4ae4ad6c57965d0caadc1e3f7b65d","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d100986096b0fa61973225f5c9fbfb5a52663ec1fbb4166589fa4b4518010ed308c458",false +"20778aa439316b57aa4637c831f974a3b87b2c4e6eb95f073fda0cfa79bbdaa3","02bf1d005c2b11c3bfac8f5d518ad7b4796b6dcc37f418dcc9ba79d22abca18746","86e8589b1adfe4421f98dd88031345a3b3d0c5bc92fd50c4c50df38d64afd274","0255ac067d96a6c45deef413141ae55a7a00ca277b13bee4bac667fced556ead18","dc909e9617b5e440b3e0a2e85a7c82cc2e31e3fc650a6b119e60c65e98f0118f","20eee2ddffddd43f02d884b1844ecc6e387cc25300d0f835ff4973b2126a514b","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebbd6d19cef77faa6e6c60412b3782f8092745486b49476472fc1410da6c3510f00","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebb4fe944545d1ac2a4a66b352b751c3aeec083c0f80178f66afc331a195ea13c8c",true +"7a34b0f93dbbd0c91b588a16fe4bd5d23f1903671f6de4403213cc3ce214ce38","027fb795e7e29f605bca05761496ef55de514d1d4cb67b1bee84597ded249ab024","6828ca09e3098a5a71b3be510794c1159c0eff6db384a830575c62a0006eaae0","033dd2db0ac9870447c63d4003eb183b0545fb86ccf929111666138c31bae62a28","be4a83480af33dd41908f009b2d9d4cb218d230acb7c1d48c40a97985e2bb2a4","4a1d8c39d92fa7405efe687837683cbc8e20f01621132161324e78d8b6c469f5","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a1127622cea4b231d718eb55b2a253ce9dc23a1e76824d74ec8174951fed0fec49de82a","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a112762c4c181193a68045ae97666ebe247628b060802504a8c0f54ba74cceb94657e8b",true +"547abc10d9ad870d554edc78028d21893f915d718b5d7849fa127a37213cddb7","0217f7e8f23c85208c10e06d55e90abafcd0bdbda49098a63a11cba7c1b16cc4b0","65c8f90e572f1828c167377f64f40796ee8aec5ce2baee05c762cf812f33883b","0269aa06097d53b56a77e39b46cde9b337084fb70a1f8c610518bb3e9c6b228445","a4c968687391d781c22729a941b6564df3c94a8eb9433f6b73f0eebba0e94318","d9fa09c670a30b031a1e0e3f72743506044f6b39a89e73c0bf87d6accd5b94ad","593acb43862897e5af5f5429e9e9142d09c75378ec70128f700c869cb6bacbfa0d527137644c31169cace2948c580d7a96347a6637b61f95ffcc6ec513b88d03","593acb43862897e5af5f5429e9e9142d09c75378ec70128f700c869cb6bacbfa731b6a45bb7b493f5e141a13f14c151184bf66c31a710d9bc72f3e4642ec153e",false +"593cc37fd28d28005adbedfcdecd18a071330e7ad7d853d738c77c61bbb83ff9","02c923dae361f1bc224a246a0592000c051b03b821ab26f5edbf69db6256d8a665","467b336868572cef5496b4f011ebb73839c7cf0c7763f1161eb1bfbdeec40d26","03d31be8ac7b423907dfe5c9834d3900247ac43b7dcadc8465ebc54b128c8910f5","81f3576f1a142901d056da8588b164a8464efa4f41cbceddccdc6f235ce49f59","86d81f4359857bbf39fbb128103d3bad5f46da4a52eea554f30d5954cbde7cb4","c435b682a7763d516da6e6483668e56fcef6ded57ccfd90ef225270d47cdc3f718482a85147e73108256664fd3eea00f5fd74042d8ceb331b678d631500b2712","c435b682a7763d516da6e6483668e56fcef6ded57ccfd90ef225270d47cdc3f7d1ccf71cac2746212dbfb15fc202e8d5e0be4e1d10b3625757997500317d5b2d",true +"2eba8f149e45f2af5c2afe5f51990d204cf240d22fc9e2078c48d480cf5ca6bf","02cd768be3e2d46d7734ebb40c87c08177b16abc092a6ae89d0aae0ff965dff8eb","ba297393b1c9aa874ba18f0351b56d52ead9a2d0a740be41779e9fe9cac59734","02b86184c2c2ab98441bb98c63bf0d98d38b8aa7e25ade0dcc116f7f6ff6c0a4db","47966fb320c357639f981bc883cbfc84d23153f09d88aa671a21a6199690ba8f","40fb47e380da85678b0ddd36c99c049f31c912ea83efbca9bc069a45151aeb4f","34bddf1a2bcc8893189cc3f719393b83d8077dbe85a8ce2bb02c4847815260550f31a7e7d3ef1661db2d70d2e408948fb21739ba17fe6a8f970559a0b4ba6bf0","34bddf1a2bcc8893189cc3f719393b83d8077dbe85a8ce2bb02c484781526055c95b1b7b85b8c0e926ceffd635be01e29cf0dc8abf3f28d10ea3f98a7f800324",false +"11ca0283f12577e4f37d0addd595c16a24ecba5ad9e6c2c6ea484525507ee953","028aad61e58c99b051ba6f95eb920e293d0f6d6fe4b9b1d55dff16b2ec368c587e","c6b5be059c076f99855f2854ac597e3d5a43310c6442592e1e5d1a8dbfaef62d","0317409274a1cf439bb11c3c0a01a7a7fbcec7ecefbee1330dc7fba93c530bc4ea","6fa2ad2be58417feca91d9c7d79b1077becd0c60a002be0e3fefaf86dc63a4cf","909676b82df8909a1a86b0a470f1fd5865a1fbc615381ead571b0b25c0b7da70","91d927a751000f0c7c72a76bf0ad15a315b131205ca964e03f8aec6925eadc8d07d3f00cad97c89b46a42fd84d47669ded22c11bd73e1ecc78fce1e2d77817bf","91d927a751000f0c7c72a76bf0ad15a315b131205ca964e03f8aec6925eadc8dce89ae12499f3834cc03582cf9a0e4db4765f2283b8077fa9759fc7097270dec",false +"29e17ae4aa2b13df44c3131c6c237c29be522e2a6eeb711a8d1f24eb137aaf0d","02edcdb8d9f4eaf8f2c9be1314886d56b1ff9f7a1892f86df38c366533356b5452","32f2f3121b5d265815c0527bea181b23e87e0d594e3af6d263eda72e7c72f645","032eba5528c77755be8ceb944dd1e886ae747c6e1390b90235941dc6e18ba7c9de","5be83e6ada36f33de34535159ab5c4fde57a60826eae6a20547a8a8203cdae8f","540c1ef21bd5a46c0a6e31a1922717aeda414903c07ed68060f17df40e7d29a0","ce472e6c3d7e75ec2683a295344c12890c2f99ddfab53a292ed1817e10f9f1069c2963013648549d786aeeca133da9ed186ff51e43a05eaa193f98743a955657","ce472e6c3d7e75ec2683a295344c12890c2f99ddfab53a292ed1817e10f9f10669366fef1aeb2e4562aa9c4e29258ec92ff1e7c4f56567d7b551f145be226012",true +"3504bee0a1d65cdbed68e8decfe9075dca5db9dc97b6aeff701ff6dac421c5c1","02c016e5f5b76175063f6336480320cd7dc23076a6d700ae34cfdb48268dac65b9","a2113b65d633a93e4196c3d68dadc7e227f8dd9d06e4d2bc5b5a3ac55ee567f5","02abbee001326b50704b9b17efcf98702dc74600749d0730328d2d5f04ccd2cde1","fdd6b40c8bb467cfc316d310ccdd0ef9ec6efd89932bccdfb8d1381e96ff5480","fbb700a915376fda2a157861af255025b510694919cba7fb5fd7a7f0744b65c1","e2b92af7dc9e15c4db7eae369fa8ae47283723930ccdd5b61848864cf7c9fffbc83aa93cdce4a1f0027b29fd82c5293c59bfa277c08ad4c48e4a243557e06265","e2b92af7dc9e15c4db7eae369fa8ae47283723930ccdd5b61848864cf7c9fffb26296dd706b0f8b1c0e46626f517615a31c6c4dab9a6020832efe96ff8fafa70",true +"84ca840ee3800300d023a01a439fbe62051efa7711781d5e17dccf24f8aba61c","026688fd24a8d11df5e166b14edab4083718859bd97928a70e05d8673ade2e8c16","fbfe17a13ae16e0adf77cf96e9af145d2fc02580ce19cf98023a3b5f9ad04f85","0221e822b2b559396c6eabdc9a5d2cff7c27861287c4ee0579a9ea435fa6b0b8d6","35cc81ff675a06d4042035116930fd52dc0a8512066e65fa454ac1f05eca8dc8","fc98beeab724efccb4ea955d51ce5cc300fad15e2811a7917fed46dc04639285","b42581ccd0779f6371209ff597344213e8e48851b6c95e80c51a75736f88d0ac078c830c97fd15e9d42ac9dc4038477df6dc171a862061bed25012d1541e4759","b42581ccd0779f6371209ff597344213e8e48851b6c95e80c51a75736f88d0ac0b8e6b6b5d1ba7def4b2fa455689331f81cace80674f32628fe835fe89843915",true +"79a7b3f13d359db306ea1778dd4c7b891793e5e57025b35e8ed7895d570b86ea","02ea1a0c4081c7a9f86352e00b135e6fd2bd0ad17c50fd22a9517472e62af05380","be025a17089052495761ef09365783b24ccf6844bf476ce54f445d0416f512b2","02987da0d941a03f75cdfe7582b90e58a80d0c80bf3073bdc42f8dc65b94da05e0","8f62d16c18211ee53a15f200c24a7e4020a04843f62c27e3ae263c1f795b5e61","a32a6d8dbd4976e31474a405592bf6280b799597e6e946203a4ac12062a29f06","010ac477501ecb0ef5b4bd8b81d1ab09de7fd1c790153e1af59101f53492a07fcdd686259bc74c3828a07a2149b8d2f3601c67133bac4672912021cff27bf547","010ac477501ecb0ef5b4bd8b81d1ab09de7fd1c790153e1af59101f53492a07f0fd42c0e9336f9eed13e8b1813614f41134cfece7c64d98d41dbc4cbdb86e295",true +"b12d7d24c6f0b0afd29b43cef391e02b5182356366344f60c12f361c44eb0c1b","02f2c0830b948cfc2b7ac9aa7c6390955e0d6e20fd67930e5f6a87b8904ee57ae0","1a41e58047f2ecb0a60028d491355b2c2dca60b9894d624332f2c707220117e4","027b6ee8e1bd3272edcc9ba857f0a1d677a455e8cb18258866508a591d9f4fda39","d48ae86c6552cd7c5d536cd4bd28520ff89fc328816fe2d4ccbdedbfbbed10d4","7e6aba5e4f518dcd4d8e1ba1289e3d5b2fef7c5e7b4b94b0b4afe38557f07bd2","19d508f771a4db7e5a1ccce0d5565686bf9c9ff506650c48c6c057c7e725042fbe9c7badacfdca549b4c484e712567233ea4a17021ceba8ac9e804d753baf13a","19d508f771a4db7e5a1ccce0d5565686bf9c9ff506650c48c6c057c7e725042fd8de612df4f0b705414c7123025ac24f6c6f0229ab1c1ccdfcdacbde75bc091e",false +"d671ee3655eb17d4512d896d851953ec8bbf5b8cc7bacd005e5d3b6442d264b3","0269aafad3bb6e1d2694a2570d7e1513e0154b1f1c7ad2ae9fa7d926bbefae213a","5152217b5100c40ebf949d863f101ad72e1d51e24be6b7edc222c26933b0e20c","02acdd14736689da6d7cd8f200ab26759ebf9c65ef1207d17488c3b3b82fecf52c","28c2b4a6f7c5e7499beb923caf7d92646c0b11af4af6873f0d52c9c10e8150c4","a5fcda2417c758bac50a05e2e29cea8ec69a166e70496ad6cdddcef0c23bc5a2","ada74b4cf2f6f67c91c8f3011a612cb44f3b3760bb139d9d80fa5cd7657d53bfb7877e86424e3faa3f123162cd0b4479027be74d40a9569de0da29402e22e0da","ada74b4cf2f6f67c91c8f3011a612cb44f3b3760bb139d9d80fa5cd7657d53bf08d9a001934f03b8fea6cee90c1b5f5175ea5c48dd476e4fe32a8d1c919d81a5",false +"29d8887c2b4dc6ad8b4e96dacd1798ad7bc1f67e458386e2577c13fce3faf282","02842cb699537d1a0d46c5728a7341890c767b17b38cd49c758874dbd65ec698c3","332bc36dfc79a7369eaf37bf1637db0b740cdd1079e212d1480178f20a3d0176","02cea3bc0b5ec890822f52e8eba87690d1d92012f86622229fb5537db321a371ed","8bc125e47d1b0593b721656d01dd4f1f68ddfc3b1a2758197569404d0bd0836c","00f1be297d9ee2b7559d61b8dc8e9a781366bec75e126acc53b8bd0e3c84755e","c4dc81aae55b1e645bc1d45f5b30e18079f36d27449268919ccf889f89b8844a28f4c4d83ca844df41f97544dc1e8ce70db632b388a96a2708cd0994be7882f0","c4dc81aae55b1e645bc1d45f5b30e18079f36d27449268919ccf889f89b8844a5c2088463921ec15e0a8ad03f25667f281c30fc4028b7cf850ce8286c8b58466",false +"feda1cf986c989c081e1c6dc8b434715ee2b00f28a0823577a56c8a64e979736","02eaba7f222004910e7be62ed1c8a78e9dca7e6b31fdd5c5e4d0751d239bd63a20","06ab57ab98caad717e6a53baf5d1202ed25b258dec3a96a32200ac9eb78d4fa6","023238fe1444f7038dbc56fdf20246ff3c133a1053caa9aea700a63fe8eea172d9","79d4fb0e147bdb23b4b82d73fcb937d63bd61d94e6ad469c77a0678a3e5c15ed","66a8ee476af678dc112ff3aef4388e3f26b68db2cbe7c545d40baf1d8062447b","1e03ce47176eca24579c88907f17acdedea160054115f59a744822b1a06d68fb87219dacf6d9e6afe333a79c40892b8b1ba7c7151711d655f02110ab3bef2a4b","1e03ce47176eca24579c88907f17acdedea160054115f59a744822b1a06d68fb8dccf5588fa49421619dfb57365a4bb9ee02eca3034c6cf91221bd49f37c79f1",false +"723ec1fac7ab8087de8f3d6cbcd7fce6ad497a32683406031efbbb534af5e4f0","02ad3e90516e2aa3a4d848ca71848f8bef22b6449ca8ff719346717c3d08c022a0","7454898bbb9418ad0287f9861b0b9245d4ba592fa2d263eae113ca5afda3a6ac","02eb538ee72c2672479361070b692f2bd05536e4f535431fed7167262d05ee6393","12432a48bd8f9d319c2673fdd7e16ddf60d2300b984da1f24fa53f1a0149f4b0","1174a245a991e949616646358d4bd79f561289343a9929a1adbf245c8efdba51","fbb9d7d05da1d35097619164867b20267a69171b8e7d2c47ea11a7076abbd2cffb74559a030c3d7c804af55698f18f1bb30dc4b707b2dae19033cb57b2c3ec4d","fbb9d7d05da1d35097619164867b20267a69171b8e7d2c47ea11a7076abbd2cf6fc8df25bea0562982d2eedcb3fd2162cd1940fffb3c9e90b1753725e03151b8",false +"ce1ed0fc2587a09dbbb3edbeb1e7d2260e694e47219ff0b5803e3633fb6e3b8b","0297fce00d0ae1a7ca4a21a248bbb56d02236b4d9071589dd83a7fda160cab6a17","0df5e4cd6a732400b909810cb2423c81231b42c1b8de953aa0abc93acccf79ef","02a08986f8334a064dd81f79e0d46a451395f901401e0dc9c3dfbe21fd6fd803fa","31432c7343d699c883557306ff017beae7ce991c525edacee0fae6cd77693f27","55bd3426ff7f94c16a439d1d7a3ae8975e2eb660ddf8695c1b1cfc0bcc711b8e","41d93b7c672c3e43dd2c8f220acb5ac7dc05a787f8eb51ba74a75272b68bf3fe5b5d97b2d663ad2c36466345d7508bc815034f9f98accdaedff225301f038efe","41d93b7c672c3e43dd2c8f220acb5ac7dc05a787f8eb51ba74a75272b68bf3fe69537c8040d6d12cef4fe4528992c849381e9261518b62e9809dee6aebd308ed",false +"a99059144b0309a1ae1c742225b10fdbbf3cdd4e89536f91c227d58175220dd4","021c840d44397a1598ffc1b967acadab2a49308ce4b2749725a3f0af7887e63f82","82e0a36bfc1d3c1fa16272abbde846f6573c7b50c1f1d1dbc45bd6d3f9237da8","03e1bc5c665861bc71da5e12509b96bcbae9f8f3faad490155fc55e00fa5313712","fd8d2ec5dcacef0072f4666268d0877c5a46f5863b4f82a579b4ccfda67c4ced","6a4c6660d79d46131e1605a25e9b8b17a8d3646e53b4d42bc49fafd26e1bef3a","662d14456a5ab31a1012cecc068b141ca8d41757e73ec703aacc955bb54e4e433560e1951c0277557426a7310dde160b8c01bc083f424e7631b844dedd38280d","662d14456a5ab31a1012cecc068b141ca8d41757e73ec703aacc955bb54e4e43b2803e291fe53b35d2c434854ff5cf13ef741d9e2c991cd62d2ecc97b44aeba6",true +"e761546d8eef5cd892957115b0e896fb066d28b14179dff8bef75735ed968011","02a09f5a0da4c7c66d5d83b6427e64100762c342ebfd8825eb8050bc6db0ffc333","a739f4b46219bd5b6f2b8d0c945ec0beee7591d2dcdc32b44853d60a6cc19476","026167160fd64b079f5b47735c2c5e27817a8cb877e3c03c940b9808b0d87ccaf7","4ff6144e72807179659e93c8b786042a5fd2bfb08785b293eae5833cf5b490ed","5d815476dca33ef58022bae294b784d0757c80ed49dc20fc86bf7375f345c30d","6e2c6e1ebb9e22e6592bb2f0ca126b09f167d1c0c29d6f8296fe65db07a5b69ea6cdc7f44c2f71bc54be5b8051a6ced0f36c0db67169485500c67e45bd6c19f5","6e2c6e1ebb9e22e6592bb2f0ca126b09f167d1c0c29d6f8296fe65db07a5b69e4e07bca8ae492f17c3e9e88ce6058f912732c2a29efcdacd8947f5c359f76d2a",false +"1fe1a3cd19735058b4517c13f7d225a0dd0b7176e8b970f72337dfbe92ea1394","026c605773a227b87c6bffad7adad8b8699a2b3b84d61a874e12bc7ddaf4051daa","c013f96c065555a9185ed60e3a37edaa9b37c83a9a87ea164075822c2e962414","0362f76db1fb4049445b220cde65785502e22aff575baa8444bdff70320a7137a6","77e0910f8dd5b3a54c54b11106323848e26dcd142209f16206735665ae740651","0430912f3219a070d6f14d8ed4bea2c961ef002efe1815cd1fec456562fec2af","2cf0452d6c63c38f4396099c1fe24843757c5acc83f9ed105e93f2ae11b65d217dc9110ff8c01b5a5b9b5e6259836902baaa6a7bcfb9dd7571680cb5b9bfb8e2","2cf0452d6c63c38f4396099c1fe24843757c5acc83f9ed105e93f2ae11b65d21bdb517a3f26ac5b1433c88541f4b7b56da217f27e47a939af0c4e9165b5fd60f",true From 932687b1f887d05d1f15c53dea556233b06e3ee6 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 23:03:19 -0800 Subject: [PATCH 13/40] do not assume tweak is even in tests --- test/secp256k1/schnorr_adaptor_test_vectors.csv | 1 + test/secp256k1/schnorr_test.exs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv index 2fae775..1d77803 100644 --- a/test/secp256k1/schnorr_adaptor_test_vectors.csv +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -1,3 +1,4 @@ +private_key_hex,public_key_sec,tweak_secret,tweak_point_sec,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated "01138d3785033d091a3fdb849d9a1291f5a6d1d24a81bdddf8881b10204e6136","02a13ad24a8a69cb200f25fbb73c2d3f403a0718ca4b02864b4c26b033cacfe2ff","665900ff1ca9e0bd80c12815122c6d928505e2b23e0d2121341fb1d9bf474f3c","03911b512d73c3602d8ae4455bee5cc82d442c00d9754b403f5b3eb83e98ed2ed9","a10616fb1ec1e3e212c30ccc0fbb5f8a3e37c9a821e35aed3e7431776ffd7c8b","eb41b70f96364551f2a8dea5177d8f7b0456dde9b21df232c9cfe1801b5d7646","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d10098fa3daffb44ed51687508d3e64825f8aaf7a4ae4ad6c57965d0caadc1e3f7b65d","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d100986096b0fa61973225f5c9fbfb5a52663ec1fbb4166589fa4b4518010ed308c458",false "20778aa439316b57aa4637c831f974a3b87b2c4e6eb95f073fda0cfa79bbdaa3","02bf1d005c2b11c3bfac8f5d518ad7b4796b6dcc37f418dcc9ba79d22abca18746","86e8589b1adfe4421f98dd88031345a3b3d0c5bc92fd50c4c50df38d64afd274","0255ac067d96a6c45deef413141ae55a7a00ca277b13bee4bac667fced556ead18","dc909e9617b5e440b3e0a2e85a7c82cc2e31e3fc650a6b119e60c65e98f0118f","20eee2ddffddd43f02d884b1844ecc6e387cc25300d0f835ff4973b2126a514b","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebbd6d19cef77faa6e6c60412b3782f8092745486b49476472fc1410da6c3510f00","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebb4fe944545d1ac2a4a66b352b751c3aeec083c0f80178f66afc331a195ea13c8c",true "7a34b0f93dbbd0c91b588a16fe4bd5d23f1903671f6de4403213cc3ce214ce38","027fb795e7e29f605bca05761496ef55de514d1d4cb67b1bee84597ded249ab024","6828ca09e3098a5a71b3be510794c1159c0eff6db384a830575c62a0006eaae0","033dd2db0ac9870447c63d4003eb183b0545fb86ccf929111666138c31bae62a28","be4a83480af33dd41908f009b2d9d4cb218d230acb7c1d48c40a97985e2bb2a4","4a1d8c39d92fa7405efe687837683cbc8e20f01621132161324e78d8b6c469f5","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a1127622cea4b231d718eb55b2a253ce9dc23a1e76824d74ec8174951fed0fec49de82a","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a112762c4c181193a68045ae97666ebe247628b060802504a8c0f54ba74cceb94657e8b",true diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 228d9c1..fe4a8aa 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -165,7 +165,6 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do # tweak tweak_int = :rand.uniform(@n - 1) {:ok, tweak} = PrivateKey.new(tweak_int) - tweak = Secp256k1.force_even_y(tweak) tweak_point = PrivateKey.to_point(tweak) msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() From 92ae2b43c1c1d2e6ff22191ba8838a499a9d80aa Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 1 Feb 2023 01:19:49 -0800 Subject: [PATCH 14/40] add key-only taproot script creation --- lib/script.ex | 27 +++++- lib/utils.ex | 2 + test/script_test.exs | 223 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+), 1 deletion(-) diff --git a/lib/script.ex b/lib/script.ex index 342a65d..05d5843 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -5,7 +5,7 @@ defmodule Bitcoinex.Script do import Bitcoinex.Opcode - alias Bitcoinex.Secp256k1.Point + alias Bitcoinex.Secp256k1.{Point, PrivateKey, Math} alias Bitcoinex.{Utils, Address, Segwit, Base58, Network} @@ -611,6 +611,31 @@ defmodule Bitcoinex.Script do def create_p2tr(q = %Point{}), do: create_witness_scriptpubkey(1, Point.x_bytes(q)) def create_p2tr(_), do: {:error, "public key must be #{@tapkey_length}-bytes"} + @spec create_p2tr_key_only(<<_::256>> | Point.t()) :: + {:ok, Bitcoinex.Script.t()} | {:error, String.t()} + def create_p2tr_key_only(<>) do + # Q = P + (int(hash_TapTweak(P)) * G) + fake_s = + pk + |> Utils.tagged_hash_taptweak() + |> :binary.decode_unsigned() + |> PrivateKey.new() + pk = Point.lift_x(pk) + + case {fake_s, pk} do + {{:error, msg}, _} -> {:error, msg} + {_, {:error, msg}} -> {:error, msg} + + {{:ok, fake_s}, {:ok, pk}} -> + fake_s_point = PrivateKey.to_point(fake_s) + pk + |> Math.add(fake_s_point) + |> create_p2tr() + end + end + def create_p2tr_key_only(p = %Point{}), do: create_p2tr_key_only(Point.x_bytes(p)) + + @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash """ diff --git a/lib/utils.ex b/lib/utils.ex index 54d5dbf..804391e 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -13,6 +13,8 @@ defmodule Bitcoinex.Utils do sha256(tag_hash <> tag_hash <> str) end + def tagged_hash_taptweak(pkx), do: tagged_hash("TapTweak", pkx) + @spec replicate(term(), integer()) :: list(term()) def replicate(_num, 0) do [] diff --git a/test/script_test.exs b/test/script_test.exs index d7072cb..77441da 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -162,6 +162,229 @@ defmodule Bitcoinex.ScriptTest do "bc1gmk9yu" ] + # from https://github.com/bitcoin/bips/blob/master/bip-0341/wallet-test-vectors.json + @bip_341_script_pubkey_test_vectors [ + %{ + given: %{ + internalPubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + scriptTree: nil + }, + intermediary: %{ + merkleRoot: nil, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweakedPubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + }, + expected: %{ + scriptPubKey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350Address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + } + }, + %{ + given: %{ + internalPubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + scriptTree: %{ + id: 0, + script: "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", + leafVersion: 192 + } + }, + intermediary: %{ + leafHashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkleRoot: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweakedPubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + }, + expected: %{ + scriptPubKey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350Address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + scriptPathControlBlocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] + } + }, + %{ + given: %{ + internalPubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + scriptTree: %{ + id: 0, + script: "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac", + leafVersion: 192 + } + }, + intermediary: %{ + leafHashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkleRoot: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweakedPubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + }, + expected: %{ + scriptPubKey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350Address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + scriptPathControlBlocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] + } + }, + %{ + given: %{ + internalPubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + scriptTree: [ + %{ + id: 0, + script: "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac", + leafVersion: 192 + }, + %{ + id: 1, + script: "06424950333431", + leafVersion: 250 + } + ] + }, + intermediary: %{ + leafHashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkleRoot: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweakedPubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + }, + expected: %{ + scriptPubKey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350Address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + scriptPathControlBlocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] + } + }, + %{ + given: %{ + internalPubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + scriptTree: [ + %{ + id: 0, + script: "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac", + leafVersion: 192 + }, + %{ + id: 1, + script: "07546170726f6f74", + leafVersion: 192 + } + ] + }, + intermediary: %{ + leafHashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkleRoot: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweakedPubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + }, + expected: %{ + scriptPubKey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350Address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + scriptPathControlBlocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] + } + }, + %{ + given: %{ + internalPubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + scriptTree: [ + %{ + id: 0, + script: "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac", + leafVersion: 192 + }, + [ + %{ + id: 1, + script: "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac", + leafVersion: 192 + }, + %{ + id: 2, + script: "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac", + leafVersion: 192 + } + ] + ] + }, + intermediary: %{ + leafHashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkleRoot: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweakedPubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + }, + expected: %{ + scriptPubKey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350Address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + scriptPathControlBlocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] + } + }, + %{ + given: %{ + internalPubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + scriptTree: [ + %{ + id: 0, + script: "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac", + leafVersion: 192 + }, + [ + %{ + id: 1, + script: "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac", + leafVersion: 192 + }, + %{ + id: 2, + script: "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac", + leafVersion: 192 + } + ] + ] + }, + intermediary: %{ + leafHashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkleRoot: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweakedPubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + }, + expected: %{ + scriptPubKey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350Address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + scriptPathControlBlocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] + } + } + ] + describe "test basics functions" do test "test new/0 and empty?/1" do s = Script.new() From f9054d262982658c0348d2eb569694ffd8d6e061 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Feb 2023 17:41:28 -0800 Subject: [PATCH 15/40] add Taproot.build_control_block and merkle tree handling --- lib/psbt.ex | 9 +- lib/script.ex | 147 +++++++------ lib/taproot.ex | 161 ++++++++++++++ lib/transaction.ex | 39 ++-- lib/utils.ex | 48 ++++- test/script_test.exs | 491 ++++++++++++++++++++++-------------------- test/taproot_test.exs | 288 +++++++++++++++++++++++++ 7 files changed, 845 insertions(+), 338 deletions(-) create mode 100644 lib/taproot.ex create mode 100644 test/taproot_test.exs diff --git a/lib/psbt.ex b/lib/psbt.ex index e0553f4..f3fdef4 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -12,6 +12,7 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Out alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t() :: %__MODULE__{} @@ -99,6 +100,7 @@ defmodule Bitcoinex.PSBT.Utils do Contains utility functions used throughout PSBT serialization. """ alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils def parse_compact_size_value(key_value) do {len, key_value} = TxUtils.get_counter(key_value) @@ -123,8 +125,8 @@ defmodule Bitcoinex.PSBT.Utils do end def serialize_kv(key, val) do - key_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(key)) - val_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(val)) + key_len = Utils.serialize_compact_size_unsigned_int(byte_size(key)) + val_len = Utils.serialize_compact_size_unsigned_int(byte_size(val)) key_len <> key <> val_len <> val end end @@ -258,6 +260,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils defstruct [ :non_witness_utxo, @@ -299,7 +302,7 @@ defmodule Bitcoinex.PSBT.In do val = <> <> - TxUtils.serialize_compact_size_unsigned_int(byte_size(script)) <> script + Utils.serialize_compact_size_unsigned_int(byte_size(script)) <> script PsbtUtils.serialize_kv(<<@psbt_in_witness_utxo::big-size(8)>>, val) end diff --git a/lib/script.ex b/lib/script.ex index 05d5843..cb17357 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -5,15 +5,18 @@ defmodule Bitcoinex.Script do import Bitcoinex.Opcode - alias Bitcoinex.Secp256k1.{Point, PrivateKey, Math} + alias Bitcoinex.Secp256k1.{Point, Math, PrivateKey} - alias Bitcoinex.{Utils, Address, Segwit, Base58, Network} + alias Bitcoinex.{Utils, Address, Segwit, Base58, Network, Taproot} @wsh_length 32 @tapkey_length 32 @h160_length 20 @pubkey_lengths [33, 65] + # hash of G.x, used to construct unsolvable internal taproot keys + @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 + @type script_type :: :p2pk | :p2pkh | :p2sh | :p2wpkh | :p2wsh | :p2tr | :multi | :non_standard @type t :: %__MODULE__{ @@ -25,7 +28,7 @@ defmodule Bitcoinex.Script do ] defstruct [:items] - defguard is_valid_multi(m, pubkeys) + defguard is_valid_multisig(m, pubkeys) when is_integer(m) and m > 0 and length(pubkeys) > 0 and length(pubkeys) >= m defp invalid_opcode_error(msg), do: {:error, "invalid opcode: #{msg}"} @@ -204,6 +207,11 @@ defmodule Bitcoinex.Script do serializer(script, <<>>) end + def serialize_with_compact_size(script = %__MODULE__{}) do + s = serialize_script(script) + Utils.serialize_compact_size_unsigned_int(byte_size(s)) <> s + end + @doc """ to_hex returns the hex of a serialized script. """ @@ -386,36 +394,36 @@ defmodule Bitcoinex.Script do def is_p2tr?(%__MODULE__{}), do: false @doc """ - is_multi? returns whether a given script is of the raw multisig format: + is_multisig? returns whether a given script is of the raw multisig format: OP_(INT) [Public Keys] OP_(INT) OP_CHECKMULTISIG """ - @spec is_multi?(t()) :: boolean - def is_multi?(%__MODULE__{items: [op_m | rest]}) + @spec is_multisig?(t()) :: boolean + def is_multisig?(%__MODULE__{items: [op_m | rest]}) when op_m > 0x50 and op_m <= 0x60 and length(rest) > 3 do - test_multi(rest, 0, op_m) + test_multisig(rest, 0, op_m) end - def is_multi?(_), do: false + def is_multisig?(_), do: false - defp test_multi([op_n, 0xAE], n, m) when op_n == 0x50 + n and m <= op_n, do: true + defp test_multisig([op_n, 0xAE], n, m) when op_n == 0x50 + n and m <= op_n, do: true - defp test_multi([op_push | [pk | rest]], n, m) when op_push in @pubkey_lengths do + defp test_multisig([op_push | [pk | rest]], n, m) when op_push in @pubkey_lengths do case Point.parse_public_key(pk) do - {:ok, _pk} -> test_multi(rest, n + 1, m) + {:ok, _pk} -> test_multisig(rest, n + 1, m) {:error, _msg} -> false end end - defp test_multi(_, _, _), do: false + defp test_multisig(_, _, _), do: false @doc """ - extract_multi_policy takes in a raw multisig script and returns the m, the + extract_multisig_policy takes in a raw multisig script and returns the m, the number of signatures required and the n authorized public keys. """ - @spec extract_multi_policy(t()) :: + @spec extract_multisig_policy(t()) :: {:ok, non_neg_integer(), list(Point.t())} | {:error, String.t()} - def extract_multi_policy(script = %__MODULE__{items: [op_m | items]}) do - if is_multi?(script) do + def extract_multisig_policy(script = %__MODULE__{items: [op_m | items]}) do + if is_multisig?(script) do {:ok, op_m - 0x50, extractor(items, [])} else {:error, "invalid raw multisig script"} @@ -445,7 +453,7 @@ defmodule Bitcoinex.Script do is_p2wsh?(script) -> :p2wsh is_p2pk?(script) -> :p2pk is_p2tr?(script) -> :p2tr - is_multi?(script) -> :multi + is_multisig?(script) -> :multi true -> :non_standard end end @@ -501,44 +509,44 @@ defmodule Bitcoinex.Script do end @doc """ - create_multi creates a raw multisig script using m and the list of public keys. + create_multisig creates a raw multisig script using m and the list of public keys. """ - @spec create_multi(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} - def create_multi(m, pubkeys) when is_valid_multi(m, pubkeys) do + @spec create_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} + def create_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do try do # checkmultisig {:ok, s} = push_op(new(), 0xAE) {:ok, s} = push_op(s, 0x50 + length(pubkeys)) - s = fill_multi_keys(s, pubkeys) + s = fill_multisig_keys(s, pubkeys) push_op(s, 0x50 + m) rescue _ -> {:error, "invalid public key."} end end - def create_multi(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} + def create_multisig(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} - defp fill_multi_keys(s, []), do: s + defp fill_multisig_keys(s, []), do: s - defp fill_multi_keys(s, [pk = %Point{} | pubkeys]) do - {:ok, s} = push_data(fill_multi_keys(s, pubkeys), Point.sec(pk)) + defp fill_multisig_keys(s, [pk = %Point{} | pubkeys]) do + {:ok, s} = push_data(fill_multisig_keys(s, pubkeys), Point.sec(pk)) s end - defp fill_multi_keys(_, _), do: raise(ArgumentError) + defp fill_multisig_keys(_, _), do: raise(ArgumentError) @doc """ - create_p2sh_multi returns both a P2SH-wrapped multisig script + create_p2sh_multisig returns both a P2SH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2sh_multi(non_neg_integer(), list(Point.t())) :: + @spec create_p2sh_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2sh_multi(m, pubkeys) do - case create_multi(m, pubkeys) do - {:ok, multi} -> - h160 = hash160(multi) + def create_p2sh_multisig(m, pubkeys) do + case create_multisig(m, pubkeys) do + {:ok, multisig} -> + h160 = hash160(multisig) {:ok, p2sh} = create_p2sh(h160) - {:ok, p2sh, multi} + {:ok, p2sh, multisig} {:error, msg} -> {:error, msg} @@ -546,17 +554,17 @@ defmodule Bitcoinex.Script do end @doc """ - create_p2wsh_multi returns both a P2WSH-wrapped multisig script + create_p2wsh_multisig returns both a P2WSH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2wsh_multi(non_neg_integer(), list(Point.t())) :: + @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2wsh_multi(m, pubkeys) do - case create_multi(m, pubkeys) do - {:ok, multi} -> - h256 = sha256(multi) + def create_p2wsh_multisig(m, pubkeys) do + case create_multisig(m, pubkeys) do + {:ok, multisig} -> + h256 = sha256(multisig) {:ok, p2wsh} = create_p2wsh(h256) - {:ok, p2wsh, multi} + {:ok, p2wsh, multisig} {:error, msg} -> {:error, msg} @@ -603,38 +611,37 @@ defmodule Bitcoinex.Script do @doc """ create_p2tr creates a p2tr script using the passed 32-byte public key - or Point. If a point is passed, it's interpreted as q, the full witness - program or taproot output key per BIP 341 rather than the keyspend pubkey. - """ - @spec create_p2tr(binary | Point.t()) :: {:ok, t()} - def create_p2tr(<>), do: create_witness_scriptpubkey(1, pk) - def create_p2tr(q = %Point{}), do: create_witness_scriptpubkey(1, Point.x_bytes(q)) - def create_p2tr(_), do: {:error, "public key must be #{@tapkey_length}-bytes"} - - @spec create_p2tr_key_only(<<_::256>> | Point.t()) :: - {:ok, Bitcoinex.Script.t()} | {:error, String.t()} - def create_p2tr_key_only(<>) do - # Q = P + (int(hash_TapTweak(P)) * G) - fake_s = - pk - |> Utils.tagged_hash_taptweak() - |> :binary.decode_unsigned() - |> PrivateKey.new() - pk = Point.lift_x(pk) - - case {fake_s, pk} do - {{:error, msg}, _} -> {:error, msg} - {_, {:error, msg}} -> {:error, msg} - - {{:ok, fake_s}, {:ok, pk}} -> - fake_s_point = PrivateKey.to_point(fake_s) - pk - |> Math.add(fake_s_point) - |> create_p2tr() - end + or Point. If a point is passed, it's interpreted as p, the internal key. + If only p is passed, the script_tree is assumed to be empty. + """ + @spec create_p2tr(<<_::256>> | Point.t(), Taproot.script_tree()) :: + {:ok, Bitcoinex.Script.t()} + def create_p2tr(p, script_tree \\ nil) + def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) + + def create_p2tr(<>, script_tree) do + {_, hash} = Taproot.merkelize_script_tree(script_tree) + {:ok, p} = Point.lift_x(px) + q = Taproot.tweak_pubkey(p, hash) + create_witness_scriptpubkey(1, Point.x_bytes(q)) end - def create_p2tr_key_only(p = %Point{}), do: create_p2tr_key_only(Point.x_bytes(p)) + @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: + {:ok, Bitcoinex.Script.t()} + def create_p2tr_script_only(script_tree, r) do + case PrivateKey.new(r) do + {:error, msg} -> + {:error, msg} + + {:ok, sk} -> + {:ok, hk} = Point.lift_x(@h) + + sk + |> PrivateKey.to_point() + |> Math.add(hk) + |> create_p2tr(script_tree) + end + end @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash diff --git a/lib/taproot.ex b/lib/taproot.ex new file mode 100644 index 0000000..168c87e --- /dev/null +++ b/lib/taproot.ex @@ -0,0 +1,161 @@ +defmodule Bitcoinex.Taproot do + alias Bitcoinex.Utils + + alias Bitcoinex.Secp256k1 + alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} + + @n Params.curve().n + + # @bip342_leaf_version 0xc0 + + @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil + + @spec tweak_privkey(PrivateKey.t(), binary) :: PrivateKey.t() | {:error, String.t()} + def tweak_privkey(sk0 = %PrivateKey{}, h) do + sk = Secp256k1.force_even_y(sk0) + + case PrivateKey.to_point(sk) do + {:error, msg} -> + {:error, msg} + + pk -> + t = calculate_taptweak(pk, h) + + if t > @n do + {:error, "invalid tweaked key"} + else + %PrivateKey{d: Math.modulo(sk.d + t, @n)} + end + end + end + + @spec tweak_pubkey(Point.t(), binary) :: Point.t() | {:error, String.t()} + def tweak_pubkey(pk = %Point{}, h) do + t = calculate_taptweak(pk, h) + + if t > @n do + {:error, "invalid tweaked key"} + else + t_point = PrivateKey.to_point(t) + Math.add(pk, t_point) + end + end + + @spec calculate_taptweak(Point.t(), binary) :: non_neg_integer + def calculate_taptweak(pk = %Point{}, h) do + pk + |> Point.x_bytes() + |> Kernel.<>(h) + |> tagged_hash_taptweak() + |> :binary.decode_unsigned() + end + + @spec tagged_hash_tapbranch(binary) :: binary + def tagged_hash_tapbranch(br), do: Utils.tagged_hash("TapBranch", br) + + @spec tagged_hash_taptweak(binary) :: binary + def tagged_hash_taptweak(root), do: Utils.tagged_hash("TapTweak", root) + + @spec tagged_hash_tapleaf(binary) :: binary + def tagged_hash_tapleaf(leaf), do: Utils.tagged_hash("TapLeaf", leaf) + + defmodule TapLeaf do + alias Bitcoinex.Script + alias Bitcoinex.Taproot + + @type t :: %__MODULE__{ + version: non_neg_integer(), + script: binary + } + @enforce_keys [ + :version, + :script + ] + defstruct [ + :version, + :script + ] + + @doc """ + from_script constructs a TapLeaf from a leaf_version and Script. + The script is stored as binary with the compact size prepended to it. + """ + @spec from_script(non_neg_integer(), Script.t()) :: t() + def from_script(leaf_version, script = %Script{}) do + s = Script.serialize_with_compact_size(script) + %__MODULE__{version: leaf_version, script: s} + end + + @spec from_string(non_neg_integer(), String.t()) :: t() + def from_string(leaf_version, script_hex) do + {:ok, script} = Script.parse_script(script_hex) + from_script(leaf_version, script) + end + + @doc """ + serialize returns a binary concatenation of the leaf_version and Script. TapLeaf structs + store the Script in binary and alredy prepended with the compact size, so that is not added here. + """ + @spec serialize(t()) :: binary + def serialize(%__MODULE__{version: v, script: s}), do: :binary.encode_unsigned(v) <> s + + @doc """ + hash returns the Hash_TapLeaf of the serialized TapLeaf + """ + @spec hash(t()) :: <<_::256>> + def hash(tapleaf = %__MODULE__{}), do: serialize(tapleaf) |> Taproot.tagged_hash_tapleaf() + end + + @typedoc """ + script_tree represents a Taproot Script Merkle Tree. Leaves are represented by TapLeaf structs + while branches are {script_tree, script_tree}. Since we sort based on hash at each level, + left vs right branches are irrelevant. An empty tree is represented by nil. + """ + @type script_tree :: TapLeaf.t() | {script_tree(), script_tree()} | nil + + @doc """ + merkelize_script_tree takes a script_tree (either nil, a TapLeaf, or a tuple of two script_trees) + and constructs the root node. It returns {root_node, hash}. The hash is nil if the script_tree is empty. + defined in BIP341 https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + """ + @spec merkelize_script_tree(script_tree()) :: {list({TapLeaf.t(), binary}), binary} + def merkelize_script_tree(nil), do: {nil, <<>>} + + def merkelize_script_tree(leaf = %TapLeaf{}) do + hash = TapLeaf.hash(leaf) + + {[{leaf, <<>>}], hash} + end + + def merkelize_script_tree({left, right}) do + {{l_branches, l_hash}, {r_branches, r_hash}} = + {merkelize_script_tree(left), merkelize_script_tree(right)} + + # cross-mix the right hash with left branch and left hash with right branch + new_left = merge_branches(l_branches, r_hash) + new_right = merge_branches(r_branches, l_hash) + + node = new_left ++ new_right + + # combine the branches to form root node. + {l_hash, r_hash} = Utils.lexicographical_sort(l_hash, r_hash) + hash = tagged_hash_tapbranch(l_hash <> r_hash) + {node, hash} + end + + defp merge_branches([], _), do: [] + + defp merge_branches([{leaf, c} | tail], hash) do + [{leaf, c <> hash} | merge_branches(tail, hash)] + end + + @spec build_control_block(Point.t(), script_tree(), non_neg_integer()) :: binary + def build_control_block(p = %Point{}, script_tree, script_index) do + {tree, hash} = merkelize_script_tree(script_tree) + {tapleaf, merkle_path} = Enum.at(tree, script_index) + q = tweak_pubkey(p, hash) + q_parity = if Point.has_even_y(q), do: 0, else: 1 + + <> <> Point.x_bytes(p) <> merkle_path + end +end diff --git a/lib/transaction.ex b/lib/transaction.ex index 1956d03..8817a10 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -114,6 +114,7 @@ defmodule Bitcoinex.Transaction.Utils do alias Bitcoinex.Transaction.In alias Bitcoinex.Transaction.Out alias Bitcoinex.Transaction.Witness + alias Bitcoinex.Utils @doc """ Returns the Variable Length Integer used in serialization. @@ -149,9 +150,9 @@ defmodule Bitcoinex.Transaction.Utils do version = <> marker = <<0x00::big-size(8)>> flag = <<0x01::big-size(8)>> - tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs)) + tx_in_count = Utils.serialize_compact_size_unsigned_int(length(txn.inputs)) inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary() - tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs)) + tx_out_count = Utils.serialize_compact_size_unsigned_int(length(txn.outputs)) outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary() witness = Witness.serialize_witness(txn.witnesses) lock_time = <> @@ -162,33 +163,14 @@ defmodule Bitcoinex.Transaction.Utils do def serialize(txn) do version = <> - tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs)) + tx_in_count = Utils.serialize_compact_size_unsigned_int(length(txn.inputs)) inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary() - tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs)) + tx_out_count = Utils.serialize_compact_size_unsigned_int(length(txn.outputs)) outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary() lock_time = <> version <> tx_in_count <> inputs <> tx_out_count <> outputs <> lock_time end - - @doc """ - Returns the serialized variable length integer. - """ - def serialize_compact_size_unsigned_int(compact_size) do - cond do - compact_size >= 0 and compact_size <= 0xFC -> - <> - - compact_size <= 0xFFFF -> - <<0xFD>> <> <> - - compact_size <= 0xFFFFFFFF -> - <<0xFE>> <> <> - - compact_size <= 0xFF -> - <<0xFF>> <> <> - end - end end defmodule Bitcoinex.Transaction.Witness do @@ -197,6 +179,7 @@ defmodule Bitcoinex.Transaction.Witness do """ alias Bitcoinex.Transaction.Witness alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ txinwitness: list(binary()) @@ -237,12 +220,12 @@ defmodule Bitcoinex.Transaction.Witness do if Enum.empty?(witness.txinwitness) do <<0x0::big-size(8)>> else - stack_len = TxUtils.serialize_compact_size_unsigned_int(length(witness.txinwitness)) + stack_len = Utils.serialize_compact_size_unsigned_int(length(witness.txinwitness)) field = Enum.reduce(witness.txinwitness, <<>>, fn v, acc -> {:ok, item} = Base.decode16(v, case: :lower) - item_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(item)) + item_len = Utils.serialize_compact_size_unsigned_int(byte_size(item)) acc <> item_len <> item end) @@ -295,6 +278,7 @@ defmodule Bitcoinex.Transaction.In do """ alias Bitcoinex.Transaction.In alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ prev_txid: binary(), @@ -330,7 +314,7 @@ defmodule Bitcoinex.Transaction.In do {:ok, script_sig} = Base.decode16(input.script_sig, case: :lower) - script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_sig)) + script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_sig)) serialized_input = [ prev_txid, @@ -377,6 +361,7 @@ defmodule Bitcoinex.Transaction.Out do """ alias Bitcoinex.Transaction.Out alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ value: non_neg_integer(), @@ -400,7 +385,7 @@ defmodule Bitcoinex.Transaction.Out do {:ok, script_pub_key} = Base.decode16(output.script_pub_key, case: :lower) - script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) + script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) serialized_output = [<>, script_len, script_pub_key] serialize_output(outputs, [serialized_outputs, serialized_output]) diff --git a/lib/utils.ex b/lib/utils.ex index 804391e..22137c5 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -13,8 +13,6 @@ defmodule Bitcoinex.Utils do sha256(tag_hash <> tag_hash <> str) end - def tagged_hash_taptweak(pkx), do: tagged_hash("TapTweak", pkx) - @spec replicate(term(), integer()) :: list(term()) def replicate(_num, 0) do [] @@ -109,4 +107,50 @@ defmodule Bitcoinex.Utils do |> Enum.map(fn {b0, b1} -> Bitwise.bxor(b0, b1) end) |> :binary.list_to_bin() end + + # ascending order + def lexicographical_sort(bin0, bin1) when is_binary(bin0) and is_binary(bin1) do + if lexicographical_sort(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do + {bin0, bin1} + else + {bin1, bin0} + end + end + + # equality case + def lexicographical_sort([], []), do: true + + def lexicographical_sort([b0 | r0], [b1 | r1]) do + cond do + b0 == b1 -> + lexicographical_sort(r0, r1) + + b1 < b0 -> + # initial order was incorrect, must be swapped + false + + true -> + # bin0, bin1 was the correct order + true + end + end + + @doc """ + Returns the serialized variable length integer. + """ + def serialize_compact_size_unsigned_int(compact_size) do + cond do + compact_size >= 0 and compact_size <= 0xFC -> + <> + + compact_size <= 0xFFFF -> + <<0xFD>> <> <> + + compact_size <= 0xFFFFFFFF -> + <<0xFE>> <> <> + + compact_size <= 0xFF -> + <<0xFF>> <> <> + end + end end diff --git a/test/script_test.exs b/test/script_test.exs index 77441da..9f2dad6 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -4,6 +4,7 @@ defmodule Bitcoinex.ScriptTest do alias Bitcoinex.{Script, Utils} alias Bitcoinex.Secp256k1.Point + alias Bitcoinex.Taproot @raw_multisig_scripts [ "522103a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af957521036ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640d210311ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef53ae", @@ -166,226 +167,249 @@ defmodule Bitcoinex.ScriptTest do @bip_341_script_pubkey_test_vectors [ %{ given: %{ - internalPubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", - scriptTree: nil + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + script_tree: nil }, intermediary: %{ - merkleRoot: nil, - tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", - tweakedPubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + merkle_root: nil, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_pubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" }, expected: %{ - scriptPubKey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", - bip350Address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + script_pubkey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350_address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" } - }, - %{ + }, + %{ given: %{ - internalPubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", - scriptTree: %{ - id: 0, - script: "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", - leafVersion: 192 - } + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" + ) }, intermediary: %{ - leafHashes: [ - "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" - ], - merkleRoot: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", - tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", - tweakedPubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + leaf_hashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_pubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" }, expected: %{ - scriptPubKey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - bip350Address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", - scriptPathControlBlocks: [ - "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" - ] + script_pubkey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350_address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + script_path_control_blocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", - scriptTree: %{ - id: 0, - script: "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac", - leafVersion: 192 - } + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac" + ) }, intermediary: %{ - leafHashes: [ - "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" - ], - merkleRoot: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", - tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", - tweakedPubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + leaf_hashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_pubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" }, expected: %{ - scriptPubKey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", - bip350Address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", - scriptPathControlBlocks: [ - "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" - ] + script_pubkey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350_address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + script_path_control_blocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", - scriptTree: [ - %{ - id: 0, - script: "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac", - leafVersion: 192 - }, - %{ - id: 1, - script: "06424950333431", - leafVersion: 250 - } - ] + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 250, + # script + "06424950333431" + ) + } }, intermediary: %{ - leafHashes: [ - "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", - "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" - ], - merkleRoot: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", - tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", - tweakedPubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + leaf_hashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_pubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" }, expected: %{ - scriptPubKey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", - bip350Address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", - scriptPathControlBlocks: [ - "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", - "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" - ] + script_pubkey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350_address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + script_path_control_blocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", - scriptTree: [ - %{ - id: 0, - script: "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac", - leafVersion: 192 - }, - %{ - id: 1, - script: "07546170726f6f74", - leafVersion: 192 - } - ] + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "07546170726f6f74" + ) + } }, intermediary: %{ - leafHashes: [ - "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", - "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" - ], - merkleRoot: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", - tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", - tweakedPubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + leaf_hashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_pubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" }, expected: %{ - scriptPubKey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", - bip350Address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", - scriptPathControlBlocks: [ - "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", - "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" - ] + script_pubkey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350_address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + script_path_control_blocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", - scriptTree: [ - %{ - id: 0, - script: "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac", - leafVersion: 192 - }, - [ - %{ - id: 1, - script: "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac", - leafVersion: 192 - }, - %{ - id: 2, - script: "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac", - leafVersion: 192 - } - ] - ] + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + 192, + # script + "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + 192, + # script + "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + 192, + # script + "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac" + ) + } + } }, intermediary: %{ - leafHashes: [ - "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", - "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", - "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" - ], - merkleRoot: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", - tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", - tweakedPubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + leaf_hashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_pubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" }, expected: %{ - scriptPubKey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", - bip350Address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", - scriptPathControlBlocks: [ - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" - ] + script_pubkey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350_address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + script_path_control_blocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", - scriptTree: [ - %{ - id: 0, - script: "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac", - leafVersion: 192 - }, - [ - %{ - id: 1, - script: "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac", - leafVersion: 192 - }, - %{ - id: 2, - script: "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac", - leafVersion: 192 - } - ] - ] + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + # version + 192, + # script + "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac" + ) + } + } }, intermediary: %{ - leafHashes: [ - "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", - "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", - "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" - ], - merkleRoot: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", - tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", - tweakedPubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + leaf_hashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_pubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" }, expected: %{ - scriptPubKey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", - bip350Address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", - scriptPathControlBlocks: [ - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" - ] + script_pubkey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350_address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + script_path_control_blocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] } - } + } ] - describe "test basics functions" do + describe "test basic functions" do test "test new/0 and empty?/1" do s = Script.new() assert Script.empty?(s) @@ -720,35 +744,14 @@ defmodule Bitcoinex.ScriptTest do assert p2wpkh == p2wpkh2 end - test "test create_p2tr" do - s = Script.new() - script_hex = "51200101010101010101010101010101010101010101010101010101010101010101" - pubkey_hex = "0101010101010101010101010101010101010101010101010101010101010101" - bin = Base.decode16!(pubkey_hex, case: :lower) - - {:ok, p2tr} = Script.push_data(s, bin) - {:ok, p2tr} = Script.push_op(p2tr, 0x51) - assert Script.is_p2tr?(p2tr) - - s_hex = Script.to_hex(p2tr) - assert s_hex == script_hex - - {:ok, p2tr_from_bin} = Script.create_p2tr(bin) - assert Script.to_hex(p2tr_from_bin) == script_hex - - {:ok, pubkey} = Point.lift_x(bin) - {:ok, p2tr_from_pk} = Script.create_p2tr(pubkey) - assert Script.to_hex(p2tr_from_pk) == script_hex - end - - test "test is_multi?" do + test "test is_multisig?" do for ms <- @raw_multisig_scripts do {:ok, multi} = Script.parse_script(ms) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) end end - test "test create_multi" do + test "test create_multisig" do # these keys will be added to multisig as compressed keys {:ok, pk1} = Point.parse_public_key( @@ -765,8 +768,8 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multisig} = Script.create_multisig(2, [pk1, pk2, pk3]) + assert Script.is_multisig?(multisig) {:ok, pk1} = Point.parse_public_key( @@ -783,24 +786,24 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, multi} = Script.create_multi(1, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(1, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) - {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(2, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) - {:ok, multi} = Script.create_multi(3, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(3, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_multi(0, []) + {:error, _msg} = Script.create_multisig(0, []) end - test "test create_p2sh_multi" do + test "test create_p2sh_multisig" do {:ok, pk1} = Point.parse_public_key( "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd" @@ -816,9 +819,9 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3]) + {:ok, p2sh, multi} = Script.create_p2sh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2sh?(p2sh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) {:ok, pk1} = Point.parse_public_key( @@ -835,19 +838,19 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3]) + {:ok, p2sh, multi} = Script.create_p2sh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2sh?(p2sh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2sh_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2sh_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_p2sh_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2sh_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2sh_multi(0, []) + {:error, _msg} = Script.create_p2sh_multisig(0, []) end - test "test create_p2wsh_multi" do + test "test create_p2wsh_multisig" do {:ok, pk1} = Point.parse_public_key( "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd" @@ -863,9 +866,9 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3]) + {:ok, p2wsh, multi} = Script.create_p2wsh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2wsh?(p2wsh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) {:ok, pk1} = Point.parse_public_key( @@ -882,16 +885,16 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3]) + {:ok, p2wsh, multi} = Script.create_p2wsh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2wsh?(p2wsh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2wsh_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2wsh_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_p2wsh_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2wsh_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2wsh_multi(0, []) + {:error, _msg} = Script.create_p2wsh_multisig(0, []) end test "test create scripts from pubkey" do @@ -1233,7 +1236,7 @@ defmodule Bitcoinex.ScriptTest do test "test serialize/parse multisig scripts" do for ms <- @raw_multisig_scripts do {:ok, multi} = Script.parse_script(ms) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) assert Script.to_hex(multi) == ms end end @@ -1380,10 +1383,10 @@ defmodule Bitcoinex.ScriptTest do test "test raw multisig to address" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) - {:ok, m, pks} = Script.extract_multi_policy(ms) + {:ok, m, pks} = Script.extract_multisig_policy(ms) if multi.sh_addr != "" do - {:ok, p2sh, multi_script} = Script.create_p2sh_multi(m, pks) + {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks) {:ok, addr} = Script.to_address(p2sh, multi.network) @@ -1392,7 +1395,7 @@ defmodule Bitcoinex.ScriptTest do end if multi.wsh_addr != "" do - {:ok, p2wsh, multi_script} = Script.create_p2wsh_multi(m, pks) + {:ok, p2wsh, multi_script} = Script.create_p2wsh_multisig(m, pks) {:ok, addr} = Script.to_address(p2wsh, multi.network) @@ -1530,16 +1533,16 @@ defmodule Bitcoinex.ScriptTest do test "extract policy from multisig script" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) - {:ok, m, pks} = Script.extract_multi_policy(ms) - {:ok, ms2} = Script.create_multi(m, pks) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + {:ok, ms2} = Script.create_multisig(m, pks) assert ms == ms2 end for m <- @raw_multisig_scripts do {:ok, ms} = Script.parse_script(m) - {:ok, m, pks} = Script.extract_multi_policy(ms) - {:ok, ms2} = Script.create_multi(m, pks) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + {:ok, ms2} = Script.create_multisig(m, pks) assert ms == ms2 end @@ -1588,4 +1591,20 @@ defmodule Bitcoinex.ScriptTest do assert Bitcoinex.Secp256k1.Point.sec(pubkey3) == pk3 end end + + describe "test bip341 create_p2tr" do + test "test bip341 test vectors" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, p} = Point.lift_x(t.given.internal_pubkey) + # TODO move to Taproot_test + # {_node, hash} = Taproot.merkelize_script_tree(t.script_tree) + # assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root + # TODO check tweak & tweak_pubkey + + {:ok, script} = Script.create_p2tr(p, t.given.script_tree) + assert Script.to_hex(script) == t.expected.script_pubkey + assert Script.to_address(script, :mainnet) == {:ok, t.expected.bip350_address} + end + end + end end diff --git a/test/taproot_test.exs b/test/taproot_test.exs new file mode 100644 index 0000000..11640ef --- /dev/null +++ b/test/taproot_test.exs @@ -0,0 +1,288 @@ +defmodule TaprootTest do + use ExUnit.Case + doctest Bitcoinex.Script + + alias Bitcoinex.Taproot + alias Bitcoinex.Secp256k1.Point + + @bip_341_script_pubkey_test_vectors [ + %{ + given: %{ + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + script_tree: nil + }, + intermediary: %{ + merkle_root: <<>>, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_pubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + }, + expected: %{ + script_pubkey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350_address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + } + }, + %{ + given: %{ + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" + ) + }, + intermediary: %{ + leaf_hashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_pubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + }, + expected: %{ + script_pubkey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350_address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + script_path_control_blocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] + } + }, + %{ + given: %{ + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac" + ) + }, + intermediary: %{ + leaf_hashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_pubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + }, + expected: %{ + script_pubkey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350_address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + script_path_control_blocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] + } + }, + %{ + given: %{ + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 250, + # script + "06424950333431" + ) + } + }, + intermediary: %{ + leaf_hashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_pubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + }, + expected: %{ + script_pubkey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350_address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + script_path_control_blocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] + } + }, + %{ + given: %{ + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "07546170726f6f74" + ) + } + }, + intermediary: %{ + leaf_hashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_pubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + }, + expected: %{ + script_pubkey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350_address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + script_path_control_blocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] + } + }, + %{ + given: %{ + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + 192, + # script + "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + 192, + # script + "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + 192, + # script + "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac" + ) + } + } + }, + intermediary: %{ + leaf_hashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_pubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + }, + expected: %{ + script_pubkey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350_address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + script_path_control_blocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] + } + }, + %{ + given: %{ + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + # version + 192, + # script + "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac" + ) + } + } + }, + intermediary: %{ + leaf_hashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_pubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + }, + expected: %{ + script_pubkey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350_address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + script_path_control_blocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] + } + } + ] + + describe "taproot script_tree" do + test "calculate_taptweak/2 & tweak_pubkey" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, pk} = Point.lift_x(t.given.internal_pubkey) + {_, hash} = Taproot.merkelize_script_tree(t.given.script_tree) + tweak = Taproot.calculate_taptweak(pk, hash) + tweak_hex = tweak |> :binary.encode_unsigned() |> Base.encode16(case: :lower) + assert tweak_hex == t.intermediary.tweak + + tweaked_pubkey = Taproot.tweak_pubkey(pk, hash) + tweaked_pubkey_hex = tweaked_pubkey |> Point.x_bytes() |> Base.encode16(case: :lower) + assert tweaked_pubkey_hex == t.intermediary.tweaked_pubkey + end + end + + test "merkelize_script_tree/1" do + for t <- @bip_341_script_pubkey_test_vectors do + {_, hash} = Taproot.merkelize_script_tree(t.given.script_tree) + assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root + end + end + + test "build_control_block/3" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, pk} = Point.lift_x(t.given.internal_pubkey) + + unless t.given.script_tree == nil do + for {c_control_block, idx} <- Enum.with_index(t.expected.script_path_control_blocks) do + control_block = Taproot.build_control_block(pk, t.given.script_tree, idx) + assert Base.encode16(control_block, case: :lower) == c_control_block + end + end + end + end + end +end From 7f34d8094d4ca2cb27a23fd578900bee3a4f7576 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Feb 2023 17:56:04 -0800 Subject: [PATCH 16/40] improve create_p2tr signature --- lib/script.ex | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index cb17357..ff377e2 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -614,9 +614,10 @@ defmodule Bitcoinex.Script do or Point. If a point is passed, it's interpreted as p, the internal key. If only p is passed, the script_tree is assumed to be empty. """ - @spec create_p2tr(<<_::256>> | Point.t(), Taproot.script_tree()) :: - {:ok, Bitcoinex.Script.t()} - def create_p2tr(p, script_tree \\ nil) + @spec create_p2tr(<<_::256>> | Point.t() | nil, Taproot.script_tree()) :: + {:ok, Bitcoinex.Script.t()} | {:ok, Bitcoinex.Script.t(), non_neg_integer()} | {:error, String.t()} + def create_p2tr(p \\ nil, script_tree \\ nil) + def create_p2tr(nil, nil), do: {:error, "script_tree or internal pubkey must be non-nil"} def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) def create_p2tr(<>, script_tree) do @@ -625,9 +626,16 @@ defmodule Bitcoinex.Script do q = Taproot.tweak_pubkey(p, hash) create_witness_scriptpubkey(1, Point.x_bytes(q)) end + def create_p2tr(nil, script_tree) do + r = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + create_p2tr_script_only(script_tree, r) + end @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: - {:ok, Bitcoinex.Script.t()} + {:ok, Bitcoinex.Script.t(), non_neg_integer()} def create_p2tr_script_only(script_tree, r) do case PrivateKey.new(r) do {:error, msg} -> @@ -636,10 +644,13 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) + {:ok, script} = sk |> PrivateKey.to_point() |> Math.add(hk) |> create_p2tr(script_tree) + + {:ok, script, r} end end From ad34bf6845f2f4aad47fa783d189633438baada6 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 02:30:38 -0800 Subject: [PATCH 17/40] finish bip341 sighash & tx signing --- lib/script.ex | 18 +- lib/segwit.ex | 4 +- lib/taproot.ex | 100 ++++++- lib/tapscript.ex | 125 +++++++++ lib/transaction.ex | 272 +++++++++++++++++- lib/utils.ex | 27 +- scripts/use_taproot_addr.exs | 92 ++++++ test/segwit_test.exs | 6 +- test/transaction_test.exs | 528 +++++++++++++++++++++++++++++++++++ 9 files changed, 1131 insertions(+), 41 deletions(-) create mode 100644 lib/tapscript.ex create mode 100644 scripts/use_taproot_addr.exs diff --git a/lib/script.ex b/lib/script.ex index ff377e2..666d2b7 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -615,7 +615,9 @@ defmodule Bitcoinex.Script do If only p is passed, the script_tree is assumed to be empty. """ @spec create_p2tr(<<_::256>> | Point.t() | nil, Taproot.script_tree()) :: - {:ok, Bitcoinex.Script.t()} | {:ok, Bitcoinex.Script.t(), non_neg_integer()} | {:error, String.t()} + {:ok, Bitcoinex.Script.t()} + | {:ok, Bitcoinex.Script.t(), non_neg_integer()} + | {:error, String.t()} def create_p2tr(p \\ nil, script_tree \\ nil) def create_p2tr(nil, nil), do: {:error, "script_tree or internal pubkey must be non-nil"} def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) @@ -626,11 +628,13 @@ defmodule Bitcoinex.Script do q = Taproot.tweak_pubkey(p, hash) create_witness_scriptpubkey(1, Point.x_bytes(q)) end + def create_p2tr(nil, script_tree) do r = 32 |> :crypto.strong_rand_bytes() |> :binary.decode_unsigned() + create_p2tr_script_only(script_tree, r) end @@ -644,13 +648,13 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) - {:ok, script} = - sk - |> PrivateKey.to_point() - |> Math.add(hk) - |> create_p2tr(script_tree) + {:ok, script} = + sk + |> PrivateKey.to_point() + |> Math.add(hk) + |> create_p2tr(script_tree) - {:ok, script, r} + {:ok, script, r} end end diff --git a/lib/segwit.ex b/lib/segwit.ex index 1ebeb33..d9c28f0 100644 --- a/lib/segwit.ex +++ b/lib/segwit.ex @@ -85,8 +85,8 @@ defmodule Bitcoinex.Segwit do @doc """ Simpler Interface to check if address is valid """ - @spec is_valid_segswit_address?(String.t()) :: boolean - def is_valid_segswit_address?(address) when is_binary(address) do + @spec is_valid_segwit_address?(String.t()) :: boolean + def is_valid_segwit_address?(address) when is_binary(address) do case decode_address(address) do {:ok, _} -> true diff --git a/lib/taproot.ex b/lib/taproot.ex index 168c87e..85074f8 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -1,7 +1,7 @@ defmodule Bitcoinex.Taproot do alias Bitcoinex.Utils - alias Bitcoinex.Secp256k1 + alias Bitcoinex.{Secp256k1, Script} alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} @n Params.curve().n @@ -50,15 +50,18 @@ defmodule Bitcoinex.Taproot do |> :binary.decode_unsigned() end - @spec tagged_hash_tapbranch(binary) :: binary + @spec tagged_hash_tapbranch(binary) :: <<_::256>> def tagged_hash_tapbranch(br), do: Utils.tagged_hash("TapBranch", br) - @spec tagged_hash_taptweak(binary) :: binary + @spec tagged_hash_taptweak(binary) :: <<_::256>> def tagged_hash_taptweak(root), do: Utils.tagged_hash("TapTweak", root) - @spec tagged_hash_tapleaf(binary) :: binary + @spec tagged_hash_tapleaf(binary) :: <<_::256>> def tagged_hash_tapleaf(leaf), do: Utils.tagged_hash("TapLeaf", leaf) + @spec tagged_hash_tapsighash(binary) :: <<_::256>> + def tagged_hash_tapsighash(sigmsg), do: Utils.tagged_hash("TapSighash", sigmsg) + defmodule TapLeaf do alias Bitcoinex.Script alias Bitcoinex.Taproot @@ -132,8 +135,8 @@ defmodule Bitcoinex.Taproot do {merkelize_script_tree(left), merkelize_script_tree(right)} # cross-mix the right hash with left branch and left hash with right branch - new_left = merge_branches(l_branches, r_hash) - new_right = merge_branches(r_branches, l_hash) + new_left = merkelize_branches(l_branches, r_hash) + new_right = merkelize_branches(r_branches, l_hash) node = new_left ++ new_right @@ -143,10 +146,10 @@ defmodule Bitcoinex.Taproot do {node, hash} end - defp merge_branches([], _), do: [] + defp merkelize_branches([], _), do: [] - defp merge_branches([{leaf, c} | tail], hash) do - [{leaf, c <> hash} | merge_branches(tail, hash)] + defp merkelize_branches([{leaf, c} | tail], hash) do + [{leaf, c <> hash} | merkelize_branches(tail, hash)] end @spec build_control_block(Point.t(), script_tree(), non_neg_integer()) :: binary @@ -158,4 +161,83 @@ defmodule Bitcoinex.Taproot do <> <> Point.x_bytes(p) <> merkle_path end + + # Should this take a Script or binary script + @spec merkelize_control_block(<<_::256>>, binary) :: any + def merkelize_control_block(<>, path) do + # Consume each 32-byte chunk of the rest of the control path, which are hashes of the merkle tree + path + |> :binary.bin_to_list() + |> Enum.chunk_every(32) + |> Enum.reduce(k0, fn e, k -> merkelize_path(e, k) end) + end + + defp merkelize_path(<>, <>) do + {l, r} = Utils.lexicographical_sort(e, k) + {:cont, tagged_hash_tapbranch(l <> r)} + end + + @spec validate_taproot_scriptpath_spend(Point.t(), binary, binary) :: + bool | {:error, String.t()} + def validate_taproot_scriptpath_spend( + q_point = %Point{}, + script, + <> <> <> <> path + ) do + leaf_version = extract_leaf_version(c) + + k0 = + tagged_hash_tapleaf( + leaf_version <> Utils.serialize_compact_size_unsigned_int(byte_size(script)) <> script + ) + + k = merkelize_control_block(k0, path) + # t is tweak + t = tagged_hash_taptweak(p <> k) |> :binary.decode_unsigned() + + case {PrivateKey.to_point(t), Point.lift_x(p)} do + {{:error, _}, _} -> + {:error, "control block yielded invalid tweak"} + + {_, {:error, _}} -> + {:error, "failed to parse point Q"} + + {tk, {:ok, pk}} -> + validate_q(q_point, Math.add(pk, tk), c) + # TODO MUST EVALUATE THE ACTUAL SCRIPT + end + end + + defp validate_q(given_q = %Point{}, calculated_q = %Point{}, <>) do + q_parity = extract_q_parity(c) + + cond do + q_parity != Point.has_even_y(given_q) -> + {:error, "incorrect Q parity"} + + given_q.x != calculated_q.x -> + {:error, "Q points do not match"} + + true -> + true + end + end + + @spec extract_leaf_version(<<_::8>>) :: binary + defp extract_leaf_version(<>) do + c + |> :binary.decode_unsigned() + |> Bitwise.band(0xFE) + |> :binary.encode_unsigned() + end + + @spec extract_q_parity(<<_::8>>) :: bool + defp extract_q_parity(<>) do + q_mod2 = + c + |> :binary.decode_unsigned() + |> Bitwise.band(1) + + q_mod2 == 0 + end end diff --git a/lib/tapscript.ex b/lib/tapscript.ex new file mode 100644 index 0000000..fa1b022 --- /dev/null +++ b/lib/tapscript.ex @@ -0,0 +1,125 @@ +# defmodule Tapscript do + +# defmodule Tapscript.Opcode do +# # BIP 342 opcodes (Tapscript) +# OP_CHECKSIGADD = 0xba + +# OP_INVALIDOPCODE = 0xff + +# OPCODE_NAMES.update({ +# OP_0: 'OP_0', +# OP_PUSHDATA1: 'OP_PUSHDATA1', +# OP_PUSHDATA2: 'OP_PUSHDATA2', +# OP_PUSHDATA4: 'OP_PUSHDATA4', +# OP_1NEGATE: 'OP_1NEGATE', +# OP_RESERVED: 'OP_RESERVED', +# OP_1: 'OP_1', +# OP_2: 'OP_2', +# OP_3: 'OP_3', +# OP_4: 'OP_4', +# OP_5: 'OP_5', +# OP_6: 'OP_6', +# OP_7: 'OP_7', +# OP_8: 'OP_8', +# OP_9: 'OP_9', +# OP_10: 'OP_10', +# OP_11: 'OP_11', +# OP_12: 'OP_12', +# OP_13: 'OP_13', +# OP_14: 'OP_14', +# OP_15: 'OP_15', +# OP_16: 'OP_16', +# OP_NOP: 'OP_NOP', +# OP_VER: 'OP_VER', +# OP_IF: 'OP_IF', +# OP_NOTIF: 'OP_NOTIF', +# OP_VERIF: 'OP_VERIF', +# OP_VERNOTIF: 'OP_VERNOTIF', +# OP_ELSE: 'OP_ELSE', +# OP_ENDIF: 'OP_ENDIF', +# OP_VERIFY: 'OP_VERIFY', +# OP_RETURN: 'OP_RETURN', +# OP_TOALTSTACK: 'OP_TOALTSTACK', +# OP_FROMALTSTACK: 'OP_FROMALTSTACK', +# OP_2DROP: 'OP_2DROP', +# OP_2DUP: 'OP_2DUP', +# OP_3DUP: 'OP_3DUP', +# OP_2OVER: 'OP_2OVER', +# OP_2ROT: 'OP_2ROT', +# OP_2SWAP: 'OP_2SWAP', +# OP_IFDUP: 'OP_IFDUP', +# OP_DEPTH: 'OP_DEPTH', +# OP_DROP: 'OP_DROP', +# OP_DUP: 'OP_DUP', +# OP_NIP: 'OP_NIP', +# OP_OVER: 'OP_OVER', +# OP_PICK: 'OP_PICK', +# OP_ROLL: 'OP_ROLL', +# OP_ROT: 'OP_ROT', +# OP_SWAP: 'OP_SWAP', +# OP_TUCK: 'OP_TUCK', +# OP_CAT: 'OP_CAT', +# OP_SUBSTR: 'OP_SUBSTR', +# OP_LEFT: 'OP_LEFT', +# OP_RIGHT: 'OP_RIGHT', +# OP_SIZE: 'OP_SIZE', +# OP_INVERT: 'OP_INVERT', +# OP_AND: 'OP_AND', +# OP_OR: 'OP_OR', +# OP_XOR: 'OP_XOR', +# OP_EQUAL: 'OP_EQUAL', +# OP_EQUALVERIFY: 'OP_EQUALVERIFY', +# OP_RESERVED1: 'OP_RESERVED1', +# OP_RESERVED2: 'OP_RESERVED2', +# OP_1ADD: 'OP_1ADD', +# OP_1SUB: 'OP_1SUB', +# OP_2MUL: 'OP_2MUL', +# OP_2DIV: 'OP_2DIV', +# OP_NEGATE: 'OP_NEGATE', +# OP_ABS: 'OP_ABS', +# OP_NOT: 'OP_NOT', +# OP_0NOTEQUAL: 'OP_0NOTEQUAL', +# OP_ADD: 'OP_ADD', +# OP_SUB: 'OP_SUB', +# OP_MUL: 'OP_MUL', +# OP_DIV: 'OP_DIV', +# OP_MOD: 'OP_MOD', +# OP_LSHIFT: 'OP_LSHIFT', +# OP_RSHIFT: 'OP_RSHIFT', +# OP_BOOLAND: 'OP_BOOLAND', +# OP_BOOLOR: 'OP_BOOLOR', +# OP_NUMEQUAL: 'OP_NUMEQUAL', +# OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY', +# OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL', +# OP_LESSTHAN: 'OP_LESSTHAN', +# OP_GREATERTHAN: 'OP_GREATERTHAN', +# OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL', +# OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL', +# OP_MIN: 'OP_MIN', +# OP_MAX: 'OP_MAX', +# OP_WITHIN: 'OP_WITHIN', +# OP_RIPEMD160: 'OP_RIPEMD160', +# OP_SHA1: 'OP_SHA1', +# OP_SHA256: 'OP_SHA256', +# OP_HASH160: 'OP_HASH160', +# OP_HASH256: 'OP_HASH256', +# OP_CODESEPARATOR: 'OP_CODESEPARATOR', +# OP_CHECKSIG: 'OP_CHECKSIG', +# OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY', +# OP_CHECKMULTISIG: 'OP_CHECKMULTISIG', +# OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY', +# OP_NOP1: 'OP_NOP1', +# OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', +# OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', +# OP_CHECKTEMPLATEVERIFY : 'OP_CHECKTEMPLATEVERIFY', +# OP_NOP5: 'OP_NOP5', +# OP_NOP6: 'OP_NOP6', +# OP_NOP7: 'OP_NOP7', +# OP_NOP8: 'OP_NOP8', +# OP_NOP9: 'OP_NOP9', +# OP_NOP10: 'OP_NOP10', +# OP_CHECKSIGADD: 'OP_CHECKSIGADD', +# OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', +# }) +# end +# end diff --git a/lib/transaction.ex b/lib/transaction.ex index 8817a10..6ac8c83 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -9,6 +9,7 @@ defmodule Bitcoinex.Transaction do alias Bitcoinex.Transaction.Witness alias Bitcoinex.Utils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Taproot @type t() :: %__MODULE__{ version: non_neg_integer(), @@ -26,6 +27,27 @@ defmodule Bitcoinex.Transaction do :lock_time ] + @sighash_default 0x00 + @sighash_all 0x01 + @sighash_none 0x02 + @sighash_single 0x03 + @sighash_anyonecanpay 0x80 + @sighash_anyonecanpay_all 0x81 + @sighash_anyonecanpay_none 0x82 + @sighash_anyonecanpay_single 0x83 + + + + @valid_sighash_flags [ + @sighash_default, + @sighash_all, + @sighash_none, + @sighash_single, + @sighash_anyonecanpay_all, + @sighash_anyonecanpay_none, + @sighash_anyonecanpay_single + ] + @doc """ Returns the TxID of the given tranasction. @@ -43,6 +65,197 @@ defmodule Bitcoinex.Transaction do ) end + def bip341_sighash( tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys) do + sigmsg = bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys) + Taproot.tagged_hash_tapsighash(sigmsg) + end + + @spec bip341_sigmsg( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(<<_::280>>) + ) :: binary + def bip341_sigmsg(_, _, ext_flag, _, _, _) when ext_flag < 0 or ext_flag > 127, + do: {:error, "ext_flag out of range 0-127"} + + def bip341_sigmsg(_, hash_type, _, _, _, _) when hash_type not in @valid_sighash_flags, + do: {:error, "invalid sighash flag"} + + def bip341_sigmsg( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys + ) do + tx_data = bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) + bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, tx_data) + end + + # TODO: refactor this function to take in individual subhashes instead of single cache + def bip341_sigmsg( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + cached_tx_data + ) do + hash_byte = :binary.encode_unsigned(hash_type) + + input_data = + bip341_input_data( + tx, + hash_type, + ext_flag, + input_idx, + Enum.at(prev_amounts, input_idx), + Enum.at(prev_scriptpubkeys, input_idx) + ) + + output_data = bip341_output_data(tx, input_idx, hash_type) + + <<0>> <> hash_byte <> cached_tx_data <> input_data <> output_data + end + + # The results of this function can be reused across input signings. + def bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) do + version = <> + lock_time = <> + acc = version <> lock_time + + acc = + if !hash_type_is_anyonecanpay(hash_type) do + sha_prevouts = bip341_sha_prevouts(tx.inputs) + sha_amounts = bip341_sha_amounts(prev_amounts) + sha_scriptpubkeys = bip341_sha_scriptpubkeys(prev_scriptpubkeys) + sha_sequences = bip341_sha_sequences(tx.inputs) + acc <> sha_prevouts <> sha_amounts <> sha_scriptpubkeys <> sha_sequences + else + acc + end + + if !hash_type_is_none_or_single(hash_type) do + sha_outputs = bip341_sha_outputs(tx.outputs) + acc <> sha_outputs + else + acc + end + end + + defp bip341_input_data( + tx, + hash_type, + ext_flag, + input_idx, + prev_amount, + <> + ) do + annex = get_annex(tx, input_idx) + spend_type = ext_flag * 2 + if annex == nil, do: 0, else: 1 + + input_commit = + if hash_type_is_anyonecanpay(hash_type) do + input = Enum.at(tx.inputs, input_idx) + prev_outpoint = Transaction.In.serialize_prevout(input) + + prev_outpoint <> + <> <> + prev_scriptpubkey <> <> + else + <> + end + + <> <> input_commit <> bip341_sha_annex(annex) + end + + defp bip341_output_data(tx, input_idx, hash_type) do + if hash_type_is_single(hash_type) do + tx.outputs + |> Enum.at(input_idx) + |> Out.serialize_output() + |> :erlang.list_to_binary() + |> Utils.sha256() + else + <<>> + end + end + + def hash_type_is_anyonecanpay(hash_type), + do: Bitwise.band(hash_type, @sighash_anyonecanpay) == @sighash_anyonecanpay + + defp hash_type_is_none_or_single(hash_type) do + b = Bitwise.band(hash_type, 3) + b == @sighash_none || b == @sighash_single + end + + defp hash_type_is_single(hash_type) do + Bitwise.band(hash_type, 3) == @sighash_single + end + + @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error, } + def get_annex(%__MODULE__{witnesses: nil}, _), do: nil + def get_annex(%__MODULE__{witnesses: []}, _), do: nil + def get_annex(%__MODULE__{witnesses: witnesses, inputs: inputs}, input_idx) + when input_idx >= 0 and input_idx < length(inputs) do + witnesses + |> Enum.at(input_idx) + |> Witness.get_annex() + end + + def get_annex(_, _), do: {:error, "input index is out of range"} + + @spec bip341_sha_prevouts(list(In.t())) :: <<_::256>> + def bip341_sha_prevouts(inputs) do + inputs + |> Transaction.In.serialize_prevouts() + |> Utils.sha256() + end + + def bip341_sha_amounts(prev_amounts) do + prev_amounts + |> Enum.reduce(<<>>, fn amount, acc -> acc <> <> end) + |> Utils.sha256() + end + + def bip341_sha_scriptpubkeys(prev_scriptpubkeys) do + prev_scriptpubkeys + |> Enum.reduce(<<>>, fn script, acc -> acc <> script end) + |> Utils.sha256() + end + + def bip341_sha_sequences(inputs) do + inputs + |> Transaction.In.serialize_sequences() + |> Utils.sha256() + end + + def bip341_sha_outputs(outputs) do + outputs + |> Transaction.Out.serialize_outputs() + |> Utils.sha256() + end + + def bip341_sha_annex(nil), do: <<>> + + def bip341_sha_annex(annex) do + annex + |> byte_size() + |> Utils.serialize_compact_size_unsigned_int() + |> Kernel.<>(annex) + |> Utils.sha256() + end + @doc """ Decodes a transaction in a hex encoded string into binary. """ @@ -189,7 +402,7 @@ defmodule Bitcoinex.Transaction.Witness do ] @doc """ - Wtiness accepts a binary and deserializes it. + Witness accepts a binary and deserializes it. """ @spec witness(binary) :: t() def witness(witness_bytes) do @@ -270,6 +483,21 @@ defmodule Bitcoinex.Transaction.Witness do stack_size - 1 ) end + + @spec get_annex(t()) :: nil | binary + def get_annex(%__MODULE__{txinwitness: witnesses}) when length(witnesses) < 2, do: nil + def get_annex(%__MODULE__{txinwitness: witnesses}) do + last = + witnesses + |> Enum.reverse() + |> Enum.at(0) + + case last do + #TODO switch to binary or int once witnesses are no longer stored as strings + "50" <> _ -> last + _ -> nil + end + end end defmodule Bitcoinex.Transaction.In do @@ -304,13 +532,7 @@ defmodule Bitcoinex.Transaction.In do defp serialize_input(inputs, serialized_inputs) do [input | inputs] = inputs - {:ok, prev_txid} = Base.decode16(input.prev_txid, case: :lower) - - prev_txid = - prev_txid - |> :binary.decode_unsigned(:big) - |> :binary.encode_unsigned(:little) - |> Bitcoinex.Utils.pad(32, :trailing) + prev_txid = prev_txid_little_endian(input.prev_txid) {:ok, script_sig} = Base.decode16(input.script_sig, case: :lower) @@ -327,6 +549,26 @@ defmodule Bitcoinex.Transaction.In do serialize_input(inputs, [serialized_inputs, serialized_input]) end + def serialize_prevouts(inputs) do + Enum.reduce(inputs, <<>>, fn input, acc -> acc <> serialize_prevout(input) end) + end + + def serialize_prevout(input) do + prev_txid = prev_txid_little_endian(input.prev_txid) + prev_txid <> <> + end + + def serialize_sequences(inputs) do + Enum.reduce(inputs, <<>>, fn input, acc -> acc <> <> end) + end + + def prev_txid_little_endian(prev_txid_hex) do + prev_txid_hex + |> Base.decode16!(case: :lower) + |> Utils.flip_endianness() + |> Utils.pad(32, :trailing) + end + def parse_inputs(counter, inputs) do parse(inputs, [], counter) end @@ -375,20 +617,22 @@ defmodule Bitcoinex.Transaction.Out do @spec serialize_outputs(list(Out.t())) :: iolist() def serialize_outputs(outputs) do - serialize_output(outputs, []) + serialize_outputs(outputs, []) end - defp serialize_output([], serialized_outputs), do: serialized_outputs + def serialize_outputs([], serialized_outputs), do: serialized_outputs - defp serialize_output(outputs, serialized_outputs) do - [output | outputs] = outputs + def serialize_outputs([output | outputs], serialized_outputs) do + serialized_output = serialize_output(output) + serialize_outputs(outputs, [serialized_outputs, serialized_output]) + end + def serialize_output(output) do {:ok, script_pub_key} = Base.decode16(output.script_pub_key, case: :lower) script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) - serialized_output = [<>, script_len, script_pub_key] - serialize_output(outputs, [serialized_outputs, serialized_output]) + [<>, script_len, script_pub_key] end def output(out_bytes) do diff --git a/lib/utils.ex b/lib/utils.ex index 22137c5..6380ba9 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -3,11 +3,12 @@ defmodule Bitcoinex.Utils do Contains useful utility functions used in Bitcoinex. """ - @spec sha256(iodata()) :: binary + @spec sha256(iodata()) :: <<_::256>> def sha256(str) do :crypto.hash(:sha256, str) end + @spec tagged_hash(binary, iodata()) :: <<_::256>> def tagged_hash(tag, str) do tag_hash = sha256(tag) sha256(tag_hash <> tag_hash <> str) @@ -22,7 +23,7 @@ defmodule Bitcoinex.Utils do for _ <- 1..num, do: x end - @spec double_sha256(iodata()) :: binary + @spec double_sha256(iodata()) :: <<_::256>> def double_sha256(preimage) do :crypto.hash( :sha256, @@ -30,7 +31,7 @@ defmodule Bitcoinex.Utils do ) end - @spec hash160(iodata()) :: binary + @spec hash160(iodata()) :: <<_::160>> def hash160(preimage) do :crypto.hash( :ripemd160, @@ -61,6 +62,13 @@ defmodule Bitcoinex.Utils do bin <> <<0::size(pad_len)>> end + @spec flip_endianness(binary) :: binary + def flip_endianness(bin) do + bin + |> :binary.decode_unsigned(:big) + |> :binary.encode_unsigned(:little) + end + @spec int_to_big(non_neg_integer(), non_neg_integer()) :: binary def int_to_big(i, p) do i @@ -68,14 +76,17 @@ defmodule Bitcoinex.Utils do |> pad(p, :leading) end + @spec int_to_little(non_neg_integer(), integer) :: binary def int_to_little(i, p) do i |> :binary.encode_unsigned(:little) |> pad(p, :trailing) end + @spec little_to_int(binary) :: non_neg_integer def little_to_int(i), do: :binary.decode_unsigned(i, :little) + @spec encode_int(non_neg_integer()) :: binary | {:error, <<_::160>>} def encode_int(i) when i > 0 do cond do i < 0xFD -> :binary.encode_unsigned(i) @@ -86,6 +97,7 @@ defmodule Bitcoinex.Utils do end end + @spec hex_to_bin(String.t()) :: binary | {:error, String.t()} def hex_to_bin(str) do str |> String.downcase() @@ -109,8 +121,9 @@ defmodule Bitcoinex.Utils do end # ascending order + @spec lexicographical_sort(binary, binary) :: {binary, binary} def lexicographical_sort(bin0, bin1) when is_binary(bin0) and is_binary(bin1) do - if lexicographical_sort(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do + if lexicographical_cmp(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do {bin0, bin1} else {bin1, bin0} @@ -118,9 +131,10 @@ defmodule Bitcoinex.Utils do end # equality case - def lexicographical_sort([], []), do: true + @spec lexicographical_cmp(list(byte), list(byte)) :: boolean + def lexicographical_cmp([], []), do: true - def lexicographical_sort([b0 | r0], [b1 | r1]) do + def lexicographical_cmp([b0 | r0], [b1 | r1]) do cond do b0 == b1 -> lexicographical_sort(r0, r1) @@ -138,6 +152,7 @@ defmodule Bitcoinex.Utils do @doc """ Returns the serialized variable length integer. """ + @spec serialize_compact_size_unsigned_int(non_neg_integer()) :: binary def serialize_compact_size_unsigned_int(compact_size) do cond do compact_size >= 0 and compact_size <= 0xFC -> diff --git a/scripts/use_taproot_addr.exs b/scripts/use_taproot_addr.exs new file mode 100644 index 0000000..8429361 --- /dev/null +++ b/scripts/use_taproot_addr.exs @@ -0,0 +1,92 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + + +new_secret = fn -> + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() +end + +{:ok, sk} = new_secret.() |> PrivateKey.new() +sk = Secp256k1.force_even_y(sk) +pk = PrivateKey.to_point(sk) + +script_tree = nil + +{:ok, scriptpubkey} = Script.create_p2tr(pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" +txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +vout = 0 +amount = 100_000_000 + +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + + #sighash_default +hash_type = 0x00 +ext_flag = 0 +input_idx = 0 + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)] +) + +{_, merkle_root_hash} = Taproot.merkelize_script_tree(script_tree) + +q_sk = Taproot.tweak_privkey(sk, merkle_root_hash) + +{:ok, sig} = Schnorr.sign(q_sk, :binary.decode_unsigned(sighash), 0) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +witness_script = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [ + witness_script + ] + } +] +} + +Transaction.serialize(tx) + +# txid: 86dcdf6a88480a16524aa353b47d11228d67f96f59c4a645d65d4aac09330065 diff --git a/test/segwit_test.exs b/test/segwit_test.exs index 2d4bcc7..4b1832c 100644 --- a/test/segwit_test.exs +++ b/test/segwit_test.exs @@ -108,19 +108,19 @@ defmodule Bitcoinex.SegwitTest do end end - describe "is_valid_segswit_address?/1" do + describe "is_valid_segwit_address?/1" do test "return true given valid address" do for {address, _hexscript} <- @valid_segwit_address_hexscript_pairs_mainnet ++ @valid_segwit_address_hexscript_pairs_testnet ++ @valid_segwit_address_hexscript_pairs_regtest do - assert Segwit.is_valid_segswit_address?(address) + assert Segwit.is_valid_segwit_address?(address) end end test "return false given invalid address" do for address <- @invalid_segwit_addresses do - refute Segwit.is_valid_segswit_address?(address) + refute Segwit.is_valid_segwit_address?(address) end end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index e4a7e7c..012d33b 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -3,6 +3,10 @@ defmodule Bitcoinex.TransactionTest do doctest Bitcoinex.Transaction alias Bitcoinex.Transaction + alias Bitcoinex.Utils + alias Bitcoinex.Script + alias Bitcoinex.Taproot + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature, Point} @txn_serialization_1 %{ tx_hex: @@ -29,6 +33,381 @@ defmodule Bitcoinex.TransactionTest do "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000" } + # https://github.com/bitcoin/bips/blob/master/bip-0341/wallet-test-vectors.jsonå + @bip341_test_vector %{ + given: %{ + unsigned_tx: + "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d", + inputs: [ + %{ + prev_scriptpubkey: + "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + amount_sats: 420_000_000 + }, + %{ + prev_scriptpubkey: + "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + amount_sats: 462_000_000 + }, + %{ + prev_scriptpubkey: "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac", + amount_sats: 294_000_000 + }, + %{ + prev_scriptpubkey: + "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + amount_sats: 504_000_000 + }, + %{ + prev_scriptpubkey: + "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + amount_sats: 630_000_000 + }, + %{ + prev_scriptpubkey: "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", + amount_sats: 378_000_000 + }, + %{ + prev_scriptpubkey: + "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + amount_sats: 672_000_000 + }, + %{ + prev_scriptpubkey: + "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + amount_sats: 546_000_000 + }, + %{ + prev_scriptpubkey: + "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + amount_sats: 588_000_000 + } + ] + }, + intermediary: %{ + hash_amounts: "58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde6", + hash_outputs: "a2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc5", + hash_prevouts: "e3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f", + hash_script_pubkeys: "23ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e21", + hash_sequences: "18959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e" + }, + input_spending: [ + %{ + given: %{ + txin_index: 0, + internal_privkey: "6b973d88838f27366ed61c9ad6367663045cb456e28335c109e30717ae0c6baa", + merkle_root: nil, + hash_type: 3 + }, + intermediary: %{ + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_privkey: "2405b971772ad26915c8dcdf10f238753a9b837e5f8e6a86fd7c0cce5b7296d9", + sigmsg: + "0003020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0000000000d0418f0e9a36245b9a50ec87f8bf5be5bcae434337b87139c3a5b1f56e33cba0", + precomputed_used: [ + "hash_amounts", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "2514a6272f85cfa0f45eb907fcb0d121b808ed37c6ea160a5a9046ed5526d555" + }, + expected: %{ + witness: [ + "ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c03" + ] + } + }, + %{ + given: %{ + txin_index: 1, + internal_privkey: "1e4da49f6aaf4e5cd175fe08a32bb5cb4863d963921255f33d3bc31e1343907f", + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + hash_type: 131 + }, + intermediary: %{ + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_privkey: "ea260c3b10e60f6de018455cd0278f2f5b7e454be1999572789e6a9565d26080", + sigmsg: + "0083020000000065cd1d00d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd9900000000808f891b00000000225120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3ffffffffffcef8fb4ca7efc5433f591ecfc57391811ce1e186a3793024def5c884cba51d", + precomputed_used: [], + sig_hash: "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d" + }, + expected: %{ + witness: [ + "052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83" + ] + } + }, + %{ + given: %{ + txin_index: 3, + internal_privkey: "d3c7af07da2d54f7a7735d3d0fc4f0a73164db638b2f2f7c43f711f6d4aa7e64", + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + hash_type: 1 + }, + intermediary: %{ + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_privkey: "97323385e57015b75b0339a549c56a948eb961555973f0951f555ae6039ef00d", + sigmsg: + "0001020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50003000000", + precomputed_used: [ + "hash_amounts", + "hash_outputs", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "bf013ea93474aa67815b1b6cc441d23b64fa310911d991e713cd34c7f5d46669" + }, + expected: %{ + witness: [ + "ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a01" + ] + } + }, + %{ + given: %{ + txin_index: 4, + internal_privkey: "f36bb07a11e469ce941d16b63b11b9b9120a84d9d87cff2c84a8d4affb438f4e", + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + hash_type: 0 + }, + intermediary: %{ + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_privkey: "a8e7aa924f0d58854185a490e6c41f6efb7b675c0f3331b7f14b549400b4d501", + sigmsg: + "0000020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50004000000", + precomputed_used: [ + "hash_amounts", + "hash_outputs", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "4f900a0bae3f1446fd48490c2958b5a023228f01661cda3496a11da502a7f7ef" + }, + expected: %{ + witness: [ + "b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f" + ] + } + }, + %{ + given: %{ + txin_index: 6, + internal_privkey: "415cfe9c15d9cea27d8104d5517c06e9de48e2f986b695e4f5ffebf230e725d8", + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + hash_type: 2 + }, + intermediary: %{ + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_privkey: "241c14f2639d0d7139282aa6abde28dd8a067baa9d633e4e7230287ec2d02901", + sigmsg: + "0002020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0006000000", + precomputed_used: [ + "hash_amounts", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "15f25c298eb5cdc7eb1d638dd2d45c97c4c59dcaec6679cfc16ad84f30876b85" + }, + expected: %{ + witness: [ + "a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee002" + ] + } + }, + %{ + given: %{ + txin_index: 7, + internal_privkey: "c7b0e81f0a9a0b0499e112279d718cca98e79a12e2f137c72ae5b213aad0d103", + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + hash_type: 130 + }, + intermediary: %{ + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_privkey: "65b6000cd2bfa6b7cf736767a8955760e62b6649058cbc970b7c0871d786346b", + sigmsg: + "0082020000000065cd1d00e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf00000000804c8b2000000000225120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5ffffffff", + precomputed_used: [], + sig_hash: "cd292de50313804dabe4685e83f923d2969577191a3e1d2882220dca88cbeb10" + }, + expected: %{ + witness: [ + "ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c482" + ] + } + }, + %{ + given: %{ + txin_index: 8, + internal_privkey: "77863416be0d0665e517e1c375fd6f75839544eca553675ef7fdf4949518ebaa", + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + hash_type: 129 + }, + intermediary: %{ + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_privkey: "ec18ce6af99f43815db543f47b8af5ff5df3b2cb7315c955aa4a86e8143d2bf5", + sigmsg: + "0081020000000065cd1da2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc500a778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af101000000002b0c230000000022512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220ffffffff", + precomputed_used: [ + "hash_outputs" + ], + sig_hash: "cccb739eca6c13a8a89e6e5cd317ffe55669bbda23f2fd37b0f18755e008edd2" + }, + expected: %{ + witness: [ + "bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd981" + ] + } + } + ], + auxiliary: %{ + signed_tx: + "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d" + } + } + + # https://gist.github.com/giacomocaironi/e41a45195b2ac6863ec46e8f86324757 + @bip341_sighash_all %{ + sighash_flag: 0x00, + unsigned_tx: + "02000000025f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d00000000000000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e10100000000000000000100ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac00000000", + signed_tx: + "020000000001025f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d00000000000000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e10100000000000000000100ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac0247304402203120452eed289de04e17740232b5f97fac0bc91e4cbb7750bb3d9f4f3c09477b02207e4e363c8d7914f707ff3ddf84e3201f9e402a7dccd02be5d7739d91b0f91adf01210271be339aeae9ed2c6a5a7f8ac5f49638da387612be881c7ed2fb3848b0ef8a6c01408608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d453300000000", + inputs: [ + %{ + prev_scriptpubkey: "0014196a5bea745288a7f947993c28e3a0f2108d2e0a", + value: 500_000_000, + privkey: "6b3973ee2ce444ada0147716925f6f77569350804835498593dd3be95163d558", + pubkey: "0271be339aeae9ed2c6a5a7f8ac5f49638da387612be881c7ed2fb3848b0ef8a6c" + }, + %{ + prev_scriptpubkey: "512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", + value: 600_000_000, + privkey: "cf3780a32ef3b2d70366f0124ee40195a251044e82a13146106be75ee049ac02", + signature: + "8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533" + } + ], + intermediary: %{ + data: + "0000020000000000000032553b113292dfa8216546e721388a6c19c76626ca65dc187e0348d6ed445f815733468db74734c00efa0b466bca091d8f1aab074af2538f36bd0a734a5940c5423cd73484fc5e3e0a623442846c279c2216f25a2f32d161fea6c5821a1adde7af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc8cdee56004a241f9c79cc55b7d79eaed04909d84660502a2d4e9c357c2047cf50001000000", + sighash: "07333acfe6dce8196f1ad62b2e039a3d9f0b6627bf955be767c519c0f8789ff4", + sha_prevouts: %{ + data: + "5f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d0000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e101000000", + hash: "32553b113292dfa8216546e721388a6c19c76626ca65dc187e0348d6ed445f81" + }, + sha_amounts: %{ + data: "0065cd1d000000000046c32300000000", + hash: "5733468db74734c00efa0b466bca091d8f1aab074af2538f36bd0a734a5940c5" + }, + sha_scriptpubkeys: %{ + data: + "160014196a5bea745288a7f947993c28e3a0f2108d2e0a22512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", + hash: "423cd73484fc5e3e0a623442846c279c2216f25a2f32d161fea6c5821a1adde7" + }, + sha_sequences: %{ + data: "0000000000000000", + hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" + }, + sha_outputs: %{ + data: "00ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac", + hash: "8cdee56004a241f9c79cc55b7d79eaed04909d84660502a2d4e9c357c2047cf5" + } + } + } + + # SIGHASH_ANYONECANPAY(ALL) + # @bip341_sighash_anyonecanpay_all %{ + # sighash_flag: 0x81, + # unsigned_tx: + # "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", + # signed_tx: + # "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", + # inputs: [ + # %{ + # prev_scriptpubkey: + # "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", + # value: 250_000_000, + # privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", + # # has sighash anyonecanpay appended + # signature: + # "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" + # } + # ], + # intermediary: %{ + # data: + # "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", + # sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", + # sha_outputs: %{ + # data: + # "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", + # hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" + # } + # } + # } + + # SIGHASH_SINGLE + # @bip341_sighash_single %{ + # sighash_flag: 0x03, + # unsigned_tx: + # "0200000002f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b0100000000000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f400000000", + # signed_tx: + # "02000000000102f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006a483045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503201da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f40141346f28fec0d31fafde1a1aa30634d6962d59e118a79456671347060697937424fd77d679deb1c7652ae15b5d932fe3bbace65b07d6cf6639b638e21688b485c0030000000000", + # inputs: [ + # %{ + # prev_scriptpubkey: + # "512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c28", + # value: 100_000_000, + # privkey: "eb54ef369f599d3da9ecfeab0529160dfc76c92f1e32ade4ba33abd8408a23b8", + # signature: + # "cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b016460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c735", + # der_signature: + # "3045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503" + # }, + # %{ + # prev_scriptpubkey: "76a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", + # value: 500_000_000, + # privkey: "5dd3e4e9cd6073da94108e26e8c5c3ccbd510197ab12bd787b19b0e88181da9c", + # pubkey: "1da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021" + # } + # ], + # intermediary: %{ + # data: + # "00030200000000000000c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc00000000007b1b0fd12e11d0c5792199a8cd4f4de1e823635e6947909f10d09a3e84cce760", + # sighash: "278ba0eea80c753d166879a844b51f290770609ec171a21fdfb6c0232d25c2e0", + # sha_prevouts: %{ + # data: + # "f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed33159200000000", + # hash: "c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd" + # }, + # sha_amounts: %{ + # data: "00e1f505000000000065cd1d00000000", + # hash: "4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863" + # }, + # sha_scriptpubkeys: %{ + # data: + # "22512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c281976a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", + # hash: "480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704" + # }, + # sha_sequences: %{ + # data: "0000000000000000", + # hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" + # } + # } + # } + describe "decode/1" do test "decodes legacy bitcoin transaction" do txn_test = @txn_serialization_1 @@ -233,4 +612,153 @@ defmodule Bitcoinex.TransactionTest do assert "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" == out_1.script_pub_key end end + + describe "bip341_sighash" do + test "BIP341 test vector" do + t = @bip341_test_vector + {:ok, unsigned_tx} = Transaction.decode(t.given.unsigned_tx) + + sha_prevouts = Transaction.bip341_sha_prevouts(unsigned_tx.inputs) + assert sha_prevouts == Utils.hex_to_bin(t.intermediary.hash_prevouts) + + prev_amounts = Enum.map(t.given.inputs, fn input -> input.amount_sats end) + sha_amounts = Transaction.bip341_sha_amounts(prev_amounts) + assert sha_amounts == Utils.hex_to_bin(t.intermediary.hash_amounts) + + prev_scriptpubkeys = + Enum.map(t.given.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) + + sha_scriptpubkeys = Transaction.bip341_sha_scriptpubkeys(prev_scriptpubkeys) + assert sha_scriptpubkeys == Utils.hex_to_bin(t.intermediary.hash_script_pubkeys) + + sha_sequences = Transaction.bip341_sha_sequences(unsigned_tx.inputs) + assert sha_sequences == Utils.hex_to_bin(t.intermediary.hash_sequences) + + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.hash_outputs) + + # test sighash for each input + for i <- t.input_spending do + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + i.given.hash_type, + 0, + i.given.txin_index, + prev_amounts, + prev_scriptpubkeys + ) + assert Base.encode16(sigmsg, case: :lower) == i.intermediary.sigmsg + + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert Base.encode16(sighash, case: :lower) == i.intermediary.sig_hash + + {:ok, sk} = + i.given.internal_privkey + |> Base.decode16!(case: :lower) + |> :binary.decode_unsigned() + |> PrivateKey.new() + + merkle_root = + if i.given.merkle_root == nil do + <<>> + else + i.given.merkle_root + |> Utils.hex_to_bin() + end + + + tweaked_sk = Taproot.tweak_privkey(sk, merkle_root) + assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == i.intermediary.tweaked_privkey + + # BIP341 declares test vectors to all use aux=0 + {:ok, sig} = Schnorr.sign(tweaked_sk, :binary.decode_unsigned(sighash), 0) + + hash_byte = + if i.given.hash_type == 0x00 do + <<>> + else + <> + end + assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == Enum.at(i.expected.witness, 0) + end + + end + + test "SIGHASH_ALL" do + t = @bip341_sighash_all + {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + # intermediary hashes + sha_prevouts = Transaction.bip341_sha_prevouts(unsigned_tx.inputs) + assert sha_prevouts == Utils.hex_to_bin(t.intermediary.sha_prevouts.hash) + + prev_amounts = Enum.map(t.inputs, fn input -> input.value end) + sha_amounts = Transaction.bip341_sha_amounts(prev_amounts) + assert sha_amounts == Utils.hex_to_bin(t.intermediary.sha_amounts.hash) + + prev_scriptpubkeys = + Enum.map(t.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) + + sha_scriptpubkeys = Transaction.bip341_sha_scriptpubkeys(prev_scriptpubkeys) + assert sha_scriptpubkeys == Utils.hex_to_bin(t.intermediary.sha_scriptpubkeys.hash) + + sha_sequences = Transaction.bip341_sha_sequences(unsigned_tx.inputs) + assert sha_sequences == Utils.hex_to_bin(t.intermediary.sha_sequences.hash) + + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.sha_outputs.hash) + + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + t.sighash_flag, + 0, + 1, + prev_amounts, + prev_scriptpubkeys + ) + + assert sigmsg == Utils.hex_to_bin(t.intermediary.data) + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert sighash == Utils.hex_to_bin(t.intermediary.sighash) + # # TODO signing & check signed_tx + {:ok, sk} = + Enum.at(t.inputs, 1).privkey + |> Base.decode16!(case: :lower) + |> :binary.decode_unsigned() + |> PrivateKey.new() + + # test vector gives us Q pk and sk, no need to tweak + pk = PrivateKey.to_point(sk) + # TODO I Don't know what Aux was used for this test vector? + assert <<0x51, 0x20>> <> Point.x_bytes(pk) == Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) + # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) + end + + # test "SIGHASH_ANYONECANPAY_ALL" do + # t = @bip341_sighash_anyonecanpay_all + # {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + + # sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + # assert sha_outputs == Base.decode16!(t.intermediary.sha_outputs.hash) + + # prev_amounts = Enum.map(t.inputs, fn input -> input.value end) + # prev_scriptpubkeys = Enum.map(t.inputs, fn input -> Base.decode16!(input.prev_scriptpubkey) end) + + # sigmsg = Transaction.bip341_sigmsg(unsigned_tx, t.sighash_flag, 0, 0, prev_amounts, prev_scriptpubkeys) + # assert sigmsg == Base.decode16!(t.intermediary.data) + + # end + + # test "SIGHASH_SINGLE" do + + # end + end end From 02a7fbd6f7146030fd630cf70740017f23cfb523 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 16:13:28 -0800 Subject: [PATCH 18/40] add scriptpath spend, op_checksigadd, scripts for examples --- lib/opcode.ex | 3 + lib/script.ex | 19 +- lib/taproot.ex | 4 +- lib/transaction.ex | 103 ++++++++--- ..._taproot_addr.exs => taproot_keyspend.exs} | 28 +-- scripts/taproot_multi_scriptspend.exs | 168 ++++++++++++++++++ scripts/taproot_scriptspend.exs | 109 ++++++++++++ test/transaction_test.exs | 17 +- 8 files changed, 406 insertions(+), 45 deletions(-) rename scripts/{use_taproot_addr.exs => taproot_keyspend.exs} (73%) create mode 100644 scripts/taproot_multi_scriptspend.exs create mode 100644 scripts/taproot_scriptspend.exs diff --git a/lib/opcode.ex b/lib/opcode.ex index 31e7c93..1fe6c0b 100644 --- a/lib/opcode.ex +++ b/lib/opcode.ex @@ -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, @@ -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, diff --git a/lib/script.ex b/lib/script.ex index 666d2b7..8999d32 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -12,7 +12,7 @@ defmodule Bitcoinex.Script do @wsh_length 32 @tapkey_length 32 @h160_length 20 - @pubkey_lengths [33, 65] + @pubkey_lengths [@tapkey_length, 33, 65] # hash of G.x, used to construct unsolvable internal taproot keys @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 @@ -339,7 +339,7 @@ defmodule Bitcoinex.Script do true end - def is_p2pk?(%__MODULE__{}), do: false + def is_p2pk?(_), do: false @doc """ is_p2pkh? returns whether a given script is of the p2pkh format: @@ -351,7 +351,7 @@ defmodule Bitcoinex.Script do }), do: true - def is_p2pkh?(%__MODULE__{}), do: false + def is_p2pkh?(_), do: false @doc """ is_p2sh? returns whether a given script is of the p2sh format: @@ -361,7 +361,7 @@ defmodule Bitcoinex.Script do def is_p2sh?(%__MODULE__{items: [0xA9, @h160_length, <<_::binary-size(@h160_length)>>, 0x87]}), do: true - def is_p2sh?(%__MODULE__{}), do: false + def is_p2sh?(_), do: false @doc """ is_p2wpkh? returns whether a given script is of the p2wpkh format: @@ -371,7 +371,7 @@ defmodule Bitcoinex.Script do def is_p2wpkh?(%__MODULE__{items: [0x00, @h160_length, <<_::binary-size(@h160_length)>>]}), do: true - def is_p2wpkh?(%__MODULE__{}), do: false + def is_p2wpkh?(_), do: false @doc """ is_p2wsh? returns whether a given script is of the p2wsh format: @@ -381,7 +381,7 @@ defmodule Bitcoinex.Script do def is_p2wsh?(%__MODULE__{items: [0x00, @wsh_length, <<_::binary-size(@wsh_length)>>]}), do: true - def is_p2wsh?(%__MODULE__{}), do: false + def is_p2wsh?(_), do: false @doc """ is_p2tr? returns whether a given script is of the p2tr format: @@ -391,7 +391,7 @@ defmodule Bitcoinex.Script do def is_p2tr?(%__MODULE__{items: [0x51, @tapkey_length, <<_::binary-size(@tapkey_length)>>]}), do: true - def is_p2tr?(%__MODULE__{}), do: false + def is_p2tr?(_), do: false @doc """ is_multisig? returns whether a given script is of the raw multisig format: @@ -464,12 +464,13 @@ defmodule Bitcoinex.Script do create_p2pk creates a p2pk script using the passed public key """ @spec create_p2pk(binary) :: {:ok, t()} | {:error, String.t()} - def create_p2pk(pk) when is_binary(pk) and byte_size(pk) in [33, 65] do + def create_p2pk(pk) when is_binary(pk) and byte_size(pk) in @pubkey_lengths do {:ok, s} = push_op(new(), 0xAC) push_data(s, pk) end - def create_p2pk(_), do: {:error, "pubkey must be 33 or 65 bytes compressed or uncompressed SEC"} + def create_p2pk(_), + do: {:error, "pubkey must be 32, 33, 65 bytes compressed or uncompressed SEC"} @doc """ create_p2pkh creates a p2pkh script using the passed 20-byte public key hash diff --git a/lib/taproot.ex b/lib/taproot.ex index 85074f8..b098688 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -6,7 +6,9 @@ defmodule Bitcoinex.Taproot do @n Params.curve().n - # @bip342_leaf_version 0xc0 + @bip342_leaf_version 0xC0 + + def bip342_leaf_version(), do: @bip342_leaf_version @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil diff --git a/lib/transaction.ex b/lib/transaction.ex index 6ac8c83..2cae4fe 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -36,8 +36,6 @@ defmodule Bitcoinex.Transaction do @sighash_anyonecanpay_none 0x82 @sighash_anyonecanpay_single 0x83 - - @valid_sighash_flags [ @sighash_default, @sighash_all, @@ -65,13 +63,18 @@ defmodule Bitcoinex.Transaction do ) end - def bip341_sighash( tx = %__MODULE__{}, - hash_type, - ext_flag, - input_idx, - prev_amounts, - prev_scriptpubkeys) do - sigmsg = bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys) + def bip341_sighash( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + opts \\ [] + ) do + sigmsg = + bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, opts) + Taproot.tagged_hash_tapsighash(sigmsg) end @@ -81,12 +84,23 @@ defmodule Bitcoinex.Transaction do non_neg_integer(), non_neg_integer(), list(non_neg_integer()), - list(<<_::280>>) + list(<<_::280>>), + list({:tapleaf, Taproot.TapLeaf.t()}) ) :: binary - def bip341_sigmsg(_, _, ext_flag, _, _, _) when ext_flag < 0 or ext_flag > 127, + def bip341_sigmsg( + tx, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + opts \\ [] + ) + + def bip341_sigmsg(_, _, ext_flag, _, _, _, _) when ext_flag < 0 or ext_flag > 127, do: {:error, "ext_flag out of range 0-127"} - def bip341_sigmsg(_, hash_type, _, _, _, _) when hash_type not in @valid_sighash_flags, + def bip341_sigmsg(_, hash_type, _, _, _, _, _) when hash_type not in @valid_sighash_flags, do: {:error, "invalid sighash flag"} def bip341_sigmsg( @@ -95,21 +109,42 @@ defmodule Bitcoinex.Transaction do ext_flag, input_idx, prev_amounts, - prev_scriptpubkeys + prev_scriptpubkeys, + opts ) do tx_data = bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) - bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, tx_data) + + bip341_sigmsg_with_cache( + tx, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + tx_data, + opts + ) end - # TODO: refactor this function to take in individual subhashes instead of single cache - def bip341_sigmsg( + @spec bip341_sigmsg_with_cache( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(binary), + binary, + list({:tapleaf, Taproot.TapLeaf.t()}) + ) :: binary + def bip341_sigmsg_with_cache( tx = %__MODULE__{}, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, - cached_tx_data + cached_tx_data, + opts \\ [] ) do hash_byte = :binary.encode_unsigned(hash_type) @@ -125,7 +160,21 @@ defmodule Bitcoinex.Transaction do output_data = bip341_output_data(tx, input_idx, hash_type) - <<0>> <> hash_byte <> cached_tx_data <> input_data <> output_data + tapleaf = Keyword.get(opts, :tapleaf, nil) + + ext = + case tapleaf do + tl = %Taproot.TapLeaf{} -> + # TODO last_executed_codesep_pos not implemented + sigmsg_extension(ext_flag, tl) + + nil -> + sigmsg_extension(ext_flag) + end + + <<0>> <> + hash_byte <> + cached_tx_data <> input_data <> output_data <> ext end # The results of this function can be reused across input signings. @@ -203,9 +252,10 @@ defmodule Bitcoinex.Transaction do Bitwise.band(hash_type, 3) == @sighash_single end - @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error, } + @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error} def get_annex(%__MODULE__{witnesses: nil}, _), do: nil def get_annex(%__MODULE__{witnesses: []}, _), do: nil + def get_annex(%__MODULE__{witnesses: witnesses, inputs: inputs}, input_idx) when input_idx >= 0 and input_idx < length(inputs) do witnesses @@ -256,6 +306,18 @@ defmodule Bitcoinex.Transaction do |> Utils.sha256() end + def sigmsg_extension(0), do: <<>> + + def sigmsg_extension(1, tapleaf, last_executed_codesep_pos \\ 0xFFFFFFFF), + do: bip342_sigmsg_ext(tapleaf, last_executed_codesep_pos) + + def bip342_sigmsg_ext(tapleaf = %Taproot.TapLeaf{}, last_executed_codesep_pos \\ 0xFFFFFFFF) do + key_version = 0x00 + + Taproot.TapLeaf.hash(tapleaf) <> + <> <> <> + end + @doc """ Decodes a transaction in a hex encoded string into binary. """ @@ -486,6 +548,7 @@ defmodule Bitcoinex.Transaction.Witness do @spec get_annex(t()) :: nil | binary def get_annex(%__MODULE__{txinwitness: witnesses}) when length(witnesses) < 2, do: nil + def get_annex(%__MODULE__{txinwitness: witnesses}) do last = witnesses @@ -493,7 +556,7 @@ defmodule Bitcoinex.Transaction.Witness do |> Enum.at(0) case last do - #TODO switch to binary or int once witnesses are no longer stored as strings + # TODO switch to binary or int once witnesses are no longer stored as strings "50" <> _ -> last _ -> nil end diff --git a/scripts/use_taproot_addr.exs b/scripts/taproot_keyspend.exs similarity index 73% rename from scripts/use_taproot_addr.exs rename to scripts/taproot_keyspend.exs index 8429361..8229459 100644 --- a/scripts/use_taproot_addr.exs +++ b/scripts/taproot_keyspend.exs @@ -5,27 +5,33 @@ alias Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} alias Bitcoinex.Taproot - -new_secret = fn -> - 32 - |> :crypto.strong_rand_bytes() - |> :binary.decode_unsigned() +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) end -{:ok, sk} = new_secret.() |> PrivateKey.new() -sk = Secp256k1.force_even_y(sk) +sk = new_privkey.() pk = PrivateKey.to_point(sk) +# EDIT if you want to include a script tree (optional, but spending is not yet enabled) script_tree = nil {:ok, scriptpubkey} = Script.create_p2tr(pk, script_tree) {:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# EDIT to the unique addr # addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" +# EDIT to the txid of your send to the addr above txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +# EDIT to the vout of the output you created for the addr vout = 0 +# EDIT to the amount you sent in the UTXO above amount = 100_000_000 - +# EDIT where you would like to spend to dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" {:ok, dest_script, _network} = Script.from_address(dest_addr) @@ -49,7 +55,7 @@ tx = %Transaction{ lock_time: 0 } - #sighash_default +#sighash_default hash_type = 0x00 ext_flag = 0 input_idx = 0 @@ -87,6 +93,8 @@ tx = %Transaction{tx | witnesses: [ ] } -Transaction.serialize(tx) +Transaction.Utils.serialize(tx) +# the last zero arg is to disable max_fee_rate +# use bitcoin-cli sendrawtransaction 0 # txid: 86dcdf6a88480a16524aa353b47d11228d67f96f59c4a645d65d4aac09330065 diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs new file mode 100644 index 0000000..101062c --- /dev/null +++ b/scripts/taproot_multi_scriptspend.exs @@ -0,0 +1,168 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +# internal_key +internal_sk = new_privkey.() +internal_pk = PrivateKey.to_point(internal_sk) + +# p2pk script key 1 +p2pk1_sk = new_privkey.() +p2pk1_pk = PrivateKey.to_point(p2pk1_sk) + +{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) + +# p2pk script key 2 +p2pk2_sk = new_privkey.() +p2pk2_pk = PrivateKey.to_point(p2pk2_sk) + +{:ok, p2pk2_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) + + +leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) + +leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk2_script) + +# single leaf +script_tree = {leaf0, leaf1} + +{:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = bcrt1pwfprzzadfx3meyn5glm0rfmyl6lgmelqgwfqvxwx3hr756tcr77spqf2aq +# broadcast and mine funding tx +# EDIT note txid of funding tx +txid = "f11bfaa48121c582f652f0a1643c7b5a1692fe3582dff7819d2787486e595f68" +# EDIT to the vout of the output you created for the addr +vout = 0 +# EDIT to the amount you sent in the UTXO above +amount = 100_000_000 +# EDIT where you would like to spend to +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + +#sighash_default +hash_type = 0x00 +ext_flag = 1 +input_idx = 0 + +# here is where you choose which leaf to use. script_idx must match leaf # +script_idx = 0 +leaf = leaf0 +sk = p2pk1_sk +script = p2pk1_script + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: leaf +) + +aux_rand = 0 + +{:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + +# 01000000000101685f596e4887279d81f7df8235fe92165a7b3c64a1f052f682c52181a4fa1bf10000000000000000800180f0fa02000000002251201650a2b627dd1fa42e7c9fd0a5caf5b858caed769228bb7235eb5553c5e15f680340f53ea81564380d49cd2bdf4bee89b5784a846048818cda51c284c6fd8eaf542bd3d25df4d696c5063d016b8fcab85e2eb78503bca277783a721a2c053c7266972220f6c4e1e4276a75cfcbba374608887f64e09a55a9e6166fe4856324d084c4a784ac41c1a280169a8ed09e3c4b34f832c0ae44d78bb081631c00084f12a602218734a27d9d06d23f4e9ac0a6d9eb2923d59c40b113cb3a150062410da36bc7408584faee00000000 + +# ALTERNATE script +script_idx = 1 +leaf = leaf1 +sk = p2pk2_sk +script = p2pk2_script + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: leaf +) + +aux_rand = 0 + +{:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + + +# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 diff --git a/scripts/taproot_scriptspend.exs b/scripts/taproot_scriptspend.exs new file mode 100644 index 0000000..acafaf8 --- /dev/null +++ b/scripts/taproot_scriptspend.exs @@ -0,0 +1,109 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +# internal_key +internal_sk = new_privkey.() +internal_pk = PrivateKey.to_point(internal_sk) + +# p2pk script key +p2pk_sk = new_privkey.() +p2pk_pk = PrivateKey.to_point(p2pk_sk) + +{:ok, p2pk_script} = Script.create_p2pk(Point.x_bytes(p2pk_pk)) + +# single leaf +script_tree = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk_script) + +{:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = bcrt1ptkdqw3d39fuzg9qtw6r6e2rj098tp5vffhhnyu8r7m62j00q3atq8hpx0m +# broadcast and mine funding tx +# EDIT note txid of funding tx +txid = "0492bc1dce2ee85c92533942219e1ca72069a395d2dc77d595bd2f171da4e6ce" +# EDIT to the vout of the output you created for the addr +vout = 0 +# EDIT to the amount you sent in the UTXO above +amount = 100_000_000 +# EDIT where you would like to spend to +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + +#sighash_default +hash_type = 0x00 +ext_flag = 1 +input_idx = 0 + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: script_tree +) + + + +aux_rand = 0 +script_idx = 0 + +{:ok, sig} = Schnorr.sign(p2pk_sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(p2pk_script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + +# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 012d33b..cec9aa5 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -328,6 +328,7 @@ defmodule Bitcoinex.TransactionTest do } } + # TODO write test # SIGHASH_ANYONECANPAY(ALL) # @bip341_sighash_anyonecanpay_all %{ # sighash_flag: 0x81, @@ -358,6 +359,7 @@ defmodule Bitcoinex.TransactionTest do # } # } + # TODO write test # SIGHASH_SINGLE # @bip341_sighash_single %{ # sighash_flag: 0x03, @@ -651,6 +653,7 @@ defmodule Bitcoinex.TransactionTest do prev_amounts, prev_scriptpubkeys ) + assert Base.encode16(sigmsg, case: :lower) == i.intermediary.sigmsg sighash = Taproot.tagged_hash_tapsighash(sigmsg) @@ -670,9 +673,10 @@ defmodule Bitcoinex.TransactionTest do |> Utils.hex_to_bin() end - tweaked_sk = Taproot.tweak_privkey(sk, merkle_root) - assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == i.intermediary.tweaked_privkey + + assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == + i.intermediary.tweaked_privkey # BIP341 declares test vectors to all use aux=0 {:ok, sig} = Schnorr.sign(tweaked_sk, :binary.decode_unsigned(sighash), 0) @@ -683,9 +687,10 @@ defmodule Bitcoinex.TransactionTest do else <> end - assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == Enum.at(i.expected.witness, 0) - end + assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == + Enum.at(i.expected.witness, 0) + end end test "SIGHASH_ALL" do @@ -737,7 +742,9 @@ defmodule Bitcoinex.TransactionTest do # test vector gives us Q pk and sk, no need to tweak pk = PrivateKey.to_point(sk) # TODO I Don't know what Aux was used for this test vector? - assert <<0x51, 0x20>> <> Point.x_bytes(pk) == Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + assert <<0x51, 0x20>> <> Point.x_bytes(pk) == + Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) end From 11fe186ef511919e1db48ff6fffc690fba761633 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 21:56:43 -0800 Subject: [PATCH 19/40] add tx hex to taproot multi-spend ex --- scripts/taproot_multi_scriptspend.exs | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs index 101062c..c68fa38 100644 --- a/scripts/taproot_multi_scriptspend.exs +++ b/scripts/taproot_multi_scriptspend.exs @@ -19,21 +19,21 @@ internal_sk = new_privkey.() internal_pk = PrivateKey.to_point(internal_sk) # p2pk script key 1 -p2pk1_sk = new_privkey.() -p2pk1_pk = PrivateKey.to_point(p2pk1_sk) +p2pk0_sk = new_privkey.() +p2pk1_pk = PrivateKey.to_point(p2pk0_sk) -{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) +{:ok, p2pk0_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) # p2pk script key 2 -p2pk2_sk = new_privkey.() -p2pk2_pk = PrivateKey.to_point(p2pk2_sk) +p2pk1_sk = new_privkey.() +p2pk2_pk = PrivateKey.to_point(p2pk1_sk) -{:ok, p2pk2_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) +{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) -leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) +leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk0_script) -leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk2_script) +leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) # single leaf script_tree = {leaf0, leaf1} @@ -79,10 +79,12 @@ ext_flag = 1 input_idx = 0 # here is where you choose which leaf to use. script_idx must match leaf # + +# SCRIPT 0 script_idx = 0 leaf = leaf0 -sk = p2pk1_sk -script = p2pk1_script +sk = p2pk0_sk +script = p2pk0_script sighash = Transaction.bip341_sighash( tx, @@ -125,8 +127,8 @@ Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) # ALTERNATE script script_idx = 1 leaf = leaf1 -sk = p2pk2_sk -script = p2pk2_script +sk = p2pk1_sk +script = p2pk1_script sighash = Transaction.bip341_sighash( tx, @@ -164,5 +166,4 @@ tx = %Transaction{tx | witnesses: [ Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) - -# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 +# 01000000000101685f596e4887279d81f7df8235fe92165a7b3c64a1f052f682c52181a4fa1bf10000000000000000800180f0fa02000000002251201650a2b627dd1fa42e7c9fd0a5caf5b858caed769228bb7235eb5553c5e15f68034077228590ea058878df865afdfbb950be53d55da15ee1a4a7f03ee5cc0c01003fb3d290c799b428a400d0743ebb3c9caa2b1c27f99d851cee6464ea7642f939582220e2aa46c4e1f43cb01c89195b39ea80dbf91d7054052fa0cb5805828b786cd734ac41c1a280169a8ed09e3c4b34f832c0ae44d78bb081631c00084f12a602218734a27d509753bb6e46ebbdcfb16bbd3c7c07b59b7aede009760ba50a5ae8ae1d0ee49200000000 From 8792568db1be9257fa2fd7db4a5c2ee28bab2b5d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 22:56:41 -0800 Subject: [PATCH 20/40] cleanup --- lib/lightning_network/invoice.ex | 2 +- lib/taproot.ex | 28 +++-- lib/transaction.ex | 17 +++ scripts/taproot_multi_scriptspend.exs | 4 +- scripts/taproot_scriptspend.exs | 2 +- test/script_test.exs | 5 - test/transaction_test.exs | 173 ++++++++++---------------- 7 files changed, 105 insertions(+), 126 deletions(-) diff --git a/lib/lightning_network/invoice.ex b/lib/lightning_network/invoice.ex index e216318..9819006 100644 --- a/lib/lightning_network/invoice.ex +++ b/lib/lightning_network/invoice.ex @@ -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 diff --git a/lib/taproot.ex b/lib/taproot.ex index b098688..3954ff3 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -8,6 +8,7 @@ defmodule Bitcoinex.Taproot do @bip342_leaf_version 0xC0 + @spec bip342_leaf_version :: 192 def bip342_leaf_version(), do: @bip342_leaf_version @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil @@ -65,12 +66,16 @@ defmodule Bitcoinex.Taproot do def tagged_hash_tapsighash(sigmsg), do: Utils.tagged_hash("TapSighash", sigmsg) defmodule TapLeaf do + @moduledoc """ + TapLeaf represents a leaf of a Taproot Merkle tree. A leaf + contains a version and a Script. + """ alias Bitcoinex.Script alias Bitcoinex.Taproot @type t :: %__MODULE__{ version: non_neg_integer(), - script: binary + script: Script.t() } @enforce_keys [ :version, @@ -82,19 +87,18 @@ defmodule Bitcoinex.Taproot do ] @doc """ - from_script constructs a TapLeaf from a leaf_version and Script. + new constructs a TapLeaf from a leaf_version and Script. The script is stored as binary with the compact size prepended to it. """ - @spec from_script(non_neg_integer(), Script.t()) :: t() - def from_script(leaf_version, script = %Script{}) do - s = Script.serialize_with_compact_size(script) - %__MODULE__{version: leaf_version, script: s} + @spec new(non_neg_integer(), Script.t()) :: t() + def new(leaf_version, script = %Script{}) do + %__MODULE__{version: leaf_version, script: script} end @spec from_string(non_neg_integer(), String.t()) :: t() def from_string(leaf_version, script_hex) do {:ok, script} = Script.parse_script(script_hex) - from_script(leaf_version, script) + new(leaf_version, script) end @doc """ @@ -102,7 +106,8 @@ defmodule Bitcoinex.Taproot do store the Script in binary and alredy prepended with the compact size, so that is not added here. """ @spec serialize(t()) :: binary - def serialize(%__MODULE__{version: v, script: s}), do: :binary.encode_unsigned(v) <> s + def serialize(%__MODULE__{version: v, script: s}), + do: :binary.encode_unsigned(v) <> Script.serialize_with_compact_size(s) @doc """ hash returns the Hash_TapLeaf of the serialized TapLeaf @@ -179,6 +184,11 @@ defmodule Bitcoinex.Taproot do {:cont, tagged_hash_tapbranch(l <> r)} end + @doc """ + validate_taproot_scriptpath_spend DOES NOT validate the actual script according to BIP342. + It only validates the BIP341 rules around how a scriptPath spend works. + See: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules + """ @spec validate_taproot_scriptpath_spend(Point.t(), binary, binary) :: bool | {:error, String.t()} def validate_taproot_scriptpath_spend( @@ -206,7 +216,7 @@ defmodule Bitcoinex.Taproot do {tk, {:ok, pk}} -> validate_q(q_point, Math.add(pk, tk), c) - # TODO MUST EVALUATE THE ACTUAL SCRIPT + # TODO evaluate actual script? end end diff --git a/lib/transaction.ex b/lib/transaction.ex index 2cae4fe..cdae60d 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -63,6 +63,15 @@ defmodule Bitcoinex.Transaction do ) end + @spec bip341_sighash( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(<<_::280>>), + list({:tapleaf, Taproot.TapLeaf.t()}) + ) :: <<_::256>> def bip341_sighash( tx = %__MODULE__{}, hash_type, @@ -178,6 +187,8 @@ defmodule Bitcoinex.Transaction do end # The results of this function can be reused across input signings. + @spec bip341_tx_data(t(), non_neg_integer(), list(non_neg_integer()), list(<<_::280>>)) :: + binary def bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) do version = <> lock_time = <> @@ -240,6 +251,7 @@ defmodule Bitcoinex.Transaction do end end + @spec hash_type_is_anyonecanpay(non_neg_integer()) :: boolean def hash_type_is_anyonecanpay(hash_type), do: Bitwise.band(hash_type, @sighash_anyonecanpay) == @sighash_anyonecanpay @@ -272,30 +284,35 @@ defmodule Bitcoinex.Transaction do |> Utils.sha256() end + @spec bip341_sha_amounts(list(non_neg_integer())) :: <<_::256>> def bip341_sha_amounts(prev_amounts) do prev_amounts |> Enum.reduce(<<>>, fn amount, acc -> acc <> <> end) |> Utils.sha256() end + @spec bip341_sha_scriptpubkeys(list(<<_::280>>)) :: <<_::256>> def bip341_sha_scriptpubkeys(prev_scriptpubkeys) do prev_scriptpubkeys |> Enum.reduce(<<>>, fn script, acc -> acc <> script end) |> Utils.sha256() end + @spec bip341_sha_sequences(list(Transaction.In.t())) :: <<_::256>> def bip341_sha_sequences(inputs) do inputs |> Transaction.In.serialize_sequences() |> Utils.sha256() end + @spec bip341_sha_outputs(list(Transaction.Out.t())) :: <<_::256>> def bip341_sha_outputs(outputs) do outputs |> Transaction.Out.serialize_outputs() |> Utils.sha256() end + @spec bip341_sha_annex(nil | binary) :: <<_::256>> def bip341_sha_annex(nil), do: <<>> def bip341_sha_annex(annex) do diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs index c68fa38..ed3e0e5 100644 --- a/scripts/taproot_multi_scriptspend.exs +++ b/scripts/taproot_multi_scriptspend.exs @@ -31,9 +31,9 @@ p2pk2_pk = PrivateKey.to_point(p2pk1_sk) {:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) -leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk0_script) +leaf0 = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk0_script) -leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) +leaf1 = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk1_script) # single leaf script_tree = {leaf0, leaf1} diff --git a/scripts/taproot_scriptspend.exs b/scripts/taproot_scriptspend.exs index acafaf8..6c2ab8d 100644 --- a/scripts/taproot_scriptspend.exs +++ b/scripts/taproot_scriptspend.exs @@ -25,7 +25,7 @@ p2pk_pk = PrivateKey.to_point(p2pk_sk) {:ok, p2pk_script} = Script.create_p2pk(Point.x_bytes(p2pk_pk)) # single leaf -script_tree = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk_script) +script_tree = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk_script) {:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) diff --git a/test/script_test.exs b/test/script_test.exs index 9f2dad6..e361dfd 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -1596,11 +1596,6 @@ defmodule Bitcoinex.ScriptTest do test "test bip341 test vectors" do for t <- @bip_341_script_pubkey_test_vectors do {:ok, p} = Point.lift_x(t.given.internal_pubkey) - # TODO move to Taproot_test - # {_node, hash} = Taproot.merkelize_script_tree(t.script_tree) - # assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root - # TODO check tweak & tweak_pubkey - {:ok, script} = Script.create_p2tr(p, t.given.script_tree) assert Script.to_hex(script) == t.expected.script_pubkey assert Script.to_address(script, :mainnet) == {:ok, t.expected.bip350_address} diff --git a/test/transaction_test.exs b/test/transaction_test.exs index cec9aa5..e730f4d 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -6,7 +6,7 @@ defmodule Bitcoinex.TransactionTest do alias Bitcoinex.Utils alias Bitcoinex.Script alias Bitcoinex.Taproot - alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature, Point} + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} @txn_serialization_1 %{ tx_hex: @@ -295,6 +295,7 @@ defmodule Bitcoinex.TransactionTest do prev_scriptpubkey: "512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", value: 600_000_000, privkey: "cf3780a32ef3b2d70366f0124ee40195a251044e82a13146106be75ee049ac02", + # We don't know what aux was used, so this can't be recreated :/ signature: "8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533" } @@ -328,87 +329,34 @@ defmodule Bitcoinex.TransactionTest do } } - # TODO write test # SIGHASH_ANYONECANPAY(ALL) - # @bip341_sighash_anyonecanpay_all %{ - # sighash_flag: 0x81, - # unsigned_tx: - # "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", - # signed_tx: - # "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", - # inputs: [ - # %{ - # prev_scriptpubkey: - # "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", - # value: 250_000_000, - # privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", - # # has sighash anyonecanpay appended - # signature: - # "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" - # } - # ], - # intermediary: %{ - # data: - # "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", - # sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", - # sha_outputs: %{ - # data: - # "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", - # hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" - # } - # } - # } - - # TODO write test - # SIGHASH_SINGLE - # @bip341_sighash_single %{ - # sighash_flag: 0x03, - # unsigned_tx: - # "0200000002f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b0100000000000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f400000000", - # signed_tx: - # "02000000000102f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006a483045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503201da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f40141346f28fec0d31fafde1a1aa30634d6962d59e118a79456671347060697937424fd77d679deb1c7652ae15b5d932fe3bbace65b07d6cf6639b638e21688b485c0030000000000", - # inputs: [ - # %{ - # prev_scriptpubkey: - # "512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c28", - # value: 100_000_000, - # privkey: "eb54ef369f599d3da9ecfeab0529160dfc76c92f1e32ade4ba33abd8408a23b8", - # signature: - # "cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b016460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c735", - # der_signature: - # "3045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503" - # }, - # %{ - # prev_scriptpubkey: "76a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", - # value: 500_000_000, - # privkey: "5dd3e4e9cd6073da94108e26e8c5c3ccbd510197ab12bd787b19b0e88181da9c", - # pubkey: "1da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021" - # } - # ], - # intermediary: %{ - # data: - # "00030200000000000000c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc00000000007b1b0fd12e11d0c5792199a8cd4f4de1e823635e6947909f10d09a3e84cce760", - # sighash: "278ba0eea80c753d166879a844b51f290770609ec171a21fdfb6c0232d25c2e0", - # sha_prevouts: %{ - # data: - # "f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed33159200000000", - # hash: "c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd" - # }, - # sha_amounts: %{ - # data: "00e1f505000000000065cd1d00000000", - # hash: "4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863" - # }, - # sha_scriptpubkeys: %{ - # data: - # "22512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c281976a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", - # hash: "480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704" - # }, - # sha_sequences: %{ - # data: "0000000000000000", - # hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" - # } - # } - # } + @bip341_sighash_anyonecanpay_all %{ + sighash_flag: 0x81, + unsigned_tx: + "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", + signed_tx: + "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", + inputs: [ + %{ + prev_scriptpubkey: "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", + value: 250_000_000, + privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", + # has sighash anyonecanpay appended + signature: + "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" + } + ], + intermediary: %{ + data: + "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", + sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", + sha_outputs: %{ + data: + "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", + hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" + } + } + } describe "decode/1" do test "decodes legacy bitcoin transaction" do @@ -732,40 +680,49 @@ defmodule Bitcoinex.TransactionTest do assert sigmsg == Utils.hex_to_bin(t.intermediary.data) sighash = Taproot.tagged_hash_tapsighash(sigmsg) assert sighash == Utils.hex_to_bin(t.intermediary.sighash) - # # TODO signing & check signed_tx - {:ok, sk} = - Enum.at(t.inputs, 1).privkey - |> Base.decode16!(case: :lower) - |> :binary.decode_unsigned() - |> PrivateKey.new() - - # test vector gives us Q pk and sk, no need to tweak - pk = PrivateKey.to_point(sk) - # TODO I Don't know what Aux was used for this test vector? - assert <<0x51, 0x20>> <> Point.x_bytes(pk) == - Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) - - # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) - # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) end - # test "SIGHASH_ANYONECANPAY_ALL" do - # t = @bip341_sighash_anyonecanpay_all - # {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + test "SIGHASH_ANYONECANPAY_ALL" do + t = @bip341_sighash_anyonecanpay_all + {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) - # sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) - # assert sha_outputs == Base.decode16!(t.intermediary.sha_outputs.hash) + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.sha_outputs.hash) - # prev_amounts = Enum.map(t.inputs, fn input -> input.value end) - # prev_scriptpubkeys = Enum.map(t.inputs, fn input -> Base.decode16!(input.prev_scriptpubkey) end) + prev_amounts = Enum.map(t.inputs, fn input -> input.value end) - # sigmsg = Transaction.bip341_sigmsg(unsigned_tx, t.sighash_flag, 0, 0, prev_amounts, prev_scriptpubkeys) - # assert sigmsg == Base.decode16!(t.intermediary.data) + prev_scriptpubkeys = + Enum.map(t.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) - # end + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + t.sighash_flag, + 0, + 0, + prev_amounts, + prev_scriptpubkeys + ) + + assert sigmsg == Utils.hex_to_bin(t.intermediary.data) - # test "SIGHASH_SINGLE" do + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert sighash == Utils.hex_to_bin(t.intermediary.sighash) - # end + sighash2 = + Transaction.bip341_sighash( + unsigned_tx, + t.sighash_flag, + 0, + 0, + prev_amounts, + prev_scriptpubkeys + ) + + assert sighash2 == Utils.hex_to_bin(t.intermediary.sighash) + end end end From 406e73fb2e74489d1ecf7f401dd05191635b3b5b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 22:57:48 -0800 Subject: [PATCH 21/40] rm tapscript.ex --- lib/tapscript.ex | 125 ----------------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 lib/tapscript.ex diff --git a/lib/tapscript.ex b/lib/tapscript.ex deleted file mode 100644 index fa1b022..0000000 --- a/lib/tapscript.ex +++ /dev/null @@ -1,125 +0,0 @@ -# defmodule Tapscript do - -# defmodule Tapscript.Opcode do -# # BIP 342 opcodes (Tapscript) -# OP_CHECKSIGADD = 0xba - -# OP_INVALIDOPCODE = 0xff - -# OPCODE_NAMES.update({ -# OP_0: 'OP_0', -# OP_PUSHDATA1: 'OP_PUSHDATA1', -# OP_PUSHDATA2: 'OP_PUSHDATA2', -# OP_PUSHDATA4: 'OP_PUSHDATA4', -# OP_1NEGATE: 'OP_1NEGATE', -# OP_RESERVED: 'OP_RESERVED', -# OP_1: 'OP_1', -# OP_2: 'OP_2', -# OP_3: 'OP_3', -# OP_4: 'OP_4', -# OP_5: 'OP_5', -# OP_6: 'OP_6', -# OP_7: 'OP_7', -# OP_8: 'OP_8', -# OP_9: 'OP_9', -# OP_10: 'OP_10', -# OP_11: 'OP_11', -# OP_12: 'OP_12', -# OP_13: 'OP_13', -# OP_14: 'OP_14', -# OP_15: 'OP_15', -# OP_16: 'OP_16', -# OP_NOP: 'OP_NOP', -# OP_VER: 'OP_VER', -# OP_IF: 'OP_IF', -# OP_NOTIF: 'OP_NOTIF', -# OP_VERIF: 'OP_VERIF', -# OP_VERNOTIF: 'OP_VERNOTIF', -# OP_ELSE: 'OP_ELSE', -# OP_ENDIF: 'OP_ENDIF', -# OP_VERIFY: 'OP_VERIFY', -# OP_RETURN: 'OP_RETURN', -# OP_TOALTSTACK: 'OP_TOALTSTACK', -# OP_FROMALTSTACK: 'OP_FROMALTSTACK', -# OP_2DROP: 'OP_2DROP', -# OP_2DUP: 'OP_2DUP', -# OP_3DUP: 'OP_3DUP', -# OP_2OVER: 'OP_2OVER', -# OP_2ROT: 'OP_2ROT', -# OP_2SWAP: 'OP_2SWAP', -# OP_IFDUP: 'OP_IFDUP', -# OP_DEPTH: 'OP_DEPTH', -# OP_DROP: 'OP_DROP', -# OP_DUP: 'OP_DUP', -# OP_NIP: 'OP_NIP', -# OP_OVER: 'OP_OVER', -# OP_PICK: 'OP_PICK', -# OP_ROLL: 'OP_ROLL', -# OP_ROT: 'OP_ROT', -# OP_SWAP: 'OP_SWAP', -# OP_TUCK: 'OP_TUCK', -# OP_CAT: 'OP_CAT', -# OP_SUBSTR: 'OP_SUBSTR', -# OP_LEFT: 'OP_LEFT', -# OP_RIGHT: 'OP_RIGHT', -# OP_SIZE: 'OP_SIZE', -# OP_INVERT: 'OP_INVERT', -# OP_AND: 'OP_AND', -# OP_OR: 'OP_OR', -# OP_XOR: 'OP_XOR', -# OP_EQUAL: 'OP_EQUAL', -# OP_EQUALVERIFY: 'OP_EQUALVERIFY', -# OP_RESERVED1: 'OP_RESERVED1', -# OP_RESERVED2: 'OP_RESERVED2', -# OP_1ADD: 'OP_1ADD', -# OP_1SUB: 'OP_1SUB', -# OP_2MUL: 'OP_2MUL', -# OP_2DIV: 'OP_2DIV', -# OP_NEGATE: 'OP_NEGATE', -# OP_ABS: 'OP_ABS', -# OP_NOT: 'OP_NOT', -# OP_0NOTEQUAL: 'OP_0NOTEQUAL', -# OP_ADD: 'OP_ADD', -# OP_SUB: 'OP_SUB', -# OP_MUL: 'OP_MUL', -# OP_DIV: 'OP_DIV', -# OP_MOD: 'OP_MOD', -# OP_LSHIFT: 'OP_LSHIFT', -# OP_RSHIFT: 'OP_RSHIFT', -# OP_BOOLAND: 'OP_BOOLAND', -# OP_BOOLOR: 'OP_BOOLOR', -# OP_NUMEQUAL: 'OP_NUMEQUAL', -# OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY', -# OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL', -# OP_LESSTHAN: 'OP_LESSTHAN', -# OP_GREATERTHAN: 'OP_GREATERTHAN', -# OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL', -# OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL', -# OP_MIN: 'OP_MIN', -# OP_MAX: 'OP_MAX', -# OP_WITHIN: 'OP_WITHIN', -# OP_RIPEMD160: 'OP_RIPEMD160', -# OP_SHA1: 'OP_SHA1', -# OP_SHA256: 'OP_SHA256', -# OP_HASH160: 'OP_HASH160', -# OP_HASH256: 'OP_HASH256', -# OP_CODESEPARATOR: 'OP_CODESEPARATOR', -# OP_CHECKSIG: 'OP_CHECKSIG', -# OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY', -# OP_CHECKMULTISIG: 'OP_CHECKMULTISIG', -# OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY', -# OP_NOP1: 'OP_NOP1', -# OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', -# OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', -# OP_CHECKTEMPLATEVERIFY : 'OP_CHECKTEMPLATEVERIFY', -# OP_NOP5: 'OP_NOP5', -# OP_NOP6: 'OP_NOP6', -# OP_NOP7: 'OP_NOP7', -# OP_NOP8: 'OP_NOP8', -# OP_NOP9: 'OP_NOP9', -# OP_NOP10: 'OP_NOP10', -# OP_CHECKSIGADD: 'OP_CHECKSIGADD', -# OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', -# }) -# end -# end From 17e28df170d2f9e0f79fc45ace7b4fe9ffe5d082 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 9 Feb 2023 00:34:22 -0800 Subject: [PATCH 22/40] add full DLC example --- lib/script.ex | 24 +- lib/secp256k1/schnorr.ex | 68 +++++- lib/transaction.ex | 2 + scripts/dlc.exs | 438 +++++++++++++++++++++++++++++++++++ scripts/taproot_keyspend.exs | 2 +- 5 files changed, 516 insertions(+), 18 deletions(-) create mode 100644 scripts/dlc.exs diff --git a/lib/script.ex b/lib/script.ex index 8999d32..fd86724 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -15,7 +15,7 @@ defmodule Bitcoinex.Script do @pubkey_lengths [@tapkey_length, 33, 65] # hash of G.x, used to construct unsolvable internal taproot keys - @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 + @h 0x50929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 @type script_type :: :p2pk | :p2pkh | :p2sh | :p2wpkh | :p2wsh | :p2tr | :multi | :non_standard @@ -642,6 +642,12 @@ defmodule Bitcoinex.Script do @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: {:ok, Bitcoinex.Script.t(), non_neg_integer()} def create_p2tr_script_only(script_tree, r) do + p = calculate_unsolvable_internal_key(r) + {:ok, script} = create_p2tr(p, script_tree) + {:ok, script, r} + end + + def calculate_unsolvable_internal_key(r) do case PrivateKey.new(r) do {:error, msg} -> {:error, msg} @@ -649,16 +655,18 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) - {:ok, script} = - sk - |> PrivateKey.to_point() - |> Math.add(hk) - |> create_p2tr(script_tree) - - {:ok, script, r} + sk + |> PrivateKey.to_point() + |> Math.add(hk) end end + @spec validate_unsolvable_internal_key(t(), Taproot.script_tree(), non_neg_integer) :: boolean + def validate_unsolvable_internal_key(p2tr_script, script_tree, r) do + {:ok, script, _} = create_p2tr_script_only(script_tree, r) + script == p2tr_script + end + @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 824d286..ab9e650 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -17,6 +17,45 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec sign(PrivateKey.t(), non_neg_integer(), non_neg_integer()) :: {:ok, Signature.t()} | {:error, String.t()} def sign(privkey, z, aux) do + case calculate_signature_nonce(privkey, z, aux) do + {:error, msg} -> + {:error, msg} + + {:ok, k, d} -> + sig = sign_with_nonce(d, k, z) + {:ok, sig} + end + end + + @doc """ + sign_with_nonce creates a signature (R,s) from a given + private key sk, nonce k, and sighash z + DANGER: signing different messages with the same sk and k + will leak sk. + """ + def sign_with_nonce(sk, k, z) do + d = Secp256k1.force_even_y(sk) + k = Secp256k1.force_even_y(k) + case {d, k} do + {{:error, _}, _} -> + {:error, "failed to force signing key even"} + {_, {:error, _}} -> + {:error, "failed to force nonce secret even"} + + {d = %PrivateKey{}, k = %PrivateKey{}} -> + r_point = PrivateKey.to_point(k) + d_point = PrivateKey.to_point(d) + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, sk, e) + %Signature{r: r_point.x, s: sig_s} + end + end + + @spec calculate_signature_nonce(PrivateKey.t(), non_neg_integer(), non_neg_integer()) :: + {:error, String.t()} + | {:ok, PrivateKey.t(), PrivateKey.t()} + def calculate_signature_nonce(privkey, z, aux) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -40,20 +79,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) - case Secp256k1.force_even_y(k0) do {:error, msg} -> {:error, msg} k -> - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, k, d} + end end - end + end end end @@ -243,7 +277,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec recover_decryption_key(Signature.t(), Signature.t(), boolean) :: PrivateKey.t() | {:error, String.t()} - def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _, _) when enc_r != r, + def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _) when enc_r != r, do: {:error, "invalid signature pair"} def recover_decryption_key( @@ -254,4 +288,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do t = Math.modulo(s - enc_s, @n) conditional_negate(t, was_negated) end + + # @spec calculate_signature_point(Point.t(), Point.(), <<_::256>>) :: Point.t() | {:error, String.t()} + def calculate_signature_point(r_point, pk, z_bytes) do + e = + calculate_e(Point.x_bytes(r_point), Point.x_bytes(pk), z_bytes) + |> PrivateKey.new() + + case e do + {:error, msg} -> {:error, msg} + + {:ok, e} -> + Math.multiply(pk, e.d) + |> Math.add(r_point) + end + end + end diff --git a/lib/transaction.ex b/lib/transaction.ex index cdae60d..72adf4c 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -94,6 +94,7 @@ defmodule Bitcoinex.Transaction do non_neg_integer(), list(non_neg_integer()), list(<<_::280>>), + # TODO do good caching list({:tapleaf, Taproot.TapLeaf.t()}) ) :: binary def bip341_sigmsg( @@ -186,6 +187,7 @@ defmodule Bitcoinex.Transaction do cached_tx_data <> input_data <> output_data <> ext end + # TODO good caching # The results of this function can be reused across input signings. @spec bip341_tx_data(t(), non_neg_integer(), list(non_neg_integer()), list(<<_::280>>)) :: binary diff --git a/scripts/dlc.exs b/scripts/dlc.exs new file mode 100644 index 0000000..6c25039 --- /dev/null +++ b/scripts/dlc.exs @@ -0,0 +1,438 @@ +alias Bitcoinex.{Secp256k1,Transaction, Script, Utils, Taproot} +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} + + +new_rand_int = fn -> + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() +end + +new_privkey = fn -> + {:ok, sk} = + new_rand_int.() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +multisig_2_of_2_script = fn a, b -> + # Script will be pseudo-multisig: + # OP_CHECKSIGVERIFY OP_CHECKSIG + # Scripts are stacks, so must be inserted in reverse order. + # This also means Alices Signature must come first in the witness_script + s = Script.new() + {:ok, s} = Script.push_op(s, :op_checksig) + {:ok, s} = Script.push_data(s, Point.x_bytes(a)) + {:ok, s} = Script.push_op(s, :op_checksigverify) + {:ok, s} = Script.push_data(s, Point.x_bytes(b)) + s +end + +# Initial setup for this example: give Alice and bob one coin worth 100,010,000 sats each, in order to fund the DLC. +# these ouotputs will be simple keyspend-only P2TRs +alice_init_sk = new_privkey.() +alice_init_pk = PrivateKey.to_point(alice_init_sk) +alice_init_script_tree = nil +{:ok, alice_init_script} = Script.create_p2tr(alice_init_pk, alice_init_script_tree) +{:ok, alice_init_addr} = Script.to_address(alice_init_script, :regtest) + +bob_init_sk = new_privkey.() +bob_init_pk = PrivateKey.to_point(bob_init_sk) +bob_init_script_tree = nil +{:ok, bob_init_script} = Script.create_p2tr(bob_init_pk, bob_init_script_tree) +{:ok, bob_init_addr} = Script.to_address(bob_init_script, :regtest) + +# In your regtest node, send bitcoin to each of these 2 addresses in the amount 100_010_000. +# if you use a different amount, edit the *_init_amount variables below +# note the outpoints for both sends. +alice_init_txid = "57495e49895e87ac3ba2f2467abc6124df166a251a0e304eb770ccc040063af4" +alice_init_vout = 1 +alice_init_amount = 100_010_000 + +bob_init_txid = "783bf305752377c006310d66756d680fa9e33c0e870d024b1c2269aa88f4654c" +bob_init_vout = 1 +bob_init_amount = 100_010_000 + +### BEGIN DLC EXAMPLE ### + +# First, Alice and Bob will create a 2-of-2 funding address + +alice_fund_sk = new_privkey.() +alice_fund_pk = PrivateKey.to_point(alice_fund_sk) + +bob_fund_sk = new_privkey.() +bob_fund_pk = PrivateKey.to_point(bob_fund_sk) + +fund_script = multisig_2_of_2_script.(alice_fund_pk, bob_fund_pk) +# If you want to examine the script, uncomment next line: +# Script.display_script(fund_script) + +fund_leaf = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), fund_script) + +# WARNING: this address only has 1 spend option: a 2-of-2 multisig. Without additional timeout +# spendpaths (allowing both parties to recover funds if the other disappears), this is a trusted +# and unsafe contract to enter. TODO: add timeout allowing both parties to reclaim funds. + +# P2TR for funding addr will have no internal key. Only way to spend is to satisfy the 2-of-2 +# TODO: when MuSig is implemented, the KeySpend route can act as 2-of-2 instead, and is cheaper. +{:ok, fund_scriptpubkey, r} = Script.create_p2tr_script_only(fund_leaf, new_rand_int.()) + +# in the above, r is a random number used to create a unique but provably unspendable internal key +# in the case that we don't want a keypath spend to be possible. If either alice or bob generated the funding +# scriptpubkey, the other should verify that the internal key is unspendable and reconstruct the same script +# to ensure they know what contract they are sending their BTC to. +fund_p = Script.calculate_unsolvable_internal_key(r) +is_correct_p2tr = Script.validate_unsolvable_internal_key(fund_scriptpubkey, fund_leaf, r) + +# ALICE NOR BOB should fund this address until both CETs are signed with adaptor sigs +{:ok, fund_addr} = Script.to_address(fund_scriptpubkey, :regtest) + +# We create the funding transaction. +init_amounts = [alice_init_amount, bob_init_amount] +init_scriptpubkeys = [ + Script.serialize_with_compact_size(alice_init_script), + Script.serialize_with_compact_size(bob_init_script) +] + +fund_amount = 200_010_000 +fund_tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: alice_init_txid, + prev_vout: alice_init_vout, + script_sig: "", + sequence_no: 2147483648 + }, + %Transaction.In{ + prev_txid: bob_init_txid, + prev_vout: bob_init_vout, + script_sig: "", + sequence_no: 2147483648 + } + ], + outputs: [ + %Transaction.Out{ + # fee of 10_000 sats paid for this tx. This means you'll probably have to disable max fee constraints in bitcoind to broadcast txs + # the next 10_000 will go towards spending this output to settle the contract + value: fund_amount, + script_pub_key: Script.to_hex(fund_scriptpubkey) + } + # A more realistic example would have change outputs to each party here. Omitted for simplicity + ], + lock_time: 0 +} + +# even though the fund tx is not signed, we can calculate the txid and use that to create contract execution txs (CETs) +# which will spend the output created in this tx +fund_txid = Transaction.transaction_id(fund_tx) +# d4be73514a8535c6e21a6007b0de049ba3d4d7d93ee953852c4b7ce97369ff49 +fund_vout = 0 +fund_amounts = [fund_amount] +fund_scriptpubkeys = [Script.serialize_with_compact_size(fund_scriptpubkey)] + +# Oracle Identity & Signing Key +oracle_sk = new_privkey.() +oracle_pk = PrivateKey.to_point(oracle_sk) + +# Oracle creates 2 tweak/point pairs, one for each possible outcome +# The bet will be a simple MOON or CRASH bet. + +# the same nonce must be used for both outcomes in order to guarantee that the Oracle +# cannot sign both events without leaking their own private key. In a more trust-minimized +# example, the Oracle should prove ownership of a UTXO with the public key they use for +# the signing, in order to prove that they have something at stake if they should sign both events. + +# Oracle does not use the standard BIP340 method for generating a nonce. +# This is because the nonce must not commit to the message, so that it can +# be reused for either outcome. +oracle_event_nonce = new_privkey.() +event_nonce_point = PrivateKey.to_point(oracle_event_nonce) + +moon_msg = "MOON! the price rose" +crash_msg = "CRASH! the price fell" + +# Oracle Broadcasts its intention to sign one of the 2 events +public = %{ + oracle_pk: oracle_pk, + event_nonce_point: event_nonce_point, + case1: moon_msg, + case2: crash_msg + # note, arbitrary number of outcomes are workable here. +} + +# Alice And Bob can now compute the Signature Point, which they will use as the tweak point for their adaptor signatures +moon_sighash = Utils.double_sha256(public.case1) +crash_sighash = Utils.double_sha256(public.case2) + +moon_sig_point = Schnorr.calculate_signature_point(public.event_nonce_point, oracle_pk, moon_sighash) +crash_sig_point = Schnorr.calculate_signature_point(public.event_nonce_point, oracle_pk, crash_sighash) + +# Alice and Bob create CETs (Settlement Transactions, which will spend the funding tx (not yet signed/broadcasted)) + +# Alice and Bob each need Addresses to settle to +# For simplicity, these dest addresses will be internal key only Taproot addresses +alice_dest_sk = new_privkey.() +alice_dest_pk = PrivateKey.to_point(alice_dest_sk) +{:ok, alice_dest_script} = Script.create_p2tr(alice_dest_pk, nil) +{:ok, alice_dest_addr} = Script.to_address(alice_dest_script, :regtest) + +bob_dest_sk = new_privkey.() +bob_dest_pk = PrivateKey.to_point(bob_dest_sk) +{:ok, bob_dest_script} = Script.create_p2tr(bob_dest_pk, nil) +{:ok, bob_dest_addr} = Script.to_address(bob_dest_script, :regtest) + + +# CET hash type will be sighash default for both, for simplicity +cet_hash_type = 0x00 + + +# First CET: MOON, alice wins, and gets 75% of the funding tx (excluding fees) +moon_cet = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: fund_txid, + prev_vout: fund_vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + # ALICE WINS! gets 150M sats from the 1M she put in + %Transaction.Out{ + value: 150_000_000, + script_pub_key: Script.to_hex(alice_dest_script) + }, + # BOB LOSES :( gets 50M sats from the 1M he put in + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(bob_dest_script) + } + ], + lock_time: 0 +} +# calculate the sighash for the MOON CET +moon_cet_sighash = Transaction.bip341_sighash( + moon_cet, + cet_hash_type, # sighash_default (all) + 0x01, # we are using taproot scriptpath spend, so ext_flag must be 1 + 0, # index we're going to sign + fund_amounts, # list of amounts for each input being spent + fund_scriptpubkeys, # list of prev scriptpubkeys for each input being spent + tapleaf: fund_leaf +) |> :binary.decode_unsigned() + +# Second CET: CRASH, bob wins, and gets 75% of the funding tx (excluding fees) +crash_cet = %Transaction{ + version: 1, + inputs: [ + # Notice this input is the same coin spent in the Moon CET tx. So they can't both be valid. + %Transaction.In{ + prev_txid: fund_txid, + prev_vout: fund_vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + # ALICE LOSES :( gets 50M sats from the 1M she put in + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(alice_dest_script) + }, + # BOB WINS! gets 150M sats from the 1M he put in + %Transaction.Out{ + value: 150_000_000, + script_pub_key: Script.to_hex(bob_dest_script) + } + ], + lock_time: 0 +} +# calculate the Sighash for the CRASH CET +crash_cet_sighash = Transaction.bip341_sighash( + crash_cet, + cet_hash_type, # sighash_default (all) + 0x01, # we are using taproot scriptpath spend, so ext_flag = 1 + 0, # only one input in this tx + fund_amounts, # list of amounts for each input being spent + fund_scriptpubkeys, # list of prev scriptpubkeys for each input being spent + tapleaf: fund_leaf +) |> :binary.decode_unsigned() + +# Alice and Bob now create adaptor signatures for each of these CETs. They must share both of their +# Adaptor Signatures with one another. These next 4 steps are in no particular order. + +# Alice creates adaptor sig for Crash Case using crash_sig_point (tweak point/encryption key). +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, alice_crash_adaptor_sig, alice_crash_was_negated} = Schnorr.encrypted_sign(alice_fund_sk, crash_cet_sighash, aux_rand, crash_sig_point) + +# Bob creates adaptor sig for Moon Case using moon_sig_point (tweak point/encryption key). +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, bob_moon_adaptor_sig, bob_moon_was_negated} = Schnorr.encrypted_sign(bob_fund_sk, moon_cet_sighash, aux_rand, moon_sig_point) + +# Alice creates adaptor sig for Moon Case using moon_sig_point. +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, alice_moon_adaptor_sig, alice_moon_was_negated} = Schnorr.encrypted_sign(alice_fund_sk, moon_cet_sighash, aux_rand, moon_sig_point) + +# Bob creates adaptor sig for Crash Case using crash_sig_point. +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, bob_crash_adaptor_sig, bob_crash_was_negated} = Schnorr.encrypted_sign(bob_fund_sk, crash_cet_sighash, aux_rand, crash_sig_point) + +# Verification Time! Alice and Bob must each verify one another's adaptor signatures to ensure they will be +# valid once the Oracle publishes the resolution signature + +# Bob verifies Alice's Crash signature +is_valid = Schnorr.verify_encrypted_signature(alice_crash_adaptor_sig, alice_fund_pk, crash_cet_sighash, crash_sig_point, alice_crash_was_negated) + +# Alice verifies Bob's Moon signature +is_valid = Schnorr.verify_encrypted_signature(bob_moon_adaptor_sig, bob_fund_pk, moon_cet_sighash, moon_sig_point, bob_moon_was_negated) + +# Bob verifies Alice's Moon signature +is_valid = Schnorr.verify_encrypted_signature(alice_moon_adaptor_sig, alice_fund_pk, moon_cet_sighash, moon_sig_point, alice_moon_was_negated) + +# Alice verifies Bob's Crash signature +is_valid = Schnorr.verify_encrypted_signature(bob_crash_adaptor_sig, bob_fund_pk, crash_cet_sighash, crash_sig_point, bob_crash_was_negated) + +# IFF all four adaptor signatures are valid, we're good to go! + +# Now that each party has the other's adaptor signatures, they can sign and broadcast the +# Funding transaction (that was created way above). They could not previously do so safely, +# because if they had locked BTC in the 2-of-2 multisig before having their counterparty's +# Adaptor signatures, they could have lost those funds if the counterparty disappeared. +# NOTE: Again, the funding tx has no timeouts in place, so if the Oracle AND a counterparty disappear, +# those funds are locked irretreivably. + +fund_hash_type = 0x00 # sighash_default (all) +fund_ext_flag = 0 # both parties are using keyspend +# This would be done independently by both parties, or collaboratively using PSBT, +alice_fund_sighash = Transaction.bip341_sighash( + fund_tx, + fund_hash_type, + fund_ext_flag, + 0, # alice's input comes first + init_amounts, + init_scriptpubkeys +) |> :binary.decode_unsigned() + +bob_fund_sighash = Transaction.bip341_sighash( + fund_tx, + fund_hash_type, + fund_ext_flag, + 1, # bob's input comes second + init_amounts, + init_scriptpubkeys +) |> :binary.decode_unsigned() + +# each party signs the funding transaction, moving their BTC into the 2-of-2 multisig. + +# In order to sign the tx with the internal private key, they must first tweak it +# so that it can sign for the external taproot key +{_, alice_init_merkle_root_hash} = Taproot.merkelize_script_tree(alice_init_script_tree) +alice_q = Taproot.tweak_privkey(alice_init_sk, alice_init_merkle_root_hash) +aux_rand = new_rand_int.() # more entropy +{:ok, alice_fund_sig} = Schnorr.sign(alice_q, alice_fund_sighash, aux_rand) + +{_, bob_init_merkle_root_hash} = Taproot.merkelize_script_tree(bob_init_script_tree) +aux_rand = new_rand_int.() # even more entropy +bob_q = Taproot.tweak_privkey(bob_init_sk, bob_init_merkle_root_hash) +{:ok, bob_fund_sig} = Schnorr.sign(bob_q, bob_fund_sighash, aux_rand) + +fund_hash_byte = + if fund_hash_type == 0x00 do + <<>> + else + <> + end + +alice_sig_hex = Signature.serialize_signature(alice_fund_sig) <> fund_hash_byte |> Base.encode16(case: :lower) +alice_witness = %Transaction.Witness{ + txinwitness: [ + alice_sig_hex + ] +} + +# NEXT: construct fund witness. +bob_sig_hex = Signature.serialize_signature(bob_fund_sig) <> fund_hash_byte |> Base.encode16(case: :lower) +bob_witness = %Transaction.Witness{ + txinwitness: [ + bob_sig_hex + ] +} + +fund_tx = %Transaction{fund_tx | witnesses: [ + alice_witness, bob_witness +]} + +fund_tx_hex = Transaction.Utils.serialize(fund_tx) |> Base.encode16(case: :lower) +# 5ac6d378bdf1c55b08e92a03de343797b662cb953cd8f3bbfb7e0108bbba7841 + +# FUND TX IS READY TO BROADCAST! +# bitcoin-cli sendrawtransaction 0 +# The 0 at the end disables max_fee constraints. Only do this if you're willing to pay a high fee. + +# ...wait for oracle to announce result... + +# RESOLUTION 1: MOON! Skip to RESOLUTION 2 to execute the CRASH scenario instead + +# Oracle Publishes: +moon_sig = Schnorr.sign_with_nonce(oracle_sk, oracle_event_nonce, :binary.decode_unsigned(moon_sighash)) + +# Alice & Bob should make sure this is a valid Schnorr signature +is_valid = Schnorr.verify_signature(oracle_pk, :binary.decode_unsigned(moon_sighash), moon_sig) + +# alice & bob can both now extract the moon_tweak, which is moon_sig.s and complete their moon_adaptor sigs. +# Since alice won, she has more incentive to do so +%Signature{s: settlement_secret} = moon_sig + +{:ok, settlement_secret} = PrivateKey.new(settlement_secret) + +cet_hash_byte = + if cet_hash_type == 0x00 do + <<>> + else + <> + end + +# Then, Alice can decrypt Bob's signature +bob_moon_sig = Schnorr.decrypt_signature(bob_moon_adaptor_sig, settlement_secret, bob_moon_was_negated) +# why not verify? +is_valid = Schnorr.verify_signature(bob_fund_pk, moon_cet_sighash, bob_moon_sig) + + +# Alice can also decrypt her own adaptor signature +alice_moon_sig = Schnorr.decrypt_signature(alice_moon_adaptor_sig, settlement_secret, alice_moon_was_negated) +# Don't trust, verify +is_valid = Schnorr.verify_signature(alice_fund_pk, moon_cet_sighash, alice_moon_sig) + +# fund_p is the internal taproot key. In this case, it is unsolvable, as verified earlier. +# we take fund_leaf, the script_tree from earlier, and select the index of the script we want to spend. +# Here, there is only 1 script in the tree, so idx must be 0 +control_block = Taproot.build_control_block(fund_p, fund_leaf, 0) + +# serialize everything for insertion into the tx +bob_moon_sig_hex = Signature.serialize_signature(bob_moon_sig) <> cet_hash_byte |> Base.encode16(case: :lower) +alice_moon_sig_hex = Signature.serialize_signature(alice_moon_sig) <> cet_hash_byte |> Base.encode16(case: :lower) +fund_script_hex = Script.to_hex(fund_script) +control_block_hex = control_block |> Base.encode16(case: :lower) + + +# She then adds these to the Moon CET and broadcasts it +tx = %Transaction{moon_cet | witnesses: [ + %Transaction.Witness{ + txinwitness: [bob_moon_sig_hex, alice_moon_sig_hex, fund_script_hex, control_block_hex] + } +] +} + +tx = %Transaction{moon_cet | witnesses: [ + %Transaction.Witness{ + txinwitness: [alice_moon_sig_hex, bob_moon_sig_hex, fund_script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + + +# RESOLUTION 2: CRASH! diff --git a/scripts/taproot_keyspend.exs b/scripts/taproot_keyspend.exs index 8229459..e6a99c9 100644 --- a/scripts/taproot_keyspend.exs +++ b/scripts/taproot_keyspend.exs @@ -26,7 +26,7 @@ script_tree = nil # EDIT to the unique addr # addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" # EDIT to the txid of your send to the addr above -txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +txid = "9d539c9fc4a17ddd920e0bd8d5d43f92382a36765f48a7725e92e184a21a50d7" # EDIT to the vout of the output you created for the addr vout = 0 # EDIT to the amount you sent in the UTXO above From 0d18d7a2038597a5c47a9fde6f928c3dbce6baae Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 9 Feb 2023 08:44:37 -0800 Subject: [PATCH 23/40] syntax --- scripts/dlc.exs | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/dlc.exs b/scripts/dlc.exs index 6c25039..11b8c9a 100644 --- a/scripts/dlc.exs +++ b/scripts/dlc.exs @@ -182,11 +182,9 @@ bob_dest_pk = PrivateKey.to_point(bob_dest_sk) {:ok, bob_dest_script} = Script.create_p2tr(bob_dest_pk, nil) {:ok, bob_dest_addr} = Script.to_address(bob_dest_script, :regtest) - # CET hash type will be sighash default for both, for simplicity cet_hash_type = 0x00 - # First CET: MOON, alice wins, and gets 75% of the funding tx (excluding fees) moon_cet = %Transaction{ version: 1, From 45ea0b3dcf34163cbdd78cbd984030d9c4ec29eb Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 12 Feb 2023 11:12:03 -0800 Subject: [PATCH 24/40] add spec for function --- lib/script.ex | 2 ++ scripts/dlc.exs | 9 +-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index fd86724..e480bb2 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -647,6 +647,8 @@ defmodule Bitcoinex.Script do {:ok, script, r} end + @spec calculate_unsolvable_internal_key(non_neg_integer) :: + {:error, String.t()} | Bitcoinex.Secp256k1.Point.t() def calculate_unsolvable_internal_key(r) do case PrivateKey.new(r) do {:error, msg} -> diff --git a/scripts/dlc.exs b/scripts/dlc.exs index 11b8c9a..697aab0 100644 --- a/scripts/dlc.exs +++ b/scripts/dlc.exs @@ -135,8 +135,7 @@ fund_scriptpubkeys = [Script.serialize_with_compact_size(fund_scriptpubkey)] oracle_sk = new_privkey.() oracle_pk = PrivateKey.to_point(oracle_sk) -# Oracle creates 2 tweak/point pairs, one for each possible outcome -# The bet will be a simple MOON or CRASH bet. +# The bet will be a simple EAGLES or CHIEFS bet. # the same nonce must be used for both outcomes in order to guarantee that the Oracle # cannot sign both events without leaking their own private key. In a more trust-minimized @@ -416,12 +415,6 @@ control_block_hex = control_block |> Base.encode16(case: :lower) # She then adds these to the Moon CET and broadcasts it -tx = %Transaction{moon_cet | witnesses: [ - %Transaction.Witness{ - txinwitness: [bob_moon_sig_hex, alice_moon_sig_hex, fund_script_hex, control_block_hex] - } -] -} tx = %Transaction{moon_cet | witnesses: [ %Transaction.Witness{ From bbce5db2f6aa0407e21a2247cee684e53bb3b5ed Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 21 Feb 2023 16:57:00 -0800 Subject: [PATCH 25/40] add add-field funcs --- lib/psbt.ex | 226 +++++++++++++++++++++++++++++++++++++-------- lib/transaction.ex | 4 + 2 files changed, 191 insertions(+), 39 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index a49defa..15b0420 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -464,6 +464,129 @@ defmodule Bitcoinex.PSBT.In do @psbt_in_tap_merkle_root 0x18 @psbt_in_proprietary 0xFC + @minimum_time_locktime Transaction.minimum_time_locktime() + + def add_non_witness_utxo(input, tx = %Transaction{}) when input.non_witness_utxo == nil do + %In{input | non_witness_utxo: tx} + end + + def add_witness_utxo(input, utxo = %Out{}) do + %In{input | witness_utxo: utxo} + end + + def add_partial_sig(input, sig = %{public_key: _, signature: _}) do + sigs = PsbtUtils.append(input.partial_sig, sig) + %In{input | partial_sig: sigs} + end + + # TODO only allow real sighash values? + def add_sighash_type(input, sighash_type) when is_integer(sighash_type) and sighash_type >= 0 do + %In{input | sighash_type: sighash_type} + end + + def add_redeem_script(input, redeem_script) do + {:ok, _} = Base.decode16(redeem_script, case: :lower) + %In{input | redeem_script: redeem_script} + end + + def add_witness_script(input, witness_script) do + {:ok, _} = Base.decode16(witness_script, case: :lower) + %In{input | witness_script: witness_script} + end + + def add_bip32_derivation(input, derivation = %{public_key: _, pfp: _, derivation: _}) do + derivations = PsbtUtils.append(input.bip32_derivation, derivation) + %In{input | bip32_derivation: derivations} + end + + def add_final_scriptsig(input, final_scriptsig) do + {:ok, _} = Base.decode16(final_scriptsig, case: :lower) + %In{input | final_scriptsig: final_scriptsig} + end + + def add_final_scriptwitness(input, final_scriptwitness = %Transaction.Witness{}) do + %In{input | final_scriptwitness: final_scriptwitness} + end + + def add_por_commitment(input, por_commitment) when is_binary(por_commitment) do + %In{input | por_commitment: por_commitment} + end + + def add_ripemd160(input, ripemd160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + ripemd160s = PsbtUtils.append(input.ripemd160, ripemd160) + %In{input | ripemd160: ripemd160s} + end + + def add_sha256(input, sha256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + sha256s = PsbtUtils.append(input.sha256, sha256) + %In{input | sha256: sha256s} + end + + def add_hash160(input, hash160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + hash160s = PsbtUtils.append(input.hash160, hash160) + %In{input | hash160: hash160s} + end + + def add_hash256(input, hash256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + hash256s = PsbtUtils.append(input.hash256, hash256) + %In{input | hash256: hash256s} + end + + def add_previous_txid(input, <>) do + %In{input | previous_txid: previous_txid} + end + + def add_output_index(input, output_index) when is_integer(output_index) and output_index >= 0 do + %In{input | output_index: output_index} + end + + def add_sequence(input, sequence) when is_integer(sequence) and sequence >= 0 do + %In{input | sequence: sequence} + end + + def add_required_time_locktime(input, locktime) when is_integer(locktime) and locktime >= @minimum_time_locktime do + %In{input | required_time_locktime: locktime} + end + + def add_required_height_locktime(input, locktime) when is_integer(locktime) and locktime < @minimum_time_locktime do + %In{input | required_height_locktime: locktime} + end + + def add_tap_key_sig(input, tap_key_sig) when is_binary(tap_key_sig) and byte_size(tap_key_sig) in [64, 65] do + %In{input | tap_key_sig: tap_key_sig} + end + + + def add_tap_script_sig(input, tap_script_sig = %{pubkey: _, leaf_hash: _, signature: _}) do + sigs = PsbtUtils.append(input.tap_script_sig, tap_script_sig) + %In{input | tap_script_sig: sigs} + end + + # TODO:taproot make this TapLeaf + def add_tap_leaf_script(input, tap_leaf_script = %{ leaf_version: _, script: _, control_block: _}) do + scripts = PsbtUtils.append(input.tap_leaf_script, tap_leaf_script) + %In{input | tap_leaf_script: scripts} + end + + def add_tap_bip32_derivation(input, tap_bip32_derivation = %{pubkey: _, leaf_hashes: _, pfp: _, derivation: _}) do + derivations = PsbtUtils.append(input.tap_bip32_derivation, tap_bip32_derivation) + %In{input | tap_bip32_derivation: derivations} + end + + def add_tap_internal_key(input, <>) do + %In{input | tap_internal_key: tap_internal_key} + end + + @spec add_tap_merkle_root(%In{}, <<_::256>>) :: %In{} + def add_tap_merkle_root(input, <>) do + %In{input | tap_merkle_root: tap_merkle_root} + end + + @spec add_proprietary(%In{}, binary) :: %In{} + def add_proprietary(input, proprietary) when is_binary(proprietary) do + %In{input | proprietary: proprietary} + end + def parse_inputs(psbt, num_inputs) do psbt |> parse_input([], num_inputs) @@ -755,14 +878,14 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {:ok, txn} = Transaction.decode(value) - input = %In{input | non_witness_utxo: txn} + input = add_non_witness_utxo(input, txn) {input, psbt} end defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) out = Out.output(value) - input = %In{input | witness_utxo: out} + input = add_witness_utxo(input, out) {input, psbt} end @@ -774,15 +897,14 @@ defmodule Bitcoinex.PSBT.In do signature: Base.encode16(value, case: :lower) } - partial_sigs = PsbtUtils.append(input.partial_sig, partial_sig) - input = %In{input | partial_sig: partial_sigs} + input = add_partial_sig(input, partial_sig) {input, psbt} end defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | sighash_type: value} + input = add_sighash_type(input, value) {input, psbt} end @@ -794,7 +916,7 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | witness_script: Base.encode16(value, case: :lower)} + input = add_witness_script(input, Base.encode16(value, case: :lower)) {input, psbt} end @@ -809,21 +931,19 @@ defmodule Bitcoinex.PSBT.In do derivation: path } - bip32_derivation = PsbtUtils.append(input.bip32_derivation, derivation) - - input = %In{input | bip32_derivation: bip32_derivation} + input = add_bip32_derivation(input, derivation) {input, psbt} end defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | final_scriptsig: Base.encode16(value, case: :lower)} + input = add_final_scriptsig(input, Base.encode16(value, case: :lower)) {input, psbt} end defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | por_commitment: value} + input = add_por_commitment(input, value) {input, psbt} end @@ -836,9 +956,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - ripemd160s = PsbtUtils.append(input.ripemd160, data) - - input = %In{input | ripemd160: ripemd160s} + input = add_ripemd160(input, data) {input, psbt} end @@ -851,8 +969,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - sha256s = PsbtUtils.append(input.sha256, data) - input = %In{input | sha256: sha256s} + input = add_sha256(input, data) {input, psbt} end @@ -865,8 +982,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - hash160s = PsbtUtils.append(input.hash160, data) - input = %In{input | hash160: hash160s} + input = add_hash160(input, data) {input, psbt} end @@ -879,47 +995,43 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - hash256s = PsbtUtils.append(input.hash256, data) - input = %In{input | hash256: hash256s} + input = add_hash256(input, data) {input, psbt} end defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do {value = <<_::binary-size(32)>>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | previous_txid: value} + input = add_previous_txid(input, value) {input, psbt} end defp parse(<<@psbt_in_output_index::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | output_index: value} + input = add_output_index(input, value) {input, psbt} end defp parse(<<@psbt_in_sequence::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | sequence: value} + input = add_sequence(input, value) {input, psbt} end defp parse(<<@psbt_in_required_time_locktime::big-size(8)>>, psbt, input) do - # TODO:validation must be > 500_000_000 {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | required_time_locktime: value} + input = add_required_time_locktime(input, value) {input, psbt} end defp parse(<<@psbt_in_required_height_locktime::big-size(8)>>, psbt, input) do - # TODO:validation must be < 500_000_000 {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | required_height_locktime: value} + input = add_required_height_locktime(input, value) {input, psbt} end defp parse(<<@psbt_in_tap_key_sig::big-size(8)>>, psbt, input) do - # TODO:validation validate script len (64|65) {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | tap_key_sig: value} + input = add_tap_key_sig(input, value) {input, psbt} end @@ -938,9 +1050,7 @@ defmodule Bitcoinex.PSBT.In do signature: value } - tap_script_sigs = PsbtUtils.append(input.tap_script_sig, data) - - input = %In{input | tap_script_sig: tap_script_sigs} + input = add_tap_script_sig(input, data) {input, psbt} end @@ -956,8 +1066,7 @@ defmodule Bitcoinex.PSBT.In do control_block: control_block } - tap_leaf_scripts = PsbtUtils.append(input.tap_leaf_script, data) - input = %In{input | tap_leaf_script: tap_leaf_scripts} + input = add_tap_leaf_script(input, data) {input, psbt} end @@ -975,9 +1084,7 @@ defmodule Bitcoinex.PSBT.In do derivation: path } - tap_bip32_derivation = PsbtUtils.append(input.tap_bip32_derivation, derivation) - - input = %In{input | tap_bip32_derivation: tap_bip32_derivation} + input = add_tap_bip32_derivation(input, derivation) {input, psbt} end @@ -1002,7 +1109,7 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) value = Witness.witness(value) - input = %In{input | final_scriptwitness: value} + input = add_final_scriptwitness(input, value) {input, psbt} end @@ -1055,6 +1162,46 @@ defmodule Bitcoinex.PSBT.Out do @psbt_out_tap_bip32_derivation 0x07 @psbt_out_proprietary 0xFC + def add_redeem_script(output, redeem_script) when is_binary(redeem_script) and output.redeem_script == nil do + %Out{output | redeem_script: redeem_script} + end + + def add_witness_script(output, witness_script) when is_binary(witness_script) and output.witness_script == nil do + %Out{output | witness_script: witness_script} + end + + def add_bip32_derivation(output, derivation = %{public_key: _, pfp: _, derivation: _}) do + # ensure no duplicate keys? + derivations = PsbtUtils.append(output.bip32_derivation, derivation) + %Out{output | bip32_derivation: derivations} + end + + def add_amount(output, amount) when is_integer(amount) and amount > 0 do + %Out{output | amount: amount} + end + + def add_script(output, script) when is_binary(script) do + %Out{output | script: script} + end + + def add_tap_internal_key(output, pk) when is_binary(pk) do + %Out{output | tap_internal_key: pk} + end + + # TODO:taproot find a good format for taptree + def add_tap_tree(output, _tree) do + output + end + + def add_tap_bip32_derivation(output, derivation = %{public_key: _, pfp: _, derivation: _}) do + derivations = PsbtUtils.append(output.tap_bip32_derivation, derivation) + %Out{output | tap_bip32_derivation: derivations} + end + + def add_proprietary(output, kv) when is_binary(kv) do + kvs = PsbtUtils.append(output.proprietary, kv) + %Out{output | proprietary: kvs} + end def serialize_outputs(outputs) when is_list(outputs) and length(outputs) > 0 do serialize_output(outputs, <<>>) end @@ -1258,7 +1405,8 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_proprietary::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | proprietary: value} + kvs = PsbtUtils.append(output.proprietary, value) + output = %Out{output | proprietary: kvs} {output, psbt} end diff --git a/lib/transaction.ex b/lib/transaction.ex index 7e72972..2b708bf 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -27,6 +27,10 @@ defmodule Bitcoinex.Transaction do :lock_time ] + @minimum_time_locktime 500_000_000 + + def minimum_time_locktime(), do: @minimum_time_locktime + @doc """ Returns the TxID of the given tranasction. From 20623954e8d353ab13355adba89302ed7236205c Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 22 Feb 2023 11:59:13 -0800 Subject: [PATCH 26/40] add specs and global add-field funcs --- lib/psbt.ex | 179 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 131 insertions(+), 48 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 15b0420..44e5a82 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -93,7 +93,7 @@ defmodule Bitcoinex.PSBT do }} end - @spec from_tx(Transaction.t()) :: {:ok, Bitcoinex.PSBT.t()} + @spec from_tx(Transaction.t()) :: {:ok, PSBT.t()} def from_tx(tx) do inputs = In.from_tx_inputs(tx.inputs, tx.witnesses) outputs = Out.from_tx_outputs(tx.outputs) @@ -106,6 +106,7 @@ defmodule Bitcoinex.PSBT do }} end + @spec to_tx(PSBT.t()) :: %Bitcoinex.Transaction{} def to_tx(psbt) do tx = psbt.global.unsigned_tx @@ -152,6 +153,7 @@ defmodule Bitcoinex.PSBT.Utils do key_len <> key <> val_len <> val end + @spec serialize_repeatable_fields(atom, list(any), any) :: binary def serialize_repeatable_fields(_, nil, _), do: <<>> def serialize_repeatable_fields(field, values, serialize_func) do @@ -159,6 +161,7 @@ defmodule Bitcoinex.PSBT.Utils do |> :erlang.list_to_binary() end + @spec parse_fingerprint_path(<<_::32, _::_*8>>) :: {<<_::32>>, DerivationPath.t()} def parse_fingerprint_path(data) do <> = data {:ok, path} = DerivationPath.parse(path_bin) @@ -184,6 +187,7 @@ defmodule Bitcoinex.PSBT.Utils do TxUtils.serialize_compact_size_unsigned_int(length(leaf_hashes)) <> leaf_hashes_bin end + @spec append(nil | list, any) :: [any] def append(nil, item), do: [item] def append(items, item), do: items ++ [item] end @@ -222,11 +226,67 @@ defmodule Bitcoinex.PSBT.Global do @psbt_global_version 0xFB @psbt_global_proprietary 0xFC + @spec add_unsigned_tx(%Global{}, Transaction.t()) :: %Global{} + def add_unsigned_tx(global, unsigned_tx = %Transaction{}) when global.unsigned_tx == nil do + %Global{global | unsigned_tx: unsigned_tx} + end + + @spec add_xpub(%Global{}, %{xpub: ExtendedKey.t(), pfp: <<_::64>>, derivation: DerivationPath.t()}) :: %Global{} + def add_xpub(global, global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::64>>, derivation: %DerivationPath{}}) do + global_xpubs = PsbtUtils.append(global.xpub, global_xpub) + %Global{global | xpub: global_xpubs} + end + + @spec add_tx_version(%Global{}, pos_integer) :: %Global{} + def add_tx_version(global, value) when global.tx_version == nil and value > 0 do + %Global{global | tx_version: value} + end + + @spec add_fallback_locktime(%Global{}, non_neg_integer) :: %Global{} + def add_fallback_locktime(global, value) when value >= 0 do + %Global{global | fallback_locktime: value} + end + + @spec add_input_count(%Global{}, non_neg_integer()) :: %Global{} + def add_input_count(global, input_count) when input_count > 0 do + %Global{global | input_count: input_count} + end + + @spec add_output_count(%Global{}, non_neg_integer()) :: %Global{} + def add_output_count(global, output_count) when output_count > 0 do + %Global{global | output_count: output_count} + end + + @spec add_tx_modifiable(%Global{}, non_neg_integer()) :: %Global{} + def add_tx_modifiable(global, value) do + %Global{global | tx_modifiable: value} + end + + @spec add_version(%Global{}, non_neg_integer()) :: %Global{} + def add_version(global, value) do + %Global{global | version: value} + end + + @spec add_proprietary(%Global{}, binary) :: %Global{} + def add_proprietary(global, value) do + proprietaries = PsbtUtils.append(global.proprietary, value) + %Global{global | proprietary: proprietaries} + end + + @spec parse_global(nonempty_binary) :: {%Global{}, binary} def parse_global(psbt) do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) end - def from_tx(tx), do: %Global{unsigned_tx: tx} + @spec from_tx(%Transaction{}) :: %Global{} + def from_tx(tx) do + %Global{ + unsigned_tx: tx, + tx_version: tx.version, + input_count: length(tx.inputs), + output_count: length(tx.outputs) + } + end # unsigned transaction defp parse(<<@psbt_global_unsigned_tx::big-size(8)>>, psbt, global) do @@ -236,7 +296,8 @@ defmodule Bitcoinex.PSBT.Global do case Transaction.decode(txn_bytes) do {:ok, txn} -> - {%Global{global | unsigned_tx: txn}, psbt} + global = add_unsigned_tx(global, txn) + {global, psbt} {:error, error_msg} -> {:error, error_msg} @@ -261,54 +322,51 @@ defmodule Bitcoinex.PSBT.Global do derivation: path } - global_xpubs = PsbtUtils.append(global.xpub, global_xpub) - - global = %Global{global | xpub: global_xpubs} - + global = add_xpub(global, global_xpub) {global, psbt} end defp parse(<<@psbt_global_tx_version::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | tx_version: value} + global = add_tx_version(global, value) {global, psbt} end defp parse(<<@psbt_global_fallback_locktime::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | fallback_locktime: value} + global = add_fallback_locktime(global, value) {global, psbt} end defp parse(<<@psbt_global_input_count::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {input_count, _} = TxUtils.get_counter(value) - global = %Global{global | input_count: input_count} + global = add_input_count(global, input_count) {global, psbt} end defp parse(<<@psbt_global_output_count::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {output_count, _} = TxUtils.get_counter(value) - global = %Global{global | output_count: output_count} + global = add_output_count(global, output_count) {global, psbt} end defp parse(<<@psbt_global_tx_modifiable::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | tx_modifiable: value} + global = add_tx_modifiable(global, value) {global, psbt} end defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | version: value} + global = add_version(global, value) {global, psbt} end defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | proprietary: value} + global = add_proprietary(global, value) {global, psbt} end @@ -358,6 +416,11 @@ defmodule Bitcoinex.PSBT.Global do PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8)>>, value) end + defp serialize_kv(:unknown, %{key: k, value: v}) do + PsbtUtils.serialize_kv(k, v) + end + + @spec serialize_global(%Global{}) :: nonempty_binary def serialize_global(global) do serialized_global = Enum.reduce( @@ -587,6 +650,11 @@ defmodule Bitcoinex.PSBT.In do %In{input | proprietary: proprietary} end + def add_unknown(input, kv) do + unknowns = PsbtUtils.append(input.unknown, kv) + %In{input | unknown: unknowns} + end + def parse_inputs(psbt, num_inputs) do psbt |> parse_input([], num_inputs) @@ -910,7 +978,7 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | redeem_script: Base.encode16(value, case: :lower)} + input = add_redeem_script(input, Base.encode16(value, case: :lower)) {input, psbt} end @@ -1090,19 +1158,19 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_tap_internal_key::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | tap_internal_key: value} + input = add_tap_internal_key(input, value) {input, psbt} end defp parse(<<@psbt_in_tap_merkle_root::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | tap_merkle_root: value} + input = add_tap_merkle_root(input, value) {input, psbt} end defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | proprietary: value} + input = add_proprietary(input, value) {input, psbt} end @@ -1117,16 +1185,7 @@ defmodule Bitcoinex.PSBT.In do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) kv = %{key: key, value: value} - unknown = - case input.unknown do - nil -> - [kv] - - _ -> - input.unknown ++ [kv] - end - - input = %In{input | unknown: unknown} + input = add_unknown(input, kv) {input, psbt} end end @@ -1138,6 +1197,7 @@ defmodule Bitcoinex.PSBT.Out do alias Bitcoinex.PSBT.Out alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Transaction.Out, as: TxOut alias Bitcoinex.Script defstruct [ @@ -1149,7 +1209,8 @@ defmodule Bitcoinex.PSBT.Out do :tap_internal_key, :tap_tree, :tap_bip32_derivation, - :proprietary + :proprietary, + :unknown ] @psbt_out_redeem_script 0x00 @@ -1162,42 +1223,62 @@ defmodule Bitcoinex.PSBT.Out do @psbt_out_tap_bip32_derivation 0x07 @psbt_out_proprietary 0xFC + @spec add_redeem_script(%Out{}, binary) :: %Out{} def add_redeem_script(output, redeem_script) when is_binary(redeem_script) and output.redeem_script == nil do %Out{output | redeem_script: redeem_script} end + @spec add_witness_script(%Out{}, binary) :: %Out{} def add_witness_script(output, witness_script) when is_binary(witness_script) and output.witness_script == nil do %Out{output | witness_script: witness_script} end + @spec add_bip32_derivation(%Out{}, %{:derivation => any, :pfp => any, :public_key => any} ) :: %Out{} def add_bip32_derivation(output, derivation = %{public_key: _, pfp: _, derivation: _}) do # ensure no duplicate keys? derivations = PsbtUtils.append(output.bip32_derivation, derivation) %Out{output | bip32_derivation: derivations} end - def add_amount(output, amount) when is_integer(amount) and amount > 0 do + @spec add_amount(%Out{}, non_neg_integer) :: %Out{} + def add_amount(output, amount) when is_integer(amount) and amount >= 0 do %Out{output | amount: amount} end + @spec add_script(%Out{}, binary) :: %Out{} def add_script(output, script) when is_binary(script) do %Out{output | script: script} end + @spec add_tap_internal_key(%Out{}, <<_::256>>) :: %Out{} def add_tap_internal_key(output, pk) when is_binary(pk) do %Out{output | tap_internal_key: pk} end # TODO:taproot find a good format for taptree + @spec add_tap_tree(%Out{}, any) :: %Out{} def add_tap_tree(output, _tree) do output end - def add_tap_bip32_derivation(output, derivation = %{public_key: _, pfp: _, derivation: _}) do + @spec add_tap_bip32_derivation( + %Out{}, + %{ + :derivation => any, + :leaf_hashes => any, + :pfp => any, + :public_key => any + } + ) :: %Out{} + def add_tap_bip32_derivation(output, derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _}) do derivations = PsbtUtils.append(output.tap_bip32_derivation, derivation) %Out{output | tap_bip32_derivation: derivations} end + @spec add_proprietary( + %Out{}, + binary + ) :: %Out{} def add_proprietary(output, kv) when is_binary(kv) do kvs = PsbtUtils.append(output.proprietary, kv) %Out{output | proprietary: kvs} @@ -1210,6 +1291,7 @@ defmodule Bitcoinex.PSBT.Out do <<>> end + @spec from_tx_outputs(list(%TxOut{})) :: list(%Out{}) def from_tx_outputs(tx_outputs) do Enum.reduce(tx_outputs, [], fn _, acc -> [%Out{} | acc] end) |> Enum.reverse() @@ -1265,6 +1347,10 @@ defmodule Bitcoinex.PSBT.Out do PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8)>>, value) end + defp serialize_kv(:unknown, %{key: k, value: v}) do + PsbtUtils.serialize_kv(k, v) + end + defp serialize_kv(_key, _value) do <<>> end @@ -1324,13 +1410,13 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | redeem_script: Base.encode16(value, case: :lower)} + output = add_redeem_script(output, Base.encode16(value, case: :lower)) {output, psbt} end defp parse(<<@psbt_out_scriptwitness::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | witness_script: Base.encode16(value, case: :lower)} + output = add_witness_script(output, Base.encode16(value, case: :lower)) {output, psbt} end @@ -1349,71 +1435,68 @@ defmodule Bitcoinex.PSBT.Out do derivation: path } - bip32_derivation = PsbtUtils.append(output.bip32_derivation, derivation) - output = %Out{output | bip32_derivation: bip32_derivation} + output = add_bip32_derivation(output, derivation) {output, psbt} end defp parse(<<@psbt_out_amount::big-size(8)>>, psbt, output) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | amount: amount} + output = add_amount(output, amount) {output, psbt} end defp parse(<<@psbt_out_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {:ok, script} = Script.parse_script(value) - output = %Out{output | script: script} + {:ok, _} = Script.parse_script(value) + output = add_script(output, value) {output, psbt} end defp parse(<<@psbt_out_tap_internal_key::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | tap_internal_key: value} + output = add_tap_internal_key(output, value) {output, psbt} end defp parse(<<@psbt_out_tap_tree::big-size(8)>>, psbt, output) do {tree, psbt} = PsbtUtils.parse_compact_size_value(psbt) leaves = parse_tap_tree(tree, []) - output = %Out{output | tap_tree: leaves} + output = add_tap_tree(output, leaves) {output, psbt} end defp parse( <<@psbt_out_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, - output + output = %Out{} ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {leaf_hash_ct, value} = TxUtils.get_counter(value) - leaf_hashes = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) + {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ - pubkey: pubkey, + public_key: pubkey, leaf_hashes: leaf_hashes, pfp: pfp, derivation: path } - tap_bip32_derivation = PsbtUtils.append(output.tap_bip32_derivation, derivation) - output = %Out{output | tap_bip32_derivation: tap_bip32_derivation} + output = add_tap_bip32_derivation(output, derivation) {output, psbt} end defp parse(<<@psbt_out_proprietary::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - kvs = PsbtUtils.append(output.proprietary, value) - output = %Out{output | proprietary: kvs} + output = add_proprietary(output, value) {output, psbt} end defp parse_tap_tree(<<>>, scripts), do: Enum.reverse(scripts) defp parse_tap_tree(tree, scripts) do - <> = tree + <> = tree {script, tree} = PsbtUtils.parse_compact_size_value(rest) {:ok, script} = Script.parse_script(script) From 181e4e8866c16e5669b11d4fb0edea0f976d784f Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 22 Feb 2023 16:39:51 -0800 Subject: [PATCH 27/40] refactor for code efficiency --- lib/psbt.ex | 159 ++++++++++++++++++++++++++-------------------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 44e5a82..85a8618 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -11,6 +11,7 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.Global alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Out + alias Bitcoinex.PSBT.Utils alias Bitcoinex.Transaction.Utils, as: TxUtils @type t() :: %__MODULE__{} @@ -24,6 +25,7 @@ defmodule Bitcoinex.PSBT do @magic 0x70736274 @separator 0xFF + @spec separator :: 255 def separator, do: @separator @doc """ @@ -116,6 +118,28 @@ defmodule Bitcoinex.PSBT do %Bitcoinex.Transaction{tx | witnesses: witnesses, inputs: inputs} end + + # Global + + def add_global_field(psbt, field, value) do + global = Global.add_field(psbt.global, field, value) + %PSBT{psbt | global: global} + end + + # Inputs + + def add_input_field(psbt, input_idx, field, value) do + inputs = Utils.set_item_field(psbt.inputs, input_idx, &In.add_field/3, field, value) + %PSBT{psbt | inputs: inputs} + end + + # Outputs + + def set_output_field(psbt, output_idx, field, value) do + outputs = Utils.set_item_field(psbt.outputs, output_idx, &Out.add_field/3, field, value) + %PSBT{psbt | outputs: outputs} + end + end defmodule Bitcoinex.PSBT.Utils do @@ -190,6 +214,15 @@ defmodule Bitcoinex.PSBT.Utils do @spec append(nil | list, any) :: [any] def append(nil, item), do: [item] def append(items, item), do: items ++ [item] + + def set_item_field(items, idx, add_field_func, field, value) do + item = + items + |> Enum.at(idx) + |> add_field_func.(field, value) + List.replace_at(items, idx, item) + end + end defmodule Bitcoinex.PSBT.Global do @@ -226,49 +259,40 @@ defmodule Bitcoinex.PSBT.Global do @psbt_global_version 0xFB @psbt_global_proprietary 0xFC - @spec add_unsigned_tx(%Global{}, Transaction.t()) :: %Global{} - def add_unsigned_tx(global, unsigned_tx = %Transaction{}) when global.unsigned_tx == nil do + def add_field(global, :unsigned_tx, unsigned_tx = %Transaction{}) when global.unsigned_tx == nil do %Global{global | unsigned_tx: unsigned_tx} end - @spec add_xpub(%Global{}, %{xpub: ExtendedKey.t(), pfp: <<_::64>>, derivation: DerivationPath.t()}) :: %Global{} - def add_xpub(global, global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::64>>, derivation: %DerivationPath{}}) do + def add_field(global, :xpub, global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::64>>, derivation: %DerivationPath{}}) do global_xpubs = PsbtUtils.append(global.xpub, global_xpub) %Global{global | xpub: global_xpubs} end - @spec add_tx_version(%Global{}, pos_integer) :: %Global{} - def add_tx_version(global, value) when global.tx_version == nil and value > 0 do + def add_field(global, :tx_version, value) when global.tx_version == nil and value > 0 do %Global{global | tx_version: value} end - @spec add_fallback_locktime(%Global{}, non_neg_integer) :: %Global{} - def add_fallback_locktime(global, value) when value >= 0 do + def add_field(global, :fallback_locktime, value) when value >= 0 do %Global{global | fallback_locktime: value} end - @spec add_input_count(%Global{}, non_neg_integer()) :: %Global{} - def add_input_count(global, input_count) when input_count > 0 do + def add_field(global, :input_count, input_count) when input_count > 0 do %Global{global | input_count: input_count} end - @spec add_output_count(%Global{}, non_neg_integer()) :: %Global{} - def add_output_count(global, output_count) when output_count > 0 do + def add_field(global, :output_count, output_count) when output_count > 0 do %Global{global | output_count: output_count} end - @spec add_tx_modifiable(%Global{}, non_neg_integer()) :: %Global{} - def add_tx_modifiable(global, value) do + def add_field(global, :tx_modifiable, value) do %Global{global | tx_modifiable: value} end - @spec add_version(%Global{}, non_neg_integer()) :: %Global{} - def add_version(global, value) do + def add_field(global, :version, value) do %Global{global | version: value} end - @spec add_proprietary(%Global{}, binary) :: %Global{} - def add_proprietary(global, value) do + def add_field(global, :proprietary, value) do proprietaries = PsbtUtils.append(global.proprietary, value) %Global{global | proprietary: proprietaries} end @@ -529,132 +553,124 @@ defmodule Bitcoinex.PSBT.In do @minimum_time_locktime Transaction.minimum_time_locktime() - def add_non_witness_utxo(input, tx = %Transaction{}) when input.non_witness_utxo == nil do + def add_field(input, :non_witness_utxo, tx = %Transaction{}) when input.non_witness_utxo == nil do %In{input | non_witness_utxo: tx} end - def add_witness_utxo(input, utxo = %Out{}) do + def add_field(input, :witness_utxo, utxo = %Out{}) do %In{input | witness_utxo: utxo} end - def add_partial_sig(input, sig = %{public_key: _, signature: _}) do + def add_field(input, :partial_sig, sig = %{public_key: _, signature: _}) do sigs = PsbtUtils.append(input.partial_sig, sig) %In{input | partial_sig: sigs} end # TODO only allow real sighash values? - def add_sighash_type(input, sighash_type) when is_integer(sighash_type) and sighash_type >= 0 do + def add_field(input, :sighash_type, sighash_type) when is_integer(sighash_type) and sighash_type >= 0 do %In{input | sighash_type: sighash_type} end - def add_redeem_script(input, redeem_script) do + def add_field(input, :redeem_script, redeem_script) do {:ok, _} = Base.decode16(redeem_script, case: :lower) %In{input | redeem_script: redeem_script} end - def add_witness_script(input, witness_script) do + def add_field(input, :witness_script, witness_script) do {:ok, _} = Base.decode16(witness_script, case: :lower) %In{input | witness_script: witness_script} end - def add_bip32_derivation(input, derivation = %{public_key: _, pfp: _, derivation: _}) do + def add_field(input, :bip32_derivation, derivation = %{public_key: _, pfp: _, derivation: _}) do derivations = PsbtUtils.append(input.bip32_derivation, derivation) %In{input | bip32_derivation: derivations} end - def add_final_scriptsig(input, final_scriptsig) do + def add_field(input, :final_scriptsig, final_scriptsig) do {:ok, _} = Base.decode16(final_scriptsig, case: :lower) %In{input | final_scriptsig: final_scriptsig} end - def add_final_scriptwitness(input, final_scriptwitness = %Transaction.Witness{}) do + def add_field(input, :final_scriptwitness, final_scriptwitness = %Transaction.Witness{}) do %In{input | final_scriptwitness: final_scriptwitness} end - def add_por_commitment(input, por_commitment) when is_binary(por_commitment) do + def add_field(input, :por_commitment, por_commitment) when is_binary(por_commitment) do %In{input | por_commitment: por_commitment} end - def add_ripemd160(input, ripemd160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + def add_field(input, :ripemd160, ripemd160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do ripemd160s = PsbtUtils.append(input.ripemd160, ripemd160) %In{input | ripemd160: ripemd160s} end - def add_sha256(input, sha256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + def add_field(input, :sha256, sha256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do sha256s = PsbtUtils.append(input.sha256, sha256) %In{input | sha256: sha256s} end - def add_hash160(input, hash160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + def add_field(input, :hash160, hash160 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do hash160s = PsbtUtils.append(input.hash160, hash160) %In{input | hash160: hash160s} end - def add_hash256(input, hash256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do + def add_field(input, :hash256, hash256 = %{hash: h, preimage: p}) when is_binary(h) and is_binary(p) do hash256s = PsbtUtils.append(input.hash256, hash256) %In{input | hash256: hash256s} end - def add_previous_txid(input, <>) do + def add_field(input, :previous_txid, <>) do %In{input | previous_txid: previous_txid} end - def add_output_index(input, output_index) when is_integer(output_index) and output_index >= 0 do + def add_field(input, :output_index, output_index) when is_integer(output_index) and output_index >= 0 do %In{input | output_index: output_index} end - def add_sequence(input, sequence) when is_integer(sequence) and sequence >= 0 do + def add_field(input, :sequence, sequence) when is_integer(sequence) and sequence >= 0 do %In{input | sequence: sequence} end - def add_required_time_locktime(input, locktime) when is_integer(locktime) and locktime >= @minimum_time_locktime do + def add_field(input, :required_time_locktime, locktime) when is_integer(locktime) and locktime >= @minimum_time_locktime do %In{input | required_time_locktime: locktime} end - def add_required_height_locktime(input, locktime) when is_integer(locktime) and locktime < @minimum_time_locktime do + def add_field(input, :required_height_locktime, locktime) when is_integer(locktime) and locktime < @minimum_time_locktime do %In{input | required_height_locktime: locktime} end - def add_tap_key_sig(input, tap_key_sig) when is_binary(tap_key_sig) and byte_size(tap_key_sig) in [64, 65] do + def add_field(input, :tap_key_sig, tap_key_sig) when is_binary(tap_key_sig) and byte_size(tap_key_sig) in [64, 65] do %In{input | tap_key_sig: tap_key_sig} end - - def add_tap_script_sig(input, tap_script_sig = %{pubkey: _, leaf_hash: _, signature: _}) do + def add_field(input, :tap_script_sig, tap_script_sig = %{pubkey: _, leaf_hash: _, signature: _}) do sigs = PsbtUtils.append(input.tap_script_sig, tap_script_sig) %In{input | tap_script_sig: sigs} end # TODO:taproot make this TapLeaf - def add_tap_leaf_script(input, tap_leaf_script = %{ leaf_version: _, script: _, control_block: _}) do + def add_field(input, :tap_leaf_script, tap_leaf_script = %{ leaf_version: _, script: _, control_block: _}) do scripts = PsbtUtils.append(input.tap_leaf_script, tap_leaf_script) %In{input | tap_leaf_script: scripts} end - def add_tap_bip32_derivation(input, tap_bip32_derivation = %{pubkey: _, leaf_hashes: _, pfp: _, derivation: _}) do + def add_field(input, :tap_bip32_derivation, tap_bip32_derivation = %{pubkey: _, leaf_hashes: _, pfp: _, derivation: _}) do derivations = PsbtUtils.append(input.tap_bip32_derivation, tap_bip32_derivation) %In{input | tap_bip32_derivation: derivations} end - def add_tap_internal_key(input, <>) do + def add_field(input, :tap_internal_key, <>) do %In{input | tap_internal_key: tap_internal_key} end - @spec add_tap_merkle_root(%In{}, <<_::256>>) :: %In{} - def add_tap_merkle_root(input, <>) do + def add_field(input, :tap_merkle_root, <>) do %In{input | tap_merkle_root: tap_merkle_root} end - @spec add_proprietary(%In{}, binary) :: %In{} - def add_proprietary(input, proprietary) when is_binary(proprietary) do + def add_field(input, :proprietary, proprietary) when is_binary(proprietary) do %In{input | proprietary: proprietary} end - def add_unknown(input, kv) do - unknowns = PsbtUtils.append(input.unknown, kv) - %In{input | unknown: unknowns} - end - def parse_inputs(psbt, num_inputs) do psbt |> parse_input([], num_inputs) @@ -1223,66 +1239,47 @@ defmodule Bitcoinex.PSBT.Out do @psbt_out_tap_bip32_derivation 0x07 @psbt_out_proprietary 0xFC - @spec add_redeem_script(%Out{}, binary) :: %Out{} - def add_redeem_script(output, redeem_script) when is_binary(redeem_script) and output.redeem_script == nil do + def add_field(output, :redeem_script, redeem_script) when is_binary(redeem_script) and output.redeem_script == nil do %Out{output | redeem_script: redeem_script} end - @spec add_witness_script(%Out{}, binary) :: %Out{} - def add_witness_script(output, witness_script) when is_binary(witness_script) and output.witness_script == nil do + def add_field(output, :witness_script, witness_script) when is_binary(witness_script) and output.witness_script == nil do %Out{output | witness_script: witness_script} end - @spec add_bip32_derivation(%Out{}, %{:derivation => any, :pfp => any, :public_key => any} ) :: %Out{} - def add_bip32_derivation(output, derivation = %{public_key: _, pfp: _, derivation: _}) do + def add_field(output, :bip32_derivation, derivation = %{public_key: _, pfp: _, derivation: _}) do # ensure no duplicate keys? derivations = PsbtUtils.append(output.bip32_derivation, derivation) %Out{output | bip32_derivation: derivations} end - @spec add_amount(%Out{}, non_neg_integer) :: %Out{} - def add_amount(output, amount) when is_integer(amount) and amount >= 0 do + def add_field(output, :amount, amount) when is_integer(amount) and amount >= 0 do %Out{output | amount: amount} end - @spec add_script(%Out{}, binary) :: %Out{} - def add_script(output, script) when is_binary(script) do + def add_field(output, :script, script) when is_binary(script) do %Out{output | script: script} end - @spec add_tap_internal_key(%Out{}, <<_::256>>) :: %Out{} - def add_tap_internal_key(output, pk) when is_binary(pk) do + def add_field(output, :tap_internal_key, pk) when is_binary(pk) do %Out{output | tap_internal_key: pk} end # TODO:taproot find a good format for taptree - @spec add_tap_tree(%Out{}, any) :: %Out{} - def add_tap_tree(output, _tree) do + def add_field(output, :tap_tree, _tree) do output end - @spec add_tap_bip32_derivation( - %Out{}, - %{ - :derivation => any, - :leaf_hashes => any, - :pfp => any, - :public_key => any - } - ) :: %Out{} - def add_tap_bip32_derivation(output, derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _}) do + def add_field(output, :tap_bip32_derivation, derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _}) do derivations = PsbtUtils.append(output.tap_bip32_derivation, derivation) %Out{output | tap_bip32_derivation: derivations} end - @spec add_proprietary( - %Out{}, - binary - ) :: %Out{} - def add_proprietary(output, kv) when is_binary(kv) do + def add_field(output, :proprietary, kv) when is_binary(kv) do kvs = PsbtUtils.append(output.proprietary, kv) %Out{output | proprietary: kvs} end + def serialize_outputs(outputs) when is_list(outputs) and length(outputs) > 0 do serialize_output(outputs, <<>>) end From 4446588d9eda8711de9ad1e77d869a81f5edbe97 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 22 Feb 2023 21:40:39 -0800 Subject: [PATCH 28/40] finalize add-fields funcs for psbt --- lib/psbt.ex | 99 ++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 85a8618..ad1e043 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -119,22 +119,19 @@ defmodule Bitcoinex.PSBT do %Bitcoinex.Transaction{tx | witnesses: witnesses, inputs: inputs} end - # Global - + @spec add_global_field(PSBT.t(),atom,any) :: PSBT.t() def add_global_field(psbt, field, value) do global = Global.add_field(psbt.global, field, value) %PSBT{psbt | global: global} end - # Inputs - + @spec add_input_field(PSBT.t(), integer, atom, any) :: PSBT.t() def add_input_field(psbt, input_idx, field, value) do inputs = Utils.set_item_field(psbt.inputs, input_idx, &In.add_field/3, field, value) %PSBT{psbt | inputs: inputs} end - # Outputs - + @spec set_output_field(PSBT.t(), non_neg_integer, atom, any) :: PSBT.t() def set_output_field(psbt, output_idx, field, value) do outputs = Utils.set_item_field(psbt.outputs, output_idx, &Out.add_field/3, field, value) %PSBT{psbt | outputs: outputs} @@ -320,7 +317,7 @@ defmodule Bitcoinex.PSBT.Global do case Transaction.decode(txn_bytes) do {:ok, txn} -> - global = add_unsigned_tx(global, txn) + global = add_field(global, :unsigned_tx, txn) {global, psbt} {:error, error_msg} -> @@ -346,51 +343,51 @@ defmodule Bitcoinex.PSBT.Global do derivation: path } - global = add_xpub(global, global_xpub) + global = add_field(global, :xpub, global_xpub) {global, psbt} end defp parse(<<@psbt_global_tx_version::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_tx_version(global, value) + global = add_field(global, :tx_version, value) {global, psbt} end defp parse(<<@psbt_global_fallback_locktime::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_fallback_locktime(global, value) + global = add_field(global, :fallback_locktime, value) {global, psbt} end defp parse(<<@psbt_global_input_count::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {input_count, _} = TxUtils.get_counter(value) - global = add_input_count(global, input_count) + global = add_field(global, :input_count, input_count) {global, psbt} end defp parse(<<@psbt_global_output_count::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {output_count, _} = TxUtils.get_counter(value) - global = add_output_count(global, output_count) + global = add_field(global, :output_count, output_count) {global, psbt} end defp parse(<<@psbt_global_tx_modifiable::big-size(8)>>, psbt, global) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_tx_modifiable(global, value) + global = add_field(global, :tx_modifiable, value) {global, psbt} end defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_version(global, value) + global = add_field(global, :version, value) {global, psbt} end defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_proprietary(global, value) + global = add_field(global, :proprietary, value) {global, psbt} end @@ -962,14 +959,14 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {:ok, txn} = Transaction.decode(value) - input = add_non_witness_utxo(input, txn) + input = add_field(input, :non_witness_utxo, txn) {input, psbt} end defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) out = Out.output(value) - input = add_witness_utxo(input, out) + input = add_field(input, :witness_utxo, out) {input, psbt} end @@ -981,26 +978,26 @@ defmodule Bitcoinex.PSBT.In do signature: Base.encode16(value, case: :lower) } - input = add_partial_sig(input, partial_sig) + input = add_field(input, :partial_sig, partial_sig) {input, psbt} end defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_sighash_type(input, value) + input = add_field(input, :sighash_type, value) {input, psbt} end defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_redeem_script(input, Base.encode16(value, case: :lower)) + input = add_field(input, :redeem_script, Base.encode16(value, case: :lower)) {input, psbt} end defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_witness_script(input, Base.encode16(value, case: :lower)) + input = add_field(input, :witness_script, Base.encode16(value, case: :lower)) {input, psbt} end @@ -1015,19 +1012,19 @@ defmodule Bitcoinex.PSBT.In do derivation: path } - input = add_bip32_derivation(input, derivation) + input = add_field(input, :bip32_derivation, derivation) {input, psbt} end defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_final_scriptsig(input, Base.encode16(value, case: :lower)) + input = add_field(input, :final_scriptsig, Base.encode16(value, case: :lower)) {input, psbt} end defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_por_commitment(input, value) + input = add_field(input, :por_commitment, value) {input, psbt} end @@ -1040,7 +1037,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - input = add_ripemd160(input, data) + input = add_field(input, :ripemd160, data) {input, psbt} end @@ -1053,7 +1050,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - input = add_sha256(input, data) + input = add_field(input, :sha256, data) {input, psbt} end @@ -1066,7 +1063,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - input = add_hash160(input, data) + input = add_field(input, :hash160, data) {input, psbt} end @@ -1079,43 +1076,43 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - input = add_hash256(input, data) + input = add_field(input, :hash256, data) {input, psbt} end defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do {value = <<_::binary-size(32)>>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_previous_txid(input, value) + input = add_field(input, :previous_txid, value) {input, psbt} end defp parse(<<@psbt_in_output_index::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_output_index(input, value) + input = add_field(input, :output_index, value) {input, psbt} end defp parse(<<@psbt_in_sequence::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_sequence(input, value) + input = add_field(input, :sequence, value) {input, psbt} end defp parse(<<@psbt_in_required_time_locktime::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_required_time_locktime(input, value) + input = add_field(input, :required_time_locktime, value) {input, psbt} end defp parse(<<@psbt_in_required_height_locktime::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_required_height_locktime(input, value) + input = add_field(input, :required_height_locktime, value) {input, psbt} end defp parse(<<@psbt_in_tap_key_sig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_tap_key_sig(input, value) + input = add_field(input, :tap_key_sig, value) {input, psbt} end @@ -1134,7 +1131,7 @@ defmodule Bitcoinex.PSBT.In do signature: value } - input = add_tap_script_sig(input, data) + input = add_field(input, :tap_script_sig, data) {input, psbt} end @@ -1150,7 +1147,7 @@ defmodule Bitcoinex.PSBT.In do control_block: control_block } - input = add_tap_leaf_script(input, data) + input = add_field(input, :tap_leaf_script, data) {input, psbt} end @@ -1168,32 +1165,32 @@ defmodule Bitcoinex.PSBT.In do derivation: path } - input = add_tap_bip32_derivation(input, derivation) + input = add_field(input, :tap_bip32_derivation, derivation) {input, psbt} end defp parse(<<@psbt_in_tap_internal_key::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_tap_internal_key(input, value) + input = add_field(input, :tap_internal_key, value) {input, psbt} end defp parse(<<@psbt_in_tap_merkle_root::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_tap_merkle_root(input, value) + input = add_field(input, :tap_merkle_root, value) {input, psbt} end defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_proprietary(input, value) + input = add_field(input, :proprietary, value) {input, psbt} end defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) value = Witness.witness(value) - input = add_final_scriptwitness(input, value) + input = add_field(input, :final_scriptwitness, value) {input, psbt} end @@ -1201,7 +1198,7 @@ defmodule Bitcoinex.PSBT.In do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) kv = %{key: key, value: value} - input = add_unknown(input, kv) + input = add_field(input, :unknown, kv) {input, psbt} end end @@ -1407,13 +1404,13 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_redeem_script(output, Base.encode16(value, case: :lower)) + output = add_field(output, :redeem_script, Base.encode16(value, case: :lower)) {output, psbt} end defp parse(<<@psbt_out_scriptwitness::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_witness_script(output, Base.encode16(value, case: :lower)) + output = add_field(output, :witness_script, Base.encode16(value, case: :lower)) {output, psbt} end @@ -1432,33 +1429,33 @@ defmodule Bitcoinex.PSBT.Out do derivation: path } - output = add_bip32_derivation(output, derivation) + output = add_field(output, :bip32_derivation, derivation) {output, psbt} end defp parse(<<@psbt_out_amount::big-size(8)>>, psbt, output) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_amount(output, amount) + output = add_field(output, :amount, amount) {output, psbt} end defp parse(<<@psbt_out_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {:ok, _} = Script.parse_script(value) - output = add_script(output, value) + output = add_field(output, :script, value) {output, psbt} end defp parse(<<@psbt_out_tap_internal_key::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_tap_internal_key(output, value) + output = add_field(output, :tap_internal_key, value) {output, psbt} end defp parse(<<@psbt_out_tap_tree::big-size(8)>>, psbt, output) do {tree, psbt} = PsbtUtils.parse_compact_size_value(psbt) leaves = parse_tap_tree(tree, []) - output = add_tap_tree(output, leaves) + output = add_field(output, :tap_tree, leaves) {output, psbt} end @@ -1480,13 +1477,13 @@ defmodule Bitcoinex.PSBT.Out do derivation: path } - output = add_tap_bip32_derivation(output, derivation) + output = add_field(output, :tap_bip32_derivation, derivation) {output, psbt} end defp parse(<<@psbt_out_proprietary::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_proprietary(output, value) + output = add_field(output, :proprietary, value) {output, psbt} end From 289e49c94cf34d4ad31ac52cb6ff4b7f919307c5 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:05:15 -0800 Subject: [PATCH 29/40] lint --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 824d286..b09c4e8 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -161,7 +161,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux,tweak_point = %Point{}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) From e659a67fac2af9b10dc666b84b8f669ebb4858b8 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:10:47 -0800 Subject: [PATCH 30/40] lint --- lib/secp256k1/schnorr.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 56d1c15..91027a0 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -36,9 +36,11 @@ defmodule Bitcoinex.Secp256k1.Schnorr do def sign_with_nonce(sk, k, z) do d = Secp256k1.force_even_y(sk) k = Secp256k1.force_even_y(k) + case {d, k} do {{:error, _}, _} -> {:error, "failed to force signing key even"} + {_, {:error, _}} -> {:error, "failed to force nonce secret even"} @@ -85,9 +87,9 @@ defmodule Bitcoinex.Secp256k1.Schnorr do k -> {:ok, k, d} - end + end end - end + end end end @@ -296,12 +298,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> PrivateKey.new() case e do - {:error, msg} -> {:error, msg} + {:error, msg} -> + {:error, msg} {:ok, e} -> Math.multiply(pk, e.d) |> Math.add(r_point) end end - end From 9bd20fa800437d221dccf4d09699bc3c2347151c Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:20:21 -0800 Subject: [PATCH 31/40] lint --- lib/secp256k1/schnorr.ex | 52 +++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index b09c4e8..f6f49fa 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -35,23 +35,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = calculate_k(t, d_point, z_bytes) + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - r_point = PrivateKey.to_point(k0) + case Secp256k1.force_even_y(k0) do + {:error, msg} -> + {:error, msg} - case Secp256k1.force_even_y(k0) do - {:error, msg} -> - {:error, msg} + k -> + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) - k -> - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, %Signature{r: r_point.x, s: sig_s}} + end end end end @@ -171,19 +168,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) # TODO always add tweak_point to the nonce to commit to it as well - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end end @doc """ From e133c5c2145d6a27d0b77022c311921a1ca4ac1e Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:24:10 -0800 Subject: [PATCH 32/40] fix aliases --- lib/taproot.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/taproot.ex b/lib/taproot.ex index 3954ff3..5cb6f8e 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -1,6 +1,7 @@ defmodule Bitcoinex.Taproot do alias Bitcoinex.Utils + alias Bitcoinex.Taproot alias Bitcoinex.{Secp256k1, Script} alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} @@ -11,8 +12,6 @@ defmodule Bitcoinex.Taproot do @spec bip342_leaf_version :: 192 def bip342_leaf_version(), do: @bip342_leaf_version - @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil - @spec tweak_privkey(PrivateKey.t(), binary) :: PrivateKey.t() | {:error, String.t()} def tweak_privkey(sk0 = %PrivateKey{}, h) do sk = Secp256k1.force_even_y(sk0) @@ -121,7 +120,7 @@ defmodule Bitcoinex.Taproot do while branches are {script_tree, script_tree}. Since we sort based on hash at each level, left vs right branches are irrelevant. An empty tree is represented by nil. """ - @type script_tree :: TapLeaf.t() | {script_tree(), script_tree()} | nil + @type script_tree :: Taproot.TapLeaf.t() | {script_tree(), script_tree()} | nil @doc """ merkelize_script_tree takes a script_tree (either nil, a TapLeaf, or a tuple of two script_trees) From 3a48dbdb653b03e57fad5120ff12e4eed94591e9 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:32:35 -0800 Subject: [PATCH 33/40] lint --- lib/secp256k1/schnorr.ex | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index f6f49fa..01391c9 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -163,24 +163,29 @@ defmodule Bitcoinex.Secp256k1.Schnorr do aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) - d = Secp256k1.force_even_y(sk) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - # TODO always add tweak_point to the nonce to commit to it as well - case calculate_k(t, d_point, z_bytes) do - {:ok, k0} -> - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case Secp256k1.force_even_y(sk) do + {:error, msg} -> + {:error, msg} + + d -> + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end end end From 73b7500e0ea71d31a8bd2eaab023af5468c4eeb4 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:35:19 -0800 Subject: [PATCH 34/40] lint --- lib/secp256k1/schnorr.ex | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 91027a0..b0a1c13 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -76,18 +76,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - case Secp256k1.force_even_y(k0) do - {:error, msg} -> - {:error, msg} - - k -> - {:ok, k, d} - end + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + case Secp256k1.force_even_y(k0) do + {:error, msg} -> + {:error, msg} + + k -> + {:ok, k, d} + end end end end From 74e4e8460fe8b2ca1df74e75baa37bea7d936ae0 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:59:37 -0800 Subject: [PATCH 35/40] temp: fix script vs binary --- lib/psbt.ex | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index b38ec87..f541a9b 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -280,7 +280,7 @@ defmodule Bitcoinex.PSBT.Global do def add_field( global, :xpub, - global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::64>>, derivation: %DerivationPath{}} + global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::binary-size(4)>>, derivation: %DerivationPath{}} ) do global_xpubs = PsbtUtils.append(global.xpub, global_xpub) %Global{global | xpub: global_xpubs} @@ -310,11 +310,17 @@ defmodule Bitcoinex.PSBT.Global do %Global{global | version: value} end + # TODO: fix def add_field(global, :proprietary, value) do proprietaries = PsbtUtils.append(global.proprietary, value) %Global{global | proprietary: proprietaries} end + def add_field(global, :unknown, value) do + unknown = PsbtUtils.append(global.unknown, value) + %Global{global | unknown: unknown} + end + @spec parse_global(nonempty_binary) :: {%Global{}, binary} def parse_global(psbt) do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) @@ -406,12 +412,19 @@ defmodule Bitcoinex.PSBT.Global do {global, psbt} end + # TODO: fix defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) global = add_field(global, :proprietary, value) {global, psbt} end + defp parse(key, psbt, global) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + global = add_field(global, :unknown, %{key: key, value: value}) + {global, psbt} + end + defp serialize_kv(:unsigned_tx, value) when value != nil do PsbtUtils.serialize_kv(<<@psbt_global_unsigned_tx::big-size(8)>>, TxUtils.serialize(value)) end @@ -711,7 +724,13 @@ defmodule Bitcoinex.PSBT.In do end def add_field(input, :proprietary, proprietary) when is_binary(proprietary) do - %In{input | proprietary: proprietary} + proprietaries = PsbtUtils.append(input.proprietary, proprietary) + %In{input | proprietary: proprietaries} + end + + def add_field(input, :unknown, value) do + unknown = PsbtUtils.append(input.unknown, value) + %In{input | unknown: unknown} end def parse_inputs(psbt, num_inputs) do @@ -910,6 +929,7 @@ defmodule Bitcoinex.PSBT.In do PsbtUtils.serialize_kv(<<@psbt_in_tap_merkle_root::big-size(8)>>, value) end + # TODO: fix defp serialize_kv(:proprietary, value) when value != nil do PsbtUtils.serialize_kv(<<@psbt_in_proprietary::big-size(8)>>, value) end @@ -1234,6 +1254,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end + # TODO: fix defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = add_field(input, :proprietary, value) @@ -1331,6 +1352,7 @@ defmodule Bitcoinex.PSBT.Out do %Out{output | tap_bip32_derivation: derivations} end + # TODO: fix def add_field(output, :proprietary, kv) when is_binary(kv) do kvs = PsbtUtils.append(output.proprietary, kv) %Out{output | proprietary: kvs} @@ -1396,6 +1418,7 @@ defmodule Bitcoinex.PSBT.Out do PsbtUtils.serialize_kv(key, leaf_hashes <> fingerprint_path) end + # TODO: fix defp serialize_kv(:proprietary, value) when value != nil do PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8)>>, value) end @@ -1500,8 +1523,8 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {:ok, _} = Script.parse_script(value) - output = add_field(output, :script, value) + {:ok, script} = Script.parse_script(value) + output = add_field(output, :script, script) {output, psbt} end @@ -1541,6 +1564,7 @@ defmodule Bitcoinex.PSBT.Out do {output, psbt} end + # TODO: fix defp parse(<<@psbt_out_proprietary::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) output = add_field(output, :proprietary, value) From fa7df7f301827adaf0fb2546167c552e82128063 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 24 Feb 2023 00:01:11 -0800 Subject: [PATCH 36/40] lint --- lib/secp256k1/schnorr.ex | 42 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index b0a1c13..d53b43a 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -199,24 +199,30 @@ defmodule Bitcoinex.Secp256k1.Schnorr do aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) - d = Secp256k1.force_even_y(sk) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - # TODO always add tweak_point to the nonce to commit to it as well - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case Secp256k1.force_even_y(sk) do + {:error, msg} -> + {:error, msg} + + d -> + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end + end end @doc """ From 6187739d9835a23af2fc48a2685b306aba948fa1 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 25 Feb 2023 13:18:40 -0800 Subject: [PATCH 37/40] all PSBT pubkeys -> Point structs --- lib/psbt.ex | 190 ++++++++----- lib/transaction.ex | 5 + test/psbt_test.exs | 644 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 621 insertions(+), 218 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index f541a9b..6f4d799 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -280,7 +280,11 @@ defmodule Bitcoinex.PSBT.Global do def add_field( global, :xpub, - global_xpub = %{xpub: %ExtendedKey{}, pfp: <<_::binary-size(4)>>, derivation: %DerivationPath{}} + global_xpub = %{ + xpub: %ExtendedKey{}, + pfp: <<_::binary-size(4)>>, + derivation: %DerivationPath{} + } ) do global_xpubs = PsbtUtils.append(global.xpub, global_xpub) %Global{global | xpub: global_xpubs} @@ -311,12 +315,14 @@ defmodule Bitcoinex.PSBT.Global do end # TODO: fix - def add_field(global, :proprietary, value) do + def add_field(global, :proprietary, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do proprietaries = PsbtUtils.append(global.proprietary, value) %Global{global | proprietary: proprietaries} end - def add_field(global, :unknown, value) do + def add_field(global, :unknown, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do unknown = PsbtUtils.append(global.unknown, value) %Global{global | unknown: unknown} end @@ -412,10 +418,9 @@ defmodule Bitcoinex.PSBT.Global do {global, psbt} end - # TODO: fix - defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do + defp parse(<<@psbt_global_proprietary::big-size(8), key::binary>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = add_field(global, :proprietary, value) + global = add_field(global, :proprietary, %{key: key, value: value}) {global, psbt} end @@ -474,8 +479,8 @@ defmodule Bitcoinex.PSBT.Global do ) end - defp serialize_kv(:proprietary, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8)>>, value) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8), k::binary>>, v) end defp serialize_kv(:unknown, %{key: k, value: v}) do @@ -531,6 +536,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.Script + alias Bitcoinex.Secp256k1.Point defstruct [ :non_witness_utxo, @@ -590,6 +596,7 @@ defmodule Bitcoinex.PSBT.In do @psbt_in_proprietary 0xFC @minimum_time_locktime Transaction.minimum_time_locktime() + @valid_sighash_flags Transaction.valid_sighash_flags() def add_field(input, :non_witness_utxo, tx = %Transaction{}) when input.non_witness_utxo == nil do @@ -605,14 +612,16 @@ defmodule Bitcoinex.PSBT.In do %In{input | partial_sig: sigs} end - # TODO only allow real sighash values? - def add_field(input, :sighash_type, sighash_type) - when is_integer(sighash_type) and sighash_type >= 0 do + def add_field(input, :sighash_type, sighash_type) when sighash_type in @valid_sighash_flags do %In{input | sighash_type: sighash_type} end - def add_field(input, :redeem_script, redeem_script) do - {:ok, _} = Base.decode16(redeem_script, case: :lower) + def add_field(input, :redeem_script, redeem_script) when is_binary(redeem_script) do + {:ok, script} = Script.parse_script(redeem_script) + add_field(input, :redeem_script, script) + end + + def add_field(input, :redeem_script, redeem_script = %Script{}) do %In{input | redeem_script: redeem_script} end @@ -626,8 +635,12 @@ defmodule Bitcoinex.PSBT.In do %In{input | bip32_derivation: derivations} end - def add_field(input, :final_scriptsig, final_scriptsig) do - {:ok, _} = Base.decode16(final_scriptsig, case: :lower) + def add_field(input, :final_scriptsig, final_scriptsig) when is_binary(final_scriptsig) do + {:ok, script} = Script.parse_script(final_scriptsig) + add_field(input, :final_scriptsig, script) + end + + def add_field(input, :final_scriptsig, final_scriptsig = %Script{}) do %In{input | final_scriptsig: final_scriptsig} end @@ -691,7 +704,11 @@ defmodule Bitcoinex.PSBT.In do %In{input | tap_key_sig: tap_key_sig} end - def add_field(input, :tap_script_sig, tap_script_sig = %{pubkey: _, leaf_hash: _, signature: _}) do + def add_field( + input, + :tap_script_sig, + tap_script_sig = %{public_key: _, leaf_hash: _, signature: _} + ) do sigs = PsbtUtils.append(input.tap_script_sig, tap_script_sig) %In{input | tap_script_sig: sigs} end @@ -709,13 +726,18 @@ defmodule Bitcoinex.PSBT.In do def add_field( input, :tap_bip32_derivation, - tap_bip32_derivation = %{pubkey: _, leaf_hashes: _, pfp: _, derivation: _} + tap_bip32_derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _} ) do derivations = PsbtUtils.append(input.tap_bip32_derivation, tap_bip32_derivation) %In{input | tap_bip32_derivation: derivations} end def add_field(input, :tap_internal_key, <>) do + {:ok, pk} = Point.lift_x(tap_internal_key) + add_field(input, :tap_internal_key, pk) + end + + def add_field(input, :tap_internal_key, tap_internal_key = %Point{}) do %In{input | tap_internal_key: tap_internal_key} end @@ -723,12 +745,14 @@ defmodule Bitcoinex.PSBT.In do %In{input | tap_merkle_root: tap_merkle_root} end - def add_field(input, :proprietary, proprietary) when is_binary(proprietary) do + def add_field(input, :proprietary, proprietary = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do proprietaries = PsbtUtils.append(input.proprietary, proprietary) %In{input | proprietary: proprietaries} end - def add_field(input, :unknown, value) do + def add_field(input, :unknown, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do unknown = PsbtUtils.append(input.unknown, value) %In{input | unknown: unknown} end @@ -786,7 +810,7 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:partial_sig, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = Base.decode16!(value.signature, case: :lower) PsbtUtils.serialize_kv(<<@psbt_in_partial_sig::big-size(8)>> <> key_data, val) @@ -799,14 +823,14 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:final_scriptsig, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_in_final_scriptsig::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end defp serialize_kv(:redeem_script, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_in_redeem_script::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end @@ -825,7 +849,7 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:bip32_derivation, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) @@ -836,35 +860,35 @@ defmodule Bitcoinex.PSBT.In do PsbtUtils.serialize_kv(<<@psbt_in_por_commitment::big-size(8)>>, value) end - defp serialize_kv(:in_ripemd160, value) when value != nil do + defp serialize_kv(:in_ripemd160, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_ripemd160::big-size(8), value.hash::binary-size(20)>>, - value.preimage + <<@psbt_in_ripemd160::big-size(8), hash::binary-size(20)>>, + preimage ) end - defp serialize_kv(:in_sha256, value) when value != nil do + defp serialize_kv(:in_sha256, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_sha256::big-size(8), value.hash::binary-size(32)>>, - value.preimage + <<@psbt_in_sha256::big-size(8), hash::binary-size(32)>>, + preimage ) end - defp serialize_kv(:in_hash160, value) when value != nil do + defp serialize_kv(:in_hash160, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_hash160::big-size(8), value.hash::binary-size(20)>>, - value.preimage + <<@psbt_in_hash160::big-size(8), hash::binary-size(20)>>, + preimage ) end - defp serialize_kv(:in_hash256, value) when value != nil do + defp serialize_kv(:in_hash256, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_hash256::big-size(8), value.hash::binary-size(32)>>, - value.preimage + <<@psbt_in_hash256::big-size(8), hash::binary-size(32)>>, + preimage ) end - defp serialize_kv(:previous_txid, value) when value != nil do + defp serialize_kv(:previous_txid, <>) do PsbtUtils.serialize_kv(<<@psbt_in_previous_txid::big-size(8)>>, value) end @@ -896,7 +920,8 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:tap_script_sig, value) when value != nil do PsbtUtils.serialize_kv( - <<@psbt_in_tap_script_sig::big-size(8), value.pubkey::binary, value.leaf_hash::binary>>, + <<@psbt_in_tap_script_sig::big-size(8), Point.x_bytes(value.public_key)::binary, + value.leaf_hash::binary>>, value.signature ) end @@ -916,22 +941,21 @@ defmodule Bitcoinex.PSBT.In do fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv( - <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey::binary>>, + <<@psbt_in_tap_bip32_derivation::big-size(8), Point.x_bytes(value.public_key)::binary>>, leaf_hashes <> fingerprint_path ) end defp serialize_kv(:tap_internal_key, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_in_tap_internal_key::big-size(8)>>, value) + PsbtUtils.serialize_kv(<<@psbt_in_tap_internal_key::big-size(8)>>, Point.x_bytes(value)) end - defp serialize_kv(:tap_merkle_root, value) when value != nil do + defp serialize_kv(:tap_merkle_root, <>) do PsbtUtils.serialize_kv(<<@psbt_in_tap_merkle_root::big-size(8)>>, value) end - # TODO: fix - defp serialize_kv(:proprietary, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_in_proprietary::big-size(8)>>, value) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_in_proprietary::big-size(8), k::binary>>, v) end defp serialize_kv(:unknown, %{key: k, value: v}) do @@ -1039,8 +1063,10 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.parse_public_key(public_key) + partial_sig = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, signature: Base.encode16(value, case: :lower) } @@ -1057,7 +1083,7 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_field(input, :redeem_script, Base.encode16(value, case: :lower)) + input = add_field(input, :redeem_script, value) {input, psbt} end @@ -1072,8 +1098,10 @@ defmodule Bitcoinex.PSBT.In do {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + {:ok, pk} = Point.parse_public_key(public_key) + derivation = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, pfp: pfp, derivation: path } @@ -1084,7 +1112,7 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_field(input, :final_scriptsig, Base.encode16(value, case: :lower)) + input = add_field(input, :final_scriptsig, value) {input, psbt} end @@ -1147,7 +1175,7 @@ defmodule Bitcoinex.PSBT.In do end defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do - {value = <<_::binary-size(32)>>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = add_field(input, :previous_txid, value) {input, psbt} end @@ -1191,8 +1219,10 @@ defmodule Bitcoinex.PSBT.In do # TODO:validation validate sig len (64|65) {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.lift_x(pubkey) + data = %{ - pubkey: pubkey, + public_key: pk, leaf_hash: leaf_hash, signature: value } @@ -1227,12 +1257,13 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.lift_x(pubkey) {leaf_hash_ct, value} = TxUtils.get_counter(value) {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ - pubkey: pubkey, + public_key: pk, leaf_hashes: leaf_hashes, pfp: pfp, derivation: path @@ -1254,10 +1285,9 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - # TODO: fix - defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_proprietary::big-size(8), key::binary>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = add_field(input, :proprietary, value) + input = add_field(input, :proprietary, %{key: key, value: value}) {input, psbt} end @@ -1286,6 +1316,7 @@ defmodule Bitcoinex.PSBT.Out do alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.Transaction.Out, as: TxOut alias Bitcoinex.Script + alias Bitcoinex.Secp256k1.Point defstruct [ :redeem_script, @@ -1310,9 +1341,14 @@ defmodule Bitcoinex.PSBT.Out do @psbt_out_tap_bip32_derivation 0x07 @psbt_out_proprietary 0xFC - def add_field(output, :redeem_script, redeem_script) - when is_binary(redeem_script) and output.redeem_script == nil do - %Out{output | redeem_script: redeem_script} + def add_field(output, :redeem_script, script_bytes) + when is_binary(script_bytes) and output.script_bytes == nil do + {:ok, redeem_script} = Script.parse_script(script_bytes) + add_field(output, :redeem_script, redeem_script) + end + + def add_field(output, :redeem_script, script = %Script{}) do + %Out{output | redeem_script: script} end def add_field(output, :witness_script, witness_script) @@ -1330,17 +1366,23 @@ defmodule Bitcoinex.PSBT.Out do %Out{output | amount: amount} end - def add_field(output, :script, script) when is_binary(script) do + def add_field(output, :script, script_bytes) when is_binary(script_bytes) do + {:ok, script} = Script.parse_script(script_bytes) + add_field(output, :script, script) + end + + def add_field(output, :script, script = %Script{}) do %Out{output | script: script} end def add_field(output, :tap_internal_key, pk) when is_binary(pk) do + {:ok, pk} = Point.lift_x(pk) %Out{output | tap_internal_key: pk} end # TODO:taproot find a good format for taptree - def add_field(output, :tap_tree, _tree) do - output + def add_field(output, :tap_tree, tree) do + %Out{output | tap_tree: tree} end def add_field( @@ -1353,7 +1395,7 @@ defmodule Bitcoinex.PSBT.Out do end # TODO: fix - def add_field(output, :proprietary, kv) when is_binary(kv) do + def add_field(output, :proprietary, kv = %{key: _, value: _}) do kvs = PsbtUtils.append(output.proprietary, kv) %Out{output | proprietary: kvs} end @@ -1375,7 +1417,7 @@ defmodule Bitcoinex.PSBT.Out do defp serialize_kv(:redeem_script, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_out_redeem_script::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end @@ -1387,7 +1429,7 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:bip32_derivation, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) @@ -1402,7 +1444,7 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:tap_internal_key, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_out_tap_internal_key::big-size(8)>>, value) + PsbtUtils.serialize_kv(<<@psbt_out_tap_internal_key::big-size(8)>>, Point.x_bytes(value)) end defp serialize_kv(:tap_tree, value) when value != nil do @@ -1411,19 +1453,18 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:tap_bip32_derivation, value) when value != nil do - key = <<@psbt_out_tap_bip32_derivation::big-size(8), value.pubkey::binary>> + key = <<@psbt_out_tap_bip32_derivation::big-size(8)>> <> Point.x_bytes(value.public_key) leaf_hashes = PsbtUtils.serialize_leaf_hashes(value.leaf_hashes) fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(key, leaf_hashes <> fingerprint_path) end - # TODO: fix - defp serialize_kv(:proprietary, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8)>>, value) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8), k::binary>>, v) end - defp serialize_kv(:unknown, %{key: k, value: v}) do + defp serialize_kv(:unknown, %{key: k, value: v}) when is_binary(k) and is_binary(v) do PsbtUtils.serialize_kv(k, v) end @@ -1486,7 +1527,7 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = add_field(output, :redeem_script, Base.encode16(value, case: :lower)) + output = add_field(output, :redeem_script, value) {output, psbt} end @@ -1503,10 +1544,12 @@ defmodule Bitcoinex.PSBT.Out do ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.parse_public_key(public_key) + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, pfp: pfp, derivation: path } @@ -1523,8 +1566,7 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {:ok, script} = Script.parse_script(value) - output = add_field(output, :script, script) + output = add_field(output, :script, value) {output, psbt} end @@ -1553,8 +1595,10 @@ defmodule Bitcoinex.PSBT.Out do {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + {:ok, pk} = Point.lift_x(pubkey) + derivation = %{ - public_key: pubkey, + public_key: pk, leaf_hashes: leaf_hashes, pfp: pfp, derivation: path diff --git a/lib/transaction.ex b/lib/transaction.ex index 2b708bf..8ee289c 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -27,6 +27,11 @@ defmodule Bitcoinex.Transaction do :lock_time ] + # https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message + @valid_sighash_flags [0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83] + + def valid_sighash_flags(), do: @valid_sighash_flags + @minimum_time_locktime 500_000_000 def minimum_time_locktime(), do: @minimum_time_locktime diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 03c312d..c2ea453 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -121,11 +121,28 @@ defmodule Bitcoinex.PSBTTest do }, expected_in: [ %In{ - final_scriptsig: - "47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292" + final_scriptsig: %Bitcoinex.Script{ + items: [ + 71, + <<48, 68, 2, 32, 71, 89, 102, 23, 151, 192, 27, 3, 107, 37, 146, 137, 72, 104, 98, + 24, 52, 125, 137, 134, 75, 113, 158, 31, 127, 207, 87, 209, 229, 17, 101, 135, 2, + 32, 83, 9, 234, 191, 86, 170, 77, 136, 145, 255, 209, 17, 253, 241, 51, 111, 58, + 41, 218, 134, 109, 127, 132, 134, 215, 85, 70, 206, 237, 175, 147, 25, 1>>, + 33, + <<3, 92, 220, 97, 252, 123, 169, 113, 192, 181, 1, 166, 70, 162, 168, 59, 16, 44, + 180, 56, 129, 33, 124, 166, 130, 220, 134, 226, 215, 63, 168, 130, 146>> + ] + } }, %In{ - redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308", + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<133, 209, 53, 55, 242, 226, 101, 64, 90, 52, 219, 175, 169, 227, 221, 160, 31, + 184, 35, 8>> + ] + }, witness_utxo: %Bitcoinex.Transaction.Out{ script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", value: 100_000_000 @@ -189,14 +206,24 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_648, 0] }, - public_key: "029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 71_297_889_195_667_677_566_853_709_053_103_131_423_162_117_776_603_813_866_869_556_867_184_864_299_121, + y: + 104_942_558_329_072_212_830_372_841_913_067_137_391_352_852_664_610_839_861_441_469_392_637_640_386_592 + }, pfp: <<217, 12, 106, 79>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_649, 0] }, - public_key: "03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 24_953_540_938_576_426_582_583_323_081_660_174_044_181_452_719_495_711_599_804_679_737_956_351_918_091, + y: + 115_045_788_123_487_702_367_674_850_763_085_807_099_618_969_835_940_161_186_892_982_331_420_489_690_021 + }, pfp: <<217, 12, 106, 79>> } ], @@ -216,7 +243,12 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_649] }, - public_key: "039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef9", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 71_916_192_309_307_030_987_819_255_271_417_383_252_226_513_354_309_312_442_460_376_541_014_510_935_801, + y: + 87_829_264_540_646_754_532_057_909_755_234_567_745_025_032_566_591_945_062_448_323_127_703_490_972_993 + }, pfp: <<237, 228, 92, 197>> } ] @@ -253,25 +285,47 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_652] }, - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 80_151_448_986_003_541_602_445_390_187_849_273_116_474_332_975_424_144_708_997_035_409_020_762_307_910, + y: + 69_508_417_946_258_487_178_124_295_602_214_619_390_077_167_556_721_617_739_677_223_796_084_805_268_603 + }, pfp: <<180, 166, 186, 103>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_653] }, - public_key: "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 100_565_082_940_006_144_500_918_712_336_860_239_214_176_819_872_577_604_419_994_557_980_044_395_840_445, + y: + 10_103_911_892_721_234_278_209_537_983_272_986_690_525_296_344_245_993_216_791_872_735_802_656_255_649 + }, pfp: <<180, 166, 186, 103>> } ], partial_sig: [ %{ - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 80_151_448_986_003_541_602_445_390_187_849_273_116_474_332_975_424_144_708_997_035_409_020_762_307_910, + y: + 69_508_417_946_258_487_178_124_295_602_214_619_390_077_167_556_721_617_739_677_223_796_084_805_268_603 + }, signature: "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" } ], - redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681", + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 32, + <<119, 31, 209, 138, 212, 89, 102, 109, 212, 159, 61, 86, 78, 61, 188, 66, 244, 200, + 71, 116, 227, 96, 173, 161, 104, 22, 168, 237, 72, 141, 86, 129>> + ] + }, witness_script: "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae", witness_utxo: %Bitcoinex.Transaction.Out{ @@ -358,21 +412,36 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed4", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 28_090_348_957_135_603_138_302_582_488_634_744_063_769_373_744_003_534_936_250_327_398_955_197_210_324, + y: + 72_997_351_855_862_386_313_265_421_144_400_773_180_384_569_016_361_889_288_840_365_141_506_887_425_034 + }, pfp: <<0, 0, 0, 0>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d31378", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 34_853_223_431_373_393_811_263_769_947_202_683_171_474_774_417_547_322_886_690_942_546_874_362_696_568, + y: + 113_268_030_251_073_357_691_209_583_004_229_905_838_714_795_934_778_666_512_471_345_919_935_656_053_596 + }, pfp: <<0, 0, 0, 0>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c725", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_870_426_774_663_552_239_665_427_301_830_100_617_674_007_218_870_368_410_715_268_929_150_665_213_733, + y: + 384_321_948_423_668_768_499_139_968_164_679_158_512_264_350_568_463_086_818_860_781_694_386_290_525 + }, pfp: <<0, 0, 0, 0>> } ], @@ -459,7 +528,14 @@ defmodule Bitcoinex.PSBTTest do value: 100_000_000, script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" }, - redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308" + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<133, 209, 53, 55, 242, 226, 101, 64, 90, 52, 219, 175, 169, 227, 221, 160, 31, + 184, 35, 8>> + ] + } } ], expected_out: [ @@ -470,7 +546,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_650] }, pfp: <<180, 166, 186, 103>>, - public_key: "02ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e99" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 106_218_583_072_196_447_736_380_334_552_715_158_727_992_653_635_463_477_362_034_291_905_132_141_629_081, + y: + 111_853_877_987_030_790_093_148_386_887_435_750_835_805_008_949_758_207_878_306_607_752_401_323_629_588 + } } ] }, @@ -481,7 +562,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_648, 2_147_483_649, 2_147_483_650] }, pfp: <<180, 166, 186, 103>>, - public_key: "0394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d05" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 67_377_249_048_514_558_622_301_148_581_987_854_752_621_201_928_971_282_930_920_275_574_615_944_805_637, + y: + 33_878_338_431_620_821_020_481_726_477_149_418_114_572_000_720_118_999_798_151_283_966_732_510_331_525 + } } ] } @@ -660,7 +746,12 @@ defmodule Bitcoinex.PSBTTest do }, partial_sig: [ %{ - public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 21_976_933_772_883_498_789_027_591_114_401_201_619_393_627_105_936_886_383_472_697_361_469_744_065_884, + y: + 7_200_326_197_606_395_130_472_957_753_047_368_877_161_908_639_095_988_897_070_963_103_467_471_435_399 + }, signature: "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" } @@ -671,7 +762,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 1] }, pfp: <<39, 86, 156, 80>>, - public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 21_976_933_772_883_498_789_027_591_114_401_201_619_393_627_105_936_886_383_472_697_361_469_744_065_884, + y: + 7_200_326_197_606_395_130_472_957_753_047_368_877_161_908_639_095_988_897_070_963_103_467_471_435_399 + } } ] }, @@ -682,7 +778,12 @@ defmodule Bitcoinex.PSBTTest do }, partial_sig: [ %{ - public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 90_220_664_355_153_390_194_324_778_431_845_886_101_502_662_137_446_551_229_950_573_847_917_083_275_536, + y: + 30_671_849_324_763_586_853_276_572_668_087_977_292_899_353_337_776_873_336_350_612_915_119_803_985_620 + }, signature: "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" } @@ -693,7 +794,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 0] }, pfp: <<39, 86, 156, 80>>, - public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 90_220_664_355_153_390_194_324_778_431_845_886_101_502_662_137_446_551_229_950_573_847_917_083_275_536, + y: + 30_671_849_324_763_586_853_276_572_668_087_977_292_899_353_337_776_873_336_350_612_915_119_803_985_620 + } } ] } @@ -707,7 +813,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 4] }, pfp: <<39, 86, 156, 80>>, - public_key: "02d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 95_008_039_234_411_418_297_358_865_313_145_660_837_471_761_864_556_347_248_745_408_858_102_616_985_839, + y: + 58_958_258_549_746_503_926_788_822_872_770_747_559_389_384_344_394_656_102_791_551_503_991_185_090_332 + } } ] } @@ -842,7 +953,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -862,7 +978,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -925,7 +1046,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -945,7 +1071,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1011,7 +1142,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1031,7 +1167,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1094,7 +1235,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1114,7 +1260,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1177,7 +1328,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1197,7 +1353,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1260,7 +1421,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1280,7 +1446,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1343,7 +1514,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1363,7 +1539,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1426,7 +1607,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1446,7 +1632,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1509,7 +1700,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1529,7 +1725,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1592,7 +1793,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1612,7 +1818,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1675,7 +1886,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1695,7 +1911,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1758,7 +1979,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1778,7 +2004,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1845,7 +2076,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1865,7 +2101,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1917,14 +2158,20 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, - 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - tap_internal_key: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, - 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ @@ -1935,7 +2182,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] }, pfp: <<119, 43, 45, 167>>, - public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_608_022_430_402_926_014_916_497_053_789_681_947_094_617_356_347_258_841_609_839_742_612_155_560_920, + y: + 69_442_595_448_928_048_809_603_412_737_824_612_252_030_373_964_082_523_340_701_998_153_322_752_075_027 + } } ] } @@ -1982,14 +2234,20 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, - 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - tap_internal_key: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, - 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ @@ -2000,7 +2258,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] }, pfp: <<119, 43, 45, 167>>, - public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_608_022_430_402_926_014_916_497_053_789_681_947_094_617_356_347_258_841_609_839_742_612_155_560_920, + y: + 69_442_595_448_928_048_809_603_412_737_824_612_252_030_373_964_082_523_340_701_998_153_322_752_075_027 + } } ] } @@ -2043,21 +2306,30 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, - 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - tap_internal_key: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, - 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ %Bitcoinex.PSBT.Out{ - tap_internal_key: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, - 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2065,9 +2337,12 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, - 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346 + } } ] } @@ -2167,9 +2442,12 @@ defmodule Bitcoinex.PSBTTest do 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, - 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2180,17 +2458,23 @@ defmodule Bitcoinex.PSBTTest do 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, - 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, leaf_hashes: [], pfp: <<124, 70, 30, 93>>, - pubkey: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, - 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2201,14 +2485,20 @@ defmodule Bitcoinex.PSBTTest do 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, - 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + } } ], - tap_internal_key: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, - 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, tap_merkle_root: <<240, 54, 46, 47, 117, 166, 244, 32, 165, 189, 227, 235, 34, 29, 150, 174, 103, 32, 207, 37, 248, 24, 144, 201, 91, 29, 119, 90, 203, 81, 94, 101>> @@ -2216,9 +2506,13 @@ defmodule Bitcoinex.PSBTTest do ], expected_out: [ %Bitcoinex.PSBT.Out{ - tap_internal_key: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, - 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2226,9 +2520,13 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, - 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + } } ] } @@ -2271,21 +2569,30 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, - 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - tap_internal_key: - <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, - 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ %Bitcoinex.PSBT.Out{ - tap_internal_key: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, - 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, tap_tree: %{ leaves: [ %{ @@ -2336,17 +2643,24 @@ defmodule Bitcoinex.PSBTTest do 59, 22, 23, 218, 58, 65, 89, 202, 0, 130, 22, 205, 133, 107, 46, 14>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<68, 250, 164, 154, 3, 56, 222, 72, 140, 141, 255, 254, 205, 251, 111, 50, 159, - 56, 11, 213, 102, 239, 32, 200, 223, 109, 129, 62, 171, 28, 66, 115>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 31_200_121_508_428_702_019_893_244_742_884_023_762_479_223_940_651_285_251_383_304_714_523_995_030_131, + y: + 53_120_362_633_623_697_201_506_777_401_802_198_637_852_190_256_815_688_867_012_397_976_660_138_781_348, + z: 0 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, leaf_hashes: [], pfp: <<124, 70, 30, 93>>, - pubkey: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, - 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2357,9 +2671,13 @@ defmodule Bitcoinex.PSBTTest do 122, 120, 234, 237, 224, 242, 226, 150, 207, 67, 89, 97, 168, 244, 202>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<99, 28, 95, 59, 88, 50, 184, 251, 222, 191, 177, 151, 4, 206, 235, 50, 60, 33, - 244, 15, 122, 36, 244, 61, 104, 239, 12, 194, 107, 18, 89, 105>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 44_829_100_993_385_313_407_048_989_888_172_542_182_954_521_609_934_891_498_742_790_273_562_110_482_793, + y: + 35_170_189_511_235_529_380_611_046_465_697_762_403_784_417_654_141_736_675_144_354_496_257_103_432_480, + z: 0 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2370,9 +2688,13 @@ defmodule Bitcoinex.PSBTTest do 96, 86, 208, 180, 8, 140, 185, 51, 202, 231, 130, 108, 184, 216, 44>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<115, 110, 87, 41, 0, 254, 18, 82, 88, 154, 33, 67, 200, 243, 199, 159, 113, 160, - 65, 45, 35, 83, 175, 117, 94, 151, 1, 199, 130, 105, 74, 2>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 52_210_932_321_595_760_052_581_731_536_224_183_934_983_599_287_982_449_328_637_004_030_112_510_331_394, + y: + 77_569_177_239_768_622_921_463_492_097_741_805_392_046_359_270_612_182_587_656_363_386_108_779_135_734, + z: 0 + } } ] } @@ -2413,9 +2735,12 @@ defmodule Bitcoinex.PSBTTest do leaf_hash: <<205, 151, 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>>, - pubkey: - <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, - 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + }, signature: <<191, 129, 141, 151, 87, 214, 255, 235, 83, 139, 160, 87, 251, 76, 31, 196, 224, 245, 239, 24, 110, 118, 91, 235, 86, 71, 145, 224, 42, 245, 253, 61, 94, 37, 81, @@ -2426,9 +2751,12 @@ defmodule Bitcoinex.PSBTTest do leaf_hash: <<17, 95, 46, 73, 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>>, - pubkey: - <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, - 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + }, signature: <<225, 241, 171, 111, 171, 250, 38, 178, 54, 242, 24, 51, 113, 157, 193, 212, 40, 171, 118, 141, 128, 249, 31, 153, 136, 216, 171, 239, 71, 191, 184, 99, 187, 31, @@ -2439,9 +2767,12 @@ defmodule Bitcoinex.PSBTTest do leaf_hash: <<111, 125, 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>>, - pubkey: - <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, - 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + }, signature: <<236, 31, 3, 121, 32, 100, 97, 200, 51, 66, 40, 84, 35, 50, 103, 8, 171, 3, 31, 13, 164, 162, 83, 238, 69, 170, 250, 91, 140, 146, 3, 77, 139, 96, 84, 144, 248, @@ -2513,9 +2844,12 @@ defmodule Bitcoinex.PSBTTest do 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, - 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2526,17 +2860,23 @@ defmodule Bitcoinex.PSBTTest do 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, - 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, leaf_hashes: [], pfp: <<124, 70, 30, 93>>, - pubkey: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, - 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2547,14 +2887,20 @@ defmodule Bitcoinex.PSBTTest do 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>> ], pfp: <<119, 43, 45, 167>>, - pubkey: - <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, - 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + } } ], - tap_internal_key: - <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, - 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, tap_merkle_root: <<240, 54, 46, 47, 117, 166, 244, 32, 165, 189, 227, 235, 34, 29, 150, 174, 103, 32, 207, 37, 248, 24, 144, 201, 91, 29, 119, 90, 203, 81, 94, 101>> @@ -2562,9 +2908,13 @@ defmodule Bitcoinex.PSBTTest do ], expected_out: [ %Bitcoinex.PSBT.Out{ - tap_internal_key: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, - 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2572,9 +2922,13 @@ defmodule Bitcoinex.PSBTTest do }, leaf_hashes: [], pfp: <<119, 43, 45, 167>>, - pubkey: - <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, - 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + } } ] } From 2331e3ede6b15f0c5c3a679c179796ce64df5ab1 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 2 Mar 2023 00:48:05 -0500 Subject: [PATCH 38/40] fix credo lint --- lib/psbt.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 6f4d799..ddc71b2 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -118,7 +118,7 @@ defmodule Bitcoinex.PSBT do }} end - @spec to_tx(PSBT.t()) :: %Bitcoinex.Transaction{} + @spec to_tx(PSBT.t()) :: Bitcoinex.Transaction def to_tx(psbt) do tx = psbt.global.unsigned_tx @@ -327,12 +327,12 @@ defmodule Bitcoinex.PSBT.Global do %Global{global | unknown: unknown} end - @spec parse_global(nonempty_binary) :: {%Global{}, binary} + @spec parse_global(nonempty_binary) :: {Global, binary} def parse_global(psbt) do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) end - @spec from_tx(%Transaction{}) :: %Global{} + @spec from_tx(Transaction) :: Global def from_tx(tx) do %Global{ unsigned_tx: tx, @@ -487,7 +487,7 @@ defmodule Bitcoinex.PSBT.Global do PsbtUtils.serialize_kv(k, v) end - @spec serialize_global(%Global{}) :: nonempty_binary + @spec serialize_global(Global) :: nonempty_binary def serialize_global(global) do serialized_global = Enum.reduce( @@ -1408,7 +1408,7 @@ defmodule Bitcoinex.PSBT.Out do <<>> end - @spec from_tx_outputs(list(%TxOut{})) :: list(%Out{}) + @spec from_tx_outputs(list(TxOut)) :: list(Out) def from_tx_outputs(tx_outputs) do Enum.reduce(tx_outputs, [], fn _, acc -> [%Out{} | acc] end) |> Enum.reverse() From 4d8aa171b531cb7e819b98580230cb7628d3cce7 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Mar 2023 16:40:46 -0500 Subject: [PATCH 39/40] lint --- lib/psbt.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index ddc71b2..a4e0913 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -118,7 +118,6 @@ defmodule Bitcoinex.PSBT do }} end - @spec to_tx(PSBT.t()) :: Bitcoinex.Transaction def to_tx(psbt) do tx = psbt.global.unsigned_tx @@ -332,7 +331,6 @@ defmodule Bitcoinex.PSBT.Global do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) end - @spec from_tx(Transaction) :: Global def from_tx(tx) do %Global{ unsigned_tx: tx, From 18d15b3c28342cb30ca388dbb1220f91f363772b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Mar 2023 18:41:19 -0500 Subject: [PATCH 40/40] lint --- lib/psbt.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index a12285b..61af54e 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -14,7 +14,6 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.Utils alias Bitcoinex.Transaction alias Bitcoinex.Transaction.Utils, as: TxUtils - alias Bitcoinex.Utils @type t() :: %__MODULE__{}