Skip to content

Commit

Permalink
Add origin shared secrets chain (#288)
Browse files Browse the repository at this point in the history
Fixes #275
  • Loading branch information
Neylix authored and Samuel committed May 6, 2022
1 parent 126b7fe commit cd0dfd3
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 25 deletions.
3 changes: 1 addition & 2 deletions assets/css/explorer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ pre {
}

.text_wrap {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
}

.modal-content {
Expand Down
44 changes: 44 additions & 0 deletions lib/archethic/bootstrap/network_init.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ 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

require Logger

@genesis_seed Application.compile_env(:archethic, [__MODULE__, :genesis_seed])

@genesis_origin_public_keys Application.compile_env!(
:archethic,
[__MODULE__, :genesis_origin_public_keys]
)

defp get_genesis_pools do
Application.get_env(:archethic, __MODULE__) |> Keyword.get(:genesis_pools, [])
end
Expand Down Expand Up @@ -74,6 +80,44 @@ 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 = 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)

encrypted_origin_private_key = Crypto.aes_encrypt(origin_private_key, secret_key)

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
]
""",
content: <<origin_public_key::binary>>,
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
"""
Expand Down
1 change: 1 addition & 0 deletions lib/archethic/bootstrap/sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/archethic/shared_secrets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
<<Crypto.storage_nonce()::binary, Atom.to_string(origin_family)::binary>>
end
end
24 changes: 1 addition & 23 deletions lib/archethic/shared_secrets/mem_tables/origin_key_lookup.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@ defmodule ArchEthic.SharedSecrets.MemTables.OriginKeyLookup do

alias ArchEthic.Crypto

alias ArchEthic.Bootstrap.NetworkInit

alias ArchEthic.SharedSecrets

require Logger

@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
Expand All @@ -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

Expand All @@ -60,21 +50,13 @@ 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}
],
[
{: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"}
],
}
Expand Down Expand Up @@ -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"
]
Expand Down
49 changes: 49 additions & 0 deletions lib/archethic_web/views/explorer_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ defmodule ArchEthicWeb.ExplorerView do

alias ArchEthic.Utils

alias ArchEthic.Crypto

alias Phoenix.Naming

def roles_to_string(roles) do
Expand Down Expand Up @@ -196,5 +198,52 @@ defmodule ArchEthicWeb.ExplorerView do
"""
end

def format_transaction_content(:origin_shared_secrets, content) do
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(<<curve_id::8, origin_id::8, rest::binary>>, acc) do
key_size = Crypto.key_size(curve_id)
<<key::binary-size(key_size), rest::binary>> = 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, &[<<curve_id::8, origin_id::8, key::binary>> | &1])
)
end
end
66 changes: 66 additions & 0 deletions test/archethic/bootstrap/network_init_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions test/archethic/bootstrap/sync_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions test/archethic/bootstrap_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule ArchEthic.BootstrapTest do

alias ArchEthic.SelfRepair.Scheduler, as: SelfRepairScheduler

alias ArchEthic.SharedSecrets
alias ArchEthic.SharedSecrets.NodeRenewalScheduler

alias ArchEthic.TransactionChain
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit cd0dfd3

Please sign in to comment.