#![doc = include_str!("../README.md")]
pub mod kdf;
pub use bls12_381_plus::elliptic_curve::ops::MulByGenerator;
pub use bls12_381_plus::group::{Group, GroupEncoding};
pub use bls12_381_plus::G1Projective as G1;
pub use bls12_381_plus::G2Projective as G2;
pub use bls12_381_plus::Scalar;
pub use secrecy::zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
pub use secrecy::{ExposeSecret, Secret};
use thiserror::Error;
#[cfg(doctest)]
pub struct ReadmeDoctests;
pub struct Manager<T: GroupEncoding> {
master_sk: Scalar,
pub master_pk: T,
}
impl<T: GroupEncoding + MulByGenerator + Group<Scalar = Scalar>> Manager<T> {
fn new(master_sk: Scalar) -> Self {
let master_pk: T = T::mul_by_generator(&master_sk);
Self {
master_sk,
master_pk,
}
}
pub fn from_seed(seed: impl AsRef<[u8]> + Zeroize + ZeroizeOnDrop) -> Self {
let master_sk: Scalar =
kdf::derive_master_sk(seed.as_ref()).expect("Seed has length of 32 bytes");
Self::new(master_sk)
}
pub fn account(&self, index: u32) -> Account<T> {
let derived_sk: Scalar = kdf::ckd_sk_hardened(&self.master_sk, index);
let derived_pk = T::mul_by_generator(&derived_sk);
Account {
index,
sk: Secret::new(derived_sk),
pk: derived_pk,
}
}
}
pub struct Account<T: GroupEncoding + MulByGenerator + Group<Scalar = Scalar>> {
pub index: u32,
sk: Secret<Scalar>,
pub pk: T,
}
impl<T: GroupEncoding + MulByGenerator + Group<Scalar = Scalar>> Account<T> {
pub fn new(index: u32, sk: Scalar, pk: T) -> Self {
Self {
index,
sk: Secret::new(sk),
pk,
}
}
pub fn expand_to(&self, length: u8) -> Expanded<T> {
let sk = Secret::new(
(0..length)
.map(|i| kdf::ckd_sk_normal::<T>(self.sk.expose_secret(), i as u32))
.collect::<Vec<Scalar>>(),
);
let pk = derive(&self.pk, length);
Expanded { sk, pk }
}
}
pub struct Expanded<T: GroupEncoding + MulByGenerator + Group<Scalar = Scalar>> {
pub sk: Secret<Vec<Scalar>>,
pub pk: Vec<T>,
}
pub fn derive<T: GroupEncoding + Group<Scalar = Scalar> + MulByGenerator>(
pk: &T,
length: u8,
) -> Vec<T> {
(0..length)
.map(|i| kdf::ckd_pk_normal::<T>(pk, i as u32))
.collect::<Vec<T>>()
}
#[cfg(test)]
mod basic_test {
use secrecy::zeroize::Zeroizing;
use super::*;
#[test]
fn smoke() {
let seed = Zeroizing::new([69u8; 32]);
let manager: Manager<G2> = Manager::from_seed(seed);
let pk2 = G2::mul_by_generator(&manager.master_sk);
assert_eq!(manager.master_pk, pk2);
println!(
"master_pk [{}]: compressed: [{:?}]",
manager.master_pk.to_bytes().as_ref().len(),
manager.master_pk.to_bytes().as_ref().len()
);
println!("master_sk [{}]", manager.master_sk);
let purpose = 1u32; let length = 8u8;
let account = manager.account(purpose);
let child = account.expand_to(length);
let hardened_child_sk = kdf::ckd_sk_hardened(&manager.master_sk, purpose);
assert_eq!(account.sk.expose_secret(), &hardened_child_sk);
assert_eq!(child.sk.expose_secret().len(), length as usize);
assert_eq!(child.pk.len(), length as usize);
let child_account = derive(&account.pk, length);
for (i, sk) in child.sk.expose_secret().iter().enumerate() {
let normal_pk = G2::mul_by_generator(sk);
assert_eq!(normal_pk, child.pk[i]);
assert_eq!(normal_pk, child_account[i]);
}
}
}