use bitcoin::{
bip32::{ChildNumber, Xpriv},
hashes::{sha256, Hash, HashEngine},
key::Secp256k1,
secp256k1::{All, PublicKey, SecretKey},
Network,
};
use crate::error::{InvalidLeafError, SparkCryptographyError};
pub const SPARK_DERIVATION_PATH_PURPOSE: u32 = 8797555;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SparkDerivationPath(pub(crate) Vec<ChildNumber>);
impl std::ops::Deref for SparkDerivationPath {
type Target = Vec<ChildNumber>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SparkKeyType {
Identity,
BaseSigning,
Deposit,
}
pub fn get_secret_key_with_key_type(
seed: &[u8],
account_index: u32,
network: Network,
key_type: SparkKeyType,
secp: &Secp256k1<All>,
) -> Result<SecretKey, SparkCryptographyError> {
let seed = Xpriv::new_master(network, seed).map_err(|_| SparkCryptographyError::InvalidSeed)?;
let identity_derivation_path = get_derivation_path_with_key_type(account_index, key_type)?;
let identity_key = seed
.derive_priv(secp, &*identity_derivation_path)
.map_err(|_| {
SparkCryptographyError::InvalidDerivationPath(format!("{:?}", identity_derivation_path))
})?;
Ok(identity_key.private_key)
}
pub fn get_public_key_with_key_type(
seed: &[u8],
account_index: u32,
network: Network,
key_type: SparkKeyType,
secp: &Secp256k1<All>,
) -> Result<PublicKey, SparkCryptographyError> {
let identity_secret_key =
get_secret_key_with_key_type(seed, account_index, network, key_type, secp)?;
Ok(identity_secret_key.public_key(secp))
}
pub fn get_leaf_index(leaf_id: &str) -> Result<ChildNumber, SparkCryptographyError> {
let mut engine = sha256::Hash::engine();
engine.input(leaf_id.as_bytes());
let hash = sha256::Hash::from_engine(engine);
let chunk = &hash[0..4];
let amount = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) % 0x80000000;
get_child_number(amount, true)
}
pub fn get_child_number(index: u32, hardened: bool) -> Result<ChildNumber, SparkCryptographyError> {
if index > 0x7FFFFFFF {
return Err(SparkCryptographyError::InvalidLeaf(
InvalidLeafError::CannotDeriveChildNumberFromIndex(index),
));
}
let child_number = if hardened {
ChildNumber::from_hardened_idx(index).unwrap()
} else {
ChildNumber::from_normal_idx(index).unwrap()
};
Ok(child_number)
}
pub fn derive_spark_key(
leaf_id: Option<String>,
account: u32,
seed_bytes: &[u8],
key_type: SparkKeyType,
network: Network,
) -> Result<SecretKey, SparkCryptographyError> {
let path = prepare_derivation_path(account, key_type, leaf_id)?;
let seed = match Xpriv::new_master(network, seed_bytes) {
Ok(seed) => seed,
Err(_) => return Err(SparkCryptographyError::InvalidSeed),
};
let secp = bitcoin::secp256k1::Secp256k1::new();
let extended_key = seed
.derive_priv(&secp, &path)
.map_err(|_| SparkCryptographyError::InvalidDerivationPath(format!("{:?}", path)))?;
Ok(extended_key.private_key)
}
fn get_key_type_index(key_type: SparkKeyType) -> Result<ChildNumber, SparkCryptographyError> {
let index = match key_type {
SparkKeyType::Identity => 0,
SparkKeyType::BaseSigning => 1,
SparkKeyType::Deposit => 2,
};
get_child_number(index, true)
}
fn prepare_derivation_path(
account_index: u32,
key_type: SparkKeyType,
leaf_index: Option<String>,
) -> Result<Vec<ChildNumber>, SparkCryptographyError> {
let purpose_index = get_child_number(SPARK_DERIVATION_PATH_PURPOSE, true)?;
let account_index = get_child_number(account_index, true)?;
let key_type_index = get_key_type_index(key_type)?;
let leaf_index = if let Some(leaf_id) = leaf_index {
Some(get_leaf_index(leaf_id.as_str())?)
} else {
None
};
Ok(prepare_path(
purpose_index,
account_index,
key_type_index,
leaf_index,
))
}
fn prepare_path(
purpose_index: ChildNumber,
account_index: ChildNumber,
key_type_index: ChildNumber,
leaf_index: Option<ChildNumber>,
) -> Vec<ChildNumber> {
let mut path = vec![purpose_index, account_index, key_type_index];
if let Some(leaf_index) = leaf_index {
path.push(leaf_index);
}
path
}
pub fn get_derivation_path_with_key_type(
account_index: u32,
key_type: SparkKeyType,
) -> Result<SparkDerivationPath, SparkCryptographyError> {
let path = prepare_derivation_path(account_index, key_type, None)?;
Ok(SparkDerivationPath(path))
}
#[cfg(test)]
mod derivation_path_tests {
use super::*;
use bitcoin::Network;
#[test]
fn test_get_leaf_index() {
let leaf_id_1 = "019534f0-f4e2-7845-87fe-c6ea2fa69f80";
let leaf_id_2 = "019534f0-f4e2-7868-b3fa-d06dc10b79e7";
let leaf_id_3 = "dbb5c090-dca4-47ec-9f20-41edd4594dcf";
let child_number_1 = get_leaf_index(leaf_id_1).unwrap();
let child_number_2 = get_leaf_index(leaf_id_2).unwrap();
let child_number_3 = get_leaf_index(leaf_id_3).unwrap();
assert_eq!(
child_number_1,
ChildNumber::from_hardened_idx(1137822116).unwrap()
);
assert_eq!(
child_number_2,
ChildNumber::from_hardened_idx(1199130649).unwrap()
);
assert_eq!(
child_number_3,
ChildNumber::from_hardened_idx(1743780874).unwrap()
);
}
#[test]
fn test_get_identity_derivation_path() {
let path = get_derivation_path_with_key_type(0, SparkKeyType::Identity).unwrap();
assert_eq!(path.0.len(), 3);
let path_account_1 = get_derivation_path_with_key_type(1, SparkKeyType::Identity).unwrap();
let path_account_2 = get_derivation_path_with_key_type(2, SparkKeyType::Identity).unwrap();
assert_eq!(path_account_1.0.len(), 3);
assert_eq!(path_account_2.0.len(), 3);
assert_eq!(
path.0[0],
ChildNumber::from_hardened_idx(SPARK_DERIVATION_PATH_PURPOSE).unwrap()
);
assert_eq!(path.0[1], ChildNumber::from_hardened_idx(0).unwrap());
assert_eq!(path.0[2], ChildNumber::from_hardened_idx(0).unwrap()); }
#[test]
fn test_derive_spark_key() {
let seed_bytes = b"0000000000000000000000000000000000000000000000000000000000000000";
let network = Network::Bitcoin;
let secp = Secp256k1::new();
let identity_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::Identity, network).unwrap();
let identity_pubkey = identity_key.public_key(&secp);
let base_signing_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::BaseSigning, network).unwrap();
let base_signing_pubkey = base_signing_key.public_key(&secp);
let deposit_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::Deposit, network).unwrap();
let deposit_pubkey = deposit_key.public_key(&secp);
assert_ne!(identity_pubkey, base_signing_pubkey);
assert_ne!(identity_pubkey, deposit_pubkey);
assert_ne!(base_signing_pubkey, deposit_pubkey);
}
#[test]
fn test_derive_spark_key_with_leaf() {
let seed_bytes = b"0000000000000000000000000000000000000000000000000000000000000000";
let network = Network::Bitcoin;
let leaf_id = "test-leaf-id";
let key_with_leaf = derive_spark_key(
Some(leaf_id.to_string()),
0,
seed_bytes,
SparkKeyType::BaseSigning,
network,
)
.unwrap();
let key_without_leaf =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::BaseSigning, network).unwrap();
assert_ne!(key_with_leaf, key_without_leaf);
}
#[test]
fn test_get_child_number() {
assert_eq!(2147483648, 0x7FFFFFFF_u32 + 1);
assert!(get_child_number(0, true).is_ok());
assert!(get_child_number(0x7FFFFFFF, true).is_ok());
assert!(get_child_number(0, false).is_ok());
assert!(get_child_number(0x7FFFFFFF, false).is_ok());
assert_eq!(
get_child_number(0x80000000, false).unwrap_err(),
SparkCryptographyError::InvalidLeaf(
InvalidLeafError::CannotDeriveChildNumberFromIndex(0x80000000)
)
);
}
#[test]
fn test_get_key_type_index() {
let identity_idx = get_key_type_index(SparkKeyType::Identity).unwrap();
let base_signing_idx = get_key_type_index(SparkKeyType::BaseSigning).unwrap();
let deposit_idx = get_key_type_index(SparkKeyType::Deposit).unwrap();
assert_eq!(identity_idx, ChildNumber::from_hardened_idx(0).unwrap());
assert_eq!(base_signing_idx, ChildNumber::from_hardened_idx(1).unwrap());
assert_eq!(deposit_idx, ChildNumber::from_hardened_idx(2).unwrap());
}
#[test]
fn test_network_specific() {
let seed_bytes = b"0000000000000000000000000000000000000000000000000000000000000000";
let secp = Secp256k1::new();
let bitcoin_key = derive_spark_key(
None,
0,
seed_bytes,
SparkKeyType::Identity,
Network::Bitcoin,
)
.unwrap();
let testnet_key = derive_spark_key(
None,
0,
seed_bytes,
SparkKeyType::Identity,
Network::Testnet,
)
.unwrap();
let regtest_key = derive_spark_key(
None,
0,
seed_bytes,
SparkKeyType::Identity,
Network::Regtest,
)
.unwrap();
let signet_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::Identity, Network::Signet).unwrap();
assert_eq!(bitcoin_key.public_key(&secp), testnet_key.public_key(&secp));
assert_eq!(bitcoin_key.public_key(&secp), regtest_key.public_key(&secp));
assert_eq!(bitcoin_key.public_key(&secp), signet_key.public_key(&secp));
}
#[test]
fn test_get_public_and_secret_keys_with_key_type() {
let seed_bytes = b"0000000000000000000000000000000000000000000000000000000000000000";
let network = Network::Bitcoin;
let secp = Secp256k1::new();
let identity_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::Identity, network).unwrap();
let identity_pubkey = identity_key.public_key(&secp);
let base_signing_key =
derive_spark_key(None, 0, seed_bytes, SparkKeyType::BaseSigning, network).unwrap();
let base_signing_pubkey = base_signing_key.public_key(&secp);
let identity_secret_key =
get_secret_key_with_key_type(seed_bytes, 0, network, SparkKeyType::Identity, &secp)
.unwrap();
assert_eq!(identity_secret_key, identity_key);
let identity_public_key =
get_public_key_with_key_type(seed_bytes, 0, network, SparkKeyType::Identity, &secp)
.unwrap();
assert_eq!(identity_public_key, identity_pubkey);
let base_signing_secret_key =
get_secret_key_with_key_type(seed_bytes, 0, network, SparkKeyType::BaseSigning, &secp)
.unwrap();
assert_eq!(base_signing_secret_key, base_signing_key);
let base_signing_public_key =
get_public_key_with_key_type(seed_bytes, 0, network, SparkKeyType::BaseSigning, &secp)
.unwrap();
assert_eq!(base_signing_public_key, base_signing_pubkey);
}
}