subspace_farmer/single_disk_farm/
identity.rsuse parity_scale_codec::{Decode, Encode};
use schnorrkel::context::SigningContext;
use schnorrkel::{ExpansionMode, Keypair, PublicKey, SecretKey, Signature};
use std::ops::Deref;
use std::path::Path;
use std::{fmt, fs, io};
use subspace_core_primitives::REWARD_SIGNING_CONTEXT;
use substrate_bip39::mini_secret_from_entropy;
use thiserror::Error;
use tracing::debug;
use zeroize::Zeroizing;
const ENTROPY_LENGTH: usize = 32;
#[derive(Debug, Encode, Decode)]
struct IdentityFileContents {
entropy: Vec<u8>,
}
fn keypair_from_entropy(entropy: &[u8]) -> Keypair {
mini_secret_from_entropy(entropy, "")
.expect("32 bytes can always build a key; qed")
.expand_to_keypair(ExpansionMode::Ed25519)
}
#[derive(Debug, Error)]
pub enum IdentityError {
#[error("Identity I/O error: {0}")]
Io(#[from] io::Error),
#[error("Invalid contents")]
InvalidContents,
#[error("Decoding error: {0}")]
Decoding(#[from] parity_scale_codec::Error),
}
#[derive(Clone)]
pub struct Identity {
keypair: Zeroizing<Keypair>,
entropy: Zeroizing<Vec<u8>>,
substrate_ctx: SigningContext,
}
impl fmt::Debug for Identity {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Identity")
.field("keypair", &self.keypair)
.finish_non_exhaustive()
}
}
impl Deref for Identity {
type Target = Keypair;
#[inline]
fn deref(&self) -> &Self::Target {
&self.keypair
}
}
impl Identity {
pub(crate) const FILE_NAME: &'static str = "identity.bin";
pub fn file_size() -> usize {
IdentityFileContents {
entropy: vec![0; ENTROPY_LENGTH],
}
.encoded_size()
}
pub fn open_or_create<B: AsRef<Path>>(base_directory: B) -> Result<Self, IdentityError> {
if let Some(identity) = Self::open(base_directory.as_ref())? {
Ok(identity)
} else {
Self::create(base_directory)
}
}
pub fn open<B: AsRef<Path>>(base_directory: B) -> Result<Option<Self>, IdentityError> {
let identity_file = base_directory.as_ref().join(Self::FILE_NAME);
if identity_file.exists() {
debug!("Opening existing keypair");
let bytes = Zeroizing::new(fs::read(identity_file)?);
let IdentityFileContents { entropy } =
IdentityFileContents::decode(&mut bytes.as_ref())?;
if entropy.len() != ENTROPY_LENGTH {
return Err(IdentityError::InvalidContents);
}
Ok(Some(Self {
keypair: Zeroizing::new(keypair_from_entropy(&entropy)),
entropy: Zeroizing::new(entropy),
substrate_ctx: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
}))
} else {
debug!("Existing keypair not found");
Ok(None)
}
}
pub fn create<B: AsRef<Path>>(base_directory: B) -> Result<Self, IdentityError> {
let identity_file = base_directory.as_ref().join(Self::FILE_NAME);
debug!("Generating new keypair");
let entropy = rand::random::<[u8; ENTROPY_LENGTH]>().to_vec();
let identity_file_contents = IdentityFileContents { entropy };
fs::write(identity_file, identity_file_contents.encode())?;
let IdentityFileContents { entropy } = identity_file_contents;
Ok(Self {
keypair: Zeroizing::new(keypair_from_entropy(&entropy)),
entropy: Zeroizing::new(entropy),
substrate_ctx: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
})
}
pub fn public_key(&self) -> &PublicKey {
&self.keypair.public
}
pub fn secret_key(&self) -> &SecretKey {
&self.keypair.secret
}
pub fn entropy(&self) -> &[u8] {
&self.entropy
}
pub fn sign_reward_hash(&self, header_hash: &[u8]) -> Signature {
self.keypair.sign(self.substrate_ctx.bytes(header_hash))
}
}