Skip to main content

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