Skip to content

Commit

Permalink
Expose API to get the shared origin keys (#292)
Browse files Browse the repository at this point in the history
Fixes #158
  • Loading branch information
Neylix committed May 3, 2022
1 parent 3e73e36 commit 6081183
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 8 deletions.
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ config :archethic, ArchEthic.Bootstrap.NetworkInit,
<<190, 107, 211, 23, 6, 230, 228, 144, 253, 154, 200, 213, 66, 172, 229, 96, 5, 171, 134, 249,
80, 160, 149, 4, 106, 249, 155, 116, 186, 125, 77, 192>>,
genesis_origin_public_keys: [
"010004AB41291F847A601055AEDD1AF24FF76FA970D6441E2DCA3818A8319B004C96B27B8FEB1DA31A044BA0A4800B4353359735719EBB3A05F98393A9CC599C3FAFD6"
"010104AB41291F847A601055AEDD1AF24FF76FA970D6441E2DCA3818A8319B004C96B27B8FEB1DA31A044BA0A4800B4353359735719EBB3A05F98393A9CC599C3FAFD6"
|> Base.decode16!(case: :mixed)
]

Expand Down
17 changes: 17 additions & 0 deletions lib/archethic/shared_secrets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,21 @@ defmodule ArchEthic.SharedSecrets do
def get_origin_family_seed(origin_family) do
<<Crypto.storage_nonce()::binary, Atom.to_string(origin_family)::binary>>
end

@doc """
Get the origin family for a given origin id
"""
@spec get_origin_family_from_origin_id(non_neg_integer()) :: origin_family()
def get_origin_family_from_origin_id(origin_id) do
case Crypto.key_origin(origin_id) do
:software ->
:software

:on_chain_wallet ->
:software

_ ->
:biometric
end
end
end
51 changes: 44 additions & 7 deletions lib/archethic/utils/regression/playbook.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ defmodule ArchEthic.Utils.Regression.Playbook do

alias ArchEthic.Utils.WebClient

alias ArchEthic.Bootstrap.NetworkInit

@callback play!([String.t()], Keyword.t()) :: :ok

@genesis_origin_private_key "01009280BDB84B8F8AEDBA205FE3552689964A5626EE2C60AA10E3BF22A91A036009"
@genesis_origin_private_key "01019280BDB84B8F8AEDBA205FE3552689964A5626EE2C60AA10E3BF22A91A036009"
|> Base.decode16!()

@genesis_origin_public_key Application.compile_env!(
:archethic,
[NetworkInit, :genesis_origin_public_keys]
)
|> Enum.at(0)

@faucet_seed Application.compile_env(:archethic, [ArchEthicWeb.FaucetController, :seed])

defmacro __using__(_opts \\ []) do
Expand Down Expand Up @@ -93,6 +101,8 @@ defmodule ArchEthic.Utils.Regression.Playbook do

{next_public_key, _} = Crypto.derive_keypair(transaction_seed, chain_length + 1, curve)

genesis_origin_private_key = get_origin_private_key(host, port)

tx =
%Transaction{
address: Crypto.derive_address(next_public_key),
Expand All @@ -101,7 +111,7 @@ defmodule ArchEthic.Utils.Regression.Playbook do
previous_public_key: previous_public_key
}
|> Transaction.previous_sign_transaction(previous_private_key)
|> Transaction.origin_sign_transaction(@genesis_origin_private_key)
|> Transaction.origin_sign_transaction(genesis_origin_private_key)

true =
Crypto.verify?(
Expand All @@ -123,6 +133,33 @@ defmodule ArchEthic.Utils.Regression.Playbook do
end
end

defp get_origin_private_key(host, port) do
body = %{
"origin_public_key" => Base.encode16(@genesis_origin_public_key)
}

case WebClient.with_connection(
host,
port,
&WebClient.json(&1, "/api/origin_key", body)
) do
{:ok,
%{
"encrypted_origin_private_keys" => encrypted_origin_private_keys,
"encrypted_secret_key" => encrypted_secret_key
}} ->
aes_key =
Base.decode16!(encrypted_secret_key, case: :mixed)
|> Crypto.ec_decrypt!(@genesis_origin_private_key)

Base.decode16!(encrypted_origin_private_keys, case: :mixed)
|> Crypto.aes_decrypt!(aes_key)

_ ->
@genesis_origin_private_key
end
end

def send_transaction_with_await_replication(
transaction_seed,
tx_type,
Expand Down Expand Up @@ -188,11 +225,11 @@ defmodule ArchEthic.Utils.Regression.Playbook do

defp await_replication(txn_address) do
query = """
subscription {
transactionConfirmed(address: "#{Base.encode16(txn_address)}") {
nbConfirmations
}
}
subscription {
transactionConfirmed(address: "#{Base.encode16(txn_address)}") {
nbConfirmations
}
}
"""

WSClient.absinthe_sub(
Expand Down
65 changes: 65 additions & 0 deletions lib/archethic_web/controllers/api/origin_key_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
defmodule ArchEthicWeb.API.OriginKeyController do
use ArchEthicWeb, :controller

alias ArchEthic.Crypto
alias ArchEthic.SharedSecrets

alias ArchEthic.TransactionChain
alias ArchEthic.TransactionChain.TransactionData.Ownership

def origin_key(conn, params) do
with %{"origin_public_key" => origin_public_key} <- params,
{:ok, origin_public_key} <- Base.decode16(origin_public_key, case: :mixed),
true <- Crypto.valid_public_key?(origin_public_key),
<<_curve_id::8, origin_id::8, _rest::binary>> <- origin_public_key,
{first_origin_family_public_key, _} <-
SharedSecrets.get_origin_family_from_origin_id(origin_id)
|> SharedSecrets.get_origin_family_seed()
|> Crypto.derive_keypair(0),
{:ok, tx} <-
Crypto.derive_address(first_origin_family_public_key)
|> TransactionChain.get_last_transaction(data: [:ownerships]),
ownership when ownership != nil <-
Enum.find(tx.data.ownerships, fn ownership ->
Ownership.authorized_public_key?(ownership, origin_public_key)
end) do
res = %{
encrypted_origin_private_keys: Base.encode16(ownership.secret),
encrypted_secret_key:
Ownership.get_encrypted_key(ownership, origin_public_key) |> Base.encode16()
}

conn
|> put_status(:ok)
|> json(res)
else
er when er in [:error, false] ->
conn
|> put_status(400)
|> json(%{
error: "Invalid public key"
})

{:error, _} ->
conn
|> put_status(404)
|> json(%{
error: "Public key not found"
})

nil ->
conn
|> put_status(404)
|> json(%{
error: "Public key not found"
})

_ ->
conn
|> put_status(404)
|> json(%{
error: "Invalid parameters"
})
end
end
end
2 changes: 2 additions & 0 deletions lib/archethic_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ defmodule ArchEthicWeb.Router do
:last_transaction_content
)

post("/origin_key", ArchEthicWeb.API.OriginKeyController, :origin_key)

post("/transaction", ArchEthicWeb.API.TransactionController, :new)
post("/transaction_fee", ArchEthicWeb.API.TransactionController, :transaction_fee)

Expand Down
116 changes: 116 additions & 0 deletions test/archethic_web/controllers/api/origin_key_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
defmodule ArchEthicWeb.API.OriginKeyControllerTest do
use ArchEthicCase
use ArchEthicWeb.ConnCase

alias ArchEthic.P2P
alias ArchEthic.P2P.Node
alias ArchEthic.Crypto

alias ArchEthic.TransactionChain.Transaction
alias ArchEthic.TransactionChain.TransactionData
alias ArchEthic.TransactionChain.TransactionData.Ownership

import Mox

setup do
P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
first_public_key: Crypto.last_node_public_key(),
last_public_key: Crypto.last_node_public_key(),
network_patch: "AAA",
geo_patch: "AAA",
available?: true,
authorized?: true,
authorization_date: DateTime.utc_now()
})

:ok
end

describe "origin_key/2" do
test "should send not_found response for invalid params", %{conn: conn} do
conn =
post(conn, "/api/origin_key", %{
origin_public_key: "0001540315"
})

assert %{
"error" => "Invalid public key"
} = json_response(conn, 400)
end

test "should send not_found response when public key isn't found in owner transactions", %{
conn: conn
} do
MockDB
|> stub(:get_transaction, fn _, _ ->
{:ok,
%Transaction{
data: %TransactionData{
ownerships: [
%Ownership{
secret: Base.decode16!("0001AAAAAA"),
authorized_keys: %{Base.decode16!("0001BBBBBB") => Base.decode16!("0001CCCCCC")}
}
]
}
}}
end)

MockDB
|> stub(:get_last_chain_address, fn _ ->
Base.decode16!("0001DDDDDD")
end)

conn =
post(conn, "/api/origin_key", %{
origin_public_key:
"00015403152aeb59b1b584d77c8f326031815674afeade8cba25f18f02737d599c39"
})

assert %{
"error" => "Public key not found"
} = json_response(conn, 404)
end

test "should send json secret values response when public key is found in owner transactions",
%{conn: conn} do
MockDB
|> stub(:get_transaction, fn _, _ ->
{:ok,
%Transaction{
data: %TransactionData{
ownerships: [
%Ownership{
secret: Base.decode16!("0001AAAAAA"),
authorized_keys: %{
Base.decode16!(
"00015403152aeb59b1b584d77c8f326031815674afeade8cba25f18f02737d599c39",
case: :mixed
) => Base.decode16!("0001CCCCCC")
}
}
]
}
}}
end)

MockDB
|> stub(:get_last_chain_address, fn _ ->
Base.decode16!("0001DDDDDD")
end)

conn =
post(conn, "/api/origin_key", %{
origin_public_key:
"00015403152aeb59b1b584d77c8f326031815674afeade8cba25f18f02737d599c39"
})

assert %{
"encrypted_origin_private_keys" => "0001AAAAAA",
"encrypted_secret_key" => "0001CCCCCC"
} = json_response(conn, 200)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule ArchEthicWeb.API.TransactionControllerTest do
alias ArchEthic.P2P.Node
alias ArchEthic.Crypto

import Mox

setup do
P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
Expand Down

0 comments on commit 6081183

Please sign in to comment.