From a9cc20996151fb64c46130082540a981538659f4 Mon Sep 17 00:00:00 2001 From: neylix Date: Tue, 26 Apr 2022 13:06:14 +0200 Subject: [PATCH 1/6] Leverage sharded origin key Fixes #275 --- lib/archethic/bootstrap/network_init.ex | 38 +++++++++++ lib/archethic/bootstrap/sync.ex | 1 + .../mem_tables/origin_key_lookup.ex | 24 +------ .../archethic/bootstrap/network_init_test.exs | 66 +++++++++++++++++++ test/archethic/bootstrap/sync_test.exs | 3 + test/archethic/bootstrap_test.exs | 3 + 6 files changed, 112 insertions(+), 23 deletions(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index 5dd472db8..7820b3bf9 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -6,6 +6,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do """ alias ArchEthic.Bootstrap + alias ArchEthic.Bootstrap.NetworkInit alias ArchEthic.BeaconChain.ReplicationAttestation @@ -31,6 +32,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do alias ArchEthic.TransactionChain.TransactionData.Ledger alias ArchEthic.TransactionChain.TransactionData.UCOLedger alias ArchEthic.TransactionChain.TransactionData.UCOLedger.Transfer + alias ArchEthic.TransactionChain.TransactionData.Ownership alias ArchEthic.TransactionChain.TransactionSummary @@ -38,6 +40,11 @@ defmodule ArchEthic.Bootstrap.NetworkInit do @genesis_seed Application.compile_env(:archethic, [__MODULE__, :genesis_seed]) + @genesis_origin_public_keys Application.compile_env!( + :archethic, + [NetworkInit, :genesis_origin_public_keys] + ) + defp get_genesis_pools do Application.get_env(:archethic, __MODULE__) |> Keyword.get(:genesis_pools, []) end @@ -74,6 +81,37 @@ defmodule ArchEthic.Bootstrap.NetworkInit do |> self_replication() end + @doc """ + Create the first origin shared secret transaction + """ + @spec init_software_origin_shared_secrets_chain() :: :ok + def init_software_origin_shared_secrets_chain do + Logger.info("Create first software origin shared secret transaction") + + origin_seed = :crypto.strong_rand_bytes(32) + secret_key = :crypto.strong_rand_bytes(32) + signing_seed = Crypto.storage_nonce() <> "software" + + # Default keypair generation creates software public key + {origin_public_key, origin_private_key} = Crypto.generate_deterministic_keypair(origin_seed) + + encrypted_origin_private_key = Crypto.aes_encrypt(origin_private_key, secret_key) + + Transaction.new( + :origin_shared_secrets, + %TransactionData{ + content: <>, + ownerships: [ + Ownership.new(encrypted_origin_private_key, secret_key, @genesis_origin_public_keys) + ] + }, + signing_seed, + 0 + ) + |> self_validation() + |> self_replication() + end + @doc """ Initializes the genesis wallets for the UCO distribution """ diff --git a/lib/archethic/bootstrap/sync.ex b/lib/archethic/bootstrap/sync.ex index a3ccbb3a3..0b9f23e74 100644 --- a/lib/archethic/bootstrap/sync.ex +++ b/lib/archethic/bootstrap/sync.ex @@ -119,6 +119,7 @@ defmodule ArchEthic.Bootstrap.Sync do P2P.set_node_globally_available(Crypto.first_node_public_key()) P2P.authorize_node(Crypto.last_node_public_key(), DateTime.utc_now()) + NetworkInit.init_software_origin_shared_secrets_chain() NetworkInit.init_node_shared_secrets_chain() NetworkInit.init_genesis_wallets() end diff --git a/lib/archethic/shared_secrets/mem_tables/origin_key_lookup.ex b/lib/archethic/shared_secrets/mem_tables/origin_key_lookup.ex index e48fea288..d7a75a08c 100644 --- a/lib/archethic/shared_secrets/mem_tables/origin_key_lookup.ex +++ b/lib/archethic/shared_secrets/mem_tables/origin_key_lookup.ex @@ -7,8 +7,6 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do alias ArchEthic.Crypto - alias ArchEthic.Bootstrap.NetworkInit - alias ArchEthic.SharedSecrets require Logger @@ -16,10 +14,6 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do @origin_key_table :archethic_origin_keys @origin_key_by_type_table :archethic_origin_key_by_type - @genesis_origin_public_keys Application.compile_env!( - :archethic, - [NetworkInit, :genesis_origin_public_keys] - ) @doc """ Initialize memory tables to index public information from the shared secrets @@ -39,10 +33,6 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do :ets.new(@origin_key_by_type_table, [:bag, :named_table, :public, read_concurrency: true]) :ets.new(@origin_key_table, [:set, :named_table, :public, read_concurrency: true]) - Enum.each(@genesis_origin_public_keys, fn key -> - add_public_key(:software, key) - end) - {:ok, []} end @@ -60,10 +50,6 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do iex> { :ets.tab2list(:archethic_origin_keys), :ets.tab2list(:archethic_origin_key_by_type) } { [ - {<<1, 0, 4, 171, 65, 41, 31, 132, 122, 96, 16, 85, 174, 221, 26, 242, 79, 247, - 111, 169, 112, 214, 68, 30, 45, 202, 56, 24, 168, 49, 155, 0, 76, 150, 178, - 123, 143, 235, 29, 163, 26, 4, 75, 160, 164, 128, 11, 67, 83, 53, 151, 53, - 113, 158, 187, 58, 5, 249, 131, 147, 169, 204, 89, 156, 63, 175, 214>>, :software}, {"key1", :software}, {"key3", :hardware}, {"key2", :hardware} @@ -71,10 +57,6 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do [ {:hardware, "key2"}, {:hardware, "key3"}, - {:software, <<1, 0, 4, 171, 65, 41, 31, 132, 122, 96, 16, 85, 174, 221, 26, 242, 79, 247, - 111, 169, 112, 214, 68, 30, 45, 202, 56, 24, 168, 49, 155, 0, 76, 150, 178, - 123, 143, 235, 29, 163, 26, 4, 75, 160, 164, 128, 11, 67, 83, 53, 151, 53, - 113, 158, 187, 58, 5, 249, 131, 147, 169, 204, 89, 156, 63, 175, 214>>}, {:software, "key1"} ], } @@ -118,11 +100,7 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do iex> :ok = OriginKeyLookup.add_public_key(:hardware, "key3") iex> OriginKeyLookup.list_public_keys() [ - <<1, 0, 4, 171, 65, 41, 31, 132, 122, 96, 16, 85, 174, 221, 26, 242, 79, 247, - 111, 169, 112, 214, 68, 30, 45, 202, 56, 24, 168, 49, 155, 0, 76, 150, 178, - 123, 143, 235, 29, 163, 26, 4, 75, 160, 164, 128, 11, 67, 83, 53, 151, 53, - 113, 158, 187, 58, 5, 249, 131, 147, 169, 204, 89, 156, 63, 175, 214>>, - "key1", + "key1", "key3", "key2" ] diff --git a/test/archethic/bootstrap/network_init_test.exs b/test/archethic/bootstrap/network_init_test.exs index 5397feb4c..24ee77b65 100644 --- a/test/archethic/bootstrap/network_init_test.exs +++ b/test/archethic/bootstrap/network_init_test.exs @@ -36,12 +36,18 @@ defmodule ArchEthic.Bootstrap.NetworkInitTest do alias ArchEthic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput alias ArchEthic.TransactionChain.TransactionData + alias ArchEthic.TransactionChain.TransactionData.Ownership alias ArchEthic.TransactionChain.TransactionData.Ledger alias ArchEthic.TransactionChain.TransactionData.UCOLedger alias ArchEthic.TransactionChain.TransactionData.UCOLedger.Transfer alias ArchEthic.TransactionChain.TransactionSummary alias ArchEthic.TransactionFactory + @genesis_origin_public_keys Application.compile_env!( + :archethic, + [NetworkInit, :genesis_origin_public_keys] + ) + import Mox setup do @@ -267,4 +273,64 @@ defmodule ArchEthic.Bootstrap.NetworkInitTest do assert %{uco: 146_000_000_000_000_000} = Account.get_balance(SharedSecrets.get_network_pool_address()) end + + test "init_software_origin_shared_secrets_chain/1 should create first origin shared secret transaction" do + MockClient + |> stub(:send_message, fn + _, %GetTransaction{}, _ -> + {:ok, %NotFound{}} + + _, %GetTransactionChain{}, _ -> + {:ok, %TransactionList{transactions: [], more?: false, paging_state: nil}} + + _, %GetUnspentOutputs{}, _ -> + {:ok, %UnspentOutputList{unspent_outputs: []}} + + _, %GetLastTransactionAddress{address: address}, _ -> + {:ok, %LastTransactionAddress{address: address}} + end) + + me = self() + + MockDB + |> expect(:write_transaction, fn tx -> + send(me, {:transaction, tx}) + :ok + end) + + P2P.add_and_connect_node(%Node{ + first_public_key: Crypto.last_node_public_key(), + last_public_key: Crypto.last_node_public_key(), + ip: {127, 0, 0, 1}, + port: 3000, + available?: true, + enrollment_date: DateTime.utc_now(), + network_patch: "AAA", + authorization_date: DateTime.utc_now(), + authorized?: true, + reward_address: <<0::8, :crypto.strong_rand_bytes(32)::binary>> + }) + + Crypto.generate_deterministic_keypair("daily_nonce_seed") + |> elem(0) + |> NetworkLookup.set_daily_nonce_public_key(DateTime.utc_now() |> DateTime.add(-10)) + + assert :ok = NetworkInit.init_software_origin_shared_secrets_chain() + + assert 1 == SharedSecrets.list_origin_public_keys() |> Enum.count() + + assert_receive {:transaction, + %Transaction{ + type: :origin_shared_secrets, + data: %TransactionData{ + ownerships: [ + %Ownership{ + authorized_keys: authorized_keys + } + ] + } + }} + + assert Map.keys(authorized_keys) == @genesis_origin_public_keys + end end diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index f30e3d21d..36533ec65 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -26,6 +26,7 @@ defmodule ArchEthic.Bootstrap.SyncTest do alias ArchEthic.P2P.Message.UnspentOutputList alias ArchEthic.P2P.Node + alias ArchEthic.SharedSecrets alias ArchEthic.SharedSecrets.NodeRenewalScheduler alias ArchEthic.TransactionChain @@ -271,6 +272,8 @@ defmodule ArchEthic.Bootstrap.SyncTest do assert %Node{authorized?: true} = P2P.get_node_info() assert 1 == Crypto.number_of_node_shared_secrets_keys() + assert 2 == SharedSecrets.list_origin_public_keys() |> Enum.count() + Application.get_env(:archethic, ArchEthic.Bootstrap.NetworkInit)[:genesis_pools] |> Enum.each(fn %{address: address, amount: amount} -> assert %{uco: amount, nft: %{}} == Account.get_balance(address) diff --git a/test/archethic/bootstrap_test.exs b/test/archethic/bootstrap_test.exs index 8c7fa3fbe..96837f0f0 100644 --- a/test/archethic/bootstrap_test.exs +++ b/test/archethic/bootstrap_test.exs @@ -35,6 +35,7 @@ defmodule ArchEthic.BootstrapTest do alias ArchEthic.SelfRepair.Scheduler, as: SelfRepairScheduler + alias ArchEthic.SharedSecrets alias ArchEthic.SharedSecrets.NodeRenewalScheduler alias ArchEthic.TransactionChain @@ -133,6 +134,8 @@ defmodule ArchEthic.BootstrapTest do P2P.list_nodes() assert 1 == Crypto.number_of_node_shared_secrets_keys() + + assert 2 == SharedSecrets.list_origin_public_keys() |> Enum.count() end end From a822439d19d249cbb1c64139f12e24a814c9e6bb Mon Sep 17 00:00:00 2001 From: neylix Date: Tue, 26 Apr 2022 16:50:10 +0200 Subject: [PATCH 2/6] Correct alias --- lib/archethic/bootstrap/network_init.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index 7820b3bf9..806e4eaf7 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -6,7 +6,6 @@ defmodule ArchEthic.Bootstrap.NetworkInit do """ alias ArchEthic.Bootstrap - alias ArchEthic.Bootstrap.NetworkInit alias ArchEthic.BeaconChain.ReplicationAttestation @@ -42,7 +41,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do @genesis_origin_public_keys Application.compile_env!( :archethic, - [NetworkInit, :genesis_origin_public_keys] + [__MODULE__, :genesis_origin_public_keys] ) defp get_genesis_pools do From ff8c70dc9e592e3a44d138561e52bddcf681806d Mon Sep 17 00:00:00 2001 From: neylix Date: Tue, 26 Apr 2022 17:04:37 +0200 Subject: [PATCH 3/6] Add content format for origin_shared_secrets --- lib/archethic_web/views/explorer_view.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/archethic_web/views/explorer_view.ex b/lib/archethic_web/views/explorer_view.ex index 70de0b33f..4da753b54 100644 --- a/lib/archethic_web/views/explorer_view.ex +++ b/lib/archethic_web/views/explorer_view.ex @@ -196,5 +196,9 @@ defmodule ArchEthicWeb.ExplorerView do """ end + def format_transaction_content(:origin_shared_secrets, content) do + Base.encode16(content) + end + def format_transaction_content(_, content), do: content end From c606fcb47f4a1b4ce6f8f223bdd9f7e3302d8efa Mon Sep 17 00:00:00 2001 From: neylix Date: Wed, 27 Apr 2022 11:48:39 +0200 Subject: [PATCH 4/6] Update explorer content formatting --- assets/css/explorer.scss | 3 +- lib/archethic_web/views/explorer_view.ex | 47 +++++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/assets/css/explorer.scss b/assets/css/explorer.scss index 2482d5a7c..114502a0b 100644 --- a/assets/css/explorer.scss +++ b/assets/css/explorer.scss @@ -23,8 +23,7 @@ pre { } .text_wrap { - overflow-wrap: break-word; - word-wrap: break-word; + word-break: break-word; } .modal-content { diff --git a/lib/archethic_web/views/explorer_view.ex b/lib/archethic_web/views/explorer_view.ex index 4da753b54..849cc526c 100644 --- a/lib/archethic_web/views/explorer_view.ex +++ b/lib/archethic_web/views/explorer_view.ex @@ -16,6 +16,8 @@ defmodule ArchEthicWeb.ExplorerView do alias ArchEthic.Utils + alias ArchEthic.Crypto + alias Phoenix.Naming def roles_to_string(roles) do @@ -197,8 +199,51 @@ defmodule ArchEthicWeb.ExplorerView do end def format_transaction_content(:origin_shared_secrets, content) do - Base.encode16(content) + get_origin_public_keys(content, %{software: [], hardware: []}) + |> Enum.map_join("\n", fn {family, keys} -> + format_origin_shared_secrets_content(family, keys) + end) end def format_transaction_content(_, content), do: content + + defp format_origin_shared_secrets_content(family, keys) do + case Enum.count(keys) do + 1 -> + "#{family} origin public key : #{Enum.at(keys, 0) |> Base.encode16()}" + + x when x > 1 -> + """ + #{family} origin public keys : + #{Enum.map_join(keys, "\n", fn key -> "- " <> Base.encode16(key) end)} + """ + + _ -> + "" + end + end + + defp get_origin_public_keys(<<>>, acc), do: acc + + defp get_origin_public_keys(<>, acc) do + key_size = Crypto.key_size(curve_id) + <> = rest + + family = + case Crypto.key_origin(origin_id) do + :software -> + :software + + :tpm -> + :hardware + + :on_chain_wallet -> + :software + end + + get_origin_public_keys( + rest, + Map.update!(acc, family, &[<> | &1]) + ) + end end From b0c2e2b9f96a4f11b240857443ac71ac0bc6e344 Mon Sep 17 00:00:00 2001 From: neylix Date: Thu, 28 Apr 2022 14:56:44 +0200 Subject: [PATCH 5/6] update origin family seed and origin chain SC condition --- lib/archethic/bootstrap/network_init.ex | 10 +++++++++- lib/archethic/crypto.ex | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index 806e4eaf7..af8cb688d 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -89,7 +89,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do origin_seed = :crypto.strong_rand_bytes(32) secret_key = :crypto.strong_rand_bytes(32) - signing_seed = Crypto.storage_nonce() <> "software" + signing_seed = Crypto.get_origin_family_seed(:software) # Default keypair generation creates software public key {origin_public_key, origin_private_key} = Crypto.generate_deterministic_keypair(origin_seed) @@ -99,6 +99,14 @@ defmodule ArchEthic.Bootstrap.NetworkInit do Transaction.new( :origin_shared_secrets, %TransactionData{ + code: """ + condition inherit: [ + # We need to ensure the type stays consistent + # So we can apply specific rules during the transaction validation + type: origin_shared_secrets, + origin_family: software + ] + """, content: <>, ownerships: [ Ownership.new(encrypted_origin_private_key, secret_key, @genesis_origin_public_keys) diff --git a/lib/archethic/crypto.ex b/lib/archethic/crypto.ex index bd2ccaf71..bc2de8bc9 100755 --- a/lib/archethic/crypto.ex +++ b/lib/archethic/crypto.ex @@ -1168,6 +1168,24 @@ defmodule ArchEthic.Crypto do @spec default_curve() :: supported_curve() def default_curve, do: Application.get_env(:archethic, __MODULE__)[:default_curve] + @doc """ + Get the origin seed for a given origin family + """ + @spec get_origin_family_seed(supported_origin()) :: binary() + def get_origin_family_seed(origin_id) do + storage_nonce() <> + case origin_id do + :software -> + "software" + + :on_chain_wallet -> + "software" + + _ -> + "hardware" + end + end + @doc """ Determine if the origin of the key is allowed From ed7a1baa44b527ef617b23e80510adcffa2df66d Mon Sep 17 00:00:00 2001 From: neylix Date: Thu, 28 Apr 2022 17:15:33 +0200 Subject: [PATCH 6/6] Correct confused origin family --- lib/archethic/bootstrap/network_init.ex | 5 ++--- lib/archethic/crypto.ex | 18 ------------------ lib/archethic/shared_secrets.ex | 8 ++++++++ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index af8cb688d..87f1a5770 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -89,7 +89,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do origin_seed = :crypto.strong_rand_bytes(32) secret_key = :crypto.strong_rand_bytes(32) - signing_seed = Crypto.get_origin_family_seed(:software) + signing_seed = SharedSecrets.get_origin_family_seed(:software) # Default keypair generation creates software public key {origin_public_key, origin_private_key} = Crypto.generate_deterministic_keypair(origin_seed) @@ -103,8 +103,7 @@ defmodule ArchEthic.Bootstrap.NetworkInit do condition inherit: [ # We need to ensure the type stays consistent # So we can apply specific rules during the transaction validation - type: origin_shared_secrets, - origin_family: software + type: origin_shared_secrets ] """, content: <>, diff --git a/lib/archethic/crypto.ex b/lib/archethic/crypto.ex index bc2de8bc9..bd2ccaf71 100755 --- a/lib/archethic/crypto.ex +++ b/lib/archethic/crypto.ex @@ -1168,24 +1168,6 @@ defmodule ArchEthic.Crypto do @spec default_curve() :: supported_curve() def default_curve, do: Application.get_env(:archethic, __MODULE__)[:default_curve] - @doc """ - Get the origin seed for a given origin family - """ - @spec get_origin_family_seed(supported_origin()) :: binary() - def get_origin_family_seed(origin_id) do - storage_nonce() <> - case origin_id do - :software -> - "software" - - :on_chain_wallet -> - "software" - - _ -> - "hardware" - end - end - @doc """ Determine if the origin of the key is allowed diff --git a/lib/archethic/shared_secrets.ex b/lib/archethic/shared_secrets.ex index 8022e37cf..c5c53a181 100644 --- a/lib/archethic/shared_secrets.ex +++ b/lib/archethic/shared_secrets.ex @@ -91,4 +91,12 @@ defmodule ArchEthic.SharedSecrets do |> Keyword.get(NodeRenewalScheduler) |> NodeRenewalScheduler.config_change() end + + @doc """ + Get the origin seed for a given origin family + """ + @spec get_origin_family_seed(origin_family()) :: binary() + def get_origin_family_seed(origin_family) do + <> + end end