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
23 changes: 23 additions & 0 deletions contracts/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ Invariant:
Invariant:
> Credits accrued while a channel was in `CHALLENGED` status are preserved — either reintegrated into the channel on challenge clearance, or carried into the next epoch on closure — and cannot be shadowed by a concurrently-opened replacement channel.

---

8. App session closure is **atomic across all participants**. The Node issues a new release receive-state on every participant's home channel as part of a single close transaction; a failure on any single participant aborts the entire close, leaving the session open and no release credits issued to anyone.

The Node refuses to issue a release state when the recipient's most recent signed state encodes an **escrow operation that `EnsureNoOngoingEscrowOperation` does not yet treat as safely settled**. In practice this covers:

* any pending `escrow_lock` or `mutual_lock` (always considered unresolved until superseded);
* `escrow_deposit` or `escrow_withdraw` whose on-chain escrow-channel version has not caught up with the signed state version — with a narrow one-version-behind allowance for `escrow_deposit` while the escrow channel is `Open` or `Closed` (the steady state during a normal finalize/purge cycle).

The release receive-state is stacked on top of the recipient's most recent signed state. If that state encodes an unfinalized escrow, signing a release on top risks state-chain invariant violations should the escrow ultimately revert or settle to a different version than was assumed when the release was issued. The Node therefore blocks until the escrow resolves rather than risking a co-signed credit that cannot be enforced on-chain.

Consequence: a single participant whose escrow has not yet completed — whether due to slow on-chain confirmation, an active challenge, or deliberate non-finalization — blocks the cooperative close for **all** other participants in the session. Remaining participants have two recovery paths:

* wait for the blocking participant's escrow to resolve and retry the close,
* if the session's state machine permits intermediate updates, individually unwind their share via off-chain transfers out of the session and re-attempt closure with the remaining (non-blocked) members.

This is an accepted trade-off for now, which may be lifted in the future: protocol safety (no release state co-signed against a potentially-reverting escrow chain) takes precedence over close-time liveness. Sessions whose participants require independent exit guarantees should be designed with state machines that support partial settlement rather than relying solely on cooperative close.

Unlike the `CHALLENGED` channel path (rule 6) — where the release issuer **does** store unsigned releases for non-`Open` home channels so the rescue squash can pick them up at close — the close path does **not** extend that unsigned-fallback pattern to the escrow-rejected case. When `EnsureNoOngoingEscrowOperation` rejects a participant, the entire close aborts rather than queueing an unsigned release on top of an in-flight escrow. Extending the fallback here is a possible future improvement but is non-trivial: the safety of stacking an unsigned release on an unfinalized escrow depends on later reconciling the eventual escrow outcome with the queued credit, and the current implementation prefers refusal over partial trust.

Invariant:
> A single participant with a pending escrow operation can block cooperative closure of an app session for all other participants until the escrow resolves; no participant receives a release credit while the close is blocked.

## Invariants

---
Expand Down
1 change: 1 addition & 0 deletions docs/protocol/security-and-limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The following capabilities are not yet implemented or have acknowledged design t
- Support for non-EVM blockchains
- Formal verification of protocol rules
- Session key off-chain scope enforcement does not apply to direct receive-state acknowledgement. Session key expiration and asset-scope restrictions are enforced by the Nitronode off-chain only; the `SessionKeyValidator` contract validates cryptographic signatures alone. A party holding a session key — even one that has expired, been revoked, or been retired — can bypass the `acknowledge` endpoint, manually sign a pending node-issued receive state, and submit it directly to the contract. This is accepted: receive states exclusively increase the user's allocation and cannot redirect funds away from the user, so out-of-scope acknowledgement carries no financial risk and preserves a recovery path when the node is unavailable.
- App session cooperative closure is atomic across all participants. The Node refuses to issue a release receive-state to any participant whose latest signed state encodes an escrow operation that the off-chain gate does not yet treat as safely settled — covering any pending `escrow_lock`/`mutual_lock`, plus `escrow_deposit` or `escrow_withdraw` states the gate still treats as unsafe (broadly, those whose on-chain escrow channel has not caught up, with a narrow one-version-behind allowance for `escrow_deposit` during normal finalize/purge transitions). Stacking a co-signed release on an unfinalized escrow risks state-chain invariant violations if the escrow ultimately reverts or settles to an unexpected version. As a consequence, a single participant with a pending escrow blocks cooperative close for all others in the session until their escrow resolves. Affected participants may wait for the obstruction to clear, or — where the session state machine permits intermediate updates — unwind their share individually via off-chain transfers out of the session and re-close without the blocked participant. This is an accepted trade-off favouring protocol safety over close-time liveness: every release the Node co-signs must remain enforceable on-chain, and an unfinalized escrow cannot offer that guarantee.

## Future Improvements

Expand Down
8 changes: 8 additions & 0 deletions protocol-description.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ These changes are reflected only in cumulative net flows until enforced on-chain

---

### App session closure and participant atomicity

Closing an app session distributes locked allocations back to each participant by issuing a new release receive-state on every participant's home channel. The operation is **atomic across all participants** — if the Node cannot issue a release for any one participant, the entire close aborts.

In particular, the Node refuses to issue a release to a recipient whose latest signed state encodes an escrow operation that the off-chain gate (`EnsureNoOngoingEscrowOperation`) does not yet treat as safely settled — covering any pending `escrow_lock`/`mutual_lock`, plus `escrow_deposit` or `escrow_withdraw` states the gate still treats as unsafe (broadly, those whose on-chain escrow channel has not caught up, with a narrow one-version-behind allowance for `escrow_deposit` during normal finalize/purge transitions). A single such participant blocks cooperative closure for everyone else in the session until their escrow resolves. See [`contracts/SECURITY.md`](contracts/SECURITY.md) Behavior rule 8 for the full rationale and recovery paths.

---

## On-chain protocol (enforcement plane)

The on-chain contract is the **final arbiter** of correctness.
Expand Down
Loading