1#![deny(warnings, trivial_casts, trivial_numeric_casts)]
19#![deny(unused_import_braces, unused_qualifications)]
20#![deny(missing_docs)]
21#![doc(html_root_url = "https://docs.rs/ledger-filecoin/0.1.0")]
22
23use ledger_transport::{APDUCommand, APDUErrorCode, Exchange};
24use ledger_zondax_generic::{App, AppExt, ChunkPayloadType, Version};
25
26pub use ledger_zondax_generic::LedgerAppError;
27
28mod params;
29use params::{InstructionCode, CLA};
30
31use std::str;
32
33const PK_LEN: usize = 65;
35
36#[derive(Debug, thiserror::Error)]
38pub enum FilError<E>
39where
40 E: std::error::Error,
41{
42 #[error("Ledger | {0}")]
43 Ledger(#[from] LedgerAppError<E>),
45
46 #[error("Secp256k1 error: {0}")]
48 Secp256k1(#[from] k256::elliptic_curve::Error),
49
50 #[error("Ecdsa error: {0}")]
52 Ecdsa(#[from] k256::ecdsa::Error),
53}
54
55pub struct FilecoinApp<E> {
57 apdu_transport: E,
58}
59
60impl<E: Exchange> App for FilecoinApp<E> {
61 const CLA: u8 = CLA;
62}
63
64pub struct Address {
66 pub public_key: k256::PublicKey,
68
69 pub addr_byte: [u8; 21],
71
72 pub addr_string: String,
74}
75
76pub struct Signature {
78 pub r: [u8; 32],
80
81 pub s: [u8; 32],
83
84 pub v: u8,
86
87 pub sig: k256::ecdsa::Signature,
89}
90
91pub struct BIP44Path {
93 pub purpose: u32,
95 pub coin: u32,
97 pub account: u32,
99 pub change: u32,
101 pub index: u32,
103}
104
105impl BIP44Path {
106 pub fn serialize_bip44(&self) -> Vec<u8> {
108 use byteorder::{LittleEndian, WriteBytesExt};
109 let mut m = Vec::new();
110
111 m.write_u32::<LittleEndian>(self.purpose).unwrap();
112 m.write_u32::<LittleEndian>(self.coin).unwrap();
113 m.write_u32::<LittleEndian>(self.account).unwrap();
114 m.write_u32::<LittleEndian>(self.change).unwrap();
115 m.write_u32::<LittleEndian>(self.index).unwrap();
116
117 m
118 }
119}
120
121impl<E> FilecoinApp<E> {
122 pub const fn new(transport: E) -> Self {
124 FilecoinApp {
125 apdu_transport: transport,
126 }
127 }
128}
129
130impl<E> FilecoinApp<E>
131where
132 E: Exchange + Send + Sync,
133 E::Error: std::error::Error,
134{
135 pub async fn version(&self) -> Result<Version, FilError<E::Error>> {
137 <Self as AppExt<E>>::get_version(&self.apdu_transport)
138 .await
139 .map_err(Into::into)
140 }
141
142 pub async fn address(
144 &self,
145 path: &BIP44Path,
146 require_confirmation: bool,
147 ) -> Result<Address, FilError<E::Error>> {
148 let serialized_path = path.serialize_bip44();
149 let p1 = if require_confirmation { 1 } else { 0 };
150
151 let command = APDUCommand {
152 cla: CLA,
153 ins: InstructionCode::GetAddrSecp256k1 as _,
154 p1,
155 p2: 0x00,
156 data: serialized_path,
157 };
158
159 let response = self
160 .apdu_transport
161 .exchange(&command)
162 .await
163 .map_err(LedgerAppError::TransportError)?;
164
165 let response_data = response.data();
166 match response.error_code() {
167 Ok(APDUErrorCode::NoError) if response_data.len() < PK_LEN => {
168 return Err(FilError::Ledger(LedgerAppError::InvalidPK))
169 }
170 Ok(APDUErrorCode::NoError) => {}
171 Ok(err) => {
172 return Err(FilError::Ledger(LedgerAppError::AppSpecific(
173 err as _,
174 err.description(),
175 )))
176 }
177 Err(err) => {
178 return Err(FilError::Ledger(LedgerAppError::AppSpecific(
179 err,
180 "[APDU_ERROR] Unknown".to_string(),
181 )))
182 }
183 }
184
185 let public_key = k256::PublicKey::from_sec1_bytes(&response_data[..PK_LEN])?;
186 let mut addr_byte = [Default::default(); 21];
187 addr_byte.copy_from_slice(&response_data[PK_LEN + 1..PK_LEN + 1 + 21]);
188 let tmp =
189 str::from_utf8(&response_data[PK_LEN + 2 + 21..]).map_err(|_| LedgerAppError::Utf8)?;
190 let addr_string = tmp.to_owned();
191
192 Ok(Address {
193 public_key,
194 addr_byte,
195 addr_string,
196 })
197 }
198
199 pub async fn sign(
201 &self,
202 path: &BIP44Path,
203 message: &[u8],
204 ) -> Result<Signature, FilError<E::Error>> {
205 let bip44path = path.serialize_bip44();
206
207 let start_command = APDUCommand {
208 cla: CLA,
209 ins: InstructionCode::SignSecp256k1 as _,
210 p1: ChunkPayloadType::Init as u8,
211 p2: 0x00,
212 data: bip44path,
213 };
214
215 let response =
216 <Self as AppExt<E>>::send_chunks(&self.apdu_transport, start_command, message).await?;
217
218 let response_data = response.data();
219 match response.error_code() {
220 Ok(APDUErrorCode::NoError) if response_data.is_empty() => {
221 return Err(FilError::Ledger(LedgerAppError::NoSignature))
222 }
223 Ok(APDUErrorCode::NoError) if response_data.len() < 3 => {
225 return Err(FilError::Ledger(LedgerAppError::InvalidSignature))
226 }
227 Ok(APDUErrorCode::NoError) => {}
228 Ok(err) => {
229 return Err(FilError::Ledger(LedgerAppError::AppSpecific(
230 err as _,
231 err.description(),
232 )))
233 }
234 Err(err) => {
235 return Err(FilError::Ledger(LedgerAppError::AppSpecific(
236 err,
237 "[APDU_ERROR] Unknown".to_string(),
238 )))
239 }
240 }
241
242 let mut r = [0; 32];
243 r.copy_from_slice(&response_data[..32]);
244
245 let mut s = [0; 32];
246 s.copy_from_slice(&response_data[32..64]);
247
248 let v = response_data[64];
249
250 let sig = k256::ecdsa::Signature::from_der(&response_data[65..])?;
251
252 let signature = Signature { r, s, v, sig };
253
254 Ok(signature)
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::BIP44Path;
261
262 #[test]
263 fn bip44() {
264 let path = BIP44Path {
265 purpose: 0x8000_0000 | 0x2c,
266 coin: 0x8000_0000 | 1,
267 account: 0x1234,
268 change: 0,
269 index: 0x5678,
270 };
271 let serialized_path = path.serialize_bip44();
272 assert_eq!(serialized_path.len(), 20);
273 assert_eq!(
274 hex::encode(&serialized_path),
275 "2c00008001000080341200000000000078560000"
276 );
277 }
278}