Private Chain with Authenticated RPC Middleware #34

Open
opened 2026-02-24 05:10:28 +00:00 by scott · 0 comments
Member

We want the chain to be private. That means outsiders should not be able to read the blocks directly. Additionally, we should validate some read operations of chain state.

One approach would be making each read call a full smart contract "change" call requiring auth, but this would bloat the chain a lot.

NEAR gives us a mechanism to work with: smart contracts can take an account id parameter and return specific data based on that. However, it's not actually authentication and the client can pass any account id.

So the idea here is to use middleware to block certain methods and provide a simple auth mechanism against smart contract reads, using the existing NEAR mechanism.

Summary

Implement an RPC middleware proxy that sits between clients and our private NEAR chain, enforcing authentication via request signing and blocking raw chain access methods that would bypass smart contract access control.

Background

Our NEAR-based chain stores data that is not universally public. Smart contracts are responsible for enforcing per-account data authorization. However, NEAR's RPC interface exposes methods that allow clients to read raw chain state directly, bypassing contract logic entirely. We need a middleware layer that:

  1. Authenticates clients by verifying NEAR keypair-signed requests
  2. Blocks RPC methods that expose raw chain state
  3. Forwards permitted calls with the verified account_id, allowing contracts to make authorization decisions

The NEAR nodes themselves are not publicly accessible — all client access goes through this middleware.

Approach

Authentication

Clients sign each request using their NEAR private key. The signature covers the RPC call parameters plus a timestamp. The middleware verifies the signature against the account's public key (looked up via the node's view_access_key endpoint) and rejects requests where:

  • No signature is present
  • The signature is invalid
  • The timestamp is outside a ±30 second window (replay attack prevention)

Once verified, the middleware forwards the request with the authenticated account_id injected as the caller context. Smart contracts can then use this identity to decide what data to return.

Blocked RPC Methods

The following methods are blocked entirely as they expose raw chain state that bypasses contract logic:

Method Reason
block Exposes full block data
chunk Exposes raw transactions and receipts
EXPERIMENTAL_changes State change diffs, no contract mediation
EXPERIMENTAL_changes_in_block State change diffs, no contract mediation
query (view_state) Dumps raw contract storage, bypasses contract methods
query (view_access_key) Exposes account key information
query (view_access_key_list) Exposes account key information
tx Returns transaction details and plaintext arguments/responses
EXPERIMENTAL_tx_status Same as above

Permitted RPC Methods

Method Notes
query (call_function) Contract methods; access control enforced by contract
broadcast_tx_async Required for submitting transactions
broadcast_tx_commit Required for submitting transactions
gas_price Operational, no sensitive data
status Operational, no sensitive data
network_info Operational, no sensitive data

Request Flow

Client
  │
  │  Signs request: { method, params, timestamp } with NEAR private key
  ▼
Middleware
  │  1. Validate timestamp is within ±30s
  │  2. Look up account's public key via view_access_key
  │  3. Verify signature
  │  4. Check method is on allowlist
  │  5. Inject verified account_id into call params
  ▼
NEAR Node (private network)
  │
  ▼
Smart Contract
     Authorizes or denies based on account_id

Smart Contract Considerations

Contracts must treat the injected account_id as the authoritative caller identity for view calls, since view calls have no native signature requirement at the NEAR protocol level. Since the middleware guarantees only authenticated, verified account_id values are forwarded, contracts can safely use this for access decisions.

For particularly sensitive operations, contracts may additionally be designed as change methods to require on-chain signing — but this should be reserved for writes or high-sensitivity reads where the added cost and latency is acceptable.

Acceptance Criteria

  • Middleware rejects requests with missing or invalid signatures
  • Middleware rejects requests with timestamps outside the ±30 second window
  • All blocked RPC methods return a 403 with a clear error message
  • Verified account_id is injected into forwarded call_function requests
  • NEAR nodes are not accessible except through the middleware
  • Integration test: a valid signed call_function request is forwarded and returns data
  • Integration test: a view_state request is rejected regardless of auth
  • Integration test: a request with a replayed timestamp is rejected

Out of Scope

  • Per-record encryption (data is visible to validators)
  • On-chain session tokens
  • Rate limiting (separate concern)
We want the chain to be private. That means outsiders should not be able to read the blocks directly. Additionally, we should validate some read operations of chain state. One approach would be making each read call a full smart contract "change" call requiring auth, but this would bloat the chain a lot. NEAR gives us a mechanism to work with: smart contracts can take an account id parameter and return specific data based on that. However, it's not actually authentication and the client can pass any account id. So the idea here is to use middleware to block certain methods and provide a simple auth mechanism against smart contract reads, using the existing NEAR mechanism. ## Summary Implement an RPC middleware proxy that sits between clients and our private NEAR chain, enforcing authentication via request signing and blocking raw chain access methods that would bypass smart contract access control. ## Background Our NEAR-based chain stores data that is not universally public. Smart contracts are responsible for enforcing per-account data authorization. However, NEAR's RPC interface exposes methods that allow clients to read raw chain state directly, bypassing contract logic entirely. We need a middleware layer that: 1. Authenticates clients by verifying NEAR keypair-signed requests 2. Blocks RPC methods that expose raw chain state 3. Forwards permitted calls with the verified `account_id`, allowing contracts to make authorization decisions The NEAR nodes themselves are not publicly accessible — all client access goes through this middleware. ## Approach ### Authentication Clients sign each request using their NEAR private key. The signature covers the RPC call parameters plus a timestamp. The middleware verifies the signature against the account's public key (looked up via the node's `view_access_key` endpoint) and rejects requests where: - No signature is present - The signature is invalid - The timestamp is outside a ±30 second window (replay attack prevention) Once verified, the middleware forwards the request with the authenticated `account_id` injected as the caller context. Smart contracts can then use this identity to decide what data to return. ### Blocked RPC Methods The following methods are blocked entirely as they expose raw chain state that bypasses contract logic: | Method | Reason | |---|---| | `block` | Exposes full block data | | `chunk` | Exposes raw transactions and receipts | | `EXPERIMENTAL_changes` | State change diffs, no contract mediation | | `EXPERIMENTAL_changes_in_block` | State change diffs, no contract mediation | | `query` (`view_state`) | Dumps raw contract storage, bypasses contract methods | | `query` (`view_access_key`) | Exposes account key information | | `query` (`view_access_key_list`) | Exposes account key information | | `tx` | Returns transaction details and plaintext arguments/responses | | `EXPERIMENTAL_tx_status` | Same as above | ### Permitted RPC Methods | Method | Notes | |---|---| | `query` (`call_function`) | Contract methods; access control enforced by contract | | `broadcast_tx_async` | Required for submitting transactions | | `broadcast_tx_commit` | Required for submitting transactions | | `gas_price` | Operational, no sensitive data | | `status` | Operational, no sensitive data | | `network_info` | Operational, no sensitive data | ## Request Flow ``` Client │ │ Signs request: { method, params, timestamp } with NEAR private key ▼ Middleware │ 1. Validate timestamp is within ±30s │ 2. Look up account's public key via view_access_key │ 3. Verify signature │ 4. Check method is on allowlist │ 5. Inject verified account_id into call params ▼ NEAR Node (private network) │ ▼ Smart Contract Authorizes or denies based on account_id ``` ## Smart Contract Considerations Contracts must treat the injected `account_id` as the authoritative caller identity for view calls, since view calls have no native signature requirement at the NEAR protocol level. Since the middleware guarantees only authenticated, verified `account_id` values are forwarded, contracts can safely use this for access decisions. For particularly sensitive operations, contracts may additionally be designed as `change` methods to require on-chain signing — but this should be reserved for writes or high-sensitivity reads where the added cost and latency is acceptable. ## Acceptance Criteria - [ ] Middleware rejects requests with missing or invalid signatures - [ ] Middleware rejects requests with timestamps outside the ±30 second window - [ ] All blocked RPC methods return a 403 with a clear error message - [ ] Verified `account_id` is injected into forwarded `call_function` requests - [ ] NEAR nodes are not accessible except through the middleware - [ ] Integration test: a valid signed `call_function` request is forwarded and returns data - [ ] Integration test: a `view_state` request is rejected regardless of auth - [ ] Integration test: a request with a replayed timestamp is rejected ## Out of Scope - Per-record encryption (data is visible to validators) - On-chain session tokens - Rate limiting (separate concern)
Sign in to join this conversation.
No labels
urgent
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_ledger#34
No description provided.