From 41b3de944171da92636ca76e26abb055ace6af0c Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 16:46:14 -0700 Subject: [PATCH 1/9] machine: support multistack in hashes and proofs --- src/challenge/ChallengeLib.sol | 6 +- src/state/Deserialize.sol | 113 +++++++++++++++++++-------------- src/state/Machine.sol | 11 +++- src/state/MultiStack.sol | 20 ++++++ 4 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 src/state/MultiStack.sol diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index eb1f1bd4..e4781970 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -63,18 +63,22 @@ library ChallengeLib { ValueStack memory internalStack; StackFrameWindow memory frameStack; GuardStack memory guardStack; + MultiStack memory emptyMultiStack; Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, + valueMultiStack:emptyMultiStack, internalStack: internalStack, frameStack: frameStack, + frameMultiStack: emptyMultiStack, guardStack: guardStack, globalStateHash: globalStateHash, moduleIdx: 0, functionIdx: 0, functionPc: 0, - modulesRoot: wasmModuleRoot + modulesRoot: wasmModuleRoot, + cothread: false }); return mach.hash(); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index bf68b78e..2afcbd4e 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "./Value.sol"; import "./ValueStack.sol"; import "./Machine.sol"; +import "./MultiStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; import "./GuardStack.sol"; @@ -130,6 +131,19 @@ library Deserialize { stack = ValueStack({proved: ValueArray(proved), remainingHash: remainingHash}); } + function multiStack(bytes calldata proof, uint256 startOffset) + internal + pure + returns (MultiStack memory multistack, uint256 offset) + { + offset = startOffset; + bytes32 inactiveStackHash; + (inactiveStackHash, offset) = b32(proof, offset); + bytes32 remainingHash; + (remainingHash, offset) = b32(proof, offset); + multistack = MultiStack({inactiveStackHash: inactiveStackHash, remainingHash: remainingHash}); + } + function instruction(bytes calldata proof, uint256 startOffset) internal pure @@ -193,15 +207,15 @@ library Deserialize { offset = startOffset; Value memory onErrorPc; bytes32 frameStack; - bytes32 valueStack; + bytes32 valueStackHash; bytes32 interStack; (frameStack, offset) = b32(proof, offset); - (valueStack, offset) = b32(proof, offset); + (valueStackHash, offset) = b32(proof, offset); (interStack, offset) = b32(proof, offset); (onErrorPc, offset) = value(proof, offset); guard = ErrorGuard({ frameStack: frameStack, - valueStack: valueStack, + valueStack: valueStackHash, interStack: interStack, onErrorPc: onErrorPc }); @@ -296,52 +310,59 @@ library Deserialize { returns (Machine memory mach, uint256 offset) { offset = startOffset; - MachineStatus status; { - uint8 statusU8; - (statusU8, offset) = u8(proof, offset); - if (statusU8 == 0) { - status = MachineStatus.RUNNING; - } else if (statusU8 == 1) { - status = MachineStatus.FINISHED; - } else if (statusU8 == 2) { - status = MachineStatus.ERRORED; - } else if (statusU8 == 3) { - status = MachineStatus.TOO_FAR; - } else { - revert("UNKNOWN_MACH_STATUS"); + MachineStatus status; + { + uint8 statusU8; + (statusU8, offset) = u8(proof, offset); + if (statusU8 == 0) { + status = MachineStatus.RUNNING; + } else if (statusU8 == 1) { + status = MachineStatus.FINISHED; + } else if (statusU8 == 2) { + status = MachineStatus.ERRORED; + } else if (statusU8 == 3) { + status = MachineStatus.TOO_FAR; + } else { + revert("UNKNOWN_MACH_STATUS"); + } } + ValueStack memory values; + ValueStack memory internalStack; + MultiStack memory valuesMulti; + StackFrameWindow memory frameStack; + MultiStack memory framesMulti; + GuardStack memory guards; + (values, offset) = valueStack(proof, offset); + (valuesMulti, offset) = multiStack(proof, offset); + (internalStack, offset) = valueStack(proof, offset); + (frameStack, offset) = stackFrameWindow(proof, offset); + (framesMulti, offset) = multiStack(proof, offset); + (guards, offset) = guardStack(proof, offset); + mach = Machine({ + status: status, + valueStack: values, + valueMultiStack: valuesMulti, + internalStack: internalStack, + frameStack: frameStack, + frameMultiStack: framesMulti, + guardStack: guards, + // below this lines vars are initialized later in the function, + // due to solidity's bounds on number of stack variables + globalStateHash: bytes32(0), + moduleIdx: 0, + functionIdx: 0, + functionPc: 0, + modulesRoot: bytes32(0), + cothread: false + }); } - ValueStack memory values; - ValueStack memory internalStack; - bytes32 globalStateHash; - uint32 moduleIdx; - uint32 functionIdx; - uint32 functionPc; - StackFrameWindow memory frameStack; - GuardStack memory guards; - bytes32 modulesRoot; - (values, offset) = valueStack(proof, offset); - (internalStack, offset) = valueStack(proof, offset); - (frameStack, offset) = stackFrameWindow(proof, offset); - (guards, offset) = guardStack(proof, offset); - (globalStateHash, offset) = b32(proof, offset); - (moduleIdx, offset) = u32(proof, offset); - (functionIdx, offset) = u32(proof, offset); - (functionPc, offset) = u32(proof, offset); - (modulesRoot, offset) = b32(proof, offset); - mach = Machine({ - status: status, - valueStack: values, - internalStack: internalStack, - frameStack: frameStack, - guardStack: guards, - globalStateHash: globalStateHash, - moduleIdx: moduleIdx, - functionIdx: functionIdx, - functionPc: functionPc, - modulesRoot: modulesRoot - }); + (mach.globalStateHash, offset) = b32(proof, offset); + (mach.moduleIdx, offset) = u32(proof, offset); + (mach.functionIdx, offset) = u32(proof, offset); + (mach.functionPc, offset) = u32(proof, offset); + (mach.modulesRoot, offset) = b32(proof, offset); + (mach.cothread, offset) = boolean(proof, offset); } function merkleProof(bytes calldata proof, uint256 startOffset) diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 47fb9b57..8458f694 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "./ValueStack.sol"; import "./Instructions.sol"; +import "./MultiStack.sol"; import "./StackFrame.sol"; import "./GuardStack.sol"; @@ -19,29 +20,35 @@ enum MachineStatus { struct Machine { MachineStatus status; ValueStack valueStack; + MultiStack valueMultiStack; ValueStack internalStack; StackFrameWindow frameStack; + MultiStack frameMultiStack; GuardStack guardStack; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; uint32 functionPc; bytes32 modulesRoot; + bool cothread; } library MachineLib { using StackFrameLib for StackFrameWindow; using GuardStackLib for GuardStack; using ValueStackLib for ValueStack; + using MultiStackLib for MultiStack; function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { + bytes32 valueMultiHash = mach.valueMultiStack.hash(mach.valueStack.hash(), mach.cothread); + bytes32 frameMultiHash = mach.frameMultiStack.hash(mach.frameStack.hash(), mach.cothread); bytes memory preimage = abi.encodePacked( "Machine running:", - mach.valueStack.hash(), + valueMultiHash, mach.internalStack.hash(), - mach.frameStack.hash(), + frameMultiHash, mach.globalStateHash, mach.moduleIdx, mach.functionIdx, diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol new file mode 100644 index 00000000..1f6efe56 --- /dev/null +++ b/src/state/MultiStack.sol @@ -0,0 +1,20 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; + +struct MultiStack { + bytes32 inactiveStackHash; + bytes32 remainingHash; +} + +library MultiStackLib { + function hash(MultiStack memory multi, bytes32 activeStackHash, bool cothread) internal pure returns (bytes32 h) { + if (cothread) { + return keccak256(abi.encodePacked("Multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash)); + } else { + return keccak256(abi.encodePacked("Multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash)); + } + } +} From 95547405eb41647a7f79e77d7e7be821883ce61f Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 17:40:37 -0700 Subject: [PATCH 2/9] remove error guard's enable replaced by machine.cothread --- src/osp/OneStepProofEntry.sol | 7 +++++-- src/osp/OneStepProverHostIo.sol | 13 ------------- src/state/Deserialize.sol | 4 +--- src/state/GuardStack.sol | 5 ----- src/state/Instructions.sol | 1 - src/state/Machine.sol | 8 ++------ 6 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index b22d43e7..d736edfd 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -117,7 +117,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.SET_ERROR_POLICY) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) ) { prover = proverHostIo; } else { @@ -132,8 +132,11 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.guardStack.canPop()) { + if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { ErrorGuard memory guard = mach.guardStack.pop(); + mach.cothread = false; + mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); + mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); mach.frameStack.overwrite(guard.frameStack); mach.valueStack.overwrite(guard.valueStack); mach.internalStack.overwrite(guard.interStack); diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index ea027766..b8158978 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -412,17 +412,6 @@ contract OneStepProverHostIo is IOneStepProver { mach.guardStack.pop(); } - function executeSetErrorPolicy( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata inst, - bytes calldata - ) internal pure { - uint32 status = mach.valueStack.pop().assumeI32(); - mach.guardStack.enabled = status != 0; - } - function executeGlobalStateAccess( ExecutionContext calldata, Machine memory mach, @@ -492,8 +481,6 @@ contract OneStepProverHostIo is IOneStepProver { impl = executePushErrorGuard; } else if (opcode == Instructions.POP_ERROR_GUARD) { impl = executePopErrorGuard; - } else if (opcode == Instructions.SET_ERROR_POLICY) { - impl = executeSetErrorPolicy; } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 2afcbd4e..8938b7c6 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -228,9 +228,7 @@ library Deserialize { { offset = startOffset; bytes32 remainingHash; - bool enabled; bool empty; - (enabled, offset) = boolean(proof, offset); (empty, offset) = boolean(proof, offset); (remainingHash, offset) = b32(proof, offset); @@ -241,7 +239,7 @@ library Deserialize { proved = new ErrorGuard[](1); (proved[0], offset) = errorGuard(proof, offset); } - window = GuardStack({proved: proved, remainingHash: remainingHash, enabled: enabled}); + window = GuardStack({proved: proved, remainingHash: remainingHash}); } function moduleMemory(bytes calldata proof, uint256 startOffset) diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol index d4500d73..59c75892 100644 --- a/src/state/GuardStack.sol +++ b/src/state/GuardStack.sol @@ -16,7 +16,6 @@ struct ErrorGuard { struct GuardStack { ErrorGuard[] proved; bytes32 remainingHash; - bool enabled; } library GuardStackLib { @@ -57,10 +56,6 @@ library GuardStackLib { } } - function canPop(GuardStack memory guards) internal pure returns (bool) { - return guards.enabled && !empty(guards); - } - function empty(GuardStack memory guards) internal pure returns (bool) { return guards.proved.length == 0 && guards.remainingHash == 0; } diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index edac4c77..2d04dc5c 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -149,7 +149,6 @@ library Instructions { uint16 internal constant UNLINK_MODULE = 0x8024; uint16 internal constant PUSH_ERROR_GUARD = 0x8025; uint16 internal constant POP_ERROR_GUARD = 0x8026; - uint16 internal constant SET_ERROR_POLICY = 0x8027; uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 8458f694..8f81f8c8 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -56,16 +56,12 @@ library MachineLib { mach.modulesRoot ); - if (mach.guardStack.empty() && !mach.guardStack.enabled) { + if (mach.guardStack.empty() && !mach.cothread) { return keccak256(preimage); } else { - bytes1 flag = 0x00; - if (mach.guardStack.enabled) { - flag = 0x01; - } return keccak256( - abi.encodePacked(preimage, "With guards:", flag, mach.guardStack.hash()) + abi.encodePacked(preimage, "With guards:", mach.guardStack.hash()) ); } } else if (mach.status == MachineStatus.FINISHED) { From b0a14ee3b49d213ea490b30d57c7d275a7f4372e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 19:40:02 -0700 Subject: [PATCH 3/9] fix introducing cothreads --- src/challenge/ChallengeLib.sol | 5 +++- src/state/Deserialize.sol | 19 +++++++-------- src/state/Machine.sol | 28 +++++++++++++++++++---- src/state/MultiStack.sol | 42 +++++++++++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index e4781970..0bdc3504 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -5,11 +5,13 @@ pragma solidity ^0.8.0; import "../state/Machine.sol"; +import "../state/MultiStack.sol"; import "../state/GlobalState.sol"; library ChallengeLib { using MachineLib for Machine; using ChallengeLib for Challenge; + using MultiStackLib for MultiStack; /// @dev It's assumed that that uninitialzed challenges have mode NONE enum ChallengeMode { @@ -64,11 +66,12 @@ library ChallengeLib { StackFrameWindow memory frameStack; GuardStack memory guardStack; MultiStack memory emptyMultiStack; + emptyMultiStack.setEmpty(); Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, - valueMultiStack:emptyMultiStack, + valueMultiStack: emptyMultiStack, internalStack: internalStack, frameStack: frameStack, frameMultiStack: emptyMultiStack, diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 8938b7c6..08680f73 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -141,7 +141,10 @@ library Deserialize { (inactiveStackHash, offset) = b32(proof, offset); bytes32 remainingHash; (remainingHash, offset) = b32(proof, offset); - multistack = MultiStack({inactiveStackHash: inactiveStackHash, remainingHash: remainingHash}); + multistack = MultiStack({ + inactiveStackHash: inactiveStackHash, + remainingHash: remainingHash + }); } function instruction(bytes calldata proof, uint256 startOffset) @@ -345,14 +348,12 @@ library Deserialize { frameStack: frameStack, frameMultiStack: framesMulti, guardStack: guards, - // below this lines vars are initialized later in the function, - // due to solidity's bounds on number of stack variables - globalStateHash: bytes32(0), - moduleIdx: 0, - functionIdx: 0, - functionPc: 0, - modulesRoot: bytes32(0), - cothread: false + globalStateHash: bytes32(0), // filled later + moduleIdx: 0, // filled later + functionIdx: 0, // filled later + functionPc: 0, // filled later + modulesRoot: bytes32(0), // filled later + cothread: false // filled later }); } (mach.globalStateHash, offset) = b32(proof, offset); diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 8f81f8c8..c18f1710 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -42,8 +42,14 @@ library MachineLib { function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { - bytes32 valueMultiHash = mach.valueMultiStack.hash(mach.valueStack.hash(), mach.cothread); - bytes32 frameMultiHash = mach.frameMultiStack.hash(mach.frameStack.hash(), mach.cothread); + bytes32 valueMultiHash = mach.valueMultiStack.hash( + mach.valueStack.hash(), + mach.cothread + ); + bytes32 frameMultiHash = mach.frameMultiStack.hash( + mach.frameStack.hash(), + mach.cothread + ); bytes memory preimage = abi.encodePacked( "Machine running:", valueMultiHash, @@ -60,9 +66,7 @@ library MachineLib { return keccak256(preimage); } else { return - keccak256( - abi.encodePacked(preimage, "With guards:", mach.guardStack.hash()) - ); + keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); } } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); @@ -75,6 +79,20 @@ library MachineLib { } } + function switchCoThread(Machine memory mach) internal pure { + bytes32 newActiveValue = mach.valueMultiStack.inactiveStackHash; + bytes32 newActiveFrame = mach.frameMultiStack.inactiveStackHash; + if (newActiveFrame == bytes32(0) || newActiveValue == bytes32(0)) { + mach.status = MachineStatus.ERRORED; + return; + } + mach.cothread = !mach.cothread; + mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); + mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); + mach.frameStack.overwrite(newActiveValue); + mach.valueStack.overwrite(newActiveFrame); + } + function setPc(Machine memory mach, Value memory pc) internal pure { if (pc.valueType == ValueType.REF_NULL) { mach.status = MachineStatus.ERRORED; diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index 1f6efe56..cb539891 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -10,11 +10,47 @@ struct MultiStack { } library MultiStackLib { - function hash(MultiStack memory multi, bytes32 activeStackHash, bool cothread) internal pure returns (bytes32 h) { + bytes32 internal constant NO_STACK_HASH = ~bytes32(0); + + function hash( + MultiStack memory multi, + bytes32 activeStackHash, + bool cothread + ) internal pure returns (bytes32 h) { if (cothread) { - return keccak256(abi.encodePacked("Multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash)); + return + keccak256( + abi.encodePacked( + "multistack: ", + multi.inactiveStackHash, + activeStackHash, + multi.remainingHash + ) + ); } else { - return keccak256(abi.encodePacked("Multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash)); + return + keccak256( + abi.encodePacked( + "multistack: ", + activeStackHash, + multi.inactiveStackHash, + multi.remainingHash + ) + ); + } + } + + function setEmpty(MultiStack memory multi) internal pure { + multi.inactiveStackHash = NO_STACK_HASH; + multi.remainingHash = NO_STACK_HASH; + } + + function pushNew(MultiStack memory multi) internal pure { + if (multi.inactiveStackHash != NO_STACK_HASH) { + multi.remainingHash = keccak256( + abi.encodePacked("cothread: ", multi.inactiveStackHash, multi.remainingHash) + ); } + multi.inactiveStackHash = 0; } } From ee178c851ec3fa741b5d3f12cb2f98ee0e9e68d1 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 19:40:25 -0700 Subject: [PATCH 4/9] add cothread onestepproofs --- src/osp/OneStepProofEntry.sol | 11 ++--- src/osp/OneStepProverHostIo.sol | 81 ++++++++++++++++++++++++++++++++- src/state/Instructions.sol | 4 ++ 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index d736edfd..19076038 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -117,7 +117,8 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) || + (opcode >= Instructions.NEW_COTHREAD && opcode <= Instructions.SWITCH_COTHREAD) ) { prover = proverHostIo; } else { @@ -134,11 +135,9 @@ contract OneStepProofEntry is IOneStepProofEntry { if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { ErrorGuard memory guard = mach.guardStack.pop(); - mach.cothread = false; - mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); - mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); - mach.frameStack.overwrite(guard.frameStack); - mach.valueStack.overwrite(guard.valueStack); + mach.frameMultiStack.inactiveStackHash = guard.frameStack; + mach.valueMultiStack.inactiveStackHash = guard.valueStack; + mach.switchCoThread(); mach.internalStack.overwrite(guard.interStack); mach.setPc(guard.onErrorPc); diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index b8158978..c9c8cc1c 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "../state/Value.sol"; import "../state/Machine.sol"; import "../state/MerkleProof.sol"; +import "../state/MultiStack.sol"; import "../state/Deserialize.sol"; import "../state/ModuleMemory.sol"; import "./IOneStepProver.sol"; @@ -15,8 +16,10 @@ import "../bridge/IBridge.sol"; contract OneStepProverHostIo is IOneStepProver { using GlobalStateLib for GlobalState; + using MachineLib for Machine; using MerkleProofLib for MerkleProof; using ModuleMemoryLib for ModuleMemory; + using MultiStackLib for MultiStack; using ValueLib for Value; using ValueStackLib for ValueStack; using StackFrameLib for StackFrameWindow; @@ -442,6 +445,80 @@ contract OneStepProverHostIo is IOneStepProver { mach.globalStateHash = state.hash(); } + function executeNewCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata + ) internal pure { + if (mach.cothread) { + // cannot create new cothread from inside cothread + mach.status = MachineStatus.ERRORED; + return; + } + mach.frameMultiStack.pushNew(); + } + + function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { + uint256 proofOffset = 0; + bytes32 newInactiveCoThread; + bytes32 newRemaining; + (newInactiveCoThread, proofOffset) = Deserialize.b32(proof, proofOffset); + (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); + if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { + require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + return; + } + require( + keccak256(abi.encodePacked("cothread: ", newInactiveCoThread, newRemaining)) == + multi.remainingHash, + "WRONG_COTHREAD_POP" + ); + multi.remainingHash = newRemaining; + multi.inactiveStackHash = newInactiveCoThread; + return; + } + + function executePopCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata proof + ) internal pure { + if (mach.cothread) { + // cannot pop cothread from inside cothread + mach.status = MachineStatus.ERRORED; + return; + } + if (mach.frameMultiStack.inactiveStackHash == MultiStackLib.NO_STACK_HASH) { + // cannot pop cothread if there isn't one + mach.status = MachineStatus.ERRORED; + return; + } + proovePopCothread(mach.valueMultiStack, proof); + proovePopCothread(mach.frameMultiStack, proof[64:]); + } + + function executeSwitchCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata inst, + bytes calldata + ) internal pure { + if (mach.frameMultiStack.inactiveStackHash == MultiStackLib.NO_STACK_HASH) { + // cannot switch cothread if there isn't one + mach.status = MachineStatus.ERRORED; + return; + } + bool jumpToCoThread = (inst.argumentData != 0); + if (jumpToCoThread != mach.cothread) { + mach.switchCoThread(); + } + } + function executeOneStep( ExecutionContext calldata execCtx, Machine calldata startMach, @@ -481,7 +558,9 @@ contract OneStepProverHostIo is IOneStepProver { impl = executePushErrorGuard; } else if (opcode == Instructions.POP_ERROR_GUARD) { impl = executePopErrorGuard; - } else { + } else if (opcode == Instructions.NEW_COTHREAD) {} else if ( + opcode == Instructions.POP_COTHREAD + ) {} else if (opcode == Instructions.SWITCH_COTHREAD) {} else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 2d04dc5c..45108211 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -150,6 +150,10 @@ library Instructions { uint16 internal constant PUSH_ERROR_GUARD = 0x8025; uint16 internal constant POP_ERROR_GUARD = 0x8026; + uint16 internal constant NEW_COTHREAD = 0x8030; + uint16 internal constant POP_COTHREAD = 0x8031; + uint16 internal constant SWITCH_COTHREAD = 0x8032; + uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; From c735bd2f7d338e4bc9caf4a267f0675fefca23d1 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 1 Feb 2024 12:20:47 -0700 Subject: [PATCH 5/9] removing error guard --- src/challenge/ChallengeLib.sol | 2 - src/osp/OneStepProofEntry.sol | 13 ++---- src/osp/OneStepProverHostIo.sol | 40 +++------------- src/state/Deserialize.sol | 47 ------------------- src/state/GuardStack.sol | 82 --------------------------------- src/state/Instructions.sol | 2 - src/state/Machine.sol | 11 +---- 7 files changed, 11 insertions(+), 186 deletions(-) delete mode 100644 src/state/GuardStack.sol diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index 0bdc3504..398e8b47 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -64,7 +64,6 @@ library ChallengeLib { ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); ValueStack memory internalStack; StackFrameWindow memory frameStack; - GuardStack memory guardStack; MultiStack memory emptyMultiStack; emptyMultiStack.setEmpty(); @@ -75,7 +74,6 @@ library ChallengeLib { internalStack: internalStack, frameStack: frameStack, frameMultiStack: emptyMultiStack, - guardStack: guardStack, globalStateHash: globalStateHash, moduleIdx: 0, functionIdx: 0, diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index 19076038..c146e4ce 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -15,7 +15,6 @@ contract OneStepProofEntry is IOneStepProofEntry { using MachineLib for Machine; using ValueStackLib for ValueStack; - using GuardStackLib for GuardStack; using StackFrameLib for StackFrameWindow; IOneStepProver public prover0; @@ -117,7 +116,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) || + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.UNLINK_MODULE) || (opcode >= Instructions.NEW_COTHREAD && opcode <= Instructions.SWITCH_COTHREAD) ) { prover = proverHostIo; @@ -133,16 +132,10 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { - ErrorGuard memory guard = mach.guardStack.pop(); - mach.frameMultiStack.inactiveStackHash = guard.frameStack; - mach.valueMultiStack.inactiveStackHash = guard.valueStack; + if (mach.status == MachineStatus.ERRORED && mach.cothread) { mach.switchCoThread(); - mach.internalStack.overwrite(guard.interStack); - mach.setPc(guard.onErrorPc); - // indicate an error and continue - mach.valueStack.push(ValueLib.newI32(0)); + mach.valueStack.push(ValueLib.newI32(1)); mach.status = MachineStatus.RUNNING; } diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index c9c8cc1c..ce0fc38a 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -23,7 +23,6 @@ contract OneStepProverHostIo is IOneStepProver { using ValueLib for Value; using ValueStackLib for ValueStack; using StackFrameLib for StackFrameWindow; - using GuardStackLib for GuardStack; uint256 private constant LEAF_SIZE = 32; uint256 private constant INBOX_NUM = 2; @@ -390,31 +389,6 @@ contract OneStepProverHostIo is IOneStepProver { } } - function executePushErrorGuard( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - bytes32 frames = mach.frameStack.hash(); - bytes32 values = mach.valueStack.hash(); - bytes32 inters = mach.internalStack.hash(); - Value memory onError = ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); - mach.guardStack.push(GuardStackLib.newErrorGuard(frames, values, inters, onError)); - mach.valueStack.push(ValueLib.newI32(1)); - } - - function executePopErrorGuard( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - mach.guardStack.pop(); - } - function executeGlobalStateAccess( ExecutionContext calldata, Machine memory mach, @@ -554,13 +528,13 @@ contract OneStepProverHostIo is IOneStepProver { impl = executeLinkModule; } else if (opcode == Instructions.UNLINK_MODULE) { impl = executeUnlinkModule; - } else if (opcode == Instructions.PUSH_ERROR_GUARD) { - impl = executePushErrorGuard; - } else if (opcode == Instructions.POP_ERROR_GUARD) { - impl = executePopErrorGuard; - } else if (opcode == Instructions.NEW_COTHREAD) {} else if ( - opcode == Instructions.POP_COTHREAD - ) {} else if (opcode == Instructions.SWITCH_COTHREAD) {} else { + } else if (opcode == Instructions.NEW_COTHREAD) { + impl = executeNewCoThread; + } else if (opcode == Instructions.POP_COTHREAD) { + impl = executePopCoThread; + } else if (opcode == Instructions.SWITCH_COTHREAD) { + impl = executeSwitchCoThread; + } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 08680f73..28a19813 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -10,7 +10,6 @@ import "./Machine.sol"; import "./MultiStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; -import "./GuardStack.sol"; import "./MerkleProof.sol"; import "./ModuleMemoryCompact.sol"; import "./Module.sol"; @@ -202,49 +201,6 @@ library Deserialize { window = StackFrameWindow({proved: proved, remainingHash: remainingHash}); } - function errorGuard(bytes calldata proof, uint256 startOffset) - internal - pure - returns (ErrorGuard memory guard, uint256 offset) - { - offset = startOffset; - Value memory onErrorPc; - bytes32 frameStack; - bytes32 valueStackHash; - bytes32 interStack; - (frameStack, offset) = b32(proof, offset); - (valueStackHash, offset) = b32(proof, offset); - (interStack, offset) = b32(proof, offset); - (onErrorPc, offset) = value(proof, offset); - guard = ErrorGuard({ - frameStack: frameStack, - valueStack: valueStackHash, - interStack: interStack, - onErrorPc: onErrorPc - }); - } - - function guardStack(bytes calldata proof, uint256 startOffset) - internal - pure - returns (GuardStack memory window, uint256 offset) - { - offset = startOffset; - bytes32 remainingHash; - bool empty; - (empty, offset) = boolean(proof, offset); - (remainingHash, offset) = b32(proof, offset); - - ErrorGuard[] memory proved; - if (empty) { - proved = new ErrorGuard[](0); - } else { - proved = new ErrorGuard[](1); - (proved[0], offset) = errorGuard(proof, offset); - } - window = GuardStack({proved: proved, remainingHash: remainingHash}); - } - function moduleMemory(bytes calldata proof, uint256 startOffset) internal pure @@ -333,13 +289,11 @@ library Deserialize { MultiStack memory valuesMulti; StackFrameWindow memory frameStack; MultiStack memory framesMulti; - GuardStack memory guards; (values, offset) = valueStack(proof, offset); (valuesMulti, offset) = multiStack(proof, offset); (internalStack, offset) = valueStack(proof, offset); (frameStack, offset) = stackFrameWindow(proof, offset); (framesMulti, offset) = multiStack(proof, offset); - (guards, offset) = guardStack(proof, offset); mach = Machine({ status: status, valueStack: values, @@ -347,7 +301,6 @@ library Deserialize { internalStack: internalStack, frameStack: frameStack, frameMultiStack: framesMulti, - guardStack: guards, globalStateHash: bytes32(0), // filled later moduleIdx: 0, // filled later functionIdx: 0, // filled later diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol deleted file mode 100644 index 59c75892..00000000 --- a/src/state/GuardStack.sol +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2023, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.0; - -import "./Value.sol"; - -struct ErrorGuard { - bytes32 frameStack; - bytes32 valueStack; - bytes32 interStack; - Value onErrorPc; -} - -struct GuardStack { - ErrorGuard[] proved; - bytes32 remainingHash; -} - -library GuardStackLib { - using ValueLib for Value; - - function newErrorGuard( - bytes32 frameStack, - bytes32 valueStack, - bytes32 interStack, - Value memory onErrorPc - ) internal pure returns (ErrorGuard memory) { - return - ErrorGuard({ - frameStack: frameStack, - valueStack: valueStack, - interStack: interStack, - onErrorPc: onErrorPc - }); - } - - function hash(ErrorGuard memory guard) internal pure returns (bytes32) { - return - keccak256( - abi.encodePacked( - "Error guard:", - guard.frameStack, - guard.valueStack, - guard.interStack, - guard.onErrorPc.hash() - ) - ); - } - - function hash(GuardStack memory guards) internal pure returns (bytes32 h) { - h = guards.remainingHash; - for (uint256 i = 0; i < guards.proved.length; i++) { - h = keccak256(abi.encodePacked("Guard stack:", hash(guards.proved[i]), h)); - } - } - - function empty(GuardStack memory guards) internal pure returns (bool) { - return guards.proved.length == 0 && guards.remainingHash == 0; - } - - function peek(GuardStack memory guards) internal pure returns (ErrorGuard memory) { - require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); - return guards.proved[0]; - } - - function pop(GuardStack memory guards) internal pure returns (ErrorGuard memory frame) { - require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); - frame = guards.proved[0]; - guards.proved = new ErrorGuard[](0); - } - - function push(GuardStack memory guards, ErrorGuard memory guard) internal pure { - ErrorGuard[] memory newProved = new ErrorGuard[](guards.proved.length + 1); - for (uint256 i = 0; i < guards.proved.length; i++) { - newProved[i] = guards.proved[i]; - } - newProved[guards.proved.length] = guard; - guards.proved = newProved; - } -} diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 45108211..9c67d518 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -147,8 +147,6 @@ library Instructions { uint16 internal constant HALT_AND_SET_FINISHED = 0x8022; uint16 internal constant LINK_MODULE = 0x8023; uint16 internal constant UNLINK_MODULE = 0x8024; - uint16 internal constant PUSH_ERROR_GUARD = 0x8025; - uint16 internal constant POP_ERROR_GUARD = 0x8026; uint16 internal constant NEW_COTHREAD = 0x8030; uint16 internal constant POP_COTHREAD = 0x8031; diff --git a/src/state/Machine.sol b/src/state/Machine.sol index c18f1710..f36dc9d0 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -8,7 +8,6 @@ import "./ValueStack.sol"; import "./Instructions.sol"; import "./MultiStack.sol"; import "./StackFrame.sol"; -import "./GuardStack.sol"; enum MachineStatus { RUNNING, @@ -24,7 +23,6 @@ struct Machine { ValueStack internalStack; StackFrameWindow frameStack; MultiStack frameMultiStack; - GuardStack guardStack; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; @@ -35,7 +33,6 @@ struct Machine { library MachineLib { using StackFrameLib for StackFrameWindow; - using GuardStackLib for GuardStack; using ValueStackLib for ValueStack; using MultiStackLib for MultiStack; @@ -61,13 +58,7 @@ library MachineLib { mach.functionPc, mach.modulesRoot ); - - if (mach.guardStack.empty() && !mach.cothread) { - return keccak256(preimage); - } else { - return - keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); - } + return keccak256(preimage); } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); } else if (mach.status == MachineStatus.ERRORED) { From 76d8d07161a33ebf0dbd362e55aba0399fb4ec8d Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 6 Feb 2024 19:28:37 -0700 Subject: [PATCH 6/9] fix cothread support, use recoveryPc --- src/challenge/ChallengeLib.sol | 4 +- src/osp/OneStepProofEntry.sol | 8 ++-- src/osp/OneStepProverHostIo.sol | 36 +++++++++++------ src/state/Deserialize.sol | 6 +-- src/state/Machine.sol | 69 +++++++++++++++++++++++++-------- src/state/MultiStack.sol | 10 +++-- 6 files changed, 93 insertions(+), 40 deletions(-) diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index 398e8b47..30a4fc1d 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -78,8 +78,8 @@ library ChallengeLib { moduleIdx: 0, functionIdx: 0, functionPc: 0, - modulesRoot: wasmModuleRoot, - cothread: false + recoveryPc: MachineLib.NO_RECOVERY_PC, + modulesRoot: wasmModuleRoot }); return mach.hash(); } diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index c146e4ce..4d03e455 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -132,10 +132,10 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.cothread) { - mach.switchCoThread(); - // indicate an error and continue - mach.valueStack.push(ValueLib.newI32(1)); + if (mach.status == MachineStatus.ERRORED && mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { + // capture error, recover into main thread. + mach.switchCoThreadStacks(); + mach.setPcFromRecovery(); mach.status = MachineStatus.RUNNING; } diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index ce0fc38a..db30abeb 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -426,12 +426,13 @@ contract OneStepProverHostIo is IOneStepProver { Instruction calldata, bytes calldata ) internal pure { - if (mach.cothread) { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { // cannot create new cothread from inside cothread mach.status = MachineStatus.ERRORED; return; } mach.frameMultiStack.pushNew(); + mach.valueMultiStack.pushNew(); } function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { @@ -442,13 +443,14 @@ contract OneStepProverHostIo is IOneStepProver { (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); - return; + require(newRemaining == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + } else { + require( + keccak256(abi.encodePacked("cothread:", newInactiveCoThread, newRemaining)) == + multi.remainingHash, + "WRONG_COTHREAD_POP" + ); } - require( - keccak256(abi.encodePacked("cothread: ", newInactiveCoThread, newRemaining)) == - multi.remainingHash, - "WRONG_COTHREAD_POP" - ); multi.remainingHash = newRemaining; multi.inactiveStackHash = newInactiveCoThread; return; @@ -461,7 +463,7 @@ contract OneStepProverHostIo is IOneStepProver { Instruction calldata, bytes calldata proof ) internal pure { - if (mach.cothread) { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { // cannot pop cothread from inside cothread mach.status = MachineStatus.ERRORED; return; @@ -487,10 +489,22 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.ERRORED; return; } - bool jumpToCoThread = (inst.argumentData != 0); - if (jumpToCoThread != mach.cothread) { - mach.switchCoThread(); + if (inst.argumentData == 0) { + if (mach.recoveryPc == MachineLib.NO_RECOVERY_PC) { + // switching to main thread, from main thread + mach.status = MachineStatus.ERRORED; + return; + } + mach.recoveryPc = MachineLib.NO_RECOVERY_PC; + } else { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { + // switching from cothread to cothread + mach.status = MachineStatus.ERRORED; + return; + } + mach.setRecoveryFromPc(uint32(inst.argumentData)); } + mach.switchCoThreadStacks(); } function executeOneStep( diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 28a19813..19303168 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -305,16 +305,16 @@ library Deserialize { moduleIdx: 0, // filled later functionIdx: 0, // filled later functionPc: 0, // filled later - modulesRoot: bytes32(0), // filled later - cothread: false // filled later + recoveryPc: bytes32(0), // filled later + modulesRoot: bytes32(0) // filled later }); } (mach.globalStateHash, offset) = b32(proof, offset); (mach.moduleIdx, offset) = u32(proof, offset); (mach.functionIdx, offset) = u32(proof, offset); (mach.functionPc, offset) = u32(proof, offset); + (mach.recoveryPc, offset) = b32(proof, offset); (mach.modulesRoot, offset) = b32(proof, offset); - (mach.cothread, offset) = boolean(proof, offset); } function merkleProof(bytes calldata proof, uint256 startOffset) diff --git a/src/state/Machine.sol b/src/state/Machine.sol index f36dc9d0..2a67e639 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -27,8 +27,8 @@ struct Machine { uint32 moduleIdx; uint32 functionIdx; uint32 functionPc; + bytes32 recoveryPc; bytes32 modulesRoot; - bool cothread; } library MachineLib { @@ -36,16 +36,18 @@ library MachineLib { using ValueStackLib for ValueStack; using MultiStackLib for MultiStack; + bytes32 internal constant NO_RECOVERY_PC = ~bytes32(0); + function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { bytes32 valueMultiHash = mach.valueMultiStack.hash( mach.valueStack.hash(), - mach.cothread + mach.recoveryPc != NO_RECOVERY_PC ); bytes32 frameMultiHash = mach.frameMultiStack.hash( mach.frameStack.hash(), - mach.cothread + mach.recoveryPc != NO_RECOVERY_PC ); bytes memory preimage = abi.encodePacked( "Machine running:", @@ -56,6 +58,7 @@ library MachineLib { mach.moduleIdx, mach.functionIdx, mach.functionPc, + mach.recoveryPc, mach.modulesRoot ); return keccak256(preimage); @@ -70,32 +73,66 @@ library MachineLib { } } - function switchCoThread(Machine memory mach) internal pure { + function switchCoThreadStacks(Machine memory mach) internal pure { bytes32 newActiveValue = mach.valueMultiStack.inactiveStackHash; bytes32 newActiveFrame = mach.frameMultiStack.inactiveStackHash; - if (newActiveFrame == bytes32(0) || newActiveValue == bytes32(0)) { + if ( + newActiveFrame == MultiStackLib.NO_STACK_HASH || + newActiveValue == MultiStackLib.NO_STACK_HASH + ) { mach.status = MachineStatus.ERRORED; return; } - mach.cothread = !mach.cothread; mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); - mach.frameStack.overwrite(newActiveValue); - mach.valueStack.overwrite(newActiveFrame); + mach.frameStack.overwrite(newActiveFrame); + mach.valueStack.overwrite(newActiveValue); } - function setPc(Machine memory mach, Value memory pc) internal pure { - if (pc.valueType == ValueType.REF_NULL) { - mach.status = MachineStatus.ERRORED; - return; + function setPcFromData(Machine memory mach, uint256 data) internal pure returns (bool) { + if (data >> 96 != 0) { + return false; } - uint256 data = pc.contents; - require(pc.valueType == ValueType.INTERNAL_REF, "INVALID_PC_TYPE"); - require(data >> 96 == 0, "INVALID_PC_DATA"); - mach.functionPc = uint32(data); mach.functionIdx = uint32(data >> 32); mach.moduleIdx = uint32(data >> 64); + return true; + } + + function setPcFromRecovery(Machine memory mach) internal pure returns (bool) { + if (!setPcFromData(mach, uint256(mach.recoveryPc))) { + return false; + } + mach.recoveryPc = NO_RECOVERY_PC; + return true; + } + + function setRecoveryFromPc(Machine memory mach, uint32 offset) internal pure returns (bool) { + if (mach.recoveryPc != NO_RECOVERY_PC) { + return false; + } + + uint256 result; + result = uint256(mach.moduleIdx) << 64; + result = result | (uint256(mach.functionIdx) << 32); + result = result | uint256(mach.functionPc + offset - 1); + mach.recoveryPc = bytes32(result); + return true; + } + + function setPc(Machine memory mach, Value memory pc) internal pure { + if (pc.valueType == ValueType.REF_NULL) { + mach.status = MachineStatus.ERRORED; + return; + } + if (pc.valueType != ValueType.INTERNAL_REF) { + mach.status = MachineStatus.ERRORED; + return; + } + if (!setPcFromData(mach, pc.contents)) { + mach.status = MachineStatus.ERRORED; + return; + } } } diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index cb539891..a051844f 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -16,12 +16,14 @@ library MultiStackLib { MultiStack memory multi, bytes32 activeStackHash, bool cothread - ) internal pure returns (bytes32 h) { + ) internal pure returns (bytes32) { + require(activeStackHash != NO_STACK_HASH, "MULTISTACK_NOSTACK_ACTIVE"); if (cothread) { + require(multi.inactiveStackHash != NO_STACK_HASH, "MULTISTACK_NOSTACK_MAIN"); return keccak256( abi.encodePacked( - "multistack: ", + "multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash @@ -31,7 +33,7 @@ library MultiStackLib { return keccak256( abi.encodePacked( - "multistack: ", + "multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash @@ -48,7 +50,7 @@ library MultiStackLib { function pushNew(MultiStack memory multi) internal pure { if (multi.inactiveStackHash != NO_STACK_HASH) { multi.remainingHash = keccak256( - abi.encodePacked("cothread: ", multi.inactiveStackHash, multi.remainingHash) + abi.encodePacked("cothread:", multi.inactiveStackHash, multi.remainingHash) ); } multi.inactiveStackHash = 0; From 16c0e33a2536981a753b74a78b753ab216dbe7b0 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 14 Feb 2024 13:57:45 -0700 Subject: [PATCH 7/9] multistack: 0 for empty remaining aligns with our stack hashing --- src/osp/OneStepProverHostIo.sol | 4 ++-- src/state/MultiStack.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index db30abeb..b4de8872 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -442,8 +442,8 @@ contract OneStepProverHostIo is IOneStepProver { (newInactiveCoThread, proofOffset) = Deserialize.b32(proof, proofOffset); (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { - require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); - require(newRemaining == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + require(newRemaining == bytes32(0), "WRONG_COTHREAD_EMPTY"); + require(multi.remainingHash == bytes32(0), "WRONG_COTHREAD_EMPTY"); } else { require( keccak256(abi.encodePacked("cothread:", newInactiveCoThread, newRemaining)) == diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index a051844f..51b7f621 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; struct MultiStack { - bytes32 inactiveStackHash; - bytes32 remainingHash; + bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack + bytes32 remainingHash; // 0 if less than 2 cothreads exist } library MultiStackLib { @@ -44,7 +44,7 @@ library MultiStackLib { function setEmpty(MultiStack memory multi) internal pure { multi.inactiveStackHash = NO_STACK_HASH; - multi.remainingHash = NO_STACK_HASH; + multi.remainingHash = 0; } function pushNew(MultiStack memory multi) internal pure { From efcc03da9a274147e263b58aba8c12f046ebd870 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 14 Feb 2024 15:18:05 -0700 Subject: [PATCH 8/9] lint --- src/state/MultiStack.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index 51b7f621..45bc7e6e 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; struct MultiStack { - bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack - bytes32 remainingHash; // 0 if less than 2 cothreads exist + bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack + bytes32 remainingHash; // 0 if less than 2 cothreads exist } library MultiStackLib { From 5666569dc3e83903de28fb73cf558535cfe22ecb Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 8 Mar 2024 07:27:29 -0700 Subject: [PATCH 9/9] fix typo --- src/osp/OneStepProverHostIo.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index b4de8872..5cab2f21 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -435,7 +435,7 @@ contract OneStepProverHostIo is IOneStepProver { mach.valueMultiStack.pushNew(); } - function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { + function provePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { uint256 proofOffset = 0; bytes32 newInactiveCoThread; bytes32 newRemaining; @@ -453,7 +453,6 @@ contract OneStepProverHostIo is IOneStepProver { } multi.remainingHash = newRemaining; multi.inactiveStackHash = newInactiveCoThread; - return; } function executePopCoThread( @@ -473,8 +472,8 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.ERRORED; return; } - proovePopCothread(mach.valueMultiStack, proof); - proovePopCothread(mach.frameMultiStack, proof[64:]); + provePopCothread(mach.valueMultiStack, proof); + provePopCothread(mach.frameMultiStack, proof[64:]); } function executeSwitchCoThread(