1use serde::Deserialize;
5use std::fmt::Debug;
6
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
9pub enum CkTapError {
10 #[error(transparent)]
11 Card(#[from] CardError),
12 #[error("CBOR deserialization error: {0}")]
13 CborDe(String),
14 #[error("CBOR value error: {0}")]
15 CborValue(String),
16 #[error("APDU transport error: {0}")]
17 Transport(String),
18 #[error("Unknown card type")]
19 UnknownCardType,
20}
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
24pub enum CardError {
25 #[error("Rare or unlucky value used/occurred. Start again")]
26 UnluckyNumber,
27 #[error("Invalid/incorrect/incomplete arguments provided to command")]
28 BadArguments,
29 #[error("Authentication details (CVC/epubkey) are wrong")]
30 BadAuth,
31 #[error("Command requires auth, and none was provided")]
32 NeedsAuth,
33 #[error("The 'cmd' field is an unsupported command")]
34 UnknownCommand,
35 #[error("Command is not valid at this time, no point retrying")]
36 InvalidCommand,
37 #[error("You can't do that right now when card is in this state")]
38 InvalidState,
39 #[error("Nonce is not unique-looking enough")]
40 WeakNonce,
41 #[error("Unable to decode CBOR data stream")]
42 BadCBOR,
43 #[error("Can't change CVC without doing a backup first")]
44 BackupFirst,
45 #[error("Due to auth failures, delay required")]
46 RateLimited,
47}
48
49impl CardError {
50 pub fn error_from_code(code: u16) -> Option<CardError> {
51 match code {
52 205 => Some(CardError::UnluckyNumber),
53 400 => Some(CardError::BadArguments),
54 401 => Some(CardError::BadAuth),
55 403 => Some(CardError::NeedsAuth),
56 404 => Some(CardError::UnknownCommand),
57 405 => Some(CardError::InvalidCommand),
58 406 => Some(CardError::InvalidState),
59 417 => Some(CardError::WeakNonce),
60 422 => Some(CardError::BadCBOR),
61 425 => Some(CardError::BackupFirst),
62 429 => Some(CardError::RateLimited),
63 _ => None,
64 }
65 }
66
67 pub fn error_code(&self) -> u16 {
68 match self {
69 CardError::UnluckyNumber => 205,
70 CardError::BadArguments => 400,
71 CardError::BadAuth => 401,
72 CardError::NeedsAuth => 403,
73 CardError::UnknownCommand => 404,
74 CardError::InvalidCommand => 405,
75 CardError::InvalidState => 406,
76 CardError::WeakNonce => 417,
77 CardError::BadCBOR => 422,
78 CardError::BackupFirst => 425,
79 CardError::RateLimited => 429,
80 }
81 }
82}
83
84impl<T> From<ciborium::de::Error<T>> for CkTapError
85where
86 T: Debug,
87{
88 fn from(e: ciborium::de::Error<T>) -> Self {
89 CkTapError::CborDe(e.to_string())
90 }
91}
92
93impl From<ciborium::value::Error> for CkTapError {
94 fn from(e: ciborium::value::Error) -> Self {
95 CkTapError::CborValue(e.to_string())
96 }
97}
98
99#[cfg(feature = "pcsc")]
100impl From<pcsc::Error> for CkTapError {
101 fn from(e: pcsc::Error) -> Self {
102 CkTapError::Transport(e.to_string())
103 }
104}
105
106#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
107pub struct ErrorResponse {
108 pub error: String,
109 pub code: u16,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
114pub enum StatusError {
115 #[error(transparent)]
116 CkTap(#[from] CkTapError),
117 #[error(transparent)]
118 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
119}
120
121#[cfg(feature = "pcsc")]
122impl From<pcsc::Error> for StatusError {
123 fn from(e: pcsc::Error) -> Self {
124 StatusError::CkTap(CkTapError::Transport(e.to_string()))
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
130pub enum ChangeError {
131 #[error(transparent)]
132 CkTap(#[from] CkTapError),
133 #[error("new cvc is too short, must be at least 6 bytes, was only {0} bytes")]
134 TooShort(usize),
135 #[error("new cvc is too long, must be at most 32 bytes, was {0} bytes")]
136 TooLong(usize),
137 #[error("new cvc is the same as the old one")]
138 SameAsOld,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
143pub enum ReadError {
144 #[error(transparent)]
145 CkTap(#[from] CkTapError),
146 #[error(transparent)]
147 Secp256k1(#[from] bitcoin::secp256k1::Error),
148 #[error(transparent)]
149 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
150}
151
152impl From<ReadError> for CertsError {
153 fn from(e: ReadError) -> Self {
154 match e {
155 ReadError::CkTap(e) => CertsError::CkTap(e),
156 ReadError::Secp256k1(e) => CertsError::Secp256k1(e),
157 ReadError::KeyFromSlice(e) => CertsError::KeyFromSlice(e),
158 }
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
164pub enum CertsError {
165 #[error(transparent)]
166 CkTap(#[from] CkTapError),
167 #[error(transparent)]
168 Secp256k1(#[from] bitcoin::secp256k1::Error),
169 #[error(transparent)]
170 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
171 #[error("Root cert is not from Coinkite. Card is counterfeit: {0}")]
172 InvalidRootCert(String),
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
177pub enum DeriveError {
178 #[error(transparent)]
179 CkTap(#[from] CkTapError),
180 #[error(transparent)]
181 Secp256k1(#[from] bitcoin::secp256k1::Error),
182 #[error(transparent)]
183 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
184 #[error("Invalid chain code: {0}")]
185 InvalidChainCode(String),
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
190pub enum XpubError {
191 #[error(transparent)]
192 CkTap(#[from] CkTapError),
193 #[error(transparent)]
194 Bip32(#[from] bitcoin::bip32::Error),
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
199pub enum UnsealError {
200 #[error(transparent)]
201 CkTap(#[from] CkTapError),
202 #[error(transparent)]
203 Secp256k1(#[from] bitcoin::secp256k1::Error),
204 #[error(transparent)]
205 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
210pub enum DumpError {
211 #[error(transparent)]
212 CkTap(#[from] CkTapError),
213 #[error(transparent)]
214 Secp256k1(#[from] bitcoin::secp256k1::Error),
215 #[error(transparent)]
216 KeyFromSlice(#[from] bitcoin::key::FromSliceError),
217 #[error("Slot is sealed: {0}")]
218 SlotSealed(u8),
219 #[error("Slot is unused: {0}")]
220 SlotUnused(u8),
221 #[error("Slot was unsealed improperly: {0}")]
225 SlotTampered(u8),
226}
227
228#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
229pub enum SignPsbtError {
230 #[error("Invalid path at index: {0}")]
231 InvalidPath(usize),
232 #[error("Invalid script at index: {0}")]
233 InvalidScript(usize),
234 #[error("Missing pubkey at index: {0}")]
235 MissingPubkey(usize),
236 #[error("Missing UTXO at index: {0}")]
237 MissingUtxo(usize),
238 #[error("Pubkey mismatch at index: {0}")]
239 PubkeyMismatch(usize),
240 #[error("Sighash error: {0}")]
241 SighashError(String),
242 #[error("Signature error: {0}")]
243 SignatureError(String),
244 #[error("Signing slot is not unsealed: {0}")]
245 SlotNotUnsealed(u8),
246 #[error(transparent)]
247 CkTap(#[from] CkTapError),
248 #[error("Witness program error: {0}")]
249 WitnessProgram(String),
250}