use std::collections::HashMap;
use std::io::Cursor;
use std::ops::Range;
use crate::consensus::encode::{Encodable, VarInt};
use crate::cryptonote::hash;
use crate::cryptonote::subaddress::{self, get_spend_secret_key, Index};
use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair};
pub const MONERO_MUL_FACTOR: u8 = 8;
#[derive(Debug, Clone)]
pub struct KeyGenerator {
pub spend: PublicKey,
pub rv: PublicKey,
}
impl KeyGenerator {
pub fn from_random(view: PublicKey, spend: PublicKey, random: PrivateKey) -> Self {
let rv = random * MONERO_MUL_FACTOR * &view;
KeyGenerator { spend, rv }
}
pub fn from_key(keys: &ViewPair, random: PublicKey) -> Self {
let rv = keys.view * MONERO_MUL_FACTOR * &random;
KeyGenerator {
spend: keys.spend,
rv,
}
}
pub fn one_time_key(&self, index: usize) -> PublicKey {
PublicKey::from_private_key(&self.get_rvn_scalar(index)) + self.spend
}
pub fn check(&self, index: usize, key: PublicKey) -> bool {
key == self.one_time_key(index)
}
pub fn get_rvn_scalar(&self, index: usize) -> PrivateKey {
let mut encoder = Cursor::new(vec![]);
self.rv.consensus_encode(&mut encoder).unwrap();
VarInt(index as u64).consensus_encode(&mut encoder).unwrap();
hash::Hash::hash_to_scalar(&encoder.into_inner())
}
}
#[derive(Debug, Clone)]
pub struct SubKeyChecker<'a> {
pub table: HashMap<PublicKey, Index>,
pub keys: &'a ViewPair,
}
impl<'a> SubKeyChecker<'a> {
pub fn new(keys: &'a ViewPair, major: Range<u32>, minor: Range<u32>) -> Self {
let mut table = HashMap::new();
major.for_each(|maj| {
minor.clone().for_each(|min| {
let index = Index {
major: maj,
minor: min,
};
let spend = subaddress::get_spend_public_key(keys, index);
table.insert(spend, index);
});
});
SubKeyChecker { table, keys }
}
pub fn check(&self, index: usize, key: &PublicKey, tx_pubkey: &PublicKey) -> Option<&Index> {
let keygen = KeyGenerator::from_key(self.keys, *tx_pubkey);
self.table
.get(&(key - PublicKey::from_private_key(&keygen.get_rvn_scalar(index))))
}
}
#[derive(Debug, Clone)]
pub struct KeyRecoverer<'a> {
pub keys: &'a KeyPair,
pub checker: KeyGenerator,
}
impl<'a> KeyRecoverer<'a> {
pub fn new(keys: &'a KeyPair, tx_pubkey: PublicKey) -> Self {
let viewpair = keys.into();
let checker = KeyGenerator::from_key(&viewpair, tx_pubkey);
KeyRecoverer { keys, checker }
}
pub fn recover(&self, oindex: usize, aindex: Index) -> PrivateKey {
let scal = self.checker.get_rvn_scalar(oindex);
let s = get_spend_secret_key(self.keys, aindex);
scal + s
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::{KeyGenerator, KeyRecoverer, SubKeyChecker};
use crate::cryptonote::subaddress::Index;
use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair};
#[test]
fn one_time_key_generator() {
let secret_view = PrivateKey::from_str(
"bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07",
)
.unwrap();
let secret_spend = PrivateKey::from_str(
"e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907",
)
.unwrap();
let public_spend = PublicKey::from_private_key(&secret_spend);
let viewpair = ViewPair {
view: secret_view,
spend: public_spend,
};
let one_time_pk =
PublicKey::from_str("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6")
.unwrap();
let tx_pubkey =
PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1")
.unwrap();
let generator = KeyGenerator::from_key(&viewpair, tx_pubkey);
assert_eq!(false, generator.check(0, one_time_pk));
assert_eq!(true, generator.check(1, one_time_pk));
assert_eq!(false, generator.check(2, one_time_pk));
}
#[test]
fn one_time_key_recover() {
let secret_view = PrivateKey::from_str(
"bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07",
)
.unwrap();
let secret_spend = PrivateKey::from_str(
"e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907",
)
.unwrap();
let keypair = KeyPair {
view: secret_view,
spend: secret_spend,
};
let one_time_sk = PrivateKey::from_str(
"afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e",
)
.unwrap();
let one_time_pk = PublicKey::from_private_key(&one_time_sk);
assert_eq!(
"e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6",
one_time_pk.to_string()
);
let tx_pubkey =
PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1")
.unwrap();
let index = 1;
let sub_index = Index::default();
let recoverer = KeyRecoverer::new(&keypair, tx_pubkey);
let rec_one_time_sk = recoverer.recover(index, sub_index);
assert_eq!(
"afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e",
rec_one_time_sk.to_string()
);
assert_eq!(one_time_pk, PublicKey::from_private_key(&rec_one_time_sk));
}
#[test]
fn one_time_subkey_recover() {
let secret_view = PrivateKey::from_str(
"bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07",
)
.unwrap();
let secret_spend = PrivateKey::from_str(
"e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907",
)
.unwrap();
let keypair = KeyPair {
view: secret_view,
spend: secret_spend,
};
let one_time_sk = PrivateKey::from_str(
"9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01",
)
.unwrap();
let one_time_pk = PublicKey::from_private_key(&one_time_sk);
assert_eq!(
"b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899",
one_time_pk.to_string()
);
let tx_pubkey =
PublicKey::from_str("d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9")
.unwrap();
let index = 1;
let sub_index = Index { major: 0, minor: 1 };
let recoverer = KeyRecoverer::new(&keypair, tx_pubkey);
let rec_one_time_sk = recoverer.recover(index, sub_index);
assert_eq!(
"9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01",
rec_one_time_sk.to_string()
);
assert_eq!(one_time_pk, PublicKey::from_private_key(&rec_one_time_sk));
}
#[test]
fn one_time_key_checker() {
let secret_view = PrivateKey::from_str(
"bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07",
)
.unwrap();
let secret_spend = PrivateKey::from_str(
"e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907",
)
.unwrap();
let public_spend = PublicKey::from_private_key(&secret_spend);
let viewpair = ViewPair {
view: secret_view,
spend: public_spend,
};
let one_time_pk =
PublicKey::from_str("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6")
.unwrap();
let tx_pubkey =
PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1")
.unwrap();
let checker = SubKeyChecker::new(&viewpair, 0..3, 0..3);
assert_eq!(None, checker.check(0, &one_time_pk, &tx_pubkey));
assert_eq!(
Some(&Index { major: 0, minor: 0 }),
checker.check(1, &one_time_pk, &tx_pubkey)
);
assert_eq!(None, checker.check(2, &one_time_pk, &tx_pubkey));
}
#[test]
fn one_time_subkey_checker() {
let secret_view = PrivateKey::from_str(
"bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07",
)
.unwrap();
let secret_spend = PrivateKey::from_str(
"e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907",
)
.unwrap();
let public_spend = PublicKey::from_private_key(&secret_spend);
let viewpair = ViewPair {
view: secret_view,
spend: public_spend,
};
let one_time_pk =
PublicKey::from_str("b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899")
.unwrap();
let tx_pubkey =
PublicKey::from_str("d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9")
.unwrap();
let checker = SubKeyChecker::new(&viewpair, 0..3, 0..3);
assert_eq!(None, checker.check(0, &one_time_pk, &tx_pubkey));
assert_eq!(
Some(&Index { major: 0, minor: 1 }),
checker.check(1, &one_time_pk, &tx_pubkey)
);
assert_eq!(None, checker.check(2, &one_time_pk, &tx_pubkey));
}
}