Solidity Account Abstraction Concepts
Account abstraction is the idea that a smart contract account validates its own operations. The account itself checks a signature, instead of a private key alone controlling the account.
An owner signature validates
The account owner signs an operation hash. The validate function returns true for that signature.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleAccount {
address public owner;
uint256 public value;
constructor(address owner_) {
owner = owner_;
}
function prefixed(bytes32 digest) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
}
function validate(bytes32 opHash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
return ecrecover(prefixed(opHash), v, r, s) == owner;
}
function execute(bytes32 opHash, uint256 newValue, uint8 v, bytes32 r, bytes32 s) public {
require(validate(opHash, v, r, s), "bad owner signature");
value = newValue;
}
}A stranger signature does not validate
Validation compares the recovered signer with the owner. A stranger signature recovers a different address and fails.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleAccount {
address public owner;
uint256 public value;
constructor(address owner_) {
owner = owner_;
}
function prefixed(bytes32 digest) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
}
function validate(bytes32 opHash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
return ecrecover(prefixed(opHash), v, r, s) == owner;
}
function execute(bytes32 opHash, uint256 newValue, uint8 v, bytes32 r, bytes32 s) public {
require(validate(opHash, v, r, s), "bad owner signature");
value = newValue;
}
}Only a validated operation executes
Execute runs the validate check first. A valid owner operation changes the value, and a stranger operation reverts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleAccount {
address public owner;
uint256 public value;
constructor(address owner_) {
owner = owner_;
}
function prefixed(bytes32 digest) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
}
function validate(bytes32 opHash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
return ecrecover(prefixed(opHash), v, r, s) == owner;
}
function execute(bytes32 opHash, uint256 newValue, uint8 v, bytes32 r, bytes32 s) public {
require(validate(opHash, v, r, s), "bad owner signature");
value = newValue;
}
}Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleAccount {
address public owner;
uint256 public value;
constructor(address owner_) {
owner = owner_;
}
function prefixed(bytes32 digest) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
}
function validate(bytes32 opHash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
return ecrecover(prefixed(opHash), v, r, s) == owner;
}
function execute(bytes32 opHash, uint256 newValue, uint8 v, bytes32 r, bytes32 s) public {
require(validate(opHash, v, r, s), "bad owner signature");
value = newValue;
}
}Key points
- Smart contract account: an account whose rules live in contract code, not in a single private key.
- Validate: the account function that checks whether an operation is allowed for its owner.
- Operation: a single requested action, named here by its hash, that the account may carry out.
- Use account validation to let the account accept an owner signature and reject everyone else.
- Entry point: the system contract that routes operations to accounts, described here in prose only.
In this example, the account checks an owner signature for an operation, the check passes, and the account then sets its value to 77.
Note
full account abstraction is out of scope for this course, and this lesson builds only a tiny validating account.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Remix IDE
Exercise
A smart contract account receives an operation signed by a stranger. What does its validate function return?