From a4b409ba6f16f85a05574fd7f9f923eacf04d29e Mon Sep 17 00:00:00 2001 From: Samuel <42943690+samuel-uniris@users.noreply.github.com> Date: Fri, 3 Sep 2021 17:29:19 +0200 Subject: [PATCH] Add multiple secrets support (#58) Now transaction data keys contains: - secrets: list of encrypted data - authorized keys: list of authorized keys by secret So we can have some secrets shared by the nodes and some secrets for the owners of the transaction chain --- .../contracts/contract/conditions.ex | 6 +- lib/archethic/contracts/contract/constants.ex | 17 +- lib/archethic/contracts/interpreter.ex | 48 ++- .../interpreter/transaction_statements.ex | 46 ++- lib/archethic/contracts/worker.ex | 65 ++-- lib/archethic/crypto.ex | 4 +- .../keystore/shared_secrets/software_impl.ex | 4 +- lib/archethic/election.ex | 4 +- .../mining/pending_transaction_validation.ex | 4 +- lib/archethic/mining/proof_of_work.ex | 8 +- lib/archethic/p2p/mem_table_loader.ex | 5 +- lib/archethic/shared_secrets/node_renewal.ex | 4 +- lib/archethic/transaction_chain.ex | 18 +- .../transaction_chain/transaction.ex | 6 +- .../transaction_chain/transaction/data.ex | 29 +- .../transaction/data/keys.ex | 242 ++++++++++----- lib/archethic/utils/playbooks/uco.ex | 6 +- .../controllers/api/transaction_payload.ex | 5 +- .../controllers/api/types/authorized_keys.ex | 73 +++-- .../controllers/api/types/secret_list.ex | 35 +++ .../graphql_schema/transaction_type.ex | 24 +- .../explorer/transaction_details.html.leex | 45 ++- mix.exs | 2 +- .../1_create_transaction_data_types.cql | 4 +- priv/static/docs/_coverpage.md | 2 +- .../docs/network/transaction_chain/README.md | 4 +- test/archethic/bootstrap/sync_test.exs | 2 +- test/archethic/bootstrap_test.exs | 2 +- test/archethic/contracts/interpreter_test.exs | 15 +- test/archethic/contracts/worker_test.exs | 12 +- .../shared_secrets/software_impl_test.exs | 2 +- test/archethic/crypto_test.exs | 4 +- .../pending_transaction_validation_test.exs | 20 +- test/archethic/p2p/mem_table_loader_test.exs | 24 +- .../shared_secrets/node_renewal_test.exs | 7 +- .../transaction/data_test.exs | 22 +- .../transaction_data/keys_test.exs | 20 +- .../api/transaction_payload_test.exs | 292 ++++++------------ 38 files changed, 622 insertions(+), 510 deletions(-) create mode 100644 lib/archethic_web/controllers/api/types/secret_list.ex diff --git a/lib/archethic/contracts/contract/conditions.ex b/lib/archethic/contracts/contract/conditions.ex index 81d7b5aca..74cd750ed 100644 --- a/lib/archethic/contracts/contract/conditions.ex +++ b/lib/archethic/contracts/contract/conditions.ex @@ -8,7 +8,7 @@ defmodule ArchEthic.Contracts.Contract.Conditions do :content, :code, :authorized_keys, - :secret, + :secrets, :uco_transfers, :nft_transfers, :previous_public_key, @@ -23,7 +23,7 @@ defmodule ArchEthic.Contracts.Contract.Conditions do content: binary() | Macro.t() | nil, code: binary() | Macro.t() | nil, authorized_keys: map() | Macro.t() | nil, - secret: binary() | Macro.t() | nil, + secrets: list(binary()) | Macro.t() | nil, uco_transfers: map() | Macro.t() | nil, nft_transfers: map() | Macro.t() | nil, previous_public_key: binary() | Macro.t() | nil, @@ -35,7 +35,7 @@ defmodule ArchEthic.Contracts.Contract.Conditions do content: nil, code: nil, authorized_keys: nil, - secret: nil, + secrets: nil, uco_transfers: nil, nft_transfers: nil, previous_public_key: nil diff --git a/lib/archethic/contracts/contract/constants.ex b/lib/archethic/contracts/contract/constants.ex index 765d1c138..02baba747 100644 --- a/lib/archethic/contracts/contract/constants.ex +++ b/lib/archethic/contracts/contract/constants.ex @@ -30,10 +30,11 @@ defmodule ArchEthic.Contracts.Contract.Constants do data: %TransactionData{ content: content, code: code, - keys: %Keys{ - authorized_keys: authorized_keys, - secret: secret - }, + keys: + keys = %Keys{ + authorized_keys: authorized_keys, + secrets: secrets + }, ledger: %Ledger{ uco: %UCOLedger{ transfers: uco_transfers @@ -50,9 +51,9 @@ defmodule ArchEthic.Contracts.Contract.Constants do "type" => Atom.to_string(type), "content" => content, "code" => code, - "authorized_public_keys" => Map.keys(authorized_keys), + "authorized_public_keys" => Keys.list_authorized_public_keys(keys), "authorized_keys" => authorized_keys, - "secret" => secret, + "secrets" => secrets, "previous_public_key" => previous_public_key, "recipients" => recipients, "uco_transfers" => @@ -84,8 +85,8 @@ defmodule ArchEthic.Contracts.Contract.Constants do code: Map.get(constants, "code", ""), content: Map.get(constants, "content", ""), keys: %Keys{ - authorized_keys: Map.get(constants, "authorized_keys", %{}), - secret: Map.get(constants, "secret", "") + authorized_keys: Map.get(constants, "authorized_keys", []), + secrets: Map.get(constants, "secrets", []) }, recipients: Map.get(constants, "recipients", []), ledger: %Ledger{ diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index 4e23709df..0ccc543a6 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -37,7 +37,7 @@ defmodule ArchEthic.Contracts.Interpreter do "uco_transfers", "nft_transfers", "authorized_public_keys", - "secret", + "secrets", "recipients" ] @@ -238,6 +238,9 @@ defmodule ArchEthic.Contracts.Interpreter do {{:error, :unexpected_token}, {{:atom, key}, metadata, _}} -> {:error, format_error_reason({metadata, "unexpected_token", key})} + {{:error, :unexpected_token}, {{:atom, key}, _}} -> + {:error, format_error_reason({[], "unexpected_token", key})} + {:error, reason = {_metadata, _message, _cause}} -> {:error, format_error_reason(reason)} end @@ -251,35 +254,25 @@ defmodule ArchEthic.Contracts.Interpreter do end defp format_error_reason({metadata, message, cause}) do - message = - if message == "unexpected token: " do - "unexpected token" - else - message - end + message = prepare_message(message) - line = Keyword.get(metadata, :line) - column = Keyword.get(metadata, :column) - - metadata_string = "L#{line}" - - metadata_string = - if column == nil do - metadata_string - else - metadata_string <> ":C#{column}" - end + [prepare_message(message), cause, metadata_to_string(metadata)] + |> Enum.reject(&(&1 == "")) + |> Enum.join(" - ") + end - message = - if is_atom(message) do - message |> Atom.to_string() |> String.replace("_", " ") - else - message - end + defp prepare_message(message) when is_atom(message) do + message |> Atom.to_string() |> String.replace("_", " ") + end - "#{message} - #{cause} - #{metadata_string}" + defp prepare_message(message) when is_binary(message) do + String.trim_trailing(message, ":") end + defp metadata_to_string(line: line, column: column), do: "L#{line}:C#{column}" + defp metadata_to_string(line: line), do: "L#{line}" + defp metadata_to_string(_), do: "" + # Whitelist operators defp prewalk(node = {:+, _, _}, acc = {:ok, %{scope: scope}}) when scope != :root, do: {node, acc} @@ -675,7 +668,8 @@ defmodule ArchEthic.Contracts.Interpreter do defp prewalk( node = [ {{:atom, "public_key"}, _public_key}, - {{:atom, "encrypted_secret_key"}, _encrypted_secret_key} + {{:atom, "encrypted_secret_key"}, _encrypted_secret_key}, + {{:atom, "secret_index"}, _secret_index} ], acc = {:ok, %{scope: {:function, "add_authorized_key", :actions}}} ) do @@ -686,7 +680,7 @@ defmodule ArchEthic.Contracts.Interpreter do node = {{:atom, arg}, _}, acc = {:ok, %{scope: {:function, "add_authorized_key", :actions}}} ) - when arg in ["public_key", "encrypted_secret_key"], + when arg in ["public_key", "encrypted_secret_key", "secret_index"], do: {node, acc} # Whitelist generics diff --git a/lib/archethic/contracts/interpreter/transaction_statements.ex b/lib/archethic/contracts/interpreter/transaction_statements.ex index 80894b1c2..a83530653 100644 --- a/lib/archethic/contracts/interpreter/transaction_statements.ex +++ b/lib/archethic/contracts/interpreter/transaction_statements.ex @@ -127,55 +127,77 @@ defmodule ArchEthic.Contracts.Interpreter.TransactionStatements do ## Examples iex> TransactionStatements.add_authorized_key(%Transaction{data: %TransactionData{}}, [ + ...> {"secret_index", 0}, ...> {"public_key", "22368B50D3B2976787CFCC27508A8E8C67483219825F998FC9D6908D54D0FE10"}, ...> {"encrypted_secret_key", "FB49F76933689ECC9D260D57C2BEF9489234FE72DD2ED1C77E2E8B4E94D9137F"} ...> ]) %Transaction{ data: %TransactionData{ keys: %Keys{ - authorized_keys: %{ + authorized_keys: [%{ <<34, 54, 139, 80, 211, 178, 151, 103, 135, 207, 204, 39, 80, 138, 142, 140, 103, 72, 50, 25, 130, 95, 153, 143, 201, 214, 144, 141, 84, 208, 254, 16>> => <<251, 73, 247, 105, 51, 104, 158, 204, 157, 38, 13, 87, 194, 190, 249, 72, 146, 52, 254, 114, 221, 46, 209, 199, 126, 46, 139, 78, 148, 217, 19, 127>> - } + }] } } } """ @spec add_authorized_key(Transaction.t(), list()) :: map() def add_authorized_key(tx = %Transaction{}, [ + {"secret_index", secret_index}, {"public_key", public_key}, {"encrypted_secret_key", encrypted_secret_key} ]) - when is_binary(public_key) and is_binary(encrypted_secret_key) do + when is_integer(secret_index) and secret_index >= 0 and is_binary(public_key) and + is_binary(encrypted_secret_key) do update_in( tx, [Access.key(:data), Access.key(:keys), Access.key(:authorized_keys)], - &Map.put(&1, decode_binary(public_key), decode_binary(encrypted_secret_key)) + fn authorized_keys -> + case length(authorized_keys) do + 0 -> + [%{decode_binary(public_key) => decode_binary(encrypted_secret_key)}] + + length when secret_index < length -> + List.update_at( + authorized_keys, + secret_index, + &Map.put(&1, decode_binary(public_key), decode_binary(encrypted_secret_key)) + ) + + length -> + List.update_at( + authorized_keys, + length - 1, + &Map.put(&1, decode_binary(public_key), decode_binary(encrypted_secret_key)) + ) + end + end ) end @doc """ - Set the transaction encrypted secret + Add a transaction encrypted secret ## Examples - iex> TransactionStatements.set_secret(%Transaction{data: %TransactionData{}}, "mysecret") + iex> TransactionStatements.add_secret(%Transaction{data: %TransactionData{}}, "mysecret") %Transaction{ data: %TransactionData{ keys: %Keys{ - secret: "mysecret" + secrets: ["mysecret"] } } } """ - @spec set_secret(Transaction.t(), binary()) :: Transaction.t() - def set_secret(tx = %Transaction{}, secret) when is_binary(secret) do - put_in( + @spec add_secret(Transaction.t(), binary()) :: Transaction.t() + def add_secret(tx = %Transaction{}, secret) when is_binary(secret) do + update_in( tx, - [Access.key(:data), Access.key(:keys), Access.key(:secret)], - decode_binary(secret) + [Access.key(:data), Access.key(:keys), Access.key(:secrets)], + &(&1 ++ [decode_binary(secret)]) ) end diff --git a/lib/archethic/contracts/worker.ex b/lib/archethic/contracts/worker.ex index 190a5b96d..80bc8079d 100644 --- a/lib/archethic/contracts/worker.ex +++ b/lib/archethic/contracts/worker.ex @@ -261,7 +261,6 @@ defmodule ArchEthic.Contracts.Worker do next_tx, prev_tx = %Transaction{ address: address, - data: %TransactionData{keys: %Keys{authorized_keys: authorized_keys, secret: secret}}, previous_public_key: previous_public_key } ) do @@ -273,19 +272,44 @@ defmodule ArchEthic.Contracts.Worker do |> chain_secret() |> chain_authorized_keys() - # TODO: improve transaction decryption and transaction signing to avoid the reveal of the transaction seed - with encrypted_key <- Map.get(authorized_keys, Crypto.storage_nonce_public_key()), - {:ok, aes_key} <- Crypto.ec_decrypt_with_storage_nonce(encrypted_key), - {:ok, transaction_seed} <- Crypto.aes_decrypt(secret, aes_key), - length <- TransactionChain.size(address) do - {:ok, - Transaction.new( - new_type, - new_data, - transaction_seed, - length, - Crypto.get_public_key_curve(previous_public_key) - )} + case get_transaction_seed(prev_tx) do + {:ok, transaction_seed} -> + length = TransactionChain.size(address) + + {:ok, + Transaction.new( + new_type, + new_data, + transaction_seed, + length, + Crypto.get_public_key_curve(previous_public_key) + )} + + _ -> + Logger.error("Cannot decrypt the transaction seed", contract: Base.encode16(address)) + {:error, :transaction_seed_decryption} + end + end + + defp get_transaction_seed(%Transaction{ + data: %TransactionData{keys: %Keys{secrets: secrets, authorized_keys: authorized_keys}} + }) do + storage_nonce_public_key = Crypto.storage_nonce_public_key() + + secret_index = + Enum.find_index(authorized_keys, fn authorized_keys_by_secret -> + authorized_keys_by_secret + |> Map.keys() + |> Enum.any?(&(&1 == storage_nonce_public_key)) + end) + + encrypted_key = authorized_keys |> Enum.at(secret_index) |> Map.get(storage_nonce_public_key) + + secret_for_node = Enum.at(secrets, secret_index) + + with {:ok, aes_key} <- Crypto.ec_decrypt_with_storage_nonce(encrypted_key), + {:ok, transaction_seed} <- Crypto.aes_decrypt(secret_for_node, aes_key) do + {:ok, transaction_seed} end end @@ -328,16 +352,16 @@ defmodule ArchEthic.Contracts.Worker do defp chain_secret( acc = %{ - next_transaction: %Transaction{data: %TransactionData{keys: %Keys{secret: ""}}}, + next_transaction: %Transaction{data: %TransactionData{keys: %Keys{secrets: []}}}, previous_transaction: %Transaction{ - data: %TransactionData{keys: %Keys{secret: previous_secret}} + data: %TransactionData{keys: %Keys{secrets: previous_secrets}} } } ) do put_in( acc, - [:next_transaction, Access.key(:data, %{}), Access.key(:keys, %{}), Access.key(:secret)], - previous_secret + [:next_transaction, Access.key(:data, %{}), Access.key(:keys, %{}), Access.key(:secrets)], + previous_secrets ) end @@ -346,14 +370,13 @@ defmodule ArchEthic.Contracts.Worker do defp chain_authorized_keys( acc = %{ next_transaction: %Transaction{ - data: %TransactionData{keys: %Keys{authorized_keys: authorized_keys}} + data: %TransactionData{keys: %Keys{authorized_keys: []}} }, previous_transaction: %Transaction{ data: %TransactionData{keys: %Keys{authorized_keys: previous_authorized_keys}} } } - ) - when map_size(authorized_keys) == 0 do + ) do put_in( acc, [ diff --git a/lib/archethic/crypto.ex b/lib/archethic/crypto.ex index 378f28f51..804ae8d99 100755 --- a/lib/archethic/crypto.ex +++ b/lib/archethic/crypto.ex @@ -972,7 +972,7 @@ defmodule ArchEthic.Crypto do def load_transaction(%Transaction{ address: address, type: :node_shared_secrets, - data: %TransactionData{keys: keys = %Keys{secret: secret}}, + data: %TransactionData{keys: keys = %Keys{secrets: [secret]}}, validation_stamp: %ValidationStamp{ timestamp: timestamp } @@ -986,7 +986,7 @@ defmodule ArchEthic.Crypto do ) if Keys.authorized_key?(keys, last_node_public_key()) do - encrypted_secret_key = Keys.get_encrypted_key(keys, last_node_public_key()) + encrypted_secret_key = Keys.get_encrypted_key_at(keys, 0, last_node_public_key()) daily_nonce_date = SharedSecrets.next_application_date(timestamp) diff --git a/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex b/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex index 0ee22ea09..691ee5813 100644 --- a/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex +++ b/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex @@ -141,11 +141,11 @@ defmodule ArchEthic.Crypto.SharedSecretsKeystore.SoftwareImpl do %Transaction{ address: address, - data: %TransactionData{keys: keys = %Keys{secret: secret}}, + data: %TransactionData{keys: keys = %Keys{secrets: [secret]}}, validation_stamp: %ValidationStamp{timestamp: timestamp} } -> if Keys.authorized_key?(keys, Crypto.last_node_public_key()) do - encrypted_secret_key = Keys.get_encrypted_key(keys, Crypto.last_node_public_key()) + encrypted_secret_key = Keys.get_encrypted_key_at(keys, 0, Crypto.last_node_public_key()) daily_nonce_date = SharedSecrets.next_application_date(timestamp) diff --git a/lib/archethic/election.ex b/lib/archethic/election.ex index 88386b33d..08695ad44 100755 --- a/lib/archethic/election.ex +++ b/lib/archethic/election.ex @@ -106,9 +106,9 @@ defmodule ArchEthic.Election do ...> ) [ %Node{last_public_key: "node6", geo_patch: "ECA"}, - %Node{last_public_key: "node5", geo_patch: "F10"}, - %Node{last_public_key: "node1", geo_patch: "AAA"}, %Node{last_public_key: "node2", geo_patch: "DEF"}, + %Node{last_public_key: "node3", geo_patch: "AA0"}, + %Node{last_public_key: "node5", geo_patch: "F10"}, ] """ @spec validation_nodes( diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 7c1316b34..8fda641d5 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -73,7 +73,7 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do }) do case Contracts.parse(code) do {:ok, %Contract{triggers: [_ | _]}} -> - if Crypto.storage_nonce_public_key() in Keys.list_authorized_keys(keys) do + if Keys.authorized_key?(keys, Crypto.storage_nonce_public_key()) do :ok else {:error, "Requires storage nonce public key as authorized keys"} @@ -133,7 +133,7 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do type: :node_shared_secrets, data: %TransactionData{ content: content, - keys: %Keys{secret: secret, authorized_keys: authorized_keys} + keys: %Keys{secrets: [secret], authorized_keys: [authorized_keys]} } }) when is_binary(secret) and byte_size(secret) > 0 and map_size(authorized_keys) > 0 do diff --git a/lib/archethic/mining/proof_of_work.ex b/lib/archethic/mining/proof_of_work.ex index ea6590488..cdcbb12b3 100644 --- a/lib/archethic/mining/proof_of_work.ex +++ b/lib/archethic/mining/proof_of_work.ex @@ -52,10 +52,10 @@ defmodule ArchEthic.Mining.ProofOfWork do ...> 71, 189, 178, 226, 124, 57, 18, 0, 115, 106, 182, 71, 149, 191, 76, 168, ...> 248, 14, 164>>, ...> data: %TransactionData{}, - ...> origin_signature: <<42, 187, 53, 200, 255, 6, 178, 201, 76, 252, 238, 154, 159, 160, 242, 99, 178, - ...> 200, 132, 133, 249, 221, 89, 138, 14, 147, 232, 43, 203, 56, 242, 89, 201, - ...> 237, 104, 188, 85, 16, 69, 142, 156, 23, 238, 183, 120, 25, 123, 144, 127, - ...> 199, 125, 205, 98, 133, 96, 78, 141, 134, 149, 216, 69, 70, 146, 14>>, + ...> origin_signature: <<196, 90, 51, 177, 4, 254, 153, 69, 234, 109, 249, 83, 185, 77, 130, 174, 53, + ...> 210, 46, 101, 70, 248, 234, 37, 245, 50, 46, 84, 148, 171, 166, 189, 115, 70, + ...> 183, 73, 66, 234, 101, 8, 109, 4, 125, 168, 170, 112, 13, 140, 93, 202, 251, + ...> 131, 151, 164, 55, 100, 57, 192, 37, 154, 242, 14, 226, 12>>, ...> previous_public_key: <<0, 0, 110, 226, 20, 197, 55, 224, 165, 95, 201, 111, 210, ...> 50, 138, 25, 142, 130, 140, 51, 143, 208, 228, 230, 150, 84, 161, 157, 32, ...> 42, 55, 118, 226, 12>>, diff --git a/lib/archethic/p2p/mem_table_loader.ex b/lib/archethic/p2p/mem_table_loader.ex index d0df545de..56a6ef7c1 100644 --- a/lib/archethic/p2p/mem_table_loader.ex +++ b/lib/archethic/p2p/mem_table_loader.ex @@ -111,15 +111,14 @@ defmodule ArchEthic.P2P.MemTableLoader do transaction_type: :node_shared_secrets ) - new_authorized_keys = Keys.list_authorized_keys(keys) + new_authorized_keys = Keys.list_authorized_public_keys_at(keys, 0) previous_authorized_keys = P2P.authorized_nodes() |> Enum.map(& &1.last_public_key) unauthorized_keys = previous_authorized_keys -- new_authorized_keys Enum.each(unauthorized_keys, &MemTable.unauthorize_node/1) - keys - |> Keys.list_authorized_keys() + new_authorized_keys |> Enum.map(&MemTable.get_first_node_key/1) |> Enum.each(&MemTable.authorize_node(&1, SharedSecrets.next_application_date(timestamp))) end diff --git a/lib/archethic/shared_secrets/node_renewal.ex b/lib/archethic/shared_secrets/node_renewal.ex index ffed62558..03897e0f3 100644 --- a/lib/archethic/shared_secrets/node_renewal.ex +++ b/lib/archethic/shared_secrets/node_renewal.ex @@ -101,11 +101,11 @@ defmodule ArchEthic.SharedSecrets.NodeRenewal do # We discard the content, authorized_key and secret verification content: true, authorized_keys: true, - secret: true + secrets: true ] """, content: <>, - keys: Keys.new(authorized_node_public_keys, secret_key, secret) + keys: Keys.add_secret(%Keys{}, secret, secret_key, authorized_node_public_keys) } ) end diff --git a/lib/archethic/transaction_chain.ex b/lib/archethic/transaction_chain.ex index 919647708..15567fb92 100644 --- a/lib/archethic/transaction_chain.ex +++ b/lib/archethic/transaction_chain.ex @@ -249,9 +249,9 @@ defmodule ArchEthic.TransactionChain do ...> 232, 135, 42, 112, 58, 181, 13>> ...> } ...> ] - ...> |> TransactionChain.proof_of_integrity() + ...> |> TransactionChain.proof_of_integrity() # Hash of the transaction - <<0, 168, 253, 68, 23, 3, 170, 98, 176, 44, 251, 175, 84, 56, 161, 147, 29, 88, 164, 4, 252, 142, 121, 184, 231, 12, 120, 218, 254, 0, 22, 4, 248>> + <<0, 129, 254, 255, 206, 75, 42, 96, 95, 51, 71, 50, 198, 115, 23, 145, 238, 155, 179, 33, 213, 110, 160, 140, 187, 135, 255, 156, 43, 52, 189, 254, 78>> With multiple transactions @@ -294,13 +294,15 @@ defmodule ArchEthic.TransactionChain do ...> 161, 155, 143, 43, 50, 6, 7, 97, 130, 134, 174, 7, 235, 183, 88, 165, 197, 25, 219, 84, ...> 232, 135, 42, 112, 58, 181, 13>>, ...> validation_stamp: %ValidationStamp{ - ...> proof_of_integrity: <<0, 168, 253, 68, 23, 3, 170, 98, 176, 44, 251, 175, 84, 56, 161, 147, 29, 88, 164, 4, 252, 142, 121, 184, 231, 12, 120, 218, 254, 0, 22, 4, 248>> + ...> proof_of_integrity: <<0, 129, 254, 255, 206, 75, 42, 96, 95, 51, 71, 50, 198, 115, 23, 145, 238, + ...> 155, 179, 33, 213, 110, 160, 140, 187, 135, 255, 156, 43, 52, 189, 254, 78>> ...> } ...> } ...> ] - ...> |> TransactionChain.proof_of_integrity() + ...> |> TransactionChain.proof_of_integrity() # Hash of the transaction + previous proof of integrity - <<0, 104, 137, 45, 231, 228, 218, 226, 222, 107, 70, 82, 114, 175, 125, 175, 67, 255, 70, 164, 89, 145, 130, 219, 69, 145, 88, 245, 160, 243, 195, 127, 70>> + <<0, 191, 70, 89, 151, 55, 18, 143, 44, 255, 246, 97, 86, 1, 102, 246, 48, 210, + 26, 207, 228, 116, 102, 47, 32, 16, 225, 45, 26, 53, 154, 123, 106>> """ @spec proof_of_integrity(nonempty_list(Transaction.t())) :: binary() def proof_of_integrity([ @@ -347,7 +349,8 @@ defmodule ArchEthic.TransactionChain do ...> 232, 135, 42, 112, 58, 181, 13>>, ...> validation_stamp: %ValidationStamp{ ...> timestamp: ~U[2020-03-30 12:06:30.000Z], - ...> proof_of_integrity: <<0, 104, 137, 45, 231, 228, 218, 226, 222, 107, 70, 82, 114, 175, 125, 175, 67, 255, 70, 164, 89, 145, 130, 219, 69, 145, 88, 245, 160, 243, 195, 127, 70>> + ...> proof_of_integrity: <<0, 191, 70, 89, 151, 55, 18, 143, 44, 255, 246, 97, 86, 1, 102, 246, 48, 210, + ...> 26, 207, 228, 116, 102, 47, 32, 16, 225, 45, 26, 53, 154, 123, 106>> ...> } ...> }, ...> %Transaction{ @@ -369,7 +372,8 @@ defmodule ArchEthic.TransactionChain do ...> 232, 135, 42, 112, 58, 181, 13>>, ...> validation_stamp: %ValidationStamp{ ...> timestamp: ~U[2020-03-30 10:06:30.000Z], - ...> proof_of_integrity: <<0, 168, 253, 68, 23, 3, 170, 98, 176, 44, 251, 175, 84, 56, 161, 147, 29, 88, 164, 4, 252, 142, 121, 184, 231, 12, 120, 218, 254, 0, 22, 4, 248>> + ...> proof_of_integrity: <<0, 129, 254, 255, 206, 75, 42, 96, 95, 51, 71, 50, 198, 115, 23, 145, 238, + ...> 155, 179, 33, 213, 110, 160, 140, 187, 135, 255, 156, 43, 52, 189, 254, 78>> ...> } ...> } ...> ] diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index c964f8410..5703b23f3 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -527,9 +527,7 @@ defmodule ArchEthic.TransactionChain.Transaction do 0, 0, 0, 0, # Content size 0, 0, 0, 0, - # Secret size - 0, 0, 0, 0, - # Nb authorized keys + # Nb secrets, 0, # Nb UCO transfers 0, @@ -679,7 +677,7 @@ defmodule ArchEthic.TransactionChain.Transaction do iex> <<0, 0, 0, 1, 0, 62, 198, 74, 197, 246, 83, 6, 174, 95, 223, 107, 92, 12, 36, 93, 197, 197, ...> 196, 186, 34, 34, 134, 184, 95, 181, 113, 255, 93, 134, 197, 243, 85, 253, - ...> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 250, 128, 151, 100, 231, 128, 158, 139, + ...> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 250, 128, 151, 100, 231, 128, 158, 139, ...> 88, 128, 68, 236, 240, 238, 116, 186, 164, 87, 3, 60, 198, 21, 248, 64, 207, 58, 221, 192, ...> 131, 180, 213, 64, 65, 66, 248, 246, 119, 69, 36, 103, 249, 201, 252, 154, 69, 24, 48, 18, 63, ...> 65, 5, 10, 248, 37, 245, 101, 19, 118, 235, 82, 161, 165, 62, 43, 249, 237, diff --git a/lib/archethic/transaction_chain/transaction/data.ex b/lib/archethic/transaction_chain/transaction/data.ex index 189992dac..3d37cfa14 100755 --- a/lib/archethic/transaction_chain/transaction/data.ex +++ b/lib/archethic/transaction_chain/transaction/data.ex @@ -34,8 +34,8 @@ defmodule ArchEthic.TransactionChain.TransactionData do ...> code: "actions do new_transaction(:transfer) |> add_uco_transfer(to: 892B5257A038BBB14F0DD8734FA09A50F4F55E8856B72F96F2A6014EEB8A2EAB72, amount: 10.5) end", ...> content: "Lorem ipsum dolor sit amet, consectetur adipiscing eli", ...> keys: %Keys{ - ...> secret: <<225, 11, 213, 74, 41, 54, 189, 139, 179, 79>>, - ...> authorized_keys: %{} + ...> secrets: [<<225, 11, 213, 74, 41, 54, 189, 139, 179, 79>>], + ...> authorized_keys: [%{}] ...> }, ...> ledger: %Ledger{}, ...> recipients: [ @@ -53,6 +53,8 @@ defmodule ArchEthic.TransactionChain.TransactionData do 0, 0, 0, 54, # Content "Lorem ipsum dolor sit amet, consectetur adipiscing eli", + # Nb secrets, + 1, # Secret size 0, 0, 0, 10, # Secret @@ -92,9 +94,16 @@ defmodule ArchEthic.TransactionChain.TransactionData do ...> "actions do new_transaction(:transfer) |> add_uco_transfer(to: 892B5257A038BBB14F0DD8734FA09A50F4F55E8856B72F96F2A6014EEB8A2EAB72, amount: 10.5) end", ...> 0, 0, 0, 54, ...> "Lorem ipsum dolor sit amet, consectetur adipiscing eli", - ...> 0, 0, 0, 10, + ...> 1, 0, 0, 0, 10, ...> 225, 11, 213, 74, 41, 54, 189, 139, 179, 79, - ...> 0, + ...> 1, + ...> 0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, + ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9, + ...> 139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, + ...> 177, 45, 9, 93, 107, 90, 254, 173, 71, 60, 181, 113, 247, 75, 151, 127, 41, 7, + ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, + ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, + ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, ...> 0, ...> 0, ...> 1, @@ -107,8 +116,16 @@ defmodule ArchEthic.TransactionChain.TransactionData do code: "actions do new_transaction(:transfer) |> add_uco_transfer(to: 892B5257A038BBB14F0DD8734FA09A50F4F55E8856B72F96F2A6014EEB8A2EAB72, amount: 10.5) end", content: "Lorem ipsum dolor sit amet, consectetur adipiscing eli", keys: %Keys{ - secret: <<225, 11, 213, 74, 41, 54, 189, 139, 179, 79>>, - authorized_keys: %{} + secrets: [<<225, 11, 213, 74, 41, 54, 189, 139, 179, 79>>], + authorized_keys: [%{ + <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, + 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => + <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, + 177, 45, 9, 93, 107, 90, 254, 173, 71, 60, 181, 113, 247, 75, 151, 127, 41, 7, + 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, + 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, + 224, 214, 225, 146, 44, 83, 111, 34, 239, 99>> + }] }, ledger: %Ledger{ uco: %UCOLedger{} diff --git a/lib/archethic/transaction_chain/transaction/data/keys.ex b/lib/archethic/transaction_chain/transaction/data/keys.ex index e285f7a53..9c42fcc2e 100644 --- a/lib/archethic/transaction_chain/transaction/data/keys.ex +++ b/lib/archethic/transaction_chain/transaction/data/keys.ex @@ -1,39 +1,50 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do @moduledoc """ - Represents section in the transaction data to store secret and authorized keys to - read the encrypted secret. + Represents section in the transaction data to store secrets and authorized keys to + read the encrypted secrets. """ - defstruct authorized_keys: %{}, secret: "" + defstruct authorized_keys: [], secrets: [] alias ArchEthic.Crypto @type t :: %__MODULE__{ - secret: binary(), - authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key :: binary()} + secrets: list(binary()), + authorized_keys: list(%{(public_key :: Crypto.key()) => encrypted_key :: binary()}) } @doc """ - Create a new transaction data keys + Add a secret with its authorized keys ## Examples iex> secret_key = :crypto.strong_rand_bytes(32) iex> secret = "important message" iex> {pub, _pv} = Crypto.generate_deterministic_keypair("seed") - iex> %{ authorized_keys: authorized_keys, secret: _} = Keys.new([pub], secret_key, secret) + iex> %Keys{authorized_keys: [authorized_keys] } = Keys.add_secret(%Keys{}, secret, secret_key, [pub]) iex> Map.keys(authorized_keys) [ - <<0, 0, 241, 101, 225, 229, 247, 194, 144, 229, 47, 46, 222, 243, 251, 171, 96, - 203, 174, 116, 191, 211, 39, 79, 142, 94, 225, 222, 51, 69, 201, 84, 161, - 102>> + <<0, 0, 241, 101, 225, 229, 247, 194, 144, 229, 47, 46, 222, 243, 251, 171, 96, 203, 174, 116, 191, 211, + 39, 79, 142, 94, 225, 222, 51, 69, 201, 84, 161,102>> ] """ - @spec new(list(Crypto.key()), secret_key :: binary(), secret :: binary()) :: t() - def new(authorized_public_keys, secret_key, secret) - when is_list(authorized_public_keys) and is_binary(secret_key) do - Enum.reduce(authorized_public_keys, %__MODULE__{secret: secret}, fn public_key, acc = %{} -> - encrypted_secret_key = Crypto.ec_encrypt(secret_key, public_key) - Map.update!(acc, :authorized_keys, &Map.put(&1, public_key, encrypted_secret_key)) + @spec add_secret( + t(), + secret :: binary(), + secret_key :: binary(), + authorized_public_keys :: list(Crypto.key()) + ) :: t() + def add_secret(keys = %__MODULE__{}, secret, secret_key, authorized_public_keys) + when is_binary(secret) and is_binary(secret_key) and is_list(authorized_public_keys) do + keys + |> Map.update!(:secrets, &(&1 ++ [secret])) + |> Map.update!(:authorized_keys, fn encrypted_keys -> + new_encrypted_keys = + Enum.map(authorized_public_keys, fn public_key -> + {public_key, Crypto.ec_encrypt(secret_key, public_key)} + end) + |> Enum.into(%{}) + + encrypted_keys ++ [new_encrypted_keys] end) end @@ -43,8 +54,8 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ## Examples iex> %Keys{ - ...> secret: <<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>, - ...> authorized_keys: %{ + ...> secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + ...> authorized_keys: [%{ ...> <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => ...> <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -52,10 +63,12 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99>> - ...> } + ...> }] ...> } ...> |> Keys.serialize() << + # Nb of secrets + 1, # Secret size 0, 0, 0, 16, # Secret @@ -73,14 +86,29 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do 224, 214, 225, 146, 44, 83, 111, 34, 239, 99 >> """ - def serialize(%__MODULE__{secret: secret, authorized_keys: authorized_keys}) do + def serialize(%__MODULE__{secrets: []}), do: <<0>> + + def serialize(%__MODULE__{secrets: secrets, authorized_keys: authorized_keys}) + when length(secrets) == length(authorized_keys) do + secrets_bin = + Enum.map(secrets, fn secret -> + <> + end) + |> :erlang.list_to_binary() + authorized_keys_bin = - Enum.reduce(authorized_keys, <<>>, fn {public_key, encrypted_key}, acc -> - <> <> acc + Enum.map(authorized_keys, fn keys_by_secret -> + authorized_keys_bin = + Enum.map(keys_by_secret, fn {public_key, encrypted_key} -> + <> + end) + |> :erlang.list_to_binary() + + <> end) + |> :erlang.list_to_binary() - nb_authorized_keys = authorized_keys |> Map.keys() |> length - <> + <> end @doc """ @@ -88,7 +116,7 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ## Examples - iex> <<0, 0, 0, 16, 205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, + iex> <<1, 0, 0, 0, 16, 205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, ...> 198, 202, 1, 0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, ...> 125, 76, 29, 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, ...> 187, 9, 139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -99,8 +127,8 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ...> |> Keys.deserialize() { %Keys{ - secret: <<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>, - authorized_keys: %{ + secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + authorized_keys: [%{ <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -108,75 +136,94 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, 224, 214, 225, 146, 44, 83, 111, 34, 239, 99>> - } + }] }, <<>> } """ @spec deserialize(bitstring()) :: {t(), bitstring} - def deserialize(<>) do - { - %__MODULE__{ - secret: secret - }, - rest - } - end - - def deserialize( - <> - ) do - {authorized_keys, rest} = reduce_authorized_keys_bin(rest, nb_authorized_keys, %{}) + def deserialize(<>) do + {secrets, rest} = reduce_secrets_bin(rest, nb_secrets, []) + {authorized_keys, rest} = reduce_authorized_keys_bin(rest, nb_secrets, []) {%__MODULE__{ - secret: secret, + secrets: secrets, authorized_keys: authorized_keys }, rest} end - defp reduce_authorized_keys_bin(rest, nb_authorized_keys, acc) do - if length(Map.keys(acc)) == nb_authorized_keys do - {acc, rest} - else - {acc, rest} = do_reduce_authorized_keys_bin(rest, acc) - reduce_authorized_keys_bin(rest, nb_authorized_keys, acc) - end + defp reduce_secrets_bin(rest, 0, _), do: {[], rest} + + defp reduce_secrets_bin(rest, nb_secrets, acc) when length(acc) == nb_secrets, + do: {Enum.reverse(acc), rest} + + defp reduce_secrets_bin( + <>, + nb_secrets, + acc + ) do + reduce_secrets_bin(rest, nb_secrets, [secret | acc]) + end + + defp reduce_authorized_keys_bin(rest, 0, _acc), do: {[], rest} + + defp reduce_authorized_keys_bin(rest, nb_secrets, acc) when length(acc) == nb_secrets, + do: {Enum.reverse(acc), rest} + + defp reduce_authorized_keys_bin(<>, nb_secrets, acc) do + {authorized_keys, rest} = do_reduce_authorized_keys_bin(rest, nb_authorized_keys, %{}) + reduce_authorized_keys_bin(rest, nb_secrets, [authorized_keys | acc]) end + defp do_reduce_authorized_keys_bin(rest, 0, _), do: {[], rest} + + defp do_reduce_authorized_keys_bin(rest, nb_authorized_keys, acc) + when map_size(acc) == nb_authorized_keys, + do: {acc, rest} + defp do_reduce_authorized_keys_bin( <<0::8, origin_id::8, key::binary-32, encrypted_key::binary-size(80), rest::bitstring>>, + nb_authorized_keys, acc ) do - {Map.put(acc, <<0::8, origin_id::8, key::binary>>, encrypted_key), rest} + do_reduce_authorized_keys_bin( + rest, + nb_authorized_keys, + Map.put(acc, <<0::8, origin_id::8, key::binary>>, encrypted_key) + ) end defp do_reduce_authorized_keys_bin( <>, + nb_authorized_keys, acc ) when curve_id in [1, 2] do - {Map.put(acc, <>, encrypted_key), rest} + do_reduce_authorized_keys_bin( + rest, + nb_authorized_keys, + Map.put(acc, <>, encrypted_key) + ) end @spec from_map(map()) :: t() def from_map(keys = %{}) do %__MODULE__{ - secret: Map.get(keys, :secret, ""), - authorized_keys: Map.get(keys, :authorized_keys, %{}) + secrets: Map.get(keys, :secrets, []), + authorized_keys: Map.get(keys, :authorized_keys, []) } end @spec to_map(t() | nil) :: map() def to_map(nil) do - %{secret: "", authorized_keys: %{}} + %{secrets: [], authorized_keys: []} end def to_map(keys = %__MODULE__{}) do %{ - secret: Map.get(keys, :secret, ""), - authorized_keys: Map.get(keys, :authorized_keys, %{}) + secrets: Map.get(keys, :secrets, []), + authorized_keys: Map.get(keys, :authorized_keys, []) } end @@ -186,8 +233,8 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ## Examples iex> %Keys{ - ...> secret: <<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>, - ...> authorized_keys: %{ + ...> secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + ...> authorized_keys: [%{ ...> <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => ...> <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -195,25 +242,28 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, 1, 126, 241, 246>> - ...> } + ...> }] ...> } ...> |> Keys.authorized_key?(<<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>>) true """ @spec authorized_key?(t(), Crypto.key()) :: boolean() - def authorized_key?(%__MODULE__{authorized_keys: auth_keys}, node_public_key) do - Map.has_key?(auth_keys, node_public_key) + def authorized_key?(%__MODULE__{authorized_keys: auth_keys}, node_public_key) + when is_binary(node_public_key) do + Enum.any?(auth_keys, fn auth_keys_by_secret -> + Map.has_key?(auth_keys_by_secret, node_public_key) + end) end @doc """ - List the authorized public keys able to decrypt the cipher + List the authorized public keys for a given secret index ## Examples iex> %Keys{ - ...> secret: <<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>, - ...> authorized_keys: %{ + ...> secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + ...> authorized_keys: [%{ ...> <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => ...> <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -221,16 +271,49 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, 1, 126, 241, 246>> - ...> } + ...> }] ...> } - ...> |> Keys.list_authorized_keys() + ...> |> Keys.list_authorized_public_keys_at(0) [ <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> ] """ - @spec list_authorized_keys(t()) :: list(Crypto.key()) - def list_authorized_keys(%__MODULE__{authorized_keys: auth_keys}), do: Map.keys(auth_keys) + @spec list_authorized_public_keys_at(t(), non_neg_integer()) :: list(Crypto.key()) + def list_authorized_public_keys_at(%__MODULE__{authorized_keys: auth_keys}, secret_index) + when is_integer(secret_index) and secret_index >= 0 do + auth_keys + |> Enum.at(secret_index) + |> Map.keys() + end + + @doc """ + List all the authorized keys + + ## Examples + + iex> %Keys{ + ...> secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + ...> authorized_keys: [%{ + ...> <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, + ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => + ...> <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, + ...> 177, 45, 9, 93, 107, 90, 254, 173, 71, 60, 181, 113, 247, 75, 151, 127, 41, 7, + ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, + ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, + ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, 1, 126, 241, 246>> + ...> }] + ...> } + ...> |> Keys.list_authorized_public_keys() + [ + <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, + 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> + ] + """ + @spec list_authorized_public_keys(t()) :: list(Crypto.key()) + def list_authorized_public_keys(%__MODULE__{authorized_keys: auth_keys}) do + Enum.flat_map(auth_keys, &Map.keys(&1)) + end @doc """ Get the encrypted key from the authorized public key to decrypt the secret @@ -238,8 +321,8 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ## Examples iex> %Keys{ - ...> secret: <<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>, - ...> authorized_keys: %{ + ...> secrets: [<<205, 124, 251, 211, 28, 69, 249, 1, 58, 108, 16, 35, 23, 206, 198, 202>>], + ...> authorized_keys: [%{ ...> <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>> => ...> <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, @@ -247,9 +330,9 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do ...> 233, 227, 98, 209, 211, 97, 117, 68, 101, 59, 121, 214, 105, 225, 218, 91, 92, ...> 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, ...> 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, 1, 126, 241, 246>> - ...> } + ...> }] ...> } - ...> |> Keys.get_encrypted_key(<<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, + ...> |> Keys.get_encrypted_key_at(0, <<0, 0, 229, 188, 159, 80, 100, 5, 54, 152, 137, 201, 204, 24, 22, 125, 76, 29, ...> 83, 14, 154, 60, 66, 69, 121, 97, 40, 215, 226, 204, 133, 54, 187, 9>>) <<139, 100, 20, 32, 187, 77, 56, 30, 116, 207, 34, 95, 157, 128, 208, 115, 113, 177, 45, 9, 93, 107, 90, 254, 173, 71, 60, 181, 113, 247, 75, 151, 127, 41, 7, @@ -257,8 +340,15 @@ defmodule ArchEthic.TransactionChain.TransactionData.Keys do 212, 162, 48, 18, 15, 181, 70, 103, 32, 141, 4, 64, 107, 93, 117, 188, 244, 7, 224, 214, 225, 146, 44, 83, 111, 34, 239, 99, 1, 126, 241, 246>> """ - @spec get_encrypted_key(t(), Crypto.key()) :: binary() - def get_encrypted_key(%__MODULE__{authorized_keys: auth_keys}, public_key) do - Map.get(auth_keys, public_key) + @spec get_encrypted_key_at( + t(), + secret_index :: non_neg_integer(), + authorized_public_key :: Crypto.key() + ) :: binary() + def get_encrypted_key_at(%__MODULE__{authorized_keys: auth_keys}, secret_index, public_key) + when is_integer(secret_index) and secret_index >= 0 and is_binary(public_key) do + auth_keys + |> Enum.at(secret_index) + |> Map.get(public_key) end end diff --git a/lib/archethic/utils/playbooks/uco.ex b/lib/archethic/utils/playbooks/uco.ex index e26677d61..0f57b904f 100644 --- a/lib/archethic/utils/playbooks/uco.ex +++ b/lib/archethic/utils/playbooks/uco.ex @@ -93,11 +93,11 @@ defmodule ArchEthic.Playbook.UCO do # "timestamp" => System.os_time(:millisecond), # "data" => %{ # "keys" => %{ - # "authorizedKeys" => %{ + # "authorizedKeys" => [%{ # "0038E847194E94769B2E185A5BA8F17F038D3E1E9D32BDEB844B4544753B42BF5F" => # "C1884136D4E29D5037445F6A9BF1432BD69581498C4E43FE25C491C6A3FA9350" - # }, - # "secret" => "D3D01CA5B390928269189ACDE0A6826F98E73484987FD48DB8D4339D4FA00F51" + # }], + # "secrets" => ["D3D01CA5B390928269189ACDE0A6826F98E73484987FD48DB8D4339D4FA00F51"] # }, # "ledger" => %{ # "uco" => %{ diff --git a/lib/archethic_web/controllers/api/transaction_payload.ex b/lib/archethic_web/controllers/api/transaction_payload.ex index ca743e326..741193430 100644 --- a/lib/archethic_web/controllers/api/transaction_payload.ex +++ b/lib/archethic_web/controllers/api/transaction_payload.ex @@ -11,6 +11,7 @@ defmodule ArchEthicWeb.API.TransactionPayload do alias ArchEthicWeb.API.Types.Hash alias ArchEthicWeb.API.Types.Hex alias ArchEthicWeb.API.Types.PublicKey + alias ArchEthicWeb.API.Types.SecretList alias ArchEthicWeb.API.Types.TransactionType embedded_schema do @@ -40,7 +41,7 @@ defmodule ArchEthicWeb.API.TransactionPayload do end embeds_one :keys, Keys do - field(:secret, Hex) + field(:secrets, SecretList) field(:authorizedKeys, AuthorizedKeys) end @@ -82,7 +83,7 @@ defmodule ArchEthicWeb.API.TransactionPayload do defp changeset_keys(changeset, params) do changeset - |> cast(params, [:secret, :authorizedKeys]) + |> cast(params, [:secrets, :authorizedKeys]) end defp changeset_ledger(changeset, params) do diff --git a/lib/archethic_web/controllers/api/types/authorized_keys.ex b/lib/archethic_web/controllers/api/types/authorized_keys.ex index bc8980bf7..ab791c80b 100644 --- a/lib/archethic_web/controllers/api/types/authorized_keys.ex +++ b/lib/archethic_web/controllers/api/types/authorized_keys.ex @@ -5,50 +5,57 @@ defmodule ArchEthicWeb.API.Types.AuthorizedKeys do alias ArchEthic.Crypto - def type, do: :map - - def cast(authorized_keys) when is_map(authorized_keys) do - results = - Enum.map(authorized_keys, fn {public_key, encrypted_key} -> - with {:public_key, {:ok, bin_public_key}} <- - {:public_key, Base.decode16(public_key, case: :mixed)}, - {:public_key, true} <- {:public_key, Crypto.valid_public_key?(bin_public_key)}, - {:encrypted_key, {:ok, bin_encrypted_key}} <- - {:encrypted_key, Base.decode16(encrypted_key, case: :mixed)}, - {:encrypted_key_size, :ok} <- - {:encrypted_key_size, check_encrypted_key_size(bin_public_key, bin_encrypted_key)} do - {bin_public_key, bin_encrypted_key} - else - {:public_key, :error} -> - {:error, "public key must be hexadecimal"} - - {:public_key, false} -> - {:error, "public key is invalid"} - - {:encrypted_key, :error} -> - {:error, "encrypted key must be hexadecimal"} - - {:encrypted_key_size, :error} -> - {:error, "encrypted key size is invalid"} - end - end) + def type, do: :array + + def cast(authorized_keys) when is_list(authorized_keys) do + results = Enum.map(authorized_keys, &do_cast/1) - case Enum.filter(results, &match?({:error, _}, &1)) do + case Enum.flat_map(results, & &1) |> Enum.filter(&match?({:error, _}, &1)) do [] -> - {:ok, Enum.into(results, %{})} + {:ok, results} errors -> {:error, Enum.map(errors, fn {:error, msg} -> {:message, msg} end)} end end - def cast(_), do: {:error, [message: "must be a map"]} + def cast(_), do: {:error, [message: "must be an array"]} + + defp do_cast(keys_by_secrets) do + Enum.map(keys_by_secrets, fn {public_key, encrypted_key} -> + with {:public_key, {:ok, bin_public_key}} <- + {:public_key, Base.decode16(public_key, case: :mixed)}, + {:public_key, true} <- {:public_key, Crypto.valid_public_key?(bin_public_key)}, + {:encrypted_key, {:ok, bin_encrypted_key}} <- + {:encrypted_key, Base.decode16(encrypted_key, case: :mixed)}, + {:encrypted_key_size, :ok} <- + {:encrypted_key_size, check_encrypted_key_size(bin_public_key, bin_encrypted_key)} do + {bin_public_key, bin_encrypted_key} + else + {:public_key, :error} -> + {:error, "public key must be hexadecimal"} + + {:public_key, false} -> + {:error, "public key is invalid"} + + {:encrypted_key, :error} -> + {:error, "encrypted key must be hexadecimal"} + + {:encrypted_key_size, :error} -> + {:error, "encrypted key size is invalid"} + end + end) + |> Enum.into(%{}) + end def load(authorized_keys), do: authorized_keys - def dump(authorized_keys) when is_map(authorized_keys) do - Enum.map(authorized_keys, fn {public_key, encrypted_key} -> - {Base.encode16(public_key), Base.encode16(encrypted_key)} + def dump(authorized_keys) when is_list(authorized_keys) do + Enum.map(authorized_keys, fn authorized_keys_by_secret -> + Enum.map(authorized_keys_by_secret, fn {public_key, encrypted_key} -> + {Base.encode16(public_key), Base.encode16(encrypted_key)} + end) + |> Enum.into(%{}) end) end diff --git a/lib/archethic_web/controllers/api/types/secret_list.ex b/lib/archethic_web/controllers/api/types/secret_list.ex new file mode 100644 index 000000000..98f94c4a7 --- /dev/null +++ b/lib/archethic_web/controllers/api/types/secret_list.ex @@ -0,0 +1,35 @@ +defmodule ArchEthicWeb.API.Types.SecretList do + @moduledoc false + + use Ecto.Type + + def type, do: :array + + def cast(secrets) when is_list(secrets) do + results = + Enum.map(secrets, fn secret -> + case Base.decode16(secret, case: :mixed) do + {:ok, bin_secret} -> + bin_secret + + _ -> + {:error, "must be hexadecimal"} + end + end) + + case Enum.filter(results, &match?({:error, _}, &1)) do + [] -> + {:ok, results} + + errors -> + {:error, Enum.map(errors, fn {:error, msg} -> {:message, msg} end)} + end + end + + def cast(_), do: {:error, [message: "must be an array"]} + + def load(secrets), do: secrets + + def dump(secrets) when is_list(secrets), do: Enum.map(secrets, &Base.encode16/1) + def dump(_), do: :error +end diff --git a/lib/archethic_web/graphql_schema/transaction_type.ex b/lib/archethic_web/graphql_schema/transaction_type.ex index 5f79ad3b6..e344b3151 100644 --- a/lib/archethic_web/graphql_schema/transaction_type.ex +++ b/lib/archethic_web/graphql_schema/transaction_type.ex @@ -85,19 +85,31 @@ defmodule ArchEthicWeb.GraphQLSchema.TransactionType do field(:transfers, list_of(:nft_transfer)) end - @desc "[Keys] represents a block to set secret and authorized public keys able to read the secret" + @desc "[Keys] represents a block to set secrets and authorized public keys able to read the secrets" object :keys do - field(:secret, :hex) - field(:authorized_keys, list_of(:authorized_key)) + field(:secrets, list_of(:hex)) + + field(:authorized_keys, list_of(list_of(:authorized_key))) do + resolve(fn _, %{source: %{authorized_keys: authorized_keys}} -> + formatted_authorized_keys = + Enum.map(authorized_keys, fn authorized_keys_by_secret -> + Enum.map(authorized_keys_by_secret, fn {public_key, encrypted_secret_key} -> + %{public_key: public_key, encrypted_secret_key: encrypted_secret_key} + end) + end) + + {:ok, formatted_authorized_keys} + end) + end end @desc """ - [AuthorizedKey] represents list of public keys with the encrypted secret for this given key. - By decrypting this secret keys, the authorized public keys will be able to decrypt the secret + [AuthorizedKey] represents a authorized public key with the encrypted secret key for this given key. + By decrypting this secret key, the authorized public key will be able to decrypt its related secret """ object :authorized_key do field(:public_key, :hex) - field(:encrypted_key, :hex) + field(:encrypted_secret_key, :hex) end @desc """ diff --git a/lib/archethic_web/templates/explorer/transaction_details.html.leex b/lib/archethic_web/templates/explorer/transaction_details.html.leex index 16e54b30b..3a8af8b40 100644 --- a/lib/archethic_web/templates/explorer/transaction_details.html.leex +++ b/lib/archethic_web/templates/explorer/transaction_details.html.leex @@ -95,20 +95,8 @@ -
  • - + ">Keys
  • "> @@ -203,20 +191,27 @@ <% end %> <% end %> - <%= if @data_section == "secret" do %> -

    Keys secret

    -

    <%= Base.encode16(@transaction.data.keys.secret) %>

    - <% end %> - - <%= if @data_section == "authorized_keys" do %> -

    Secret authorized keys

    - <%= for { key, _enc_key} <- @transaction.data.keys.authorized_keys do %> -
    + <%= if @data_section == "keys" do %> +

    Keys and Secrets

    + <%= for { secret, index } <- Enum.with_index(@transaction.data.keys.secrets) do %> +
    -
    - <%= Base.encode16(key) %> -
    +
    +

    Secret: <%= Base.encode16(secret) %>

    +
    +
    +
    +

    Authorized keys

    +
    +
    + <%= for { key, _enc_key} <- Enum.at(@transaction.data.keys.authorized_keys, index) do %> +
    +
    + <%= Base.encode16(key) %> +
    +
    + <% end %>
    <% end %> <% end %> diff --git a/mix.exs b/mix.exs index f9b9dc9cb..cefe4b9b8 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ArchEthic.MixProject do def project do [ app: :archethic, - version: "0.10.0", + version: "0.11.0", build_path: "_build", config_path: "config/config.exs", deps_path: "deps", diff --git a/priv/migrations/1_create_transaction_data_types.cql b/priv/migrations/1_create_transaction_data_types.cql index dc75aba31..9c6f3430c 100644 --- a/priv/migrations/1_create_transaction_data_types.cql +++ b/priv/migrations/1_create_transaction_data_types.cql @@ -18,8 +18,8 @@ CREATE TYPE IF NOT EXISTS archethic.nft_ledger( ); CREATE TYPE IF NOT EXISTS archethic.pending_transaction_data_keys( - authorized_keys map, - secret blob + authorized_keys LIST>>, + secrets LIST ); CREATE TYPE IF NOT EXISTS archethic.pending_transaction_ledger( diff --git a/priv/static/docs/_coverpage.md b/priv/static/docs/_coverpage.md index 584565897..2f8d8f04d 100644 --- a/priv/static/docs/_coverpage.md +++ b/priv/static/docs/_coverpage.md @@ -1,4 +1,4 @@ -# ArchEthic Network 0.10 +# ArchEthic Network 0.11 > A truly decentralized and unlimited P2P network diff --git a/priv/static/docs/network/transaction_chain/README.md b/priv/static/docs/network/transaction_chain/README.md index 04af4ce7d..bf295daca 100644 --- a/priv/static/docs/network/transaction_chain/README.md +++ b/priv/static/docs/network/transaction_chain/README.md @@ -57,8 +57,8 @@ Its structure is described as below: - NFT: for non financial transactions (intended for P2P uses - as tokens, loyalties, etc.) - Stock: to manage inventory of items (Will be available soon) - Keys: Define some cryptographic delegations - - Authorized keys: list of authorized keys to be able to decrypt secret - - Secret: Encrypt content which can be decrypted by the authorized key + - Authorized keys: list of authorized keys to be able to decrypt secrets + - Secrets: Encrypted contents which can be decrypted by the authorized keys - Recipients: Additional recipients to target smart contracts - Previous public key: Corresponds to the public key associated to the previous transaction - Previous signature: Corresponds to the signature of the private key associated with the mentioned previous public key diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index 5da181849..2e24c5e05 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -227,7 +227,7 @@ defmodule ArchEthic.Bootstrap.SyncTest do [ %Transaction{ type: :node_shared_secrets, - data: %TransactionData{keys: %Keys{authorized_keys: keys, secret: secret}} + data: %TransactionData{keys: %Keys{authorized_keys: [keys], secrets: [secret]}} } ] -> encrypted_key = Map.get(keys, Crypto.last_node_public_key()) diff --git a/test/archethic/bootstrap_test.exs b/test/archethic/bootstrap_test.exs index 50a0d9e29..768dd280a 100644 --- a/test/archethic/bootstrap_test.exs +++ b/test/archethic/bootstrap_test.exs @@ -99,7 +99,7 @@ defmodule ArchEthic.BootstrapTest do [ %Transaction{ type: :node_shared_secrets, - data: %TransactionData{keys: %Keys{authorized_keys: keys, secret: secret}} + data: %TransactionData{keys: %Keys{authorized_keys: [keys], secrets: [secret]}} } ] -> encrypted_key = Map.get(keys, Crypto.last_node_public_key()) diff --git a/test/archethic/contracts/interpreter_test.exs b/test/archethic/contracts/interpreter_test.exs index de0e32639..45217be4a 100644 --- a/test/archethic/contracts/interpreter_test.exs +++ b/test/archethic/contracts/interpreter_test.exs @@ -199,7 +199,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do alias: ArchEthic.Contracts.Interpreter.TransactionStatements ], [:TransactionStatements]}, - :set_secret + :add_secret ]}, [line: 6], [{:&, [line: 6], [1]}, "MyEncryptedSecret"]} ] } @@ -243,7 +243,8 @@ defmodule ArchEthic.Contracts.InterpreterTest do {"encrypted_secret_key", <<71, 68, 42, 253, 51, 143, 131, 189, 220, 222, 156, 242, 174, 221, 105, 176, 33, 62, 127, 149, 110, 32, - 39, 105, 226, 144, 240, 226, 105, 94, 147, 81>>} + 39, 105, 226, 144, 240, 226, 105, 94, 147, 81>>}, + {"secret_index", 0} ] ] } @@ -303,8 +304,8 @@ defmodule ArchEthic.Contracts.InterpreterTest do add_uco_transfer to: \"7F6661ACE282F947ACA2EF947D01BDDC90C65F09EE828BDADE2E3ED4258470B3\", amount: 10.04 add_nft_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 200.0, nft: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\" set_content \"Receipt\" - set_secret \"MyEncryptedSecret\" - add_authorized_key public_key: "70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A", encrypted_secret_key: \"47442AFD338F83BDDCDE9CF2AEDD69B0213E7F956E202769E290F0E2695E9351\" + add_secret \"MyEncryptedSecret\" + add_authorized_key public_key: "70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A", encrypted_secret_key: \"47442AFD338F83BDDCDE9CF2AEDD69B0213E7F956E202769E290F0E2695E9351\", secret_index: 0 add_recipient \"78273C5CBCEB8617F54380CC2F173DF2404DB676C9F10D546B6F395E6F3BDDEE\" end """ @@ -426,7 +427,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do test "should flatten comparison operators" do code = """ condition inherit: [ - secret: size() >= 10 + content: size() >= 10 ] """ @@ -434,7 +435,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do %Contract{ conditions: %{ inherit: %Conditions{ - secret: + content: {:>=, [line: 2], [ {{:., [line: 2], @@ -444,7 +445,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do :size ]}, [line: 2], [ - {:get_in, [line: 2], [{:scope, [line: 2], nil}, ["next", "secret"]]} + {:get_in, [line: 2], [{:scope, [line: 2], nil}, ["next", "content"]]} ]}, 10 ]} diff --git a/test/archethic/contracts/worker_test.exs b/test/archethic/contracts/worker_test.exs index 79ebcd52b..ff28e67a0 100644 --- a/test/archethic/contracts/worker_test.exs +++ b/test/archethic/contracts/worker_test.exs @@ -58,11 +58,13 @@ defmodule ArchEthic.Contracts.WorkerTest do constants = %{ "address" => "@SC1", - "authorized_keys" => %{ - Crypto.storage_nonce_public_key() => - Crypto.ec_encrypt(aes_key, Crypto.storage_nonce_public_key()) - }, - "secret" => secret, + "authorized_keys" => [ + %{ + Crypto.storage_nonce_public_key() => + Crypto.ec_encrypt(aes_key, Crypto.storage_nonce_public_key()) + } + ], + "secrets" => [secret], "content" => "", "uco_transferred" => 0.0, "nft_transferred" => 0.0, diff --git a/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs b/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs index 469e4f2de..f4a133266 100644 --- a/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs +++ b/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs @@ -34,7 +34,7 @@ defmodule ArchEthic.Crypto.SharedSecrets.SoftwareImplTest do [ %Transaction{ data: %TransactionData{ - keys: Keys.new([Crypto.last_node_public_key()], aes_key, secrets) + keys: Keys.add_secret(%Keys{}, secrets, aes_key, [Crypto.last_node_public_key()]) }, validation_stamp: %ValidationStamp{ timestamp: timestamp diff --git a/test/archethic/crypto_test.exs b/test/archethic/crypto_test.exs index a558f806b..b4455b22a 100644 --- a/test/archethic/crypto_test.exs +++ b/test/archethic/crypto_test.exs @@ -101,7 +101,7 @@ defmodule CryptoTest do tx = %Transaction{ address: "@NodeSharedSecrets1", type: :node_shared_secrets, - data: %TransactionData{}, + data: %TransactionData{keys: %Keys{secrets: [:crypto.strong_rand_bytes(32)]}}, validation_stamp: %ValidationStamp{timestamp: DateTime.utc_now()} } @@ -133,7 +133,7 @@ defmodule CryptoTest do secret = <> - tx_keys = Keys.new([Crypto.last_node_public_key()], secret_key, secret) + tx_keys = Keys.add_secret(%Keys{}, secret, secret_key, [Crypto.last_node_public_key()]) MockDB |> expect(:chain_size, fn _ -> 0 end) diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index a86b070a8..3f8d1972a 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -80,11 +80,13 @@ defmodule ArchEthic.Mining.PendingTransactionValidationTest do ] """, keys: %Keys{ - secret: :crypto.strong_rand_bytes(32), - authorized_keys: %{ - "node_key1" => "", - "node_key2" => "" - } + secrets: [:crypto.strong_rand_bytes(32)], + authorized_keys: [ + %{ + "node_key1" => "", + "node_key2" => "" + } + ] } } ) @@ -181,11 +183,9 @@ defmodule ArchEthic.Mining.PendingTransactionValidationTest do end """, keys: - Keys.new( - [Crypto.storage_nonce_public_key()], - :crypto.strong_rand_bytes(32), - tx_seed - ) + Keys.add_secret(%Keys{}, tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) }, tx_seed, 0 diff --git a/test/archethic/p2p/mem_table_loader_test.exs b/test/archethic/p2p/mem_table_loader_test.exs index 4d2491c8c..cebf3c4ca 100644 --- a/test/archethic/p2p/mem_table_loader_test.exs +++ b/test/archethic/p2p/mem_table_loader_test.exs @@ -90,9 +90,11 @@ defmodule ArchEthic.P2P.MemTableLoaderTest do type: :node_shared_secrets, data: %TransactionData{ keys: %Keys{ - authorized_keys: %{ - @node_1_public_key => :crypto.strong_rand_bytes(32) - } + authorized_keys: [ + %{ + @node_1_public_key => :crypto.strong_rand_bytes(32) + } + ] } }, validation_stamp: %ValidationStamp{ @@ -105,9 +107,11 @@ defmodule ArchEthic.P2P.MemTableLoaderTest do type: :node_shared_secrets, data: %TransactionData{ keys: %Keys{ - authorized_keys: %{ - @node_2_public_key => :crypto.strong_rand_bytes(32) - } + authorized_keys: [ + %{ + @node_2_public_key => :crypto.strong_rand_bytes(32) + } + ] } }, validation_stamp: %ValidationStamp{ @@ -155,9 +159,11 @@ defmodule ArchEthic.P2P.MemTableLoaderTest do type: :node_shared_secrets, data: %TransactionData{ keys: %Keys{ - authorized_keys: %{ - @node_1_public_key => :crypto.strong_rand_bytes(32) - } + authorized_keys: [ + %{ + @node_1_public_key => :crypto.strong_rand_bytes(32) + } + ] } }, validation_stamp: %ValidationStamp{ diff --git a/test/archethic/shared_secrets/node_renewal_test.exs b/test/archethic/shared_secrets/node_renewal_test.exs index e459da5b2..fd4dfb394 100644 --- a/test/archethic/shared_secrets/node_renewal_test.exs +++ b/test/archethic/shared_secrets/node_renewal_test.exs @@ -23,10 +23,7 @@ defmodule ArchEthic.SharedSecrets.NodeRenewalTest do %Transaction{ type: :node_shared_secrets, data: %TransactionData{ - keys: %Keys{ - authorized_keys: authorized_keys, - secret: _ - }, + keys: keys = %Keys{}, content: content } } = @@ -36,7 +33,7 @@ defmodule ArchEthic.SharedSecrets.NodeRenewalTest do aes_key ) - assert Map.has_key?(authorized_keys, Crypto.last_node_public_key()) + assert Keys.authorized_key?(keys, Crypto.last_node_public_key()) assert {:ok, _, _} = NodeRenewal.decode_transaction_content(content) end diff --git a/test/archethic/transaction_chain/transaction/data_test.exs b/test/archethic/transaction_chain/transaction/data_test.exs index fffc99ca8..3a80859b9 100644 --- a/test/archethic/transaction_chain/transaction/data_test.exs +++ b/test/archethic/transaction_chain/transaction/data_test.exs @@ -16,8 +16,9 @@ defmodule ArchEthic.TransactionChain.TransactionDataTest do check all( code <- StreamData.binary(), content <- StreamData.binary(), - secret <- StreamData.binary(), - authorized_key_seeds <- StreamData.list_of(StreamData.binary(length: 32)), + secret <- StreamData.binary(min_length: 1), + authorized_key_seeds <- + StreamData.list_of(StreamData.binary(length: 32), min_length: 1), transfers <- StreamData.map_of(StreamData.binary(length: 32), StreamData.float(min: 0.0)), recipients <- list_of(StreamData.binary(length: 32)) @@ -39,7 +40,13 @@ defmodule ArchEthic.TransactionChain.TransactionDataTest do %TransactionData{ code: code, content: content, - keys: Keys.new(authorized_public_keys, :crypto.strong_rand_bytes(32), secret), + keys: + Keys.add_secret( + %Keys{}, + secret, + :crypto.strong_rand_bytes(32), + authorized_public_keys + ), ledger: %Ledger{ uco: %UCOLedger{ transfers: transfers @@ -52,8 +59,13 @@ defmodule ArchEthic.TransactionChain.TransactionDataTest do assert tx_data.code == code assert tx_data.content == content - assert tx_data.keys.secret == secret - assert Enum.all?(Map.keys(tx_data.keys.authorized_keys), &(&1 in authorized_public_keys)) + assert tx_data.keys.secrets == [secret] + + assert Enum.all?( + Keys.list_authorized_public_keys(tx_data.keys), + &(&1 in authorized_public_keys) + ) + assert tx_data.recipients == recipients_addresses assert tx_data.ledger.uco.transfers == transfers end diff --git a/test/archethic/transaction_chain/transaction_data/keys_test.exs b/test/archethic/transaction_chain/transaction_data/keys_test.exs index 4bd48cbe0..382e69a9a 100644 --- a/test/archethic/transaction_chain/transaction_data/keys_test.exs +++ b/test/archethic/transaction_chain/transaction_data/keys_test.exs @@ -13,16 +13,14 @@ defmodule ArchEthic.TransactionChain.TransactionData.KeysTest do {pub, pv} = Crypto.generate_deterministic_keypair("seed", :secp256r1) {pub2, pv2} = Crypto.generate_deterministic_keypair("other_seed") - %Keys{authorized_keys: authorized_keys, secret: secret} = - Keys.new([pub, pub2], secret_key, secret) - - assert Map.has_key?(authorized_keys, pub) - encrypted_key = Map.get(authorized_keys, pub) + %Keys{secrets: [secret]} = keys = Keys.add_secret(%Keys{}, secret, secret_key, [pub, pub2]) + assert Keys.authorized_key?(keys, pub) + encrypted_key = Keys.get_encrypted_key_at(keys, 0, pub) secret_key = Crypto.ec_decrypt!(encrypted_key, pv) assert "important message" == Crypto.aes_decrypt!(secret, secret_key) - encrypted_key = Map.get(authorized_keys, pub2) + encrypted_key = Keys.get_encrypted_key_at(keys, 0, pub2) secret_key = Crypto.ec_decrypt!(encrypted_key, pv2) assert "important message" == Crypto.aes_decrypt!(secret, secret_key) @@ -31,7 +29,7 @@ defmodule ArchEthic.TransactionChain.TransactionData.KeysTest do property "symmetric serialization/deserialization" do check all( secret <- StreamData.binary(min_length: 1), - seeds <- StreamData.list_of(StreamData.binary(length: 32)), + seeds <- StreamData.list_of(StreamData.binary(length: 32), min_length: 1), secret_key <- StreamData.binary(length: 32) ) do public_keys = @@ -41,14 +39,14 @@ defmodule ArchEthic.TransactionChain.TransactionData.KeysTest do end) {keys, _} = - public_keys - |> Keys.new(secret_key, secret) + %Keys{} + |> Keys.add_secret(secret, secret_key, public_keys) |> Keys.serialize() |> Keys.deserialize() - assert keys.secret == secret + assert keys.secrets == [secret] - assert Enum.all?(Map.keys(keys.authorized_keys), &(&1 in public_keys)) + assert Enum.all?(Keys.list_authorized_public_keys(keys), &(&1 in public_keys)) end end end diff --git a/test/archethic_web/controllers/api/transaction_payload_test.exs b/test/archethic_web/controllers/api/transaction_payload_test.exs index ac29528aa..47a9eb797 100644 --- a/test/archethic_web/controllers/api/transaction_payload_test.exs +++ b/test/archethic_web/controllers/api/transaction_payload_test.exs @@ -96,28 +96,10 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do end test "should return an error if the uco ledger transfer address is invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - ledger: %{ - changes: %{ - uco: %{ - changes: %{ - transfers: [ - %{ - errors: errors - } - ] - } - } - } - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -132,32 +114,17 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - assert {"must be hexadecimal", _} = Keyword.get(errors, :to) + assert [%{to: ["must be hexadecimal"]}] = + changeset + |> get_errors() + |> get_in([:data, :ledger, :uco, :transfers]) end test "should return an error if the uco ledger transfer amount is invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - ledger: %{ - changes: %{ - uco: %{ - changes: %{ - transfers: [ - %{ - errors: errors - } - ] - } - } - } - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -181,32 +148,15 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - assert {"is invalid", _} = Keyword.get(errors, :amount) + assert [%{amount: ["is invalid"]}] = + changeset |> get_errors() |> get_in([:data, :ledger, :uco, :transfers]) end test "should return an error if the nft ledger transfer address is invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - ledger: %{ - changes: %{ - nft: %{ - changes: %{ - transfers: [ - %{ - errors: errors - } - ] - } - } - } - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -231,32 +181,15 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - assert {"must be hexadecimal", _} = Keyword.get(errors, :to) + assert [%{to: ["must be hexadecimal"]}] = + changeset |> get_errors() |> get_in([:data, :ledger, :nft, :transfers]) end test "should return an error if the nft ledger transfer amount is invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - ledger: %{ - changes: %{ - nft: %{ - changes: %{ - transfers: [ - %{ - errors: errors - } - ] - } - } - } - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -281,32 +214,20 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - assert {"is invalid", _} = Keyword.get(errors, :amount) + assert [ + %{ + amount: [ + "is invalid" + ] + } + ] = changeset |> get_errors |> get_in([:data, :ledger, :nft, :transfers]) end test "should return an error if the nft ledger transfer nft address is invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - ledger: %{ - changes: %{ - nft: %{ - changes: %{ - transfers: [ - %{ - errors: errors - } - ] - } - } - } - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -331,22 +252,15 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - assert {"must be hexadecimal", _} = Keyword.get(errors, :nft) + assert [%{nft: ["must be hexadecimal"]}] = + changeset |> get_errors |> get_in([:data, :ledger, :nft, :transfers]) end test "should return an error if the encrypted secret is not an hexadecimal" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - keys: %{ - errors: errors - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -358,27 +272,19 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "keys" => %{ - "secret" => "abc" + "secrets" => ["abc"] } } }) - assert {"must be hexadecimal", _} = Keyword.get(errors, :secret) + assert ["must be hexadecimal"] = changeset |> get_errors |> get_in([:data, :keys, :secrets]) end test "should return an error if the public key in the authorized keys is not valid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - keys: %{ - errors: errors - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -390,27 +296,22 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "keys" => %{ - "authorizedKeys" => %{ - "key" => "hello" - } + "authorizedKeys" => [ + %{ + "key" => "hello" + } + ] } } }) - assert {"public key must be hexadecimal", _} = Keyword.get(errors, :authorizedKeys) + assert ["public key must be hexadecimal"] = + changeset |> get_errors |> get_in([:data, :keys, :authorizedKeys]) - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - keys: %{ - errors: errors - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -422,28 +323,22 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "keys" => %{ - "authorizedKeys" => + "authorizedKeys" => [ Map.put(%{}, Base.encode16(:crypto.strong_rand_bytes(32)), "hello") + ] } } }) - assert {"public key is invalid", _} = Keyword.get(errors, :authorizedKeys) + assert ["public key is invalid"] = + changeset |> get_errors |> get_in([:data, :keys, :authorizedKeys]) end test "should return an error if the encrypted key in the authorized keys is not valid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - changes: %{ - keys: %{ - errors: errors - } - } - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -455,28 +350,26 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "keys" => %{ - "authorizedKeys" => + "authorizedKeys" => [ Map.put( %{}, Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "hello" ) + ] } } }) - assert {"encrypted key must be hexadecimal", _} = Keyword.get(errors, :authorizedKeys) + assert ["encrypted key must be hexadecimal"] = + changeset |> get_errors |> get_in([:data, :keys, :authorizedKeys]) end test "should return an error if the recipients are invalid" do - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - errors: errors - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -491,16 +384,12 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - {"must be hexadecimal", _} = Keyword.get(errors, :recipients) + assert ["must be hexadecimal"] = changeset |> get_errors() |> get_in([:data, :recipients]) - %Ecto.Changeset{ - valid?: false, - changes: %{ - data: %{ - errors: errors - } - } - } = + changeset = + %Ecto.Changeset{ + valid?: false + } = TransactionPayload.changeset(%{ "version" => 1, "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), @@ -515,7 +404,7 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }) - {"invalid hash", _} = Keyword.get(errors, :recipients) + assert ["invalid hash"] = changeset |> get_errors() |> get_in([:data, :recipients]) end end @@ -549,10 +438,12 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }, keys: %{ - secret: secret, - authorized_keys: %{ - authorized_public_key => encrypted_key - } + secrets: [secret], + authorized_keys: [ + %{ + authorized_public_key => encrypted_key + } + ] } } } == @@ -572,17 +463,24 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do } }, "keys" => %{ - "secret" => Base.encode16(secret), - "authorizedKeys" => + "secrets" => [Base.encode16(secret)], + "authorizedKeys" => [ Map.put( %{}, Base.encode16(authorized_public_key), Base.encode16(encrypted_key) ) + ] }, "recipients" => [Base.encode16(recipient)] } }) |> TransactionPayload.to_map() end + + defp get_errors(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {msg, _} -> + msg + end) + end end