Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
💄 Reformat
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestognw committed Apr 20, 2024
1 parent c9b2794 commit 997213d
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 76 deletions.
91 changes: 68 additions & 23 deletions src/Plumaa.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ import {SafeManager} from "./base/SafeManager.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";

/// @title Plumaa - An RSA SHA256 PKCS1.5 enabler module for Safe{Wallet} Smart Accounts
///
/// It allows the Safe{Wallet} Smart Account to execute transactions signed with an RSA PKCS1.5 signature.
/// A notable example of RSA signatures in real-world applications are the government-issued digital certificates.
contract Plumaa is RSAOwnerManager, SafeManager, EIP712Upgradeable {
/// @notice A transaction signed with the Bytes32 owner's private key was executed
event ExecutedRSATransaction(address indexed safe, bytes32 indexed sha256Digest, uint32 nonce, bool success);
event ExecutedRSATransaction(
address indexed safe,
bytes32 indexed sha256Digest,
uint32 nonce,
bool success
);

/// @notice The provided message SHA256 digest doesn't match signature for exponent and modulus
error InvalidRSASignature(bytes32 digest, bytes signature, bytes exponent, bytes modulus);
error InvalidRSASignature(
bytes32 digest,
bytes signature,
bytes exponent,
bytes modulus
);

/// @dev The request `deadline` has expired.
error ExpiredRSATransaction(uint48 deadline);
Expand All @@ -34,9 +47,10 @@ contract Plumaa is RSAOwnerManager, SafeManager, EIP712Upgradeable {
bytes modulus;
}

bytes32 internal constant _TRANSACTION_REQUEST_TYPEHASH = keccak256(
"TransactionRequest(address to,uint256 value,uint8 operation,uint48 deadline,bytes data,uint32 nonce)"
);
bytes32 internal constant _TRANSACTION_REQUEST_TYPEHASH =
keccak256(
"TransactionRequest(address to,uint256 value,uint8 operation,uint48 deadline,bytes data,uint32 nonce)"
);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand All @@ -47,49 +61,69 @@ contract Plumaa is RSAOwnerManager, SafeManager, EIP712Upgradeable {
/// @param exponent The exponent of the RSA public key
/// @param modulus The modulus of the RSA public key
/// @param safe_ The address of the Safe{Wallet} Smart Account
function setupPlumaa(bytes memory exponent, bytes memory modulus, Safe safe_) public initializer {
function setupPlumaa(
bytes memory exponent,
bytes memory modulus,
Safe safe_
) public initializer {
__EIP712_init("RSAOwnerManager", "1");
__RSAOwnerManager_init(exponent, modulus);
__SafeManager_init(safe_);
}

/// @notice Executes a transaction from the associated Safe{Wallet} Smart Account
/// using an RSA PKCS1.5 signature
/// @notice Executes a transaction from the associated Safe{Wallet} Smart Account sing an RSA PKCS1.5 signature
/// @param request The transaction request
function executeTransaction(TransactionRequestData calldata request) public virtual returns (bool) {
function executeTransaction(
TransactionRequestData calldata request
) public virtual returns (bool) {
if (block.timestamp >= request.deadline) {
revert ExpiredRSATransaction(request.deadline);
}

uint32 currentNonce = _useOwnerNonce();

(bool valid, bytes32 sha256Digest) = verifyRSAOwnerTransactionRequest(request, currentNonce);
(bool valid, bytes32 sha256Digest) = verifyRSAOwnerTransactionRequest(
request,
currentNonce
);

if (!valid) {
revert InvalidRSASignature(sha256Digest, request.signature, request.exponent, request.modulus);
revert InvalidRSASignature(
sha256Digest,
request.signature,
request.exponent,
request.modulus
);
}

Safe _safe = safe();

bool success = _safe.execTransactionFromModule(request.to, request.value, request.data, request.operation);
bool success = _safe.execTransactionFromModule(
request.to,
request.value,
request.data,
request.operation
);

emit ExecutedRSATransaction(address(_safe), sha256Digest, currentNonce, success);
emit ExecutedRSATransaction(
address(_safe),
sha256Digest,
currentNonce,
success
);

return success;
}

/// @notice Checks if the SHA256 digest of a transaction request
/// {_hashTypedDataV4} value is signed by the RSA owner.
/// @notice Checks if the SHA256 digest of a transaction request {_hashTypedDataV4} value is signed by the RSA owner.
/// @param request The transaction request
/// @param currentNonce The nonce of the RSA owner
/// @return valid True if the transaction request is signed by the RSA owner
/// @return digest The transaction request digest
function verifyRSAOwnerTransactionRequest(TransactionRequestData calldata request, uint32 currentNonce)
public
view
virtual
returns (bool valid, bytes32 digest)
{
function verifyRSAOwnerTransactionRequest(
TransactionRequestData calldata request,
uint32 currentNonce
) public view virtual returns (bool valid, bytes32 digest) {
bytes32 typehash = _hashTypedDataV4(
keccak256(
abi.encode(
Expand All @@ -107,13 +141,24 @@ contract Plumaa is RSAOwnerManager, SafeManager, EIP712Upgradeable {
// Hashing again is required to be PKCS8 compliant
bytes32 sha256Digest = sha256(abi.encodePacked(typehash));

return (_verifyRSAOwner(sha256Digest, request.signature, request.exponent, request.modulus), sha256Digest);
return (
_verifyRSAOwner(
sha256Digest,
request.signature,
request.exponent,
request.modulus
),
sha256Digest
);
}

/// @notice Sets the RSA Owner
/// @param exponent The exponent of the RSA public key
/// @param modulus The modulus of the RSA public key
function setOwner(bytes memory exponent, bytes memory modulus) public onlySafe {
function setOwner(
bytes memory exponent,
bytes memory modulus
) public onlySafe {
_setOwner(exponent, modulus);
}
}
79 changes: 48 additions & 31 deletions src/base/RSAOwnerManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

pragma solidity ^0.8.20;

import {SelfAuthorized} from "@safe/contracts/common/SelfAuthorized.sol";
import {RsaVerify} from "../utils/RsaVerify.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

/**
* @title Modified version of Safe's OwnerManager for bytes32 that includes
* EIP7201 support.
* (https://github.com/safe-global/safe-contracts/blob/main/contracts/base/OwnerManager.sol)
* @title Modified version of Safe's OwnerManager for bytes32 that includes EIP7201 support.
* [Safe's implementation](https://github.com/safe-global/safe-contracts/blob/main/contracts/base/OwnerManager.sol)
*
* An RSA Owner is a public key identified by a `keccak(modulus, exponent)`.
* @notice This version uses bytes32 instead of address for owners
Expand All @@ -18,18 +16,25 @@ abstract contract RSAOwnerManager is Initializable {
using RsaVerify for bytes32;

/// @notice Emitted when the owner is changed.
event OwnershipTransferred(bytes32 indexed previousOwner, bytes32 indexed newOwner);
event OwnershipTransferred(
bytes32 indexed previousOwner,
bytes32 indexed newOwner
);

// keccak256(abi.encode(uint256(keccak256("plumaa.storage.RSAOwnerManager")) - 1)) & ~bytes32(uint256(0xff))
bytes32 constant RSAOwnerManagerStorageLocation = 0xd2cca958b80dbad5ce6e876a8c46f66173a169ce6aba515198c38d288b5cc600;
bytes32 internal constant RSA_OWNER_MANAGER_STORAGE_LOCATION =
0xd2cca958b80dbad5ce6e876a8c46f66173a169ce6aba515198c38d288b5cc600;

struct RSAOwnerManagerStorage {
bytes32 owner;
uint32 nonce;
}

/// @notice Sets the initial storage of the contract.
function __RSAOwnerManager_init(bytes memory exponent, bytes memory modulus) internal onlyInitializing {
function __RSAOwnerManager_init(
bytes memory exponent,
bytes memory modulus
) internal onlyInitializing {
_setOwner(exponent, modulus);
}

Expand All @@ -43,8 +48,7 @@ abstract contract RSAOwnerManager is Initializable {
return _getRSAOwnerManagerStorage().nonce;
}

/// @notice Sets a new authorized public key bytes32 id. See
/// {_toPublicKeyId}.
/// @notice Sets a new authorized public key bytes32 id. See {_toPublicKeyId}.
/// Beware this internal version doesn't require access control.
/// @param exponent The exponent of the RSA public key.
/// @param modulus The modulus of the RSA public key.
Expand All @@ -55,32 +59,34 @@ abstract contract RSAOwnerManager is Initializable {
emit OwnershipTransferred(previousOwner, newOwner);
}

/// @notice Returns true if the provided signature is valid for the dignest
/// and public key.
/// @notice Returns true if the provided signature is valid for the digest and public key.
/// @param message The message to verify.
/// @param signature The signature to verify.
/// @param exponent The exponent of the RSA public key.
/// @param modulus The modulus of the RSA public key.
function _verifyRSAOwner(bytes memory message, bytes memory signature, bytes memory exponent, bytes memory modulus)
internal
view
returns (bool)
{
function _verifyRSAOwner(
bytes memory message,
bytes memory signature,
bytes memory exponent,
bytes memory modulus
) internal view returns (bool) {
return _verifyRSAOwner(sha256(message), signature, exponent, modulus);
}

/// @notice Returns true if the provided signature is valid for the dignest
/// and public key.
/// @notice Returns true if the provided signature is valid for the digest and public key.
/// @param sha256Digest The digest of the message to verify.
/// @param signature The signature to verify.
/// @param exponent The exponent of the RSA public key.
/// @param modulus The modulus of the RSA public key.
function _verifyRSAOwner(bytes32 sha256Digest, bytes memory signature, bytes memory exponent, bytes memory modulus)
internal
view
returns (bool)
{
return _isRSAOwner(exponent, modulus) && sha256Digest.pkcs1Sha256(signature, exponent, modulus);
function _verifyRSAOwner(
bytes32 sha256Digest,
bytes memory signature,
bytes memory exponent,
bytes memory modulus
) internal view returns (bool) {
return
_isRSAOwner(exponent, modulus) &&
sha256Digest.pkcs1Sha256(signature, exponent, modulus);
}

/// @notice Consumes a nonce.
Expand All @@ -95,22 +101,33 @@ abstract contract RSAOwnerManager is Initializable {
/// @notice Returns true if the provided public key is an owner of the Plumaa.
/// @param exponent The exponent of the RSA public key.
/// @param modulus The modulus of the RSA public key.
function _isRSAOwner(bytes memory exponent, bytes memory modulus) private view returns (bool) {
return _getRSAOwnerManagerStorage().owner == _toPublicKeyId(exponent, modulus);
function _isRSAOwner(
bytes memory exponent,
bytes memory modulus
) private view returns (bool) {
return
_getRSAOwnerManagerStorage().owner ==
_toPublicKeyId(exponent, modulus);
}

/// @notice On the absense of a proper public key, we identify each RSA
/// owner by a keccak256(modulus, exponent).
/// @notice On the absense of a proper public key, we identify each RSA owner by a keccak256(modulus, exponent).
/// @param exponent The exponent of the RSA public key.
/// @param modulus The modulus of the RSA public key.
function _toPublicKeyId(bytes memory exponent, bytes memory modulus) private pure returns (bytes32) {
function _toPublicKeyId(
bytes memory exponent,
bytes memory modulus
) private pure returns (bytes32) {
return keccak256(abi.encodePacked(exponent, modulus));
}

/// @notice Get EIP-7201 storage
function _getRSAOwnerManagerStorage() private pure returns (RSAOwnerManagerStorage storage $) {
function _getRSAOwnerManagerStorage()
private
pure
returns (RSAOwnerManagerStorage storage $)
{
assembly {
$.slot := RSAOwnerManagerStorageLocation
$.slot := RSA_OWNER_MANAGER_STORAGE_LOCATION
}
}
}
17 changes: 11 additions & 6 deletions src/base/SafeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ pragma solidity ^0.8.20;
import {Safe} from "@safe/contracts/Safe.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

/// @title Contract to manage the ownership of a Plumaa with EIP7201 support.
/// @title Manager of the Safe{Wallet} Smart Account connected to Plumaa.
contract SafeManager is Initializable {
/// @dev The caller account is not authorized to perform an operation.
error SafeManagerUnauthorizedAccount(address account);

// keccak256(abi.encode(uint256(keccak256("plumaa.storage.SafeManager")) - 1)) & ~bytes32(uint256(0xff))
bytes32 constant SafeManagerStorageLocation = 0x6465d3aa1a400bf0e2554af5439f7f8a5b30fd78a22df4e95de86b9d82986200;
bytes32 internal constant SAFE_MANAGER_STORAGE_LOCATION =
0x6465d3aa1a400bf0e2554af5439f7f8a5b30fd78a22df4e95de86b9d82986200;

struct SafeManagerStorage {
Safe _safe;
Expand All @@ -27,22 +28,26 @@ contract SafeManager is Initializable {
_getSafeManagerStorage()._safe = safe_;
}

/// @dev Returns the address of the owner safe.
/// @dev Returns the address of the owned safe.
function safe() public view virtual returns (Safe) {
return _getSafeManagerStorage()._safe;
}

/// @dev Throws if the sender is not the owner.
/// @dev Throws if the sender is not the owned safe.
function _checkSafe() internal view virtual {
if (address(safe()) != msg.sender) {
revert SafeManagerUnauthorizedAccount(msg.sender);
}
}

/// @notice Get EIP-7201 storage
function _getSafeManagerStorage() private pure returns (SafeManagerStorage storage $) {
function _getSafeManagerStorage()
private
pure
returns (SafeManagerStorage storage $)
{
assembly {
$.slot := SafeManagerStorageLocation
$.slot := SAFE_MANAGER_STORAGE_LOCATION
}
}
}
2 changes: 1 addition & 1 deletion test/base/mocks/RSAOwnerManager.m.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contract RSAOwnerManagerMock is RSAOwnerManager {
/// @notice Get EIP-7201 storage
function _storage() private pure returns (RSAOwnerManagerStorage storage $) {
assembly {
$.slot := RSAOwnerManagerStorageLocation
$.slot := RSA_OWNER_MANAGER_STORAGE_LOCATION
}
}
}
Loading

0 comments on commit 997213d

Please sign in to comment.