solana/chacha/src/chacha.rs

186 lines
5.7 KiB
Rust

use solana_ledger::blockstore::Blockstore;
use solana_sdk::clock::Slot;
use std::fs::File;
use std::io;
use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::Arc;
pub use solana_chacha_sys::chacha_cbc_encrypt;
pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32;
pub fn chacha_cbc_encrypt_ledger(
blockstore: &Arc<Blockstore>,
start_slot: Slot,
slots_per_segment: u64,
out_path: &Path,
ivec: &mut [u8; CHACHA_BLOCK_SIZE],
) -> io::Result<usize> {
let mut out_file =
BufWriter::new(File::create(out_path).expect("Can't open ledger encrypted data file"));
const BUFFER_SIZE: usize = 8 * 1024;
let mut buffer = [0; BUFFER_SIZE];
let mut encrypted_buffer = [0; BUFFER_SIZE];
let key = [0; CHACHA_KEY_SIZE];
let mut total_size = 0;
let mut current_slot = start_slot;
let mut start_index = 0;
loop {
match blockstore.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
Ok((last_index, mut size)) => {
debug!(
"chacha: encrypting slice: {} num_shreds: {} data_len: {}",
current_slot,
last_index.saturating_sub(start_index),
size
);
debug!("read {} bytes", size);
if size == 0 {
if current_slot.saturating_sub(start_slot) < slots_per_segment {
current_slot += 1;
start_index = 0;
continue;
} else {
break;
}
}
if size < BUFFER_SIZE {
// round to the nearest key_size boundary
size = (size + CHACHA_KEY_SIZE - 1) & !(CHACHA_KEY_SIZE - 1);
}
total_size += size;
chacha_cbc_encrypt(&buffer[..size], &mut encrypted_buffer[..size], &key, ivec);
if let Err(res) = out_file.write(&encrypted_buffer[..size]) {
warn!("Error writing file! {:?}", res);
return Err(res);
}
start_index = last_index + 1;
}
Err(e) => {
info!("Error encrypting file: {:?}", e);
break;
}
}
}
Ok(total_size)
}
#[cfg(test)]
mod tests {
use crate::chacha::chacha_cbc_encrypt_ledger;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use solana_ledger::blockstore::Blockstore;
use solana_ledger::entry::Entry;
use solana_ledger::get_tmp_ledger_path;
use solana_sdk::hash::{hash, Hash, Hasher};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use std::fs::remove_file;
use std::fs::File;
use std::io::Read;
use std::sync::Arc;
fn make_tiny_deterministic_test_entries(num: usize) -> Vec<Entry> {
let zero = Hash::default();
let one = hash(&zero.as_ref());
let seed = [2u8; 32];
let mut generator = ChaChaRng::from_seed(seed);
let keypair = Keypair::generate(&mut generator);
let mut id = one;
let mut num_hashes = 0;
(0..num)
.map(|_| {
Entry::new_mut(
&mut id,
&mut num_hashes,
vec![system_transaction::transfer(
&keypair,
&keypair.pubkey(),
1,
one,
)],
)
})
.collect()
}
use std::{env, fs::create_dir_all, path::PathBuf};
fn tmp_file_path(name: &str) -> PathBuf {
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let mut path = PathBuf::new();
path.push(out_dir);
path.push("tmp");
create_dir_all(&path).unwrap();
path.push(format!("{}-{}", name, Pubkey::new_rand()));
path
}
#[test]
fn test_encrypt_ledger() {
solana_logger::setup();
let ledger_path = get_tmp_ledger_path!();
let ticks_per_slot = 16;
let slots_per_segment = 32;
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
let out_path = tmp_file_path("test_encrypt_ledger");
let seed = [2u8; 32];
let mut generator = ChaChaRng::from_seed(seed);
let keypair = Keypair::generate(&mut generator);
let entries = make_tiny_deterministic_test_entries(slots_per_segment);
blockstore
.write_entries(
0,
0,
0,
ticks_per_slot,
None,
true,
&Arc::new(keypair),
entries,
0,
)
.unwrap();
let mut key = hex!(
"abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234
abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234"
);
chacha_cbc_encrypt_ledger(
&blockstore,
0,
slots_per_segment as u64,
&out_path,
&mut key,
)
.unwrap();
let mut out_file = File::open(&out_path).unwrap();
let mut buf = vec![];
let size = out_file.read_to_end(&mut buf).unwrap();
let mut hasher = Hasher::default();
hasher.hash(&buf[..size]);
// golden needs to be updated if shred structure changes....
let golden: Hash = "2rq8nR6rns2T5zsbQAGBDZb41NVtacneLgkCH17CVxZm"
.parse()
.unwrap();
assert_eq!(hasher.result(), golden);
remove_file(&out_path).unwrap();
}
}