Skip to content

Commit d8bb5d4

Browse files
authored
chore: CRP-2823 modularize parts of the vetKD-related code in examples to simplify them (#135)
This makes it easier to use them in developer docs.
1 parent 9d51aa9 commit d8bb5d4

3 files changed

Lines changed: 112 additions & 68 deletions

File tree

examples/basic_ibe/frontend/src/main.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ async function getRootIbePublicKey(): Promise<DerivedPublicKey> {
5656
return ibePublicKey;
5757
}
5858

59+
async function encrypt(
60+
cleartext: Uint8Array,
61+
receiver: Principal,
62+
): Promise<Uint8Array> {
63+
const publicKey = await getRootIbePublicKey();
64+
const ciphertext = IbeCiphertext.encrypt(
65+
publicKey,
66+
IbeIdentity.fromPrincipal(receiver),
67+
cleartext,
68+
IbeSeed.random(),
69+
);
70+
return ciphertext.serialize();
71+
}
72+
5973
// Get the user's encrypted IBE key
6074
async function getMyIbePrivateKey(): Promise<VetKey> {
6175
if (ibePrivateKey) return ibePrivateKey;
@@ -78,6 +92,13 @@ async function getMyIbePrivateKey(): Promise<VetKey> {
7892
}
7993
}
8094

95+
async function decryptMessage(encryptedMessage: Uint8Array): Promise<string> {
96+
const ibeKey = await getMyIbePrivateKey();
97+
const ciphertext = IbeCiphertext.deserialize(encryptedMessage);
98+
const plaintext = ciphertext.decrypt(ibeKey);
99+
return new TextDecoder().decode(plaintext);
100+
}
101+
81102
// Send a message
82103
async function sendMessage() {
83104
const message = prompt("Enter your message:");
@@ -89,17 +110,13 @@ async function sendMessage() {
89110
const receiverPrincipal = Principal.fromText(receiver);
90111

91112
try {
92-
const publicKey = await getRootIbePublicKey();
93-
94-
const encryptedMessage = IbeCiphertext.encrypt(
95-
publicKey,
96-
IbeIdentity.fromPrincipal(receiverPrincipal),
113+
const encryptedMessage = await encrypt(
97114
new TextEncoder().encode(message),
98-
IbeSeed.random(),
115+
receiverPrincipal,
99116
);
100117

101118
const result = await getBasicIbeCanister().send_message({
102-
encrypted_message: encryptedMessage.serialize(),
119+
encrypted_message: encryptedMessage,
103120
receiver: receiverPrincipal,
104121
});
105122

@@ -118,13 +135,6 @@ async function showMessages() {
118135
await displayMessages(inbox);
119136
}
120137

121-
async function decryptMessage(encryptedMessage: Uint8Array): Promise<string> {
122-
const ibeKey = await getMyIbePrivateKey();
123-
const ciphertext = IbeCiphertext.deserialize(encryptedMessage);
124-
const plaintext = ciphertext.decrypt(ibeKey);
125-
return new TextDecoder().decode(plaintext);
126-
}
127-
128138
function createMessageElement(
129139
sender: Principal,
130140
timestamp: bigint,

examples/basic_timelock_ibe/backend/src/lib.rs

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,62 @@ async fn decrypt_bids(
299299
encrypted_bids: Vec<EncryptedBid>,
300300
root_ibe_public_key_bytes: Vec<u8>,
301301
) -> Vec<DecryptedBid> {
302+
let decrypted_values = decrypt_ciphertexts(
303+
lot_id.to_le_bytes().to_vec(),
304+
encrypted_bids
305+
.iter()
306+
.map(|bid| bid.encrypted_amount.as_slice())
307+
.collect::<Vec<_>>(),
308+
root_ibe_public_key_bytes,
309+
)
310+
.await;
311+
312+
let mut decrypted_bids = Vec::with_capacity(encrypted_bids.len());
313+
for decrypted_value in decrypted_values {
314+
let decrypted_bid: Result<u128, String> = decrypted_value
315+
.and_then(|v| {
316+
v.as_slice()
317+
.try_into()
318+
.map_err(|_| "failed to convert amount to u128".to_string())
319+
})
320+
.map(u128::from_le_bytes);
321+
decrypted_bids.push(decrypted_bid);
322+
}
323+
324+
encrypted_bids
325+
.into_iter()
326+
.zip(decrypted_bids.into_iter())
327+
.inspect(|(encrypted_bid, decrypted_bid)| {
328+
if let Err(e) = decrypted_bid {
329+
ic_cdk::println!(
330+
"Failed to decrypt bid for lot id {lot_id} by {}: {e}",
331+
encrypted_bid.bidder
332+
);
333+
}
334+
})
335+
.filter_map(|(encrypted_bid, decrypted_bid)| {
336+
decrypted_bid.ok().map(|amount| DecryptedBid {
337+
amount,
338+
bidder: encrypted_bid.bidder,
339+
})
340+
})
341+
.collect()
342+
}
343+
344+
/// In the canister, using the IBE key derived from the identity decrypt a vector of ciphertexts, which makes them public.
345+
/// Returns a vector, where each value is either a decrypted plaintext or an error message.
346+
async fn decrypt_ciphertexts(
347+
identity: Vec<u8>,
348+
encrypted_values: Vec<&[u8]>,
349+
root_ibe_public_key_bytes: Vec<u8>,
350+
) -> Vec<Result<Vec<u8>, String>> {
302351
let dummy_seed = vec![0; 32];
303352
let transport_secret_key = ic_vetkeys::TransportSecretKey::from_seed(dummy_seed.clone())
304353
.expect("failed to create transport secret key");
305354

306355
let request = VetKDDeriveKeyRequest {
307356
context: DOMAIN_SEPARATOR.as_bytes().to_vec(),
308-
input: lot_id.to_le_bytes().to_vec(),
357+
input: identity.clone(),
309358
key_id: key_id(),
310359
transport_public_key: transport_secret_key.public_key().to_vec(),
311360
};
@@ -326,43 +375,22 @@ async fn decrypt_bids(
326375
.decrypt_and_verify(
327376
&transport_secret_key,
328377
&root_ibe_public_key,
329-
lot_id.to_le_bytes().as_ref(),
378+
identity.as_ref(),
330379
)
331380
.expect("failed to decrypt ibe key");
332381

333-
let mut decrypted_bids = Vec::new();
334-
335-
for encrypted_bid in encrypted_bids {
336-
let decrypted_bid: Result<u128, String> =
337-
ic_vetkeys::IbeCiphertext::deserialize(&encrypted_bid.encrypted_amount)
338-
.map_err(|e| format!("failed to deserialize ibe ciphertext: {e}"))
339-
.and_then(|c| {
340-
c.decrypt(&ibe_decryption_key)
341-
.map_err(|_| "failed to decrypt ibe ciphertext".to_string())
342-
})
343-
.and_then(|bytes| {
344-
bytes
345-
.as_slice()
346-
.try_into()
347-
.map_err(|_| "failed to convert amount to u128".to_string())
348-
})
349-
.map(u128::from_le_bytes);
350-
match decrypted_bid {
351-
Ok(amount) => {
352-
decrypted_bids.push(DecryptedBid {
353-
amount,
354-
bidder: encrypted_bid.bidder,
355-
});
356-
}
357-
Err(e) => {
358-
ic_cdk::println!(
359-
"Failed to decrypt bid for lot id {lot_id} by {}: {e}",
360-
encrypted_bid.bidder
361-
);
362-
}
363-
}
382+
let mut decrypted_values = Vec::new();
383+
384+
for encrypted_value in encrypted_values.into_iter() {
385+
let decrypted_value = ic_vetkeys::IbeCiphertext::deserialize(encrypted_value)
386+
.map_err(|e| format!("failed to deserialize ibe ciphertext: {e}"))
387+
.and_then(|c| {
388+
c.decrypt(&ibe_decryption_key)
389+
.map_err(|_| "failed to decrypt ibe ciphertext".to_string())
390+
});
391+
decrypted_values.push(decrypted_value);
364392
}
365-
decrypted_bids
393+
decrypted_values
366394
}
367395

368396
fn is_authenticated() -> Result<(), String> {

examples/basic_timelock_ibe/frontend/src/main.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,6 @@ function getBasicTimelockIbeCanister(): ActorSubclass<_SERVICE> {
5050
return basicTimelockIbeCanister;
5151
}
5252

53-
// Get the root IBE public key
54-
async function getRootIbePublicKey(): Promise<DerivedPublicKey> {
55-
if (ibePublicKey) return ibePublicKey;
56-
ibePublicKey = DerivedPublicKey.deserialize(
57-
new Uint8Array(
58-
await getBasicTimelockIbeCanister().get_root_ibe_public_key(),
59-
),
60-
);
61-
return ibePublicKey;
62-
}
63-
6453
export function login(client: AuthClient) {
6554
void client.login({
6655
maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000),
@@ -193,6 +182,30 @@ document.getElementById("createLotForm")!.addEventListener("submit", (e) => {
193182
void createLot(name, description, duration);
194183
});
195184

185+
async function getRootIbePublicKey(): Promise<DerivedPublicKey> {
186+
if (ibePublicKey) return ibePublicKey;
187+
ibePublicKey = DerivedPublicKey.deserialize(
188+
new Uint8Array(
189+
await getBasicTimelockIbeCanister().get_root_ibe_public_key(),
190+
),
191+
);
192+
return ibePublicKey;
193+
}
194+
195+
async function encrypt(
196+
cleartext: Uint8Array,
197+
identity: Uint8Array,
198+
): Promise<Uint8Array> {
199+
const publicKey = await getRootIbePublicKey();
200+
const ciphertext = IbeCiphertext.encrypt(
201+
publicKey,
202+
IbeIdentity.fromBytes(identity),
203+
cleartext,
204+
IbeSeed.random(),
205+
);
206+
return ciphertext.serialize();
207+
}
208+
196209
async function createLot(
197210
name: string,
198211
description: string,
@@ -424,23 +437,16 @@ async function listLots() {
424437

425438
async function placeBid(lotId: bigint, amount: number) {
426439
try {
427-
// Get the root IBE public key
428-
const rootIbePublicKey = await getRootIbePublicKey();
429440
const lotIdBytes = u128ToLeBytes(lotId);
430441
const amountBytes = u128ToLeBytes(BigInt(amount));
431442

432443
// Encrypt the bid amount using IBE
433-
const encryptedAmount = IbeCiphertext.encrypt(
434-
rootIbePublicKey,
435-
IbeIdentity.fromBytes(lotIdBytes),
436-
amountBytes,
437-
IbeSeed.random(),
438-
);
444+
const encryptedAmount = await encrypt(amountBytes, lotIdBytes);
439445

440446
// Place the bid
441447
const result = await getBasicTimelockIbeCanister().place_bid(
442448
lotId,
443-
encryptedAmount.serialize(),
449+
encryptedAmount,
444450
);
445451
if ("Err" in result) {
446452
alert(`Failed to place bid: ${result.Err}`);

0 commit comments

Comments
 (0)