-
Notifications
You must be signed in to change notification settings - Fork 7
feat: w3-deal protocol #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,217 @@ | ||||||
| # Filecoin Deal Signing Protocol | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| ## Editors | ||||||
|
|
||||||
| - [Irakli Gozalishvili], [Protocol Labs] | ||||||
|
|
||||||
| ## Authors | ||||||
|
|
||||||
| - [Irakli Gozalishvili], [Protocol Labs] | ||||||
|
|
||||||
| # Abstract | ||||||
|
|
||||||
| This specification describes a [UCAN] protocol allowing a broker like [spade] to request signing a storage deal. | ||||||
|
|
||||||
| ## Language | ||||||
|
|
||||||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119). | ||||||
|
|
||||||
| # Terminology | ||||||
|
|
||||||
| ## Roles | ||||||
|
|
||||||
| There are several roles that actors can have in this protocol: | ||||||
|
|
||||||
| | Name | Description | | ||||||
| | ----------- | ----------- | | ||||||
| | Storefront | [Principal] identified by [`did:web`] identifier, representing a storage aggregator like w3up | | ||||||
| | Broker | [Principal] that arranges filecoin deals with storage providers like [spade] | | ||||||
| | Agency | [Principal] submitting pieces to the _Broker_ like spade-proxy | | ||||||
|
|
||||||
| ### Storefront | ||||||
|
|
||||||
| A _Storefront_ is a type of [principal] identified by a [`did:web`] that aggregates user data into [aggregate][] pieces and submits those to the broker to arrange deals with storage providers. | ||||||
|
|
||||||
| ### Broker | ||||||
|
|
||||||
| A _Broker_ is a type of [principal] identified that arranges deals for the aggregates submitted by _Storefront_. | ||||||
|
|
||||||
| # Protocol | ||||||
|
|
||||||
| ## Overview | ||||||
|
|
||||||
| All the filecoin deals need to be signed by a Fil wallet, in order to avoid passing private keys to wallet _Storefront_ could delegate a capability to a sign a deal to a _Broker_ instead. | ||||||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
How does this sound ? |
||||||
|
|
||||||
| Here we propose set of UCAN capabilities that can be invoked by authorized actors (like _Agency_ or a _Broker_) to sign deals on behalf of the delegate (_Storefront_). | ||||||
|
|
||||||
| ## IPLD Schema | ||||||
|
|
||||||
| ```ipldsch | ||||||
| type Deal union { | ||||||
| | Sign "deal/sign" | ||||||
| } representation inline { | ||||||
| discriminantKey "can" | ||||||
| } | ||||||
|
|
||||||
| type Sign struct { | ||||||
| with StorefrontDID | ||||||
| nb DealProposal | ||||||
| } | ||||||
|
|
||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/ff2ed169ff566458f2acd8b135d62e8ca27e7d0c/builtin/v9/market/deal.go#L201-L221 | ||||||
| # We use capital case letters because that the way go likes them | ||||||
| type DealProposal struct { | ||||||
| PieceCID &Piece | ||||||
| PieceSize PaddedPieceSize | ||||||
| VerifiedDeal bool | ||||||
| # Signer wallet (f0) address | ||||||
| Client Address | ||||||
| # Storage provider wallet (f0) address for whom the contract is made | ||||||
| Provider Address | ||||||
| # Label is an arbitrary client chosen label to apply to the deal | ||||||
| Label DealLabel | ||||||
|
|
||||||
| # Nominal start epoch. Deal payment is linear between StartEpoch and EndEpoch, | ||||||
| # with total amount StoragePricePerEpoch * (EndEpoch - StartEpoch). | ||||||
| # Storage deal must appear in a sealed (proven) sector no later than StartEpoch, | ||||||
| # otherwise it is invalid. | ||||||
| StartEpoch ChainEpoch | ||||||
| EndEpoch ChainEpoch | ||||||
| StoragePricePerEpoch TokenAmount | ||||||
|
|
||||||
| ProviderCollateral TokenAmount | ||||||
| ClientCollateral TokenAmount | ||||||
| } | ||||||
|
|
||||||
| type StorefrontDID string | ||||||
|
|
||||||
| # Piece CID is Piece / Aggregate merkle root encoded as CID | ||||||
| type Piece = Any | ||||||
|
|
||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/ff2ed169ff566458f2acd8b135d62e8ca27e7d0c/abi/piece.go#L12 | ||||||
| type PaddedPieceSize = Uint64 | ||||||
|
|
||||||
| # @see https://github.com/filecoin-project/go-address/blob/master/address.go#L39-L40 | ||||||
| type Address struct { addr: string } | ||||||
|
|
||||||
|
|
||||||
| # The DealLabel is a kinded union of string or byte slice. | ||||||
| # It serializes to a CBOR string or CBOR byte string depending on which form it | ||||||
| # takes. | ||||||
| # The zero value is serialized as an empty CBOR string (maj type 3). | ||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/ff2ed169ff566458f2acd8b135d62e8ca27e7d0c/builtin/v9/market/deal.go#L37C1-L44C1 | ||||||
| type DealLabel struct { | ||||||
| bs Bytes | ||||||
| notString bool | ||||||
| } | ||||||
|
|
||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/ff2ed169ff566458f2acd8b135d62e8ca27e7d0c/abi/chain.go#L9C1-L10 | ||||||
| type ChainEpoc = Int64 | ||||||
|
|
||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/ff2ed169ff566458f2acd8b135d62e8ca27e7d0c/abi/chain.go#L16-L22 | ||||||
| type TokenAmount = SerializedBigInt | ||||||
|
|
||||||
| # In principal IPLD Int range is unlimited, but in practice it is | ||||||
| # something implementations need to worry about so it's best to | ||||||
| # specify | ||||||
| # @see https://ipld.io/design/tricky-choices/numeric-domain/#integers | ||||||
| type Uint64 = Int | ||||||
| type Int64 = Int | ||||||
| # Looks like an Int serialized into a string if I'm not mistaken | ||||||
| # @see https://github.com/filecoin-project/go-state-types/blob/master/big/int.go#L294-L303 | ||||||
| type SerializedBigInt = string | ||||||
| ``` | ||||||
|
|
||||||
| ## Ucanto Interface | ||||||
|
|
||||||
| ```ts | ||||||
| import { Piece, PaddedPieceSize, Uint68 } from "@web3-storage/data-segment" | ||||||
| import { Invocation, ToString, InvocationError } from "@ucanto/interface" | ||||||
|
|
||||||
| export interface Deal { | ||||||
| (invocation: Invocation<{ can: "deal/sign", with: StorefrontDID, nb: DealProposal }>): Result<Signature, SignError> | ||||||
| } | ||||||
|
|
||||||
| // Note that we use capital case field names for compatibility with go | ||||||
| export interface DealProposal { | ||||||
| Piece: Piece.Link | ||||||
| Size: Piece.PaddedSize | ||||||
|
|
||||||
| VerifiedDeal: boolean | ||||||
|
|
||||||
| Client: Address | ||||||
| Provider: Address | ||||||
| Label: DealLabel | ||||||
|
|
||||||
| StartEpoch: ChainEpoch | ||||||
| EndEpoch: ChainEpoch | ||||||
| StoragePricePerEpoch: TokenAmount | ||||||
|
|
||||||
| ProviderCollateral: TokenAmount | ||||||
| ClientCollateral: TokenAmount | ||||||
| } | ||||||
|
|
||||||
| export interface Address { | ||||||
| addr: string | ||||||
| } | ||||||
|
|
||||||
| export interface DealLabel { | ||||||
| bs: Uint8Array | ||||||
| notString: boolean | ||||||
| } | ||||||
|
|
||||||
| export type ChainEpoch = Uint68 | ||||||
| export type TokenAmount = ToString<bigint> | ||||||
|
|
||||||
| export type Signature = Uint8Array | ||||||
|
|
||||||
| export type SignError = | ||||||
| | InvocationError | ||||||
| | InvalidDeal // ProposalRemarshalMismatch | ||||||
| | SigningError // WalletSignError | ||||||
| ``` | ||||||
|
|
||||||
| ## Capabilities | ||||||
|
|
||||||
| ### `deal/sign` | ||||||
|
|
||||||
| Broker can invoke `deal/sign` capability with `DealProposal` in (`nb` field). Storefront MUST encode supplied `DealProposal` (`nb` field) into a CBOR block and then sign it with a wallet private key. | ||||||
|
|
||||||
| Provider MUST respond with raw bytes of the signature. | ||||||
|
|
||||||
| ## HTTP Interface | ||||||
|
|
||||||
| Given that some actors (e.g. Spade) do not support UCANs natively they are not able to send signed invocations. As compromise protocol implementer is RECOMMENDED to expose plain HTTP API that trade-offs some security for convenience of interop. | ||||||
|
|
||||||
| Implementation MUST expose HTTP POST endpoint that accepts `application/vnd.ucan.cbor` requests with CBOR encoded as payload. These requests MUST provide `Authorization: Bearer` header with a UCAN delegation authorizing a request. Receiving principal MUST derive invocation from the provided `Authorization` and set invoked capability `nb` field to decoded CBOR block of the payload. Receiving principal MUST execute received capability and encode result of the invocation as an HTTP response. | ||||||
|
|
||||||
| > Above HTTP interface could be utilized by spade to obtain signatures from w3up without having to proxy them through UCAN proxy. | ||||||
|
|
||||||
| ## Interaction Flow | ||||||
|
|
||||||
| ### Authorization per aggregate | ||||||
|
|
||||||
| _Storefront_ (w3up) MAY delegate `deal/sign` UCAN capability to the _Agency_ (spade-proxy) and specify `Piece` and `Size` fields of the submitted aggregate. | ||||||
|
|
||||||
| _Agency_ (spade-proxy) could also re-delegate that capability to the _Broker_ (spade) allowing it to request signature directly from _Storefront_ (w3up). | ||||||
|
|
||||||
| > ℹ️ Since ♠️ Spade does not support UCANs (yet), _Agency_ could instead create an invocation UCAN and pass it on to Spade so it could be used by spade as a plain, but short lived, JWT token for signing that specific deal. | ||||||
|
|
||||||
| ### Long term authorization | ||||||
|
|
||||||
| _Storefront_ (w3up) MAY delegate unconstrained `deal/sign` UCAN capability to the _Agency_ (spade-proxy). By leaving out `Piece` and `Size` fields it will authorize it to sign any deals. | ||||||
|
|
||||||
| _Agency_ (spade-proxy) could also re-delegate same unconstrained `deal/sign` UCAN capability to the _Broker_ (spade) allowing it to sign any deals. | ||||||
|
|
||||||
| This trade-offs increased security for convenience. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can delegate capability when we perform
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should go for auth per offer, that said I think it's good to have both options listed. There is another tradeoff that I failed to communicate here, which is token per offer means that those tokens are likely to be included in requests and consequently can get leaked very easily. Long term tokens are likely exchanged ahead of time and out of bound, so they are less likely to get leaked in comparison to per-request approach. |
||||||
|
|
||||||
| [spade]:https://github.com/ribasushi/spade | ||||||
| [`did:web`]: https://w3c-ccg.github.io/did-method-web/ | ||||||
| [UCAN]: https://github.com/ucan-wg/spec/ | ||||||
| [principal]: https://github.com/ucan-wg/spec/#321-principals | ||||||
|
|
||||||
| [Protocol Labs]:https://protocol.ai/ | ||||||
| [Irakli Gozalishvili]:https://github.com/Gozala | ||||||
| [aggregate]:https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0058.md#specification | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The broker that in this case would be the Agency, at least in first iteration where Agency and Broker are not the same. Think it would be good to make that clear, or at least describe Agency role above and make clear its role today
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I see now the HTTP interface section below. With that in mind, I would suggest to add a small note here still to avoid same confusion from readers until later