Cartouche.Signer is a GenServer which can sign messages. The runtime carrier
is a {backend_module, config} pair implementing Cartouche.Signer.Backend
(for instance Cartouche.Signer.Curvy with a local key, or
Cartouche.Signer.CloudKMS with GCP Cloud KMS coordinates). A legacy
{module, function, args} MFA is also accepted so existing call sites
(Cartouche.Signer.sign_direct/4, and start_link/1 handed a 3-tuple)
keep working. In either case, start the GenServer and call
Cartouche.Signer.sign(MySigner, "message") to get a 65-byte Ethereum
signature.
This library never emits a 65-byte Ethereum signature with s > n/2
(EIP-2). Low-s canonicalization is applied at the emission funnel, not by
the configured backend: both the {backend, config} path and the legacy
MFA path pass through Cartouche.Recover.normalize_low_s/1 before the
recovery-bit search and EIP-155 packing. The MFA carrier is kept because
sign_direct/4 is the production signing route in the downstream onchain
repo; migrating that call site is a separate task. It cannot bypass the
invariant.
Note: we also enforce that a given signer process knows its public key, such that we can verify signatures recovery bits. That is, since CloudKMS and other signing tools don't return a recovery bit, necessary for Ethereum, we test all 4 possible bits to make sure a signature recovers to the correct signer address, but we need to know what that address should be to accomplish this task.
Additionally, chain_id is used to return EIP-155 compliant signatures.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
sign_direct | 4 | Sign a message directly with a signing MFA and known signer address. | message: value, address: value, signer_mfa: value, chain_id_or_name: value |
chain_id | 1 | Get the chain id configured for a signer process. | name: value |
address | 1 | Get the Ethereum address controlled by a signer process. | name: value |
sign | 3 | Sign a message with a running signer process. | message: value, name: value, opts: value |
start_link | 1 | Start a signer process backed by the provided signer backend carrier. | signer_options: value |
child_spec | 1 | Build the supervisor child specification for a signer process. | init_arg: value |
Summary
Functions
Gets the address for this signer.
Gets the chain id for this signer.
Returns a specification to start this module under a supervisor.
Signs a message using this signing key.
Directly sign a message, not using a signer process.
Starts a new Cartouche.Signer process.
Functions
@spec address(GenServer.server()) :: Cartouche.address()
Gets the address for this signer.
Examples
iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> Cartouche.Signer.address(signer_proc) |> Cartouche.Hex.to_address()
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"
@spec chain_id(GenServer.server()) :: integer()
Gets the chain id for this signer.
Examples
iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> Cartouche.Signer.chain_id(signer_proc)
5
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec sign(String.t(), GenServer.server(), Keyword.t()) :: {:ok, binary()} | {:error, term()}
Signs a message using this signing key.
Examples
iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> {:ok, sig} = Cartouche.Signer.sign("test", signer_proc)
iex> Cartouche.Recover.recover_eth("test", sig)
...> |> Cartouche.Hex.to_address()
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"
iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> {:ok, <<_r::256, _s::256, v::binary>>} = Cartouche.Signer.sign("test", signer_proc, chain_id: 0x05f5e0ff)
iex> :binary.decode_unsigned(v)
0x05f5e0ff * 2 + 35 + 1
@spec sign_direct( String.t(), binary(), {module(), atom(), [any()]}, integer() | atom() | nil ) :: {:ok, binary()} | {:error, String.t()}
Directly sign a message, not using a signer process.
This is mostly used internally, but can be used safely externally as well. The returned 65-byte signature is always low-s (EIP-2), regardless of whether the MFA backend normalized.
@spec start_link( mfa: Cartouche.Signer.Backend.t() | {module(), atom(), [any()]}, name: GenServer.name() | nil ) :: GenServer.on_start()
Starts a new Cartouche.Signer process.