Skip to content

Commit 0e5a278

Browse files
committed
feat: omnilock transaction support pubkeyhash/multisign/eth
1 parent 2554656 commit 0e5a278

File tree

11 files changed

+664
-193
lines changed

11 files changed

+664
-193
lines changed

src/tests/transaction/omnilock.rs

Lines changed: 179 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use ckb_types::{
1010
use crate::{
1111
constants::ONE_CKB,
1212
tests::{
13-
build_omnilock_script, build_sighash_script, init_context, ACCOUNT0_KEY, ACCOUNT2_ARG,
14-
FEE_RATE, OMNILOCK_BIN,
13+
build_omnilock_script, build_sighash_script, init_context, ACCOUNT0_ARG, ACCOUNT0_KEY,
14+
ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, FEE_RATE, OMNILOCK_BIN,
1515
},
1616
transaction::{
1717
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
@@ -26,8 +26,8 @@ use crate::{
2626
signer::{SignContexts, TransactionSigner},
2727
TransactionBuilderConfiguration,
2828
},
29-
unlock::OmniLockConfig,
30-
util::keccak160,
29+
unlock::{MultisigConfig, OmniLockConfig},
30+
util::{blake160, keccak160},
3131
NetworkInfo,
3232
};
3333

@@ -65,11 +65,17 @@ fn test_omnilock_config(omnilock_outpoint: OutPoint) -> TransactionBuilderConfig
6565
}
6666

6767
#[test]
68-
fn test_transfer_from_omnilock_ethereum() {
68+
fn test_omnilock_ethereum() {
69+
omnilock_ethereum(false);
70+
omnilock_ethereum(true)
71+
}
72+
73+
fn omnilock_ethereum(cobuild: bool) {
6974
let network_info = NetworkInfo::testnet();
7075
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
7176
let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key);
72-
let cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref()));
77+
let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref()));
78+
cfg.enable_cobuild(cobuild);
7379

7480
let sender = build_omnilock_script(&cfg);
7581
let receiver = build_sighash_script(ACCOUNT2_ARG);
@@ -104,8 +110,8 @@ fn test_transfer_from_omnilock_ethereum() {
104110

105111
let mut tx_with_groups = builder.build(&contexts).expect("build failed");
106112

107-
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
108-
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
113+
// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
114+
// println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
109115

110116
TransactionSigner::new(&network_info)
111117
// use unitest lock to verify
@@ -119,8 +125,171 @@ fn test_transfer_from_omnilock_ethereum() {
119125
)
120126
.unwrap();
121127

122-
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
123-
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
128+
// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
129+
// println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
130+
131+
let tx = tx_with_groups.get_tx_view().clone();
132+
let script_groups = tx_with_groups.script_groups.clone();
133+
assert_eq!(script_groups.len(), 1);
134+
assert_eq!(tx.header_deps().len(), 0);
135+
assert_eq!(tx.cell_deps().len(), 2);
136+
assert_eq!(tx.inputs().len(), 2);
137+
for out_point in tx.input_pts_iter() {
138+
assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender);
139+
}
140+
assert_eq!(tx.outputs().len(), 2);
141+
assert_eq!(tx.output(0).unwrap(), output);
142+
assert_eq!(tx.output(1).unwrap().lock(), sender);
143+
let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack();
144+
let fee = (100 + 200 - 120) * ONE_CKB - change_capacity;
145+
assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee);
146+
147+
ctx.verify(tx, FEE_RATE).unwrap();
148+
}
149+
150+
#[test]
151+
fn test_omnilock_pubkeyhash() {
152+
omnilock_pubkeyhash(false);
153+
omnilock_pubkeyhash(true)
154+
}
155+
156+
fn omnilock_pubkeyhash(cobuild: bool) {
157+
let network_info = NetworkInfo::testnet();
158+
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
159+
let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key);
160+
let mut cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize()));
161+
cfg.enable_cobuild(cobuild);
162+
163+
let sender = build_omnilock_script(&cfg);
164+
let receiver = build_sighash_script(ACCOUNT2_ARG);
165+
let (ctx, mut outpoints) = init_context(
166+
vec![(OMNILOCK_BIN, true)],
167+
vec![
168+
(sender.clone(), Some(100 * ONE_CKB)),
169+
(sender.clone(), Some(200 * ONE_CKB)),
170+
(sender.clone(), Some(300 * ONE_CKB)),
171+
],
172+
);
173+
174+
let configuration = test_omnilock_config(outpoints.pop().unwrap());
175+
176+
let iterator = InputIterator::new_with_cell_collector(
177+
vec![sender.clone()],
178+
Box::new(ctx.to_live_cells_context()) as Box<_>,
179+
);
180+
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);
181+
182+
let output = CellOutput::new_builder()
183+
.capacity((120 * ONE_CKB).pack())
184+
.lock(receiver)
185+
.build();
186+
builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default());
187+
builder.set_change_lock(sender.clone());
188+
189+
let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone());
190+
let mut contexts = HandlerContexts::default();
191+
contexts.add_context(Box::new(context) as Box<_>);
192+
193+
let mut tx_with_groups = builder.build(&contexts).expect("build failed");
194+
195+
// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
196+
197+
TransactionSigner::new(&network_info)
198+
// use unitest lock to verify
199+
.insert_unlocker(
200+
crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))),
201+
crate::transaction::signer::omnilock::OmnilockSigner {},
202+
)
203+
.sign_transaction(
204+
&mut tx_with_groups,
205+
&SignContexts::new_omnilock(vec![account0_key], cfg),
206+
)
207+
.unwrap();
208+
209+
let tx = tx_with_groups.get_tx_view().clone();
210+
let script_groups = tx_with_groups.script_groups.clone();
211+
assert_eq!(script_groups.len(), 1);
212+
assert_eq!(tx.header_deps().len(), 0);
213+
assert_eq!(tx.cell_deps().len(), 2);
214+
assert_eq!(tx.inputs().len(), 2);
215+
for out_point in tx.input_pts_iter() {
216+
assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender);
217+
}
218+
assert_eq!(tx.outputs().len(), 2);
219+
assert_eq!(tx.output(0).unwrap(), output);
220+
assert_eq!(tx.output(1).unwrap().lock(), sender);
221+
let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack();
222+
let fee = (100 + 200 - 120) * ONE_CKB - change_capacity;
223+
assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee);
224+
225+
ctx.verify(tx, FEE_RATE).unwrap();
226+
}
227+
228+
#[test]
229+
fn test_omnilock_multisign() {
230+
omnilock_multisign(false);
231+
omnilock_multisign(true)
232+
}
233+
234+
fn omnilock_multisign(cobuild: bool) {
235+
let network_info = NetworkInfo::testnet();
236+
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
237+
let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap();
238+
let lock_args = vec![
239+
ACCOUNT0_ARG.clone(),
240+
ACCOUNT1_ARG.clone(),
241+
ACCOUNT2_ARG.clone(),
242+
];
243+
let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap();
244+
let mut cfg = OmniLockConfig::new_multisig(multi_cfg);
245+
cfg.enable_cobuild(cobuild);
246+
247+
let sender = build_omnilock_script(&cfg);
248+
let receiver = build_sighash_script(ACCOUNT2_ARG);
249+
250+
let (ctx, mut outpoints) = init_context(
251+
vec![(OMNILOCK_BIN, true)],
252+
vec![
253+
(sender.clone(), Some(100 * ONE_CKB)),
254+
(sender.clone(), Some(200 * ONE_CKB)),
255+
(sender.clone(), Some(300 * ONE_CKB)),
256+
],
257+
);
258+
259+
let configuration = test_omnilock_config(outpoints.pop().unwrap());
260+
261+
let iterator = InputIterator::new_with_cell_collector(
262+
vec![sender.clone()],
263+
Box::new(ctx.to_live_cells_context()) as Box<_>,
264+
);
265+
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);
266+
267+
let output = CellOutput::new_builder()
268+
.capacity((120 * ONE_CKB).pack())
269+
.lock(receiver)
270+
.build();
271+
builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default());
272+
builder.set_change_lock(sender.clone());
273+
274+
let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone());
275+
let mut contexts = HandlerContexts::default();
276+
contexts.add_context(Box::new(context) as Box<_>);
277+
278+
let mut tx_with_groups = builder.build(&contexts).expect("build failed");
279+
280+
// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
281+
282+
TransactionSigner::new(&network_info)
283+
// use unitest lock to verify
284+
.insert_unlocker(
285+
crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))),
286+
crate::transaction::signer::omnilock::OmnilockSigner {},
287+
)
288+
.sign_transaction(
289+
&mut tx_with_groups,
290+
&SignContexts::new_omnilock(vec![account0_key, account1_key], cfg),
291+
)
292+
.unwrap();
124293

125294
let tx = tx_with_groups.get_tx_view().clone();
126295
let script_groups = tx_with_groups.script_groups.clone();

src/transaction/builder/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
};
1111
use ckb_types::{
1212
core::{Capacity, TransactionView},
13-
packed::{self, Byte32, CellOutput, Script},
13+
packed::{self, Byte32, CellOutput, OutPoint, Script},
1414
prelude::{Builder, Entity, Pack, Unpack},
1515
};
1616
pub mod fee_calculator;
@@ -146,6 +146,7 @@ fn inner_build<
146146
) -> Result<TransactionWithScriptGroups, TxBuilderError> {
147147
let mut lock_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
148148
let mut type_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
149+
let mut inputs: HashMap<OutPoint, (CellOutput, bytes::Bytes)> = HashMap::default();
149150

150151
// setup outputs' type script group
151152
for (output_idx, output) in tx.get_outputs().clone().iter().enumerate() {
@@ -167,6 +168,14 @@ fn inner_build<
167168
tx.input(input.cell_input());
168169
tx.witness(packed::Bytes::default());
169170

171+
inputs.insert(
172+
input.live_cell.out_point.clone(),
173+
(
174+
input.live_cell.output.clone(),
175+
input.live_cell.output_data.clone(),
176+
),
177+
);
178+
170179
let previous_output = input.previous_output();
171180
let lock_script = previous_output.lock();
172181
lock_groups
@@ -203,7 +212,11 @@ fn inner_build<
203212

204213
let tx_view = change_builder.finalize(tx);
205214

206-
return Ok(TransactionWithScriptGroups::new(tx_view, script_groups));
215+
return Ok(TransactionWithScriptGroups::new(
216+
tx_view,
217+
script_groups,
218+
inputs,
219+
));
207220
}
208221
}
209222

src/transaction/handler/omnilock.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ use super::{cell_dep, HandlerContext, ScriptHandler};
99
use crate::{
1010
core::TransactionBuilder,
1111
tx_builder::TxBuilderError,
12+
types::cobuild::{
13+
basic::{Message, SighashAll, SighashAllOnly},
14+
top_level::WitnessLayout,
15+
},
1216
unlock::{OmniLockConfig, OmniUnlockMode},
1317
NetworkInfo, NetworkType, ScriptGroup, ScriptId,
1418
};
@@ -79,10 +83,43 @@ impl ScriptHandler for OmnilockScriptHandler {
7983
if let Some(args) = context.as_any().downcast_ref::<OmnilockScriptContext>() {
8084
tx_builder.dedup_cell_deps(self.cell_deps.clone());
8185
let index = script_group.input_indices.first().unwrap();
82-
let placeholder_witness = args.cfg.placeholder_witness(args.unlock_mode)?;
83-
if let Some(lock) = placeholder_witness.lock().to_opt() {
84-
tx_builder.set_witness_lock(*index, Some(lock.raw_data()));
86+
if args.cfg.enable_cobuild {
87+
let lock_field = args.cfg.placeholder_witness_lock(args.unlock_mode)?;
88+
89+
let witness = match &args.cfg.cobuild_message {
90+
None => {
91+
let sighash_all_only = SighashAllOnly::new_builder()
92+
.seal(
93+
[bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field]
94+
.concat()
95+
.pack(),
96+
)
97+
.build();
98+
let sighash_all_only =
99+
WitnessLayout::new_builder().set(sighash_all_only).build();
100+
sighash_all_only.as_bytes().pack()
101+
}
102+
Some(msg) => {
103+
let sighash_all = SighashAll::new_builder()
104+
.message(Message::new_unchecked(msg.clone()))
105+
.seal(
106+
[bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field]
107+
.concat()
108+
.pack(),
109+
)
110+
.build();
111+
let sighash_all = WitnessLayout::new_builder().set(sighash_all).build();
112+
sighash_all.as_bytes().pack()
113+
}
114+
};
115+
tx_builder.set_witness(*index, witness);
116+
} else {
117+
let placeholder_witness = args.cfg.placeholder_witness(args.unlock_mode)?;
118+
if let Some(lock) = placeholder_witness.lock().to_opt() {
119+
tx_builder.set_witness_lock(*index, Some(lock.raw_data()));
120+
}
85121
}
122+
86123
Ok(true)
87124
} else {
88125
Ok(false)

src/transaction/signer/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use ckb_types::{core, H256};
1+
use ckb_types::{
2+
core,
3+
packed::{CellOutput, OutPoint},
4+
H256,
5+
};
26
use std::collections::HashMap;
37

48
use crate::{
@@ -22,6 +26,7 @@ pub trait CKBScriptSigner {
2226
tx_view: &core::TransactionView,
2327
script_group: &ScriptGroup,
2428
context: &dyn SignContext,
29+
inputs: &HashMap<OutPoint, (CellOutput, bytes::Bytes)>,
2530
) -> Result<core::TransactionView, UnlockError>;
2631
}
2732

@@ -144,7 +149,12 @@ impl TransactionSigner {
144149
if !unlocker.match_context(context.as_ref()) {
145150
continue;
146151
}
147-
tx = unlocker.sign_transaction(&tx, script_group, context.as_ref())?;
152+
tx = unlocker.sign_transaction(
153+
&tx,
154+
script_group,
155+
context.as_ref(),
156+
&transaction.inputs,
157+
)?;
148158
signed_groups_indices.push(idx);
149159
break;
150160
}

src/transaction/signer/multisig.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use ckb_types::core;
1+
use std::collections::HashMap;
2+
3+
use ckb_types::{core, packed};
24

35
use crate::{
46
traits::{dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner},
@@ -45,6 +47,7 @@ impl CKBScriptSigner for Secp256k1Blake160MultisigAllSigner {
4547
transaction: &core::TransactionView,
4648
script_group: &crate::ScriptGroup,
4749
context: &dyn super::SignContext,
50+
_inputs: &HashMap<packed::OutPoint, (packed::CellOutput, bytes::Bytes)>,
4851
) -> Result<core::TransactionView, UnlockError> {
4952
if let Some(args) = context
5053
.as_any()

0 commit comments

Comments
 (0)