1#![forbid(unsafe_code)]
6#![warn(rust_2018_idioms)]
7#![forbid(missing_docs)]
8
9use hex_literal::hex;
10use ic_bls12_381::{
11 hash_to_curve::{ExpandMsgXmd, HashToCurve},
12 G1Affine, G1Projective, G2Affine, G2Prepared, Gt, Scalar,
13};
14use ic_cdk::management_canister::{VetKDCurve, VetKDDeriveKeyArgs, VetKDKeyId};
15use rand::SeedableRng;
16use rand_chacha::ChaCha20Rng;
17use std::array::TryFromSliceError;
18use std::ops::Neg;
19use zeroize::{Zeroize, ZeroizeOnDrop};
20
21const MASTER_PUBLIC_KEY_BYTES_KEY_1 : [u8; 96] = hex!("a9caf9ae8af0c7c7272f8a122133e2e0c7c0899b75e502bda9e109ca8193ded3ef042ed96db1125e1bdaad77d8cc60d917e122fe2501c45b96274f43705edf0cfd455bc66c3c060faa2fcd15486e76351edf91fecb993797273bbc8beaa47404");
22
23const MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1 : [u8; 96] = hex!("ad86e8ff845912f022a0838a502d763fdea547c9948f8cb20ea7738dd52c1c38dcb4c6ca9ac29f9ac690fc5ad7681cb41922b8dffbd65d94bff141f5fb5b6624eccc03bf850f222052df888cf9b1e47203556d7522271cbb879b2ef4b8c2bfb1");
24
25const POCKETIC_MASTER_PUBLIC_KEY_BYTES_KEY_1 : [u8; 96] = hex!("8c800b5cff00463d26e8167369168827f1e48f4d8d60f71dd6a295580f65275b5f5f8e6a792c876b2c72492136530d0710a27522ee63977a76216c3cef9e70bfcb45b88736fc62142e7e0737848ce06cbb1f45a4a6a349b142ae5cf7853561e0");
26
27const POCKETIC_MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1 : [u8; 96] = hex!("9069b82c7aae418cef27678291e7f2cb1a008a500eceba7199bffca12421b07c158987c6a22618af3d1958738b2835691028801f7663d311799733286c557c8979184bb62cb559a4d582fca7d2e48b860f08ed6641aef66a059ec891889a6218");
28
29const POCKETIC_MASTER_PUBLIC_KEY_BYTES_DFX_TEST_KEY : [u8; 96] = hex!("b181c14cf9d04ba45d782c0067a44b0aaa9fc2acf94f1a875f0dae801af4f80339a7e6bf8b09fcf993824c8df3080b3f1409b688ca08cbd44d2cb28db9899f4aa3b5f06b9174240448e10be2f01f9f80079ea5431ce2d11d1c8d1c775333315f");
30
31fn decode_g2_mpk(bytes: &[u8; 96]) -> G2Affine {
32 G2Affine::from_compressed(bytes).expect("Hardcoded master public key not a valid point")
33}
34
35lazy_static::lazy_static! {
36 static ref G2PREPARED_NEG_G : G2Prepared = G2Affine::generator().neg().into();
37
38 static ref PROD_G2_KEY_1: G2Affine = decode_g2_mpk(&MASTER_PUBLIC_KEY_BYTES_KEY_1);
39 static ref PROD_G2_TEST_KEY_1: G2Affine = decode_g2_mpk(&MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1);
40
41 static ref POCKETIC_G2_KEY_1: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_KEY_1);
42 static ref POCKETIC_G2_TEST_KEY_1: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1);
43 static ref POCKETIC_G2_DFX_TEST_KEY: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_DFX_TEST_KEY);
44}
45
46const G1AFFINE_BYTES: usize = 48; const G2AFFINE_BYTES: usize = 96; struct G2PrecomputedTable {
50 tbl: Vec<G2Affine>,
51}
52
53impl G2PrecomputedTable {
54 const WINDOW_BITS: usize = 4;
74
75 const SUBGROUP_BITS: usize = 255;
77
78 const WINDOW_MASK: u8 = (1 << Self::WINDOW_BITS) - 1;
80
81 const WINDOWS: usize = Self::SUBGROUP_BITS.div_ceil(Self::WINDOW_BITS);
83
84 const WINDOW_ELEMENTS: usize = (1 << Self::WINDOW_BITS) - 1;
89
90 const TABLE_SIZE: usize = Self::WINDOW_ELEMENTS * Self::WINDOWS;
92
93 fn new(pt: &G2Affine) -> Self {
95 let mut ptbl = vec![ic_bls12_381::G2Projective::identity(); Self::TABLE_SIZE];
96
97 let mut accum = ic_bls12_381::G2Projective::from(pt);
98
99 for i in 0..Self::WINDOWS {
100 let tbl_i = &mut ptbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
101
102 tbl_i[0] = accum;
103 for j in 1..Self::WINDOW_ELEMENTS {
104 tbl_i[j] = if j % 2 == 1 {
111 tbl_i[j / 2].double()
112 } else {
113 tbl_i[j - 1] + tbl_i[0]
114 };
115 }
116
117 accum = tbl_i[Self::WINDOW_ELEMENTS / 2].double();
119 }
120
121 let mut tbl = vec![ic_bls12_381::G2Affine::identity(); Self::TABLE_SIZE];
124 ic_bls12_381::G2Projective::batch_normalize(&ptbl, &mut tbl);
125
126 Self { tbl }
127 }
128
129 fn mul_vartime(&self, scalar: &Scalar, extra_add: Option<&G2Affine>) -> ic_bls12_381::G2Affine {
131 let s = {
132 let mut s = scalar.to_bytes();
133 s.reverse(); s
135 };
136
137 let mut accum = if let Some(add) = extra_add {
138 ic_bls12_381::G2Projective::from(add)
139 } else {
140 ic_bls12_381::G2Projective::identity()
141 };
142
143 for i in 0..Self::WINDOWS {
144 let tbl_for_i = &self.tbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
145
146 let b = Self::get_window(&s, Self::WINDOW_BITS * i);
147 if b > 0 {
148 accum += tbl_for_i[b as usize - 1];
149 }
150 }
151
152 G2Affine::from(accum)
153 }
154
155 fn mul(&self, scalar: &Scalar) -> ic_bls12_381::G2Affine {
157 let s = {
158 let mut s = scalar.to_bytes();
159 s.reverse(); s
161 };
162
163 let mut accum = ic_bls12_381::G2Projective::identity();
164
165 for i in 0..Self::WINDOWS {
166 let tbl_for_i = &self.tbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
167
168 let b = Self::get_window(&s, Self::WINDOW_BITS * i);
169 accum += Self::ct_select(tbl_for_i, b as usize);
170 }
171
172 G2Affine::from(accum)
173 }
174
175 #[inline(always)]
177 fn get_window(s: &[u8], offset: usize) -> u8 {
178 const BITS_IN_BYTE: usize = 8;
179
180 let shift = offset % BITS_IN_BYTE;
181 let byte_offset = s.len() - 1 - (offset / BITS_IN_BYTE);
182
183 let w0 = s[byte_offset];
184
185 let single_byte_window = shift <= (BITS_IN_BYTE - Self::WINDOW_BITS) || byte_offset == 0;
186
187 let bits = if single_byte_window {
188 w0 >> shift
190 } else {
191 let w1 = s[byte_offset - 1];
193 (w0 >> shift) | (w1 << (BITS_IN_BYTE - shift))
194 };
195
196 bits & Self::WINDOW_MASK
197 }
198
199 #[inline(always)]
205 fn ct_select(from: &[ic_bls12_381::G2Affine], index: usize) -> ic_bls12_381::G2Affine {
206 use subtle::{ConditionallySelectable, ConstantTimeEq};
207
208 let mut val = ic_bls12_381::G2Affine::identity();
209
210 let index = index.wrapping_sub(1);
211 for (idx, v) in from.iter().enumerate() {
212 val.conditional_assign(v, usize::ct_eq(&idx, &index));
213 }
214
215 val
216 }
217}
218
219lazy_static::lazy_static! {
220 static ref G2_MUL_TABLE: G2PrecomputedTable = G2PrecomputedTable::new(&G2Affine::generator());
221}
222
223fn hkdf(okm: &mut [u8], input: &[u8], domain_sep: &str) {
225 let hk = hkdf::Hkdf::<sha2::Sha256>::new(None, input);
226 hk.expand(domain_sep.as_bytes(), okm)
227 .expect("Unsupported output length for HKDF");
228}
229
230pub fn derive_symmetric_key(input: &[u8], domain_sep: &str, len: usize) -> Vec<u8> {
240 let mut okm = vec![0u8; len];
241 hkdf(&mut okm, input, domain_sep);
242 okm
243}
244
245fn hash_to_scalar(input: &[u8], domain_sep: &str) -> ic_bls12_381::Scalar {
246 use ic_bls12_381::hash_to_curve::HashToField;
247
248 let mut s = [ic_bls12_381::Scalar::zero()];
249 <ic_bls12_381::Scalar as HashToField>::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
250 input,
251 domain_sep.as_bytes(),
252 &mut s,
253 );
254 s[0]
255}
256
257fn extend_with_length_prefix(vec: &mut Vec<u8>, data: &[u8]) {
258 vec.extend_from_slice(&(data.len() as u64).to_be_bytes());
259 vec.extend(data);
260}
261
262fn hash_to_scalar_two_inputs(
263 input1: &[u8],
264 input2: &[u8],
265 domain_sep: &str,
266) -> ic_bls12_381::Scalar {
267 let combined_input = {
268 let mut c = Vec::with_capacity(2 * 8 + input1.len() + input2.len());
269 extend_with_length_prefix(&mut c, input1);
270 extend_with_length_prefix(&mut c, input2);
271 c
272 };
273
274 hash_to_scalar(&combined_input, domain_sep)
275}
276
277#[derive(Clone, Zeroize, ZeroizeOnDrop)]
278pub struct TransportSecretKey {
280 secret_key: Box<Scalar>,
293}
294
295impl TransportSecretKey {
296 pub fn from_seed(seed: Vec<u8>) -> Result<TransportSecretKey, String> {
298 let seed_32_bytes: [u8; 32] = seed.try_into().map_err(|_e| "seed not 32 bytes")?;
299 let rng = &mut ChaCha20Rng::from_seed(seed_32_bytes);
300 use pairing::group::ff::Field;
301 let secret_key = Box::new(Scalar::random(rng));
302 Ok(Self { secret_key })
303 }
304
305 pub fn public_key(&self) -> Vec<u8> {
307 let public_key = G1Affine::generator() * (*self.secret_key);
308 use pairing::group::Curve;
309 public_key.to_affine().to_compressed().to_vec()
310 }
311
312 pub fn serialize(&self) -> Vec<u8> {
314 self.secret_key.to_bytes().to_vec()
315 }
316
317 pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
319 if bytes.len() != 32 {
320 return Err(format!(
321 "TransportSecretKey must be exactly 32 bytes not {}",
322 bytes.len()
323 ));
324 }
325
326 let bytes: [u8; 32] = bytes.try_into().expect("Length already checked");
327
328 if let Some(s) = Scalar::from_bytes(&bytes).into_option() {
329 Ok(Self {
330 secret_key: Box::new(s),
331 })
332 } else {
333 Err("Invalid TransportSecretKey bytes".to_string())
334 }
335 }
336}
337
338pub fn is_valid_transport_public_key_encoding(bytes: &[u8]) -> bool {
340 match bytes.try_into() {
341 Ok(bytes) => G1Affine::from_compressed(&bytes).into_option().is_some(),
342 Err(_) => false,
343 }
344}
345
346#[derive(Copy, Clone, Debug)]
347pub enum PublicKeyDeserializationError {
349 InvalidPublicKey,
351}
352
353#[derive(Clone, Debug, Eq, PartialEq)]
354pub struct MasterPublicKey {
356 point: G2Affine,
357}
358
359impl MasterPublicKey {
360 const BYTES: usize = G2AFFINE_BYTES;
361
362 pub fn deserialize(bytes: &[u8]) -> Result<Self, PublicKeyDeserializationError> {
373 let dpk_bytes: &[u8; Self::BYTES] = bytes
374 .try_into()
375 .map_err(|_e: TryFromSliceError| PublicKeyDeserializationError::InvalidPublicKey)?;
376 let dpk = G2Affine::from_compressed(dpk_bytes)
377 .into_option()
378 .ok_or(PublicKeyDeserializationError::InvalidPublicKey)?;
379 Ok(Self { point: dpk })
380 }
381
382 pub fn derive_canister_key(&self, canister_id: &[u8]) -> DerivedPublicKey {
393 let dst = "ic-vetkd-bls12-381-g2-canister-id";
394
395 let offset = hash_to_scalar_two_inputs(&self.serialize(), canister_id, dst);
396
397 let derived_key = G2_MUL_TABLE.mul_vartime(&offset, Some(&self.point));
398 DerivedPublicKey { point: derived_key }
399 }
400
401 pub fn serialize(&self) -> Vec<u8> {
403 self.point.to_compressed().to_vec()
404 }
405
406 pub fn for_mainnet_key(key_id: &VetKDKeyId) -> Option<Self> {
412 match (key_id.curve, key_id.name.as_str()) {
413 (VetKDCurve::Bls12_381_G2, "key_1") => Some(Self::new(*PROD_G2_KEY_1)),
414 (VetKDCurve::Bls12_381_G2, "test_key_1") => Some(Self::new(*PROD_G2_TEST_KEY_1)),
415 (_, _) => None,
416 }
417 }
418
419 pub fn for_pocketic_key(key_id: &VetKDKeyId) -> Option<Self> {
423 match (key_id.curve, key_id.name.as_str()) {
424 (VetKDCurve::Bls12_381_G2, "key_1") => Some(Self::new(*POCKETIC_G2_KEY_1)),
425 (VetKDCurve::Bls12_381_G2, "test_key_1") => Some(Self::new(*POCKETIC_G2_TEST_KEY_1)),
426 (VetKDCurve::Bls12_381_G2, "dfx_test_key") => {
427 Some(Self::new(*POCKETIC_G2_DFX_TEST_KEY))
428 }
429 (_, _) => None,
430 }
431 }
432
433 fn new(point: G2Affine) -> Self {
434 Self { point }
435 }
436}
437
438#[derive(Clone, Debug, Eq, PartialEq)]
439pub struct DerivedPublicKey {
441 point: G2Affine,
442}
443
444impl From<DerivedPublicKey> for G2Affine {
445 fn from(public_key: DerivedPublicKey) -> Self {
446 public_key.point
447 }
448}
449
450impl DerivedPublicKey {
451 const BYTES: usize = G2AFFINE_BYTES;
452
453 pub fn deserialize(bytes: &[u8]) -> Result<Self, PublicKeyDeserializationError> {
464 let dpk_bytes: &[u8; Self::BYTES] = bytes
465 .try_into()
466 .map_err(|_e: TryFromSliceError| PublicKeyDeserializationError::InvalidPublicKey)?;
467 let dpk = G2Affine::from_compressed(dpk_bytes)
468 .into_option()
469 .ok_or(PublicKeyDeserializationError::InvalidPublicKey)?;
470 Ok(Self { point: dpk })
471 }
472
473 pub fn derive_sub_key(&self, context: &[u8]) -> Self {
487 if context.is_empty() {
488 return self.clone();
489 }
490
491 let dst = "ic-vetkd-bls12-381-g2-context";
492
493 let offset = hash_to_scalar_two_inputs(&self.serialize(), context, dst);
494
495 let derived_key = G2_MUL_TABLE.mul_vartime(&offset, Some(&self.point));
496 Self { point: derived_key }
497 }
498
499 pub fn serialize(&self) -> Vec<u8> {
501 self.point.to_compressed().to_vec()
502 }
503}
504
505#[derive(Clone, Debug, Eq, PartialEq, Zeroize, ZeroizeOnDrop)]
511pub struct VetKey {
512 vetkey: Box<(G1Affine, [u8; 48])>,
514}
515
516impl VetKey {
517 fn new(pt: G1Affine) -> Self {
518 let vetkey = Box::new((pt, pt.to_compressed()));
519 Self { vetkey }
520 }
521
522 pub fn signature_bytes(&self) -> &[u8; 48] {
531 &self.vetkey.1
532 }
533
534 pub fn serialize(&self) -> &[u8; 48] {
541 &self.vetkey.1
542 }
543
544 pub(crate) fn point(&self) -> &G1Affine {
545 &self.vetkey.0
546 }
547
548 pub fn derive_symmetric_key(&self, domain_sep: &str, output_len: usize) -> Vec<u8> {
557 derive_symmetric_key(self.serialize(), domain_sep, output_len)
558 }
559
560 pub fn as_derived_key_material(&self) -> DerivedKeyMaterial {
567 let key = self.derive_symmetric_key("ic-vetkd-bls12-381-g2-derived-key-material", 32);
568 DerivedKeyMaterial {
569 key,
570 raw_vetkey: self.vetkey.1.to_vec(),
571 }
572 }
573
574 pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
580 let bytes48: [u8; 48] = bytes.try_into().map_err(|_e: TryFromSliceError| {
581 format!("Vetkey is unexpected length {}", bytes.len())
582 })?;
583
584 if let Some(pt) = G1Affine::from_compressed(&bytes48).into_option() {
585 Ok(Self::new(pt))
586 } else {
587 Err("Invalid VetKey".to_string())
588 }
589 }
590}
591
592#[derive(Clone, Zeroize, ZeroizeOnDrop)]
600pub struct DerivedKeyMaterial {
601 key: Vec<u8>,
602 raw_vetkey: Vec<u8>,
603}
604
605#[derive(Copy, Clone, Eq, PartialEq, Debug)]
606pub enum EncryptionError {
608 PlaintextTooLong,
610}
611
612#[derive(Copy, Clone, Eq, PartialEq, Debug)]
613pub enum DecryptionError {
615 MessageTooShort,
617 InvalidCiphertext,
619 UnknownHeader,
624}
625
626impl DerivedKeyMaterial {
627 const GCM_KEY_SIZE: usize = 32;
628 const GCM_TAG_SIZE: usize = 16;
629 const GCM_NONCE_SIZE: usize = 12;
630
631 const GCM_HEADER_VERSION: u8 = 2;
632 const GCM_HEADER_SIZE: usize = 8;
633 const GCM_HEADER: [u8; Self::GCM_HEADER_SIZE] = *b"IC GCMv2";
634
635 fn derive_aes_gcm_key(&self, domain_sep: &str, version: u8) -> Vec<u8> {
640 derive_symmetric_key(
641 &self.key,
642 &format!("ic-vetkd-bls12-381-g2-aes-gcm-v{}-{}", version, domain_sep),
643 Self::GCM_KEY_SIZE,
644 )
645 }
646
647 pub fn encrypt_message<R: rand::RngCore + rand::CryptoRng>(
669 &self,
670 message: &[u8],
671 domain_sep: &str,
672 associated_data: &[u8],
673 rng: &mut R,
674 ) -> Result<Vec<u8>, EncryptionError> {
675 use aes_gcm::{aead::Aead, aead::AeadCore, Aes256Gcm, Key, KeyInit};
676 let key = self.derive_aes_gcm_key(domain_sep, Self::GCM_HEADER_VERSION);
677 let key = Key::<Aes256Gcm>::from_slice(&key);
678 let nonce = Aes256Gcm::generate_nonce(rng);
680 assert_eq!(nonce.len(), Self::GCM_NONCE_SIZE);
681 let gcm = Aes256Gcm::new(key);
682
683 let prefixed_aad = {
686 let mut r = Vec::with_capacity(Self::GCM_HEADER.len() + associated_data.len());
687 r.extend_from_slice(&Self::GCM_HEADER); r.extend_from_slice(associated_data);
689 r
690 };
691
692 let msg = aes_gcm::aead::Payload {
693 msg: message,
694 aad: &prefixed_aad,
695 };
696
697 let ctext = gcm
701 .encrypt(&nonce, msg)
702 .map_err(|_| EncryptionError::PlaintextTooLong)?;
703
704 let mut res = vec![];
705 res.extend_from_slice(&Self::GCM_HEADER);
706 res.extend_from_slice(nonce.as_slice());
707 res.extend_from_slice(ctext.as_slice());
708 Ok(res)
709 }
710
711 pub fn decrypt_message(
716 &self,
717 ctext: &[u8],
718 domain_sep: &str,
719 associated_data: &[u8],
720 ) -> Result<Vec<u8>, DecryptionError> {
721 use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit};
722
723 if ctext.len() < Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE + Self::GCM_TAG_SIZE {
725 return Err(DecryptionError::MessageTooShort);
726 }
727
728 if ctext[0..Self::GCM_HEADER_SIZE] != Self::GCM_HEADER {
733 if associated_data.is_empty() {
734 let key = derive_symmetric_key(&self.raw_vetkey, domain_sep, Self::GCM_KEY_SIZE);
738
739 let nonce = aes_gcm::Nonce::from_slice(&ctext[0..Self::GCM_NONCE_SIZE]);
740 let gcm = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key));
741
742 let ptext = gcm
743 .decrypt(nonce, &ctext[Self::GCM_NONCE_SIZE..])
744 .map_err(|_| DecryptionError::InvalidCiphertext)?;
745
746 return Ok(ptext.as_slice().to_vec());
747 } else {
748 return Err(DecryptionError::UnknownHeader);
749 }
750 }
751
752 let key = self.derive_aes_gcm_key(domain_sep, Self::GCM_HEADER_VERSION);
753 let key = Key::<Aes256Gcm>::from_slice(&key);
754
755 let nonce = aes_gcm::Nonce::from_slice(
756 &ctext[Self::GCM_HEADER_SIZE..Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE],
757 );
758 let gcm = Aes256Gcm::new(key);
759
760 let prefixed_aad = {
761 let mut r = Vec::with_capacity(Self::GCM_HEADER.len() + associated_data.len());
762 r.extend_from_slice(&ctext[0..Self::GCM_HEADER_SIZE]);
763 r.extend_from_slice(associated_data);
764 r
765 };
766
767 let msg = aes_gcm::aead::Payload {
768 msg: &ctext[Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE..],
769 aad: &prefixed_aad,
770 };
771
772 let ptext = gcm
773 .decrypt(nonce, msg)
774 .map_err(|_| DecryptionError::InvalidCiphertext)?;
775
776 Ok(ptext.as_slice().to_vec())
777 }
778}
779
780#[derive(Copy, Clone, Debug)]
781pub enum EncryptedVetKeyDeserializationError {
783 InvalidEncryptedVetKey,
785}
786
787#[derive(Clone, Debug, Eq, PartialEq)]
789pub struct EncryptedVetKey {
790 c1: G1Affine,
791 c2: G2Affine,
792 c3: G1Affine,
793}
794
795impl EncryptedVetKey {
796 const BYTES: usize = 2 * G1AFFINE_BYTES + G2AFFINE_BYTES;
798
799 const C2_OFFSET: usize = G1AFFINE_BYTES;
800 const C3_OFFSET: usize = G1AFFINE_BYTES + G2AFFINE_BYTES;
801
802 pub fn decrypt_and_verify(
804 &self,
805 tsk: &TransportSecretKey,
806 derived_public_key: &DerivedPublicKey,
807 input: &[u8],
808 ) -> Result<VetKey, String> {
809 use pairing::group::Group;
810
811 let c2_prep = G2Prepared::from(self.c2);
814
815 let c1_c2 = gt_multipairing(&[
816 (&self.c1, &G2PREPARED_NEG_G),
817 (&G1Affine::generator(), &c2_prep),
818 ]);
819
820 if !bool::from(c1_c2.is_identity()) {
821 return Err("invalid encrypted key: c1 inconsistent with c2".to_string());
822 }
823
824 let k = G1Affine::from(G1Projective::from(&self.c3) - self.c1 * (*tsk.secret_key));
826
827 if verify_bls_signature_pt(derived_public_key, input, &k) {
829 Ok(VetKey::new(k))
830 } else {
831 Err("invalid encrypted key: verification failed".to_string())
832 }
833 }
834
835 pub fn serialize(&self) -> Vec<u8> {
837 let mut result = vec![];
838
839 result.extend_from_slice(&self.c1.to_compressed());
840 result.extend_from_slice(&self.c2.to_compressed());
841 result.extend_from_slice(&self.c3.to_compressed());
842
843 result
844 }
845
846 pub fn deserialize(bytes: &[u8]) -> Result<EncryptedVetKey, String> {
848 let ek_bytes: &[u8; Self::BYTES] = bytes.try_into().map_err(|_e: TryFromSliceError| {
849 format!("key not {} bytes but {}", Self::BYTES, bytes.len())
850 })?;
851 Self::deserialize_array(ek_bytes).map_err(|e| format!("{e:?}"))
852 }
853
854 pub fn deserialize_array(
856 val: &[u8; Self::BYTES],
857 ) -> Result<Self, EncryptedVetKeyDeserializationError> {
858 let c1_bytes: &[u8; G1AFFINE_BYTES] = &val[..Self::C2_OFFSET]
859 .try_into()
860 .map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
861 let c2_bytes: &[u8; G2AFFINE_BYTES] = &val[Self::C2_OFFSET..Self::C3_OFFSET]
862 .try_into()
863 .map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
864 let c3_bytes: &[u8; G1AFFINE_BYTES] = &val[Self::C3_OFFSET..]
865 .try_into()
866 .map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
867
868 let c1 = G1Affine::from_compressed(c1_bytes).into_option();
869 let c2 = G2Affine::from_compressed(c2_bytes).into_option();
870 let c3 = G1Affine::from_compressed(c3_bytes).into_option();
871
872 match (c1, c2, c3) {
873 (Some(c1), Some(c2), Some(c3)) => Ok(Self { c1, c2, c3 }),
874 (_, _, _) => Err(EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey),
875 }
876 }
877}
878
879#[derive(Clone, Debug, Eq, PartialEq)]
880pub struct IbeIdentity {
885 val: Vec<u8>,
886}
887
888impl IbeIdentity {
889 pub fn from_bytes(bytes: &[u8]) -> Self {
891 Self {
892 val: bytes.to_vec(),
893 }
894 }
895
896 pub fn from_string(str: &str) -> Self {
898 Self::from_bytes(str.as_bytes())
899 }
900
901 pub fn from_principal(principal: &candid::Principal) -> Self {
903 Self::from_bytes(principal.as_slice())
904 }
905
906 pub fn value(&self) -> &[u8] {
908 &self.val
909 }
910}
911
912const IBE_SEED_BYTES: usize = 32;
916
917#[derive(Zeroize, ZeroizeOnDrop)]
919pub struct IbeSeed {
920 val: Box<[u8; IBE_SEED_BYTES]>,
922}
923
924impl IbeSeed {
925 pub fn random<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
927 use rand::Rng;
928 Self {
929 val: Box::new(rng.gen::<[u8; IBE_SEED_BYTES]>()),
930 }
931 }
932
933 pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
943 if bytes.len() < 16 {
944 return Err("Insufficient input material for IbeSeed derivation".to_string());
945 }
946
947 let mut val = Box::new([0u8; IBE_SEED_BYTES]);
948 if bytes.len() == IBE_SEED_BYTES {
949 val.copy_from_slice(bytes)
950 } else {
951 let hkdf =
952 derive_symmetric_key(bytes, "ic-vetkd-bls12-381-ibe-hash-seed", IBE_SEED_BYTES);
953 val.copy_from_slice(&hkdf);
954 }
955
956 Ok(Self { val })
957 }
958
959 fn value(&self) -> &[u8; IBE_SEED_BYTES] {
960 &self.val
961 }
962}
963
964const IBE_HEADER: [u8; 8] = [b'I', b'C', b' ', b'I', b'B', b'E', 0x00, 0x01];
974
975const IBE_HEADER_BYTES: usize = IBE_HEADER.len();
976
977const IBE_OVERHEAD: usize = IBE_HEADER_BYTES + IBE_SEED_BYTES + G2AFFINE_BYTES;
978
979#[derive(Clone, Debug, Eq, PartialEq)]
980pub struct IbeCiphertext {
982 header: Vec<u8>,
983 c1: G2Affine,
984 c2: [u8; IBE_SEED_BYTES],
985 c3: Vec<u8>,
986}
987
988enum IbeDomainSep {
989 HashToMask,
990 MaskSeed,
991 MaskMsg(usize),
992}
993
994impl IbeDomainSep {
995 #[allow(clippy::inherent_to_string)]
996 fn to_string(&self) -> String {
997 match self {
998 Self::HashToMask => "ic-vetkd-bls12-381-ibe-hash-to-mask".to_owned(),
999 Self::MaskSeed => "ic-vetkd-bls12-381-ibe-mask-seed".to_owned(),
1000 Self::MaskMsg(len) => format!("ic-vetkd-bls12-381-ibe-mask-msg-{len:020}"),
1007 }
1008 }
1009}
1010
1011impl IbeCiphertext {
1012 pub fn serialize(&self) -> Vec<u8> {
1014 let mut output = Vec::with_capacity(IBE_OVERHEAD + self.c3.len());
1015
1016 output.extend_from_slice(&self.header);
1017 output.extend_from_slice(&self.c1.to_compressed());
1018 output.extend_from_slice(&self.c2);
1019 output.extend_from_slice(&self.c3);
1020
1021 output
1022 }
1023
1024 pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
1028 if bytes.len() < IBE_OVERHEAD {
1029 return Err("IbeCiphertext too short to be valid".to_string());
1030 }
1031
1032 let header = bytes[0..IBE_HEADER_BYTES].to_vec();
1033 let c1 = deserialize_g2(&bytes[IBE_HEADER_BYTES..(IBE_HEADER_BYTES + G2AFFINE_BYTES)])?;
1034
1035 let mut c2 = [0u8; IBE_SEED_BYTES];
1036 c2.copy_from_slice(
1037 &bytes[IBE_HEADER_BYTES + G2AFFINE_BYTES
1038 ..(IBE_HEADER_BYTES + G2AFFINE_BYTES + IBE_SEED_BYTES)],
1039 );
1040
1041 let c3 = bytes[IBE_HEADER_BYTES + G2AFFINE_BYTES + IBE_SEED_BYTES..].to_vec();
1042
1043 if header != IBE_HEADER {
1044 return Err("IbeCiphertext has unknown header".to_string());
1045 }
1046
1047 Ok(Self { header, c1, c2, c3 })
1048 }
1049
1050 fn hash_to_mask(header: &[u8], seed: &[u8; IBE_SEED_BYTES], msg: &[u8]) -> Scalar {
1051 let domain_sep = IbeDomainSep::HashToMask;
1059 let mut ro_input = Vec::with_capacity(seed.len() + msg.len());
1060 ro_input.extend_from_slice(header);
1061 ro_input.extend_from_slice(seed);
1062 ro_input.extend_from_slice(msg);
1063
1064 hash_to_scalar(&ro_input, &domain_sep.to_string())
1065 }
1066
1067 fn mask_seed(seed: &[u8; IBE_SEED_BYTES], t: &Gt) -> [u8; IBE_SEED_BYTES] {
1068 let domain_sep = IbeDomainSep::MaskSeed;
1069 let mask = derive_symmetric_key(&t.to_bytes(), &domain_sep.to_string(), IBE_SEED_BYTES);
1070
1071 let mut masked_seed = [0u8; IBE_SEED_BYTES];
1072 for i in 0..IBE_SEED_BYTES {
1073 masked_seed[i] = mask[i] ^ seed[i];
1074 }
1075 masked_seed
1076 }
1077
1078 fn mask_msg(msg: &[u8], seed: &[u8; IBE_SEED_BYTES]) -> Vec<u8> {
1079 fn derive_ibe_ctext_mask(seed: &[u8], msg_len: usize) -> Vec<u8> {
1080 use sha3::{
1081 digest::{ExtendableOutputReset, Update, XofReader},
1082 Shake256,
1083 };
1084
1085 let mut shake = Shake256::default();
1086 shake.update(seed);
1087
1088 let mut xof = shake.finalize_xof_reset();
1089 let mut mask = vec![0u8; msg_len];
1090 xof.read(&mut mask);
1091 mask
1092 }
1093
1094 let domain_sep = IbeDomainSep::MaskMsg(msg.len());
1095
1096 let mut shake_seed = derive_symmetric_key(seed, &domain_sep.to_string(), IBE_SEED_BYTES);
1097
1098 let mut mask = derive_ibe_ctext_mask(&shake_seed, msg.len());
1099 shake_seed.zeroize();
1100
1101 for i in 0..msg.len() {
1102 mask[i] ^= msg[i];
1103 }
1104
1105 mask
1106 }
1107
1108 pub fn encrypt(
1127 dpk: &DerivedPublicKey,
1128 identity: &IbeIdentity,
1129 msg: &[u8],
1130 seed: &IbeSeed,
1131 ) -> Self {
1132 let header = IBE_HEADER.to_vec();
1133
1134 let t = Self::hash_to_mask(&header, seed.value(), msg);
1135
1136 let pt = augmented_hash_to_g1(&dpk.point, identity.value());
1137
1138 let tsig = ic_bls12_381::pairing(&pt, &dpk.point) * t;
1139
1140 let c1 = G2_MUL_TABLE.mul(&t);
1141 let c2 = Self::mask_seed(seed.value(), &tsig);
1142 let c3 = Self::mask_msg(msg, seed.value());
1143
1144 Self { header, c1, c2, c3 }
1145 }
1146
1147 pub fn decrypt(&self, vetkey: &VetKey) -> Result<Vec<u8>, String> {
1160 let tsig = ic_bls12_381::pairing(vetkey.point(), &self.c1);
1161
1162 let seed = Self::mask_seed(&self.c2, &tsig);
1163
1164 let msg = Self::mask_msg(&self.c3, &seed);
1165
1166 let t = Self::hash_to_mask(&self.header, &seed, &msg);
1167
1168 let g_t = G2_MUL_TABLE.mul(&t);
1169
1170 if self.c1 == g_t {
1171 Ok(msg)
1172 } else {
1173 Err("decryption failed".to_string())
1174 }
1175 }
1176
1177 pub fn ciphertext_size(plaintext_size: usize) -> usize {
1179 plaintext_size + IBE_OVERHEAD
1180 }
1181
1182 pub fn plaintext_size(ciphertext_size: usize) -> Option<usize> {
1187 if ciphertext_size >= IBE_OVERHEAD {
1188 Some(ciphertext_size - IBE_OVERHEAD)
1189 } else {
1190 None
1191 }
1192 }
1193}
1194
1195#[derive(Copy, Clone, Debug)]
1197pub enum InvalidVrfOutput {
1198 UnexpectedLength,
1200 InvalidData,
1202 InvalidProof,
1204}
1205
1206#[derive(Eq, PartialEq)]
1219pub struct VrfOutput {
1220 proof: VetKey,
1221 dpk: DerivedPublicKey,
1222 output: [u8; Self::VRF_BYTES],
1223 input: Vec<u8>,
1224}
1225
1226impl VrfOutput {
1227 pub const VRF_BYTES: usize = 32;
1229
1230 fn compute_vrf_hash(
1231 vetkey: &VetKey,
1232 dpk: &DerivedPublicKey,
1233 input: &[u8],
1234 ) -> [u8; Self::VRF_BYTES] {
1235 let mut ro_input =
1246 Vec::with_capacity(G1AFFINE_BYTES + G2AFFINE_BYTES + input.len() + 3 * 8);
1247 extend_with_length_prefix(&mut ro_input, vetkey.serialize());
1248 extend_with_length_prefix(&mut ro_input, &dpk.serialize());
1249 extend_with_length_prefix(&mut ro_input, input);
1250
1251 let mut output = [0u8; Self::VRF_BYTES];
1252 hkdf(&mut output, &ro_input, "ic-vetkd-bls12-381-g2-vrf");
1253 output
1254 }
1255
1256 pub(crate) fn create(
1261 proof: VetKey,
1262 input: Vec<u8>,
1263 dpk: DerivedPublicKey,
1264 ) -> Result<Self, InvalidVrfOutput> {
1265 if !verify_bls_signature_pt(&dpk, &input, proof.point()) {
1266 return Err(InvalidVrfOutput::InvalidProof);
1267 }
1268
1269 let output = Self::compute_vrf_hash(&proof, &dpk, &input);
1270 Ok(Self {
1271 proof,
1272 dpk,
1273 output,
1274 input,
1275 })
1276 }
1277
1278 pub fn serialize(&self) -> Vec<u8> {
1280 let mut output = Vec::with_capacity(G1AFFINE_BYTES + G2AFFINE_BYTES + self.input.len());
1284 output.extend_from_slice(self.proof.serialize());
1285 output.extend_from_slice(&self.dpk.serialize());
1286 output.extend_from_slice(&self.input);
1287 output
1288 }
1289
1290 pub fn deserialize(bytes: &[u8]) -> Result<Self, InvalidVrfOutput> {
1298 if bytes.len() < G1AFFINE_BYTES + G2AFFINE_BYTES {
1299 return Err(InvalidVrfOutput::UnexpectedLength);
1300 }
1301
1302 let proof = VetKey::deserialize(&bytes[0..G1AFFINE_BYTES])
1303 .map_err(|_| InvalidVrfOutput::InvalidData)?;
1304
1305 let dpk =
1306 DerivedPublicKey::deserialize(&bytes[G1AFFINE_BYTES..G1AFFINE_BYTES + G2AFFINE_BYTES])
1307 .map_err(|_| InvalidVrfOutput::InvalidData)?;
1308
1309 let input = bytes[G1AFFINE_BYTES + G2AFFINE_BYTES..].to_vec();
1310
1311 if !verify_bls_signature_pt(&dpk, &input, proof.point()) {
1312 return Err(InvalidVrfOutput::InvalidProof);
1313 }
1314
1315 let output = Self::compute_vrf_hash(&proof, &dpk, &input);
1316
1317 Ok(Self {
1318 proof,
1319 dpk,
1320 output,
1321 input,
1322 })
1323 }
1324
1325 pub fn input(&self) -> &[u8] {
1327 &self.input
1328 }
1329
1330 pub fn public_key(&self) -> &DerivedPublicKey {
1332 &self.dpk
1333 }
1334
1335 pub fn output(&self) -> &[u8; Self::VRF_BYTES] {
1347 &self.output
1348 }
1349}
1350
1351pub fn verify_bls_signature(dpk: &DerivedPublicKey, input: &[u8], signature: &[u8]) -> bool {
1364 let signature: G1Affine = match <[u8; 48]>::try_from(signature) {
1365 Ok(bytes) => match G1Affine::from_compressed(&bytes).into_option() {
1366 Some(pt) => pt,
1367 None => return false,
1368 },
1369 Err(_) => return false,
1370 };
1371
1372 verify_bls_signature_pt(dpk, input, &signature)
1373}
1374
1375fn verify_bls_signature_pt(dpk: &DerivedPublicKey, input: &[u8], signature: &G1Affine) -> bool {
1380 if dpk.point.is_identity().into() {
1381 return false;
1382 }
1383
1384 let msg = augmented_hash_to_g1(&dpk.point, input);
1385 let dpk_prep = G2Prepared::from(dpk.point);
1386
1387 use pairing::group::Group;
1390 let is_valid =
1391 gt_multipairing(&[(signature, &G2PREPARED_NEG_G), (&msg, &dpk_prep)]).is_identity();
1392 bool::from(is_valid)
1393}
1394
1395fn augmented_hash_to_g1(pk: &G2Affine, data: &[u8]) -> G1Affine {
1396 let domain_sep = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_";
1397
1398 let mut signature_input = Vec::with_capacity(G2AFFINE_BYTES + data.len());
1399 signature_input.extend_from_slice(&pk.to_compressed());
1400 signature_input.extend_from_slice(data);
1401
1402 let pt = <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
1403 signature_input,
1404 domain_sep,
1405 );
1406 G1Affine::from(pt)
1407}
1408
1409fn gt_multipairing(terms: &[(&G1Affine, &G2Prepared)]) -> Gt {
1410 ic_bls12_381::multi_miller_loop(terms).final_exponentiation()
1411}
1412
1413fn deserialize_g2(bytes: &[u8]) -> Result<G2Affine, String> {
1414 let bytes: &[u8; G2AFFINE_BYTES] = bytes
1415 .try_into()
1416 .map_err(|_| "Invalid length for G2".to_string())?;
1417
1418 let pt = G2Affine::from_compressed(bytes);
1419 if bool::from(pt.is_some()) {
1420 Ok(pt.unwrap())
1421 } else {
1422 Err("Invalid G2 elliptic curve point".to_string())
1423 }
1424}
1425
1426pub mod management_canister {
1428 use ic_cdk::{call::CallResult, management_canister::VetKDPublicKeyArgs};
1429
1430 use crate::types::CanisterId;
1431
1432 use super::*;
1433
1434 async fn derive_unencrypted_vetkey(
1453 input: Vec<u8>,
1454 context: Vec<u8>,
1455 key_id: VetKDKeyId,
1456 ) -> Result<Vec<u8>, VetKDDeriveKeyCallError> {
1457 if key_id.curve != VetKDCurve::Bls12_381_G2 {
1458 return Err(VetKDDeriveKeyCallError::UnsupportedCurve);
1459 }
1460
1461 let request = VetKDDeriveKeyArgs {
1462 input,
1463 context,
1464 key_id,
1465 transport_public_key: G1Affine::identity().to_compressed().to_vec(),
1467 };
1468
1469 let reply = ic_cdk::management_canister::vetkd_derive_key(&request)
1470 .await
1471 .map_err(VetKDDeriveKeyCallError::CallFailed)?;
1472
1473 if reply.encrypted_key.len() != EncryptedVetKey::BYTES {
1474 return Err(VetKDDeriveKeyCallError::InvalidReply);
1475 }
1476
1477 Ok(reply.encrypted_key
1478 [EncryptedVetKey::C3_OFFSET..EncryptedVetKey::C3_OFFSET + G1AFFINE_BYTES]
1479 .to_vec())
1480 }
1481
1482 #[derive(Debug)]
1483 pub enum VetKDDeriveKeyCallError {
1485 UnsupportedCurve,
1487 CallFailed(ic_cdk::management_canister::SignCallError),
1489 InvalidReply,
1491 }
1492
1493 pub async fn sign_with_bls(
1511 message: Vec<u8>,
1512 context: Vec<u8>,
1513 key_id: VetKDKeyId,
1514 ) -> Result<Vec<u8>, VetKDDeriveKeyCallError> {
1515 derive_unencrypted_vetkey(message, context, key_id).await
1516 }
1517
1518 pub async fn bls_public_key(
1530 canister_id: Option<CanisterId>,
1531 context: Vec<u8>,
1532 key_id: VetKDKeyId,
1533 ) -> CallResult<Vec<u8>> {
1534 ic_cdk::management_canister::vetkd_public_key(&VetKDPublicKeyArgs {
1535 canister_id,
1536 context,
1537 key_id,
1538 })
1539 .await
1540 .map(|r| r.public_key)
1541 }
1542
1543 pub async fn compute_vrf(
1566 input: Vec<u8>,
1567 context: Vec<u8>,
1568 key_id: VetKDKeyId,
1569 ) -> Result<VrfOutput, VetKDDeriveKeyCallError> {
1570 let vetkey_bytes =
1571 derive_unencrypted_vetkey(input.clone(), context.clone(), key_id.clone())
1572 .await
1573 .map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
1574
1575 let vetkey = VetKey::deserialize(&vetkey_bytes)
1576 .map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
1577 let canister_id = ic_cdk::api::canister_self();
1578
1579 let dpk = match MasterPublicKey::for_mainnet_key(&key_id) {
1580 Some(mk) => mk
1581 .derive_canister_key(canister_id.as_slice())
1582 .derive_sub_key(&context),
1583 None => {
1584 let dpk_bytes =
1587 ic_cdk::management_canister::vetkd_public_key(&VetKDPublicKeyArgs {
1588 canister_id: Some(canister_id),
1589 context,
1590 key_id,
1591 })
1592 .await
1593 .map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
1594
1595 DerivedPublicKey::deserialize(&dpk_bytes.public_key)
1596 .map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?
1597 }
1598 };
1599
1600 VrfOutput::create(vetkey, input, dpk).map_err(|_| VetKDDeriveKeyCallError::InvalidReply)
1601 }
1602}