#![forbid(unsafe_code)]
#![warn(rust_2018_idioms)]
#![forbid(missing_docs)]
use hex_literal::hex;
use ic_bls12_381::{
hash_to_curve::{ExpandMsgXmd, HashToCurve},
G1Affine, G1Projective, G2Affine, G2Prepared, Gt, Scalar,
};
use ic_cdk_management_canister::{VetKDCurve, VetKDDeriveKeyArgs, VetKDKeyId};
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
use std::array::TryFromSliceError;
use std::ops::Neg;
use zeroize::{Zeroize, ZeroizeOnDrop};
const MASTER_PUBLIC_KEY_BYTES_KEY_1 : [u8; 96] = hex!("a9caf9ae8af0c7c7272f8a122133e2e0c7c0899b75e502bda9e109ca8193ded3ef042ed96db1125e1bdaad77d8cc60d917e122fe2501c45b96274f43705edf0cfd455bc66c3c060faa2fcd15486e76351edf91fecb993797273bbc8beaa47404");
const MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1 : [u8; 96] = hex!("ad86e8ff845912f022a0838a502d763fdea547c9948f8cb20ea7738dd52c1c38dcb4c6ca9ac29f9ac690fc5ad7681cb41922b8dffbd65d94bff141f5fb5b6624eccc03bf850f222052df888cf9b1e47203556d7522271cbb879b2ef4b8c2bfb1");
const POCKETIC_MASTER_PUBLIC_KEY_BYTES_KEY_1 : [u8; 96] = hex!("8c800b5cff00463d26e8167369168827f1e48f4d8d60f71dd6a295580f65275b5f5f8e6a792c876b2c72492136530d0710a27522ee63977a76216c3cef9e70bfcb45b88736fc62142e7e0737848ce06cbb1f45a4a6a349b142ae5cf7853561e0");
const POCKETIC_MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1 : [u8; 96] = hex!("9069b82c7aae418cef27678291e7f2cb1a008a500eceba7199bffca12421b07c158987c6a22618af3d1958738b2835691028801f7663d311799733286c557c8979184bb62cb559a4d582fca7d2e48b860f08ed6641aef66a059ec891889a6218");
const POCKETIC_MASTER_PUBLIC_KEY_BYTES_DFX_TEST_KEY : [u8; 96] = hex!("b181c14cf9d04ba45d782c0067a44b0aaa9fc2acf94f1a875f0dae801af4f80339a7e6bf8b09fcf993824c8df3080b3f1409b688ca08cbd44d2cb28db9899f4aa3b5f06b9174240448e10be2f01f9f80079ea5431ce2d11d1c8d1c775333315f");
fn decode_g2_mpk(bytes: &[u8; 96]) -> G2Affine {
G2Affine::from_compressed(bytes).expect("Hardcoded master public key not a valid point")
}
lazy_static::lazy_static! {
static ref G2PREPARED_NEG_G : G2Prepared = G2Affine::generator().neg().into();
static ref PROD_G2_KEY_1: G2Affine = decode_g2_mpk(&MASTER_PUBLIC_KEY_BYTES_KEY_1);
static ref PROD_G2_TEST_KEY_1: G2Affine = decode_g2_mpk(&MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1);
static ref POCKETIC_G2_KEY_1: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_KEY_1);
static ref POCKETIC_G2_TEST_KEY_1: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_TEST_KEY_1);
static ref POCKETIC_G2_DFX_TEST_KEY: G2Affine = decode_g2_mpk(&POCKETIC_MASTER_PUBLIC_KEY_BYTES_DFX_TEST_KEY);
}
const G1AFFINE_BYTES: usize = 48; const G2AFFINE_BYTES: usize = 96;
struct G2PrecomputedTable {
tbl: Vec<G2Affine>,
}
impl G2PrecomputedTable {
const WINDOW_BITS: usize = 4;
const SUBGROUP_BITS: usize = 255;
const WINDOW_MASK: u8 = (1 << Self::WINDOW_BITS) - 1;
const WINDOWS: usize = Self::SUBGROUP_BITS.div_ceil(Self::WINDOW_BITS);
const WINDOW_ELEMENTS: usize = (1 << Self::WINDOW_BITS) - 1;
const TABLE_SIZE: usize = Self::WINDOW_ELEMENTS * Self::WINDOWS;
fn new(pt: &G2Affine) -> Self {
let mut ptbl = vec![ic_bls12_381::G2Projective::identity(); Self::TABLE_SIZE];
let mut accum = ic_bls12_381::G2Projective::from(pt);
for i in 0..Self::WINDOWS {
let tbl_i = &mut ptbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
tbl_i[0] = accum;
for j in 1..Self::WINDOW_ELEMENTS {
tbl_i[j] = if j % 2 == 1 {
tbl_i[j / 2].double()
} else {
tbl_i[j - 1] + tbl_i[0]
};
}
accum = tbl_i[Self::WINDOW_ELEMENTS / 2].double();
}
let mut tbl = vec![ic_bls12_381::G2Affine::identity(); Self::TABLE_SIZE];
ic_bls12_381::G2Projective::batch_normalize(&ptbl, &mut tbl);
Self { tbl }
}
fn mul_vartime(&self, scalar: &Scalar, extra_add: Option<&G2Affine>) -> ic_bls12_381::G2Affine {
let s = {
let mut s = scalar.to_bytes();
s.reverse(); s
};
let mut accum = if let Some(add) = extra_add {
ic_bls12_381::G2Projective::from(add)
} else {
ic_bls12_381::G2Projective::identity()
};
for i in 0..Self::WINDOWS {
let tbl_for_i = &self.tbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
let b = Self::get_window(&s, Self::WINDOW_BITS * i);
if b > 0 {
accum += tbl_for_i[b as usize - 1];
}
}
G2Affine::from(accum)
}
fn mul(&self, scalar: &Scalar) -> ic_bls12_381::G2Affine {
let s = {
let mut s = scalar.to_bytes();
s.reverse(); s
};
let mut accum = ic_bls12_381::G2Projective::identity();
for i in 0..Self::WINDOWS {
let tbl_for_i = &self.tbl[Self::WINDOW_ELEMENTS * i..Self::WINDOW_ELEMENTS * (i + 1)];
let b = Self::get_window(&s, Self::WINDOW_BITS * i);
accum += Self::ct_select(tbl_for_i, b as usize);
}
G2Affine::from(accum)
}
#[inline(always)]
fn get_window(s: &[u8], offset: usize) -> u8 {
const BITS_IN_BYTE: usize = 8;
let shift = offset % BITS_IN_BYTE;
let byte_offset = s.len() - 1 - (offset / BITS_IN_BYTE);
let w0 = s[byte_offset];
let single_byte_window = shift <= (BITS_IN_BYTE - Self::WINDOW_BITS) || byte_offset == 0;
let bits = if single_byte_window {
w0 >> shift
} else {
let w1 = s[byte_offset - 1];
(w0 >> shift) | (w1 << (BITS_IN_BYTE - shift))
};
bits & Self::WINDOW_MASK
}
#[inline(always)]
fn ct_select(from: &[ic_bls12_381::G2Affine], index: usize) -> ic_bls12_381::G2Affine {
use subtle::{ConditionallySelectable, ConstantTimeEq};
let mut val = ic_bls12_381::G2Affine::identity();
let index = index.wrapping_sub(1);
for (idx, v) in from.iter().enumerate() {
val.conditional_assign(v, usize::ct_eq(&idx, &index));
}
val
}
}
lazy_static::lazy_static! {
static ref G2_MUL_TABLE: G2PrecomputedTable = G2PrecomputedTable::new(&G2Affine::generator());
}
fn hkdf(okm: &mut [u8], input: &[u8], domain_sep: &str) {
let hk = hkdf::Hkdf::<sha2::Sha256>::new(None, input);
hk.expand(domain_sep.as_bytes(), okm)
.expect("Unsupported output length for HKDF");
}
pub fn derive_symmetric_key(input: &[u8], domain_sep: &str, len: usize) -> Vec<u8> {
let mut okm = vec![0u8; len];
hkdf(&mut okm, input, domain_sep);
okm
}
fn hash_to_scalar(input: &[u8], domain_sep: &str) -> ic_bls12_381::Scalar {
use ic_bls12_381::hash_to_curve::HashToField;
let mut s = [ic_bls12_381::Scalar::zero()];
<ic_bls12_381::Scalar as HashToField>::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
input,
domain_sep.as_bytes(),
&mut s,
);
s[0]
}
fn extend_with_length_prefix(vec: &mut Vec<u8>, data: &[u8]) {
vec.extend_from_slice(&(data.len() as u64).to_be_bytes());
vec.extend(data);
}
fn hash_to_scalar_two_inputs(
input1: &[u8],
input2: &[u8],
domain_sep: &str,
) -> ic_bls12_381::Scalar {
let combined_input = {
let mut c = Vec::with_capacity(2 * 8 + input1.len() + input2.len());
extend_with_length_prefix(&mut c, input1);
extend_with_length_prefix(&mut c, input2);
c
};
hash_to_scalar(&combined_input, domain_sep)
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct TransportSecretKey {
secret_key: Box<Scalar>,
}
impl TransportSecretKey {
pub fn from_seed(seed: Vec<u8>) -> Result<TransportSecretKey, String> {
let seed_32_bytes: [u8; 32] = seed.try_into().map_err(|_e| "seed not 32 bytes")?;
let rng = &mut ChaCha20Rng::from_seed(seed_32_bytes);
use pairing::group::ff::Field;
let secret_key = Box::new(Scalar::random(rng));
Ok(Self { secret_key })
}
pub fn public_key(&self) -> Vec<u8> {
let public_key = G1Affine::generator() * (*self.secret_key);
use pairing::group::Curve;
public_key.to_affine().to_compressed().to_vec()
}
pub fn serialize(&self) -> Vec<u8> {
self.secret_key.to_bytes().to_vec()
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
if bytes.len() != 32 {
return Err(format!(
"TransportSecretKey must be exactly 32 bytes not {}",
bytes.len()
));
}
let bytes: [u8; 32] = bytes.try_into().expect("Length already checked");
if let Some(s) = Scalar::from_bytes(&bytes).into_option() {
Ok(Self {
secret_key: Box::new(s),
})
} else {
Err("Invalid TransportSecretKey bytes".to_string())
}
}
}
pub fn is_valid_transport_public_key_encoding(bytes: &[u8]) -> bool {
match bytes.try_into() {
Ok(bytes) => G1Affine::from_compressed(&bytes).into_option().is_some(),
Err(_) => false,
}
}
#[derive(Copy, Clone, Debug)]
pub enum PublicKeyDeserializationError {
InvalidPublicKey,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MasterPublicKey {
point: G2Affine,
}
impl MasterPublicKey {
const BYTES: usize = G2AFFINE_BYTES;
pub fn deserialize(bytes: &[u8]) -> Result<Self, PublicKeyDeserializationError> {
let dpk_bytes: &[u8; Self::BYTES] = bytes
.try_into()
.map_err(|_e: TryFromSliceError| PublicKeyDeserializationError::InvalidPublicKey)?;
let dpk = G2Affine::from_compressed(dpk_bytes)
.into_option()
.ok_or(PublicKeyDeserializationError::InvalidPublicKey)?;
Ok(Self { point: dpk })
}
pub fn derive_canister_key(&self, canister_id: &[u8]) -> DerivedPublicKey {
let dst = "ic-vetkd-bls12-381-g2-canister-id";
let offset = hash_to_scalar_two_inputs(&self.serialize(), canister_id, dst);
let derived_key = G2_MUL_TABLE.mul_vartime(&offset, Some(&self.point));
DerivedPublicKey { point: derived_key }
}
pub fn serialize(&self) -> Vec<u8> {
self.point.to_compressed().to_vec()
}
pub fn for_mainnet_key(key_id: &VetKDKeyId) -> Option<Self> {
match (key_id.curve, key_id.name.as_str()) {
(VetKDCurve::Bls12_381_G2, "key_1") => Some(Self::new(*PROD_G2_KEY_1)),
(VetKDCurve::Bls12_381_G2, "test_key_1") => Some(Self::new(*PROD_G2_TEST_KEY_1)),
(_, _) => None,
}
}
pub fn for_pocketic_key(key_id: &VetKDKeyId) -> Option<Self> {
match (key_id.curve, key_id.name.as_str()) {
(VetKDCurve::Bls12_381_G2, "key_1") => Some(Self::new(*POCKETIC_G2_KEY_1)),
(VetKDCurve::Bls12_381_G2, "test_key_1") => Some(Self::new(*POCKETIC_G2_TEST_KEY_1)),
(VetKDCurve::Bls12_381_G2, "dfx_test_key") => {
Some(Self::new(*POCKETIC_G2_DFX_TEST_KEY))
}
(_, _) => None,
}
}
fn new(point: G2Affine) -> Self {
Self { point }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DerivedPublicKey {
point: G2Affine,
}
impl From<DerivedPublicKey> for G2Affine {
fn from(public_key: DerivedPublicKey) -> Self {
public_key.point
}
}
impl DerivedPublicKey {
const BYTES: usize = G2AFFINE_BYTES;
pub fn deserialize(bytes: &[u8]) -> Result<Self, PublicKeyDeserializationError> {
let dpk_bytes: &[u8; Self::BYTES] = bytes
.try_into()
.map_err(|_e: TryFromSliceError| PublicKeyDeserializationError::InvalidPublicKey)?;
let dpk = G2Affine::from_compressed(dpk_bytes)
.into_option()
.ok_or(PublicKeyDeserializationError::InvalidPublicKey)?;
Ok(Self { point: dpk })
}
pub fn derive_sub_key(&self, context: &[u8]) -> Self {
if context.is_empty() {
return self.clone();
}
let dst = "ic-vetkd-bls12-381-g2-context";
let offset = hash_to_scalar_two_inputs(&self.serialize(), context, dst);
let derived_key = G2_MUL_TABLE.mul_vartime(&offset, Some(&self.point));
Self { point: derived_key }
}
pub fn serialize(&self) -> Vec<u8> {
self.point.to_compressed().to_vec()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Zeroize, ZeroizeOnDrop)]
pub struct VetKey {
vetkey: Box<(G1Affine, [u8; 48])>,
}
impl VetKey {
fn new(pt: G1Affine) -> Self {
let vetkey = Box::new((pt, pt.to_compressed()));
Self { vetkey }
}
pub fn signature_bytes(&self) -> &[u8; 48] {
&self.vetkey.1
}
pub fn serialize(&self) -> &[u8; 48] {
&self.vetkey.1
}
pub(crate) fn point(&self) -> &G1Affine {
&self.vetkey.0
}
pub fn derive_symmetric_key(&self, domain_sep: &str, output_len: usize) -> Vec<u8> {
derive_symmetric_key(self.serialize(), domain_sep, output_len)
}
pub fn as_derived_key_material(&self) -> DerivedKeyMaterial {
let key = self.derive_symmetric_key("ic-vetkd-bls12-381-g2-derived-key-material", 32);
DerivedKeyMaterial {
key,
raw_vetkey: self.vetkey.1.to_vec(),
}
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
let bytes48: [u8; 48] = bytes.try_into().map_err(|_e: TryFromSliceError| {
format!("Vetkey is unexpected length {}", bytes.len())
})?;
if let Some(pt) = G1Affine::from_compressed(&bytes48).into_option() {
Ok(Self::new(pt))
} else {
Err("Invalid VetKey".to_string())
}
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct DerivedKeyMaterial {
key: Vec<u8>,
raw_vetkey: Vec<u8>,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum EncryptionError {
PlaintextTooLong,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum DecryptionError {
MessageTooShort,
InvalidCiphertext,
UnknownHeader,
}
impl DerivedKeyMaterial {
const GCM_KEY_SIZE: usize = 32;
const GCM_TAG_SIZE: usize = 16;
const GCM_NONCE_SIZE: usize = 12;
const GCM_HEADER_VERSION: u8 = 2;
const GCM_HEADER_SIZE: usize = 8;
const GCM_HEADER: [u8; Self::GCM_HEADER_SIZE] = *b"IC GCMv2";
fn derive_aes_gcm_key(&self, domain_sep: &str, version: u8) -> Vec<u8> {
derive_symmetric_key(
&self.key,
&format!("ic-vetkd-bls12-381-g2-aes-gcm-v{version}-{domain_sep}"),
Self::GCM_KEY_SIZE,
)
}
pub fn encrypt_message<R: rand::RngCore + rand::CryptoRng>(
&self,
message: &[u8],
domain_sep: &str,
associated_data: &[u8],
rng: &mut R,
) -> Result<Vec<u8>, EncryptionError> {
use aes_gcm::{aead::Aead, aead::AeadCore, Aes256Gcm, Key, KeyInit};
let key = self.derive_aes_gcm_key(domain_sep, Self::GCM_HEADER_VERSION);
let key = Key::<Aes256Gcm>::from_slice(&key);
let nonce = Aes256Gcm::generate_nonce(rng);
assert_eq!(nonce.len(), Self::GCM_NONCE_SIZE);
let gcm = Aes256Gcm::new(key);
let prefixed_aad = {
let mut r = Vec::with_capacity(Self::GCM_HEADER.len() + associated_data.len());
r.extend_from_slice(&Self::GCM_HEADER); r.extend_from_slice(associated_data);
r
};
let msg = aes_gcm::aead::Payload {
msg: message,
aad: &prefixed_aad,
};
let ctext = gcm
.encrypt(&nonce, msg)
.map_err(|_| EncryptionError::PlaintextTooLong)?;
let mut res = vec![];
res.extend_from_slice(&Self::GCM_HEADER);
res.extend_from_slice(nonce.as_slice());
res.extend_from_slice(ctext.as_slice());
Ok(res)
}
pub fn decrypt_message(
&self,
ctext: &[u8],
domain_sep: &str,
associated_data: &[u8],
) -> Result<Vec<u8>, DecryptionError> {
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit};
if ctext.len() < Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE + Self::GCM_TAG_SIZE {
return Err(DecryptionError::MessageTooShort);
}
if ctext[0..Self::GCM_HEADER_SIZE] != Self::GCM_HEADER {
if associated_data.is_empty() {
let key = derive_symmetric_key(&self.raw_vetkey, domain_sep, Self::GCM_KEY_SIZE);
let nonce = aes_gcm::Nonce::from_slice(&ctext[0..Self::GCM_NONCE_SIZE]);
let gcm = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key));
let ptext = gcm
.decrypt(nonce, &ctext[Self::GCM_NONCE_SIZE..])
.map_err(|_| DecryptionError::InvalidCiphertext)?;
return Ok(ptext.as_slice().to_vec());
} else {
return Err(DecryptionError::UnknownHeader);
}
}
let key = self.derive_aes_gcm_key(domain_sep, Self::GCM_HEADER_VERSION);
let key = Key::<Aes256Gcm>::from_slice(&key);
let nonce = aes_gcm::Nonce::from_slice(
&ctext[Self::GCM_HEADER_SIZE..Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE],
);
let gcm = Aes256Gcm::new(key);
let prefixed_aad = {
let mut r = Vec::with_capacity(Self::GCM_HEADER.len() + associated_data.len());
r.extend_from_slice(&ctext[0..Self::GCM_HEADER_SIZE]);
r.extend_from_slice(associated_data);
r
};
let msg = aes_gcm::aead::Payload {
msg: &ctext[Self::GCM_HEADER_SIZE + Self::GCM_NONCE_SIZE..],
aad: &prefixed_aad,
};
let ptext = gcm
.decrypt(nonce, msg)
.map_err(|_| DecryptionError::InvalidCiphertext)?;
Ok(ptext.as_slice().to_vec())
}
}
#[derive(Copy, Clone, Debug)]
pub enum EncryptedVetKeyDeserializationError {
InvalidEncryptedVetKey,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EncryptedVetKey {
c1: G1Affine,
c2: G2Affine,
c3: G1Affine,
}
impl EncryptedVetKey {
const BYTES: usize = 2 * G1AFFINE_BYTES + G2AFFINE_BYTES;
const C2_OFFSET: usize = G1AFFINE_BYTES;
const C3_OFFSET: usize = G1AFFINE_BYTES + G2AFFINE_BYTES;
pub fn decrypt_and_verify(
&self,
tsk: &TransportSecretKey,
derived_public_key: &DerivedPublicKey,
input: &[u8],
) -> Result<VetKey, String> {
use pairing::group::Group;
let c2_prep = G2Prepared::from(self.c2);
let c1_c2 = gt_multipairing(&[
(&self.c1, &G2PREPARED_NEG_G),
(&G1Affine::generator(), &c2_prep),
]);
if !bool::from(c1_c2.is_identity()) {
return Err("invalid encrypted key: c1 inconsistent with c2".to_string());
}
let k = G1Affine::from(G1Projective::from(&self.c3) - self.c1 * (*tsk.secret_key));
if verify_bls_signature_pt(derived_public_key, input, &k) {
Ok(VetKey::new(k))
} else {
Err("invalid encrypted key: verification failed".to_string())
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut result = vec![];
result.extend_from_slice(&self.c1.to_compressed());
result.extend_from_slice(&self.c2.to_compressed());
result.extend_from_slice(&self.c3.to_compressed());
result
}
pub fn deserialize(bytes: &[u8]) -> Result<EncryptedVetKey, String> {
let ek_bytes: &[u8; Self::BYTES] = bytes.try_into().map_err(|_e: TryFromSliceError| {
format!("key not {} bytes but {}", Self::BYTES, bytes.len())
})?;
Self::deserialize_array(ek_bytes).map_err(|e| format!("{e:?}"))
}
pub fn deserialize_array(
val: &[u8; Self::BYTES],
) -> Result<Self, EncryptedVetKeyDeserializationError> {
let c1_bytes: &[u8; G1AFFINE_BYTES] = &val[..Self::C2_OFFSET]
.try_into()
.map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
let c2_bytes: &[u8; G2AFFINE_BYTES] = &val[Self::C2_OFFSET..Self::C3_OFFSET]
.try_into()
.map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
let c3_bytes: &[u8; G1AFFINE_BYTES] = &val[Self::C3_OFFSET..]
.try_into()
.map_err(|_e| EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey)?;
let c1 = G1Affine::from_compressed(c1_bytes).into_option();
let c2 = G2Affine::from_compressed(c2_bytes).into_option();
let c3 = G1Affine::from_compressed(c3_bytes).into_option();
match (c1, c2, c3) {
(Some(c1), Some(c2), Some(c3)) => Ok(Self { c1, c2, c3 }),
(_, _, _) => Err(EncryptedVetKeyDeserializationError::InvalidEncryptedVetKey),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IbeIdentity {
val: Vec<u8>,
}
impl IbeIdentity {
pub fn from_bytes(bytes: &[u8]) -> Self {
Self {
val: bytes.to_vec(),
}
}
pub fn from_string(str: &str) -> Self {
Self::from_bytes(str.as_bytes())
}
pub fn from_principal(principal: &candid::Principal) -> Self {
Self::from_bytes(principal.as_slice())
}
pub fn value(&self) -> &[u8] {
&self.val
}
}
const IBE_SEED_BYTES: usize = 32;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct IbeSeed {
val: Box<[u8; IBE_SEED_BYTES]>,
}
impl IbeSeed {
pub fn random<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
use rand::Rng;
Self {
val: Box::new(rng.gen::<[u8; IBE_SEED_BYTES]>()),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
if bytes.len() < 16 {
return Err("Insufficient input material for IbeSeed derivation".to_string());
}
let mut val = Box::new([0u8; IBE_SEED_BYTES]);
if bytes.len() == IBE_SEED_BYTES {
val.copy_from_slice(bytes)
} else {
let hkdf =
derive_symmetric_key(bytes, "ic-vetkd-bls12-381-ibe-hash-seed", IBE_SEED_BYTES);
val.copy_from_slice(&hkdf);
}
Ok(Self { val })
}
fn value(&self) -> &[u8; IBE_SEED_BYTES] {
&self.val
}
}
const IBE_HEADER: [u8; 8] = [b'I', b'C', b' ', b'I', b'B', b'E', 0x00, 0x01];
const IBE_HEADER_BYTES: usize = IBE_HEADER.len();
const IBE_OVERHEAD: usize = IBE_HEADER_BYTES + IBE_SEED_BYTES + G2AFFINE_BYTES;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IbeCiphertext {
header: Vec<u8>,
c1: G2Affine,
c2: [u8; IBE_SEED_BYTES],
c3: Vec<u8>,
}
enum IbeDomainSep {
HashToMask,
MaskSeed,
MaskMsg(usize),
}
impl IbeDomainSep {
#[allow(clippy::inherent_to_string)]
fn to_string(&self) -> String {
match self {
Self::HashToMask => "ic-vetkd-bls12-381-ibe-hash-to-mask".to_owned(),
Self::MaskSeed => "ic-vetkd-bls12-381-ibe-mask-seed".to_owned(),
Self::MaskMsg(len) => format!("ic-vetkd-bls12-381-ibe-mask-msg-{len:020}"),
}
}
}
impl IbeCiphertext {
pub fn serialize(&self) -> Vec<u8> {
let mut output = Vec::with_capacity(IBE_OVERHEAD + self.c3.len());
output.extend_from_slice(&self.header);
output.extend_from_slice(&self.c1.to_compressed());
output.extend_from_slice(&self.c2);
output.extend_from_slice(&self.c3);
output
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, String> {
if bytes.len() < IBE_OVERHEAD {
return Err("IbeCiphertext too short to be valid".to_string());
}
let header = bytes[0..IBE_HEADER_BYTES].to_vec();
let c1 = deserialize_g2(&bytes[IBE_HEADER_BYTES..(IBE_HEADER_BYTES + G2AFFINE_BYTES)])?;
let mut c2 = [0u8; IBE_SEED_BYTES];
c2.copy_from_slice(
&bytes[IBE_HEADER_BYTES + G2AFFINE_BYTES
..(IBE_HEADER_BYTES + G2AFFINE_BYTES + IBE_SEED_BYTES)],
);
let c3 = bytes[IBE_HEADER_BYTES + G2AFFINE_BYTES + IBE_SEED_BYTES..].to_vec();
if header != IBE_HEADER {
return Err("IbeCiphertext has unknown header".to_string());
}
Ok(Self { header, c1, c2, c3 })
}
fn hash_to_mask(header: &[u8], seed: &[u8; IBE_SEED_BYTES], msg: &[u8]) -> Scalar {
let domain_sep = IbeDomainSep::HashToMask;
let mut ro_input = Vec::with_capacity(seed.len() + msg.len());
ro_input.extend_from_slice(header);
ro_input.extend_from_slice(seed);
ro_input.extend_from_slice(msg);
hash_to_scalar(&ro_input, &domain_sep.to_string())
}
fn mask_seed(seed: &[u8; IBE_SEED_BYTES], t: &Gt) -> [u8; IBE_SEED_BYTES] {
let domain_sep = IbeDomainSep::MaskSeed;
let mask = derive_symmetric_key(&t.to_bytes(), &domain_sep.to_string(), IBE_SEED_BYTES);
let mut masked_seed = [0u8; IBE_SEED_BYTES];
for i in 0..IBE_SEED_BYTES {
masked_seed[i] = mask[i] ^ seed[i];
}
masked_seed
}
fn mask_msg(msg: &[u8], seed: &[u8; IBE_SEED_BYTES]) -> Vec<u8> {
fn derive_ibe_ctext_mask(seed: &[u8], msg_len: usize) -> Vec<u8> {
use sha3::{
digest::{ExtendableOutputReset, Update, XofReader},
Shake256,
};
let mut shake = Shake256::default();
shake.update(seed);
let mut xof = shake.finalize_xof_reset();
let mut mask = vec![0u8; msg_len];
xof.read(&mut mask);
mask
}
let domain_sep = IbeDomainSep::MaskMsg(msg.len());
let mut shake_seed = derive_symmetric_key(seed, &domain_sep.to_string(), IBE_SEED_BYTES);
let mut mask = derive_ibe_ctext_mask(&shake_seed, msg.len());
shake_seed.zeroize();
for i in 0..msg.len() {
mask[i] ^= msg[i];
}
mask
}
pub fn encrypt(
dpk: &DerivedPublicKey,
identity: &IbeIdentity,
msg: &[u8],
seed: &IbeSeed,
) -> Self {
let header = IBE_HEADER.to_vec();
let t = Self::hash_to_mask(&header, seed.value(), msg);
let pt = augmented_hash_to_g1(&dpk.point, identity.value());
let tsig = ic_bls12_381::pairing(&pt, &dpk.point) * t;
let c1 = G2_MUL_TABLE.mul(&t);
let c2 = Self::mask_seed(seed.value(), &tsig);
let c3 = Self::mask_msg(msg, seed.value());
Self { header, c1, c2, c3 }
}
pub fn decrypt(&self, vetkey: &VetKey) -> Result<Vec<u8>, String> {
let tsig = ic_bls12_381::pairing(vetkey.point(), &self.c1);
let seed = Self::mask_seed(&self.c2, &tsig);
let msg = Self::mask_msg(&self.c3, &seed);
let t = Self::hash_to_mask(&self.header, &seed, &msg);
let g_t = G2_MUL_TABLE.mul(&t);
if self.c1 == g_t {
Ok(msg)
} else {
Err("decryption failed".to_string())
}
}
pub fn ciphertext_size(plaintext_size: usize) -> usize {
plaintext_size + IBE_OVERHEAD
}
pub fn plaintext_size(ciphertext_size: usize) -> Option<usize> {
if ciphertext_size >= IBE_OVERHEAD {
Some(ciphertext_size - IBE_OVERHEAD)
} else {
None
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum InvalidVrfOutput {
UnexpectedLength,
InvalidData,
InvalidProof,
}
#[derive(Eq, PartialEq)]
pub struct VrfOutput {
proof: VetKey,
dpk: DerivedPublicKey,
output: [u8; Self::VRF_BYTES],
input: Vec<u8>,
}
impl VrfOutput {
pub const VRF_BYTES: usize = 32;
fn compute_vrf_hash(
vetkey: &VetKey,
dpk: &DerivedPublicKey,
input: &[u8],
) -> [u8; Self::VRF_BYTES] {
let mut ro_input =
Vec::with_capacity(G1AFFINE_BYTES + G2AFFINE_BYTES + input.len() + 3 * 8);
extend_with_length_prefix(&mut ro_input, vetkey.serialize());
extend_with_length_prefix(&mut ro_input, &dpk.serialize());
extend_with_length_prefix(&mut ro_input, input);
let mut output = [0u8; Self::VRF_BYTES];
hkdf(&mut output, &ro_input, "ic-vetkd-bls12-381-g2-vrf");
output
}
pub(crate) fn create(
proof: VetKey,
input: Vec<u8>,
dpk: DerivedPublicKey,
) -> Result<Self, InvalidVrfOutput> {
if !verify_bls_signature_pt(&dpk, &input, proof.point()) {
return Err(InvalidVrfOutput::InvalidProof);
}
let output = Self::compute_vrf_hash(&proof, &dpk, &input);
Ok(Self {
proof,
dpk,
output,
input,
})
}
pub fn serialize(&self) -> Vec<u8> {
let mut output = Vec::with_capacity(G1AFFINE_BYTES + G2AFFINE_BYTES + self.input.len());
output.extend_from_slice(self.proof.serialize());
output.extend_from_slice(&self.dpk.serialize());
output.extend_from_slice(&self.input);
output
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, InvalidVrfOutput> {
if bytes.len() < G1AFFINE_BYTES + G2AFFINE_BYTES {
return Err(InvalidVrfOutput::UnexpectedLength);
}
let proof = VetKey::deserialize(&bytes[0..G1AFFINE_BYTES])
.map_err(|_| InvalidVrfOutput::InvalidData)?;
let dpk =
DerivedPublicKey::deserialize(&bytes[G1AFFINE_BYTES..G1AFFINE_BYTES + G2AFFINE_BYTES])
.map_err(|_| InvalidVrfOutput::InvalidData)?;
let input = bytes[G1AFFINE_BYTES + G2AFFINE_BYTES..].to_vec();
if !verify_bls_signature_pt(&dpk, &input, proof.point()) {
return Err(InvalidVrfOutput::InvalidProof);
}
let output = Self::compute_vrf_hash(&proof, &dpk, &input);
Ok(Self {
proof,
dpk,
output,
input,
})
}
pub fn input(&self) -> &[u8] {
&self.input
}
pub fn public_key(&self) -> &DerivedPublicKey {
&self.dpk
}
pub fn output(&self) -> &[u8; Self::VRF_BYTES] {
&self.output
}
}
pub fn verify_bls_signature(dpk: &DerivedPublicKey, input: &[u8], signature: &[u8]) -> bool {
let signature: G1Affine = match <[u8; 48]>::try_from(signature) {
Ok(bytes) => match G1Affine::from_compressed(&bytes).into_option() {
Some(pt) => pt,
None => return false,
},
Err(_) => return false,
};
verify_bls_signature_pt(dpk, input, &signature)
}
fn verify_bls_signature_pt(dpk: &DerivedPublicKey, input: &[u8], signature: &G1Affine) -> bool {
if dpk.point.is_identity().into() {
return false;
}
let msg = augmented_hash_to_g1(&dpk.point, input);
let dpk_prep = G2Prepared::from(dpk.point);
use pairing::group::Group;
let is_valid =
gt_multipairing(&[(signature, &G2PREPARED_NEG_G), (&msg, &dpk_prep)]).is_identity();
bool::from(is_valid)
}
fn augmented_hash_to_g1(pk: &G2Affine, data: &[u8]) -> G1Affine {
let domain_sep = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_";
let mut signature_input = Vec::with_capacity(G2AFFINE_BYTES + data.len());
signature_input.extend_from_slice(&pk.to_compressed());
signature_input.extend_from_slice(data);
let pt = <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
signature_input,
domain_sep,
);
G1Affine::from(pt)
}
fn gt_multipairing(terms: &[(&G1Affine, &G2Prepared)]) -> Gt {
ic_bls12_381::multi_miller_loop(terms).final_exponentiation()
}
fn deserialize_g2(bytes: &[u8]) -> Result<G2Affine, String> {
let bytes: &[u8; G2AFFINE_BYTES] = bytes
.try_into()
.map_err(|_| "Invalid length for G2".to_string())?;
let pt = G2Affine::from_compressed(bytes);
if bool::from(pt.is_some()) {
Ok(pt.unwrap())
} else {
Err("Invalid G2 elliptic curve point".to_string())
}
}
pub mod management_canister {
use ic_cdk::call::CallResult;
use ic_cdk_management_canister::VetKDPublicKeyArgs;
use crate::types::CanisterId;
use super::*;
async fn derive_unencrypted_vetkey(
input: Vec<u8>,
context: Vec<u8>,
key_id: VetKDKeyId,
) -> Result<Vec<u8>, VetKDDeriveKeyCallError> {
if key_id.curve != VetKDCurve::Bls12_381_G2 {
return Err(VetKDDeriveKeyCallError::UnsupportedCurve);
}
let request = VetKDDeriveKeyArgs {
input,
context,
key_id,
transport_public_key: G1Affine::identity().to_compressed().to_vec(),
};
let reply = ic_cdk_management_canister::vetkd_derive_key(&request)
.await
.map_err(VetKDDeriveKeyCallError::CallFailed)?;
if reply.encrypted_key.len() != EncryptedVetKey::BYTES {
return Err(VetKDDeriveKeyCallError::InvalidReply);
}
Ok(reply.encrypted_key
[EncryptedVetKey::C3_OFFSET..EncryptedVetKey::C3_OFFSET + G1AFFINE_BYTES]
.to_vec())
}
#[derive(Debug)]
pub enum VetKDDeriveKeyCallError {
UnsupportedCurve,
CallFailed(ic_cdk_management_canister::SignCallError),
InvalidReply,
}
pub async fn sign_with_bls(
message: Vec<u8>,
context: Vec<u8>,
key_id: VetKDKeyId,
) -> Result<Vec<u8>, VetKDDeriveKeyCallError> {
derive_unencrypted_vetkey(message, context, key_id).await
}
pub async fn bls_public_key(
canister_id: Option<CanisterId>,
context: Vec<u8>,
key_id: VetKDKeyId,
) -> CallResult<Vec<u8>> {
ic_cdk_management_canister::vetkd_public_key(&VetKDPublicKeyArgs {
canister_id,
context,
key_id,
})
.await
.map(|r| r.public_key)
}
pub async fn compute_vrf(
input: Vec<u8>,
context: Vec<u8>,
key_id: VetKDKeyId,
) -> Result<VrfOutput, VetKDDeriveKeyCallError> {
let vetkey_bytes =
derive_unencrypted_vetkey(input.clone(), context.clone(), key_id.clone())
.await
.map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
let vetkey = VetKey::deserialize(&vetkey_bytes)
.map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
let canister_id = ic_cdk::api::canister_self();
let dpk = match MasterPublicKey::for_mainnet_key(&key_id) {
Some(mk) => mk
.derive_canister_key(canister_id.as_slice())
.derive_sub_key(&context),
None => {
let dpk_bytes = ic_cdk_management_canister::vetkd_public_key(&VetKDPublicKeyArgs {
canister_id: Some(canister_id),
context,
key_id,
})
.await
.map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?;
DerivedPublicKey::deserialize(&dpk_bytes.public_key)
.map_err(|_| VetKDDeriveKeyCallError::InvalidReply)?
}
};
VrfOutput::create(vetkey, input, dpk).map_err(|_| VetKDDeriveKeyCallError::InvalidReply)
}
}