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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Copyright (c) 2023, MaidSafe.
// All rights reserved.
//
// This SAFE Network Software is licensed under the BSD-3-Clause license.
// Please see the LICENSE file for more details.

use super::{NanoTokens, SignedSpend, UniquePubkey};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, collections::BTreeSet};
use tiny_keccak::{Hasher, Sha3};

use crate::TransferError;

type Result<T> = std::result::Result<T, TransferError>;

#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, Hash)]
pub struct Input {
    pub unique_pubkey: UniquePubkey,
    pub amount: NanoTokens,
}

impl Input {
    pub fn new(unique_pubkey: UniquePubkey, amount: u64) -> Self {
        Self {
            unique_pubkey,
            amount: NanoTokens::from(amount),
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Default::default();
        v.extend(self.unique_pubkey.to_bytes().as_ref());
        v.extend(self.amount.to_bytes());
        v
    }

    pub fn unique_pubkey(&self) -> &UniquePubkey {
        &self.unique_pubkey
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Output {
    pub unique_pubkey: UniquePubkey,
    pub amount: NanoTokens,
}

impl Output {
    pub fn new(unique_pubkey: UniquePubkey, amount: u64) -> Self {
        Self {
            unique_pubkey,
            amount: NanoTokens::from(amount),
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Default::default();
        v.extend(self.unique_pubkey.to_bytes().as_ref());
        v.extend(self.amount.to_bytes());
        v
    }

    pub fn unique_pubkey(&self) -> &UniquePubkey {
        &self.unique_pubkey
    }
}

#[derive(Clone, Default, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Transaction {
    pub inputs: Vec<Input>,
    pub outputs: Vec<Output>,
}

/// debug method for Transaction which does not print the full content
impl std::fmt::Debug for Transaction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // use self.hash to avoid printing the full content
        f.debug_struct("Transaction")
            .field("inputs", &self.inputs.len())
            .field("outputs", &self.outputs.len())
            .field("hash", &self.hash())
            .finish()
    }
}

impl PartialOrd for Transaction {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Transaction {
    fn cmp(&self, other: &Self) -> Ordering {
        self.hash().cmp(&other.hash())
    }
}

impl Transaction {
    pub fn empty() -> Self {
        Self {
            inputs: vec![],
            outputs: vec![],
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Default::default();
        v.extend("inputs".as_bytes());
        for m in self.inputs.iter() {
            v.extend(&m.to_bytes());
        }
        v.extend("outputs".as_bytes());
        for o in self.outputs.iter() {
            v.extend(&o.to_bytes());
        }
        v.extend("end".as_bytes());
        v
    }

    pub fn hash(&self) -> crate::Hash {
        let mut sha3 = Sha3::v256();
        sha3.update(&self.to_bytes());
        let mut hash = [0; 32];
        sha3.finalize(&mut hash);
        crate::Hash::from(hash)
    }

    /// Quickly check is a transaction is balanced
    fn verify_balanced(&self) -> Result<()> {
        // Check that the input and output tokens are equal.
        let input_sum: u64 = self
            .inputs
            .iter()
            .map(|i| i.amount)
            .try_fold(0, |acc: u64, i| {
                acc.checked_add(i.as_nano())
                    .ok_or(TransferError::NumericOverflow)
            })?;
        let output_sum: u64 =
            self.outputs
                .iter()
                .map(|o| o.amount)
                .try_fold(0, |acc: u64, o| {
                    acc.checked_add(o.as_nano())
                        .ok_or(TransferError::NumericOverflow)
                })?;

        if input_sum != output_sum {
            Err(TransferError::UnbalancedTransaction)
        } else {
            Ok(())
        }
    }

    /// Verifies a transaction with Network held signed spends.
    ///
    /// This function assumes that the signed spends where previously fetched from the Network and where not double spent.
    /// This function will verify that:
    /// - the transaction is balanced (sum inputs = sum outputs)
    /// - the inputs and outputs are unique
    /// - the inputs and outputs are different
    /// - the inputs have a corresponding signed spend
    /// - those signed spends are valid and refer to this transaction
    pub fn verify_against_inputs_spent<'a, T>(&self, signed_spends: T) -> Result<()>
    where
        T: IntoIterator<Item = &'a SignedSpend> + Clone,
    {
        // verify that the tx has at least one input
        if self.inputs.is_empty() {
            return Err(TransferError::MissingTxInputs);
        }

        // check spends match the inputs
        let input_keys = self
            .inputs
            .iter()
            .map(|i| i.unique_pubkey())
            .collect::<BTreeSet<_>>();
        let signed_spend_keys = signed_spends
            .clone()
            .into_iter()
            .map(|s| s.unique_pubkey())
            .collect::<BTreeSet<_>>();
        if input_keys != signed_spend_keys {
            debug!("SpendsDoNotMatchInputs: {input_keys:#?} != {signed_spend_keys:#?}");
            return Err(TransferError::SpendsDoNotMatchInputs);
        }

        // Verify that each output is unique
        let output_pks: BTreeSet<&UniquePubkey> =
            self.outputs.iter().map(|o| (o.unique_pubkey())).collect();
        if output_pks.len() != self.outputs.len() {
            return Err(TransferError::UniquePubkeyNotUniqueInTx);
        }

        // Verify that each input is unique
        let input_pks: BTreeSet<&UniquePubkey> =
            self.inputs.iter().map(|i| (i.unique_pubkey())).collect();
        if input_pks.len() != self.inputs.len() {
            return Err(TransferError::UniquePubkeyNotUniqueInTx);
        }

        // Verify that inputs are different from outputs
        if !input_pks.is_disjoint(&output_pks) {
            return Err(TransferError::UniquePubkeyNotUniqueInTx);
        }

        // Verify that each signed spend is valid and was spent in this transaction
        let spent_tx_hash = self.hash();
        for s in signed_spends {
            s.verify(spent_tx_hash)?;
        }

        // Verify that the transaction is balanced
        self.verify_balanced()
    }

    /// Deserializes a `Transaction` represented as a hex string to a `Transaction`.
    pub fn from_hex(hex: &str) -> Result<Self> {
        let mut bytes =
            hex::decode(hex).map_err(|_| TransferError::TransferDeserializationFailed)?;
        bytes.reverse();
        let transaction: Self = rmp_serde::from_slice(&bytes)
            .map_err(|_| TransferError::TransferDeserializationFailed)?;
        Ok(transaction)
    }

    /// Serialize this `Transaction` instance to a readable hex string that a human can copy paste
    pub fn to_hex(&self) -> Result<String> {
        let mut serialized =
            rmp_serde::to_vec(&self).map_err(|_| TransferError::TransferSerializationFailed)?;
        serialized.reverse();
        Ok(hex::encode(serialized))
    }
}