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

Sure, it’s the first evaluation criteria as it should be for a crypto heavy application :slight_smile:

1 Like

@domwoe - I notice that part of the acceptance criteria is that there’s a demo app deployed to the IC. This is probably a stupid question and I’m probably completely missing something here, but isn’t the vetKD api still to be implemented on the IC? Which means, the demo wouldn’t be functional?

You can deploy the canister vetkd_system_api and it should work, but yeah I mean it should not be used in prod applications until mainet launch of vetkd_system_api. However, it is good for demo.


This definitely works :slight_smile: I’ve tried it.

1 Like

But it should be fine to submit a little bit early, right?

Submission category

Identity Based Encryption - we’re gonna encrypt user’s digital documents using an encryption key derived from user’s Metamask address.


Kwic - digital document based proof of personhood system. More info in the repo and demo video.

Live on the IC:

Please, make sure you only access this website with your desktop. The mobile version is not ready.
Also, make sure you have Metamask browser extension installed (maybe other web3 providers would also work, but idk). Let me know if you have any troubles following the scenario from the pitch video (attached below).

Github repo:

The repository contains some short clues on how to implement each feature from this demo, but let me know if you need a more detailed overview.

Demo video

VetKeys feedback

I believe this is truly the most powerfull feature you’ve released this year. It will enable a lot of crazy stuff in the future. The API is simple and easy to use, but requires more documentation of what parameter does what exactly.

This is my first use-case bounty, so I don’t know what is the expected quality of the project - should it just demonstrate a single working scenario (how I did) or should it be a ready-to-use MVP. Let me know, if the project should be improved to be accepted as a submission to this bounty.


:memo: Introducing FormThing: End to End Encrypted Forms on the Blockchain

A proof of concept enabling you to create beautiful, fully accessible forms and control your own data.

This could be entered in to either of the IBE or Group Sharing categories - I’ll let the judges decide where it best fits as I’m not quite sure!

:wave: Introduction

FormThing was created for Web3 companies to “do forms” the Web3 way:

  • Login via Web3 wallet (Internet Identity)
  • Create and manage forms as needed
    • Manage user access to forms and submissions
    • Turn submissions on or off (active / inactive form)
  • Share public URL to created forms for submissions
  • Store your data on the blockchain instead of trusting it to the big boys (Google Forms, Typeform etc). Form submissions:
    • Are End-to-end encrypted
    • Only readable by who you choose to give access
    • Cannot be read by the blockchain nor the smart contract
    • Protected from spam by a simple nonce system
    • Exportable to CSV to do with as you please

Full information about how it all works can be found in the Github repo linked below.

:link: Helpful Links

:key: vetKey system API

This was easy to use and addresses the needs for this use case. I was initially concerned after looking at the demos and playing with using symmetric_key as the derivation_path, until ya’ll pointed me in the right direction and updated the demos and vetkd_utils to include ibe_encryption. Once I’d wrapped my head around that, it was extremely simple to then gate-keep who gets access to derive a given key.

:speech_balloon: Feedback

Please reach out to me if you have any feedback - I’d love to hear it!

Edit 1: Update video link
Edit 2: Add in vetKey system API feedback


Rabbit Hole

Rabbit Hole is an encrypted file service that runs fully on-chain and allows you to keep your files safe and secure. All files that you throw into the rabbit hole will be encrypted and placed in the storages of the decentralized Internet Computer. The data is encrypted with your private key, which only you have, which excludes any unauthorized access. Now you are the full owner of your data.

If possible, I would like to apply in the following categories:

IBE - the user stores files in encrypted form. The user’s principal is used as the derivation_id, the derivation_path contains the file identifier.
Group Sharing - the user can share files to any other users by selecting them from the list. It is also possible to make the file public via a link.
Timelock - when sharing a file, the user can optionally specify a date until which the encrypted key will not leave the canister.
For fairness, I note that this is still IBE. The restriction is based on the current time on the node and the field in the metadata of the shared file (link on the post). Perhaps a timeslot-based implementation would be better suited to participate in this category, so that the derivation_id is formed from the value of the timeslot.

:link: Links

:eyes: What’s next?

Collaboration of several users in one log is the next goal of the application roadmap. The user can create a journal and add other users to it and assign different rights for each user (read/write/etc.).

I’ve marked completed and upcoming tasks in the repository in the readme file to better reflect the current development status.

:key: vetKey API

I’ve been waiting for this feature since the discussion about it started. Only 2 methods for a developer, but so much magic and enormously important work inside. From impatience, I tried to start using it back in early July, and then trying to figure out the difference between key_id, derivation_id, derivation_path almost drove me crazy. The appearance of examples in the official repository has greatly improved the situation. This is an incredibly powerful feature, the team provides unique opportunities for developers. I hope as many devs as possible hear about it and join us. In the future, I would like to get a library for Motoko, with which it will be possible to work with cryptography.

:warning: Disclaimer:

The application is under active development and uses the vetKeys feature, which is not yet implemented on the IC system interface. I do not recommend using the app to store sensitive data until further notice. I reserve the right to update user’s canisters in any mode (upgrade or reinstall). Use this application only to test the capabilities of the Internet Computer.

If you have ideas, wishes, feedback (or no invitation code :eyes:) - I will be glad to receive a message from you.


Super cool :sunglasses: Might need to collab with Rabbit Hole to get encrypted file uploads for FormThing going! Could it work for other canisters to interact with Rabbit Hole?


I checked carefully and I’m pretty sure I’m using the same role value. Here’s the code I’m using:


// Function to update encrypted content for the user's role
document.getElementById("updateContent").addEventListener("submit", async (e) => {
  const roleInput = document.getElementById("role_input4").value;
  const fetched_symmetric_key = await get_aes_256_gcm_key(roleInput);
  const ciphertext = await aes_gcm_encrypt(document.getElementById("content_input").value, fetched_symmetric_key);
  await actor.addContent(ciphertext, JSON.parse(roleInput));

async function get_aes_256_gcm_key(roleInput) {
  const seed = window.crypto.getRandomValues(new Uint8Array(32));
  const tsk = new vetkd.TransportSecretKey(seed);
  const ek_bytes_hex = await actor.encrypted_symmetric_key_for_caller(tsk.public_key(), JSON.parse(roleInput));
  const pk_bytes_hex = await actor.symmetric_key_verification_key(JSON.parse(roleInput));
  return tsk.decrypt_and_hash(
    new TextEncoder().encode("aes-256-gcm")

async function aes_gcm_encrypt(message, rawKey) {
  const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96-bits; unique per message
  const aes_key = await window.crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["encrypt"]);
  const message_encoded = new TextEncoder().encode(message);
  const ciphertext_buffer = await window.crypto.subtle.encrypt(
    { name: "AES-GCM", iv: iv },
  const ciphertext = new Uint8Array(ciphertext_buffer);
  var iv_and_ciphertext = new Uint8Array(iv.length + ciphertext.length);
  iv_and_ciphertext.set(iv, 0);
  iv_and_ciphertext.set(ciphertext, iv.length);
  return hex_encode(iv_and_ciphertext);

  public shared({ caller }) func symmetric_key_verification_key(role: Role): async Text {
    let { public_key } = await vetkd_system_api.vetkd_public_key({
      canister_id = null;
      derivation_path = Array.make(Text.encodeUtf8(toText(role)));
      key_id = { curve = #bls12_381; name = "test_key_1" };

  public shared ({ caller }) func encrypted_symmetric_key_for_caller(encryption_public_key : Blob, role: Role) : async Text {
    let caller_blob = Principal.toBlob(caller);
    let { encrypted_key } = await vetkd_system_api.vetkd_encrypted_key({
      derivation_id = Principal.toBlob(caller);
      public_key_derivation_path = Array.make(Text.encodeUtf8(toText(role)));
      key_id = { curve = #bls12_381; name = "test_key_1" };

Update - Solved it! Within the “login” function, I changed
app_backend_principal = await Actor.agentOf(actor).getPrincipal();
app_backend_principal = identity.getPrincipal();.

I noticed this variable was holding the same value and length (a one-element array) as the anonymous identity even after logging in with II. This change fixed it and the error stopped occurring.

Unfortunately, this is not supported in this version, but I thought about this possibility and would like to move some logic to separate client libraries. Thank you!


So far this bounty BNT-8 - vetKeys - Enabling Privacy Preserving Applications on the IC is proving to be a great generator of interesting and useful examples of IC mini-dapps. I think all the submitting developers should win something and I know everyone and the IC are winners with this one!


Browser-based AI Chatbot Served From The IC - Now With State-of-the-Art Privacy

DeVinci is the browser-based AI chatbot app served from the Internet Computer. You can chat with the AI model loaded into your browser so your chats remain fully on your device. If you choose to log in, you can also store your chats on the Internet Computer and reload them later.

On vetKeys

DeVinci uses vetKeys to encrypt all user chats before they are being stored. The logged in user’s Principal is being used for this. When the user wants to reload or preview a chat, the messages are first being decrypted. Thus, vetKeys enables DeVinci to store fully private chats and to offer a private, decentralized AI chat app.

Try DeVinci with the vetKeys encryption enabled here

Note, this isn’t the DeVinci “production” app though (see below).


I’ve been excited about vetKeys as I haven’t seen anything like it; privacy with great UX and something that (to the best of my knowledge) neither other blockchains or, even more importantly, traditional cloud infrastructure can provide. In general, I could see this feature enabling the IC to offer “privacy-as-a-service” to all kinds of Web2 and Web3 apps.

Integrating the vetKeys API: The encrypted notes example repo was great and it made it pretty easy to integrate the needed code into mine. The only real blocker I had was getting the wasm from ic-vetkd-utils to work with vite (which I had already been working with) but once I started using vite-plugin-wasm-pack, it worked right away. I hadn’t paid attention initially that there isn’t any vetkd_system_api canister deployed yet on the IC but once understood/properly read, deploying that was also pretty swift and worked without any debugging. So the integration work was straightforward and working with this new feature was really pleasant, especially as vetKeys were running perfectly right from the start once integrated and the API only had few functions that needed to be used.

Speed of initializing encryption service: I brought the time down to ca 10 sec (by making the calls from the frontend in parallel) but it would be great if this could be decreased further. Do you think this could be sped up somehow? After vetKeys are released to production, do you foresee this initiation time to change?

Combining vetKeys with local storage: as it came up in this thread and I find it interesting; to which extent would you recommend approaches that cache the keys (e.g. to not have to initialize them again)? Which optimizations might work well with vetKeys?

Recommendations and best practices: I think it’d be great if you could release a primer on a few general security aspects around vetKeys. E.g. Are there any additional risks/disadvantages for the users and could they be mitigated? Are there any best practices or guidelines devs should follow specific to vetKeys?

I hope that vetKeys will be released soon such that I can release my integration to production as well. Thank you.


IBE & Open / Blue skies


Please find the Readme here



DeVinci is running live on the Internet Computer. The “production” stage doesn’t have the vetKeys changes integrated yet though - that’s only on the showcase stage (as noted above). You can give the “production” DeVinci a try here: For the showcase stage with vetKeys integration, please see here:

Please let me know if you have any feedback, thanks :slight_smile:


Nostric - a simple Nostr client running on the ICP

Hello, I am submitting our project for the vetKeys bounty. We developed a very simple Nostr client running on the ICP and implemented vetKeys so we can securely store private keys in the backend of the application. This is great from security perspective as well as user experience for Nostr users as they do not need to handle their private keys manually and use some chrome extensions to store them and sign each messages.

Nostric currently supports these features:

  • Generate PK, SK
  • Create a profile
  • Update profile
  • NIP-01 kind:1 Post publish
  • NIP-01 kind:0 User Metadata publish


Link to the GitHub repo here.
Link to the presentation video here.
Link to the frontend canister running in production here.


We are submitting to the IBE category.


Overall developer experience: We struggled at the beginning trying to run sveltekit project with all the dependencies and current version of DFX and js-agents. Would be cool if there is someone responsible for keeping these templates up to date as it would make life easier for new developers in the ecosystem.

vetkey API feedback: It was fairly easy to include vetkd_system_api canister in our project and start using it so the experience was great in this regard. It would be cool if there is some documentation saying what the text parameters in the key_id or derivation path mean.


If you have any feedback or questions regarding to this project, we will be happy to talk!


medpx - Keep your electronic medical prescriptions safe, under your control

This is my entry. It stores medical prescriptions in electronic form, on the blockchain, using cryptography to ensure nobody can access them – unless the owner (patient) allows that.

It depends on VetKeys to encrypt/decrypt patients medical prescriptions. Only the doctor (creator), the patient and users (third-parties) allowed by the patient can access and decrypt the prescriptions. Doctors must use their smart cards (containing X.509 certificates) to signup and also to sign prescriptions.

The app was made specifically for this bounty.

Only in my country, Brazil, more than 28 million electronic prescriptions are created per month using a single, closed-source, proprietary app, that allows anyone to access a prescription if they use a simple 6-digits hex code or if they scan a QR-code, that gives the patients no control over their data.



It falls under multiple categories:

  • IBE: doctor creates a encrypted prescription that can be shared by the patient with third-parties like drugstores, hospitals etc
  • Group sharing: patient can create groups of users and share the prescriptions with those groups, anyone part of the group can access and decrypt the data
  • Timelock: patients can define a date limit when access to the prescriptions will end


The VetKeys API, as proposed, was really easy to use, as it follows the ECDSA API. The examples were simple to follow. The hardest part was getting the utils JS library to work with NPM + webpack, but @nathanosdev posts on this thread helped a lot.

There’s a chicken-egg problem I faced and that I see happening with most entries in this bounty: nobody is checking for user permission when vetkd_encrypted_key() is called. That can be a major cause of cycle draining DDoS attacks, as that API method won’t be cheap to invoke. To prevent that, I had to create prescriptions in two stages: first the prescription is created in a pre-stage by a caller (a doctor). Then the same caller will ask for the encrypted key for that prescription. The key will only be generated and returned if there’s permission to access that prescription. Then, after encrypting (using the key returned by VetKeys) and signing (using a X.509 certificate) the prescription at client side, the 2nd stage canister method will be called to complete creating.


Capsule - A Time Capsule / Deadman Switch for Files

Deadman Switch Feature: Life is unpredictable, and there’s wisdom in being prepared. Our Deadman Switch activates when you are unable to check-in within a pre-determined time frame, making pre-set files public. Whether it’s a heartfelt letter, encrypted passwords, or critical documents, rest assured that your voice will be heard even if you can’t be there to speak.

Time Capsule Feature: Embrace the magic of time travel with our digital Time Capsule. Save photographs, wills, videos, or any digital memento and set a date for its future unveiling. It’s like burying a treasure for your future self or loved ones to find, allowing you to communicate across time and remind them of special moments, life lessons, or just how much you care.


Timelock Encryption






(video is not great since I had to edit to keep it at 4min)


Establishing a clean code client connection using ic-vetkd-utils presented its own set of challenges but with feedback from forum it was solved. @nathanosdev posts helped. While the API is user-friendly, a more detailed insight into the inner workings of each field would have been appreciated. I believe this detailed information should be readily available within the codebase, as learning through practical examples resonates with me the most.

Up until now, it has catered to my requirements effectively. Segmenting the encryption and decryption processes posed some difficulty, especially since the provided example catered only to text. However, overall, its performance has been commendable.


Introducing B3Note: Revolutionizing Note-Sharing through Witness-Like Encryption on the Internet Computer


I’m thrilled to introduce B3Note, an innovative application that capitalizes on the Internet Computer’s capabilities. B3Note elevates the standard for secure and anonymous note-sharing. It not only enables users to write and share notes anonymously without the need for a login but also incorporates a unique form of encryption similar to Witness Encryption. This adds another layer of security and functionality to the platform.

Additionally, I’ve developed B3Utils, a Rust crate that serves as a comprehensive helper, streamlining the work with stable memory, timers, logging, and more on the Internet Computer. This crate includes powerful and unique features, such as the amazing vetkd capabilities. B3Utils is open-source and available for anyone to use, making it easier to implement functionalities similar to those found in B3Note. It demonstrates capabilities that other blockchains can only dream of.

Use Cases

Open/Blue Skies with a Focus on Witness-Like Encryption

One of B3Note’s standout features is its encryption mechanism, which operates in a manner similar to Witness Encryption. This approach enables secure multi-party computation, anonymous credentials, and intricate access control systems. Furthermore, it allows us to verify anonymous users directly on-chain, marking a significant leap forward in decentralized technologies.

IBE (Identity-Based Encryption) with Generated Public Keys

B3Note employs IBE for secure note-sharing based on generated public keys. This eliminates the need for traditional identifiers like email addresses or usernames and removes the complexities associated with pre-shared keys.

Timelock Encryption

B3Note also incorporates the concept of Timelock Encryption to provide an added layer of security and functionality. Notes created by anonymous users expire after 1 hour, and shared links for those notes are also only available for the same time frame. This aligns well with the notion of Timelock Encryption, where encrypted data can only be accessed for a specific period.

Key Features

  • Anonymous Usage: B3Note can be used anonymously, with no login required.
    Limited Notes for Anonymous Users : Anonymous users can create up to 5 notes, each with a 1-hour timeout, after which the notes are automatically deleted. Additionally, any generated share link will also expire after 1 hour.
  • Canister Global Timer: A global timer is used to clean up expired users and one-time keys, enhancing system efficiency and security.
  • Secure Sharing with On-Chain Verification: Notes can be securely shared through a simple link that contains the signature of your public key. This signature is verifiable on the blockchain and pairs with the note’s unique ID to confirm access to the decryption key.
  • Auto Deletion: Notes are automatically deleted after the first read or if they remain unread for an hour.
  • Advanced Encryption: A witness-like encryption mechanism ensures that only authorized users can access the notes.
  • Timelock Encryption: Encrypted Decryption Key, Notes, and shared links come with a built-in expiry feature, aligning with the concept of Timelock Encryption for temporary secure access.

Live Demo and Source Code

I invite everyone to experience B3Note and delve into its features. Your feedback would be invaluable as we continually develop and enhance this project.

Feedback on the vetKD System API:

I found the vetKD system API to be quite flexible and powerful for most use cases. However, for my specific application, B3Note + B3Wallet, I needed the ability to sign messages using the transport secret key for on-chain verification. To address this, I extended the ic-vetkd-utils library to include a sign function.

This function hashes an input message to a curve point in G2, multiplies it by the secret key, and returns the serialized point. This allows us to sign unique note identifiers with private keys, which can then be verified on-chain using pairing functions. Here’s a snippet of the function:

pub fn sign(&self, input: &[u8]) -> Result<Vec<u8>, String> {
    let hashed_input = augmented_hash_to_g2(&G1Affine::generator(), input);
    let signed_input = hashed_input.mul(self.secret_key);
    let signed_input_bytes = signed_input.to_affine().to_compressed();

This addition has significantly enhanced the security and verification aspects of B3Note and possibly B3Wallet.

:tada: Special B3Note Challenge: Win a Gift! :tada:

To celebrate the launch of B3Note, I’m hosting a fun game. I’ve created a secure, encrypted note using B3Note’s anonymous note-sharing feature. The first person to click the link and decrypt the note will find instructions on how to claim a special gift of 5 ICP tokens!

Ready? Click here to decrypt the note and claim your gift!

Remember, the link will expire after 1 hour or as soon as someone claims the prize, so act fast!


Can anyone help me solve this bug?

I’m working on an app that will allow content to be encrypted for members of a gaming community with different content for different user levels. The top levels are owners and admins. The first time the app is opened in a browser, the canister owner and a specified principal (the anonymous principal for the local/test version) are appointed as the first two “owners”. A master key is created and then encrypted for each user who is added to the platform. This is then decrypted in order to add or read content and for encrypting the master key whenever owners or admins add a new user. Encryption works fine, but when I try to decrypt the key I get an Uncaught (in promise) Error.

Here are the relevant code sections:

// Function to appoint Owners when the canister is run for the first time
const init_status = await actor.checkInitStatus();
console.log("init_status =", init_status);
if (init_status == false) { //const startup = async () => {
  const randvals = window.crypto.getRandomValues(new Uint8Array(32));
  const randtext = new TextDecoder("utf-8").decode(randvals);
  let msk = await get_aes_256_gcm_key_from_text(randtext, "master_key");
  let temp_key = await get_aes_256_gcm_key_for_canister_owner();
  let encrypted_msk = await aes_gcm_encrypt(new TextDecoder("utf-8").decode(msk), temp_key);
  const intialised = await actor.initialise(encrypted_msk);
  console.log("intialised =", intialised);
  // For local deployment and testing
  temp_key = await get_aes_256_gcm_key_from_text("2vxsx-fae", "Owner");
  encrypted_msk = await aes_gcm_encrypt(new TextDecoder("utf-8").decode(msk), temp_key);
  let owner2_added = await actor.addSecondOwner("2vxsx-fae", encrypted_msk);
  // For deployment to IC (comment 3 lines above, uncomment next 3 lines and replace with your own principal ID)
  // temp_key = await get_aes_256_gcm_key_from_text("mypri-ncipa-lid", "Owner");
  // encrypted_msk = await aes_gcm_encrypt(new TextDecoder("utf-8").decode(msk), temp_key);
  // let owner2_added = await actor.addSecondOwner("mypri-ncipa-lid", encrypted_msk);
  console.log("owner2_added =", owner2_added);
  msk = null;
  encrypted_msk = null;
// Function to appoint new Admin (Owner-only function)
document.getElementById("appointAdmin").addEventListener("submit", async (e) => {
  const userId = document.getElementById("user_id5").value;
  const callerRole = await actor.getUserRole(app_backend_principal.toText());
  const decrypt_key = await get_aes_256_gcm_key(JSON.stringify(callerRole[0]));
  const caller_enc_msk = await actor.getEncryptedMSK(app_backend_principal.toText());
  const _msk = await aes_gcm_decrypt(caller_enc_msk, decrypt_key);    // CODE STOPS RUNNING HERE
  const encrypt_key = await get_aes_256_gcm_key_from_text(userId, '{"Admin":null}');
  const _encrypted_msk = await aes_gcm_encrypt(_msk, encrypt_key);
  await actor.appointAdmin(userId, _encrypted_msk);
  // Get and display the updated role for the user
  const updatedRole = await actor.getUserRole(userId);
  document.getElementById("aa_result").innerText = "Role: " + JSON.stringify(updatedRole);
});    // THIS IS LINE 122
async function aes_gcm_decrypt(ciphertext_hex, rawKey) {
  const iv_and_ciphertext = hex_decode(ciphertext_hex);
  const iv = iv_and_ciphertext.subarray(0, 12); // 96-bits; unique per message
  console.log("iv =", iv);
  const ciphertext = iv_and_ciphertext.subarray(12);
  console.log("ciphertext =", ciphertext);
  const aes_key = await window.crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["decrypt"]);
  console.log("aes_key =", aes_key);
  console.log(typeof aes_key);                         // CODE STOPS RUNNING HERE
  let decrypted = await window.crypto.subtle.decrypt(
    { name: "AES-GCM", iv: iv },
  console.log("decrypted =", decrypted);
  return new TextDecoder().decode(decrypted);

From the console output it can be seen where the code stops running:

No further error details are given in the output. Line 122 is at the end of the appointAdmin function as shown above. When await is removed from
let decrypted = await window.crypto.subtle.decrypt(
the decrypted variable is logged like so:


There’s a Motoko backend but I can’t find any issue there. (I can provide it if anyone wants to see it.) I’ve checked through everything carefully. The iv values match between the encryption and decryption stages. This all worked fine when I tried it in simpler examples without the full functionality. I’m at a loss as to what’s going wrong here.

Hi @domwoe A 1 week extension would be a great benefit for our team as well. As the feature is new and we have encountered many bugs hindering the process of developement. Thanks

Congrats to @Berg for being the first to access the note.

Thanks to all the participants.

Hi @kamalbuilds, the deadline has already been extended by more than a week. The original deadline was Augst 29.