dubp_wallet/source/
v10.rs

1//  Copyright (C) 2020  Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Define DUBP currency source v10
17
18use crate::*;
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
21pub struct SourceV10 {
22    pub id: SourceIdV10,
23    pub amount: SourceAmount,
24}
25
26#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
27pub enum SourceIdV10 {
28    Ud(UdSourceIdV10),
29    Utxo(UtxoIdV10),
30}
31
32#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
33pub struct UdSourceIdV10 {
34    pub issuer: PublicKey,
35    pub block_number: BlockNumber,
36}
37
38#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
39pub struct UtxoIdV10 {
40    pub tx_hash: Hash,
41    pub output_index: usize,
42}
43
44impl std::fmt::Display for UtxoIdV10 {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(f, "{}:{}", self.tx_hash, self.output_index)
47    }
48}
49
50#[derive(Clone, Copy, Debug, Error, PartialEq)]
51pub enum SourceV10NotUnlockableError {
52    #[error("{0}")]
53    ScriptNeverUnlockable(ScriptNeverUnlockableError),
54    #[error("Too long signer index: {0}")]
55    TooLongSignerIndex(usize),
56    #[error("Too many proofs: found {found}, used {used}")]
57    TooManyProofs { found: usize, used: usize },
58}
59
60impl SourceV10 {
61    /// Indicates from which blockchain timestamp the currency source can be unlocked.
62    pub fn unlockable_on(
63        tx_signers: &[PublicKey],
64        proofs: &[WalletUnlockProofV10],
65        source_written_on: u64,
66        utxo_script: &WalletScriptV10,
67    ) -> Result<u64, SourceV10NotUnlockableError> {
68        let mut input_signers = HashSet::with_capacity(proofs.len());
69        let mut codes_hash = HashSet::with_capacity(proofs.len());
70
71        for proof in proofs {
72            match proof {
73                WalletUnlockProofV10::Sig(tx_signers_index) => {
74                    if *tx_signers_index >= tx_signers.len() {
75                        return Err(SourceV10NotUnlockableError::TooLongSignerIndex(
76                            *tx_signers_index,
77                        ));
78                    } else {
79                        input_signers.insert(&tx_signers[*tx_signers_index].as_ref()[..32]);
80                    }
81                }
82                WalletUnlockProofV10::Xhx(code) => {
83                    codes_hash.insert(Hash::compute(code.as_bytes()));
84                }
85            }
86        }
87
88        let (script_unlockable_on, used_proofs) = utxo_script
89            .unlockable_on(&input_signers, &codes_hash, source_written_on)
90            .map_err(SourceV10NotUnlockableError::ScriptNeverUnlockable)?;
91
92        if used_proofs.len() < proofs.len() {
93            Err(SourceV10NotUnlockableError::TooManyProofs {
94                found: proofs.len(),
95                used: used_proofs.len(),
96            })
97        } else {
98            Ok(script_unlockable_on)
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use dubp_common::crypto::keys::PublicKey as _;
107    use unwrap::unwrap;
108
109    #[inline(always)]
110    fn pk(pk_b58: &str) -> PublicKey {
111        unwrap!(PublicKey::from_base58(pk_b58))
112    }
113
114    #[test]
115    fn test_source_unlockable_on_invariant_with_leading_1() {
116        let p43 = pk("XoFs76G4yidvVY3FZBwYyLXTMjabryhFD8mNQPkQKHk");
117        let p43_with_leading_1 = pk("1XoFs76G4yidvVY3FZBwYyLXTMjabryhFD8mNQPkQKHk");
118        let script = WalletScriptV10::single(WalletConditionV10::Sig(p43));
119        let script_with_leading_1 =
120            WalletScriptV10::single(WalletConditionV10::Sig(p43_with_leading_1));
121        let proofs = vec![WalletUnlockProofV10::Sig(0)];
122
123        assert_eq!(
124            Ok(0),
125            SourceV10::unlockable_on(&[p43_with_leading_1], &proofs, 0, &script)
126        );
127
128        assert_eq!(
129            Ok(0),
130            SourceV10::unlockable_on(&[p43], &proofs, 0, &script_with_leading_1)
131        );
132    }
133
134    #[test]
135    fn test_source_unlockable_on() {
136        let p1 = pk("D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C");
137        let p2 = pk("42jMJtb8chXrpHMAMcreVdyPJK7LtWjEeRqkPw4eSEVp");
138        let script = WalletScriptV10::or(WalletConditionV10::Sig(p1), WalletConditionV10::Sig(p2));
139        let signers = vec![p1, p2];
140        let proofs = vec![WalletUnlockProofV10::Sig(0), WalletUnlockProofV10::Sig(1)];
141
142        assert_eq!(
143            Err(SourceV10NotUnlockableError::TooManyProofs { found: 2, used: 1 }),
144            SourceV10::unlockable_on(&signers, &proofs, 0, &script)
145        );
146        assert_eq!(
147            Err(SourceV10NotUnlockableError::TooLongSignerIndex(2)),
148            SourceV10::unlockable_on(&signers, &[WalletUnlockProofV10::Sig(2)], 0, &script)
149        );
150        assert_eq!(
151            Err(SourceV10NotUnlockableError::ScriptNeverUnlockable(
152                ScriptNeverUnlockableError
153            )),
154            SourceV10::unlockable_on(&signers, &[], 0, &script)
155        );
156        assert_eq!(
157            Ok(0),
158            SourceV10::unlockable_on(&signers, &[WalletUnlockProofV10::Sig(1)], 0, &script)
159        );
160    }
161}