Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate bls signature scheme #1541

Merged
merged 11 commits into from
Sep 9, 2024
3 changes: 2 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ config :archethic, Archethic.Crypto,
supported_curves: [
:ed25519,
:secp256r1,
:secp256k1
:secp256k1,
:bls
],
supported_hashes: [
:sha256,
Expand Down
4 changes: 2 additions & 2 deletions lib/archethic/bootstrap.ex
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ defmodule Archethic.Bootstrap do
)

{:ok, _ip, _p2p_port, _http_port, _transport, last_reward_address, _origin_public_key,
_key_certificate} = Node.decode_transaction_content(content)
_key_certificate, _mining_public_key} = Node.decode_transaction_content(content)

update_node(
ip,
Expand Down Expand Up @@ -286,7 +286,7 @@ defmodule Archethic.Bootstrap do
TransactionChain.fetch_transaction(last_address, closest_bootstrapping_nodes)

{:ok, _ip, _p2p_port, _http_port, _transport, last_reward_address, _origin_public_key,
_key_certificate} = Node.decode_transaction_content(content)
_key_certificate, _mining_public_key} = Node.decode_transaction_content(content)

last_reward_address
else
Expand Down
4 changes: 3 additions & 1 deletion lib/archethic/bootstrap/transaction_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ defmodule Archethic.Bootstrap.TransactionHandler do
when is_number(port) and port >= 0 and is_binary(reward_address) do
origin_public_key = Crypto.origin_node_public_key()
origin_public_key_certificate = Crypto.get_key_certificate(origin_public_key)
mining_public_key = Crypto.mining_node_public_key()

Transaction.new(:node, %TransactionData{
code: """
Expand All @@ -100,7 +101,8 @@ defmodule Archethic.Bootstrap.TransactionHandler do
transport,
reward_address,
origin_public_key,
origin_public_key_certificate
origin_public_key_certificate,
mining_public_key
)
})
end
Expand Down
70 changes: 65 additions & 5 deletions lib/archethic/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ defmodule Archethic.Crypto do
@typedoc """
List of the supported elliptic curves
"""
@type supported_curve :: :ed25519 | ECDSA.curve()
@type supported_curve :: :ed25519 | ECDSA.curve() | :bls

@typedoc """
List of the supported key origins
Expand Down Expand Up @@ -392,6 +392,17 @@ defmodule Archethic.Crypto do
|> ID.prepend_keypair(:ed25519, origin)
end

defp do_generate_deterministic_keypair(:bls, origin, seed) do
private_key = :crypto.hash(:sha512, seed)

keypair = {
BlsEx.get_public_key(private_key),
private_key
}

ID.prepend_keypair(keypair, :bls, origin)
end

defp do_generate_deterministic_keypair(curve, origin, seed) do
curve
|> ECDSA.generate_keypair(seed)
Expand Down Expand Up @@ -431,6 +442,7 @@ defmodule Archethic.Crypto do
end

defp do_sign(:ed25519, data, key), do: Ed25519.sign(key, data)
defp do_sign(:bls, data, key), do: BlsEx.sign(key, data)
defp do_sign(curve, data, key), do: ECDSA.sign(curve, key, data)

@doc """
Expand Down Expand Up @@ -564,6 +576,7 @@ defmodule Archethic.Crypto do
end

defp do_verify?(:ed25519, key, data, sig), do: Ed25519.verify?(key, data, sig)
defp do_verify?(:bls, key, data, sig), do: BlsEx.verify_signature(key, data, sig)
defp do_verify?(curve, key, data, sig), do: ECDSA.verify?(curve, key, data, sig)

@doc """
Expand Down Expand Up @@ -1007,6 +1020,11 @@ defmodule Archethic.Crypto do
|> ID.prepend_curve(curve_type)
end

@type key_size :: ed25519_key_size | ecdsa_key_size | bls_key_size
@type ed25519_key_size :: 32
@type ecdsa_key_size :: 65
@type bls_key_size :: 48

@doc """
Return the size of key using the curve id

Expand All @@ -1021,18 +1039,20 @@ defmodule Archethic.Crypto do
iex> Crypto.key_size(ID.from_curve(:secp256k1))
65
"""
@spec key_size(curve_id :: 0 | 1 | 2) :: 32 | 65
@spec key_size(curve_id :: 0 | 1 | 2 | 3) :: key_size()
def key_size(0), do: 32
def key_size(1), do: 65
def key_size(2), do: 65
def key_size(3), do: 48

@doc """
Determine if a public key is valid
"""
@spec valid_public_key?(binary()) :: boolean()
def valid_public_key?(<<0::8, _::8, _::binary-size(32)>>), do: true
def valid_public_key?(<<1::8, _::8, _::binary-size(65)>>), do: true
def valid_public_key?(<<2::8, _::8, _::binary-size(65)>>), do: true
def valid_public_key?(<<curve::8, _::8, public_key::binary>>) when curve in [0, 1, 2, 3] do
byte_size(public_key) == key_size(curve)
end

def valid_public_key?(_), do: false

@doc """
Expand Down Expand Up @@ -1392,4 +1412,44 @@ defmodule Archethic.Crypto do
def list_supported_hash_functions(), do: @supported_hashes
@string_hashes Enum.map(@supported_hashes, &Atom.to_string/1)
def list_supported_hash_functions(:string), do: @string_hashes

@doc """
Retrieve the node's mining public key
"""
@spec mining_node_public_key() :: key()
defdelegate mining_node_public_key, to: NodeKeystore, as: :mining_public_key

@doc """
Sign a message using the node's mining key
"""
@spec sign_with_mining_node_key(data :: iodata()) :: signature :: binary()
def sign_with_mining_node_key(data) do
data
|> Utils.wrap_binary()
|> NodeKeystore.sign_with_mining_key()
end

@doc """
Aggregate a list of BLS signatures with the associated public keys

The signatures and public keys order must be the same
"""
@spec aggregate_signatures(signatures :: list(binary()), public_keys :: list(key())) :: binary()
def aggregate_signatures(signatures, public_keys) do
BlsEx.aggregate_signatures(
signatures,
Enum.map(public_keys, fn <<_::8, _::8, public_key::binary>> -> public_key end)
)
end

@doc """
Aggregate a list of mining BLS public keys into a single one
"""
@spec aggregate_mining_public_keys(list(key())) :: key()
def aggregate_mining_public_keys(public_keys) do
public_keys
|> Enum.map(fn <<_::8, _::8, public_key::binary>> -> public_key end)
|> BlsEx.aggregate_public_keys()
|> ID.prepend_key(:bls)
end
end
2 changes: 2 additions & 0 deletions lib/archethic/crypto/id.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Archethic.Crypto.ID do
def from_curve(:ed25519), do: 0
def from_curve(:secp256r1), do: 1
def from_curve(:secp256k1), do: 2
def from_curve(:bls), do: 3

@doc """
Get a curve name from an curve ID
Expand All @@ -34,6 +35,7 @@ defmodule Archethic.Crypto.ID do
def to_curve(0), do: :ed25519
def to_curve(1), do: :secp256r1
def to_curve(2), do: :secp256k1
def to_curve(3), do: :bls

@doc """
Get an identification from an hash algorithm
Expand Down
3 changes: 3 additions & 0 deletions lib/archethic/crypto/keystore/node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ defmodule Archethic.Crypto.NodeKeystore do

@spec sign_with_origin_key(iodata()) :: binary()
defdelegate sign_with_origin_key(data), to: Origin

@callback sign_with_mining_key(iodata()) :: binary()
@callback mining_public_key() :: binary()
end
117 changes: 62 additions & 55 deletions lib/archethic/crypto/keystore/node/software_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,32 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
@impl NodeKeystore
@spec first_public_key() :: Crypto.key()
def first_public_key do
public_key_fun = get_public_key_fun()
public_key_fun.(0)
{pub, _} = Crypto.derive_keypair(get_node_seed(), 0)
pub
end

@impl NodeKeystore
@spec last_public_key() :: Crypto.key()
def last_public_key do
index = get_last_key_index()
public_key_fun = get_public_key_fun()
public_key_fun.(index)
{pub, _} = Crypto.derive_keypair(get_node_seed(), index)
pub
end

@impl NodeKeystore
@spec next_public_key() :: Crypto.key()
def next_public_key do
index = get_next_key_index()
public_key_fun = get_public_key_fun()
public_key_fun.(index)
{pub, _} = Crypto.derive_keypair(get_node_seed(), index)
pub
end

@impl NodeKeystore
@spec previous_public_key() :: Crypto.key()
def previous_public_key do
index = get_previous_key_index()
public_key_fun = get_public_key_fun()
public_key_fun.(index)
{pub, _} = Crypto.derive_keypair(get_node_seed(), index)
pub
end

@impl NodeKeystore
Expand All @@ -62,39 +62,53 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
@impl NodeKeystore
@spec sign_with_first_key(iodata()) :: binary()
def sign_with_first_key(data) do
sign_fun = get_sign_fun()
sign_fun.(data, 0)
{_, pv} = Crypto.derive_keypair(get_node_seed(), 0)
Crypto.sign(data, pv)
end

@impl NodeKeystore
@spec sign_with_last_key(iodata()) :: binary()
def sign_with_last_key(data) do
index = get_last_key_index()
sign_fun = get_sign_fun()
sign_fun.(data, index)
{_, pv} = Crypto.derive_keypair(get_node_seed(), index)
Crypto.sign(data, pv)
end

@impl NodeKeystore
@spec sign_with_previous_key(iodata()) :: binary()
def sign_with_previous_key(data) do
index = get_previous_key_index()
sign_fun = get_sign_fun()
sign_fun.(data, index)
{_, pv} = Crypto.derive_keypair(get_node_seed(), index)
Crypto.sign(data, pv)
end

@impl NodeKeystore
@spec diffie_hellman_with_first_key(Crypto.key()) :: binary()
def diffie_hellman_with_first_key(public_key) do
dh_fun = get_diffie_helmann_fun()
dh_fun.(public_key, 0)
{_, pv} = Crypto.derive_keypair(get_node_seed(), 0)
do_diffie_helmann(pv, public_key)
end

@impl NodeKeystore
@spec diffie_hellman_with_last_key(Crypto.key()) :: binary()
def diffie_hellman_with_last_key(public_key) do
index = get_last_key_index()
dh_fun = get_diffie_helmann_fun()
dh_fun.(public_key, index)
{_, pv} = Crypto.derive_keypair(get_node_seed(), index)
do_diffie_helmann(pv, public_key)
end

@impl NodeKeystore
@spec sign_with_mining_key(iodata()) :: binary()
def sign_with_mining_key(data) do
{_, pv} = Crypto.generate_deterministic_keypair(get_node_seed(), :bls)
Crypto.sign(data, pv)
end

@impl NodeKeystore
@spec mining_public_key() :: binary()
def mining_public_key do
{pub, _} = Crypto.generate_deterministic_keypair(get_node_seed(), :bls)
pub
end

defp get_last_key_index do
Expand All @@ -112,19 +126,9 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
index
end

defp get_sign_fun do
[{_, fun}] = :ets.lookup(@keystore_table, :sign_fun)
fun
end

defp get_public_key_fun do
[{_, fun}] = :ets.lookup(@keystore_table, :public_key_fun)
fun
end

defp get_diffie_helmann_fun do
[{_, fun}] = :ets.lookup(@keystore_table, :dh_fun)
fun
defp get_node_seed do
[{_, node_seed}] = :ets.lookup(@keystore_table, :node_seed)
node_seed
end

defp do_diffie_helmann(<<curve_id::8, _origin_id::8, raw_pv::binary>>, public_key) do
Expand All @@ -141,27 +145,9 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
@impl GenServer
def init(_arg \\ []) do
:ets.new(@keystore_table, [:set, :named_table, :protected, read_concurrency: true])
node_seed = Origin.retrieve_node_seed()

# Use anynomous functions to hide the node seed from the processes or tables
sign_fun = fn data, index ->
{_, pv} = Crypto.derive_keypair(node_seed, index)
Crypto.sign(data, pv)
end

public_key_fun = fn index ->
{pub, _} = Crypto.derive_keypair(node_seed, index)
pub
end

dh_fun = fn public_key, index ->
{_, pv} = Crypto.derive_keypair(node_seed, index)
do_diffie_helmann(pv, public_key)
end

:ets.insert(@keystore_table, {:sign_fun, sign_fun})
:ets.insert(@keystore_table, {:public_key_fun, public_key_fun})
:ets.insert(@keystore_table, {:dh_fun, dh_fun})
node_seed = Origin.retrieve_node_seed()
:ets.insert(@keystore_table, {:node_seed, node_seed})

unless File.exists?(Utils.mut_dir("crypto")) do
File.mkdir_p!(Utils.mut_dir("crypto"))
Expand Down Expand Up @@ -226,11 +212,14 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
:ets.insert(@keystore_table, {:previous_index, index + 1})
:ets.insert(@keystore_table, {:next_index, index + 2})

public_key_fun = get_public_key_fun()
node_seed = get_node_seed()
{next_pub, _} = Crypto.derive_keypair(node_seed, index + 2)
{previous_pub, _} = Crypto.derive_keypair(node_seed, index + 1)
{last_pub, _} = Crypto.derive_keypair(node_seed, index)

Logger.info("Next public key will be #{Base.encode16(public_key_fun.(index + 2))}")
Logger.info("Previous public key will be #{Base.encode16(public_key_fun.(index + 1))}")
Logger.info("Publication/Last public key will be #{Base.encode16(public_key_fun.(index))}")
Logger.info("Next public key will be #{Base.encode16(next_pub)}")
Logger.info("Previous public key will be #{Base.encode16(previous_pub)}")
Logger.info("Publication/Last public key will be #{Base.encode16(last_pub)}")

write_index(index + 1)
{:reply, :ok, state}
Expand All @@ -241,4 +230,22 @@ defmodule Archethic.Crypto.NodeKeystore.SoftwareImpl do
store_node_key_indexes(index)
{:reply, :ok, state}
end

@impl GenServer
# FIXME: use genserver message because ets table is protected
def handle_cast(:migrate_1_5_6, state) do
node_seed = Origin.retrieve_node_seed()
:ets.insert(@keystore_table, {:node_seed, node_seed})
:ets.delete(@keystore_table, :sign_fun)
:ets.delete(@keystore_table, :public_key_fun)
:ets.delete(@keystore_table, :dh_fun)

{:noreply, state}
end

# FIXME: to remove after 1.5.6
@doc false
def migrate_ets_table_1_5_6 do
GenServer.cast(__MODULE__, :migrate_1_5_6)
end
end
Loading
Loading