This demo application allows you to send encrypted files to any Ethereum address. It uses Identity-based encryption (IBE) together with the vetKeys (Verifiably Encrypted Threshold Key Derivation) Internet Computer feature.
TL;DR vetKeys on the Internet Computer allow developers to more easily perform encryption, threshold decryption, and signing. vetKeys allows a public blockchain to hold secrets.
Note
The vetKeys feature will be available Q2 2025. This demo uses the chainkey_testing_canister to simulate vetKeys.
Live demo: https://h62xu-2iaaa-aaaal-qshoq-cai.icp0.io
- Sending a file
- Receiving a file
- Run locally
- Run frontend in development mode
- Deploy to the Internet Computer
- License
- Contributing
The user signs in using their Ethereum address with the help of the ic-siwe provider canister and its support library, ic-siwe-js.
The sign in process securely establishes a link between the user's Ethereum address and their Internet Identity.
The user can create a transfer by specifying the recipient's Ethereum address and the file to send.
When Send file
is clicked, this is what happens:
The frontend calls the vetkd_public_key
method on the chainkey_testing_canister
to get the recipient's public key. The recipient's Ethereum address is used as the identity for the IBE scheme that generates the public key.
#[update]
async fn vetkd_public_key(address: String) -> Result<Vec<u8>, String> {
let address = Address::parse_checksummed(address, None).map_err(|e| e.to_string())?;
let args = VetkdPublicKeyArgs {
key_id: VetkdPublicKeyArgsKeyId {
name: "insecure_test_key_1".to_string(),
curve: VetkdCurve::Bls12381,
},
derivation_path: vec![ByteBuf::from(*address.0)],
canister_id: None,
};
let (result,) = chainkey_testing_canister
.vetkd_public_key(args)
.await
.unwrap();
Ok(result.public_key.to_vec())
}
After the vetKeys feature is available, the testing canister call would be replaced with the actual vetKeys call.
The frontend encrypts the file using the recipient's public key. The file is encrypted using the vetkd.IBECiphertext.encrypt
method from the ic-vetkd-utils
support package. This package will be available in a new version when the vetKeys feature is available.
/// ...
const response = await backend.vetkd_public_key(recipientAddress);
if ("Err" in response) {
console.error("Error getting recipient public key", response.Err);
return;
}
const recipientPublicKey = response.Ok as Uint8Array;
const seed = window.crypto.getRandomValues(new Uint8Array(32));
const fileBuffer = await file.arrayBuffer();
const encodedMessage = new Uint8Array(fileBuffer);
const encryptedFile = vetkd.IBECiphertext.encrypt(
recipientPublicKey,
new Uint8Array(0),
encodedMessage,
seed
);
const request = {
to: recipientAddress,
content_type: file.type,
filename: file.name,
data: encryptedFile.serialize(),
};
return backend.transfer_create(request);
The backend canister verifies the from
and to
parameters and stores the encrypted file.
#[update]
pub async fn transfer_create(args: TransferCreateRequest) -> Result<Transfer, String> {
let principal_blob = principal_to_blob(ic_cdk::caller());
let from = UserManager::get(principal_blob).ok_or("User not found".to_string())?;
let to = Address::parse_checksummed(args.to, None).map_err(|e| e.to_string())?;
let transfer = TransferManager::create(TransferManagerCreateArgs {
from,
to,
filename: args.filename,
content_type: args.content_type,
data: args.data,
})?;
Ok(transfer)
}
A receiveing user logs in with their Ethereum address same as the sender. The frontend calls the transfer_list
method on the backend to get a list of transfers. The user can then download the encrypted file and decrypt it using their private key.
vetKeys on the Internet Computer allow developers to more easily perform encryption, threshold decryption, and signing when building dapps on ICP. It is powered by a protocol called vetKD (Verifiably Encrypted Threshold Key Derivation) that allows to derive decryption keys on demand.
One of the challenges when doing encryption in an open network like the Internet Computer is how to securely store and retrieve the private key. vetKeys solves this problem by allowing the user to derive the private key on demand. To securely transfer the private key to the requesting user, a transport secret key is used to encrypt the private key while in transfer.
// ...
const seed = window.crypto.getRandomValues(new Uint8Array(32));
const transportSecretKey = new vetkd.TransportSecretKey(seed);
const response = await backend?.vetkd_encrypted_key(
transportSecretKey.public_key(),
);
Similar to when getting the public key, the backend uses the Ethereum address of the caller as the key derivation path. Also note how the transport secret key is passed to vetkd_encrypted_key
function to encrypt the private key before returning it to the frontend.
#[update]
async fn vetkd_encrypted_key(encryption_public_key: Vec<u8>) -> Result<Vec<u8>, String> {
let address = get_caller_address().await?;
let args = VetkdEncryptedKeyArgs {
key_id: VetkdEncryptedKeyArgsKeyId {
name: "insecure_test_key_1".to_string(),
curve: VetkdCurve::Bls12381,
},
public_key_derivation_path: vec![ByteBuf::from(*address.0)],
derivation_id: ByteBuf::new(),
encryption_public_key: ByteBuf::from(encryption_public_key),
};
let (result,) = chainkey_testing_canister
.vetkd_encrypted_key(args)
.await
.unwrap();
Ok(result.encrypted_key.to_vec())
}
Once in posession of the encrypted private key, the frontend can first decrypt the private key using the transport secret key and then use the private key to decrypt the file.
// ...
const key = transportSecretKey.decrypt(
encryptedKey,
publicKey!,
new Uint8Array()
);
const ibeCiphertext = vetkd.IBECiphertext.deserialize(
transfer.data as Uint8Array
);
const decryptedData = ibeCiphertext.decrypt(key);
return { decryptedData, ...transfer };
Pre-requisites:
Once you have the prerequisites installed, you can clone this repository and run the project.
dfx start --clean --background
pnpm i
bash scripts/deploy.sh
pnpm run dev
bash scripts/deploy-ic.sh
This project is licensed under the MIT License. See the LICENSE file for more details.
Contributions are welcome! Please open an issue or submit a pull request if you have any suggestions or improvements.