From 5141ce1f4e1c2d34de0d4bff7124581149e3b791 Mon Sep 17 00:00:00 2001 From: victor felder Date: Sun, 26 Apr 2026 09:55:16 +0200 Subject: [PATCH 1/4] fix(forge_operation): use kind tag instead of string endorsement/1, endorsement_with_slot/1, failing_noop/1 called forge_tag(content["kind"]) with the kind string instead of the @operation_tags lookup, which raised ArgumentError. --- lib/forge_operation.ex | 12 +++++++++--- test/forge_operation_test.exs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/forge_operation.ex b/lib/forge_operation.ex index 764cbce..5031046 100644 --- a/lib/forge_operation.ex +++ b/lib/forge_operation.ex @@ -245,7 +245,10 @@ defmodule Tezex.ForgeOperation do def endorsement(content) do with :ok <- validate_required_keys(content, ~w(kind level)) do content = - [forge_tag(content["kind"]), Forge.forge_int32(String.to_integer(content["level"]))] + [ + forge_tag(@operation_tags[content["kind"]]), + Forge.forge_int32(String.to_integer(content["level"])) + ] |> IO.iodata_to_binary() {:ok, content} @@ -278,7 +281,7 @@ defmodule Tezex.ForgeOperation do {:ok, endorsement} <- inline_endorsement(content["endorsement"]) do content = [ - forge_tag(content["kind"]), + forge_tag(@operation_tags[content["kind"]]), Forge.forge_array(endorsement), Forge.forge_int16(String.to_integer(content["slot"])) ] @@ -292,7 +295,10 @@ defmodule Tezex.ForgeOperation do def failing_noop(content) do with :ok <- validate_required_keys(content, ~w(kind arbitrary)) do content = - [forge_tag(content["kind"]), Forge.forge_array(content["arbitrary"])] + [ + forge_tag(@operation_tags[content["kind"]]), + Forge.forge_array(content["arbitrary"]) + ] |> IO.iodata_to_binary() {:ok, content} diff --git a/test/forge_operation_test.exs b/test/forge_operation_test.exs index 79a3f29..f3dd151 100644 --- a/test/forge_operation_test.exs +++ b/test/forge_operation_test.exs @@ -233,6 +233,41 @@ defmodule Tezex.ForgeOperationTest do end end + describe "operations using the @operation_tags lookup" do + test "endorsement encodes the kind tag, not the kind string" do + assert {:ok, result} = + ForgeOperation.endorsement(%{"kind" => "endorsement", "level" => "100"}) + + # tag for "endorsement" is 0; level is forge_int32(100) = <<0,0,0,100>> + assert result == <<0, 0, 0, 0, 100>> + end + + test "failing_noop encodes the kind tag, not the kind string" do + assert {:ok, result} = + ForgeOperation.failing_noop(%{"kind" => "failing_noop", "arbitrary" => "deadbeef"}) + + # tag for "failing_noop" is 17; "arbitrary" is wrapped in a forge_array + assert result == <<17, 0, 0, 0, 8, "deadbeef">> + end + + test "endorsement_with_slot encodes the kind tag, not the kind string" do + assert {:ok, result} = + ForgeOperation.endorsement_with_slot(%{ + "kind" => "endorsement_with_slot", + "endorsement" => %{ + "branch" => "BLWdshvgEYbtUaABnmqkMuyMezpfsu36DEJPDJN63CW3TFuk7bP", + "operations" => %{"kind" => "endorsement", "level" => "100"}, + "signature" => + "edsigtqqVg7CM2ynDXRHUfVEdL4LxKAJ1xrM1poMPXZdwVQ3SQ6YBkqPcAuaGYbeqUTrg374dDNFvJKCVfMA7T1Vrotg91Hsuam" + }, + "slot" => "1" + }) + + # tag for "endorsement_with_slot" is 10 + assert <<10, _rest::binary>> = result + end + end + test "validate_required_keys/2" do assert ForgeOperation.validate_required_keys(%{"a" => 1, "b" => 2}, ~w(a b)) == :ok From 7b03f0ec4062fb9c2d40a748c5274a09c78ccac6 Mon Sep 17 00:00:00 2001 From: victor felder Date: Tue, 19 May 2026 09:41:16 +0200 Subject: [PATCH 2/4] refactor(forge_operation): move tag lookup into forge_tag/1 --- lib/forge_operation.ex | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/forge_operation.ex b/lib/forge_operation.ex index 5031046..ac5b382 100644 --- a/lib/forge_operation.ex +++ b/lib/forge_operation.ex @@ -53,8 +53,8 @@ defmodule Tezex.ForgeOperation do end end - defp forge_tag(tag) do - <> + defp forge_tag(kind) do + <> end @spec operation(map()) :: {:ok, nonempty_binary()} | {:error, nonempty_binary()} @@ -100,7 +100,7 @@ defmodule Tezex.ForgeOperation do with :ok <- validate_required_keys(content, ~w(kind pkh secret)) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), binary_slice(Forge.forge_address(content["pkh"]), 2..-1//1), Base.decode16!(content["secret"], case: :mixed) ] @@ -119,7 +119,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -155,7 +155,7 @@ defmodule Tezex.ForgeOperation do )) || :ok do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -190,7 +190,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -221,7 +221,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -246,7 +246,7 @@ defmodule Tezex.ForgeOperation do with :ok <- validate_required_keys(content, ~w(kind level)) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_int32(String.to_integer(content["level"])) ] |> IO.iodata_to_binary() @@ -281,7 +281,7 @@ defmodule Tezex.ForgeOperation do {:ok, endorsement} <- inline_endorsement(content["endorsement"]) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_array(endorsement), Forge.forge_int16(String.to_integer(content["slot"])) ] @@ -296,7 +296,7 @@ defmodule Tezex.ForgeOperation do with :ok <- validate_required_keys(content, ~w(kind arbitrary)) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_array(content["arbitrary"]) ] |> IO.iodata_to_binary() @@ -314,7 +314,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -337,7 +337,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -365,7 +365,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), @@ -394,7 +394,7 @@ defmodule Tezex.ForgeOperation do ) do content = [ - forge_tag(@operation_tags[content["kind"]]), + forge_tag(content["kind"]), Forge.forge_address(content["source"], :bytes, true), Forge.forge_nat(String.to_integer(content["fee"])), Forge.forge_nat(String.to_integer(content["counter"])), From 5db1de02affeadba3cf7f2117eea1bb2d1a64c55 Mon Sep 17 00:00:00 2001 From: victor felder Date: Tue, 19 May 2026 09:33:13 +0200 Subject: [PATCH 3/4] fix(crypto): fix crypto_secretbox_open/3 spec --- lib/crypto/nacl.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/crypto/nacl.ex b/lib/crypto/nacl.ex index 1e720e1..ee704ce 100644 --- a/lib/crypto/nacl.ex +++ b/lib/crypto/nacl.ex @@ -21,7 +21,8 @@ defmodule Tezex.Crypto.NaCl do - `{:error, reason}` - Decryption failed """ @spec crypto_secretbox_open(binary(), binary(), binary()) :: - {:ok, binary()} | {:error, :decryption_failed} + {:ok, binary()} + | {:error, :decryption_failed | :invalid_nonce_length | :invalid_key_length} def crypto_secretbox_open(_ciphertext, nonce, _key) when byte_size(nonce) != 24 do {:error, :invalid_nonce_length} end From 1804687988f2011b7742c4e981bc12bf924f32fa Mon Sep 17 00:00:00 2001 From: victor felder Date: Mon, 18 May 2026 11:37:01 +0200 Subject: [PATCH 4/4] style(forge): use and instead of && --- lib/forge.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forge.ex b/lib/forge.ex index 82e9edd..8b66c91 100644 --- a/lib/forge.ex +++ b/lib/forge.ex @@ -360,7 +360,7 @@ defmodule Tezex.Forge do address_bytes = forge_address(address) - if entrypoint != nil && entrypoint != "default" do + if entrypoint != nil and entrypoint != "default" do address_bytes <> entrypoint else address_bytes