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
// Copyright (c) 2022-2023 The MobileCoin Foundation

//! Application State APDUs
//!

use encdec::{DecodeOwned, Encode};
use ledger_proto::ApduError;
use num_enum::TryFromPrimitive;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest as _, Sha512_256};
use strum::{Display, EnumIter, EnumString, EnumVariantNames};

/// Engine state enumeration
/// used in [crate::tx::TxInfo] to communicate transaction progress
#[derive(
    Copy, Clone, PartialEq, Debug, EnumString, Display, EnumVariantNames, EnumIter, TryFromPrimitive,
)]
#[repr(u8)]
pub enum TxState {
    Init = 0x00,
    SignMemos = 0x01,
    SetMessage = 0x02,
    SummaryInit = 0x03,
    SummaryAddTxOut = 0x04,
    SummaryAddTxIn = 0x05,
    SummaryReady = 0x06,
    SummaryComplete = 0x07,
    Pending = 0x10,
    Ready = 0x20,
    RingInit = 0x30,
    RingBuild = 0x31,
    RingSign = 0x32,
    RingComplete = 0x33,
    TxComplete = 0x40,
    TxDenied = 0x41,
    IdentPending = 0x50,
    IdentApproved = 0x51,
    IdentDenied = 0x52,
    Error = 0xFF,
}

impl Encode for TxState {
    type Error = ApduError;

    fn encode_len(&self) -> Result<usize, ApduError> {
        Ok(1)
    }

    fn encode(&self, buff: &mut [u8]) -> Result<usize, ApduError> {
        buff[0] = *self as u8;
        Ok(1)
    }
}

impl DecodeOwned for TxState {
    type Output = Self;

    type Error = ApduError;

    fn decode_owned(buff: &[u8]) -> Result<(Self::Output, usize), ApduError> {
        if buff.is_empty() {
            return Err(ApduError::InvalidLength);
        }

        match Self::try_from(buff[0]) {
            Ok(v) => Ok((v, 1)),
            Err(_) => Err(ApduError::InvalidEncoding),
        }
    }
}

/// Transaction digest, used to keep a running digest of inputs to
/// the transaction engine to ensure sync between the host and hardware
/// wallet.
#[derive(Clone, PartialEq, Encode)]
pub struct Digest([u8; 32]);

impl Digest {
    /// Create a new (empty) state digest
    pub const fn new() -> Self {
        Self([0u8; 32])
    }

    /// Reset state digest from random seed
    #[inline(never)]
    pub fn from_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
        let mut b = [0u8; 32];
        rng.fill_bytes(&mut b);

        let r = Sha512_256::new().chain_update(b).finalize();
        b.copy_from_slice(r.as_ref());

        Self(b)
    }

    /// Update transaction digest with new event
    // TODO: what if we apply an event but lose the response, will the client retry..?
    // TODO: swap to tree approach, cache prior event and skip updates to allow retries
    // TODO: could use [Digestible], though this adds dependencies for implementers?
    #[inline(never)]
    pub fn update(&mut self, evt: &[u8; 32]) -> &Self {
        // Build and update digest
        let mut d = Sha512_256::new();

        // Prior state
        d.update(self.0);

        // New event
        d.update(evt);

        // Write to internal state
        self.0.copy_from_slice(d.finalize().as_ref());

        self
    }
}

/// Debug format [Digest] as hex
impl core::fmt::Debug for Digest {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for b in &self.0[..] {
            write!(f, "{b:02x}")?;
        }
        Ok(())
    }
}

/// Display [Digest] as hex
impl core::fmt::Display for Digest {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for b in &self.0[..] {
            write!(f, "{b:02x}")?;
        }
        Ok(())
    }
}

/// Decode [Digest] into owned array
impl DecodeOwned for Digest {
    type Output = Digest;

    type Error = encdec::Error;

    fn decode_owned(buff: &[u8]) -> Result<(Self::Output, usize), Self::Error> {
        if buff.len() < 32 {
            return Err(encdec::Error::Length);
        }

        let mut d = [0u8; 32];
        d.copy_from_slice(&buff[..32]);
        Ok((Self(d), 32))
    }
}