-
Notifications
You must be signed in to change notification settings - Fork 4
feat: ID-4134: Support Bootstrap Flow for Wallet Initial Transaction #74
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
7716f65
7aeb11b
ed644cd
caa0c72
f45f41d
2340e7f
0b4b46a
277c76d
8754f44
8fbbef3
ebe0a91
ba4e8e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,3 +30,5 @@ scripts/*_output*.json | |
| .env.devnet | ||
| .env.testnet | ||
| .env.mainnet | ||
|
|
||
| lib/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,19 +4,155 @@ pragma solidity 0.8.17; | |
|
|
||
| import "./ModuleAuthUpgradable.sol"; | ||
| import "./ImageHashKey.sol"; | ||
| import "./ModuleStorage.sol"; | ||
| import "./NonceKey.sol"; | ||
| import "../../Wallet.sol"; | ||
|
|
||
| import "../../utils/LibBytes.sol"; | ||
|
|
||
| abstract contract ModuleAuthDynamic is ModuleAuthUpgradable { | ||
| using LibBytes for bytes; | ||
|
|
||
| bytes32 public immutable INIT_CODE_HASH; | ||
| address public immutable FACTORY; | ||
| address public immutable IMMUTABLE_SIGNER_CONTRACT; | ||
|
|
||
| constructor(address _factory, address _startupWalletImpl) { | ||
| constructor(address _factory, address _startupWalletImpl, address _immutableSignerContract) { | ||
| // Build init code hash of the deployed wallets using that module | ||
| bytes32 initCodeHash = keccak256(abi.encodePacked(Wallet.creationCode, uint256(uint160(_startupWalletImpl)))); | ||
|
|
||
| INIT_CODE_HASH = initCodeHash; | ||
| FACTORY = _factory; | ||
| IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract; | ||
naveen-imtb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * @notice Verify if signer is default wallet owner | ||
| * @param _hash Hashed signed message | ||
| * @param _signature Array of signatures with signers ordered | ||
| * like the the keys in the multisig configs | ||
| * | ||
| * @dev The signature must be solidity packed and contain the total number of owners, | ||
| * the threshold, the weight and either the address or a signature for each owner. | ||
| * | ||
| * Each weight & (address or signature) pair is prefixed by a flag that signals if such pair | ||
| * contains an address or a signature. The aggregated weight of the signatures must surpass the threshold. | ||
| * | ||
| * Flag types: | ||
| * 0x00 - Signature | ||
| * 0x01 - Address | ||
| * | ||
| * E.g: | ||
| * abi.encodePacked( | ||
| * uint16 threshold, | ||
| * uint8 01, uint8 weight_1, address signer_1, | ||
| * uint8 00, uint8 weight_2, bytes signature_2, | ||
| * ... | ||
| * uint8 01, uint8 weight_5, address signer_5 | ||
| * ) | ||
| */ | ||
| function _signatureValidation( | ||
naveen-imtb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| bytes32 _hash, | ||
| bytes memory _signature | ||
| ) | ||
| internal virtual override returns (bool) | ||
| { | ||
| (bool verified, bool needsUpdate, bytes32 imageHash) = _signatureValidationWithUpdateCheck(_hash, _signature); | ||
| if (needsUpdate) { | ||
| updateImageHashInternal(imageHash); | ||
| } | ||
| return verified; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Verify signature and determine if image hash needs updating | ||
| * @param _hash Hashed signed message | ||
| * @param _signature Packed signature data containing threshold, flags, weights, and addresses/signatures | ||
| * @return verified True if the signature is valid and weight threshold is met | ||
| * @return needsUpdate True if the image hash needs to be stored (first transaction) | ||
| * @return imageHash The computed image hash from the signature | ||
| * | ||
| * @dev This function parses the signature, recovers/reads signer addresses, and validates them. | ||
| * For defensive validation, each extracted address is compared against IMMUTABLE_SIGNER_CONTRACT. | ||
| * If a match is found, it is recorded. | ||
| * | ||
| * Special case: If this is the first transaction (nonce == 0) and the immutable signer contract | ||
| * is one of the signers, the signature is automatically validated and approved without checking | ||
| * the stored image hash. This allows the immutable signer to bootstrap the wallet on first use. | ||
| */ | ||
| function _signatureValidationWithUpdateCheck( | ||
| bytes32 _hash, | ||
| bytes memory _signature | ||
| ) | ||
| internal view override returns (bool, bool, bytes32) | ||
| { | ||
| ( | ||
| uint16 threshold, // required threshold signature | ||
| uint256 rindex // read index | ||
| ) = _signature.readFirstUint16(); | ||
|
|
||
| // Start image hash generation | ||
| bytes32 imageHash = bytes32(uint256(threshold)); | ||
|
|
||
| // Acumulated weight of signatures | ||
| uint256 totalWeight; | ||
|
|
||
| // Track if immutable signer contract is one of the signers | ||
| bool immutableSignerContractFound = false; | ||
|
|
||
| // Iterate until the image is completed | ||
| while (rindex < _signature.length) { | ||
| // Read next item type and addrWeight | ||
| uint256 flag; uint256 addrWeight; address addr; | ||
| (flag, addrWeight, rindex) = _signature.readUint8Uint8(rindex); | ||
|
|
||
| if (flag == FLAG_ADDRESS) { | ||
| // Read plain address | ||
|
||
| (addr, rindex) = _signature.readAddress(rindex); | ||
| } else if (flag == FLAG_SIGNATURE) { | ||
| // Read single signature and recover signer | ||
| bytes memory signature; | ||
| (signature, rindex) = _signature.readBytes66(rindex); | ||
| addr = recoverSigner(_hash, signature); | ||
|
|
||
| // Acumulate total weight of the signature | ||
| totalWeight += addrWeight; | ||
| } else if (flag == FLAG_DYNAMIC_SIGNATURE) { | ||
| // Read signer | ||
| (addr, rindex) = _signature.readAddress(rindex); | ||
|
|
||
| // Read signature size | ||
| uint256 size; | ||
| (size, rindex) = _signature.readUint16(rindex); | ||
|
|
||
| // Read dynamic size signature | ||
| bytes memory signature; | ||
| (signature, rindex) = _signature.readBytes(rindex, size); | ||
| require(isValidSignature(_hash, addr, signature), "ModuleAuthDynamic#_signatureValidation: INVALID_SIGNATURE"); | ||
|
|
||
| // Acumulate total weight of the signature | ||
| totalWeight += addrWeight; | ||
| } else { | ||
| revert("ModuleAuthDynamic#_signatureValidation INVALID_FLAG"); | ||
| } | ||
|
|
||
| // Defensive check: compare extracted address with target address | ||
| if (IMMUTABLE_SIGNER_CONTRACT != address(0) && addr == IMMUTABLE_SIGNER_CONTRACT) { | ||
naveen-imtb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| immutableSignerContractFound = true; | ||
| } | ||
|
|
||
| // Write weight and address to image | ||
| imageHash = keccak256(abi.encode(imageHash, addrWeight, addr)); | ||
| } | ||
|
|
||
| // Check if this is the first transaction (nonce == 0) and immutable signer contract is one of the signers | ||
| uint256 currentNonce = uint256(ModuleStorage.readBytes32Map(NonceKey.NONCE_KEY, bytes32(uint256(0)))); | ||
| if (currentNonce == 0 && immutableSignerContractFound) { | ||
| return (true, true, imageHash); | ||
| } | ||
naveen-imtb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| (bool verified, bool needsUpdate) = _isValidImage(imageHash); | ||
| return ((totalWeight >= threshold && verified), needsUpdate, imageHash); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@drinkcoffee I tweaked this settling to resolve a "stack too deep" error. Not sure if there's any unintended side effects that I'm not aware of. Pls advise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@drinkcoffee I ended up changing this because it caused issues with the existing tests where revert messages were validated. I had to introduce an inline struct to reduce the number of variables on the stack.