Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "nitro_rpc/lib/forge-std"]
path = nitro_rpc/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "nitro_rpc/lib/exit-format"]
path = nitro_rpc/lib/exit-format
url = https://github.com/statechannels/exit-format
[submodule "nitro_rpc/lib/nitro"]
path = nitro_rpc/lib/nitro
url = https://github.com/erc7824/nitro
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"solidity.packageDefaultDependenciesContractsDirectory": "src",
"solidity.packageDefaultDependenciesDirectory": "lib"
}

1 change: 1 addition & 0 deletions nitro_rpc/lib/exit-format
Submodule exit-format added at 08169b
1 change: 1 addition & 0 deletions nitro_rpc/lib/nitro
Submodule nitro added at cc2d45
69 changes: 64 additions & 5 deletions nitro_rpc/src/NitroRPC.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;


import {INitroTypes} from "nitro/interfaces/INitroTypes.sol";
import {NitroUtils} from "nitro/libraries/NitroUtils.sol";
import {IForceMoveApp} from "nitro/interfaces/IForceMoveApp.sol";

/**
* @title NitroRPC
* @dev Solidity implementation of Nitro RPC protocol structures
*/
interface NitroRPC {

contract NitroRPC is IForceMoveApp {
struct Payload {
uint256 requestId;
uint256 timestamp;
string method;
bytes params;
bytes result;
uint256 timestamp;
}

struct PayloadSigned {
Payload rpcMessage;
INitroTypes.Signature clientSig
INitroTypes.Signature serverSig
INitroTypes.Signature clientSig;
INitroTypes.Signature serverSig;
}

enum AllocationIndices {
Client,
Server
}

/**
* @notice Encodes application-specific rules for a particular ForceMove-compliant state channel.
* @dev Encodes application-specific rules for a particular ForceMove-compliant state channel.
* @param fixedPart Fixed part of the state channel.
* @param proof Array of recovered variable parts which constitutes a support proof for the candidate.
* @param candidate Recovered variable part the proof was supplied for.
*/
function stateIsSupported(
INitroTypes.FixedPart calldata fixedPart,
INitroTypes.RecoveredVariablePart[] calldata proof,
INitroTypes.RecoveredVariablePart calldata candidate
) external pure override returns (bool, string memory) {
require(fixedPart.participants.length == uint256(AllocationIndices.Server) + 1, "bad number of participants");

PayloadSigned memory payloadSigned = abi.decode(candidate.variablePart.appData, (PayloadSigned));
requireValidPayload(fixedPart, payloadSigned);

return (true, "");
Comment on lines +48 to +51

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any other checks pertaining to Payload? Should payloads be chained, in which case we should introduce proof checks?

}

function requireValidPayload(INitroTypes.FixedPart calldata fixedPart, PayloadSigned memory payloadSigned) internal pure {
require(recoverPayloadSigner(payloadSigned.rpcMessage, payloadSigned.clientSig) == fixedPart.participants[uint256(AllocationIndices.Client)], "bad client signature");
require(recoverPayloadSigner(payloadSigned.rpcMessage, payloadSigned.serverSig) == fixedPart.participants[uint256(AllocationIndices.Server)], "bad server signature");

// TODO: verify timestamp and requestId
}

// This pure internal function recovers the signer address from the payload and its signature.
function recoverPayloadSigner(Payload memory payload, INitroTypes.Signature memory signature) internal pure returns (address) {
// Encode and hash the payload data.
// Using abi.encode ensures proper padding and decoding, avoiding potential ambiguities with dynamic types.
bytes32 messageHash = keccak256(
abi.encode(
payload.requestId,
payload.timestamp,
payload.method,
payload.params,
payload.result
)
);

// Apply the Ethereum Signed Message prefix.
bytes32 ethSignedMessageHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
);

// Recover the signer address using ecrecover.
return ecrecover(ethSignedMessageHash, signature.v, signature.r, signature.s);
}
}