Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 117 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ckb-util = "=0.200.0"
ckb-error = "=0.200.0"
ckb-script = "=0.200.0"
ckb-chain-spec = "=0.200.0"
ckb-sdk = { version = "3.7.0", features = ["native-tls-vendored"] }
ckb-sdk = { version = "4.0.0", features = ["native-tls-vendored"] }
ckb-mock-tx-types = "=0.200.0"
ckb-signer = { path = "ckb-signer", version = "0.4.1" }
plugin-protocol = { path = "plugin-protocol", package = "ckb-cli-plugin-protocol", version = "=1.3.1" }
Expand Down
2 changes: 1 addition & 1 deletion ckb-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ anyhow = "1.0.63"
ckb-types = "=0.200.0"
ckb-hash = "=0.200.0"
ckb-crypto = { version = "=0.200.0", features = ["secp"] }
ckb-sdk = { version = "3.7.0", features = ["native-tls-vendored"] }
ckb-sdk = { version = "4.0.0", features = ["native-tls-vendored"] }
21 changes: 15 additions & 6 deletions src/subcommands/deploy/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use anyhow::{anyhow, Result};
use ckb_sdk::{
constants::{MULTISIG_TYPE_HASH, SIGHASH_TYPE_HASH},
constants::{MultisigScript, SIGHASH_TYPE_HASH},
traits::{
CellCollector, CellQueryOptions, DefaultCellCollector, DefaultHeaderDepResolver,
DefaultTransactionDependencyProvider, OffchainTransactionDependencyProvider, Signer,
Expand Down Expand Up @@ -79,10 +79,7 @@ pub fn build_tx<T: ChangeInfo>(
.iter()
.filter_map(|info| info.build_cell_output(lock_script, first_cell_input))
.unzip();
let mut cell_deps = vec![genesis_info.sighash_dep()];
if multisig_config.is_some() {
cell_deps.push(genesis_info.multisig_dep());
}

let mut unlockers = HashMap::new();
let signer = DummySigner {
args: vec![from_address.payload().args()],
Expand All @@ -93,10 +90,22 @@ pub fn build_tx<T: ChangeInfo>(
sighash_script_id,
Box::new(sighash_unlocker) as Box<dyn ScriptUnlocker>,
);

let mut cell_deps = vec![genesis_info.sighash_dep()];
if let Some(cfg) = multisig_config {
let multisig_script =
MultisigScript::try_from(cfg.lock_code_hash()).unwrap_or_else(|_err| {
panic!(
"Failed to get multisig script from {}",
cfg.lock_code_hash(),
)
});

cell_deps.push(genesis_info.multisig_dep(multisig_script));

let multisig_signer = SecpMultisigScriptSigner::new(Box::new(signer), cfg.clone());
let multisig_unlocker = SecpMultisigUnlocker::new(multisig_signer);
let multisig_script_id = ScriptId::new_type(MULTISIG_TYPE_HASH.clone());
let multisig_script_id = multisig_script.script_id();
unlockers.insert(
multisig_script_id,
Box::new(multisig_unlocker) as Box<dyn ScriptUnlocker>,
Expand Down
35 changes: 33 additions & 2 deletions src/subcommands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod wallet;

pub use account::AccountSubCommand;
pub use api_server::ApiServerSubCommand;
use ckb_sdk::constants::MultisigScript;
use ckb_types::H256;
pub use dao::DAOSubCommand;
pub use deploy::DeploySubCommand;
pub use mock_tx::MockTxSubCommand;
Expand All @@ -26,10 +28,13 @@ pub use tx::TxSubCommand;
pub use util::UtilSubCommand;
pub use wallet::{TransferArgs, WalletSubCommand};

use clap::ArgMatches;
use clap::{Arg, ArgMatches};
use serde::Serialize;

use crate::utils::printer::{OutputFormat, Printable};
use crate::utils::{
arg_parser::{ArgParser, FixedHashParser},
printer::{OutputFormat, Printable},
};

pub struct Output {
stdout: Option<serde_json::Value>,
Expand Down Expand Up @@ -95,3 +100,29 @@ Key Considerations:
- Permanent Immutability: Script logic and data will be permanently fixed on-chain.
- No Recovery Mechanism: If vulnerabilities or defects exist in the script, there is no way to upgrade, patch, or revoke it.
- Use with Caution: Thoroughly audit and test the script before deployment. This option is recommended only for scenarios requiring absolute finality, where script behavior must remain tamper-proof indefinitely.";

fn arg_multisig_code_hash() -> Arg<'static> {
let arg_multisig_code_hash = Arg::with_name("multisig-code-hash")
.long("multisig-code-hash")
.takes_value(true)
.multiple(false)
.required(true)
.possible_values(&[
// legacy code hash
"legacy",
"0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8",
// V2 code hash
"v2",
"0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29",
])
.about("Specifies the multisig code hash to use:\n - v2(default): `0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29`. \n - legacy(deprecated): `0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8` is NOT recommended for use.\n\n");
arg_multisig_code_hash
}

fn arg_get_multisig_code_hash(m: &ArgMatches) -> Result<H256, String> {
match m.value_of("multisig-code-hash").unwrap() {
"legacy" => Ok(MultisigScript::Legacy.script_id().code_hash),
"v2" => Ok(MultisigScript::V2.script_id().code_hash),
_ => FixedHashParser::<H256>::default().from_matches(m, "multisig-code-hash"),
}
}
114 changes: 94 additions & 20 deletions src/subcommands/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use std::str::FromStr;

use ckb_jsonrpc_types as json_types;
use ckb_jsonrpc_types::JsonBytes;
use ckb_sdk::constants::MultisigScript;
use ckb_sdk::{
constants::{MULTISIG_TYPE_HASH, SECP_SIGNATURE_SIZE},
unlock::MultisigConfig,
Address, AddressPayload, HumanCapacity, NetworkType,
constants::SECP_SIGNATURE_SIZE, unlock::MultisigConfig, Address, AddressPayload, HumanCapacity,
NetworkType,
};
use ckb_types::{
bytes::Bytes,
Expand All @@ -24,7 +24,10 @@ use clap::{App, Arg, ArgMatches};
use faster_hex::hex_string;
use serde_derive::{Deserialize, Serialize};

use super::{CliSubCommand, Output, ALLOW_ZERO_LOCK_HELP_MSG};
use super::{
arg_get_multisig_code_hash, arg_multisig_code_hash, CliSubCommand, Output,
ALLOW_ZERO_LOCK_HELP_MSG,
};
use crate::plugin::{KeyStoreHandler, PluginManager, SignTarget};
use crate::utils::{
arg,
Expand Down Expand Up @@ -107,6 +110,7 @@ impl<'a> TxSubCommand<'a> {
App::new("add-multisig-config")
.about("Add multisig config")
.arg(arg_sighash_address.clone())
.arg(arg_multisig_code_hash().required(true))
.arg(arg_require_first_n.clone())
.arg(arg_threshold.clone())
.arg(arg_tx_file.clone()),
Expand Down Expand Up @@ -160,14 +164,25 @@ impl<'a> TxSubCommand<'a> {
.long("to-short-multisig-address")
.conflicts_with("to-long-multisig-address")
.takes_value(true)
.validator(|input| AddressParser::new_multisig().validate(input))
.about("To short multisig address"),
.validator(|input| {
AddressParser::new_multisig(MultisigScript::Legacy)
.validate(input)
.or(AddressParser::new_multisig(MultisigScript::V2)
.validate(input))
})
.about("To short multisig address(encode with legacy multisig script)"),
)
.arg(
Arg::with_name("to-long-multisig-address")
.long("to-long-multisig-address")
.takes_value(true)
.validator(|input| AddressParser::new_multisig().validate(input))
.requires("multisig-code-hash")
.validator(|input| {
AddressParser::new_multisig(MultisigScript::Legacy)
.validate(input)
.or(AddressParser::new_multisig(MultisigScript::V2)
.validate(input))
})
.about("To long multisig address (special case, include since)"),
)
.arg(arg::capacity().required(true))
Expand Down Expand Up @@ -234,6 +249,7 @@ impl<'a> TxSubCommand<'a> {
)
.arg(arg_sighash_address.clone())
.arg(arg_require_first_n.clone())
.arg(arg_multisig_code_hash().required(true))
.arg(arg_threshold.clone())
.arg(arg_since_absolute_epoch.clone()),
])
Expand Down Expand Up @@ -308,13 +324,22 @@ impl CliSubCommand for TxSubCommand<'_> {
("add-output", Some(m)) => {
let tx_file: PathBuf = FilePathParser::new(true).from_matches(m, "tx-file")?;
let capacity: u64 = CapacityParser.from_matches(m, "capacity")?;

let to_sighash_address_opt: Option<Address> =
AddressParser::new_sighash().from_matches_opt(m, "to-sighash-address")?;
let to_short_multisig_address_opt: Option<Address> = AddressParser::new_multisig()
.from_matches_opt(m, "to-short-multisig-address")?;
let to_long_multisig_address_opt: Option<Address> =
AddressParser::new_multisig()
.from_matches_opt(m, "to-long-multisig-address")?;
let to_short_multisig_address_opt: Option<Address> =
AddressParser::new_multisig(MultisigScript::Legacy)
.from_matches_opt(m, "to-short-multisig-address")
.or_else(|_| {
AddressParser::new_multisig(MultisigScript::V2)
.from_matches_opt(m, "to-short-multisig-address")
})?;
let to_long_multisig_address_opt: Option<Address> = {
AddressParser::new_multisig(MultisigScript::Legacy)
.from_matches_opt(m, "to-long-multisig-address")
.or(AddressParser::new_multisig(MultisigScript::V2)
.from_matches_opt(m, "to-long-multisig-address"))?
};

let to_data = get_to_data(m)?;
check_capacity(capacity, to_data.len())?;
Expand Down Expand Up @@ -355,6 +380,15 @@ impl CliSubCommand for TxSubCommand<'_> {
Ok(Output::new_success())
}
("add-multisig-config", Some(m)) => {
let multisig_lock_code_hash: H256 = arg_get_multisig_code_hash(m)?;
let multisig_script = MultisigScript::try_from(multisig_lock_code_hash.clone())
.map_err(|_err| {
format!(
"invalid multisig lock code hash: {}",
multisig_lock_code_hash
)
})?;

let tx_file: PathBuf = FilePathParser::new(false).from_matches(m, "tx-file")?;
let sighash_addresses: Vec<Address> = AddressParser::new_sighash()
.set_network(network)
Expand All @@ -367,8 +401,13 @@ impl CliSubCommand for TxSubCommand<'_> {
.into_iter()
.map(|address| H160::from_slice(address.payload().args().as_ref()).unwrap())
.collect::<Vec<_>>();
let cfg = MultisigConfig::new_with(sighash_addresses, require_first_n, threshold)
.map_err(|err| err.to_string())?;
let cfg = MultisigConfig::new_with(
multisig_script,
sighash_addresses,
require_first_n,
threshold,
)
.map_err(|err| err.to_string())?;
modify_tx_file(&tx_file, network, |helper| {
helper.add_multisig_config(cfg);
Ok(())
Expand Down Expand Up @@ -567,6 +606,15 @@ impl CliSubCommand for TxSubCommand<'_> {
Ok(Output::new_output(resp))
}
("build-multisig-address", Some(m)) => {
let multisig_lock_code_hash: H256 = arg_get_multisig_code_hash(m)?;
let multisig_script = MultisigScript::try_from(multisig_lock_code_hash.clone())
.map_err(|_err| {
format!(
"invalid multisig lock code hash: {}",
multisig_lock_code_hash
)
})?;

let sighash_addresses: Vec<Address> = AddressParser::new_sighash()
.set_network(network)
.from_matches_vec(m, "sighash-address")?;
Expand All @@ -580,9 +628,15 @@ impl CliSubCommand for TxSubCommand<'_> {
.into_iter()
.map(|address| H160::from_slice(address.payload().args().as_ref()).unwrap())
.collect::<Vec<_>>();
let cfg = MultisigConfig::new_with(sighash_addresses, require_first_n, threshold)
.map_err(|err| err.to_string())?;
let address_payload = cfg.to_address_payload(since_absolute_epoch_opt);
let cfg = MultisigConfig::new_with(
multisig_script,
sighash_addresses,
require_first_n,
threshold,
)
.map_err(|err| err.to_string())?;
let address_payload =
cfg.to_address_payload(multisig_script, since_absolute_epoch_opt);
let lock_script = Script::from(&address_payload);
let resp = serde_json::json!({
"mainnet": Address::new(NetworkType::Mainnet, address_payload.clone(), true).to_string(),
Expand All @@ -606,7 +660,12 @@ fn print_cell_info(
type_script_empty: bool,
) {
let address_payload = AddressPayload::from(lock);
let lock_kind = if address_payload.code_hash(Some(network)) == MULTISIG_TYPE_HASH.pack() {
let lock_kind = if [
MultisigScript::Legacy.script_id().code_hash.pack(),
MultisigScript::V2.script_id().code_hash.pack(),
]
.contains(&address_payload.code_hash(Some(network)))
{
if address_payload.args().len() == 20 {
"multisig without since"
} else {
Expand Down Expand Up @@ -787,11 +846,18 @@ impl TryFrom<ReprTxHelper> for TxHelper {
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(deny_unknown_fields)]
pub struct ReprMultisigConfig {
#[serde(default = "compatibility_lock_code_hash")]
pub lock_code_hash: H256,
pub sighash_addresses: Vec<String>,
pub require_first_n: u8,
pub threshold: u8,
}

// for compatibility
fn compatibility_lock_code_hash() -> H256 {
MultisigScript::Legacy.script_id().code_hash
}

impl ReprMultisigConfig {
pub(crate) fn new(cfg: MultisigConfig, network: NetworkType) -> Self {
let sighash_addresses = cfg
Expand All @@ -803,6 +869,7 @@ impl ReprMultisigConfig {
})
.collect();
ReprMultisigConfig {
lock_code_hash: cfg.lock_code_hash(),
sighash_addresses,
require_first_n: cfg.require_first_n(),
threshold: cfg.threshold(),
Expand All @@ -822,7 +889,14 @@ impl TryFrom<ReprMultisigConfig> for MultisigConfig {
.map_err(|err| format!("invalid address: {address_string} error: {err:?}"))
})
.collect::<Result<Vec<_>, String>>()?;
MultisigConfig::new_with(sighash_addresses, repr.require_first_n, repr.threshold)
.map_err(|err| err.to_string())
let multisig_script = MultisigScript::try_from(repr.lock_code_hash.clone())
.map_err(|_err| format!("invalid lock_code_hash {}", repr.lock_code_hash))?;
MultisigConfig::new_with(
multisig_script,
sighash_addresses,
repr.require_first_n,
repr.threshold,
)
.map_err(|err| err.to_string())
}
}
Loading