subspace_farmer/single_disk_farm/
identity.rs

1//! Farm identity
2
3use parity_scale_codec::{Decode, Encode};
4use schnorrkel::context::SigningContext;
5use schnorrkel::{ExpansionMode, Keypair, PublicKey, SecretKey, Signature};
6use std::ops::Deref;
7use std::path::Path;
8use std::{fmt, fs, io};
9use subspace_core_primitives::REWARD_SIGNING_CONTEXT;
10use substrate_bip39::mini_secret_from_entropy;
11use thiserror::Error;
12use tracing::debug;
13use zeroize::Zeroizing;
14
15/// Entropy used for identity generation.
16const ENTROPY_LENGTH: usize = 32;
17
18#[derive(Debug, Encode, Decode)]
19struct IdentityFileContents {
20    entropy: Vec<u8>,
21}
22
23fn keypair_from_entropy(entropy: &[u8]) -> Keypair {
24    mini_secret_from_entropy(entropy, "")
25        .expect("32 bytes can always build a key; qed")
26        .expand_to_keypair(ExpansionMode::Ed25519)
27}
28
29/// Errors happening when trying to create/open single disk farm
30#[derive(Debug, Error)]
31pub enum IdentityError {
32    /// I/O error occurred
33    #[error("Identity I/O error: {0}")]
34    Io(#[from] io::Error),
35    /// Invalid contents
36    #[error("Invalid contents")]
37    InvalidContents,
38    /// Decoding error
39    #[error("Decoding error: {0}")]
40    Decoding(#[from] parity_scale_codec::Error),
41}
42
43/// `Identity` struct is an abstraction of public & secret key related operations.
44///
45/// It is basically a wrapper of the keypair (which holds public & secret keys)
46/// and a context that will be used for signing.
47#[derive(Clone)]
48pub struct Identity {
49    keypair: Zeroizing<Keypair>,
50    entropy: Zeroizing<Vec<u8>>,
51    substrate_ctx: SigningContext,
52}
53
54impl fmt::Debug for Identity {
55    #[inline]
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("Identity")
58            .field("keypair", &self.keypair)
59            .finish_non_exhaustive()
60    }
61}
62
63impl Deref for Identity {
64    type Target = Keypair;
65
66    #[inline]
67    fn deref(&self) -> &Self::Target {
68        &self.keypair
69    }
70}
71
72impl Identity {
73    pub(crate) const FILE_NAME: &'static str = "identity.bin";
74
75    /// Size of the identity file on disk
76    pub fn file_size() -> usize {
77        IdentityFileContents {
78            entropy: vec![0; ENTROPY_LENGTH],
79        }
80        .encoded_size()
81    }
82
83    /// Opens the existing identity, or creates a new one.
84    pub fn open_or_create<B: AsRef<Path>>(base_directory: B) -> Result<Self, IdentityError> {
85        if let Some(identity) = Self::open(base_directory.as_ref())? {
86            Ok(identity)
87        } else {
88            Self::create(base_directory)
89        }
90    }
91
92    /// Opens the existing identity, returns `Ok(None)` if it doesn't exist.
93    pub fn open<B: AsRef<Path>>(base_directory: B) -> Result<Option<Self>, IdentityError> {
94        let identity_file = base_directory.as_ref().join(Self::FILE_NAME);
95        if identity_file.exists() {
96            debug!("Opening existing keypair");
97            let bytes = Zeroizing::new(fs::read(identity_file)?);
98            let IdentityFileContents { entropy } =
99                IdentityFileContents::decode(&mut bytes.as_ref())?;
100
101            if entropy.len() != ENTROPY_LENGTH {
102                return Err(IdentityError::InvalidContents);
103            }
104
105            Ok(Some(Self {
106                keypair: Zeroizing::new(keypair_from_entropy(&entropy)),
107                entropy: Zeroizing::new(entropy),
108                substrate_ctx: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
109            }))
110        } else {
111            debug!("Existing keypair not found");
112            Ok(None)
113        }
114    }
115
116    /// Creates new identity, overrides identity that might already exist.
117    pub fn create<B: AsRef<Path>>(base_directory: B) -> Result<Self, IdentityError> {
118        let identity_file = base_directory.as_ref().join(Self::FILE_NAME);
119        debug!("Generating new keypair");
120        let entropy = rand::random::<[u8; ENTROPY_LENGTH]>().to_vec();
121
122        let identity_file_contents = IdentityFileContents { entropy };
123        fs::write(identity_file, identity_file_contents.encode())?;
124
125        let IdentityFileContents { entropy } = identity_file_contents;
126
127        Ok(Self {
128            keypair: Zeroizing::new(keypair_from_entropy(&entropy)),
129            entropy: Zeroizing::new(entropy),
130            substrate_ctx: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
131        })
132    }
133
134    /// Returns the public key of the identity.
135    pub fn public_key(&self) -> &PublicKey {
136        &self.keypair.public
137    }
138
139    /// Returns the secret key of the identity.
140    pub fn secret_key(&self) -> &SecretKey {
141        &self.keypair.secret
142    }
143
144    /// Returns entropy used to generate keypair.
145    pub fn entropy(&self) -> &[u8] {
146        &self.entropy
147    }
148
149    /// Sign reward hash.
150    pub fn sign_reward_hash(&self, header_hash: &[u8]) -> Signature {
151        self.keypair.sign(self.substrate_ctx.bytes(header_hash))
152    }
153}