Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "nitro_rpc/lib/nitro"]
path = nitro_rpc/lib/nitro
url = https://github.com/erc7824/nitro
[submodule "nitro_rpc/lib/openzeppelin-contracts"]
path = nitro_rpc/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
158 changes: 150 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,96 @@ Nitro Protocol Examples

## Nitro RPC

Those application leverage the NitroRPC Asynchronous protocol
Those application leverage the NitroRPC Asynchronous protocol, it describe a data format, that must be understood and readable by both backend, frontend and smart-contract NitroRPCApp (adjudication)

Here is the format:

### Solidity
```solidity
struct NitroRPC {
uint64 req_id; // Unique request ID (non-zero)
string method; // RPC method name (e.g., "substract")
string[] params; // Array of parameters for the RPC call
uint64 ts; // Milisecond unix timestamp provided by the server api
}

NitroRPC memory rpc = NitroRPC({
req_id: 123,
method: "subtract",
params: new string ts: 1710474422
});

bytes memory encoded1 = ABI.encode(rpc);
bytes memory encoded2 = ABI.encode(rpc.req_id, rpc.method, rpc.params, rpc.ts);

require(keccak256(encoded1) == keccak256(encoded2), "Mismatch in encoding!");
```

### The RPCHash

Client and server must sign the Nitro RPC Hash as followed

### Solidity

```solidity
rpc_hash = keccak256(
abi.encode(
rpc.req_id,
rpc.method,
rpc.params,
rpc.ts
)
);

# rpc_hash can be used to erecover the public key
```

### Go lang
```go
package main

import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"log"
)

type NitroRPC struct {
ReqID uint64
Method string
Params []string
TS uint64
}

func main() {
rpc := NitroRPC{
ReqID: 123,
Method: "subtract",
Params: []string{"param1", "param2"},
TS: 1710474422,
}

packedData, err := abi.Arguments{
{Type: abi.UintTy},
{Type: abi.StringTy},
{Type: abi.StringSliceTy},
{Type: abi.UintTy},
}.Pack(rpc.ReqID, rpc.Method, rpc.Params, rpc.TS)
if err != nil {
log.Fatal("ABI encoding failed:", err)
}

hash := crypto.Keccak256(packedData)
fmt.Println("Keccak256 Hash:", hexutil.Encode(hash))
}
```

## NitroRPC Transport

In NitroRPC the transport layer is agnostic to describe in any syntax as long as the NitroRPC Type, RPCHash and signature convention are valid.
You can use json-rpc, msgpack, protobug, gRPC or any custom marshalling and transport layer.

### NitroRPC Request

Expand Down Expand Up @@ -58,13 +147,66 @@ CREATE TABLE rpc_states (
```

#### TODO:
- Create AppData in solidity
- Create NitroApp to validate states including AppData
- Create Protobuf description of NitroRPC Request / Response
- Include NitroRPC types into gRPC and generate client and server
- Implement methods: `add(int), sub(int), mul(int), mod(int)` starting from `0`
- Create a simplified NitroCalc Service using sqlite for state (in go)
- Finally, we can import nitro pkg, and client or server can pack and sqlite state and call nitro of on-chain channel operations.
- [ ] Create AppData in solidity
- [ ] Create NitroApp to validate states including AppData
- [ ] Create Protobuf description of NitroRPC Request / Response
- [ ] Include NitroRPC types into gRPC and generate client and server
- [ ] Implement methods: `add(int), sub(int), mul(int), mod(int)` starting from `0`
- [ ] Create a simplified NitroCalc Service using sqlite for state (in go)
- [ ] Finally, we can import nitro pkg, and client or server can pack and sqlite state and call nitro of on-chain channel operations.

### Questions

#### Where state-management is happening?

Like the TicTacToe example, the Logic and App State is managed by the Nitro.App,
But also the state is created in compliance by implementing correctly Nitro.App interface See ADR-002
nitro package responsability is to unpack the state and submit it on-chain.

#### Communication diagram between BusinessApp, NitroRPC and NitroCore

NitroRPC is embeded into the BusinessApp and it's only a norm, expect for the smart-contract NitroRPCApp

nitro go package is providing helpers and functions to abstract the blockchain level things,
it will take your Nitro.App state and execute the blockchain operation you request on the NitroAdjudicator (`prefunding, postfunding, finalize, checkpoint, challenge`)

#### Who is responsible for the state signing? (One signer on Client, One signer on Server???)

Client Nitro.App signs requests
Server Nitro.App signs reponses

Channel service or nitro package does sign for you, the private key is obviously not part of the package.
But nitro pkg will help you sign and simplify all the nitro state creation.

#### Do we have 2-step communication like?
- Client -> Server: Request
- Server -> Client: Response (Server signs?)
- Client -> Server: Acknowledge (Client signs?)
- Server -> Client: Acknowledge

I would say 1-step is Request-Response pair.

- Request is signed by client
- Response is signed by server

anything else is an invalidate state (request signed without response, signature missing)

#### Do we implement nitro specific methods (open, checkpoint, dispute, finalize) in the NitroRPC service?

You only need to implement the transport, for example json-rpc handlers, or grpc, those specific method will be standardized
nitro pkg will provide the implementation of those methods, you just need to provide the correct prefunding postfunding state in nitro.App

A nitro.App is a state container, which can hold 1 or many state indexed by TurnNum, serialized and passed to nitro pkg/svc for execution.

#### Does NitroRPC server have BusinessApp logic?

NitroRPC is just a convention, the Application has the business logic and implement an RPC protocol which comply with the state convention

#### Does NitroRPC server stores rpc states?

It's high recommended, in the event of answering to a challenge, but most of the time you need only the recent state,
but like I provided an SQL table, Application should keep track of state in some degree, it could be in memory and in a custom format
as long as it's able to form an RPCHash again.

#### Markdown Table Example

Expand Down
186 changes: 186 additions & 0 deletions nitro_client_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Higher-Level NitroClient API

## 1. Overview of the Solution

The NitroCore package provides all low‑level functionality for state channels—including state construction, signing, and on‑chain enforcement—but its interface is complex. To make integration easier for most developers, we propose an additional abstraction layer—NitroClient—which exposes a high‑level HTTP (or JSON‑RPC) API. This API would handle the internal state management and on‑chain interactions while exposing simple, business‑oriented methods. The client (user) interacts only via high‑level commands (for example, openChannel, makeMove, and finalize) while the NitroClient internally constructs and signs state updates based on the NitroCore functions.

A key requirement is that the client’s signature must be made over the state data (i.e. the state hash), ensuring that the state remains secure and verifiable. This is achieved by leveraging functions like `(c *Channel) SignAndAddState(s State, ls signer.Signer) (SignedState, error)` in NitroCore.

---

## 2. High-Level API Design

### Key Design Decisions

- **Abstraction of Low-Level Details:**
Users are not required to manually manage state objects, turn numbers, or cryptographic signing. Instead, the API exposes simple endpoints that encapsulate these details.

- **Language-Agnostic Communication:**
Exposing an HTTP or JSON‑RPC interface enables clients written in any language to interact with the service.

- **Pluggable Business Logic:**
The API can support multiple applications (e.g. TicTacToe, betting, calc) by associating each channel with an `app_type` or by using schema‑driven validation. This dispatch layer routes high‑level commands to the correct business‑logic module.

- **Internal Security and Signing:**
While the API abstracts state management, it still enforces that every state update is signed over its full hash. The client must provide a signature on the computed state hash, which is then verified and merged with the server’s signature to produce a full support proof.

### Example Endpoints

1. **POST /channel/open**
*Purpose:* Open a new channel.
*Request Example:*
```json
{
"client_id": "user-123",
"counterparty": "server-abc",
"initial_funds": { "ETH": "1000000000000000000" },
"client_signature": "0xabc..."
}
```
*Response Example:*
```json
{
"channel_id": "0xdeadbeef...",
"state_hash": "0x1234abcd...",
"server_signature": "0xdef..."
}
```

2. **POST /channel/move**
*Purpose:* Submit a state update (e.g. a move in a game).
*Request Example:*
```json
{
"channel_id": "0xdeadbeef...",
"action": "move",
"params": { "x": 1, "y": 2 },
"client_signature": "0xabc..."
}
```
*Response Example:*
```json
{
"new_state_hash": "0x5678efgh...",
"server_signature": "0xdef..."
}
```

3. **POST /channel/finalize**
*Purpose:* Finalize and close the channel.
*Request Example:*
```json
{
"channel_id": "0xdeadbeef...",
"client_signature": "0xabc..."
}
```
*Response Example:*
```json
{
"final_state_hash": "0x9abc...",
"server_signature": "0xdef..."
}
```

4. **GET /channel/state/{channel_id}**
*Purpose:* Retrieve the current channel state (for debugging or verification).

---

## 3. Communication Flow Examples

### Client ↔ NitroClient API

- **Opening a Channel:**
1. The client sends a POST request to `/channel/open` with parameters such as client ID, counterparty info, and initial funds along with a client‑generated signature.
2. The API creates an initial Nitro state using NitroCore, verifies the client’s signature (which is over the state hash), and then signs the state itself.
3. The API returns a channel ID, state hash, and server signature.

- **Making a Move (State Update):**
1. The client sends a POST request to `/channel/move` with an `"action": "move"`, relevant parameters (for example, `{ "x": 1, "y": 2 }`), and a client signature.
2. The server fetches the current channel state, dispatches the request to the appropriate business logic module (e.g. TicTacToe handler), and applies the move.
3. The module computes the new state (incrementing turn number and updating app data) and calculates its hash.
4. The server verifies the client’s signature over the new state hash, signs the state update using NitroCore’s `SignAndAddState`, and returns the new state hash and its own signature.

- **Finalizing a Channel:**
1. When ready to close the channel, the client sends a POST request to `/channel/finalize` with the channel ID and its signature.
2. The server confirms that the state is final (or constructs a final state), signs it, and triggers the on‑chain finalization process via NitroCore.
3. The response includes the final state hash and server signature.

### Internal (NitroClient API ↔ NitroCore)

- **State Construction and Signing:**
The API layer calls NitroCore functions such as:
```go
currentState := channel.LatestSignedState().State
newState := currentState.Clone()
// Business logic: update newState.AppData, newState.Outcome, etc.
newState.TurnNum++
// Verify client signature over newState.Hash()
clientSignedState, err := channel.SignAndAddState(newState, clientSigner)
// Server then signs the state update:
serverSig, _ := newState.Sign(serverSigner)
channel.AddStateWithSignature(newState, serverSig)
```
This ensures that the new state carries both the client’s and the server’s signatures, binding the state data cryptographically.

---

## 4. Pros and Cons of the Proposed API

### Pros

- **User-Friendly:**
- Simplifies interaction by exposing high-level endpoints instead of requiring developers to manage Nitro states manually.
- Provides a consistent, language-agnostic interface (via HTTP/JSON‑RPC) that can be used by web, mobile, or desktop applications.

- **Separation of Concerns:**
- NitroCore continues to focus on cryptographic signing, state validation, and on‑chain operations.
- NitroClient handles business logic, state dispatch, and coordination without exposing low‑level details.

- **Centralized State Management & Auditing:**
- All state transitions are logged and stored centrally, which can simplify debugging and dispute resolution.
- The server can manage the entire channel lifecycle—from channel opening to finalization—ensuring consistency.

- **Simplified On‑Chain Operations:**
- The API abstracts away gas management, transaction signing, and blockchain submission.
- Automated aggregation of client and server signatures ensures that every state update is secure and enforceable on‑chain.

- **Flexibility Across Applications:**
- A pluggable or schema‑driven dispatch system allows the API to support different business logic modules (TicTacToe, betting, calculator, etc.) under a unified interface.

### Cons

- **Centralization and Trust Concerns:**
- The server becomes a single point of control; clients must trust that the server processes valid requests and does not act maliciously.
- Clients have limited direct access to raw states, reducing their ability to independently audit and challenge state transitions.

- **Reduced Transparency:**
- Because the Nitro state is managed internally by the API, clients may have fewer tools to verify the underlying state if a dispute arises.
- Reliance on the server for state history and proof can be problematic in adversarial scenarios.

- **Single Point of Failure and Scalability:**
- The centralized API must be highly available and secure; downtime or a breach could affect all channel operations.
- Scaling the API to handle many simultaneous channels and state updates might require significant infrastructure investment.

- **Complexity in Generic Handling:**
- Designing a truly generic endpoint (e.g., `/channel/move`) requires a dispatch layer to route requests to the appropriate business logic.
- Managing dynamic schemas or multiple endpoints for different applications increases the overall complexity of the API.

- **Security Risks in Key Management:**
- The server’s private keys must be managed with extreme care. A compromise of these keys could jeopardize the entire system.
- The verification logic must correctly enforce that client signatures are over the accurate state hash.

- **Potential Latency:**
- The extra network hops (client → API → NitroCore → blockchain) could introduce delays, which may impact time-sensitive applications.

---

## 5. Conclusion

The proposed NitroClient API offers a significant usability improvement by abstracting the complex NitroCore state management into a simple, high-level HTTP interface. Users benefit from an intuitive, business-focused API while the heavy lifting—state creation, signing, and on‑chain interactions—is managed internally.

However, this additional layer introduces trade‑offs in terms of centralization, trust, and potential complexity in handling different business logics. Security concerns regarding key management and state transparency must be addressed, and provisions such as audit modes or state proof access may help mitigate these risks.

Overall, the abstraction layer is a promising solution to lower the barrier for developers while preserving the core security and functionality of the Nitro protocol, as long as careful attention is paid to balancing usability with trust and decentralization.

24 changes: 24 additions & 0 deletions nitro_rpc/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# Nitro RPC

## Protobuf & Twirp Setup

This guide walks you through setting up `protoc`, adding the required plugins, and configuring your development environment to work seamlessly with Twirp and protobuf.
Twirp uses your protobuf service definitions to generate an HTTP/1.1 API. It’s lightweight and simpler than gRPC, which can be a good fit if you prefer HTTP/1.1 over HTTP/2.

### Prerequisites

Before generating the Go files from your protobuf definitions, ensure that you have installed the Protocol Buffers compiler (`protoc`) and the necessary Go plugins for Twirp and protobuf generation. Detailed installation instructions are available in the [Twirp Installation Documentation](https://twitchtv.github.io/twirp/docs/install.html).

### Usage

All protobuf definitions are maintained inside the `proto/` directory. You can generate the corresponding Go files using the following command:

```bash
protoc -I proto \
--go_out=proto --go_opt=paths=source_relative \
--twirp_out=proto --twirp_opt=paths=source_relative \
proto/nitro_rpc.proto
```

This command will create both the `.pb.go` and `.twirp.go` files in the `proto/` directory, keeping your service definitions and generated code neatly organized.

## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
Expand Down
Loading