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
use bech32::{ToBase32, Variant};
use bip39::{Language, Mnemonic, Seed};
use rand::rngs::OsRng;
use rand::RngCore;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{
    decode_extended_full_viewing_key, decode_extended_spending_key,
    encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
};
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_primitives::consensus::Parameters;
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};

pub struct KeyHelpers {
    coin_type: CoinType,
}

impl KeyHelpers {
    pub fn new(coin_type: CoinType) -> Self {
        KeyHelpers { coin_type }
    }

    fn chain(&self) -> &dyn CoinChain {
        get_coin_chain(self.coin_type)
    }

    pub fn decode_key(
        &self,
        key: &str,
        index: u32,
    ) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
        let network = self.chain().network();
        let res = if let Ok(mnemonic) = Mnemonic::from_phrase(key, Language::English) {
            let (sk, ivk, pa) = self.derive_secret_key(&mnemonic, index)?;
            Ok((Some(key.to_string()), Some(sk), ivk, pa))
        } else if let Ok(Some(sk)) =
            decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
        {
            let (ivk, pa) = self.derive_viewing_key(&sk)?;
            Ok((None, Some(key.to_string()), ivk, pa))
        } else if let Ok(Some(fvk)) =
            decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
        {
            let pa = self.derive_address(&fvk)?;
            Ok((None, None, key.to_string(), pa))
        } else {
            Err(anyhow::anyhow!("Not a valid key"))
        };
        res
    }

    pub fn is_valid_key(&self, key: &str) -> i8 {
        let network = self.chain().network();
        if Mnemonic::from_phrase(key, Language::English).is_ok() {
            return 0;
        }
        if let Ok(Some(_)) =
            decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
        {
            return 1;
        }
        if let Ok(Some(_)) =
            decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
        {
            return 2;
        }
        -1
    }

    pub fn derive_secret_key(
        &self,
        mnemonic: &Mnemonic,
        index: u32,
    ) -> anyhow::Result<(String, String, String)> {
        let network = self.chain().network();
        let seed = Seed::new(mnemonic, "");
        let master = ExtendedSpendingKey::master(seed.as_bytes());
        let path = [
            ChildIndex::Hardened(32),
            ChildIndex::Hardened(network.coin_type()),
            ChildIndex::Hardened(index),
        ];
        let extsk = ExtendedSpendingKey::from_path(&master, &path);
        let sk = encode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk);

        let (fvk, pa) = self.derive_viewing_key(&extsk)?;
        Ok((sk, fvk, pa))
    }

    pub fn derive_viewing_key(
        &self,
        extsk: &ExtendedSpendingKey,
    ) -> anyhow::Result<(String, String)> {
        let network = self.chain().network();
        let fvk = ExtendedFullViewingKey::from(extsk);
        let pa = self.derive_address(&fvk)?;
        let fvk =
            encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk);
        Ok((fvk, pa))
    }

    pub fn derive_address(&self, fvk: &ExtendedFullViewingKey) -> anyhow::Result<String> {
        let network = self.chain().network();
        let (_, payment_address) = fvk.default_address();
        let address =
            encode_payment_address(network.hrp_sapling_payment_address(), &payment_address);
        Ok(address)
    }

    pub fn valid_address(&self, address: &str) -> bool {
        let recipient = RecipientAddress::decode(self.chain().network(), address);
        recipient.is_some()
    }
}