Skip to content

Commit

Permalink
feat: omnilock transaction support pubkeyhash/multisign/eth
Browse files Browse the repository at this point in the history
  • Loading branch information
driftluo committed Apr 17, 2024
1 parent 2554656 commit 8af2641
Show file tree
Hide file tree
Showing 11 changed files with 624 additions and 194 deletions.
189 changes: 179 additions & 10 deletions src/tests/transaction/omnilock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use ckb_types::{
use crate::{
constants::ONE_CKB,
tests::{
build_omnilock_script, build_sighash_script, init_context, ACCOUNT0_KEY, ACCOUNT2_ARG,
FEE_RATE, OMNILOCK_BIN,
build_omnilock_script, build_sighash_script, init_context, ACCOUNT0_ARG, ACCOUNT0_KEY,
ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, FEE_RATE, OMNILOCK_BIN,
},
transaction::{
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
Expand All @@ -26,8 +26,8 @@ use crate::{
signer::{SignContexts, TransactionSigner},
TransactionBuilderConfiguration,
},
unlock::OmniLockConfig,
util::keccak160,
unlock::{MultisigConfig, OmniLockConfig},
util::{blake160, keccak160},
NetworkInfo,
};

Expand Down Expand Up @@ -65,11 +65,17 @@ fn test_omnilock_config(omnilock_outpoint: OutPoint) -> TransactionBuilderConfig
}

#[test]
fn test_transfer_from_omnilock_ethereum() {
fn test_omnilock_ethereum() {
omnilock_ethereum(false);
omnilock_ethereum(true)
}

fn omnilock_ethereum(cobuild: bool) {
let network_info = NetworkInfo::testnet();
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key);
let cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref()));
let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref()));
cfg.enable_cobuild(cobuild);

let sender = build_omnilock_script(&cfg);
let receiver = build_sighash_script(ACCOUNT2_ARG);
Expand Down Expand Up @@ -104,8 +110,8 @@ fn test_transfer_from_omnilock_ethereum() {

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

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

TransactionSigner::new(&network_info)
// use unitest lock to verify
Expand All @@ -119,8 +125,171 @@ fn test_transfer_from_omnilock_ethereum() {
)
.unwrap();

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

let tx = tx_with_groups.get_tx_view().clone();
let script_groups = tx_with_groups.script_groups.clone();
assert_eq!(script_groups.len(), 1);
assert_eq!(tx.header_deps().len(), 0);
assert_eq!(tx.cell_deps().len(), 2);
assert_eq!(tx.inputs().len(), 2);
for out_point in tx.input_pts_iter() {
assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender);
}
assert_eq!(tx.outputs().len(), 2);
assert_eq!(tx.output(0).unwrap(), output);
assert_eq!(tx.output(1).unwrap().lock(), sender);
let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack();
let fee = (100 + 200 - 120) * ONE_CKB - change_capacity;
assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee);

ctx.verify(tx, FEE_RATE).unwrap();
}

#[test]
fn test_omnilock_pubkeyhash() {
omnilock_pubkeyhash(false);
omnilock_pubkeyhash(true)
}

fn omnilock_pubkeyhash(cobuild: bool) {
let network_info = NetworkInfo::testnet();
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key);
let mut cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize()));
cfg.enable_cobuild(cobuild);

let sender = build_omnilock_script(&cfg);
let receiver = build_sighash_script(ACCOUNT2_ARG);
let (ctx, mut outpoints) = init_context(
vec![(OMNILOCK_BIN, true)],
vec![
(sender.clone(), Some(100 * ONE_CKB)),
(sender.clone(), Some(200 * ONE_CKB)),
(sender.clone(), Some(300 * ONE_CKB)),
],
);

let configuration = test_omnilock_config(outpoints.pop().unwrap());

let iterator = InputIterator::new_with_cell_collector(
vec![sender.clone()],
Box::new(ctx.to_live_cells_context()) as Box<_>,
);
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);

let output = CellOutput::new_builder()
.capacity((120 * ONE_CKB).pack())
.lock(receiver)
.build();
builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default());
builder.set_change_lock(sender.clone());

let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone());
let mut contexts = HandlerContexts::default();
contexts.add_context(Box::new(context) as Box<_>);

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

// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());

TransactionSigner::new(&network_info)
// use unitest lock to verify
.insert_unlocker(
crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))),
crate::transaction::signer::omnilock::OmnilockSigner {},
)
.sign_transaction(
&mut tx_with_groups,
&SignContexts::new_omnilock(vec![account0_key], cfg),
)
.unwrap();

let tx = tx_with_groups.get_tx_view().clone();
let script_groups = tx_with_groups.script_groups.clone();
assert_eq!(script_groups.len(), 1);
assert_eq!(tx.header_deps().len(), 0);
assert_eq!(tx.cell_deps().len(), 2);
assert_eq!(tx.inputs().len(), 2);
for out_point in tx.input_pts_iter() {
assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender);
}
assert_eq!(tx.outputs().len(), 2);
assert_eq!(tx.output(0).unwrap(), output);
assert_eq!(tx.output(1).unwrap().lock(), sender);
let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack();
let fee = (100 + 200 - 120) * ONE_CKB - change_capacity;
assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee);

ctx.verify(tx, FEE_RATE).unwrap();
}

#[test]
fn test_omnilock_multisign() {
omnilock_multisign(false);
omnilock_multisign(true)
}

fn omnilock_multisign(cobuild: bool) {
let network_info = NetworkInfo::testnet();
let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap();
let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap();
let lock_args = vec![
ACCOUNT0_ARG.clone(),
ACCOUNT1_ARG.clone(),
ACCOUNT2_ARG.clone(),
];
let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap();
let mut cfg = OmniLockConfig::new_multisig(multi_cfg);
cfg.enable_cobuild(cobuild);

let sender = build_omnilock_script(&cfg);
let receiver = build_sighash_script(ACCOUNT2_ARG);

let (ctx, mut outpoints) = init_context(
vec![(OMNILOCK_BIN, true)],
vec![
(sender.clone(), Some(100 * ONE_CKB)),
(sender.clone(), Some(200 * ONE_CKB)),
(sender.clone(), Some(300 * ONE_CKB)),
],
);

let configuration = test_omnilock_config(outpoints.pop().unwrap());

let iterator = InputIterator::new_with_cell_collector(
vec![sender.clone()],
Box::new(ctx.to_live_cells_context()) as Box<_>,
);
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);

let output = CellOutput::new_builder()
.capacity((120 * ONE_CKB).pack())
.lock(receiver)
.build();
builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default());
builder.set_change_lock(sender.clone());

let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone());
let mut contexts = HandlerContexts::default();
contexts.add_context(Box::new(context) as Box<_>);

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

// let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());

TransactionSigner::new(&network_info)
// use unitest lock to verify
.insert_unlocker(
crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))),
crate::transaction::signer::omnilock::OmnilockSigner {},
)
.sign_transaction(
&mut tx_with_groups,
&SignContexts::new_omnilock(vec![account0_key, account1_key], cfg),
)
.unwrap();

let tx = tx_with_groups.get_tx_view().clone();
let script_groups = tx_with_groups.script_groups.clone();
Expand Down
17 changes: 15 additions & 2 deletions src/transaction/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
};
use ckb_types::{
core::{Capacity, TransactionView},
packed::{self, Byte32, CellOutput, Script},
packed::{self, Byte32, CellOutput, OutPoint, Script},
prelude::{Builder, Entity, Pack, Unpack},
};
pub mod fee_calculator;
Expand Down Expand Up @@ -146,6 +146,7 @@ fn inner_build<
) -> Result<TransactionWithScriptGroups, TxBuilderError> {
let mut lock_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
let mut type_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
let mut inputs: HashMap<OutPoint, (CellOutput, bytes::Bytes)> = HashMap::default();

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

inputs.insert(
input.live_cell.out_point.clone(),
(
input.live_cell.output.clone(),
input.live_cell.output_data.clone(),
),
);

let previous_output = input.previous_output();
let lock_script = previous_output.lock();
lock_groups
Expand Down Expand Up @@ -203,7 +212,11 @@ fn inner_build<

let tx_view = change_builder.finalize(tx);

return Ok(TransactionWithScriptGroups::new(tx_view, script_groups));
return Ok(TransactionWithScriptGroups::new(
tx_view,
script_groups,
inputs,
));
}
}

Expand Down
43 changes: 40 additions & 3 deletions src/transaction/handler/omnilock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use super::{cell_dep, HandlerContext, ScriptHandler};
use crate::{
core::TransactionBuilder,
tx_builder::TxBuilderError,
types::cobuild::{
basic::{Message, SighashAll, SighashAllOnly},
top_level::WitnessLayout,
},
unlock::{OmniLockConfig, OmniUnlockMode},
NetworkInfo, NetworkType, ScriptGroup, ScriptId,
};
Expand Down Expand Up @@ -79,10 +83,43 @@ impl ScriptHandler for OmnilockScriptHandler {
if let Some(args) = context.as_any().downcast_ref::<OmnilockScriptContext>() {
tx_builder.dedup_cell_deps(self.cell_deps.clone());
let index = script_group.input_indices.first().unwrap();
let placeholder_witness = args.cfg.placeholder_witness(args.unlock_mode)?;
if let Some(lock) = placeholder_witness.lock().to_opt() {
tx_builder.set_witness_lock(*index, Some(lock.raw_data()));
if args.cfg.enable_cobuild {
let lock_field = args.cfg.placeholder_witness_lock(args.unlock_mode)?;

let witness = match &args.cfg.cobuild_message {
None => {
let sighash_all_only = SighashAllOnly::new_builder()
.seal(
[bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field]
.concat()
.pack(),
)
.build();
let sighash_all_only =
WitnessLayout::new_builder().set(sighash_all_only).build();
sighash_all_only.as_bytes().pack()
}
Some(msg) => {
let sighash_all = SighashAll::new_builder()
.message(Message::new_unchecked(msg.clone()))
.seal(
[bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field]
.concat()
.pack(),
)
.build();
let sighash_all = WitnessLayout::new_builder().set(sighash_all).build();
sighash_all.as_bytes().pack()
}
};
tx_builder.set_witness(*index, witness);
} else {
let placeholder_witness = args.cfg.placeholder_witness(args.unlock_mode)?;
if let Some(lock) = placeholder_witness.lock().to_opt() {
tx_builder.set_witness_lock(*index, Some(lock.raw_data()));
}
}

Ok(true)
} else {
Ok(false)
Expand Down
14 changes: 12 additions & 2 deletions src/transaction/signer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use ckb_types::{core, H256};
use ckb_types::{
core,
packed::{CellOutput, OutPoint},
H256,
};
use std::collections::HashMap;

use crate::{
Expand All @@ -22,6 +26,7 @@ pub trait CKBScriptSigner {
tx_view: &core::TransactionView,
script_group: &ScriptGroup,
context: &dyn SignContext,
inputs: &HashMap<OutPoint, (CellOutput, bytes::Bytes)>,
) -> Result<core::TransactionView, UnlockError>;
}

Expand Down Expand Up @@ -144,7 +149,12 @@ impl TransactionSigner {
if !unlocker.match_context(context.as_ref()) {
continue;
}
tx = unlocker.sign_transaction(&tx, script_group, context.as_ref())?;
tx = unlocker.sign_transaction(
&tx,
script_group,
context.as_ref(),
&transaction.inputs,
)?;
signed_groups_indices.push(idx);
break;
}
Expand Down
5 changes: 4 additions & 1 deletion src/transaction/signer/multisig.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use ckb_types::core;
use std::collections::HashMap;

use ckb_types::{core, packed};

use crate::{
traits::{dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner},
Expand Down Expand Up @@ -45,6 +47,7 @@ impl CKBScriptSigner for Secp256k1Blake160MultisigAllSigner {
transaction: &core::TransactionView,
script_group: &crate::ScriptGroup,
context: &dyn super::SignContext,
_inputs: &HashMap<packed::OutPoint, (packed::CellOutput, bytes::Bytes)>,
) -> Result<core::TransactionView, UnlockError> {
if let Some(args) = context
.as_any()
Expand Down
Loading

0 comments on commit 8af2641

Please sign in to comment.