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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::{
    DerivationIndex, DerivedSecretKey, Hash, MainPubkey, MainSecretKey, NanoTokens, SignedSpend,
    Transaction, UniquePubkey,
};

use crate::{Result, TransferError};

use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tiny_keccak::{Hasher, Sha3};

/// OutputDetails: (CashNoteReason, pub_key, derived_key)
pub type CashNoteOutputDetails = (String, MainPubkey, DerivationIndex);

/// Represents a CashNote (CashNote).
///
/// A CashNote is like a check. Only the recipient can spend it.
///
/// A CashNote has a MainPubkey representing the recipient of the CashNote.
///
/// An MainPubkey consists of a PublicKey.
/// The user who receives payments to this MainPubkey, will be holding
/// a MainSecretKey - a secret key, which corresponds to the MainPubkey.
///
/// The MainPubkey can be given out to multiple parties and
/// multiple CashNotes can share the same MainPubkey.
///
/// The spentbook nodes never sees the MainPubkey. Instead, when a
/// transaction output cashnote is created for a given MainPubkey, a random
/// derivation index is generated and used to derive a UniquePubkey, which will be
/// used for this new cashnote.
///
/// The UniquePubkey is a unique identifier of a CashNote.
/// So there can only ever be one CashNote with that id, previously, now and forever.
/// The UniquePubkey consists of a PublicKey. To unlock the tokens of the CashNote,
/// the corresponding DerivedSecretKey (consists of a SecretKey) must be used.
/// It is derived from the MainSecretKey, in the same way as the UniquePubkey was derived
/// from the MainPubkey to get the UniquePubkey.
///
/// So, there are two important pairs to conceptually be aware of.
/// The MainSecretKey and MainPubkey is a unique pair of a user, where the MainSecretKey
/// is held secret, and the MainPubkey is given to all and anyone who wishes to send tokens to you.
/// A sender of tokens will derive the UniquePubkey from the MainPubkey, which will identify the CashNote that
/// holds the tokens going to the recipient. The sender does this using a derivation index.
/// The recipient of the tokens, will use the same derivation index, to derive the DerivedSecretKey
/// from the MainSecretKey. The DerivedSecretKey and UniquePubkey pair is the second important pair.
/// For an outsider, there is no way to associate either the DerivedSecretKey or the UniquePubkey to the MainPubkey
/// (or for that matter to the MainSecretKey, if they were ever to see it, which they shouldn't of course).
/// Only by having the derivation index, which is only known to sender and recipient, can such a connection be made.
///
/// To spend or work with a CashNote, wallet software must obtain the corresponding
/// MainSecretKey from the user, and then call an API function that accepts a MainSecretKey,
/// eg: `cashnote.derivation_index(&main_key)`
#[derive(custom_debug::Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct CashNote {
    /// The unique public key of this CashNote. It is unique, and there can never
    /// be another CashNote with the same public key. It used in SignedSpends.
    pub unique_pubkey: UniquePubkey,
    /// The transaction where this CashNote was created.
    #[debug(skip)]
    pub parent_tx: Transaction,
    /// The transaction's input's SignedSpends
    pub parent_spends: BTreeSet<SignedSpend>,
    /// The purpose this cash_note created for
    /// eg. `store cost pay to...`, `network royalty`, `change to self`, `payment transfer`, ...
    pub purpose: String,
    /// This is the MainPubkey of the owner of this CashNote
    pub main_pubkey: MainPubkey,
    /// The derivation index used to derive the UniquePubkey and DerivedSecretKey from the MainPubkey and MainSecretKey respectively.
    /// It is to be kept secret to preserve the privacy of the owner.
    /// Without it, it is very hard to link the MainPubkey (original owner) and the UniquePubkey (derived unique identity of the CashNote)
    pub derivation_index: DerivationIndex,
}

impl CashNote {
    /// Return the unique pubkey of this CashNote.
    pub fn unique_pubkey(&self) -> UniquePubkey {
        self.unique_pubkey
    }

    // Return MainPubkey from which UniquePubkey is derived.
    pub fn main_pubkey(&self) -> &MainPubkey {
        &self.main_pubkey
    }

    /// Return DerivedSecretKey using MainSecretKey supplied by caller.
    /// Will return an error if the supplied MainSecretKey does not match the
    /// CashNote MainPubkey.
    pub fn derived_key(&self, main_key: &MainSecretKey) -> Result<DerivedSecretKey> {
        if &main_key.main_pubkey() != self.main_pubkey() {
            return Err(TransferError::MainSecretKeyDoesNotMatchMainPubkey);
        }
        Ok(main_key.derive_key(&self.derivation_index()))
    }

    /// Return UniquePubkey using MainPubkey supplied by caller.
    /// Will return an error if the supplied MainPubkey does not match the
    /// CashNote MainPubkey.
    pub fn derived_pubkey(&self, main_pubkey: &MainPubkey) -> Result<UniquePubkey> {
        if main_pubkey != self.main_pubkey() {
            return Err(TransferError::MainPubkeyMismatch);
        }
        Ok(main_pubkey.new_unique_pubkey(&self.derivation_index()))
    }

    /// Return the derivation index that was used to derive UniquePubkey and corresponding DerivedSecretKey of a CashNote.
    pub fn derivation_index(&self) -> DerivationIndex {
        self.derivation_index
    }

    /// Return the reason why this CashNote was spent.
    /// Will be the default Hash (empty) if reason is none.
    pub fn spent_reason(&self) -> Hash {
        self.parent_spends
            .iter()
            .next()
            .map(|s| s.reason())
            .unwrap_or_default()
    }

    /// Return the purpose why this CashNote was created.
    pub fn purpose(&self) -> String {
        self.purpose.clone()
    }

    /// Return the value in NanoTokens for this CashNote.
    pub fn value(&self) -> Result<NanoTokens> {
        Ok(self
            .parent_tx
            .outputs
            .iter()
            .find(|o| &self.unique_pubkey() == o.unique_pubkey())
            .ok_or(TransferError::OutputNotFound)?
            .amount)
    }

    /// Generate the hash of this CashNote
    pub fn hash(&self) -> Hash {
        let mut sha3 = Sha3::v256();
        sha3.update(self.parent_tx.hash().as_ref());
        sha3.update(&self.main_pubkey.to_bytes());
        sha3.update(&self.derivation_index.0);

        for sp in self.parent_spends.iter() {
            sha3.update(&sp.to_bytes());
        }

        sha3.update(&self.purpose.clone().into_bytes());

        sha3.update(self.spent_reason().as_ref());
        let mut hash = [0u8; 32];
        sha3.finalize(&mut hash);
        Hash::from(hash)
    }

    /// Verifies that this CashNote is valid.
    ///
    /// A CashNote recipient should call this immediately upon receipt.
    ///
    /// important: this will verify there is a matching transaction provided
    /// for each SignedSpend, although this does not check if the CashNote has been spent.
    /// For that, one must query the spentbook nodes.
    ///
    /// Note that the spentbook nodes cannot perform this check.  Only the CashNote
    /// recipient (private key holder) can.
    ///
    /// see TransactionVerifier::verify() for a description of
    /// verifier requirements.
    pub fn verify(&self, main_key: &MainSecretKey) -> Result<(), TransferError> {
        self.parent_tx
            .verify_against_inputs_spent(self.parent_spends.iter())?;

        let unique_pubkey = self.derived_key(main_key)?.unique_pubkey();
        if !self
            .parent_tx
            .outputs
            .iter()
            .any(|o| unique_pubkey.eq(o.unique_pubkey()))
        {
            return Err(TransferError::CashNoteCiphersNotPresentInTransactionOutput);
        }

        // verify that all signed_spend reasons are equal
        let spent_reason = self.spent_reason();
        let reasons_are_equal = |s: &SignedSpend| spent_reason == s.reason();
        if !self.parent_spends.iter().all(reasons_are_equal) {
            return Err(TransferError::SignedSpendReasonMismatch(unique_pubkey));
        }
        Ok(())
    }

    /// Deserializes a `CashNote` represented as a hex string to a `CashNote`.
    pub fn from_hex(hex: &str) -> Result<Self, TransferError> {
        let mut bytes =
            hex::decode(hex).map_err(|e| TransferError::HexDeserializationFailed(e.to_string()))?;
        bytes.reverse();
        let cashnote: CashNote = rmp_serde::from_slice(&bytes)
            .map_err(|e| TransferError::HexDeserializationFailed(e.to_string()))?;
        Ok(cashnote)
    }

    /// Serialize this `CashNote` instance to a hex string.
    pub fn to_hex(&self) -> Result<String, TransferError> {
        let mut serialized = rmp_serde::to_vec(&self)
            .map_err(|e| TransferError::HexSerializationFailed(e.to_string()))?;
        serialized.reverse();
        Ok(hex::encode(serialized))
    }
}