passportxyz / passport

Passport allows users to prove their identity through a secure, decentralized UI
Other
989 stars 459 forks source link

Assessment of VC Schema Alignment with EAS x Ceramic Collaboration #1935

Open erichfi opened 11 months ago

erichfi commented 11 months ago

Objective:

To conduct a swift, one-day investigation into our current VC schema against the EAS x Ceramic collaboration, ensuring our approach is aligned with the latest standards and practices before our migration to ComposeDB.

Background:

With the strategic move to Ceramic ComposeDB on the horizon, it's essential to verify that our VC schema is in line with the latest industry developments, specifically the collaboration between EAS and Ceramic.

Key Questions:

Tasks:

Time Frame: The investigation will be timeboxed to one day, with findings to be presented at the end of the day.

Deliverables:

mzkrasner commented 11 months ago

Just catching myself up to speed here on the passport side, so apologies in advance if I misunderstand the current architecture in any way. I created the EAS x Ceramic guide, so hopefully I'll be able to share some helpful insight there.

As you've seen, the example I used in the EAS x Ceramic off-chain attestation guide used a simple MetIRL Schema that only took in a boolean value, while the issuer and recipient are defined when the issuing event occurs.

Also, if my understanding of this example implementation is correct, there would presumably be one static issuer of each off-chain passport attestation (unlike the EAS x Ceramic example).

Putting the current deployed Passport Stamps V1 definitions aside for a moment and looking only at the values within a passport vc vs my understanding of how one would issue an off-chain passport attestation with the same fields, I'd imagine the schema types needed would just be:

Using ComposeDB, we could define a general attestation interface that each individual provider could implement, thus opening up multiple entrypoints. For example:

interface Attestation 
@createModel(description: "An attestation interface")
{
  publisher: DID! @documentAccount 
  uid: String! @string(minLength: 66, maxLength: 66)
  schema: String! @string(minLength: 66, maxLength: 66)
  attester: String! @string(minLength: 42, maxLength: 42)
  verifyingContract: String! @string(minLength: 42, maxLength: 42)
  easVersion: String! @string(maxLength: 5)
  version: Int!
  chainId: Int! 
  r: String! @string(minLength: 66, maxLength: 66)
  s: String! @string(minLength: 66, maxLength: 66)
  v: Int! 
  types: [Types] @list(maxLength: 100)
  recipient: String @string(minLength: 42, maxLength: 42)
  expirationTime: DateTime
  revocationTime: DateTime
  refUID: String @string(minLength: 66, maxLength: 66)
  time: Int! 
  data: String! @string(maxLength: 1000000)
}

type Types {
  name: String! @string(maxLength: 20)
  type: String! @string(maxLength: 20)
}

type TwitterVerification implements Attestation 
  @createModel(accountRelation: LIST, description: "An account attestation")
  @createIndex(fields: [{ path: ["attester"] }])
  @createIndex(fields: [{ path: ["recipient"] }])
  @createIndex(fields: [{ path: ["time"] }])
{
  publisher: DID! @documentAccount 
  uid: String! @string(minLength: 66, maxLength: 66)
  schema: String! @string(minLength: 66, maxLength: 66)
  attester: String! @string(minLength: 42, maxLength: 42)
  verifyingContract: String! @string(minLength: 42, maxLength: 42)
  easVersion: String! @string(maxLength: 5)
  version: Int!
  chainId: Int! 
  r: String! @string(minLength: 66, maxLength: 66)
  s: String! @string(minLength: 66, maxLength: 66)
  v: Int! 
  types: [Types] @list(maxLength: 100)
  recipient: String @string(minLength: 42, maxLength: 42)
  expirationTime: DateTime
  revocationTime: DateTime
  refUID: String @string(minLength: 66, maxLength: 66)
  time: Int! 
  data: String! @string(maxLength: 1000000)
}

When generating the offchain instances, the data values themselves (the key-values for things like provider, etc) get encoded and signed (EIP712), so the ComposeDB storage definitions are intended to save both the encoded data, as well as the signatures needed to later validate to prove they have not been tampered with, if necessary.

In the current Ceramic x EAS repo, you'll notice that the individual user is creating and signing each attestation, while an API is called thereafter to write the attestation to ComposeDB using a static key:did. For each Gitcoin issuance, one difference here is that both the offchain issuance and the writing to ComposeDB would be done server-side by a static key, which would honestly simplify things further.

I'm happy to make a branch within our EAS + Ceramic repo to show POC for this if you'd like. IMO, it would a fairly light lift to retrofit the existing code to create offchain passport attestations, and most of the decision-making would be around how to translate those payloads into composeDB schemas that support the type of querying you'll need.

lucianHymer commented 11 months ago

@mzkrasner Awesome, thank you so much for all this info and for digging into this!

So it seems like the main change here is that the attestations do not contain the signature, instead the attestation itself is signed and written to Ceramic by the issuer (us). And so this means that the credential will be part of our Ceramic stream, not the users' streams.

This is pretty different from how we issue our credentials now. Currently, the credential itself includes our signature for the credentialSubject, so we can pass the credential around and have anybody store it wherever they want and we can still see that it's a valid credential that we issued. In the current architecture, the user writes this to their Ceramic stream and to our database (validated with the same did-session as their ceramic stream).

@mzkrasner have you guys considered some sort of AttestationVC or AttestationByOther (I'm having trouble thinking of a good name right now haha) that would include a signature for some subset of the data? Maybe even include verification of that signature when verifying the offchain attestation?

lucianHymer commented 11 months ago

More broadly speaking, it seems like we've ended up with two distinct ways of representing passports: 1) Attestations 2) VCs

The Attestation could be an attestation about the state of one or more credentials in a passport, or an attestation about the score of a passport at a particular time

VCs are basically a credential attestation wrapped in some additional data that can be used to verify it. We don't currently issue VCs for scores.

It's easy to go from a VC to an attestation, and also possible to go from an attestation to a pseudo-VC if we either 1. hang on to the signature and tie it with the attestation or 2. we're the issuer and we can re-sign it.

We can store both attestations and VCs in any storage mechanism (onchain with EAS, offchain in ceramic or our DB, etc.).

With our EAS implementation, we're signing an attestation and then passing this attestation and signature back to the user (this is sort of a pseudo-VC at this point. Now that we've got the EIP-712 credentials, we could just pass those real VCs back to the user instead.). The user writes this onchain, paying for the transaction themself. Our contract verifies the attestation signature and then writes the attestation to EAS.

One worry about switching to attestations that we issue directly to ComposeDB from our backend is that we'll be on the hook if Ceramic starts charging a fee. And then we'd have to scramble to re-architect everything, or pay the fees ourselves.

lucianHymer commented 11 months ago

It would be a simpler system overall if we were just writing directly to our DB and our Ceramic stream from our backend. But I like how it's a bit more self-sovereign at this point. Technically anybody could use our backend to get VCs and then store them wherever they want.

I might just be into the narrative, and it may not make a difference in the end. But we sure do like our narratives in crypto haha.

nutrina commented 11 months ago

Hey @mzkrasner thanks for providing this detailed example. @lucianHymer I have a bit of a different perspective on this.

From my point of view, both VCs and Off-chain Attestations are self-contained records. Both contain:

The VCs are issued and signed by our backend, and we can do the same with EAS attestation (I have not tested yet, I have only tested off-chain attestation issues in the browser, but I see no impediment in doing this). And in both case we can store the data (JSON object) wherever we choose, for example in a DB, on ceramic, in the users local storage, or convert to a QR code). Even more, once we switch our VC format to use EIP-712 signatures, the 2 approaches will become even more similar.

So, the 2 formats from my perspective are interchangeable (at least for the current use-case that we have).

The current schema that we have for writing passports on-chain however, I think is not well suited for off-chain usage, because we have binary-encoded all the users VCs into a single structure to save gas. So using this schema for off-chain data is hard and I would not go this route.

However if we are thinking of creating a attestation schema for a single VC on EAS, then this could really be a replacement for our current VCs. We would then essentially have an attestations and a VC with the exact payload, and each having a signature attesting to that payload, but in a different data structure.

There are also other aspects to consider of course (for example revocations- but both formats support this ...).

nutrina commented 11 months ago

@lucianHymer I have created an overview for showing the current data flow: https://app.diagrams.net/#Hgitcoinco%2Fpassport%2F1935_data_flow%2Fdocs%2FOverview.md

It's stored in the branch linked to this ticket ....

lucianHymer commented 11 months ago

@nutrina Okay interesting, I misinterpreted this ticket I think.

So I think the VC is self-contained, but the attestation either 1. needs to be written to Ceramic by the attester or 2. we have to make sure to hold on to the signature and pass it around with the attestation (not as part of the attestation itself, the schema doesn't support that), and ensure that some process is verifying this signature (like we're doing now with our onchain attestations).

It's sort of just semantic, we can make either work. But if we were to write offchain Attestations of the above format to Ceramic, we would have to do it directly from our backend which is a deviation from how we're handling things currently. But perhaps that's fine, we mostly did it this way to avoid paying users' gas.

mzkrasner commented 11 months ago

Apologies for my delay - just catching up on this thread now.

@lucianHymer - appreciate you explaining the current setup (being that the credentials themselves are signed by Gitcoin, but the stream is controlled by the user).

Both VCs and off-chain attestations are tamper-evident, so you could still create the same setup for allowing users to store their own attestations that are signed by passport, and can be passed around and validated where and when validation is needed. EAS's off-chain attestation verification methods function similarly to a VC lib like Veramo, so as @nutrina mentioned, regardless of who owns the Ceramic stream (user vs some static server), the validation calls can tell whether it's been tampered with or not

nutrina commented 11 months ago

I have compared the following scenarios:

Scenario A - Gitcoin Passport implements writing VCs to compose DB. This would follow the VC specification (this is already on our roadmap).

Scenario B - EAS off-chain attestation: a user writes his stamps as EAS off-chain attestations to ComposeDB (this is currently a hypothetical scenario, we only use EAS for creating on-chain attestations atm.)

In both cases we end up having a JSON document that is signed by the same party (the Gitcoin Issuer) that is stored on ComposeDB. The difference will be in the structure of the JSON document:

For our use-case, we could also switch from one format to the other (from VC to EAS off-chain attestation). But this would come at the cost of adding support for the EAS format (handling the attestation document, reading & decoding the payload, check the validity ...).

If on the other hand EAS would allow writing off-chain attestations in a VC format (instead of the proprietary one) and preferably not ABI-encode the payload into a byte-string then loading these attestation would be easier, we could essentially introduce support for loading EAS off-chain attestation from Ceramic for example, and stick to the same tooling.

To the key questions in this tickets description:

Are there elements in the EAS x Ceramic schema that offer significant benefits over our chosen schema?

Answer: No. We can also use EAS off-chain attestations to store our stamps, but sticking to VC seems currently the better choice, also because of the wider support (multiple libraries across several languages), and the payload is not binary encoded.

How does our current VC schema compare in terms of interoperability and future-readiness with the EAS x Ceramic standards?

Answer: This depends on the future-perspective. EAS I think is very much on-chain focused, while the VC format is not (I think this is reflected also in the off-chain attestation format). If EAS however would support writing attestation into a VC format (with EIP-712 signature), interoperability easier). User could use tools like didkit (available in several languages) to validate these attestations), and attestations would still remain verifiable on-chain.

What are the implications of adopting the EAS x Ceramic schema for our migration strategy and operational continuity?

Answer: I asume this question refers to this example: https://docs.attest.sh/docs/tutorials/ceramic-storage . So, the format presented here would also get the job done, but:

  • the payload is binary-encoded (compared to cleartext in the VC)
  • VC has a better support in tooling (several providers of libraries, across multiple programming languages)