Skip to content

Commit e6379b5

Browse files
committed
Upgrade multisig
Signed-off-by: Eval EXEC <execvy@gmail.com>
1 parent 0022ed7 commit e6379b5

17 files changed

+522
-146
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ ckb-traits = "0.200.0"
3838
ckb-jsonrpc-types = "0.200.0"
3939
ckb-hash = "0.200.0"
4040
ckb-resource = "0.200.0"
41+
ckb-system-scripts-v0_5_4 = { package="ckb-system-scripts", version="=0.5.4" }
42+
ckb-system-scripts-v0_6_0 = { package="ckb-system-scripts", version="=0.6.0" }
4143
ckb-crypto = { version = "=0.200.0", features = ["secp"] }
4244
ckb-script = "0.200.0"
4345
bitflags = "1.3.2"
4446
sha3 = "0.10.1"
4547
enum-repr-derive = "0.2.0"
48+
hex = "0.4"
4649

4750
# for feature test
4851
rand = { version = "0.7.3", optional = true }
@@ -61,4 +64,4 @@ test = []
6164
[dev-dependencies]
6265
clap = { version = "4.4.18", features = ["derive"] }
6366
httpmock = "0.6"
64-
hex = "0.4"
67+
tempfile = "3.19.1"

examples/send_ckb_multisig_example.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use ckb_sdk::{
2+
constants::MultisigScript,
23
transaction::{
34
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
45
handler::HandlerContexts,
@@ -17,22 +18,25 @@ fn main() -> Result<(), Box<dyn StdErr>> {
1718
let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?;
1819

1920
let multisig_config = MultisigConfig::new_with(
21+
MultisigScript::V2,
2022
vec![
2123
h160!("0x7336b0ba900684cb3cb00f0d46d4f64c0994a562"),
2224
h160!("0x5724c1e3925a5206944d753a6f3edaedf977d77f"),
2325
],
2426
0,
2527
2,
2628
)?;
27-
let sender = multisig_config.to_address(network_info.network_type, None);
29+
let sender = multisig_config.to_address(network_info.network_type, MultisigScript::V2, None);
30+
println!("sender: {}", sender);
2831
let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?;
2932

3033
let iterator = InputIterator::new_with_address(&[sender], &network_info);
3134
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);
32-
builder.add_output(&receiver, Capacity::shannons(510_0000_0000u64));
35+
builder.add_output(&receiver, Capacity::shannons(6100000000u64));
3336

3437
let mut tx_with_groups =
3538
builder.build(&HandlerContexts::new_multisig(multisig_config.clone()))?;
39+
println!("tx_with_groups:{:?}", &tx_with_groups);
3640

3741
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
3842
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());

examples/transfer_from_multisig.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::path::PathBuf;
66
use ckb_hash::blake2b_256;
77
use ckb_jsonrpc_types as json_types;
88
use ckb_sdk::{
9-
constants::{MULTISIG_TYPE_HASH, SIGHASH_TYPE_HASH},
9+
constants::{MultisigScript, SIGHASH_TYPE_HASH},
1010
rpc::CkbRpcClient,
1111
traits::{
1212
DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver,
@@ -144,7 +144,12 @@ fn main() -> Result<(), Box<dyn StdErr>> {
144144
}
145145
sighash_addresses.push(H160::from_slice(lock_args.as_ref()).unwrap());
146146
}
147-
MultisigConfig::new_with(sighash_addresses, args.require_first_n, args.threshold)?
147+
MultisigConfig::new_with(
148+
ckb_sdk::constants::MultisigScript::V2,
149+
sighash_addresses,
150+
args.require_first_n,
151+
args.threshold,
152+
)?
148153
};
149154
let tx = build_transfer_tx(&args, &multisig_config)?;
150155
let tx_info = TxInfo {
@@ -222,8 +227,8 @@ fn build_transfer_tx(
222227
) -> Result<TransactionView, Box<dyn StdErr>> {
223228
// Build CapacityBalancer
224229
let sender = Script::new_builder()
225-
.code_hash(MULTISIG_TYPE_HASH.pack())
226-
.hash_type(ScriptHashType::Type.into())
230+
.code_hash(MultisigScript::V2.script_id().code_hash.pack())
231+
.hash_type(MultisigScript::V2.script_id().hash_type.into())
227232
.args(Bytes::from(multisig_config.hash160().as_bytes().to_vec()).pack())
228233
.build();
229234
let sender_addr = Address::new(args.receiver.network(), sender.clone().into(), true);
@@ -289,7 +294,7 @@ fn build_multisig_unlockers(
289294
let signer = SecpCkbRawKeySigner::new_with_secret_keys(keys);
290295
let multisig_signer = SecpMultisigScriptSigner::new(Box::new(signer), config);
291296
let multisig_unlocker = SecpMultisigUnlocker::new(multisig_signer);
292-
let multisig_script_id = ScriptId::new_type(MULTISIG_TYPE_HASH.clone());
297+
let multisig_script_id = MultisigScript::V2.script_id();
293298
let mut unlockers = HashMap::default();
294299
unlockers.insert(
295300
multisig_script_id,

examples/transfer_from_omnilock_multisig.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ckb_jsonrpc_types as json_types;
22
use ckb_sdk::{
3-
constants::SIGHASH_TYPE_HASH,
3+
constants::{MultisigScript, SIGHASH_TYPE_HASH},
44
rpc::CkbRpcClient,
55
traits::{
66
DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver,
@@ -11,8 +11,10 @@ use ckb_sdk::{
1111
unlock_tx, CapacityBalancer, TxBuilder,
1212
},
1313
types::NetworkType,
14-
unlock::{MultisigConfig, OmniLockUnlocker, ScriptUnlocker},
15-
unlock::{OmniLockConfig, OmniLockScriptSigner, OmniUnlockMode},
14+
unlock::{
15+
MultisigConfig, OmniLockConfig, OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode,
16+
ScriptUnlocker,
17+
},
1618
Address, HumanCapacity, ScriptGroup, ScriptId,
1719
};
1820
use ckb_types::{
@@ -272,6 +274,7 @@ fn build_multisig_config(
272274
sighash_addresses.push(H160::from_slice(lock_args.as_ref()).unwrap());
273275
}
274276
Ok(MultisigConfig::new_with(
277+
MultisigScript::V2,
275278
sighash_addresses,
276279
require_first_n,
277280
threshold,

src/constants.rs

Lines changed: 229 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
use ckb_types::{core::EpochNumberWithFraction, h256, H256};
1+
use std::convert::TryFrom;
2+
3+
use crate::{CkbRpcClient, NetworkInfo, NetworkType, ScriptId};
4+
use ckb_system_scripts_v0_5_4::{
5+
CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL as CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_LEGACY,
6+
CODE_HASH_SECP256K1_DATA,
7+
};
8+
use ckb_system_scripts_v0_6_0::CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL as CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_V2;
9+
use ckb_types::{
10+
core::EpochNumberWithFraction,
11+
h256,
12+
packed::{Byte32, CellOutput, OutPoint, OutPointVecReader},
13+
prelude::*,
14+
H256,
15+
};
216

317
pub const PREFIX_MAINNET: &str = "ckb";
418
pub const PREFIX_TESTNET: &str = "ckt";
@@ -19,10 +33,10 @@ pub const REMAIN_FLAGS_BITS: u64 = 0x1f00_0000_0000_0000;
1933

2034
// Special cells in genesis transactions: (transaction-index, output-index)
2135
pub const SIGHASH_OUTPUT_LOC: (usize, usize) = (0, 1);
22-
pub const MULTISIG_OUTPUT_LOC: (usize, usize) = (0, 4);
36+
pub const MULTISIG_LEGACY_OUTPUT_LOC: (usize, usize) = (0, 4);
2337
pub const DAO_OUTPUT_LOC: (usize, usize) = (0, 2);
2438
pub const SIGHASH_GROUP_OUTPUT_LOC: (usize, usize) = (1, 0);
25-
pub const MULTISIG_GROUP_OUTPUT_LOC: (usize, usize) = (1, 1);
39+
pub const MULTISIG_LEGACY_GROUP_OUTPUT_LOC: (usize, usize) = (1, 1);
2640

2741
pub const ONE_CKB: u64 = 100_000_000;
2842
pub const MIN_SECP_CELL_CAPACITY: u64 = 61 * ONE_CKB;
@@ -35,8 +49,13 @@ pub const TYPE_ID_CODE_HASH: H256 = h256!("0x545950455f4944");
3549

3650
pub const SIGHASH_TYPE_HASH: H256 =
3751
h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8");
38-
pub const MULTISIG_TYPE_HASH: H256 =
39-
h256!("0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8");
52+
53+
pub const GENESIS_BLOCK_HASH_MAINNET: H256 =
54+
h256!("0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5");
55+
56+
pub const GENESIS_BLOCK_HASH_TESTNET: H256 =
57+
h256!("0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606");
58+
4059
pub const DAO_TYPE_HASH: H256 =
4160
h256!("0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e");
4261

@@ -51,13 +70,182 @@ pub const ACP_TYPE_HASH_AGGRON: H256 =
5170
/// cheque withdraw since value
5271
pub const CHEQUE_CELL_SINCE: u64 = 0xA000000000000006;
5372

73+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74+
pub enum MultisigScript {
75+
/// Multisig Script deployed on Genesis Block
76+
/// https://explorer.nervos.org/script/0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8/type
77+
Legacy,
78+
79+
/// Latest multisig script
80+
/// https://explorer.nervos.org/script/0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29/data1
81+
V2,
82+
}
83+
84+
impl MultisigScript {
85+
pub const fn script_id(&self) -> ScriptId {
86+
match self {
87+
MultisigScript::Legacy => ScriptId::new_type(h256!(
88+
"0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8"
89+
)),
90+
MultisigScript::V2 => ScriptId::new_data1(h256!(
91+
"0x36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29"
92+
)),
93+
}
94+
}
95+
96+
fn dep_group_from_env(&self, _network: NetworkInfo) -> Option<(H256, u32)> {
97+
let env_dep_group = match self {
98+
MultisigScript::Legacy => std::env::var("MULTISIG_LEGACY_DEP_GROUP"),
99+
MultisigScript::V2 => std::env::var("MULTISIG_V2_DEP_GROUP"),
100+
}
101+
.ok()?;
102+
103+
let vars = env_dep_group.split(",").collect::<Vec<_>>();
104+
match (vars.first(), vars.get(1)) {
105+
(Some(hash), Some(index)) => {
106+
let index_u32: u32 = index.parse().ok()?;
107+
108+
if !hash.starts_with("0x") {
109+
return None;
110+
}
111+
let hash_bytes = hex::decode(&hash[2..]).ok()?;
112+
113+
let hash = H256::from_slice(&hash_bytes).ok()?;
114+
Some((hash, index_u32))
115+
}
116+
_ => None,
117+
}
118+
}
119+
120+
/// Get dep group from env first:
121+
/// 1. MULTISIG_LEGACY_DEP_GROUP=0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c,1
122+
/// 2. MULTISIG_V2_DEP_GROUP=0x6888aa39ab30c570c2c30d9d5684d3769bf77265a7973211a3c087fe8efbf738,2
123+
///
124+
/// If env not set, then get it from dep_group_inner
125+
pub fn dep_group(&self, network: NetworkInfo) -> Option<(H256, u32)> {
126+
self.dep_group_from_env(network.clone())
127+
.or(self.dep_group_inner(network))
128+
}
129+
130+
pub fn dep_group_inner(&self, network: NetworkInfo) -> Option<(H256, u32)> {
131+
match network.network_type {
132+
NetworkType::Mainnet => Some(match self {
133+
MultisigScript::Legacy => (
134+
h256!("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"),
135+
1,
136+
),
137+
MultisigScript::V2 => (
138+
h256!("0x6888aa39ab30c570c2c30d9d5684d3769bf77265a7973211a3c087fe8efbf738"),
139+
0,
140+
),
141+
}),
142+
NetworkType::Testnet => Some(match self {
143+
MultisigScript::Legacy => (
144+
h256!("0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37"),
145+
1,
146+
),
147+
MultisigScript::V2 => (
148+
h256!("0x2eefdeb21f3a3edf697c28a52601b4419806ed60bb427420455cc29a090b26d5"),
149+
0,
150+
),
151+
}),
152+
NetworkType::Staging | NetworkType::Preview | NetworkType::Dev => {
153+
let client = CkbRpcClient::new(network.url.as_str());
154+
let json_genesis_block = client.get_block_by_number(0_u64.into()).ok()??;
155+
let genesis_block: ckb_types::core::BlockView = json_genesis_block.into();
156+
157+
let secp256k1_data_outpoint =
158+
find_cell_match_data_hash(&genesis_block, CODE_HASH_SECP256K1_DATA.pack())?;
159+
160+
let target_data_hash = match self {
161+
MultisigScript::Legacy => {
162+
CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_LEGACY.pack()
163+
}
164+
MultisigScript::V2 => CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_V2.pack(),
165+
};
166+
let multisig_outpoint =
167+
find_cell_match_data_hash(&genesis_block, target_data_hash)?;
168+
169+
let (dep_hash, dep_index) = find_cell_match_data_hash_find_dep(
170+
&genesis_block,
171+
vec![secp256k1_data_outpoint, multisig_outpoint],
172+
)?;
173+
174+
let dep_hash: H256 = dep_hash.unpack();
175+
Some((dep_hash, dep_index))
176+
}
177+
}
178+
}
179+
}
180+
181+
fn find_cell_match_data_hash(
182+
genesis_block: &ckb_types::core::BlockView,
183+
target_data_hash: Byte32,
184+
) -> Option<OutPoint> {
185+
genesis_block.transactions().iter().find_map(|tx| {
186+
let multisig_legacy_cell_index =
187+
tx.outputs_with_data_iter()
188+
.enumerate()
189+
.find_map(|(index, (_output, data))| {
190+
let data_hash = CellOutput::calc_data_hash(&data);
191+
data_hash.eq(&target_data_hash).then_some(index)
192+
});
193+
multisig_legacy_cell_index.map(|cell_index| OutPoint::new(tx.hash(), cell_index as u32))
194+
})
195+
}
196+
197+
fn find_cell_match_data_hash_find_dep(
198+
genesis_block: &ckb_types::core::BlockView,
199+
target_points: Vec<OutPoint>,
200+
) -> Option<(ckb_types::packed::Byte32, u32)> {
201+
genesis_block.transactions().iter().find_map(|tx| {
202+
let multisig_cell_index: Option<u32> =
203+
tx.outputs_with_data_iter()
204+
.enumerate()
205+
.find_map(|(index, (_output, data))| {
206+
let he = hex_string(&data);
207+
if he.len() > 200 {
208+
return None;
209+
}
210+
let outpoint_vec = OutPointVecReader::from_slice(&data)
211+
.map(|reader| reader.to_entity())
212+
.ok()?;
213+
214+
target_points
215+
.iter()
216+
.all(|target_point| {
217+
outpoint_vec
218+
.clone()
219+
.into_iter()
220+
.any(|outpoint| outpoint.eq(target_point))
221+
})
222+
.then_some(index as u32)
223+
});
224+
225+
multisig_cell_index.map(|cell_index| (tx.hash(), cell_index))
226+
})
227+
}
228+
229+
impl TryFrom<H256> for MultisigScript {
230+
type Error = ();
231+
232+
fn try_from(code_hash: H256) -> Result<Self, Self::Error> {
233+
if code_hash.eq(&MultisigScript::Legacy.script_id().code_hash) {
234+
Ok(MultisigScript::Legacy)
235+
} else if code_hash.eq(&MultisigScript::V2.script_id().code_hash) {
236+
Ok(MultisigScript::V2)
237+
} else {
238+
Err(())
239+
}
240+
}
241+
}
242+
54243
#[cfg(test)]
55244
mod test {
56245
use super::*;
57246
use ckb_types::{
58247
core::Capacity,
59248
packed::{CellOutput, Script},
60-
prelude::*,
61249
H160,
62250
};
63251

@@ -76,4 +264,39 @@ mod test {
76264

77265
assert_eq!(min_secp_cell_capacity, MIN_SECP_CELL_CAPACITY);
78266
}
267+
268+
#[test]
269+
fn test_multisig_deps() {
270+
assert_ne!(
271+
CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_LEGACY,
272+
CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL_V2
273+
);
274+
275+
let mainnet = NetworkInfo::mainnet();
276+
assert!(MultisigScript::Legacy.dep_group(mainnet.clone()).is_some());
277+
assert!(MultisigScript::V2.dep_group(mainnet).is_some());
278+
279+
let testnet = NetworkInfo::testnet();
280+
assert!(MultisigScript::Legacy.dep_group(testnet.clone()).is_some());
281+
assert!(MultisigScript::V2.dep_group(testnet).is_some());
282+
283+
// TODO: start ckb devchain in this unit test
284+
// let devnet = NetworkInfo::devnet();
285+
// assert!(MultisigScript::Legacy.dep_group(devnet.clone()).is_some());
286+
287+
// TODO, let ckb devnet deploy multisig_v2 on genesis block
288+
// assert!(MultisigScript::V1.dep_group(devnet).is_some());
289+
}
290+
291+
#[test]
292+
fn test_dep_group_from_env() {
293+
let legacy = MultisigScript::Legacy;
294+
std::env::set_var(
295+
"MULTISIG_LEGACY_DEP_GROUP",
296+
"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c,10000",
297+
);
298+
let dep_group = legacy.dep_group_from_env(NetworkInfo::devnet());
299+
assert!(dep_group.is_some());
300+
assert_eq!(dep_group.unwrap().1, 10000)
301+
}
79302
}

0 commit comments

Comments
 (0)