Skip to content

Commit ba5e678

Browse files
committed
Upgrade multisig
Signed-off-by: Eval EXEC <[email protected]>
1 parent efb5bb0 commit ba5e678

File tree

14 files changed

+454
-155
lines changed

14 files changed

+454
-155
lines changed

Cargo.lock

Lines changed: 117 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ ckb-util = "=0.200.0"
1919
ckb-error = "=0.200.0"
2020
ckb-script = "=0.200.0"
2121
ckb-chain-spec = "=0.200.0"
22-
ckb-sdk = { version = "3.7.0", features = ["native-tls-vendored"] }
22+
ckb-sdk = { version = "4.0.0", features = ["native-tls-vendored"] }
2323
ckb-mock-tx-types = "=0.200.0"
2424
ckb-signer = { path = "ckb-signer", version = "0.4.1" }
2525
plugin-protocol = { path = "plugin-protocol", package = "ckb-cli-plugin-protocol", version = "=1.3.1" }

ckb-signer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ anyhow = "1.0.63"
2727
ckb-types = "=0.200.0"
2828
ckb-hash = "=0.200.0"
2929
ckb-crypto = { version = "=0.200.0", features = ["secp"] }
30-
ckb-sdk = { version = "3.7.0", features = ["native-tls-vendored"] }
30+
ckb-sdk = { version = "4.0.0", features = ["native-tls-vendored"] }

src/subcommands/deploy/tx_builder.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashMap;
22

33
use anyhow::{anyhow, Result};
44
use ckb_sdk::{
5-
constants::{MULTISIG_TYPE_HASH, SIGHASH_TYPE_HASH},
5+
constants::{MultisigScript, SIGHASH_TYPE_HASH},
66
traits::{
77
CellCollector, CellQueryOptions, DefaultCellCollector, DefaultHeaderDepResolver,
88
DefaultTransactionDependencyProvider, OffchainTransactionDependencyProvider, Signer,
@@ -79,10 +79,7 @@ pub fn build_tx<T: ChangeInfo>(
7979
.iter()
8080
.filter_map(|info| info.build_cell_output(lock_script, first_cell_input))
8181
.unzip();
82-
let mut cell_deps = vec![genesis_info.sighash_dep()];
83-
if multisig_config.is_some() {
84-
cell_deps.push(genesis_info.multisig_dep());
85-
}
82+
8683
let mut unlockers = HashMap::new();
8784
let signer = DummySigner {
8885
args: vec![from_address.payload().args()],
@@ -93,10 +90,22 @@ pub fn build_tx<T: ChangeInfo>(
9390
sighash_script_id,
9491
Box::new(sighash_unlocker) as Box<dyn ScriptUnlocker>,
9592
);
93+
94+
let mut cell_deps = vec![genesis_info.sighash_dep()];
9695
if let Some(cfg) = multisig_config {
96+
let multisig_script =
97+
MultisigScript::try_from(cfg.lock_code_hash()).unwrap_or_else(|_err| {
98+
panic!(
99+
"Failed to get multisig script from {}",
100+
cfg.lock_code_hash(),
101+
)
102+
});
103+
104+
cell_deps.push(genesis_info.multisig_dep(multisig_script));
105+
97106
let multisig_signer = SecpMultisigScriptSigner::new(Box::new(signer), cfg.clone());
98107
let multisig_unlocker = SecpMultisigUnlocker::new(multisig_signer);
99-
let multisig_script_id = ScriptId::new_type(MULTISIG_TYPE_HASH.clone());
108+
let multisig_script_id = multisig_script.script_id();
100109
unlockers.insert(
101110
multisig_script_id,
102111
Box::new(multisig_unlocker) as Box<dyn ScriptUnlocker>,

src/subcommands/mod.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub mod wallet;
1414

1515
pub use account::AccountSubCommand;
1616
pub use api_server::ApiServerSubCommand;
17+
use ckb_sdk::constants::MultisigScript;
18+
use ckb_types::H256;
1719
pub use dao::DAOSubCommand;
1820
pub use deploy::DeploySubCommand;
1921
pub use mock_tx::MockTxSubCommand;
@@ -26,10 +28,13 @@ pub use tx::TxSubCommand;
2628
pub use util::UtilSubCommand;
2729
pub use wallet::{TransferArgs, WalletSubCommand};
2830

29-
use clap::ArgMatches;
31+
use clap::{Arg, ArgMatches};
3032
use serde::Serialize;
3133

32-
use crate::utils::printer::{OutputFormat, Printable};
34+
use crate::utils::{
35+
arg_parser::{ArgParser, FixedHashParser},
36+
printer::{OutputFormat, Printable},
37+
};
3338

3439
pub struct Output {
3540
stdout: Option<serde_json::Value>,
@@ -95,3 +100,29 @@ Key Considerations:
95100
- Permanent Immutability: Script logic and data will be permanently fixed on-chain.
96101
- No Recovery Mechanism: If vulnerabilities or defects exist in the script, there is no way to upgrade, patch, or revoke it.
97102
- 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.";
103+
104+
fn arg_multisig_code_hash() -> Arg<'static> {
105+
let arg_multisig_code_hash = Arg::with_name("multisig-code-hash")
106+
.long("multisig-code-hash")
107+
.takes_value(true)
108+
.multiple(false)
109+
.required(true)
110+
.possible_values(&[
111+
// legacy code hash
112+
"legacy",
113+
"0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8",
114+
// V2 code hash
115+
"v2",
116+
"0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29",
117+
])
118+
.about("Specifies the multisig code hash to use:\n - v2(default): `0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29`. \n - legacy(deprecated): `0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8` is NOT recommended for use.\n\n");
119+
arg_multisig_code_hash
120+
}
121+
122+
fn arg_get_multisig_code_hash(m: &ArgMatches) -> Result<H256, String> {
123+
match m.value_of("multisig-code-hash").unwrap() {
124+
"legacy" => Ok(MultisigScript::Legacy.script_id().code_hash),
125+
"v2" => Ok(MultisigScript::V2.script_id().code_hash),
126+
_ => FixedHashParser::<H256>::default().from_matches(m, "multisig-code-hash"),
127+
}
128+
}

src/subcommands/tx.rs

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use std::str::FromStr;
77

88
use ckb_jsonrpc_types as json_types;
99
use ckb_jsonrpc_types::JsonBytes;
10+
use ckb_sdk::constants::MultisigScript;
1011
use ckb_sdk::{
11-
constants::{MULTISIG_TYPE_HASH, SECP_SIGNATURE_SIZE},
12-
unlock::MultisigConfig,
13-
Address, AddressPayload, HumanCapacity, NetworkType,
12+
constants::SECP_SIGNATURE_SIZE, unlock::MultisigConfig, Address, AddressPayload, HumanCapacity,
13+
NetworkType,
1414
};
1515
use ckb_types::{
1616
bytes::Bytes,
@@ -24,7 +24,10 @@ use clap::{App, Arg, ArgMatches};
2424
use faster_hex::hex_string;
2525
use serde_derive::{Deserialize, Serialize};
2626

27-
use super::{CliSubCommand, Output, ALLOW_ZERO_LOCK_HELP_MSG};
27+
use super::{
28+
arg_get_multisig_code_hash, arg_multisig_code_hash, CliSubCommand, Output,
29+
ALLOW_ZERO_LOCK_HELP_MSG,
30+
};
2831
use crate::plugin::{KeyStoreHandler, PluginManager, SignTarget};
2932
use crate::utils::{
3033
arg,
@@ -107,6 +110,7 @@ impl<'a> TxSubCommand<'a> {
107110
App::new("add-multisig-config")
108111
.about("Add multisig config")
109112
.arg(arg_sighash_address.clone())
113+
.arg(arg_multisig_code_hash().required(true))
110114
.arg(arg_require_first_n.clone())
111115
.arg(arg_threshold.clone())
112116
.arg(arg_tx_file.clone()),
@@ -160,14 +164,25 @@ impl<'a> TxSubCommand<'a> {
160164
.long("to-short-multisig-address")
161165
.conflicts_with("to-long-multisig-address")
162166
.takes_value(true)
163-
.validator(|input| AddressParser::new_multisig().validate(input))
164-
.about("To short multisig address"),
167+
.validator(|input| {
168+
AddressParser::new_multisig(MultisigScript::Legacy)
169+
.validate(input)
170+
.or(AddressParser::new_multisig(MultisigScript::V2)
171+
.validate(input))
172+
})
173+
.about("To short multisig address(encode with legacy multisig script)"),
165174
)
166175
.arg(
167176
Arg::with_name("to-long-multisig-address")
168177
.long("to-long-multisig-address")
169178
.takes_value(true)
170-
.validator(|input| AddressParser::new_multisig().validate(input))
179+
.requires("multisig-code-hash")
180+
.validator(|input| {
181+
AddressParser::new_multisig(MultisigScript::Legacy)
182+
.validate(input)
183+
.or(AddressParser::new_multisig(MultisigScript::V2)
184+
.validate(input))
185+
})
171186
.about("To long multisig address (special case, include since)"),
172187
)
173188
.arg(arg::capacity().required(true))
@@ -234,6 +249,7 @@ impl<'a> TxSubCommand<'a> {
234249
)
235250
.arg(arg_sighash_address.clone())
236251
.arg(arg_require_first_n.clone())
252+
.arg(arg_multisig_code_hash().required(true))
237253
.arg(arg_threshold.clone())
238254
.arg(arg_since_absolute_epoch.clone()),
239255
])
@@ -308,13 +324,22 @@ impl CliSubCommand for TxSubCommand<'_> {
308324
("add-output", Some(m)) => {
309325
let tx_file: PathBuf = FilePathParser::new(true).from_matches(m, "tx-file")?;
310326
let capacity: u64 = CapacityParser.from_matches(m, "capacity")?;
327+
311328
let to_sighash_address_opt: Option<Address> =
312329
AddressParser::new_sighash().from_matches_opt(m, "to-sighash-address")?;
313-
let to_short_multisig_address_opt: Option<Address> = AddressParser::new_multisig()
314-
.from_matches_opt(m, "to-short-multisig-address")?;
315-
let to_long_multisig_address_opt: Option<Address> =
316-
AddressParser::new_multisig()
317-
.from_matches_opt(m, "to-long-multisig-address")?;
330+
let to_short_multisig_address_opt: Option<Address> =
331+
AddressParser::new_multisig(MultisigScript::Legacy)
332+
.from_matches_opt(m, "to-short-multisig-address")
333+
.or_else(|_| {
334+
AddressParser::new_multisig(MultisigScript::V2)
335+
.from_matches_opt(m, "to-short-multisig-address")
336+
})?;
337+
let to_long_multisig_address_opt: Option<Address> = {
338+
AddressParser::new_multisig(MultisigScript::Legacy)
339+
.from_matches_opt(m, "to-long-multisig-address")
340+
.or(AddressParser::new_multisig(MultisigScript::V2)
341+
.from_matches_opt(m, "to-long-multisig-address"))?
342+
};
318343

319344
let to_data = get_to_data(m)?;
320345
check_capacity(capacity, to_data.len())?;
@@ -355,6 +380,15 @@ impl CliSubCommand for TxSubCommand<'_> {
355380
Ok(Output::new_success())
356381
}
357382
("add-multisig-config", Some(m)) => {
383+
let multisig_lock_code_hash: H256 = arg_get_multisig_code_hash(m)?;
384+
let multisig_script = MultisigScript::try_from(multisig_lock_code_hash.clone())
385+
.map_err(|_err| {
386+
format!(
387+
"invalid multisig lock code hash: {}",
388+
multisig_lock_code_hash
389+
)
390+
})?;
391+
358392
let tx_file: PathBuf = FilePathParser::new(false).from_matches(m, "tx-file")?;
359393
let sighash_addresses: Vec<Address> = AddressParser::new_sighash()
360394
.set_network(network)
@@ -367,8 +401,13 @@ impl CliSubCommand for TxSubCommand<'_> {
367401
.into_iter()
368402
.map(|address| H160::from_slice(address.payload().args().as_ref()).unwrap())
369403
.collect::<Vec<_>>();
370-
let cfg = MultisigConfig::new_with(sighash_addresses, require_first_n, threshold)
371-
.map_err(|err| err.to_string())?;
404+
let cfg = MultisigConfig::new_with(
405+
multisig_script,
406+
sighash_addresses,
407+
require_first_n,
408+
threshold,
409+
)
410+
.map_err(|err| err.to_string())?;
372411
modify_tx_file(&tx_file, network, |helper| {
373412
helper.add_multisig_config(cfg);
374413
Ok(())
@@ -567,6 +606,15 @@ impl CliSubCommand for TxSubCommand<'_> {
567606
Ok(Output::new_output(resp))
568607
}
569608
("build-multisig-address", Some(m)) => {
609+
let multisig_lock_code_hash: H256 = arg_get_multisig_code_hash(m)?;
610+
let multisig_script = MultisigScript::try_from(multisig_lock_code_hash.clone())
611+
.map_err(|_err| {
612+
format!(
613+
"invalid multisig lock code hash: {}",
614+
multisig_lock_code_hash
615+
)
616+
})?;
617+
570618
let sighash_addresses: Vec<Address> = AddressParser::new_sighash()
571619
.set_network(network)
572620
.from_matches_vec(m, "sighash-address")?;
@@ -580,9 +628,15 @@ impl CliSubCommand for TxSubCommand<'_> {
580628
.into_iter()
581629
.map(|address| H160::from_slice(address.payload().args().as_ref()).unwrap())
582630
.collect::<Vec<_>>();
583-
let cfg = MultisigConfig::new_with(sighash_addresses, require_first_n, threshold)
584-
.map_err(|err| err.to_string())?;
585-
let address_payload = cfg.to_address_payload(since_absolute_epoch_opt);
631+
let cfg = MultisigConfig::new_with(
632+
multisig_script,
633+
sighash_addresses,
634+
require_first_n,
635+
threshold,
636+
)
637+
.map_err(|err| err.to_string())?;
638+
let address_payload =
639+
cfg.to_address_payload(multisig_script, since_absolute_epoch_opt);
586640
let lock_script = Script::from(&address_payload);
587641
let resp = serde_json::json!({
588642
"mainnet": Address::new(NetworkType::Mainnet, address_payload.clone(), true).to_string(),
@@ -606,7 +660,12 @@ fn print_cell_info(
606660
type_script_empty: bool,
607661
) {
608662
let address_payload = AddressPayload::from(lock);
609-
let lock_kind = if address_payload.code_hash(Some(network)) == MULTISIG_TYPE_HASH.pack() {
663+
let lock_kind = if [
664+
MultisigScript::Legacy.script_id().code_hash.pack(),
665+
MultisigScript::V2.script_id().code_hash.pack(),
666+
]
667+
.contains(&address_payload.code_hash(Some(network)))
668+
{
610669
if address_payload.args().len() == 20 {
611670
"multisig without since"
612671
} else {
@@ -787,11 +846,18 @@ impl TryFrom<ReprTxHelper> for TxHelper {
787846
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
788847
#[serde(deny_unknown_fields)]
789848
pub struct ReprMultisigConfig {
849+
#[serde(default = "compatibility_lock_code_hash")]
850+
pub lock_code_hash: H256,
790851
pub sighash_addresses: Vec<String>,
791852
pub require_first_n: u8,
792853
pub threshold: u8,
793854
}
794855

856+
// for compatibility
857+
fn compatibility_lock_code_hash() -> H256 {
858+
MultisigScript::Legacy.script_id().code_hash
859+
}
860+
795861
impl ReprMultisigConfig {
796862
pub(crate) fn new(cfg: MultisigConfig, network: NetworkType) -> Self {
797863
let sighash_addresses = cfg
@@ -803,6 +869,7 @@ impl ReprMultisigConfig {
803869
})
804870
.collect();
805871
ReprMultisigConfig {
872+
lock_code_hash: cfg.lock_code_hash(),
806873
sighash_addresses,
807874
require_first_n: cfg.require_first_n(),
808875
threshold: cfg.threshold(),
@@ -822,7 +889,14 @@ impl TryFrom<ReprMultisigConfig> for MultisigConfig {
822889
.map_err(|err| format!("invalid address: {address_string} error: {err:?}"))
823890
})
824891
.collect::<Result<Vec<_>, String>>()?;
825-
MultisigConfig::new_with(sighash_addresses, repr.require_first_n, repr.threshold)
826-
.map_err(|err| err.to_string())
892+
let multisig_script = MultisigScript::try_from(repr.lock_code_hash.clone())
893+
.map_err(|_err| format!("invalid lock_code_hash {}", repr.lock_code_hash))?;
894+
MultisigConfig::new_with(
895+
multisig_script,
896+
sighash_addresses,
897+
repr.require_first_n,
898+
repr.threshold,
899+
)
900+
.map_err(|err| err.to_string())
827901
}
828902
}

0 commit comments

Comments
 (0)