ckb_sdk/unlock/
unlocker.rs

1use anyhow::anyhow;
2use ckb_types::{
3    bytes::Bytes,
4    core::TransactionView,
5    packed::{self, Byte32, BytesOpt, WitnessArgs},
6    prelude::*,
7};
8use thiserror::Error;
9
10use super::{
11    omni_lock::{ConfigError, OmniLockFlags},
12    signer::{
13        AcpScriptSigner, ChequeAction, ChequeScriptSigner, MultisigConfig, ScriptSignError,
14        ScriptSigner, SecpMultisigScriptSigner, SecpSighashScriptSigner,
15    },
16    OmniLockConfig, OmniLockScriptSigner, OmniUnlockMode,
17};
18use crate::traits::{Signer, TransactionDependencyError, TransactionDependencyProvider};
19use crate::types::ScriptGroup;
20
21const CHEQUE_CLAIM_SINCE: u64 = 0;
22const CHEQUE_WITHDRAW_SINCE: u64 = 0xA000000000000006;
23
24#[derive(Error, Debug)]
25pub enum UnlockError {
26    #[error("sign script error: `{0}`")]
27    ScriptSigner(#[from] ScriptSignError),
28
29    #[error("transaction dependency error: `{0}`")]
30    TxDep(#[from] TransactionDependencyError),
31
32    #[error("invalid witness args: witness index=`{0}`")]
33    InvalidWitnessArgs(usize),
34
35    #[error("there is an configuration error: `{0}`")]
36    InvalidConfig(#[from] ConfigError),
37
38    #[error("sign context is incorrect")]
39    SignContextTypeIncorrect,
40
41    #[error(transparent)]
42    Other(#[from] anyhow::Error),
43}
44
45/// Script unlock logic:
46///   * Parse the script.args
47///   * Sign the transaction
48///   * Put extra unlock information into transaction (e.g. SMT proof in omni-lock case)
49///
50/// See example in `examples/script_unlocker_example.rs`
51#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
52#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
53pub trait ScriptUnlocker: Sync + Send {
54    fn match_args(&self, args: &[u8]) -> bool;
55
56    /// Check if the script group is already unlocked
57    async fn is_unlocked_async(
58        &self,
59        _tx: &TransactionView,
60        _script_group: &ScriptGroup,
61        _tx_dep_provider: &dyn TransactionDependencyProvider,
62    ) -> Result<bool, UnlockError> {
63        Ok(false)
64    }
65    #[cfg(not(target_arch = "wasm32"))]
66    fn is_unlocked(
67        &self,
68        tx: &TransactionView,
69        script_group: &ScriptGroup,
70        tx_dep_provider: &dyn TransactionDependencyProvider,
71    ) -> Result<bool, UnlockError> {
72        crate::rpc::block_on(self.is_unlocked_async(tx, script_group, tx_dep_provider))
73    }
74
75    /// Add signature or other information to witnesses, when the script is
76    /// already unlocked should reset the witness instead.
77    async fn unlock_async(
78        &self,
79        tx: &TransactionView,
80        script_group: &ScriptGroup,
81        tx_dep_provider: &dyn TransactionDependencyProvider,
82    ) -> Result<TransactionView, UnlockError>;
83    #[cfg(not(target_arch = "wasm32"))]
84    fn unlock(
85        &self,
86        tx: &TransactionView,
87        script_group: &ScriptGroup,
88        tx_dep_provider: &dyn TransactionDependencyProvider,
89    ) -> Result<TransactionView, UnlockError> {
90        crate::rpc::block_on(self.unlock_async(tx, script_group, tx_dep_provider))
91    }
92
93    fn clear_placeholder_witness(
94        &self,
95        tx: &TransactionView,
96        _script_group: &ScriptGroup,
97    ) -> Result<TransactionView, UnlockError> {
98        Ok(tx.clone())
99    }
100
101    /// Fill a placehodler witness before balance the transaction capacity
102    async fn fill_placeholder_witness_async(
103        &self,
104        tx: &TransactionView,
105        script_group: &ScriptGroup,
106        tx_dep_provider: &dyn TransactionDependencyProvider,
107    ) -> Result<TransactionView, UnlockError>;
108    #[cfg(not(target_arch = "wasm32"))]
109    fn fill_placeholder_witness(
110        &self,
111        tx: &TransactionView,
112        script_group: &ScriptGroup,
113        tx_dep_provider: &dyn TransactionDependencyProvider,
114    ) -> Result<TransactionView, UnlockError> {
115        crate::rpc::block_on(self.fill_placeholder_witness_async(tx, script_group, tx_dep_provider))
116    }
117}
118
119pub fn fill_witness_lock(
120    tx: &TransactionView,
121    script_group: &ScriptGroup,
122    lock_field: Bytes,
123) -> Result<TransactionView, UnlockError> {
124    let witness_idx = script_group.input_indices[0];
125    let mut witnesses: Vec<packed::Bytes> = tx.witnesses().into_iter().collect();
126    while witnesses.len() <= witness_idx {
127        witnesses.push(Default::default());
128    }
129    let witness_data = witnesses[witness_idx].raw_data();
130    let mut witness = if witness_data.is_empty() {
131        WitnessArgs::default()
132    } else {
133        WitnessArgs::from_slice(witness_data.as_ref())
134            .map_err(|_| UnlockError::InvalidWitnessArgs(witness_idx))?
135    };
136    if witness.lock().is_none() {
137        witness = witness.as_builder().lock(Some(lock_field).pack()).build();
138    }
139    witnesses[witness_idx] = witness.as_bytes().pack();
140    Ok(tx.as_advanced_builder().set_witnesses(witnesses).build())
141}
142
143pub fn reset_witness_lock(
144    tx: TransactionView,
145    witness_idx: usize,
146) -> Result<TransactionView, usize> {
147    let mut witnesses: Vec<packed::Bytes> = tx.witnesses().into_iter().collect();
148    if let Some(witness_data) = witnesses
149        .get(witness_idx)
150        .map(|data| data.raw_data())
151        .filter(|data| !data.is_empty())
152    {
153        let witness = WitnessArgs::from_slice(witness_data.as_ref()).map_err(|_| witness_idx)?;
154        let data = if witness.input_type().is_none() && witness.output_type().is_none() {
155            Bytes::default()
156        } else {
157            witness
158                .as_builder()
159                .lock(BytesOpt::default())
160                .build()
161                .as_bytes()
162        };
163        witnesses[witness_idx] = data.pack();
164        Ok(tx.as_advanced_builder().set_witnesses(witnesses).build())
165    } else {
166        Ok(tx)
167    }
168}
169
170pub struct SecpSighashUnlocker {
171    signer: SecpSighashScriptSigner,
172}
173impl SecpSighashUnlocker {
174    pub fn new(signer: SecpSighashScriptSigner) -> SecpSighashUnlocker {
175        SecpSighashUnlocker { signer }
176    }
177}
178impl From<Box<dyn Signer>> for SecpSighashUnlocker {
179    fn from(signer: Box<dyn Signer>) -> SecpSighashUnlocker {
180        SecpSighashUnlocker::new(SecpSighashScriptSigner::new(signer))
181    }
182}
183#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
184#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
185impl ScriptUnlocker for SecpSighashUnlocker {
186    fn match_args(&self, args: &[u8]) -> bool {
187        self.signer.match_args(args)
188    }
189
190    async fn unlock_async(
191        &self,
192        tx: &TransactionView,
193        script_group: &ScriptGroup,
194        _tx_dep_provider: &dyn TransactionDependencyProvider,
195    ) -> Result<TransactionView, UnlockError> {
196        Ok(self.signer.sign_tx(tx, script_group)?)
197    }
198
199    async fn fill_placeholder_witness_async(
200        &self,
201        tx: &TransactionView,
202        script_group: &ScriptGroup,
203        _tx_dep_provider: &dyn TransactionDependencyProvider,
204    ) -> Result<TransactionView, UnlockError> {
205        fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]))
206    }
207}
208
209pub struct SecpMultisigUnlocker {
210    signer: SecpMultisigScriptSigner,
211}
212impl SecpMultisigUnlocker {
213    pub fn new(signer: SecpMultisigScriptSigner) -> SecpMultisigUnlocker {
214        SecpMultisigUnlocker { signer }
215    }
216}
217impl From<(Box<dyn Signer>, MultisigConfig)> for SecpMultisigUnlocker {
218    fn from((signer, config): (Box<dyn Signer>, MultisigConfig)) -> SecpMultisigUnlocker {
219        SecpMultisigUnlocker::new(SecpMultisigScriptSigner::new(signer, config))
220    }
221}
222#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
223#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
224impl ScriptUnlocker for SecpMultisigUnlocker {
225    fn match_args(&self, args: &[u8]) -> bool {
226        (args.len() == 20 || args.len() == 28) && self.signer.match_args(args)
227    }
228
229    async fn unlock_async(
230        &self,
231        tx: &TransactionView,
232        script_group: &ScriptGroup,
233        _tx_dep_provider: &dyn TransactionDependencyProvider,
234    ) -> Result<TransactionView, UnlockError> {
235        Ok(self.signer.sign_tx(tx, script_group)?)
236    }
237
238    async fn fill_placeholder_witness_async(
239        &self,
240        tx: &TransactionView,
241        script_group: &ScriptGroup,
242        _tx_dep_provider: &dyn TransactionDependencyProvider,
243    ) -> Result<TransactionView, UnlockError> {
244        let config = self.signer.config();
245        let config_data = config.to_witness_data();
246        let mut zero_lock = vec![0u8; config_data.len() + 65 * (config.threshold() as usize)];
247        zero_lock[0..config_data.len()].copy_from_slice(&config_data);
248        fill_witness_lock(tx, script_group, Bytes::from(zero_lock))
249    }
250}
251
252pub struct AcpUnlocker {
253    signer: AcpScriptSigner,
254}
255
256impl AcpUnlocker {
257    pub fn new(signer: AcpScriptSigner) -> AcpUnlocker {
258        AcpUnlocker { signer }
259    }
260}
261impl From<Box<dyn Signer>> for AcpUnlocker {
262    fn from(signer: Box<dyn Signer>) -> AcpUnlocker {
263        AcpUnlocker::new(AcpScriptSigner::new(signer))
264    }
265}
266
267async fn acp_is_unlocked(
268    tx: &TransactionView,
269    script_group: &ScriptGroup,
270    tx_dep_provider: &dyn TransactionDependencyProvider,
271    acp_args: &[u8],
272) -> Result<bool, UnlockError> {
273    const POW10: [u64; 20] = [
274        1,
275        10,
276        100,
277        1000,
278        10000,
279        100000,
280        1000000,
281        10000000,
282        100000000,
283        1000000000,
284        10000000000,
285        100000000000,
286        1000000000000,
287        10000000000000,
288        100000000000000,
289        1000000000000000,
290        10000000000000000,
291        100000000000000000,
292        1000000000000000000,
293        10000000000000000000,
294    ];
295    let min_ckb_amount = if acp_args.is_empty() {
296        0
297    } else {
298        let idx = acp_args[0];
299        if idx >= 20 {
300            return Err(UnlockError::Other(anyhow!("invalid min ckb amount config in script.args, got: {}, expected: value >=0 and value < 20", idx)));
301        }
302        POW10[idx as usize]
303    };
304    let min_udt_amount = if acp_args.len() > 1 {
305        let idx = acp_args[1];
306        if idx >= 39 {
307            return Err(UnlockError::Other(anyhow!("invalid min udt amount config in script.args, got: {}, expected: value >=0 and value < 39", idx)));
308        }
309        if idx >= 20 {
310            (POW10[19] as u128) * (POW10[idx as usize - 19] as u128)
311        } else {
312            POW10[idx as usize] as u128
313        }
314    } else {
315        0
316    };
317
318    struct InputWallet {
319        type_hash_opt: Option<Byte32>,
320        ckb_amount: u64,
321        udt_amount: u128,
322        output_cnt: usize,
323    }
324    let mut input_wallets = Vec::new();
325
326    for idx in script_group.input_indices.iter() {
327        let input = tx
328            .inputs()
329            .get(*idx)
330            .ok_or_else(|| anyhow!("input index in script group is out of bound: {}", idx))?;
331        let output = tx_dep_provider
332            .get_cell_async(&input.previous_output())
333            .await?;
334        let output_data = tx_dep_provider
335            .get_cell_data_async(&input.previous_output())
336            .await?;
337
338        let type_hash_opt = output
339            .type_()
340            .to_opt()
341            .map(|script| script.calc_script_hash());
342        if type_hash_opt.is_some() && output_data.len() < 16 {
343            return Err(UnlockError::Other(anyhow!(
344                "invalid udt output data in input cell: {:?}",
345                input
346            )));
347        }
348        let udt_amount = if type_hash_opt.is_some() {
349            let mut amount_bytes = [0u8; 16];
350            amount_bytes.copy_from_slice(&output_data[0..16]);
351            u128::from_le_bytes(amount_bytes)
352        } else {
353            0
354        };
355        input_wallets.push(InputWallet {
356            type_hash_opt,
357            ckb_amount: output.capacity().unpack(),
358            udt_amount,
359            output_cnt: 0,
360        })
361    }
362
363    for (output_idx, output) in tx.outputs().into_iter().enumerate() {
364        if output.lock() != script_group.script {
365            continue;
366        }
367        let output_data: Bytes = tx
368            .outputs_data()
369            .get(output_idx)
370            .map(|data| data.raw_data())
371            .ok_or_else(|| {
372                anyhow!(
373                    "output data index in script group is out of bound: {}",
374                    output_idx
375                )
376            })?;
377        let type_hash_opt = output
378            .type_()
379            .to_opt()
380            .map(|script| script.calc_script_hash());
381        if type_hash_opt.is_some() && output_data.len() < 16 {
382            return Err(UnlockError::Other(anyhow!(
383                "invalid udt output data in output cell: index={}",
384                output_idx
385            )));
386        }
387        let ckb_amount: u64 = output.capacity().unpack();
388        let udt_amount = if type_hash_opt.is_some() {
389            let mut amount_bytes = [0u8; 16];
390            amount_bytes.copy_from_slice(&output_data[0..16]);
391            u128::from_le_bytes(amount_bytes)
392        } else {
393            0
394        };
395        let mut found_inputs = 0;
396        for input_wallet in &mut input_wallets {
397            if input_wallet.type_hash_opt == type_hash_opt {
398                let (min_output_ckb_amount, ckb_overflow) =
399                    input_wallet.ckb_amount.overflowing_add(min_ckb_amount);
400                let meet_ckb_cond = !ckb_overflow && ckb_amount >= min_output_ckb_amount;
401                let (min_output_udt_amount, udt_overflow) =
402                    input_wallet.udt_amount.overflowing_add(min_udt_amount);
403                let meet_udt_cond = !udt_overflow && udt_amount >= min_output_udt_amount;
404                if !(meet_ckb_cond || meet_udt_cond) {
405                    // ERROR_OUTPUT_AMOUNT_NOT_ENOUGH
406                    return Ok(false);
407                }
408                if (!meet_ckb_cond && ckb_amount != input_wallet.ckb_amount)
409                    || (!meet_udt_cond && udt_amount != input_wallet.udt_amount)
410                {
411                    // ERROR_OUTPUT_AMOUNT_NOT_ENOUGH
412                    return Ok(false);
413                }
414                found_inputs += 1;
415                input_wallet.output_cnt += 1;
416                if found_inputs > 1 {
417                    // ERROR_DUPLICATED_INPUTS
418                    return Ok(false);
419                }
420                if input_wallet.output_cnt > 1 {
421                    // ERROR_DUPLICATED_OUTPUTS
422                    return Ok(false);
423                }
424            }
425        }
426        if found_inputs != 1 {
427            // ERROR_NO_PAIR + ERROR_DUPLICATED_INPUTS
428            return Ok(false);
429        }
430    }
431    for input_wallet in &input_wallets {
432        if input_wallet.output_cnt != 1 {
433            // ERROR_NO_PAIR + ERROR_DUPLICATED_OUTPUTS
434            return Ok(false);
435        }
436    }
437    Ok(true)
438}
439
440#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
441#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
442impl ScriptUnlocker for AcpUnlocker {
443    fn match_args(&self, args: &[u8]) -> bool {
444        self.signer.match_args(args)
445    }
446
447    async fn is_unlocked_async(
448        &self,
449        tx: &TransactionView,
450        script_group: &ScriptGroup,
451        tx_dep_provider: &dyn TransactionDependencyProvider,
452    ) -> Result<bool, UnlockError> {
453        let raw_data = script_group.script.args().raw_data();
454        let acp_args = {
455            let data = raw_data.as_ref();
456            if data.len() > 20 {
457                &data[20..]
458            } else {
459                &[]
460            }
461        };
462        acp_is_unlocked(tx, script_group, tx_dep_provider, acp_args).await
463    }
464
465    async fn unlock_async(
466        &self,
467        tx: &TransactionView,
468        script_group: &ScriptGroup,
469        tx_dep_provider: &dyn TransactionDependencyProvider,
470    ) -> Result<TransactionView, UnlockError> {
471        if self
472            .is_unlocked_async(tx, script_group, tx_dep_provider)
473            .await?
474        {
475            self.clear_placeholder_witness(tx, script_group)
476        } else {
477            Ok(self.signer.sign_tx(tx, script_group)?)
478        }
479    }
480
481    fn clear_placeholder_witness(
482        &self,
483        tx: &TransactionView,
484        script_group: &ScriptGroup,
485    ) -> Result<TransactionView, UnlockError> {
486        reset_witness_lock(tx.clone(), script_group.input_indices[0])
487            .map_err(UnlockError::InvalidWitnessArgs)
488    }
489
490    async fn fill_placeholder_witness_async(
491        &self,
492        tx: &TransactionView,
493        script_group: &ScriptGroup,
494        tx_dep_provider: &dyn TransactionDependencyProvider,
495    ) -> Result<TransactionView, UnlockError> {
496        if self
497            .is_unlocked_async(tx, script_group, tx_dep_provider)
498            .await?
499        {
500            Ok(tx.clone())
501        } else {
502            fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]))
503        }
504    }
505}
506
507pub struct ChequeUnlocker {
508    signer: ChequeScriptSigner,
509}
510impl ChequeUnlocker {
511    pub fn new(signer: ChequeScriptSigner) -> ChequeUnlocker {
512        ChequeUnlocker { signer }
513    }
514}
515impl From<(Box<dyn Signer>, ChequeAction)> for ChequeUnlocker {
516    fn from((signer, action): (Box<dyn Signer>, ChequeAction)) -> ChequeUnlocker {
517        ChequeUnlocker::new(ChequeScriptSigner::new(signer, action))
518    }
519}
520
521#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
522#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
523impl ScriptUnlocker for ChequeUnlocker {
524    fn match_args(&self, args: &[u8]) -> bool {
525        self.signer.match_args(args)
526    }
527
528    async fn is_unlocked_async(
529        &self,
530        tx: &TransactionView,
531        script_group: &ScriptGroup,
532        tx_dep_provider: &dyn TransactionDependencyProvider,
533    ) -> Result<bool, UnlockError> {
534        let args = script_group.script.args().raw_data();
535        if args.len() != 40 {
536            return Err(UnlockError::Other(anyhow!(
537                "invalid script args length, expected: 40, got: {}",
538                args.len()
539            )));
540        }
541        let inputs: Vec<_> = tx.inputs().into_iter().collect();
542        let group_since_list: Vec<u64> = script_group
543            .input_indices
544            .iter()
545            .map(|idx| inputs[*idx].since().unpack())
546            .collect();
547
548        // Check if unlocked via lock hash in inputs
549        let receiver_lock_hash = &args.as_ref()[0..20];
550        let sender_lock_hash = &args.as_ref()[20..40];
551        let mut receiver_lock_witness = None;
552        let mut sender_lock_witness = None;
553        for (input_idx, input) in inputs.into_iter().enumerate() {
554            let output = tx_dep_provider
555                .get_cell_async(&input.previous_output())
556                .await?;
557            let lock_hash = output.lock().calc_script_hash();
558            let lock_hash_prefix = &lock_hash.as_slice()[0..20];
559            let witness = tx
560                .witnesses()
561                .get(input_idx)
562                .map(|witness| witness.raw_data())
563                .unwrap_or_default();
564
565            #[allow(clippy::collapsible_if)]
566            if lock_hash_prefix == receiver_lock_hash {
567                if receiver_lock_witness.is_none() {
568                    receiver_lock_witness = Some((input_idx, witness));
569                }
570            } else if lock_hash_prefix == sender_lock_hash {
571                if sender_lock_witness.is_none() {
572                    sender_lock_witness = Some((input_idx, witness));
573                }
574            }
575        }
576        // NOTE: receiver has higher priority than sender
577        if self.signer.action() == ChequeAction::Claim {
578            if let Some((_input_idx, witness)) = receiver_lock_witness {
579                if group_since_list
580                    .iter()
581                    .any(|since| *since != CHEQUE_CLAIM_SINCE)
582                {
583                    return Err(UnlockError::Other(anyhow!(
584                        "claim action must have all zero since in cheque inputs"
585                    )));
586                }
587                let witness_args = match WitnessArgs::from_slice(witness.as_ref()) {
588                    Ok(args) => args,
589                    Err(_) => {
590                        return Ok(false);
591                    }
592                };
593                if witness_args.lock().to_opt().is_none() {
594                    return Ok(false);
595                }
596                return Ok(true);
597            }
598        } else if let Some((_input_idx, witness)) = sender_lock_witness {
599            if group_since_list
600                .iter()
601                .any(|since| *since != CHEQUE_WITHDRAW_SINCE)
602            {
603                return Err(UnlockError::Other(anyhow!(
604                    "withdraw action must have all relative 6 epochs since in cheque inputs"
605                )));
606            }
607            let witness_args = match WitnessArgs::from_slice(witness.as_ref()) {
608                Ok(args) => args,
609                Err(_) => {
610                    return Ok(false);
611                }
612            };
613            if witness_args.lock().to_opt().is_none() {
614                return Ok(false);
615            }
616            return Ok(true);
617        }
618        Ok(false)
619    }
620
621    async fn unlock_async(
622        &self,
623        tx: &TransactionView,
624        script_group: &ScriptGroup,
625        tx_dep_provider: &dyn TransactionDependencyProvider,
626    ) -> Result<TransactionView, UnlockError> {
627        if self
628            .is_unlocked_async(tx, script_group, tx_dep_provider)
629            .await?
630        {
631            self.clear_placeholder_witness(tx, script_group)
632        } else {
633            Ok(self.signer.sign_tx(tx, script_group)?)
634        }
635    }
636
637    fn clear_placeholder_witness(
638        &self,
639        tx: &TransactionView,
640        script_group: &ScriptGroup,
641    ) -> Result<TransactionView, UnlockError> {
642        reset_witness_lock(tx.clone(), script_group.input_indices[0])
643            .map_err(UnlockError::InvalidWitnessArgs)
644    }
645
646    async fn fill_placeholder_witness_async(
647        &self,
648        tx: &TransactionView,
649        script_group: &ScriptGroup,
650        tx_dep_provider: &dyn TransactionDependencyProvider,
651    ) -> Result<TransactionView, UnlockError> {
652        if self
653            .is_unlocked_async(tx, script_group, tx_dep_provider)
654            .await?
655        {
656            Ok(tx.clone())
657        } else {
658            fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]))
659        }
660    }
661}
662
663pub struct OmniLockUnlocker {
664    signer: OmniLockScriptSigner,
665    config: OmniLockConfig,
666}
667impl OmniLockUnlocker {
668    pub fn new(signer: OmniLockScriptSigner, config: OmniLockConfig) -> OmniLockUnlocker {
669        OmniLockUnlocker { signer, config }
670    }
671}
672impl From<(Box<dyn Signer>, OmniLockConfig, OmniUnlockMode)> for OmniLockUnlocker {
673    fn from(
674        (signer, config, unlock_mode): (Box<dyn Signer>, OmniLockConfig, OmniUnlockMode),
675    ) -> OmniLockUnlocker {
676        let cfg = config.clone();
677        OmniLockUnlocker::new(OmniLockScriptSigner::new(signer, config, unlock_mode), cfg)
678    }
679}
680#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
681#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
682impl ScriptUnlocker for OmniLockUnlocker {
683    fn match_args(&self, args: &[u8]) -> bool {
684        self.signer.match_args(args)
685    }
686
687    /// Check if the script group is already unlocked
688    async fn is_unlocked_async(
689        &self,
690        tx: &TransactionView,
691        script_group: &ScriptGroup,
692        tx_dep_provider: &dyn TransactionDependencyProvider,
693    ) -> Result<bool, UnlockError> {
694        if self.config.omni_lock_flags().contains(OmniLockFlags::ACP) {
695            let raw_data = script_group.script.args().raw_data();
696            let acp_args = {
697                let mut offset = 22;
698                if self.config.omni_lock_flags().contains(OmniLockFlags::ADMIN) {
699                    offset += 32;
700                }
701                let data = raw_data.as_ref();
702                if data.len() > offset {
703                    &data[offset..]
704                } else {
705                    &[]
706                }
707            };
708            let acp_unlocked = acp_is_unlocked(tx, script_group, tx_dep_provider, acp_args).await?;
709            if acp_unlocked {
710                return Ok(true);
711            }
712        }
713        if !self.signer.config().is_ownerlock() {
714            return Ok(false);
715        }
716        let args = script_group.script.args().raw_data();
717        if args.len() < 22 {
718            return Err(UnlockError::Other(anyhow!(
719                "invalid script args length, expected not less than 22, got: {}",
720                args.len()
721            )));
722        }
723        // If use admin mode, should use the id in admin configuration
724        let auth_content = if self.config.omni_lock_flags().contains(OmniLockFlags::ADMIN) {
725            self.config
726                .get_admin_config()
727                .unwrap()
728                .get_auth()
729                .auth_content()
730        } else {
731            self.config.id().auth_content()
732        };
733
734        let inputs = tx.inputs();
735        if tx.inputs().len() < 2 {
736            return Err(UnlockError::Other(anyhow!(
737                "expect more than 1 input, got: {}",
738                inputs.len()
739            )));
740        }
741        let mut matched = false;
742        for (_idx, input) in tx
743            .inputs()
744            .into_iter()
745            .enumerate()
746            .filter(|(idx, _input)| !script_group.input_indices.contains(idx))
747        {
748            if let Ok(output) = tx_dep_provider
749                .get_cell_async(&input.previous_output())
750                .await
751            {
752                let lock_hash = output.calc_lock_hash();
753                let h = &lock_hash.as_slice()[0..20];
754                if h == auth_content.as_bytes() {
755                    matched = true;
756                    break;
757                }
758            }
759        }
760        if !matched {
761            return Err(UnlockError::Other(anyhow!(
762                "can not find according owner lock input"
763            )));
764        }
765        Ok(matched)
766    }
767
768    async fn unlock_async(
769        &self,
770        tx: &TransactionView,
771        script_group: &ScriptGroup,
772        _tx_dep_provider: &dyn TransactionDependencyProvider,
773    ) -> Result<TransactionView, UnlockError> {
774        Ok(self.signer.sign_tx(tx, script_group)?)
775    }
776
777    async fn fill_placeholder_witness_async(
778        &self,
779        tx: &TransactionView,
780        script_group: &ScriptGroup,
781        _tx_dep_provider: &dyn TransactionDependencyProvider,
782    ) -> Result<TransactionView, UnlockError> {
783        let config = self.signer.config();
784        let lock_field = config.placeholder_witness_lock(self.signer.unlock_mode())?;
785        fill_witness_lock(tx, script_group, lock_field)
786    }
787}
788#[cfg(test)]
789mod anyhow_tests {
790    use anyhow::anyhow;
791    #[test]
792    fn test_unlock_error() {
793        let error = super::UnlockError::InvalidWitnessArgs(0);
794        let error = anyhow!(error);
795        assert_eq!("invalid witness args: witness index=`0`", error.to_string());
796    }
797}