Skip to main content

lwk_jade/
lib.rs

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
53/// Vendor ID and Product ID to filter blockstream JADEs on the serial.
54///
55/// Note these refer to the usb serial chip not to the JADE itself, so you may have false-positive.
56///
57/// Note that DYI device may be filtered out by these.
58///
59/// Taken from reference impl <https://github.com/Blockstream/Jade/blob/f7fc4de8c3662b082c7d41e9354c4ff573f371ff/jadepy/jade_serial.py#L24>
60pub const JADE_DEVICE_IDS: [(u16, u16); 6] = [
61    (0x10c4, 0xea60),
62    (0x1a86, 0x55d4),
63    (0x0403, 0x6001),
64    (0x1a86, 0x7523),
65    // new
66    (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    // serde_cbor::to_value doesn't exist
114    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(); // Jade want it reversed
134        let unblinded = output.script_pubkey.is_empty() || output.script_pubkey == burn_script;
135        let trusted_commitment = if unblinded {
136            // fee output or burn output
137            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                // This ensures that path has at least 2 elements
174                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                                    // path has at least 2 elements
201                                    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                                                // FIXME: here we should only pass the paths that were
210                                                // not passed when calling register_multisig. However
211                                                // deducing them now is not trivial, thus we only take
212                                                // the last 2 elements in the derivation path which we
213                                                // expect to be "0|1,*"
214                                                let v = derivation_path_to_vec(path);
215                                                // path has at least 2 elements
216                                                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; // No need to check for more multisigs
229                                        }
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        // TODO: handle token metadata
257    }
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
271// Get a script from witness script pubkey hash
272fn script_code_wpkh(script: &Script) -> Script {
273    assert!(script.is_v0_p2wpkh());
274    // ugly segwit stuff
275    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
282// taken and adapted from:
283// https://github.com/rust-bitcoin/rust-bitcoin/blob/37daf4620c71dc9332c3e08885cf9de696204bca/bitcoin/src/blockdata/script/borrowed.rs#L266
284// TODO remove once it's released
285fn 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(&not_multisig));
374    }
375}