1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), deny(clippy::unwrap_used))]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5#[cfg(feature = "asyncr")]
6pub mod asyncr;
7
8pub mod consts;
9pub mod error;
10pub mod get_receive_address;
11pub mod protocol;
12pub mod register_multisig;
13pub mod sign_liquid_tx;
14
15#[cfg(feature = "test_emulator")]
16mod jade_emulator;
17
18#[cfg(feature = "test_emulator")]
19pub use jade_emulator::TestJadeEmulator;
20
21#[cfg(feature = "sync")]
22mod sync;
23
24use std::collections::HashSet;
25
26pub use consts::{BAUD_RATE, TIMEOUT};
27use elements::{
28 bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint},
29 encode::serialize,
30 hex::ToHex,
31 opcodes::{
32 all::{OP_CHECKMULTISIG, OP_PUSHNUM_1, OP_PUSHNUM_16},
33 All,
34 },
35 pset::PartiallySignedTransaction,
36 script::Instruction,
37 Script,
38};
39pub use error::Error;
40use get_receive_address::{SingleOrMulti, Variant};
41use lwk_common::{burn_script, Network};
42
43use register_multisig::RegisteredMultisigDetails;
44use sign_liquid_tx::{AssetInfo, Change, Commitment, Contract, Prevout, SignLiquidTxParams};
45#[cfg(feature = "sync")]
46pub use sync::Jade;
47
48#[cfg(feature = "serial")]
49pub use serialport;
50
51pub type Result<T> = std::result::Result<T, error::Error>;
52
53pub const JADE_DEVICE_IDS: [(u16, u16); 6] = [
61 (0x10c4, 0xea60),
62 (0x1a86, 0x55d4),
63 (0x0403, 0x6001),
64 (0x1a86, 0x7523),
65 (0x303a, 0x4001),
67 (0x303a, 0x1001),
68];
69
70const CHANGE_CHAIN: ChildNumber = ChildNumber::Normal { index: 1 };
71
72fn try_parse_response<T>(reader: &[u8]) -> Option<Result<T>>
73where
74 T: std::fmt::Debug + serde::de::DeserializeOwned,
75{
76 match serde_cbor::from_reader::<protocol::Response<T>, &[u8]>(reader) {
77 Ok(r) => {
78 if let Some(result) = r.result {
79 log::debug!(
80 "\n<---\t{:?}\n\t({} bytes) {}",
81 &result,
82 reader.len(),
83 hex::encode(reader)
84 );
85 return Some(Ok(result));
86 }
87 if let Some(error) = r.error {
88 return Some(Err(Error::JadeError(error)));
89 }
90 return Some(Err(Error::JadeNeitherErrorNorResult));
91 }
92
93 Err(e) => {
94 let res = serde_cbor::from_reader::<serde_cbor::Value, &[u8]>(reader);
95 if let Ok(value) = res {
96 log::warn!("The value returned is a valid CBOR, but our structs doesn't map it correctly: {value:?}");
97 return Some(Err(Error::SerdeCbor(e)));
98 }
99 }
100 }
101 None
102}
103
104pub fn derivation_path_to_vec(path: &DerivationPath) -> Vec<u32> {
105 path.into_iter().map(|e| (*e).into()).collect()
106}
107
108pub(crate) fn vec_to_derivation_path(path: &[u32]) -> DerivationPath {
109 DerivationPath::from_iter(path.iter().cloned().map(Into::into))
110}
111
112pub(crate) fn json_to_cbor(value: &serde_json::Value) -> Result<serde_cbor::Value> {
113 Ok(serde_cbor::from_slice(&serde_cbor::to_vec(&value)?)?)
115}
116
117fn create_jade_sign_req(
118 pset: &mut PartiallySignedTransaction,
119 my_fingerprint: Fingerprint,
120 multisigs_details: Vec<RegisteredMultisigDetails>,
121 network: Network,
122) -> Result<SignLiquidTxParams> {
123 let tx = pset.extract_tx()?;
124 let txn = serialize(&tx);
125 let burn_script = burn_script();
126 let mut asset_ids_in_tx = HashSet::new();
127 let mut trusted_commitments = vec![];
128 let mut changes = vec![];
129 for (i, output) in pset.outputs().iter().enumerate() {
130 let asset_id = output.asset.ok_or(Error::MissingAssetIdInOutput(i))?;
131 asset_ids_in_tx.insert(asset_id);
132 let mut asset_id = serialize(&asset_id);
133 asset_id.reverse(); let unblinded = output.script_pubkey.is_empty() || output.script_pubkey == burn_script;
135 let trusted_commitment = if unblinded {
136 None
138 } else {
139 Some(Commitment {
140 asset_blind_proof: output
141 .blind_asset_proof
142 .as_ref()
143 .ok_or(Error::MissingBlindAssetProofInOutput(i))?
144 .serialize(),
145 asset_generator: output
146 .asset_comm
147 .ok_or(Error::MissingAssetCommInOutput(i))?
148 .serialize()
149 .to_vec(),
150 asset_id,
151 blinding_key: output
152 .blinding_key
153 .ok_or(Error::MissingBlindingKeyInOutput(i))?
154 .to_bytes(),
155 value: output.amount.ok_or(Error::MissingAmountInOutput(i))?,
156 value_commitment: output
157 .amount_comm
158 .ok_or(Error::MissingAmountCommInOutput(i))?
159 .serialize()
160 .to_vec(),
161 value_blind_proof: output
162 .blind_value_proof
163 .as_ref()
164 .ok_or(Error::MissingBlindValueProofInOutput(i))?
165 .serialize(),
166 })
167 };
168 trusted_commitments.push(trusted_commitment);
169
170 let mut change = None;
171 for (fingerprint, path) in output.bip32_derivation.values() {
172 if fingerprint == &my_fingerprint {
173 let is_change = path.clone().into_iter().nth_back(1) == Some(&CHANGE_CHAIN);
175 if is_change {
176 if output.script_pubkey.is_v0_p2wpkh() {
177 change = Some(Change {
178 address: SingleOrMulti::Single {
179 variant: Variant::Wpkh,
180 path: derivation_path_to_vec(path),
181 },
182 is_change,
183 });
184 } else if output.script_pubkey.is_p2sh() {
185 if let Some(redeem_script) = output.redeem_script.as_ref() {
186 if redeem_script.is_v0_p2wpkh() {
187 change = Some(Change {
188 address: SingleOrMulti::Single {
189 variant: Variant::ShWpkh,
190 path: derivation_path_to_vec(path),
191 },
192 is_change,
193 });
194 }
195 }
196 } else if output.script_pubkey.is_v0_p2wsh() {
197 if let Some(witness_script) = output.witness_script.as_ref() {
198 if is_multisig(witness_script) {
199 for details in &multisigs_details {
200 let index = path[path.len() - 1];
202 if let Ok(derived_witness_script) = details
203 .descriptor
204 .derive_witness_script(is_change, index.into())
205 {
206 if witness_script == &derived_witness_script {
207 let mut paths = vec![];
208 for _ in 0..details.descriptor.signers.len() {
209 let v = derivation_path_to_vec(path);
215 let v = v[(path.len() - 2)..].to_vec();
217 paths.push(v);
218 }
219 change = Some(Change {
220 address: SingleOrMulti::Multi {
221 multisig_name: details
222 .multisig_name
223 .to_string(),
224 paths,
225 },
226 is_change,
227 });
228 break; }
230 }
231 }
232 }
233 }
234 }
235 }
236 }
237 }
238 changes.push(change);
239 }
240 let mut assets_info = vec![];
241 for asset_id in asset_ids_in_tx {
242 if let Some(Ok(meta)) = pset.get_asset_metadata(asset_id) {
243 if let Ok(contract) = serde_json::from_str::<Contract>(meta.contract()) {
244 let asset_info = AssetInfo {
245 asset_id: asset_id.to_string(),
246 contract,
247 issuance_prevout: Prevout {
248 txid: meta.issuance_prevout().txid.to_hex(),
249 vout: meta.issuance_prevout().vout,
250 },
251 };
252
253 assets_info.push(asset_info);
254 }
255 }
256 }
258 let params = SignLiquidTxParams {
259 network,
260 txn,
261 num_inputs: tx.input.len() as u32,
262 use_ae_signatures: true,
263 change: changes,
264 asset_info: assets_info,
265 trusted_commitments,
266 additional_info: None,
267 };
268 Ok(params)
269}
270
271fn script_code_wpkh(script: &Script) -> Script {
273 assert!(script.is_v0_p2wpkh());
274 let mut script_code = vec![0x76u8, 0xa9, 0x14];
276 script_code.extend(&script.as_bytes()[2..]);
277 script_code.push(0x88);
278 script_code.push(0xac);
279 Script::from(script_code)
280}
281
282fn is_multisig(script: &Script) -> bool {
286 fn decode_pushnum(op: All) -> Option<u8> {
287 let start: u8 = OP_PUSHNUM_1.into_u8();
288 let end: u8 = OP_PUSHNUM_16.into_u8();
289 if start < op.into_u8() && end >= op.into_u8() {
290 Some(op.into_u8() - start + 1)
291 } else {
292 None
293 }
294 }
295
296 let required_sigs;
297
298 let mut instructions = script.instructions();
299 if let Some(Ok(Instruction::Op(op))) = instructions.next() {
300 if let Some(pushnum) = decode_pushnum(op) {
301 required_sigs = pushnum;
302 } else {
303 return false;
304 }
305 } else {
306 return false;
307 }
308
309 let mut num_pubkeys: u8 = 0;
310 while let Some(Ok(instruction)) = instructions.next() {
311 match instruction {
312 Instruction::PushBytes(_) => {
313 num_pubkeys += 1;
314 }
315 Instruction::Op(op) => {
316 if let Some(pushnum) = decode_pushnum(op) {
317 if pushnum != num_pubkeys {
318 return false;
319 }
320 }
321 break;
322 }
323 }
324 }
325
326 if required_sigs > num_pubkeys {
327 return false;
328 }
329
330 if let Some(Ok(Instruction::Op(op))) = instructions.next() {
331 if op != OP_CHECKMULTISIG {
332 return false;
333 }
334 } else {
335 return false;
336 }
337
338 instructions.next().is_none()
339}
340
341#[cfg(test)]
342mod test {
343 use std::str::FromStr;
344
345 use elements::Script;
346
347 use crate::{is_multisig, json_to_cbor};
348
349 fn cbor_to_json(value: serde_cbor::Value) -> Result<serde_json::Value, crate::Error> {
350 Ok(serde_json::to_value(value)?)
351 }
352
353 #[test]
354 fn json_to_cbor_roundtrip() {
355 let json = serde_json::json!({"foo": 8, "bar": [1, 2], "baz": "ciao"});
356 let cbor = json_to_cbor(&json).unwrap();
357 let back = cbor_to_json(cbor).unwrap();
358 assert_eq!(json, back);
359 }
360
361 #[test]
362 fn test_is_multisig() {
363 let multisig = Script::from_str("522102ebc62c20f1e09e169a88745f60f6dac878c92db5c7ed78c6703d2d0426a01f942102c2d59d677122bc292048833003fd5cb19d27d32896b1d0feec654c291f7ede9e52ae").unwrap();
364 assert_eq!(multisig.asm(), "OP_PUSHNUM_2 OP_PUSHBYTES_33 02ebc62c20f1e09e169a88745f60f6dac878c92db5c7ed78c6703d2d0426a01f94 OP_PUSHBYTES_33 02c2d59d677122bc292048833003fd5cb19d27d32896b1d0feec654c291f7ede9e OP_PUSHNUM_2 OP_CHECKMULTISIG");
365 assert!(is_multisig(&multisig));
366
367 let not_multisig =
368 Script::from_str("001414fe45f2c2a2b7c00d0940d694a3b6af6c9bf165").unwrap();
369 assert_eq!(
370 not_multisig.asm(),
371 "OP_0 OP_PUSHBYTES_20 14fe45f2c2a2b7c00d0940d694a3b6af6c9bf165"
372 );
373 assert!(!is_multisig(¬_multisig));
374 }
375}