Skip to content

Commit

Permalink
Merge ec7c992 into 9a1555a
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Jan 30, 2023
2 parents 9a1555a + ec7c992 commit a46f3db
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
2 changes: 1 addition & 1 deletion contracts/Safe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ contract Safe is
return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
}

/// @dev Returns the bytes that are hashed to be signed by owners.
/// @dev Returns the pre-image of the transaction hash (see getTransactionHash).
/// @param to Destination address.
/// @param value Ether value.
/// @param data Data payload.
Expand Down
17 changes: 13 additions & 4 deletions contracts/handler/CompatibilityFallbackHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ contract CompatibilityFallbackHandler is DefaultCallbackHandler, ISignatureValid
function isValidSignature(bytes memory _data, bytes memory _signature) public view override returns (bytes4) {
// Caller should be a Safe
Safe safe = Safe(payable(msg.sender));
bytes32 messageHash = getMessageHashForSafe(safe, _data);
bytes memory messageData = encodeMessageDataForSafe(safe, _data);
bytes32 messageHash = keccak256(messageData);
if (_signature.length == 0) {
require(safe.signedMessages(messageHash) != 0, "Hash not approved");
} else {
safe.checkSignatures(messageHash, _data, _signature);
safe.checkSignatures(messageHash, messageData, _signature);
}
return EIP1271_MAGIC_VALUE;
}
Expand All @@ -44,13 +45,21 @@ contract CompatibilityFallbackHandler is DefaultCallbackHandler, ISignatureValid
return getMessageHashForSafe(Safe(payable(msg.sender)), message);
}

/// @dev Returns the pre-image of the message hash (see getMessageHashForSafe)
/// @param safe Safe to which the message is targeted
/// @param message Message that should be encoded
/// @return Encoded message.
function encodeMessageDataForSafe(Safe safe, bytes memory message) public view returns (bytes memory) {
bytes32 safeMessageHash = keccak256(abi.encode(SAFE_MSG_TYPEHASH, keccak256(message)));
return abi.encodePacked(bytes1(0x19), bytes1(0x01), safe.domainSeparator(), safeMessageHash);
}

/// @dev Returns hash of a message that can be signed by owners.
/// @param safe Safe to which the message is targeted
/// @param message Message that should be hashed
/// @return Message hash.
function getMessageHashForSafe(Safe safe, bytes memory message) public view returns (bytes32) {
bytes32 safeMessageHash = keccak256(abi.encode(SAFE_MSG_TYPEHASH, keccak256(message)));
return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), safe.domainSeparator(), safeMessageHash));
return keccak256(encodeMessageDataForSafe(safe, message));
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/utils/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export const calculateSafeTransactionHash = (safe: Contract, safeTx: SafeTransac
return utils._TypedDataEncoder.hash({ verifyingContract: safe.address, chainId }, EIP712_SAFE_TX_TYPE, safeTx)
}

export const preimageSafeMessageHash = (safe: Contract, message: string, chainId: BigNumberish): string => {
return utils._TypedDataEncoder.encode({ verifyingContract: safe.address, chainId }, EIP712_SAFE_MESSAGE_TYPE, { message })
}

export const calculateSafeMessageHash = (safe: Contract, message: string, chainId: BigNumberish): string => {
return utils._TypedDataEncoder.hash({ verifyingContract: safe.address, chainId }, EIP712_SAFE_MESSAGE_TYPE, { message })
}
Expand Down
39 changes: 38 additions & 1 deletion test/handlers/CompatibilityFallbackHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { buildContractSignature } from './../../src/utils/execution';
import { expect } from "chai";
import hre, { deployments, waffle, ethers } from "hardhat";
import "@nomiclabs/hardhat-ethers";
import { AddressZero } from "@ethersproject/constants";
import { compatFallbackHandlerContract, getCompatFallbackHandler, getSafeWithOwners } from "../utils/setup";
import { buildSignatureBytes, executeContractCallWithSigners, calculateSafeMessageHash, EIP712_SAFE_MESSAGE_TYPE, signHash } from "../../src/utils/execution";
import { buildSignatureBytes, executeContractCallWithSigners, calculateSafeMessageHash, preimageSafeMessageHash, EIP712_SAFE_MESSAGE_TYPE, signHash } from "../../src/utils/execution";
import { chainId } from "../utils/encoding";
import { BigNumber } from "ethers";
import { killLibContract } from "../utils/contracts";
Expand Down Expand Up @@ -92,6 +93,24 @@ describe("CompatibilityFallbackHandler", async () => {
const sig2 = await signHash(user2, calculateSafeMessageHash(validator, "0xbaddad", await chainId()))
expect(await validator.callStatic['isValidSignature(bytes,bytes)']("0xbaddad", buildSignatureBytes([sig1, sig2]))).to.be.eq("0x20c13b0b")
})

it('should pass a keccak256 hash of the pre-image message to support contract signatures', async () => {
const {handler} = await setupTests()
const signerSafe = await getSafeWithOwners([user1.address], 1, handler.address)
const safe = await getSafeWithOwners([signerSafe.address], 1, handler.address)
const validator = (await compatFallbackHandlerContract()).attach(safe.address)

const randomBytes32 = ethers.utils.hexlify(ethers.utils.randomBytes(32))
const validatorPreImageMessage = preimageSafeMessageHash(validator, randomBytes32, await chainId())
const signerSafeMessageHash = calculateSafeMessageHash(signerSafe, validatorPreImageMessage, await chainId())
const signerSafeOwnerSignature = await signHash(user1, signerSafeMessageHash)
const signerSafeSig = buildContractSignature(signerSafe.address, signerSafeOwnerSignature.data)
const signatureBytes = buildSignatureBytes([signerSafeSig])

expect(
await validator.callStatic['isValidSignature(bytes,bytes)'](randomBytes32, signatureBytes)
).to.be.eq("0x20c13b0b")
})
})

describe("isValidSignature(bytes32,bytes)", async () => {
Expand Down Expand Up @@ -131,6 +150,24 @@ describe("CompatibilityFallbackHandler", async () => {
const sig2 = await signHash(user2, calculateSafeMessageHash(validator,dataHash, await chainId()))
expect(await validator.callStatic['isValidSignature(bytes32,bytes)'](dataHash, buildSignatureBytes([sig1, sig2]))).to.be.eq("0x1626ba7e")
})

it('should pass a keccak256 hash of the pre-image message to support contract signatures', async () => {
const {handler} = await setupTests()
const signerSafe = await getSafeWithOwners([user1.address], 1, handler.address)
const safe = await getSafeWithOwners([signerSafe.address], 1, handler.address)
const validator = (await compatFallbackHandlerContract()).attach(safe.address)

const randomBytes32 = ethers.utils.hexlify(ethers.utils.randomBytes(32))
const validatorPreImageMessage = preimageSafeMessageHash(validator, randomBytes32, await chainId())
const signerSafeMessageHash = calculateSafeMessageHash(signerSafe, validatorPreImageMessage, await chainId())
const signerSafeOwnerSignature = await signHash(user1, signerSafeMessageHash)
const signerSafeSig = buildContractSignature(signerSafe.address, signerSafeOwnerSignature.data)
const signatureBytes = buildSignatureBytes([signerSafeSig])

expect(
await validator.callStatic['isValidSignature(bytes32,bytes)'](randomBytes32, signatureBytes)
).to.be.eq("0x1626ba7e")
})
})

describe("getModules", async () => {
Expand Down

0 comments on commit a46f3db

Please sign in to comment.