Skip to content

Commit

Permalink
Allow lowercase/uppercase Ethereum address in the subject
Browse files Browse the repository at this point in the history
  • Loading branch information
SoraSuegami committed Sep 2, 2024
1 parent df3efd0 commit d393a63
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 19 deletions.
40 changes: 31 additions & 9 deletions packages/contracts/src/EmailWalletCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,43 @@ contract EmailWalletCore is Initializable, UUPSUpgradeable, OwnableUpgradeable {
}

// Validate computed subject = passed subject
(string memory computedSubject, ) = SubjectUtils.computeMaskedSubjectForEmailOp(
emailOp,
accountHandler.getWalletOfSalt(emailOp.accountSalt),
this // Core contract to read some states
);
// (string memory computedSubject, ) = SubjectUtils.computeMaskedSubjectForEmailOp(
// emailOp,
// accountHandler.getWalletOfSalt(emailOp.accountSalt),
// this // Core contract to read some states
// );
bytes memory maskedSubjectBytes = bytes(emailOp.maskedSubject);
require(emailOp.skipSubjectPrefix < maskedSubjectBytes.length, "skipSubjectPrefix too high");
bytes memory skippedSubjectBytes = new bytes(maskedSubjectBytes.length - emailOp.skipSubjectPrefix);
for (uint i = 0; i < skippedSubjectBytes.length; i++) {
skippedSubjectBytes[i] = maskedSubjectBytes[emailOp.skipSubjectPrefix + i];
}
require(
Strings.equal(computedSubject, string(skippedSubjectBytes)),
string.concat("subject != ", computedSubject)
);
string memory computedSubject = "";
for (uint stringCase = 0; stringCase < 3; stringCase++) {
(computedSubject, ) = SubjectUtils.computeMaskedSubjectForEmailOp(
emailOp,
accountHandler.getWalletOfSalt(emailOp.accountSalt),
this, // Core contract to read some states
stringCase
);
if (Strings.equal(computedSubject, string(skippedSubjectBytes))) {
break;
}
if (stringCase == 2) {
revert(
string.concat(
"given subject: ",
string(skippedSubjectBytes),
"!= expected subject: ",
computedSubject
)
);
}
}
// require(
// Strings.equal(computedSubject, string(skippedSubjectBytes)),
// string.concat("subject != ", computedSubject)
// );

// Verify proof
require(
Expand Down
37 changes: 31 additions & 6 deletions packages/contracts/src/libraries/SubjectUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ library SubjectUtils {
bytes16 private constant LOWER_HEX_DIGITS = "0123456789abcdef";
bytes16 private constant UPPER_HEX_DIGITS = "0123456789ABCDEF";

function addressToHexString(address addr, uint stringCase) internal pure returns (string memory) {
if (stringCase == 0) {
return addressToChecksumHexString(addr);
} else if (stringCase == 1) {
return Strings.toHexString(addr);
} else if (stringCase == 2) {
return lowerToUpperCase(Strings.toHexString(addr));
} else {
revert("invalid stringCase");
}
}

function addressToChecksumHexString(address addr) internal pure returns (string memory) {
string memory lowerCaseAddrWithOx = Strings.toHexString(addr);

Expand Down Expand Up @@ -54,6 +66,16 @@ library SubjectUtils {
return string(result);
}

function lowerToUpperCase(string memory hexStr) internal pure returns (string memory) {
bytes memory bytesStr = bytes(hexStr);
for (uint i = 0; i < bytesStr.length; i++) {
if (bytesStr[i] >= 0x61 && bytesStr[i] <= 0x66) {
bytesStr[i] = bytes1(uint8(bytesStr[i]) - 32);
}
}
return string(bytesStr);
}

/// @notice Convert bytes to hex string without 0x prefix
/// @param data bytes to convert
function bytesToHexString(bytes memory data) public pure returns (string memory) {
Expand All @@ -74,11 +96,14 @@ library SubjectUtils {
/// @param emailOp EmailOp to compute masked subject for
/// @param walletAddr Address of the user's wallet
/// @param core EmailWalletCore contract to read some states for validation
/// @param stringCase Case of the hex string to be used in the subject
function computeMaskedSubjectForEmailOp(
EmailOp memory emailOp,
address walletAddr,
EmailWalletCore core
EmailWalletCore core,
uint stringCase
) public view returns (string memory maskedSubject, bool isExtension) {
require(stringCase < 3, "invalid stringCase");
ExtensionHandler extensionHandler = ExtensionHandler(core.extensionHandler());

// Sample: Send 1 ETH to [email protected]
Expand All @@ -100,7 +125,7 @@ library SubjectUtils {
);

if (emailOp.recipientETHAddr != address(0)) {
maskedSubject = string.concat(maskedSubject, addressToChecksumHexString(emailOp.recipientETHAddr));
maskedSubject = string.concat(maskedSubject, addressToHexString(emailOp.recipientETHAddr, stringCase));
}
}
// Sample: Execute 0x000112aa..
Expand Down Expand Up @@ -157,7 +182,7 @@ library SubjectUtils {
maskedSubject = string.concat(
Commands.EXIT_EMAIL_WALLET,
" Email Wallet. Change ownership to ",
addressToChecksumHexString(emailOp.newWalletOwner)
addressToHexString(emailOp.newWalletOwner, stringCase)
);
}
// Sample: DKIM registry as 0x000112aa..
Expand All @@ -167,7 +192,7 @@ library SubjectUtils {
maskedSubject = string.concat(
Commands.DKIM,
" registry set to ",
addressToChecksumHexString(emailOp.newDkimRegistry)
addressToHexString(emailOp.newDkimRegistry, stringCase)
);
}
// The command is for an extension
Expand Down Expand Up @@ -230,13 +255,13 @@ library SubjectUtils {
// {addres} for wallet address
else if (Strings.equal(matcher, Commands.ADDRESS_TEMPLATE)) {
address addr = abi.decode(emailOp.extensionParams.subjectParams[nextParamIndex], (address));
value = addressToChecksumHexString(addr);
value = addressToHexString(addr, stringCase);
nextParamIndex++;
}
// {recipient} is either the recipient's ETH address or zero bytes with the same length of the email address
else if (Strings.equal(matcher, Commands.RECIPIENT_TEMPLATE)) {
if (!emailOp.hasEmailRecipient) {
value = addressToChecksumHexString(emailOp.recipientETHAddr);
value = addressToHexString(emailOp.recipientETHAddr, stringCase);
} else {
bytes memory zeros = new bytes(emailOp.numRecipientEmailAddrBytes);
value = string(zeros);
Expand Down
26 changes: 22 additions & 4 deletions packages/contracts/test/EmailWalletCore.cmd.send.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ contract TransferTest is EmailWalletCoreTestHelper {
vm.stopPrank();
}

// We only support addres in checksum format in the subject (not all lowercase)
function test_Revert_SendingToEthAddress_WithNonChecksumAddress() public {
// We support addres in lowercase format in the subject
function test_SendingToEthAddress_WithLowercaseAddress() public {
address recipient = vm.addr(5);
daiToken.freeMint(walletAddr, 1 ether);
// vm.addr(5) in lowecase = 0xe1ab8145f7e55dc933d51a18c793f901a3a0b276
Expand All @@ -57,7 +57,25 @@ contract TransferTest is EmailWalletCoreTestHelper {
emailOp.walletParams.tokenName = "DAI";

vm.startPrank(relayer);
vm.expectRevert("subject != Send 1 DAI to 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276");
core.validateEmailOp(emailOp);
vm.stopPrank();
}

// We support addres in uppercase format in the subject
function test_SendingToEthAddress_WithUppercaseAddress() public {
address recipient = vm.addr(5);
daiToken.freeMint(walletAddr, 1 ether);
// vm.addr(5) in lowecase = 0xE1AB8145F7E55DC933D51A18C793F901A3A0B276
string memory subject = string.concat("Send 1 DAI to 0xE1AB8145F7E55DC933D51A18C793F901A3A0B276");

EmailOp memory emailOp = _getBaseEmailOp();
emailOp.command = "Send";
emailOp.maskedSubject = subject;
emailOp.recipientETHAddr = recipient;
emailOp.walletParams.amount = 1 ether;
emailOp.walletParams.tokenName = "DAI";

vm.startPrank(relayer);
core.validateEmailOp(emailOp);
vm.stopPrank();
}
Expand Down Expand Up @@ -268,7 +286,7 @@ contract TransferTest is EmailWalletCoreTestHelper {
emailOp.skipSubjectPrefix = 3;

vm.startPrank(relayer);
vm.expectRevert("subject != Send 65.4 DAI to ");
vm.expectRevert("given subject: Send 65.4 DAI to != expected subject: Send 65.4 DAI to ");
core.validateEmailOp(emailOp);
vm.stopPrank();
}
Expand Down

0 comments on commit d393a63

Please sign in to comment.