Closed: BNT-8 - vetKeys - Enabling Privacy Preserving Applications on the IC

Hey @timk11,

we’ll definitely evaluate your submission.


Hey @hawk,

can you provide a video pitch/demo and a demo application deployed to the IC?

Hey @domwoe,
Thanks for the reminder. I will decline this bounty as UI won’t be ready in time, but will start preparing to incorporate vetKeys in the main product :slight_smile:


An app for encrypting and sharing role-based content within a gaming community.

GuildNotes enables gaming communities to share content with players. Different content is able to be accessed by different levels of players. Users within the app are ranked as Owner, Admin, Conqueror, Explorer or Player. The app features two frontends - an Admin Dashboard for Owners and Admins to manage content and access, and an End User Interface for all levels of players to access role-specific content.

Category - Group sharing

Repository - GitHub - timk11/guildnotes: An app for encrypting and sharing role-based content within a gaming community, based on the Internet Computer as a demonstration of the vetKeys feature
Video demo -
IC deployed version: (updated links)
  • Admin dashboard -
  • End user interface -

The vetKeys feature is great and was easy to set up using the provided demo versions. Modifying it to perform a different function of this sort was a major undertaking, and one I would personally put into the “very hard” category. Part of this is me, given I only came to the IC earlier this year with little knowledge of web design. It’s been a huge but fascinating learning curve!
I based my design on the ic/examples/motoko/vetkd version. I tried a few different approaches to get it deployable and working. Lots of debugging along the way. In the final stretch a few specific features needed changing:

  • AES-GCM worked with the aes_gcm_encrypt function but not with aes_gcm_decrypt. I ended up switching to AES-CTR, which I know is less secure but is now working well.
  • TextEncoder("utf-8").encode() did not work for encrypting and then decrypting a key. The key is 32 bytes. When returned as a string the decrypted key looked the same as the equivalent original version, but when transformed into a BufferArray the array length came out as some longer odd number, usually the same (incorrect) result each time it was tried. My eventual solution was to swap out TextEncoder/Decoder and replace them with hex_encode and hex_decode respectively.
  • hex_encode from the example version consistently gave an error message (bytes.reduce is not a function). An alternative solution I found on Stack Overflow is in the repo.

Overall a very exciting feature. Having to do so much debugging was a good chance to get my head around a lot of the finer details and I look forward to seeing how this all shapes up, and contributing further if there are opportunities.


Received! Can you also share the link to the demo application deployed to the IC?

I’m having trouble deploying it at the moment. I keep getting tcp connect error: Network is unreachable (os error 101) or it just stays stuck on Creating canister.... Could it be a cycles issue? I thought I sent ICP to my wallet but the cycles balance hasn’t changed.

Can you please create a new forum thread on the issue?

GuildNotes is now deployed. I’ve put the URLs in my previous message above.

I have a new problem in that the page displays correctly but does not function, giving these errors:  
Refused to execute script from 
because its MIME type ('text/html') is not executable, 
and strict MIME type checking is enabled.
index.js:2  Uncaught (in promise) TypeError: 
Failed to execute 'compile' on 'WebAssembly': 
Incorrect response MIME type. Expected 'application/wasm'.

I’ve started a new thread for this topic at How to solve "MIME type" errors in deployed canisters?. Any advice would be gratefully received.

GuildNotes has been re-deployed.

The canister URLs have changed. The new ones are:
• Admin Dashboard -
• End User Interface -

Still having a couple of issues. Internet Identity login is working for the End User Interface but not for the Admin Dashboard. However, I’ve registered the anonymous identity as an Owner, so you’ll have this role when you first open the page and be able to add and read content, assign roles, etc.

GuildNotes is now fully working!

The canister URLs have changed (again). The new ones are:
• Admin Dashboard -
• End User Interface -

I’ve registered the anonymous identity as an Owner, so you’ll have this role when you first open the page and be able to add and read content, assign roles, etc.

Sorry for being a little bit off-topic.
I’m trying to put my finger on how vetkeys work more in-depth.
So far it seems like I understand (at least at high level) all the steps, except for the ElGamal one.

(this article)

I didn’t read any of the sources provided in the article - too much in-depth for me :slight_smile:

This is how I understand the protocol:

  1. The user wants to derive some secure entropy from their identity, so they make a call to a canister, providing their public encryption key (transfer key).
  2. The canister sees this request, decides that the user is allowed to do that and passes the transfer key with the user’s identity to a system call.
  3. This system call makes all the nodes to simultaneously grab their BLS private key share and use it to sign the user’s identity, to obtain a BLS signature share.
  4. Each node then encrypts this BLS signature share using the transfer public key.
  5. Each node then somehow (probably through p2p) passes this encrypted signature share to a current consensus leader.
  6. The leader combines all encrypted signature shares into an encrypted signature and puts it in a block as a system call response.
  7. Once the block is in execution, the canister receives the encrypted signature and transfers it back to the user.
  8. The user (and only they) is able to decrypt the signature, using their private transport key.
  9. The user is also able to verify that the signature was produced by the subnet to which they were originally requesting, and not someone else, since this is just a signature and we have a public key of the subnet.
  10. The user reinterprets this signature as entropy and uses it as they wish.

The problem I have is in step 6.
Each node produces a signature share that is meant to be combined with BLS scheme. Then each node encrypts its signature share, so each node now has a piece of ElGamal cyphertext, which should no longer have the same qualities. I mean, these cyphertexts are no longer valid BLS signatures, so they can’t be combined. But the round leader combines them somehow and moreover, the user decrypts this combined cyphertext and somehow gets the BLS-combined signature out of it.

Can someone please explain what I got wrong and how it works in reality. Thanks a lot!

1 Like

Great question! The main trick here is that the (ElGamal) encryption scheme used is additively homomorphic, which is just a way to say that you can add ciphertexts together to obtain an encryption of the sum of the messages.

The messages encrypted by each node are shares of a BLS signature S_i. When they are on cleartext, you can reconstruct the full signature by computing a linear function of the shares, something like:
S:= a_1*S_1+a_2*S_2+.. where the S_i are shares of the BLS signatures (i.e. they are group elements on the EC BLS12_381) and the a_1 are scalars (i.e. integers modulo the order of the group). Since the encryption scheme allows to you add ciphertexts together, given the encrypted signature shares you can apply the linear function directly to the ciphertexts to get an encryption of the combined signature.

There is one more subtlety: the combiner cannot decrypt the messages inside the ciphertexts, so if one signature share is wrong, also the combined (encrypted) signature will be wrong. Fortunately, the scheme enables verification of encrypted signatures while they are still encrypted, so the combiner can verify the shares before combining them. In addition to the homomorphic property of the encryption scheme, this is possible thanks to the bilinearity of the pairing operation of the BLS curve. At an high level the pairing operation let you combine 2 group elements to form another group element: pairing(G1, G2)->G3. The pairing is also used to verify BLS signatures, via the following verification equation:
pairing(S,B)=pairing(M,PK), where S is the signature, B is fixed group element, M is the hash of the message and PK is the public key.

The following is not fully correct, it’s more to give an intuition of what is happening: If you encrypt both sides of the verification equation you obtain Enc(pairing(S,B))=Enc(pairing(M,PK)). Thanks to the various linear properties, the left-hand side is also equal to pairing(Enc(S), B), which essentially means that it can be computed given the encryption of the signature. The right-hand side can be compute from all public info (i.e. the message, the public key and the encryption key). This allows you to verify that the encrypted signature is actually correct. Again, this is a bit of a simplification, so don’t take it literally.

If you are interested in understanding the details, @ais is planning to do a followup article more focused on the scheme we are planning to integrate. This should be more easy to parse than the research paper!


Hi guys,

We first want to thank you so much for the submissions. We’re really impressed by the amount of thought and effort you put into them. We also had a lot of fun looking at them, trying to see if they’re secure or not :wink: and particularly watching the demo videos - they’re great!

As it was a ‘contest’ bounty, we had to choose some way to divvy up the prize pool. Now we have some view on this, we’d like to announce the winners at the next Public Global R&D meeting on Wednesday September 27th at 17:30 CET.
It will not be live streamed this time, so if you participated in the bounty and would like to join, ping me and I’ll send you a Zoom invite. For everyone else, you can catch the recording the next day. We’ll also make the announcement here in the forum.

I know you care about the bounty, but the most interesting part for us was seeing the parts that you all understand very clearly, and the parts that are misunderstood. We learned a lot. I’m collating all those points and will share them with you here, and with a followup to the primer article, and updated documentation. It’s not easy to get such feedback if people don’t properly engage. You all did, and we’re super grateful. Thanks already for your contributions to the privacy landscape :heart:


Please send me an invitation to a meeting, this is my favorite series :slightly_smiling_face:

P.S. I definitely love what I have to deal with when developing my application and programming canisters in general. A few years ago I lost interest in programming, perhaps it was due to the bull market that generously rewarded early holders, or perhaps the work itself was uninteresting and did not make me feel proud of what I was doing. I quit my job and devoted my free time to studying IC, damn it was difficult, but at the same time I became interested. Regardless of the market situation, opinions in chats, I just continue to build and enjoy the process! And all this thanks to the DFINITY team! Thank you for inspiring us and creating unique opportunities! :fire:


Thanks a lot!
Can’t say I did understand everything, but it is a lot clearer now!


I was trying to understand why this feature seems so exiting to me and realized that this is actually the first thing in Web3 ever that Web2 eagerly needs, but was unable to get all this time. Meaning that all the previous Web3 stuff, like tokens and NFTs was cool, but very questionable from the perspective of a typical Web2 company. Like “yeah, tokens seem fun, but we already have Stripe for payments, which works million times faster and is compatible with our jurisdiction”.

Web3 stack is typically chosen only for the use-cases with very specific requirements. Regular Web2 apps don’t need Web3 tech, because they are fine without it. And most of them, when given a choice “do you want to build your app on AWS or on blockchain”, would choose the first, especially for some performance-critical applications.

But this feature is different. Imagine yourself as a developer of some privacy-enabled app, like WhatsApp (this statement is controversal, but anyway). Like, you want people to have e2e encrypted chats, but in order to achieve that you need to solve a whole bunch of engineering complexities related to key management. And you have no other choice other than to store these keys on your own servers or on some third party servers, which is either hard or expensive.

With VETKeys (and II) this is actually easier and cheaper to spin up a simple canister with one little endpoint for key derriviation, than to handle key management by hand. It is easier to stick with Web3, than with Web2.


One can even integrate VETKeys in a regular client-server system. Even without Internet Identity or any other key management system. The server authorizes users with regular login/password and then operates as a proxy between clients and the VETKeys canister (so only the server has to manage the key pair to communicate with the IC). Even if the server is comprimised, no data will leak, since every generated entropy (from logs or caches) will be e2e encrypted with a client’s transport key.


Thank you for the opportunity! As I’m interested in releasing the vetKeys integration in my app to production, I wondered when vetKeys will be released. Is there a date for this already? And in case you found any security vulnerabilities while looking through the apps, will you be able to share these (with the respective projects) so we can fix them before release?

It is my understanding that vetKey’s ID is not only Internet Identity, but also web2 ID and maybe even the private key inside the social security card. It would be interesting to have an extension that allows E2EE messages with Google ID or Apple ID. The previous YouTube video explained that there is a sufficient number of signatures per second that can be done, but is it possible to scale the BLS signatures enough to withstand the number of processes for web2 enterprises, not web3?

Encrypted files, encrypted forms, encrypted notes, Nostr Clients and key management, deadman’s switches, private AI, medical privacy, key derivation and credentials, and privacy for our gamers… We didn’t imagine you’d give us so much!

Announcing the winners
First, thanks to everyone for the consideration and participation. Even if you didn’t manage to submit something this time, we’re really grateful that there are so many interesting ideas and applications floating around. Thanks a lot also for the continued discussion.

Accepted submissions
There were a list of acceptance criteria that projects needed to meet. These included using the proposed vetKeys system API, deploying an app to the IC, and recording a demo video, among other things. There were 9 submissions in total that satisfied the acceptance criteria. We list them below.

Project Name Description Participant
Kwic digital document based proof of personhood system. More info in the repo and demo video. senior.joinu
FormThing End to End Encrypted Forms on the Blockchain conorseed
Rabbit Hole an encrypted file service that runs fully on-chain rabbithole
Browser-based AI Chatbot Served From The IC Now With State-of-the-Art Privacy patnorris
Nostric a simple Nostr client running on the ICP lukevoz
medpx Keep your electronic medical prescriptions safe, under your control v1ctor
Capsule A Time Capsule / Deadman Switch for Files cyberowl
B3Note Revolutionizing Note-Sharing through Witness-Like Encryption on the Internet Computer b3hr4d
GuildNotes An app for encrypting and sharing role-based content within a gaming community. timk11

There were 4 categories that submissions fell into based on where they best contributed. Submissions were then assessed based on design/UX, functionality, and code quality. Ultimately, all accepted submissions get part of the bounty.
It was not an easy exercise to participate here and given the amount of effort that everyone put in, it was deemed that everybody’s a winner.
Plot twist: we didn’t restrict the submissions to one category, so if a project nailed the functionality in several categories, we allowed it. Below you’ll see the rankings of the submissions and the respective prize amounts in USD.

IBE Group Sharing Timelock Blue Skies
:1st_place_medal:FormThing ($4000) :1st_place_medal:medpx ($2500) :1st_place_medal:Capsule ($4000) :1st_place_medal:Kwic ($3000)
:2nd_place_medal:B3Note ($2000) :2nd_place_medal:FormThing ($2000) :2nd_place_medal:Rabbithole ($2000) :2nd_place_medal:Nostric ($2000)
:3rd_place_medal:Rabbithole ($1000) :3rd_place_medal:AI Chatbot ($1000)
:muscle:Guildnotes ($500)

Over the coming days we’ll give some feedback and comments that arose when we were reviewing the submissions. We also learned a lot, so we’ll give a summary of that and outline how the next steps will be influenced. @domwoe will reach out the the winners about the $$.

Congratulations to the winners and thanks once again everyone for the participation!