Skip to content

Last Call - Chain Agnostic CApability Object#74

Merged
ligi merged 15 commits intoChainAgnostic:masterfrom
ukstv:cacao
Mar 10, 2022
Merged

Last Call - Chain Agnostic CApability Object#74
ligi merged 15 commits intoChainAgnostic:masterfrom
ukstv:cacao

Conversation

@ukstv
Copy link
Copy Markdown
Contributor

@ukstv ukstv commented Nov 1, 2021

caip: <to be assigned>
title: CACAO: Chain Agnostic CApability Object
author: Sergey Ukustov (@ukstv)
discussions-to: https://github.com/ChainAgnostic/CAIPs/pull/74
status: Last Call
type: Standard
created: 2021-11-01
updated: 2022-03-10

caip:
title: CACAO: Chain Agnostic CApability Object
author: Sergey Ukustov (@ukstv)
discussions-to: #74
status: Draft
type: Standard
created: 2021-11-01
updated: 2021-11-01

Simple Summary

Represent a chain-agnostic Object Capability (OCAP), created using EIP-4361 (or similar for other blockchains), as an IPLD object.

Abstract

In this document we define a way to present a result of EIP-4361 "Sign-in with Ethereum"
signing operation as an IPLD-based object capability (OCAP).
As we expect other blockchains to follow a path similar to Ethereum, CAIP seems to be the best place for such a proposal.

Motivation

"Sign-in with Ethereum" is a way for a user to authenticate into a service, and provide authorization. In essence, it is a signature of a well-formed payload, that can be read by a human as well as a machine.
We could see this as a stepping point for a rich capability-based authorization system.
In order to do this, we would like to have a standardized IPLD-based representation of the payload and the signature, that together comprise a capability.

Specification

Container format

We start construction with declaring a container format, that represents a signed payload.
It should contain meta-information, payload and signatures. For reference let's call such container CACAO.
We use IPLD schema language to describe the format.
Reminder, unless a field is marked optional, it is mandatory.

type CACAO struct {
  h Header // container meta-information
  p Payload // payload
  s Signature // signature, single
}

Header uniquely identifies the payload format:

type Header struct {
  t String // specifies format of the payload
}

For now, we expect this to be "eip4361" only. In the future, we anticipate creating a specialized registry for payload formats.
For "eip4361" the payload structure must be presented as follows:

type Payload struct {
  domain String // =domain
  iss String // = DID pkh
  aud String // =uri
  version String
  nonce String
  iat String // RFC3339 date-time =issued-at
  nbf optional String // RFC3339 date-time =not-before
  exp optional String // RFC3339 date-time = expiration-time
  statement optional String // =statement
  requestId optional String // =request-id
  resources optional [ String ] // =resources as URIs
}

It is important to note, that issuer here is did:pkh, which includes both blockchain address and blockchain network information.
Also, as per EIP-4361 "Sign-in with Ethereum" specificaction,
iat, nbf, and exp are encoded as RFC 3339 date-time, which could include milliseconds precision.

The signature in essence is just bytes, but we have to give a hint on how the signature verification should work.
At the moment, we limit the signature verification by two types:

  • eip191 indicates that that signature is made by an Ethereum externally owned account (EOA),
  • eip1271 indicates that the signature is made by an Etereum contract account (like Gnosis Safe or Argent); the verification should be done according to EIP-1271.

In the future, we anticipate creating a specialized registry for signature types.

type Signature struct {
  t String //= "eip191" or "eip1271"
  m optional SignatureMeta
  s Bytes
}

type SignatureMeta struct {
}

This construction allows a dApp to uniformly request a SIWE signature regardless of the user's account nature. The user's wallet determines if it should use an EOA or a contract account.

Signature Verification

We reconstruct the EIP4361 payload as follows:

{.p.domain} wants you to sign in with your Ethereum account:
{.p.iss[address]}

{.p.statement}

URI: {.p.aud}
Version: {.p.version}
Chain ID: {.p.iss[chainId]}
Nonce: {.p.nonce}
Issued At: {.p.iat}
Resources:
- {.p.resources[0]}
- {.p.resources[1]}
...
- {.p.resources[n]}

Signature verification goes according to t in SignatureMeta:

Serialization

As a proper IPLD object, it can be deterministically serialized using CBOR into bytes.
Performance is almost as fast as vanilla JSON serialization. For transport purposes we propose that a CACAO is passed inside a base64url-serialized CAR file,
with root of the CAR file set to a tip of capability chain. Here and now we use CARv1 format, as CARv2 is still being worked on.

We propose, that all the necessary parent CACAOs are passed there as well. This way, even if a referenced CACAO is not yet available over IPFS, both consumer and presenter of CACAO still can access it.

Rationale

  • As a chain-agnostic standard, a capability should identify chain-specific signature methods.
  • While "Sign-in with Ethereum" standardizes payload format, the payload could be extended in future.
  • The standard should be usable for DID-based signing methods as well as blockchain based ones.
  • The format we are creating here should be uniquely serialized as an IPLD object; we expect it to be identified by CID.
  • A capability format described here should allow chaining capabilities together.
  • We should standardize on a url-safe serialization format of a capability chain suitable for well-established non-binary transport protocols.

Backwards Compatibility

Not applicable.

Test Cases

Below you could find a CACAO, along with its serialized presentation in CAR file.

CACAO:

{
  "h": {
    "t": "eip4361"
  },
  "p": {
    "aud": "http://localhost:3000/login",
    "exp": "2022-03-10T18:09:21.481+03:00",
    "iat": "2022-03-10T17:09:21.481+03:00",
    "iss": "did:pkh:eip155:1:0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07",
    "nbf": "2022-03-10T17:09:21.481+03:00",
    "nonce": "328917",
    "domain": "localhost:3000",
    "version": 1,
    "requestId": "request-id-random",
    "resources": [
      "ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq",
      "https://example.com/my-web2-claim.json"
    ],
    "statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos"
  },
  "s": {
    "s": "5ccb134ad3d874cbb40a32b399549cd32c953dc5dc87dc64624a3e3dc0684d7d4833043dd7e9f4a6894853f8dc555f97bc7e3c7dd3fcc66409eb982bff3a44671b",
    "t": "eip191"
  }
}

CACAO Serialized: base64url-encoded CARv1 file with the IPFS block of the CACAO above:

uOqJlcm9vdHOB2CpYJQABcRIgEbxa4r0lKwE4Oj8ZUbYCpULmPfgw2g_r12IcKX1CxNlndmVyc2lvbgHdBAFxEiARvFrivSUrATg6PxlRtgKlQuY9-DDaD-vXYhwpfULE2aNhaKFhdGdlaXA0MzYxYXCrY2F1ZHgbaHR0cDovL2xvY2FsaG9zdDozMDAwL2xvZ2luY2V4cHgdMjAyMi0wMy0xMFQxODowOToyMS40ODErMDM6MDBjaWF0eB0yMDIyLTAzLTEwVDE3OjA5OjIxLjQ4MSswMzowMGNpc3N4O2RpZDpwa2g6ZWlwMTU1OjE6MHhCQWM2NzVDMzEwNzIxNzE3Q2Q0QTM3RjZjYmVBMUYwODFiMUMyYTA3Y25iZngdMjAyMi0wMy0xMFQxNzowOToyMS40ODErMDM6MDBlbm9uY2VmMzI4OTE3ZmRvbWFpbm5sb2NhbGhvc3Q6MzAwMGd2ZXJzaW9uAWlyZXF1ZXN0SWRxcmVxdWVzdC1pZC1yYW5kb21pcmVzb3VyY2VzgnhCaXBmczovL2JhZnliZWllbXhmNWFiandqYmlrb3o0bWMzYTNkbGE2dWFsM2pzZ3BkcjRjanIzb3ozZXZmeWF2aHdxeCZodHRwczovL2V4YW1wbGUuY29tL215LXdlYjItY2xhaW0uanNvbmlzdGF0ZW1lbnR4QUkgYWNjZXB0IHRoZSBTZXJ2aWNlT3JnIFRlcm1zIG9mIFNlcnZpY2U6IGh0dHBzOi8vc2VydmljZS5vcmcvdG9zYXOiYXNYQVzLE0rT2HTLtAoys5lUnNMslT3F3IfcZGJKPj3AaE19SDMEPdfp9KaJSFP43FVfl7x-PH3T_MZkCeuYK_86RGcbYXRmZWlwMTkx

Links

Copyright

Copyright and related rights waived via CC0.

@oed
Copy link
Copy Markdown
Collaborator

oed commented Nov 9, 2021

Should iss not be a DID PKH? Feels like we can collapse chainId + address into that?

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Nov 11, 2021

Should iss not be a DID PKH? Feels like we can collapse chainId + address into that?

True. Thank you. When writing the first draft, I mistakingly looked at an old version of did-pkh specification, which did not include chain information. Changed the spec here.

@chunningham chunningham mentioned this pull request Nov 25, 2021
@chunningham
Copy link
Copy Markdown

Is there a preferred precision for the Unix time stamps? Microsecond? Nanosecond?

@bumblefudge
Copy link
Copy Markdown
Collaborator

w3c/vc-data-model#846
^If we really wanted to be strict on time and future-proof against ecosystem-wide timestamp changes...

@chunningham
Copy link
Copy Markdown

It seems there is a round-trip inconsistancy between SIWE's string representation and CACAO. Valid SIWE messages can contain timestamps which have timezone offsets, which is included in the signing/verification input. When such a message is converted to CACAO form the timestamps are converted into UNIX time, discarding the time zone. Verifying the signature for such a CACAO would fail, as re-serializing the fields into SIWE form will represent all timestamps as UTC. I'm not opposed to requiring/generally expecting UTC timestamps but the SIWE spec doesnt require it, so not all SIWE messages will be losslessly convertable to CACAO form.

@oed
Copy link
Copy Markdown
Collaborator

oed commented Jan 10, 2022

@chunningham huh, I was under the impression that SIWE required the timestamp to always be UTC.

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Jan 10, 2022

@chunningham thanks for the notice. Apparently, date-time per ISO8601 indeed contains time zone specifier, and it is not required to be UTC, which we lose when converting to unix timestamp. We should adjust the spec to retain that information.

@oed
Copy link
Copy Markdown
Collaborator

oed commented Jan 10, 2022

@ukstv also, does ISO8601 support ms or not? If they do we need to solve for ms since UNIX timestamps are in seconds only.

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Jan 10, 2022

@ukstv also, does ISO8601 support ms or not? If they do we need to solve for ms since UNIX timestamps are in seconds only.

It does. Instead of unix timestamp we have to use full ISO8601 specifier, as string.

@chunningham
Copy link
Copy Markdown

chunningham commented Jan 10, 2022

If possible, I'd suggest RFC3339 as it's a little more restrictive, but in ways that most people would expect (ISO8601 allows for example "2022-100" = 100th day of 2022). IME when most people say ISO8601 they mean RFC3339. IIRC the SIWE spec is being updated to resolve the inconsistancy between ISO8601 in spec text and RFC3339 in the ABNF, in favour of RFC3339 everywhere (as ultimately the goal is for the message to be layperson-readable).

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Jan 13, 2022

Updated the spec. Now date-time values are encoded as a string, which should be formatted as per RFC3339. Added a note on possible inclusion of milliseconds.

Also, updated the example. Now it contains correct dates and issuer now is proper did:pkh.

@ukstv ukstv changed the title Draft - Chain Agnostic CApability Object Last Call - Chain Agnostic CApability Object Jan 13, 2022
@pedrouid
Copy link
Copy Markdown
Member

I was proposing to @oed a JSON-RPC method where a dapp can use a chain-agnostic interface to request a CACAO object by providing the payload params only and receiving a full object for CACAO with a header and a signature

However I think that the type for message construction (eg. eip-4361) should be specified from the dapp side as well and this would require splitting how CACAO types are currently specified

Essentially we now have two types:

  • eip4361-eip191
  • eip4361-eip1271
    But this includes both a standard for message construction and a standard for signature verification.

From my POV the responsibility of defining message construction is of the Dapp and and defining signature verification if of the wallet.. so if we split these two types then we could have a much better API between the two

The dapp requests a CACAO signature by providing a header and payload with type eip4361 and then the wallet can return the full CACAO object with a signature that includes the type eip191 or eip1271

@oed
Copy link
Copy Markdown
Collaborator

oed commented Jan 31, 2022

So an example would be something like this?

{
  h: {
    t: 'eip4361'
  },
  p: { ... }
  s: {
    m: {
      t: 'eip191'
    },
    s: ...
  }
}

Or maybe assume signature always have a type?

{
  h: {
    t: 'eip4361'
  },
  p: { ... }
  s: {
    t: 'eip191'
    s: ...
  }
}

@chunningham
Copy link
Copy Markdown

I'd assume signatures always have a type, to prevent ambiguity. Is it possible to include a compound object as the signature value? Specifically I've worked a little on a ZCAP-LD representation for CACAO with a linked data proof as the signature value (speculatively with type zcapld-ldp, but potentially distinguishing between invocation and delegation types, i.e. something like zcapInv-ldp and zcapDel-ldp).

@pedrouid
Copy link
Copy Markdown
Member

pedrouid commented Feb 3, 2022

@oed correct, the second one would be the preferred one

{
  h: {
    t: 'eip4361'
  },
  p: { ... }
  s: {
    t: 'eip191'
    s: ...
  }
}

@bumblefudge
Copy link
Copy Markdown
Collaborator

Discussed on today's call: draft will be updated to reflect split typing, if ready for review before next meeting we will tag reviewers from both implementing companies and an editor.
Implementer's listing desired, we could add a self-PR registry to that CAIP.

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Feb 17, 2022

Updated the spec for split typing, mentioning future registries for signatures and payloads.

@chunningham what is the best way to link to your CACAO implementation?

Copy link
Copy Markdown
Collaborator

@oed oed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I have one requested change though.

@chunningham
Copy link
Copy Markdown

I'm still working around some ipld issues. I just moved laptops but will upload the repo shortly. I've also noticed that nonce is an int in this model, while SIWE uses alphanumeric strings, is that an issue?

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Feb 18, 2022

@chunningham In the schema description, nonce is String. It is the example, that used integer nonce. Changed it.

@oed oed self-requested a review February 21, 2022 10:11
Copy link
Copy Markdown
Collaborator

@oed oed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be good to merge now 👍

@awoie
Copy link
Copy Markdown

awoie commented Mar 8, 2022

I think I found a bug in the example:

{ ... "p": { ...
    "aud": "http://localhost:3000", ...
    "uri": "http://localhost:3000/login", ...
  }, ...
}

Shouldn't be this rather the following?

{ ... "p": { ...
    "aud": "http://localhost:3000/login", ...
    "domain": "localhost:3000", ...
  }, ...
}

Two things:

  • domain according to SIWE is only the authority party
  • uri should be called domain according to the payload spec, see below ...
type Payload struct {
  domain String // =domain
  aud String // =uri
  ...
}

Copy link
Copy Markdown

@awoie awoie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might have found a bug in the example

"iat": "2022-02-21T13:06:33.933+03:00",
"iss": "did:pkh:eip155:1:0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07",
"nbf": "2022-02-21T13:06:33.940+03:00",
"uri": "http://localhost:3000/login",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be domain and if it is domain, we need only the authority part.

"t": "eip4361"
},
"p": {
"aud": "http://localhost:3000",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be the URI, in that case http://localhost:3000/login?

@oed
Copy link
Copy Markdown
Collaborator

oed commented Mar 8, 2022

Nice catch @awoie!

@ukstv
Copy link
Copy Markdown
Contributor Author

ukstv commented Mar 10, 2022

Thank you @awoie !! Changed the example to conform with the spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants