Summary
SingleOwnerAccount::new accepts a signer and an address without verifying that the signer is actually authorized for that account.
This is not a protocol vulnerability—the account contract still rejects invalid signatures during transaction validation—but it can lead to a client-side footgun where an application treats account.address() as an address it can control, even though the configured signer cannot authorize transactions for that account.
Current behavior
SingleOwnerAccount::new simply stores the provided fields:
pub const fn new(
provider: P,
signer: S,
address: Felt,
chain_id: Felt,
encoding: ExecutionEncoding,
) -> Self {
Self {
provider,
signer,
address,
chain_id,
block_id: BlockId::Tag(BlockTag::PreConfirmed),
encoding,
}
}
No relationship between the signer and account address is validated.
Why this matters
A mismatched signer/address pair is only discovered when the first transaction is submitted and rejected by the account contract.
However, many applications expose account.address() as a receive address immediately after construction. If that address does not correspond to the supplied signer, users may receive funds into an account they cannot operate.
For example:
- Construct a
SingleOwnerAccount with the wrong signer.
- Publish
account.address() as the deposit address.
- Funds are sent to that address.
- The first transaction fails because the signer is not authorized.
At that point, the caller has no way to spend those funds unless they possess the actual signing key for that account.
Why not verify in new
Performing verification during construction would introduce several problems:
- Counterfactual accounts cannot be verified before deployment.
new is currently synchronous, infallible, and const; verification would require asynchronous RPC calls or signing operations.
- Interactive signers (e.g. hardware wallets) would require user confirmation during construction.
Because of these constraints, automatic verification inside new does not seem practical.
Possible approaches
Some possibilities that might improve safety without breaking existing behavior:
- Add an opt-in
verify_signer() helper that validates the signer against the deployed account (e.g. via is_valid_signature).
- Provide a
new_verified(...) -> Result<Self, _> constructor alongside the existing new.
- Add explicit documentation to
new and/or address() clarifying that the signer/address relationship is not validated.
- Document that accounts derived through
AccountFactory are not affected since the address is deterministically derived from the signing key.
Questions
- Is this considered a client-side safety issue worth addressing?
- If so, would an explicit verification API be preferable to additional documentation?
- Are there other approaches that fit the library's design better?
Summary
SingleOwnerAccount::newaccepts asignerand anaddresswithout verifying that the signer is actually authorized for that account.This is not a protocol vulnerability—the account contract still rejects invalid signatures during transaction validation—but it can lead to a client-side footgun where an application treats
account.address()as an address it can control, even though the configured signer cannot authorize transactions for that account.Current behavior
SingleOwnerAccount::newsimply stores the provided fields:No relationship between the signer and account address is validated.
Why this matters
A mismatched signer/address pair is only discovered when the first transaction is submitted and rejected by the account contract.
However, many applications expose
account.address()as a receive address immediately after construction. If that address does not correspond to the supplied signer, users may receive funds into an account they cannot operate.For example:
SingleOwnerAccountwith the wrong signer.account.address()as the deposit address.At that point, the caller has no way to spend those funds unless they possess the actual signing key for that account.
Why not verify in
newPerforming verification during construction would introduce several problems:
newis currently synchronous, infallible, andconst; verification would require asynchronous RPC calls or signing operations.Because of these constraints, automatic verification inside
newdoes not seem practical.Possible approaches
Some possibilities that might improve safety without breaking existing behavior:
verify_signer()helper that validates the signer against the deployed account (e.g. viais_valid_signature).new_verified(...) -> Result<Self, _>constructor alongside the existingnew.newand/oraddress()clarifying that the signer/address relationship is not validated.AccountFactoryare not affected since the address is deterministically derived from the signing key.Questions