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
123
//
// Wildland Project
//
// Copyright © 2022 Golem Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as published by
// the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use thiserror::Error;
use wildland_crypto::{
    error::KeyDeriveError,
    identity::{new_device_identity, Identity as CryptoIdentity},
};

use super::wildland::WildlandIdentity;

#[derive(Debug)]
pub struct MasterIdentity {
    crypto_identity: Option<CryptoIdentity>,
}

#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum ForestIdentityCreationError {
    #[error("Crypto identity is required to create a new forest")]
    CryptoIdentityNotFound,
    #[error(transparent)]
    KeyDeriveError(#[from] KeyDeriveError),
}

impl MasterIdentity {
    #[tracing::instrument(level = "debug", skip(crypto_identity))]
    pub fn new(crypto_identity: Option<CryptoIdentity>) -> Self {
        tracing::debug!("creating new identity");
        Self { crypto_identity }
    }

    #[tracing::instrument(level = "debug", skip(self))]
    pub fn create_forest_identity(
        &self,
        index: u64,
    ) -> Result<WildlandIdentity, ForestIdentityCreationError> {
        let keypair = self
            .crypto_identity
            .as_ref()
            .map(|identity| identity.forest_keypair(index))
            .ok_or(ForestIdentityCreationError::CryptoIdentityNotFound)??;
        let identity = WildlandIdentity::Forest(index, keypair);

        Ok(identity)
    }

    #[tracing::instrument(level = "debug", skip(self))]
    pub fn create_device_identity(&self, name: String) -> WildlandIdentity {
        let keypair = new_device_identity();
        WildlandIdentity::Device(name, keypair)
    }
}

#[cfg(test)]
mod tests {
    use crate::{ForestIdentityCreationError, MasterIdentity, WildlandIdentity};
    use wildland_crypto::identity::{generate_random_mnemonic, Identity};

    fn create_crypto_identity() -> Identity {
        generate_random_mnemonic()
            .map(|mnemonic| Identity::try_from(&mnemonic).unwrap())
            .unwrap()
    }

    #[test]
    fn should_create_forest_identity() {
        let crypto_identity = create_crypto_identity();
        let master_identity = MasterIdentity::new(Some(crypto_identity));
        let forest_identity = master_identity.create_forest_identity(0).unwrap();

        assert!(matches!(forest_identity, WildlandIdentity::Forest(_, _)));
        assert_eq!(forest_identity.get_identifier(), "0");
        assert!(!forest_identity.get_private_key().is_empty());
        assert!(!forest_identity.get_public_key().is_empty());
    }

    #[test]
    fn should_not_create_forest_identity_without_crypto_identity() {
        let master_identity = MasterIdentity::new(None);
        let result = master_identity.create_forest_identity(0);
        assert_eq!(
            result.unwrap_err(),
            ForestIdentityCreationError::CryptoIdentityNotFound
        );
    }

    #[test]
    fn should_create_device_identity_with_crypto_identity() {
        let crypto_identity = create_crypto_identity();
        let master_identity = MasterIdentity::new(Some(crypto_identity));
        let device_identity = master_identity.create_device_identity("Device 1".to_string());

        assert!(matches!(device_identity, WildlandIdentity::Device(_, _)));
        assert_eq!(device_identity.get_identifier(), "Device 1");
        assert!(!device_identity.get_private_key().is_empty());
        assert!(!device_identity.get_public_key().is_empty());
    }

    #[test]
    fn should_create_device_identity_without_crypto_identity() {
        let master_identity = MasterIdentity::new(None);
        let device_identity = master_identity.create_device_identity("Device 1".to_string());

        assert!(matches!(device_identity, WildlandIdentity::Device(_, _)));
        assert_eq!(device_identity.get_identifier(), "Device 1");
        assert!(!device_identity.get_private_key().is_empty());
        assert!(!device_identity.get_public_key().is_empty());
    }
}