Sitelet https://github.com/ithacaxyz/account/commit/6dd67d0f1c5cb88825a96d52956e32b62903c727
Skip to content

Commit 6dd67d0

Browse files
authored
feat: fix and simplify multichain design (#327)
* feat: simplify multichain nonce design * chore: readd merkle verification prefix * chore: undo blank line addns * chore: lint * chore: redundant multichain bool * fix: lint * .
1 parent ab0a493 commit 6dd67d0

9 files changed

Lines changed: 114 additions & 116 deletions

File tree

‎src/IthacaAccount.sol‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,11 +478,11 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
478478
)
479479
);
480480
}
481-
bool isMultichain = nonce >> 240 == MULTICHAIN_NONCE_PREFIX;
482-
bytes32 structHash = EfficientHashLib.hash(
483-
uint256(EXECUTE_TYPEHASH), LibBit.toUint(isMultichain), uint256(a.hash()), nonce
484-
);
485-
return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash);
481+
bytes32 structHash =
482+
EfficientHashLib.hash(uint256(EXECUTE_TYPEHASH), uint256(a.hash()), nonce);
483+
return nonce >> 240 == MULTICHAIN_NONCE_PREFIX
484+
? _hashTypedDataSansChainId(structHash)
485+
: _hashTypedData(structHash);
486486
}
487487

488488
/// @dev Returns if the signature is valid, along with its `keyHash`.

‎src/Orchestrator.sol‎

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
127127
/// This constant is a pun for "chain ID 0".
128128
uint16 public constant MULTICHAIN_NONCE_PREFIX = 0xc1d0;
129129

130+
/// @dev Nonce prefix to signal that the payload should use merkle verification.
131+
/// This constant is "mv" in hex.
132+
uint16 public constant MERKLE_VERIFICATION = 0x6D76;
133+
130134
/// @dev For ensuring that the remaining gas is sufficient for a self-call with
131135
/// overhead for cleaning up after the self-call. This also has an added benefit
132136
/// of preventing the censorship vector of calling `execute` in a very deep call-stack.
@@ -349,13 +353,11 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
349353
// Early skip the entire pay-verify-call workflow if the payer lacks tokens,
350354
// so that less gas is wasted when the Intent fails.
351355
// For multi chain mode, we skip this check, as the funding happens inside the self call.
352-
if (!i.isMultichain && LibBit.and(i.paymentAmount != 0, err == 0)) {
353-
if (TokenTransferLib.balanceOf(i.paymentToken, payer) < i.paymentAmount) {
354-
err = PaymentError.selector;
356+
if (TokenTransferLib.balanceOf(i.paymentToken, payer) < i.paymentAmount) {
357+
err = PaymentError.selector;
355358

356-
if (flags == _SIMULATION_MODE_FLAG) {
357-
revert PaymentError();
358-
}
359+
if (flags == _SIMULATION_MODE_FLAG) {
360+
revert PaymentError();
359361
}
360362
}
361363

@@ -476,7 +478,8 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
476478

477479
bool isValid;
478480
bytes32 keyHash;
479-
if (i.isMultichain) {
481+
482+
if (i.nonce >> 240 == MERKLE_VERIFICATION) {
480483
// For multi chain intents, we have to verify using merkle sigs.
481484
(isValid, keyHash) = _verifyMerkleSig(digest, eoa, i.signature);
482485

@@ -754,40 +757,35 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
754757
function _computeDigest(SignedCall calldata p) internal view virtual returns (bytes32) {
755758
bool isMultichain = p.nonce >> 240 == MULTICHAIN_NONCE_PREFIX;
756759
// To avoid stack-too-deep. Faster than a regular Solidity array anyways.
757-
bytes32[] memory f = EfficientHashLib.malloc(5);
760+
bytes32[] memory f = EfficientHashLib.malloc(4);
758761
f.set(0, SIGNED_CALL_TYPEHASH);
759-
f.set(1, LibBit.toUint(isMultichain));
760-
f.set(2, uint160(p.eoa));
761-
f.set(3, _executionDataHash(p.executionData));
762-
f.set(4, p.nonce);
762+
f.set(1, uint160(p.eoa));
763+
f.set(2, _executionDataHash(p.executionData));
764+
f.set(3, p.nonce);
763765

764766
return isMultichain ? _hashTypedDataSansChainId(f.hash()) : _hashTypedData(f.hash());
765767
}
766768

767769
/// @dev Computes the EIP712 digest for the Intent.
768-
/// If the the nonce starts with `MULTICHAIN_NONCE_PREFIX`,
769-
/// the digest will be computed without the chain ID.
770-
/// Otherwise, the digest will be computed with the chain ID.
771770
function _computeDigest(Intent calldata i) internal view virtual returns (bytes32) {
772-
bool isMultichain = i.nonce >> 240 == MULTICHAIN_NONCE_PREFIX;
773-
774771
// To avoid stack-too-deep. Faster than a regular Solidity array anyways.
775-
bytes32[] memory f = EfficientHashLib.malloc(13);
772+
bytes32[] memory f = EfficientHashLib.malloc(12);
776773
f.set(0, INTENT_TYPEHASH);
777-
f.set(1, LibBit.toUint(isMultichain));
778-
f.set(2, uint160(i.eoa));
779-
f.set(3, _executionDataHash(i.executionData));
780-
f.set(4, i.nonce);
781-
f.set(5, uint160(i.payer));
782-
f.set(6, uint160(i.paymentToken));
783-
f.set(7, i.paymentMaxAmount);
784-
f.set(8, i.combinedGas);
785-
f.set(9, _encodedArrHash(i.encodedPreCalls));
786-
f.set(10, _encodedArrHash(i.encodedFundTransfers));
787-
f.set(11, uint160(i.settler));
788-
f.set(12, i.expiry);
789-
790-
return isMultichain ? _hashTypedDataSansChainId(f.hash()) : _hashTypedData(f.hash());
774+
f.set(1, uint160(i.eoa));
775+
f.set(2, _executionDataHash(i.executionData));
776+
f.set(3, i.nonce);
777+
f.set(4, uint160(i.payer));
778+
f.set(5, uint160(i.paymentToken));
779+
f.set(6, i.paymentMaxAmount);
780+
f.set(7, i.combinedGas);
781+
f.set(8, _encodedArrHash(i.encodedPreCalls));
782+
f.set(9, _encodedArrHash(i.encodedFundTransfers));
783+
f.set(10, uint160(i.settler));
784+
f.set(11, i.expiry);
785+
786+
return i.nonce >> 240 == MULTICHAIN_NONCE_PREFIX
787+
? _hashTypedDataSansChainId(f.hash())
788+
: _hashTypedData(f.hash());
791789
}
792790

793791
/// @dev Helper function to return the hash of the `execuctionData`.

‎src/interfaces/ICommon.sol‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ interface ICommon {
5555
////////////////////////////////////////////////////////////////////////
5656
// Additional Fields (Not included in EIP-712)
5757
////////////////////////////////////////////////////////////////////////
58-
/// @dev Whether the intent should use the multichain mode - i.e verify with merkle sigs
59-
/// and send the cross chain message.
60-
bool isMultichain;
6158
/// @dev The funder address.
6259
address funder;
6360
/// @dev The funder signature.

‎test/Account.t.sol‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ contract AccountTest is BaseTest {
303303
}
304304

305305
// Prepare main Intent structure (will be reused with same pre-calls)
306-
Orchestrator.Intent memory baseIntent;
306+
ICommon.Intent memory baseIntent;
307307
baseIntent.eoa = eoaAddress;
308308
baseIntent.paymentToken = address(paymentToken);
309309
baseIntent.paymentAmount = _bound(_random(), 0, 2 ** 32 - 1);
@@ -327,12 +327,11 @@ contract AccountTest is BaseTest {
327327
vm.etch(eoaAddress, abi.encodePacked(hex"ef0100", impl));
328328

329329
// Use the prepared pre-calls on chain 1
330-
Orchestrator.Intent memory u1 = baseIntent;
331-
u1.nonce = (0xc1d0 << 240) | 0; // Multichain nonce for main intent
332-
u1.signature = _sig(adminKey, u1);
330+
baseIntent.nonce = (0xc1d0 << 240) | 0; // Multichain nonce for main intent
331+
baseIntent.signature = _sig(adminKey, oc.computeDigest(baseIntent));
333332

334333
// Execute on chain 1 - should succeed
335-
assertEq(oc.execute(abi.encode(u1)), 0, "Execution should succeed on chain 1");
334+
assertEq(oc.execute(abi.encode(baseIntent)), 0, "Execution should succeed on chain 1");
336335

337336
// Verify keys were added on chain 1
338337
uint256 keysCount1 = IthacaAccount(eoaAddress).keyCount();

‎test/Base.t.sol‎

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ contract BaseTest is SoladyTest {
129129
k.keyHash = _hash(k.k);
130130
}
131131

132-
function _sig(DelegatedEOA memory d, Orchestrator.Intent memory i)
132+
function _sig(DelegatedEOA memory d, ICommon.Intent memory i)
133133
internal
134134
view
135135
returns (bytes memory)
@@ -141,7 +141,7 @@ contract BaseTest is SoladyTest {
141141
return _eoaSig(d.privateKey, digest);
142142
}
143143

144-
function _eoaSig(uint256 privateKey, Orchestrator.Intent memory i)
144+
function _eoaSig(uint256 privateKey, ICommon.Intent memory i)
145145
internal
146146
view
147147
returns (bytes memory)
@@ -154,11 +154,7 @@ contract BaseTest is SoladyTest {
154154
return abi.encodePacked(r, s, v);
155155
}
156156

157-
function _sig(PassKey memory k, Orchestrator.Intent memory i)
158-
internal
159-
view
160-
returns (bytes memory)
161-
{
157+
function _sig(PassKey memory k, ICommon.Intent memory i) internal view returns (bytes memory) {
162158
return _sig(k, false, oc.computeDigest(i));
163159
}
164160

@@ -184,7 +180,7 @@ contract BaseTest is SoladyTest {
184180
return _multiSig(k, _hash(k.k), false, digest);
185181
}
186182

187-
function _sig(MultiSigKey memory k, Orchestrator.Intent memory u)
183+
function _sig(MultiSigKey memory k, ICommon.Intent memory u)
188184
internal
189185
view
190186
returns (bytes memory)
@@ -248,7 +244,7 @@ contract BaseTest is SoladyTest {
248244
return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0));
249245
}
250246

251-
function _estimateGasForEOAKey(Orchestrator.Intent memory i)
247+
function _estimateGasForEOAKey(ICommon.Intent memory i)
252248
internal
253249
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
254250
{
@@ -258,7 +254,7 @@ contract BaseTest is SoladyTest {
258254
return _estimateGas(i);
259255
}
260256

261-
function _estimateGas(PassKey memory k, Orchestrator.Intent memory i)
257+
function _estimateGas(PassKey memory k, ICommon.Intent memory i)
262258
internal
263259
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
264260
{
@@ -271,7 +267,7 @@ contract BaseTest is SoladyTest {
271267
revert("Unsupported");
272268
}
273269

274-
function _estimateGasForSecp256k1Key(bytes32 keyHash, Orchestrator.Intent memory i)
270+
function _estimateGasForSecp256k1Key(bytes32 keyHash, ICommon.Intent memory i)
275271
internal
276272
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
277273
{
@@ -281,7 +277,7 @@ contract BaseTest is SoladyTest {
281277
return _estimateGas(i);
282278
}
283279

284-
function _estimateGasForSecp256r1Key(bytes32 keyHash, Orchestrator.Intent memory i)
280+
function _estimateGasForSecp256r1Key(bytes32 keyHash, ICommon.Intent memory i)
285281
internal
286282
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
287283
{
@@ -290,7 +286,7 @@ contract BaseTest is SoladyTest {
290286
return _estimateGas(i);
291287
}
292288

293-
function _estimateGasForMultiSigKey(MultiSigKey memory k, Orchestrator.Intent memory u)
289+
function _estimateGasForMultiSigKey(MultiSigKey memory k, ICommon.Intent memory u)
294290
internal
295291
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
296292
{
@@ -305,7 +301,7 @@ contract BaseTest is SoladyTest {
305301
);
306302
}
307303

308-
function _estimateGas(Orchestrator.Intent memory i)
304+
function _estimateGas(ICommon.Intent memory i)
309305
internal
310306
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
311307
{
@@ -324,7 +320,7 @@ contract BaseTest is SoladyTest {
324320
}
325321

326322
struct _EstimateGasParams {
327-
Orchestrator.Intent u;
323+
ICommon.Intent u;
328324
uint8 paymentPerGasPrecision;
329325
uint256 paymentPerGas;
330326
uint256 combinedGasIncrement;
@@ -490,4 +486,14 @@ contract BaseTest is SoladyTest {
490486
hex"3d604052610216565b60008060006ffffffffeffffffffffffffffffffffff60601b19808687098188890982838389096004098384858485093d510985868b8c096003090891508384828308850385848509089650838485858609600809850385868a880385088509089550505050808188880960020991505093509350939050565b81513d83015160408401516ffffffffeffffffffffffffffffffffff60601b19808384098183840982838388096004098384858485093d510985868a8b096003090896508384828308850385898a09089150610102848587890960020985868787880960080987038788878a0387088c0908848b523d8b015260408a0152565b505050505050505050565b81513d830151604084015185513d87015160408801518361013d578287523d870182905260408701819052610102565b80610157578587523d870185905260408701849052610102565b6ffffffffeffffffffffffffffffffffff60601b19808586098183840982818a099850828385830989099750508188830383838809089450818783038384898509870908935050826101be57836101be576101b28a89610082565b50505050505050505050565b808485098181860982828a09985082838a8b0884038483860386898a09080891506102088384868a0988098485848c09860386878789038f088a0908848d523d8d015260408c0152565b505050505050505050505050565b6020357fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325513d6040357f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a88111156102695782035b60206108005260206108205260206108405280610860526002830361088052826108a0526ffffffffeffffffffffffffffffffffff60601b198060031860205260603560803560203d60c061080060055afa60203d1416837f5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b8585873d5189898a09080908848384091484831085851016888710871510898b108b151016609f3611161616166103195760206080f35b60809182523d820152600160c08190527f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2966102009081527f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f53d909101526102405261038992509050610100610082565b610397610200610400610082565b6103a7610100608061018061010d565b6103b7610200608061028061010d565b6103c861020061010061030061010d565b6103d961020061018061038061010d565b6103e9610400608061048061010d565b6103fa61040061010061050061010d565b61040b61040061018061058061010d565b61041c61040061020061060061010d565b61042c610600608061068061010d565b61043d61060061010061070061010d565b61044e61060061018061078061010d565b81815182350982825185098283846ffffffffeffffffffffffffffffffffff60601b193d515b82156105245781858609828485098384838809600409848586848509860986878a8b096003090885868384088703878384090886878887880960080988038889848b03870885090887888a8d096002098882830996508881820995508889888509600409945088898a8889098a098a8b86870960030908935088898687088a038a868709089a5088898284096002099950505050858687868709600809870387888b8a0386088409089850505050505b61018086891b60f71c16610600888a1b60f51c16176040810151801585151715610564578061055357506105fe565b81513d8301519750955093506105fe565b83858609848283098581890986878584098b0991508681880388858851090887838903898a8c88093d8a015109089350836105b957806105b9576105a9898c8c610008565b9a509b50995050505050506105fe565b8781820988818309898285099350898a8586088b038b838d038d8a8b0908089b50898a8287098b038b8c8f8e0388088909089c5050508788868b098209985050505050505b5082156106af5781858609828485098384838809600409848586848509860986878a8b096003090885868384088703878384090886878887880960080988038889848b03870885090887888a8d096002098882830996508881820995508889888509600409945088898a8889098a098a8b86870960030908935088898687088a038a868709089a5088898284096002099950505050858687868709600809870387888b8a0386088409089850505050505b61018086891b60f51c16610600888a1b60f31c161760408101518015851517156106ef57806106de5750610789565b81513d830151975095509350610789565b83858609848283098581890986878584098b0991508681880388858851090887838903898a8c88093d8a01510908935083610744578061074457610734898c8c610008565b9a509b5099505050505050610789565b8781820988818309898285099350898a8586088b038b838d038d8a8b0908089b50898a8287098b038b8c8f8e0388088909089c5050508788868b098209985050505050505b50600488019760fb19016104745750816107a2573d6040f35b81610860526002810361088052806108a0523d3d60c061080060055afa898983843d513d510987090614163d525050505050505050503d3df3fea264697066735822122063ce32ec0e56e7893a1f6101795ce2e38aca14dd12adb703c71fe3bee27da71e64736f6c634300081a0033";
491487
vm.etch(address(0x100), verifierBytecode);
492488
}
489+
490+
function _computeDigest(ICommon.Intent memory m, uint256 chainId)
491+
internal
492+
returns (bytes32 digest)
493+
{
494+
uint256 currChain = block.chainid;
495+
vm.chainId(chainId);
496+
digest = oc.computeDigest(m);
497+
vm.chainId(currChain);
498+
}
493499
}

‎test/Benchmark.t.sol‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,7 +1714,7 @@ contract BenchmarkTest is BaseTest {
17141714
) internal view returns (bytes[] memory) {
17151715
bytes[] memory encodedIntents = new bytes[](delegatedEOAs.length);
17161716
for (uint256 i = 0; i < delegatedEOAs.length; i++) {
1717-
Orchestrator.Intent memory u;
1717+
ICommon.Intent memory u;
17181718
u.eoa = delegatedEOAs[i].eoa;
17191719
u.nonce = 0;
17201720
u.combinedGas = 1000000;
@@ -1780,7 +1780,7 @@ contract BenchmarkTest is BaseTest {
17801780
d.d.setSpendLimit(k.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, 1 ether);
17811781
vm.stopPrank();
17821782

1783-
Orchestrator.Intent memory u;
1783+
ICommon.Intent memory u;
17841784
u.eoa = d.eoa;
17851785
u.nonce = 0;
17861786
u.combinedGas = 1000000;

0 commit comments

Comments
 (0)