1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use tracing::{debug, info, trace};

use crate::{libp2p::Keypair, utils::io::write_new_sensitive_file};
use std::{fs, path::Path};

const KEYPAIR_FILE: &str = "keypair";

/// Returns the libp2p key-pair for the node, generating a new one if it doesn't exist
/// in the data directory.
pub fn get_or_create_keypair(data_dir: &Path) -> anyhow::Result<Keypair> {
    match get_keypair(&data_dir.join(KEYPAIR_FILE)) {
        Some(keypair) => Ok(keypair),
        None => create_and_save_keypair(data_dir),
    }
}

/// Creates and saves a new `ED25519` key-pair to the given path.
/// If an older key-pair exists, it will be backed-up.
/// Returns the generated key-pair.
fn create_and_save_keypair(path: &Path) -> anyhow::Result<Keypair> {
    let gen_keypair = crate::libp2p::ed25519::Keypair::generate();

    let keypair_path = path.join(KEYPAIR_FILE);
    if keypair_path.exists() {
        let mut backup_path = keypair_path.clone();
        backup_path.set_extension("bak");

        info!("Backing up existing keypair to {}", backup_path.display());
        fs::rename(&keypair_path, &backup_path)?;
    }

    write_new_sensitive_file(&gen_keypair.to_bytes(), &keypair_path)?;

    Ok(gen_keypair.into())
}

// Fetch key-pair from disk, returning none if it cannot be decoded.
pub fn get_keypair(path_to_file: &Path) -> Option<Keypair> {
    match std::fs::read(path_to_file) {
        Err(e) => {
            info!("Networking keystore not found!");
            trace!("Error {e}");
            None
        }
        Ok(mut vec) => match crate::libp2p::ed25519::Keypair::try_from_bytes(&mut vec) {
            Ok(kp) => {
                debug!("Recovered libp2p keypair from {}", path_to_file.display());
                Some(kp.into())
            }
            Err(e) => {
                info!("Could not decode networking keystore!");
                info!("Error {e}");
                None
            }
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::remove_file;
    use tempfile::tempdir;

    #[test]
    fn test_get_or_create_keypair() {
        let dir = tempdir().unwrap();
        let path_to_file = dir.path().join(KEYPAIR_FILE);

        // Test that a keypair is generated and saved to disk
        let keypair = get_or_create_keypair(dir.path()).unwrap();
        assert!(path_to_file.exists());

        // Test that the same keypair is returned if it already exists
        let keypair2 = get_or_create_keypair(dir.path()).unwrap();
        assert_eq!(keypair.public(), keypair2.public());
        assert_eq!(
            keypair.clone().try_into_ed25519().unwrap().to_bytes(),
            keypair2.try_into_ed25519().unwrap().to_bytes()
        );

        // Test that a new keypair is generated if the file is deleted
        remove_file(&path_to_file).unwrap();
        let keypair3 = get_or_create_keypair(dir.path()).unwrap();
        assert_ne!(keypair.public(), keypair3.public());
        assert_ne!(
            keypair.try_into_ed25519().unwrap().to_bytes(),
            keypair3.try_into_ed25519().unwrap().to_bytes()
        );
    }

    #[test]
    fn test_backup_keypair() {
        let dir = tempdir().unwrap();
        let path_to_file = dir.path().join(KEYPAIR_FILE);

        // Test that a keypair is generated and saved to disk
        let keypair = create_and_save_keypair(dir.path()).unwrap();
        assert!(path_to_file.exists());

        // corrupt the existing keypair file
        fs::write(
            &path_to_file,
            b"Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        )
        .unwrap();

        // Test that a new keypair is generated if the file is corrupted
        // and the old file is backed up
        let keypair2 = get_or_create_keypair(dir.path()).unwrap();
        assert_ne!(keypair.public(), keypair2.public());
        assert_ne!(
            keypair.try_into_ed25519().unwrap().to_bytes(),
            keypair2.try_into_ed25519().unwrap().to_bytes()
        );
        assert!(path_to_file.exists());
        assert!(dir.path().join(format!("{}.bak", KEYPAIR_FILE)).exists());
    }
}