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

use encdec::{Decode, Encode};
use ledger_proto::ApduStatic;

use mc_core::keys::Key;
use mc_crypto_keys::{RistrettoPrivate, RistrettoPublic};

use crate::{helpers::*, ApduError, Instruction, MOB_APDU_CLA};

/// Marker trait for onetime key type (to be moved to `mc_core_types`)
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct TxOnetime;

/// Transaction onetime key type (to be moved to `mc_core_types`)
pub type TxOnetimeKey = Key<TxOnetime, TxOnetime, RistrettoPrivate>;

/// Marker trait for transaction key type (to be moved to `mc_core_types`)
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Tx;

/// Transaction public key type (to be moved to `mc_core_types`)
pub type TxPublicKey = Key<Tx, Tx, RistrettoPublic>;

/// Transaction private key type (to be moved to `mc_core_types`)
pub type TxPrivateKey = Key<Tx, Tx, RistrettoPrivate>;

/// Transaction initialisation APDU, sets up a transaction for execution
///
/// ## Encoding:
/// ```text
///  0                   1                   2                   3
///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |   NUM_RINGS   |                    RESERVED                   |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                        ACCOUNT_INDEX                          |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                           TOKEN_ID                            |
/// |                        (u64, 8-byte)                          |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ```
///
#[derive(Clone, Debug, PartialEq, Encode, Decode)]
#[encdec(error = "ApduError")]
pub struct TxInit {
    /// Number of rings to be signed
    pub num_rings: u8,

    /// Reserved for future use (maintains 32-bit field alignment)
    #[encdec(with = "arr")]
    reserved: [u8; 3],

    /// Account index for SLIP-010 derivation
    pub account_index: u32,
}

impl ApduStatic for TxInit {
    const CLA: u8 = MOB_APDU_CLA;
    const INS: u8 = Instruction::TxInit as u8;
}

impl TxInit {
    /// Create a new [`TxInit`] request
    pub fn new(account_index: u32, num_rings: u8) -> Self {
        Self {
            num_rings,
            reserved: [0u8; 3],
            account_index,
        }
    }
}

/// Set the message for the transaction
///
/// ## Encoding:
/// ```text
///  0                   1                   2                   3
///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |  MESSAGE_LEN  |                    RESERVED                   |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                            MESSAGE                            /
/// /                       (variable length)                       /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ```
#[derive(Clone, PartialEq, Debug)]
pub struct TxSetMessage<'a> {
    /// `Message` for transaction, derived from prefix
    pub message: &'a [u8],
}

impl<'a> ApduStatic for TxSetMessage<'a> {
    const CLA: u8 = MOB_APDU_CLA;
    const INS: u8 = Instruction::TxSetMessage as u8;
}

impl<'a> TxSetMessage<'a> {
    pub fn new(message: &'a [u8]) -> Self {
        Self { message }
    }

    /// Compute hash of [TxSetMessage] object
    pub fn hash(&self) -> [u8; 32] {
        crate::digest::digest_tx_set_message(self.message)
    }
}

impl<'a> Encode for TxSetMessage<'a> {
    type Error = ApduError;

    /// Encode an [`TxSetMessage`] APDU into the provided buffer
    #[inline]
    fn encode(&self, buff: &mut [u8]) -> Result<usize, ApduError> {
        let d = self.message;

        if buff.len() < d.len() + 1 {
            return Err(ApduError::InvalidLength);
        }

        let mut index = 0;
        buff[index] = d.len() as u8;
        index += 4;

        buff[index..][..d.len()].copy_from_slice(d);
        index += d.len();

        Ok(index)
    }

    #[inline]
    fn encode_len(&self) -> Result<usize, ApduError> {
        Ok(1 + self.message.len())
    }
}

impl<'a> Decode<'a> for TxSetMessage<'a> {
    type Output = Self;
    type Error = ApduError;

    /// Decode a [`TxSetMessage`] APDU from the provided buffer
    #[inline]
    fn decode(buff: &'a [u8]) -> Result<(Self, usize), ApduError> {
        let mut index = 0;

        let l = buff[index] as usize;
        index += 4;

        let message = &buff[index..][..l];
        index += l;

        Ok((Self { message }, index))
    }
}

#[cfg(test)]
mod test {
    use rand::random;

    use super::TxInit;
    use crate::test::encode_decode_apdu;

    #[test]
    fn encode_decode_txinit() {
        let apdu = TxInit::new(random(), random());

        let mut buff = [0u8; 256];
        let _n = encode_decode_apdu(&mut buff, &apdu);

        //assert_eq!(n, 48);
    }
}