defmodule Stellar.TxBuild.SorobanAuthorizationEntry do
@moduledoc """
`SorobanAuthorizationEntry` struct definition.
"""
alias StellarBase.XDR.HashIDPreimage, as: HashIDPreimageXDR
alias StellarBase.XDR.HashIDPreimageSorobanAuthorization,
as: HashIDPreimageSorobanAuthorizationXDR
alias StellarBase.XDR.SorobanAddressCredentials, as: SorobanAddressCredentialsXDR
alias StellarBase.XDR.SorobanAuthorizedInvocation, as: SorobanAuthorizedInvocationXDR
alias StellarBase.XDR.{EnvelopeType, Int64, SorobanAuthorizationEntry, UInt32}
alias Stellar.{KeyPair, Network}
alias Stellar.TxBuild.{
HashIDPreimage,
HashIDPreimageSorobanAuthorization,
SCMapEntry,
SCVal,
SorobanAddressCredentials,
SorobanCredentials,
SorobanAuthorizedInvocation
}
@behaviour Stellar.TxBuild.XDR
@type error :: {:error, atom()}
@type validation :: {:ok, any()} | error()
@type base_64 :: String.t()
@type secret_key :: String.t()
@type sign_authorization :: String.t()
@type latest_ledger :: non_neg_integer()
@type network_passphrase :: String.t()
@type credentials :: SorobanCredentials.t()
@type root_invocation :: SorobanAuthorizedInvocation.t()
@type t :: %__MODULE__{
credentials: credentials(),
root_invocation: root_invocation()
}
defstruct [:credentials, :root_invocation]
@impl true
def new(value, opts \\ [])
def new(args, _opts) when is_list(args) do
credentials = Keyword.get(args, :credentials)
root_invocation = Keyword.get(args, :root_invocation)
with {:ok, credentials} <- validate_credentials(credentials),
{:ok, root_invocation} <- validate_root_invocation(root_invocation) do
%__MODULE__{
credentials: credentials,
root_invocation: root_invocation
}
end
end
def new(_value, _opts), do: {:error, :invalid_auth_entry_args}
@impl true
def to_xdr(%__MODULE__{
credentials: credentials,
root_invocation: root_invocation
}) do
root_invocation = SorobanAuthorizedInvocation.to_xdr(root_invocation)
credentials
|> SorobanCredentials.to_xdr()
|> SorobanAuthorizationEntry.new(root_invocation)
end
def to_xdr(_struct), do: {:error, :invalid_struct}
@spec sign(
credentials :: t(),
secret_key :: secret_key(),
network_passphrase :: network_passphrase()
) :: t() | error()
def sign(
%__MODULE__{
credentials: %SorobanCredentials{
value:
%SorobanAddressCredentials{
nonce: nonce,
signature_expiration_ledger: signature_expiration_ledger,
signature: %SCVal{value: signature_args}
} = soroban_address_credentials
},
root_invocation: root_invocation
} = credentials,
secret_key,
network_passphrase
)
when is_binary(secret_key) do
{public_key, _secret_key} = KeyPair.from_secret_seed(secret_key)
raw_public_key = KeyPair.raw_public_key(public_key)
network_id = Network.network_id(network_passphrase)
signature =
[
network_id: network_id,
nonce: nonce,
signature_expiration_ledger: signature_expiration_ledger + 3,
invocation: root_invocation
]
|> HashIDPreimageSorobanAuthorization.new()
|> (&HashIDPreimage.new(soroban_auth: &1)).()
|> HashIDPreimage.to_xdr()
|> HashIDPreimageXDR.encode_xdr!()
|> hash()
|> KeyPair.sign(secret_key)
public_key_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "public_key"),
SCVal.new(bytes: raw_public_key)
)
signature_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "signature"),
SCVal.new(bytes: signature)
)
signature_sc_val = SCVal.new(map: [public_key_map_entry, signature_map_entry])
soroban_address_credentials = %{
soroban_address_credentials
| signature: SCVal.new(vec: signature_args ++ [signature_sc_val])
}
%{credentials | credentials: soroban_address_credentials}
end
def sign(_args, _secret_key, _network_passphrase), do: {:error, :invalid_sign_args}
@spec sign_xdr(
base_64 :: base_64(),
secret_key :: secret_key(),
latest_ledger :: latest_ledger(),
network_passphrase :: network_passphrase()
) :: sign_authorization() | error()
def sign_xdr(base_64, secret_key, latest_ledger, network_passphrase)
when is_binary(base_64) and is_binary(secret_key) and is_integer(latest_ledger) and
is_binary(network_passphrase) do
{%SorobanAuthorizationEntry{
credentials:
%{
value:
%SorobanAddressCredentialsXDR{
nonce: nonce
} = soroban_address_credentials
} = credentials,
root_invocation: root_invocation
} = soroban_auth,
""} =
base_64
|> Base.decode64!()
|> SorobanAuthorizationEntry.decode_xdr!()
signature_expiration_ledger = UInt32.new(latest_ledger + 3)
signature =
nonce
|> build_signature_from_xdr(
signature_expiration_ledger,
root_invocation,
secret_key,
network_passphrase
)
|> (&SCVal.new(vec: [&1])).()
|> SCVal.to_xdr()
soroban_address_credentials = %{
soroban_address_credentials
| signature: signature,
signature_expiration_ledger: signature_expiration_ledger
}
credentials = %{credentials | value: soroban_address_credentials}
%{soroban_auth | credentials: credentials}
|> SorobanAuthorizationEntry.encode_xdr!()
|> Base.encode64()
end
def sign_xdr(_base_64, _secret_key, _latest_ledger, _network_passphrase),
do: {:error, :invalid_sign_args}
@spec hash(data :: binary()) :: binary()
defp hash(data), do: :crypto.hash(:sha256, data)
@spec validate_credentials(credentials :: credentials()) :: validation()
defp validate_credentials(%SorobanCredentials{} = credentials), do: {:ok, credentials}
defp validate_credentials(_credentials), do: {:error, :invalid_credentials}
@spec validate_root_invocation(root_invocation :: root_invocation()) :: validation()
defp validate_root_invocation(%SorobanAuthorizedInvocation{} = root_invocation),
do: {:ok, root_invocation}
defp validate_root_invocation(_root_invocation), do: {:error, :invalid_root_invocation}
@spec build_signature_from_xdr(
nonce :: Int64.t(),
signature_expiration_ledger :: UInt32.t(),
root_invocation :: SorobanAuthorizedInvocationXDR.t(),
secret_key :: secret_key(),
network_passphrase :: network_passphrase()
) :: SCVal.t() | error()
defp build_signature_from_xdr(
nonce,
signature_expiration_ledger,
root_invocation,
secret_key,
network_passphrase
) do
{public_key, _secret_key} = KeyPair.from_secret_seed(secret_key)
raw_public_key = KeyPair.raw_public_key(public_key)
envelope_type = EnvelopeType.new(:ENVELOPE_TYPE_SOROBAN_AUTHORIZATION)
signature =
network_passphrase
|> Network.network_id_xdr()
|> HashIDPreimageSorobanAuthorizationXDR.new(
nonce,
signature_expiration_ledger,
root_invocation
)
|> HashIDPreimageXDR.new(envelope_type)
|> HashIDPreimageXDR.encode_xdr!()
|> hash()
|> KeyPair.sign(secret_key)
public_key_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "public_key"),
SCVal.new(bytes: raw_public_key)
)
signature_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "signature"),
SCVal.new(bytes: signature)
)
SCVal.new(map: [public_key_map_entry, signature_map_entry])
end
end