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
// Copyright (c) 2022-2023 The MobileCoin Foundation
//! Protocol / APDU definitions for MobileCoin app communication
//!
//! This module provides a protocol specification and reference implementation for communication
//! with MobileCoin wallets.
//!
//! APDUs use a primitive binary encoding to simplify implementation with unsupported languages and platforms
//! (as well as due to inconvenient incompatibilities with prost/heapless/no_std/no_alloc that preclude the direct use of
//! existing protobuf encodings, if this is resolved in future we _may_ be able to share the standard protocols)
//!
//! Encodings are intended to be _roughly_ equivalent to packed c structures while maintaining
//! 32-bit field alignment to reduce the need for unaligned access on constrained platforms.
//! All field encodings are little-endian, because most of the world is these days.
//!
//! See [Instruction] for APDU instruction codes.
//!
#![no_std]
use core::fmt::Debug;
pub use ledger_proto::{ApduError, ApduReq, ApduStatic};
pub mod app_info;
pub mod digest;
pub mod ident;
pub mod key_image;
pub mod prelude;
pub mod random;
pub mod state;
pub mod subaddress_keys;
pub mod tx;
pub mod wallet_keys;
mod helpers;
/// MobileCoin APDU Class
pub const MOB_APDU_CLA: u8 = 0xab;
pub const MOB_PROTO_VERSION: u8 = 0x01;
/// MobileCoin APDU instruction codes
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum Instruction {
// General instructions
GetAppInfo = 0x00,
// Mobilecoin instructions
/// Fetch wallet keys
GetWalletKeys = 0x10,
/// Fetch keys for a specific subaddress
GetSubaddressKeys = 0x11,
/// Request a key image
GetKeyImage = 0x12,
/// Fetch a random value
GetRandom = 0x13,
/// Issue SLIP-0017 ED25519 identity request
IdentSignReq = 0x14,
/// Fetched signed identity following approval
IdentGetReq = 0x15,
/// Initialise a transaction
TxInit = 0x20,
/// Sign a memo
TxMemoSign = 0x21,
/// Set message for signing
TxSetMessage = 0x22,
/// Start building TX summary
TxSummaryInit = 0x30,
/// Add TxOut to summary
TxSummaryAddTxOut = 0x31,
/// Add TxOut unblinding to summary
TxSummaryAddTxOutUnblinding = 0x32,
/// Add TxIn to summary
TxSummaryAddTxIn = 0x33,
/// Build Tx summary
TxSummaryBuild = 0x34,
/// Start a ring signing operation
TxRingInit = 0x40,
/// Set blinding factors
TxSetBlinding = 0x41,
/// Add TxOuts to ring
TxAddTxOut = 0x42,
/// Sign ring
TxSign = 0x43,
/// Fetch key image for a signed ring
TxGetKeyImage = 0x44,
/// Fetch a response for a given ring entry in the signed ring
TxGetResponse = 0x45,
/// Complete a transaction
TxComplete = 0x50,
/// Fetch transaction state
TxGetInfo = 0x51,
}
/// Helper macro for encoding `bitflags` types
#[macro_export]
macro_rules! encdec_bitflags {
($b:ty) => {
impl encdec::Encode for $b {
type Error = ApduError;
fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
let bits: u8 = self.bits();
encdec::Encode::encode(&bits, buff).map_err(|e| e.into())
}
fn encode_len(&self) -> Result<usize, Self::Error> {
let bits: u8 = self.bits();
encdec::Encode::encode_len(&bits).map_err(|e| e.into())
}
}
impl encdec::DecodeOwned for $b {
type Output = $b;
type Error = ApduError;
fn decode_owned(buff: &[u8]) -> Result<(Self, usize), Self::Error> {
let v = <$b>::from_bits_truncate(buff[0]);
Ok((v, 1))
}
}
};
}
#[cfg(test)]
pub(crate) mod test {
use encdec::EncDec;
use super::*;
/// Helper for APDU encode / decode tests
pub fn encode_decode_apdu<'a, A: EncDec<'a, ApduError> + PartialEq>(
buff: &'a mut [u8],
apdu: &A,
) -> usize {
// Encode APDU
let n = apdu.encode(buff).expect("encode failed");
// Ensure encoded data fits maximum APDU payload
let m = 249;
assert!(n < m, "encoded length {n} exceeds maximum APDU payload {m}");
// Check encoded length matches expected length
let expected_n = apdu.encode_len().expect("get length failed");
assert_eq!(n, expected_n, "encode length mismatch");
// Decode APDU
let (decoded, decoded_n) = A::decode(&buff[..n]).expect("decode failed");
// Check decoded object and length match
assert_eq!(apdu, &decoded);
assert_eq!(expected_n, decoded_n);
// Return length, useful for rough confirmation of packing expectations
n
}
}