Hybrid Crypto-Credentials: Using Decentralized Learning As An Example

Ethereum Mar 6, 2022


At the core of the Self-Sovereign Identity movement are the new W3 specs for Decentralized Identifiers (DIDs) and Verifiable Credentials (VCs).
From working on InterRep I was already familiar with using an NFT as a way to represent credentials on-chain, but VCs appeared as an interesting alternative with different characteristics. Here are some similarities and differences:

  • VCs are not tied to any single blockchain, whereas NFTs live in smart contracts on a specific blockchain.
  • If DeFi has shown something, it's how powerful composability can become when building on top of an open, permissionless platform. As soon as an NFT is generated by one protocol it becomes usable by all other smart contracts out of the box! (NFT ownership can be verified in one line of code)
    VCs, however, can't be used natively in blockchain applications as they don't live on chain.
    It's still possible to use a VC with smart contracts by relying on an oracle or passing a proof as part of the transaction data but the process is much more cumbersome.
  • Everything stored on a blockchain, like Ethereum, is public. As a result, NFTs and any data related to them stored in their smart contract also is. That has massive implications in terms of privacy. As a rule of thumb, no personally identifiable information (PII) should be stored on-chain. VCs are like fancy JSON documents so they don't suffer from the same characteristic, they can be stored securely and privately. What's more with zero-knowledge proof and selective disclosure, a VC holder can choose to reveal only a part of the information attested by a VC or a true statement derived from it.
  • Both rely on cryptographic signatures and are tamper-resistant, although in the case of NFTs you are also relying on the security of the underlying blockchain for a few things; chief among them is preserving ownership of the tokens according to the rules written in the NFT smart contract.

But why not combine both?

Since NFTs often have a reference to some metadata, that reference could actually point to a VC!

Only the VC's URI would be stored in the NFT smart contract. Note that this URI could actually point to a "location" in an encrypted user data vault such that permissions could be put in place: then this URI would only resolve to the VC document only if you have been granted read access. With this approach, you get the best of both worlds, personal, private information can stay off-chain and stored in the VC, while the token, free of personally identifiable or private information, can be used on-chain.
This is what I would call hybrid crypto-credentials.

Right when I wanted to test this idea, a friend of mine suggested that we participate in the HackFS21 hackathon. It seemed like the perfect opportunity. We fleshed out the idea a bit more and chose to create an online learning platform that would issue these hybrid crypto-credentials. As an additional requirement, the whole system we set ourselves to create should be as much decentralized as possible: in other words no traditional backend servers (!).

The lessons for this online learning platform could be crowdsourced as the code is public on Github. Anyone would be able to create a new lesson and submit a Pull Request to add it to the catalog. This gives added meaning to the word "open" in Massive Open Online Courses: access and contributions are open to everyone. A DAO could be formed and tokens awarded to contributors whose lessons are added, but I digress... This blog post focuses on describing this "Proof Of Concept" that Iustin and I built.

Although it's clear that some improvements can be made (again, it's just a hack!), I'm pretty happy with the end result: it proves that hybrid crypto-credentials work and the whole system has very few centralized parts. Only one server is needed to sign VCs while keeping the signing key private (but even that could potentially be upgraded to a threshold signature scheme to avoid full centralization).


Here's an overview of how this decentralized learning platform works and issues hybrid crypto-credentials:

Lessons are accessible via a front-end. They are interactive in the sense that they require users to write some code and submit transactions to a smart contract (not a backend API).
To pass a lesson, the user has to submit a final transaction with the right data. If it's successful, a chain of actions are triggered: a Chainlink oracle asks for the issuance of a Verifiable Credential which gets stored on the decentralized storage network Ceramic, then, a non-transferrable NFT is minted on-chain and the location on the Ceramic Network of its VC counterpart is associated to it. Everything is pseudonymous as users are identified by a DID or their Ethereum address.

How it works

Curious to know more details? Here are the different step that go into getting the hybrid credentials.

The system is composed of two distinct parts: Open_Cred which can be re-used to create hybrid crypto-credential for any kind of use case and Open_Classes (which uses Open_Cred) containing everything related to this decentralized learning example.


First a user / student connects their wallet and log in with 3ID (step #0) on the Open_Classes website. 3ID is a DID method created by Ceramic. This is so we can issue an NFT to their address but also a VC to their DID.

💡 We could have simply used the ethr did method as we already know the user's address, but 3ID offers more flexiblity. It gives users the ability to control a DID via multiple blockchain accounts.

Lesson 1

The first lesson teaches you how to query events using ethers.js. The user first calls the smart contract for this lesson (#1) which triggers the generation of a unique random number using Chainlink VRF (#2, #3). This random number is stored and associated with the student's address. In addition, an event is emitted containing both this random number and the student's address.
The goal for this lesson is to find this random number, using Javascript and ethers.js, filtering the events by your address.

As a shortcut we added a CodeSandbox to have an IDE embedded right in our lesson's page. By following the instructions on the left side and adding some code on the right side, the random number can be printed to its console! 💪

Once the random number is found, it can be submitted on this same page which crafts a transaction to the lesson smart contract containing the number and the user's DID (#5). The smart contract in turn verifies it corresponds to the number expected for the sender's address (#6). If that's the case, the student passed the lesson and the credential issuance flow is initiated: the DID is passed as credential subject and the name of the lesson as credential title to the OpenCredentials smart contract (#7).


Once the OpenCredentials smart contract receives a request to generate a hybrid credential with all the properties needed, it forwards this request to a Chainlink node via an on-chain Oracle (#8). It also stores the requestId along with the recipient's address and the credential id (each "lesson smart contract" submitting a request is mapped to a credential id), these will be needed later.

Then, the Chainlink node calls a Lambda function on AWS via an external adapter (#9). You can view this lambda as the "VC issuer module" deployed by our Open Classes University to generate Verifiable Credentials and sign them (#10).
How are they signed? Inside the Lambda, a seed is used to create a DID which is authenticated with Ceramic, this time simply using the Key DID method, and able to create JSON Web Signatures (JWS).

Next, the signed VC is stored on the Ceramic (Clay test) Network in a Stream and the issuer's DID is passed as controller (#11). This returns a streamId which enables anyone to retrieve the VC from Ceramic (#12).

💡 For simplicity, a lot of thing were done from inside the Lambda, however I think only the signature could be performed there and the rest, handled by the Chainlink node including generating and storing the VC.

With the VC created and stored, now it's a matter of taking the same route in the opposite direction: the streamId is passed back from the lambda to the Chainlink node (#13) and back to the OpenCredentials smart contract with the right requestId (Chainlink handles that automatically).
Here's one of such transactions: https://rinkeby.etherscan.io/tx/0xa7c13f10ec64f9ba7607fe09f74b987c8984d35c1d82c191b9950942ad31e07a

😬 Fun fact showing how early this tech is: For a while Chainlink only permitted 32 bytes to be returned to a smart contract. As a result, any string you'd want to return from a Chainlink node was getting truncated, only keeping the first 32 characters. So I was actually stuck for a bit for that last step because a Ceramic streamId is more than 32 characters long... Fortunately Chainlink recently released a new version supporting "large responses" and unblocked me after I upgraded my node and Oracle smart contract.

From there, the OpenCredentials contract is able to retrieve the token recipient's address and the credential / lesson id from the requestId, it then calls the NFT contract to mint a token with recipient, lessonId and streamId.

The NFT contract is a ERC721 contract with some modifications:

  • The NFTs are non-transferrable. They can be minted by authorized addresses and burnt by their owner or authorized parties, but they cannot be transferred. If they were transferrable, someone would be able to acquire these tokens just by buying them and they would lose their significance as proof of completion of a lesson for all token recipients.
    Because it's a ERC721 contract the non-transferrability restriction of this contract is not straighforward at all at first glance. Plus, a lot of unused code for transfers and approvals ends up being deployed. For these reasons, I've been working on ERC1238 towards making a streamline token standard for non-transferable tokens a.k.a Badges.
  • Each token is uniquely identified by a unique tokenId - they're Non-Fungible Tokens after all - but they also belong to a group under a lessonId. From a tokenId you can retrieve the lessonId it is associated with.
  • Balances for a lesson are tracked. This lets someone see if an address received a credential for a specific lesson (querying by lessonId) and how many of them they own. This comes from the assumption that an address may own several tokens for the same lesson. For example if lessons were graded, someone might decide to obtain the crypto-credential of a lesson again but with a higher grade.

I created a demo video going through the whole flow and getting credentials as an end user:

All the code is available here:

Deployments on Rinkeby:
OpenClasses: 0xb25873E1fd210EF76D4528F04d1142434efbA8bc
VCNFT: 0x0D7f626141Ab3866533f98b4D4406b23e8bE7608
OpenCredentials: 0x27187729F39de1bEB68e9Aa4E3D52240DD409730

Usual disclaimer: it's been developed for demonstration purposes only, has not been tested nor audited and must contain bugs, do not use in production!

If you're interested in the topic of decentralized identity and crypto-credentials, don't hesitate to reach out to me on Twitter!