diff --git a/lib/secp256k1/point.ex b/lib/secp256k1/point.ex index ff980c1..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 """ @@ -124,7 +134,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 728e3ec..01391c9 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -35,35 +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} = - 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) - - case Secp256k1.force_even_y(k0) do - {:error, msg} -> - {:error, msg} - - k -> - e = - tagged_hash_challenge( - Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes - ) - |> :binary.decode_unsigned() - |> Math.modulo(@n) - - sig_s = - (k.d + d.d * e) - |> Math.modulo(@n) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + 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 end end end @@ -73,8 +58,72 @@ 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) + 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() + |> 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) -> + {:error, "R point is infinite"} + + r_point.x != rx -> + {:error, "x's do not match #{r_point.x} vs #{rx}"} + + 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()} @@ -85,17 +134,127 @@ defmodule Bitcoinex.Secp256k1.Schnorr do def verify_signature(pubkey, z, %Signature{r: r, s: s}) 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) - e = - tagged_hash_challenge(r_bytes <> Point.x_bytes(pubkey) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) + r_point = calculate_r(pubkey, s, e) + + validate_r(r_point, r) + end + + # negate a secret + defp conditional_negate(d, true), do: %PrivateKey{d: d} |> PrivateKey.negate() + defp conditional_negate(d, false), do: %PrivateKey{d: d} - r_point = - @generator_point - |> Math.multiply(s) - |> Math.add(Math.multiply(pubkey, @n - e)) + # 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) + aux_bytes = Utils.int_to_big(aux, 32) + d_point = PrivateKey.to_point(sk) + + 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 """ + 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 + 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) + + {: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 + {Point.negate(point), true} + end + end + + @doc """ + decrypt_signature uses the tweak/decryption key to transform an + adaptor/encrypted signature into a final, valid Schnorr signature. + """ + @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 = conditional_negate(tweak.d, was_negated) + final_s = Math.modulo(tweak.d + s, @n) + %Signature{r: r, s: final_s} + 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"} - !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == r + 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/lib/secp256k1/secp256k1.ex b/lib/secp256k1/secp256k1.ex index 2f5efa2..59fe059 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/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv new file mode 100644 index 0000000..1d77803 --- /dev/null +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -0,0 +1,21 @@ +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 +"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 diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index d5aa295..fe4a8aa 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -4,7 +4,10 @@ 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 @@ -153,6 +156,25 @@ 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 for t <- @schnorr_signatures_with_secrets do @@ -260,7 +282,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do for _ <- 1..1000 do secret = - 32 + 31 |> :crypto.strong_rand_bytes() |> :binary.decode_unsigned() @@ -271,4 +293,49 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end end end + + describe "encrypted signature testing" do + test "encrypted_sign/4 and verify_encrypted_signature/5" do + for _ <- 1..1000 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..1000 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, was_negated) + # ensure valid Schnorr signature + assert Schnorr.verify_signature(pk, z, sig) + end + end + + test "encrypt & recover descryption key" do + for _ <- 1..1000 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, 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 a723d01..4a6c4f5 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