Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default defineConfig({
'/developer-guides/storage/split-operations/': '/developer-guides/storage/upload-pipeline/',
'/developer-guides/react-integration/': '/developer-guides/synapse-react/',
'/developer-guides/devnet/': '/resources/devnet/',
'/developer-guides/session-keys/': '/developer-guides/access-control/session-keys/',
'/developer-guides/programmable-acls/': '/developer-guides/access-control/programmable-acls/',
},
markdown: {
// rehype-external-links attaches to the unified processor Starlight runs.
Expand Down
21 changes: 20 additions & 1 deletion docs/src/content/docs/core-concepts/fwss-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,31 @@ Together, FWSS enables builders to depend on Filecoin not only for “store and

WarmStorage manages the complete storage marketplace:

1. **Client Authentication**: Validates all client operations via EIP-712 signaturess.
1. **Client Authentication**: Validates all client operations via EIP-712 signatures.
2. **Payment Coordination**: Automatically creates and manages payment rails between clients and service providers.
3. **Cost Calculation**: Determines pricing based on size, duration, and CDN usage.
4. **Metadata Management**: Stores data set and piece metadata for discovery.
5. **Fault Handling**: Integrates PDP verification results with payment adjustments

### Authorizing writes

For the write operations on a data set — adding pieces, scheduling piece removals, and terminating
the service — FWSS supports three authorization models, in increasing order of flexibility:

1. **Payer signature (default)**: the data set's payer signs an EIP-712 message; FWSS recovers the
secp256k1 signer and checks it against the payer.
2. **[Session keys](/developer-guides/access-control/session-keys/)**: the payer delegates to an ephemeral
secp256k1 key with time-limited, per-operation permissions recorded in the `SessionKeyRegistry`.
This is the recommended way to get silent, popup-free signing while keeping the standard model.
3. **[Programmable ACLs](/developer-guides/access-control/programmable-acls/) (Data Set Authorizers)**: the payer
attaches a contract implementing `IDataSetAuthorizer` to a single data set. FWSS then delegates
the entire authorization decision for that data set's writes to the contract, which can enforce
any policy — verifying a **different curve** (e.g. a P256 passkey / WebAuthn assertion), requiring
human presence, or adding expiry, rate-limits, or a kill-switch.

The three are complementary: session keys and programmable ACLs are both opt-in delegation layers on
top of the default payer-signature model, chosen per data set.

## How FWSS works

**Filecoin Warm Storage Service (FWSS)** combines PDP (Proof of Data Possession) verification with integrated payment rails using Filecoin Pay to offer data set management for developers.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
label: Access Control
collapsed: true
order: 6
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
title: Programmable ACLs
description: Delegate the entire write-authorization decision for a data set to your own on-chain contract (Data Set Authorizers).
sidebar:
order: 3
---

:::tip[Alternative: session keys]
FWSS offers 2 distinct methods of controlling write access to data sets.

Ensure you pick the right one by consulting [which should I use?](/developer-guides/access-control/which-to-use/) first.
:::

## What are programmable ACLs?

By default, every write to a data set — adding pieces, scheduling piece removals, terminating the
service — is authorized by an **EIP-712 signature from the data set's payer**, optionally delegated
to a [session key](/developer-guides/access-control/session-keys/). Session key delegation offers a
powerful UX upgrade over repetitive wallet signing operations but they delegate authority for *all
datasets* that the payer owns.

A **programmable ACL** overrides that built-in auth check on an individual dataset basis with a
smart contract supplied by the payer that implements the `IDataSetAuthorizer` interface.
When the payer attaches an *authorizer* to a dataset, FWSS calls the authorizer to make the
decision **instead of** checking the session key registry.

The authorizer can implement any policy you want:

- verify a signature using a different algorithm — for example a **P256 passkey** assertion
(Touch ID, secure enclave), which the built-in secp256k1 path cannot do;
- require **human presence** (a biometric user-verification flag) for sensitive operations;
- gate on the operation's contents (metadata, piece paths);
- authorize a machine agent's stored key **and** your human passkey on the same data set,
each scoped to different operations.

## The interface

An authorizer is any contract implementing a single method:

```solidity
interface IDataSetAuthorizer {
function isAuthorized(
uint256 dataSetId,
address payer,
bytes32 operation, // EIP-712 type hash of the op (AddPieces / SchedulePieceRemovals / TerminateService)
bytes32 digest, // the EIP-712 digest FWSS computed for this exact operation
bytes calldata signature, // opaque to FWSS — your contract interprets it
bytes calldata operationData // ABI-encoded raw op payload (empty for terminate)
) external returns (bool authorized);
}
```

FWSS calls this **instead of** its built-in signature check, for the three write operations.
Semantics:

- return **`true`** → the operation proceeds;
- return **`false`** → FWSS reverts with `Unauthorized`;
- **revert / out-of-gas** → treated as "not authorized"; the operation reverts.

`isAuthorized` is a **state-mutating** call (not `view`), so an authorizer may update its own storage
while deciding — consume a nonce, tick a rate-limiter, log an event. FWSS gas-caps the sub-call and
blocks re-entry into the authorization path while a decision is in flight.

The `digest` is FWSS's EIP-712 digest for that exact operation and its parameters; the `signature`
is whatever blob your authorizer expects (FWSS does not interpret it); `operationData` is the raw
ABI-encoded operation payload, so a policy can gate on contents (it is empty for terminate).

## Step 1 — write and deploy an authorizer

Here is a minimal authorizer that accepts a **P256 (secp256r1) signature** over the operation
digest from a single registered key — the kind of delegation the built-in secp256k1 path can't
express. It verifies via the FEVM **secp256r1 precompile at `0x100`**:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SingleP256Authorizer {
uint256 public immutable pubKeyX;
uint256 public immutable pubKeyY;

constructor(uint256 x, uint256 y) { pubKeyX = x; pubKeyY = y; }

function isAuthorized(uint256, address, bytes32, bytes32 digest, bytes calldata signature, bytes calldata)
external view returns (bool)
{
(bytes32 r, bytes32 s) = abi.decode(signature, (bytes32, bytes32));
// 0x100 input: digest ‖ r ‖ s ‖ x ‖ y ; returns 32-byte 1 on success
(bool ok, bytes memory out) =
address(0x100).staticcall(abi.encodePacked(digest, r, s, bytes32(pubKeyX), bytes32(pubKeyY)));
return ok && out.length == 32 && bytes32(out) == bytes32(uint256(1));
}
}
```

Deploy it with your usual tooling (Foundry, Hardhat, or `viem`'s `deployContract`), passing your
P256 public-key coordinates to the constructor.

:::caution[Gas limits]
In order to mitigate SP griefing the entire `isAuthorized` check is hard limited to 150M gas.
This is enough to implement something sophisticated like a touchID passkey verifier but you
still need to be careful an write optimized code when writing or deploying your own authorizer.
:::

## Step 2 — attach it to a data set

:::note[ABI preview]
Because the SDK's bundled FWSS ABI predates this method, the below example uses an inline ABI fragment
with `viem`. As soon as this feature reaches GA this will be cleaned up.
:::

Attaching is a call to `setDataSetAuthorizer(dataSetId, authorizer)` on the FWSS contract. **Only the
data set's payer may call it.**:

```ts
import { createWalletClient, http, getAddress, type Hex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { calibration } from '@filoz/synapse-core/chains'

// FWSS address for your network — see /resources/contracts/
const WARM_STORAGE = '0x...' as Hex

const setAuthorizerAbi = [{
type: 'function', name: 'setDataSetAuthorizer', stateMutability: 'nonpayable',
inputs: [{ name: 'dataSetId', type: 'uint256' }, { name: 'authorizer', type: 'address' }],
outputs: [],
}] as const

const payer = createWalletClient({
account: privateKeyToAccount('0x<payer-private-key>' as Hex),
chain: calibration,
transport: http(),
})

await payer.writeContract({
address: WARM_STORAGE,
abi: setAuthorizerAbi,
functionName: 'setDataSetAuthorizer',
args: [42n /* dataSetId */, getAddress('0x<your-authorizer>')],
})
```

`setDataSetAuthorizer` requires the target to be a deployed contract (`authorizer.code.length > 0`),
or `address(0)` to detach (see Step 4). You can read the current authorizer back with the matching
`getDataSetAuthorizer(uint256) → address` view (or `getDataSetAuthorizer` on the FWSS **State View**
contract).

:::caution[The authorizer is the *sole* gate for the operations it covers]
While attached, the authorizer **fully replaces** the payer and session-key signature check for
add-pieces, schedule-removals, and *signed (immediate)* termination. **There is no payer bypass**
so if the you still wish to call FWSS operations direct from the data set payer then they must be
recognized by the authorizer too.

To protect the payer wallet from too much unnecessary exposure the preferred route would be to
create a dedicated delegate key and register it with the authorizer contract, but if for any reason
this fails the payer can always detach the authorizer with `setDataSetAuthorizer(dataSetId, address(0))`
and return to the default signature path.
:::

## Step 3 — authorize operations with `extraData`

FWSS operations that can be gated by the authorizer are:
- AddPieces
- SchedulePieceRemovals
- TerminateService

Because all access control decisions are passed to the registered authorizer, it must handle all of
these operations, else they will always revert.

Build the blob your `isAuthorized` implementation expects and pass it as the pre-built
**`extraData`** on the operation:

```ts
import { encodeAbiParameters, type Hex } from 'viem'

// For our SingleP256Authorizer, `signature` = abi.encode(r, s) over the FWSS digest.
// Compute the digest FWSS will use for this add-pieces operation, sign it with your P256 key,
// then wrap the (r, s) exactly as your authorizer's abi.decode expects.
const signature = encodeAbiParameters(
[{ type: 'bytes32' }, { type: 'bytes32' }],
[rHex, sHex],
)
// FWSS forwards `extraData` to the SP, which passes it to your authorizer as `signature`.
const extraData = signature as Hex

// High-level: pass extraData to skip the SDK's own signing
await synapse.storage.addPieces({ dataSetId: 42n, pieces, extraData })
```

## Step 4 — rotate or remove the authorizer

Attach a different contract to rotate, or `address(0)` to detach and return the data set to the
default payer/session-key behavior:

```ts
await payer.writeContract({
address: WARM_STORAGE,
abi: setAuthorizerAbi,
functionName: 'setDataSetAuthorizer',
args: [42n, '0x0000000000000000000000000000000000000000'],
})
```

Detaching is immediate and always available to the payer, so a malfunctioning authorizer can
never permanently lock a payer out of their own data set.

## Session keys vs programmable ACLs

Both approaches delegate signing away from the payer wallet; they solve different problems
and can be used together (a session key for routine UX, an authorizer for a specific policies
on specific data sets).

| | [Session keys](/developer-guides/access-control/session-keys/) | Programmable ACLs |
| --- | --- | --- |
| **What it is** | An SDK-native ephemeral **secp256k1** key with on-chain permission grants | **Your own contract** deciding each write |
| **Where policy lives** | The shared `SessionKeyRegistry` (per-key, per-operation, time-boxed grants) | Arbitrary logic in your authorizer |
| **Curves / auth** | secp256k1 (EVM signatures) | Anything — P256 passkeys/WebAuthn, multisig, thresholds, oracles |
| **SDK support** | First-class (`@filoz/synapse-core/session-key`, `Synapse({ sessionKey })`) | `extraData` passthrough + `viem` (no dedicated helper yet) |
| **Scope** | Per session key, across all your data sets | Per data set |
| **Reach for it when** | You want silent signing / better dApp UX with the standard model | You need a curve or policy the built-in check can't express |

Session keys remain the recommended default for ordinary "sign once, operate silently" UX.
Reach for a programmable ACL when you need something the standard model can't do — most commonly
**passkey/WebAuthn authorization** or **custom on-chain policy**.

## Security Considerations & Caveats

- **Full responsibility.** When attached to a dataset the authorizer contract takes over **all*
responsibility for access controls, **including verifying whatever `signature` it expects over
`digest`**. Exercise extreme diligence in security review of any authorizer contract you deploy.
Ideally you should use an FWSS-provided contract and only write your own if you need functionality
that is not already covered.
- **Gas.** A P256/passkey authorizer verifies via the FEVM secp256r1 precompile, which is expensive
on Filecoin (~100M-120M gas for a full passkey `isAuthorized`). The storage provider that relays
your operation must supply enough gas; FWSS caps the authorizer sub-call at 150M gas.
- **Providers may not accept every authorizer.** Because the storage provider fronts the gas for
`isAuthorized`, a provider can restrict which authorizer *code* it is willing to relay for —
typically an allowlist of audited implementations, matched by code identity rather than address (so
you can run your own isolated instance of approved code, e.g. an EIP-1167 clone). A bespoke
authorizer may be rejected at add-pieces / removal / terminate time unless the provider recognizes
it. Prefer an FWSS-provided / audited authorizer (or a clone of one), or check with your provider
before relying on a custom one.
- **`extraData` size.** Space for the auth envelope in `extraData` is limited to 1024 bytes.
Compact signatures (a raw P256 `(r, s)`) fit comfortably; a full WebAuthn passkey envelope is
larger (~600–750 bytes), so ensure that the auth data is as compact as possible.
- **Replay.** FWSS enforces replay protection for **add-pieces** (a per-payer nonce baked into the
digest). The schedule-removals and terminate digests are **not** nonce-bound, so if your policy
needs replay protection for those, your authorizer must provide it (e.g. consume its own nonce).
- **Direct termination is not gated.** An attached authorizer gates *signed (immediate)* termination
only. It does **not** restrict a payer or the storage provider from terminating a data set via the
plain, unsigned path. Do not rely on an authorizer to gate or prevent termination.

## Next Steps

- [Session Keys](/developer-guides/access-control/session-keys/) — the SDK-native delegation model.
- [Storage Operations](/developer-guides/storage/storage-operations/) — add-pieces, removals, and
termination that accept the `extraData` override.
- [Filecoin Warm Storage Service](/core-concepts/fwss-overview/) — how FWSS authorizes writes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
title: Session Keys
description: Delegate signing permissions to ephemeral keys for improved UX and security.
sidebar:
order: 6
order: 2
---

:::tip[Which access control method to use?]
FWSS offers 2 distinct methods of controlling write access to data sets.

Ensure you pick the right one by consulting [which should I use?](/developer-guides/access-control/which-to-use/) first.
:::

## What are session keys?

Session keys are ephemeral signing keys that can perform a limited set of operations on behalf of a root wallet. They are registered on-chain via the **SessionKeyRegistry** contract, which stores permission grants as time-limited authorizations.
Expand Down Expand Up @@ -337,4 +343,8 @@ const expirations = await SessionKey.getExpirations(publicClient, {

## Next Steps

- [Programmable ACLs](/developer-guides/access-control/programmable-acls/) — fine-grained per-dataset operation access control on FWSS.
- [Storage Operations](/developer-guides/storage/storage-operations/) — add-pieces, removals, and
termination that accept the `extraData` override.
- [Filecoin Warm Storage Service](/core-concepts/fwss-overview/) — how FWSS authorizes writes.
- [Session Keys API](/reference/filoz/synapse-core/session-key/toc/#functions): Reference documentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Which Access Control System To Use
description: Which access control system to use for your data sets
sidebar:
order: 1
---

## Two types of access control delegation

FWSS offers 2 distinct ways of controlling access to dataset write operations (addPieces,
schedulePieceDeletion, and terminateService). Which you should choose will depend on your use
case and may even vary between data sets.

The two are complementary and can be used together on different datasets belonging to the same
owner.

### Session Keys

Session keys allow the payer to delegate its authority to update data sets to a secp256k1
key, enabling operations without the need for constant wallet authorizations from the
payer wallet. This is a simple, coarse delegation that empowers the session key holder
to update any dataset belonging to the payer.

### Programmable ACLs

Programmable ACLS in FWSS provide rich, fine-grained per-dataset, per-operation control using a
smart contract that you supply. They enable you to use different signing algorithms and more detailed
per-dataset delegation than the default mechanism (eg P256 passkey (Touch ID), a multisig, or
per-operation rate-limits) .

## Session keys vs programmable ACLs

Both approaches delegate signing away from the payer wallet; they solve different problems
and can be used together (a session key for routine UX on basic data sets, an authorizer for
specific policies on specific data sets).

| | [Session keys](/developer-guides/access-control/session-keys/) | [Programmable ACLs](/developer-guides/access-control/programmable-acls/) |
| --- | --- | --- |
| **What it is** | An SDK-native ephemeral **secp256k1** key with on-chain permission grants | **Your own contract** deciding each write |
| **Where policy lives** | The shared `SessionKeyRegistry` (per-key, per-operation, time-boxed grants) | Arbitrary logic and storage in your authorizer contract |
| **Curves / auth** | secp256k1 (EVM signatures) | Anything — P256 passkeys/WebAuthn, multisig, thresholds, oracles - so long as they fit within [the limits](/developer-guides/access-control/programmable-acls#security-considerations--caveats) |
| **SDK support** | Applies to native Synapse and FWSS | FWSS only |
| **Scope** | All your data sets at once | Fine-grained per data set |
| **Reach for it when** | You want silent signing / better dApp UX with the standard model | You need an algorithm or policy the session key registry can't express |

Session keys are the recommended default for ordinary "sign once, operate silently" UX. They are fast and
cheap and work with raw Synapse SDK as well as FWSS.

Reach for a programmable ACL when you need something the standard model can't do — most commonly
things like **passkey/WebAuthn authorization** or **custom on-chain policy**.

## Next Steps

- [Session Keys](/developer-guides/access-control/session-keys/) — the SDK-native delegation model.
- [Programmable ACLs](/developer-guides/access-control/programmable-acls/) — fine-grained per-dataset operation access control on FWSS.
Loading