From 09e57517d601f64d4d987fcceb922c05c9e01af8 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Wed, 5 Oct 2022 19:49:58 +0800 Subject: [PATCH 01/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Add=20functions=20?= =?UTF-8?q?to=20read=20transaction=20data=20and=20hash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add functions to read transaction data; 2. Handle opentx input commands. --- src/unlock/mod.rs | 1 + src/unlock/omni_lock.rs | 49 +++- src/unlock/opentx/hasher.rs | 479 ++++++++++++++++++++++++++++++ src/unlock/opentx/mod.rs | 4 + src/unlock/opentx/reader.rs | 563 ++++++++++++++++++++++++++++++++++++ src/unlock/signer.rs | 3 + 6 files changed, 1092 insertions(+), 7 deletions(-) create mode 100644 src/unlock/opentx/hasher.rs create mode 100644 src/unlock/opentx/mod.rs create mode 100644 src/unlock/opentx/reader.rs diff --git a/src/unlock/mod.rs b/src/unlock/mod.rs index edda5d0d..9cc2cdc1 100644 --- a/src/unlock/mod.rs +++ b/src/unlock/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod omni_lock; +pub mod opentx; pub mod rc_data; mod signer; mod unlocker; diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 12a31720..78dd0642 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -2,6 +2,7 @@ use core::hash; use std::fmt::Display; use crate::{ + constants::SECP_SIGNATURE_SIZE, tx_builder::SinceSource, types::{ omni_lock::{Auth, Identity as IdentityType, IdentityOpt, OmniLockWitnessLock}, @@ -22,7 +23,7 @@ use std::convert::TryFrom; use bitflags::bitflags; -use super::{MultisigConfig, OmniUnlockMode}; +use super::{opentx::OpentxWitness, MultisigConfig, OmniUnlockMode}; use thiserror::Error; #[derive( @@ -196,6 +197,8 @@ bitflags! { const TIME_LOCK = 1<<2; /// supply mode, flag is 1<<3, affected args: type script hash for supply const SUPPLY = 1<<3; + /// open transaction mode. + const OPENTX = 1<<4; } } @@ -419,6 +422,9 @@ pub struct OmniLockConfig { time_lock_config: Option, // 32 bytes type script hash info_cell: Option, + + /// open tx config + opentx_input: Option, } impl OmniLockConfig { @@ -442,6 +448,7 @@ impl OmniLockConfig { acp_config: None, time_lock_config: None, info_cell: None, + opentx_input: None, } } /// Create an ethereum algorithm omnilock with pubkey @@ -488,6 +495,7 @@ impl OmniLockConfig { acp_config: None, time_lock_config: None, info_cell: None, + opentx_input: None, } } @@ -539,6 +547,22 @@ impl OmniLockConfig { self.info_cell = None; } + /// Set the open transaction input data, and set the OmniLockFlags::OPENTX to omni_lock_flags. + pub fn set_opentx_input(&mut self, opentx_input: OpentxWitness) { + self.omni_lock_flags.set(OmniLockFlags::OPENTX, true); + self.opentx_input = Some(opentx_input); + } + + /// Clear the open transaction input data, and clear OmniLockFlags::OPENTX from omni_lock_flags. + pub fn clear_opentx_input(&mut self) { + self.omni_lock_flags.set(OmniLockFlags::OPENTX, false); + self.opentx_input = None; + } + + pub fn get_opentx_input(&self) -> Option<&OpentxWitness> { + self.opentx_input.as_ref() + } + pub fn id(&self) -> &Identity { &self.id } @@ -650,9 +674,15 @@ impl OmniLockConfig { &self, unlock_mode: OmniUnlockMode, ) -> Result { + let mut buf = BytesMut::new(); + if let Some(optx) = self.opentx_input.as_ref() { + buf.extend_from_slice(&optx.to_witness_data()); + } let mut builder = match self.id.flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => OmniLockWitnessLock::new_builder() - .signature(Some(Bytes::from(vec![0u8; 65])).pack()), + IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => { + buf.extend_from_slice(&[0u8; SECP_SIGNATURE_SIZE]); + OmniLockWitnessLock::new_builder().signature(Some(buf.freeze()).pack()) + } IdentityFlag::Multisig => { let multisig_config = match unlock_mode { OmniUnlockMode::Admin => self @@ -668,10 +698,15 @@ impl OmniLockConfig { .ok_or(ConfigError::NoMultiSigConfig)?, }; let config_data = multisig_config.to_witness_data(); - let multisig_len = config_data.len() + multisig_config.threshold() as usize * 65; - let mut omni_sig = vec![0u8; multisig_len]; - omni_sig[..config_data.len()].copy_from_slice(&config_data); - OmniLockWitnessLock::new_builder().signature(Some(Bytes::from(omni_sig)).pack()) + let multisig_len = + config_data.len() + multisig_config.threshold() as usize * SECP_SIGNATURE_SIZE; + // let mut omni_sig = vec![0u8; multisig_len]; + // omni_sig[..config_data.len()].copy_from_slice(&config_data); + // buf.extend_from_slice(&omni_sig); + let offset = buf.len(); + buf.extend_from_slice(&config_data); + buf.resize(offset + multisig_len, 0u8); + OmniLockWitnessLock::new_builder().signature(Some(buf.freeze()).pack()) } IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder(), _ => todo!("to support other placeholder_witness_lock implementions"), diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs new file mode 100644 index 00000000..71565b43 --- /dev/null +++ b/src/unlock/opentx/hasher.rs @@ -0,0 +1,479 @@ +use bitflags::bitflags; +use bytes::Bytes; +use ckb_hash::Blake2b; +use ckb_types::{bytes::BytesMut, prelude::*}; +use serde::{Deserialize, Serialize}; + +use enum_repr_derive::{FromEnumToRepr, TryFromReprToEnum}; + +use crate::unlock::ScriptSignError; + +use super::reader::{OpenTxCellField, OpenTxReader, OpenTxReaderError, OpenTxSource}; + +use thiserror::Error; +const ARG1_MASK: u16 = 0xFFF; +const ARG2_MASK: u16 = 0xFFF; + +#[derive(Error, Debug)] +pub enum OpenTxHashError { + #[error("arg1(`{0}`) out of range")] + Arg1OutOfRange(u16), + #[error("arg2(`{0}`) out of range")] + Arg2OutOfRange(u16), +} +/// Open transaction signature input command. +#[derive( + Clone, + Copy, + Serialize, + Deserialize, + Debug, + Hash, + Eq, + PartialEq, + TryFromReprToEnum, + FromEnumToRepr, +)] +#[repr(u8)] +pub enum OpentxCommand { + /// Hash the full current transaction hash + TxHash = 0x00, + /// Hash length of input & output cells in current script group + GroupInputOutputLen = 0x01, + /// Hash part or the whole output cell. arg1 is index of output cell, arg2 is cell mask. + IndexOutput = 0x11, + /// Hash part or the whole output cell. arg1 is offset of output cell, arg2 is cell mask. + OffsetOutput = 0x12, + /// Hash part or the whole input cell. arg1 is index of input cell, arg2 is cell mask. + IndexInput = 0x13, + /// Hash part or the whole input cell. arg1 is offset of input cell, arg2 is cell mask. + OffsetInput = 0x14, + /// Hash part or the whole cell input structure, arg1 is index of input cell, arg2 is input mask. + CellInputIndex = 0x15, + /// Hash part or the whole cell input structure, arg1 is offset of input cell, arg2 is input mask.` + CellInputOffset = 0x16, + /// Concatenate ARG 1 and ARG 2, arg1 is lower 12 bit, arg2 is higher 12 bit. + /// The purpose of this command is to add salt for hash. + ConcatArg1Arg2 = 0x20, + /// Terminate and generate the final blake2b hash + End = 0xF0, +} + +bitflags! { + /// The bits control the data to generate from a cell. + #[derive(Serialize, Deserialize)] + pub struct CellMask: u16 { + /// capacity + const CAPACITY = 0x1; + /// lock.code_hash + const LOCK_CODE_HASH = 0x2; + /// lock.hash_type + const LOCK_HASH_TYPE = 0x4; + /// lock.args + const LOCK_ARGS = 0x8; + /// type.code_hash + const TYPE_CODE_HASH = 0x10; + /// type.hash_type + const TYPE_HASH_TYPE = 0x20; + /// type.args + const TYPE_ARGS = 0x40; + /// Cell data + const CELL_DATA = 0x80; + /// Lock script hash + const TYPE_SCRIPT_HASH = 0x100; + /// Type script hash + const LOCK_SCRIPT_HASH = 0x200; + /// The whole cell + const WHOLE_CELL = 0x400; + } +} +bitflags! { + /// The bits control the data to generate from a CellInput structure. + #[derive(Serialize, Deserialize)] + pub struct InputMask: u16 { + /// previous_output.tx_hash + const TX_HASH = 0x1; + /// previous_output.index + const INDEX = 0x2; + /// since + const SINCE = 0x4; + /// previous_output + const PREVIOUS_OUTPUT = 0x8; + /// The whole CellInput structure + const WHOLE = 0x10; + } +} + +#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] +pub struct OpenTxSigInput { + pub cmd: OpentxCommand, + pub arg1: u16, + pub arg2: u16, +} + +impl OpenTxSigInput { + pub fn compose(&self) -> u32 { + (self.cmd as u32) + + (((self.arg1 & ARG1_MASK) as u32) << 8) + + (((self.arg2 & ARG2_MASK) as u32) << 20) + } + + /// new OpentxCommand::TxHash OpenTxSigInput, command 0x00 + pub fn new_tx_hash() -> OpenTxSigInput { + OpenTxSigInput { + cmd: OpentxCommand::TxHash, + arg1: 0, + arg2: 0, + } + } + // new OpentxCommand::GroupInputOutputLen OpenTxSigInput, command 0x01 + pub fn new_group_input_output_len() -> OpenTxSigInput { + OpenTxSigInput { + cmd: OpentxCommand::GroupInputOutputLen, + arg1: 0, + arg2: 0, + } + } + /// new OpentxCommand::IndexOutput OpenTxSigInput, command 0x11 + pub fn new_index_output(arg1: u16, arg2: CellMask) -> Result { + Self::new_cell_command(OpentxCommand::IndexOutput, arg1, arg2) + } + /// new OpentxCommand::OffsetOutput OpenTxSigInput, command 0x12 + pub fn new_offset_output(arg1: u16, arg2: CellMask) -> Result { + Self::new_cell_command(OpentxCommand::OffsetOutput, arg1, arg2) + } + /// new OpentxCommand::IndexInput OpenTxSigInput, command 0x13 + pub fn new_index_input(arg1: u16, arg2: CellMask) -> Result { + Self::new_cell_command(OpentxCommand::IndexInput, arg1, arg2) + } + /// new OpentxCommand::OffsetInput OpenTxSigInput, command 0x14 + pub fn new_offset_input(arg1: u16, arg2: CellMask) -> Result { + Self::new_cell_command(OpentxCommand::OffsetInput, arg1, arg2) + } + /// new OpenTxSigInput to handle part or the whole input/output cell + pub fn new_cell_command( + cmd: OpentxCommand, + arg1: u16, + arg2: CellMask, + ) -> Result { + if arg1 > ARG1_MASK { + return Err(OpenTxHashError::Arg1OutOfRange(arg1)); + } + + Ok(OpenTxSigInput { + cmd, + arg1, + arg2: arg2.bits, + }) + } + /// new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x15 + pub fn new_cell_input_index( + arg1: u16, + arg2: InputMask, + ) -> Result { + Self::new_input_command(OpentxCommand::CellInputIndex, arg1, arg2) + } + //// new OpentxCommand::CellInputOffset OpenTxSigInput, command 0x16 + pub fn new_cell_input_offset( + arg1: u16, + arg2: InputMask, + ) -> Result { + Self::new_input_command(OpentxCommand::CellInputOffset, arg1, arg2) + } + /// new OpenTxSigInput to hash part or the whole cell input structure + pub fn new_input_command( + cmd: OpentxCommand, + arg1: u16, + arg2: InputMask, + ) -> Result { + if arg1 > ARG1_MASK { + return Err(OpenTxHashError::Arg1OutOfRange(arg1)); + } + + Ok(OpenTxSigInput { + cmd, + arg1, + arg2: arg2.bits, + }) + } + + /// new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x20 + pub fn new_concat_arg1_arg2(arg1: u16, arg2: u16) -> OpenTxSigInput { + OpenTxSigInput { + cmd: OpentxCommand::ConcatArg1Arg2, + arg1: arg1 & ARG1_MASK, + arg2: arg2 & ARG2_MASK, + } + } + /// new OpentxCommand::End OpenTxSigInput, command 0xF0 + pub fn new_end() -> OpenTxSigInput { + OpenTxSigInput { + cmd: OpentxCommand::End, + arg1: 0, + arg2: 0, + } + } + fn hash_cell( + &self, + cache: &mut OpentxCache, + reader: &OpenTxReader, + is_input: bool, + with_offset: bool, + base_index: u32, + ) -> Result<(), OpenTxReaderError> { + let mut index = self.arg1 as usize; + if with_offset { + index += base_index as usize; + } + let source = if is_input { + OpenTxSource::Input + } else { + OpenTxSource::Outpout + }; + let cell_mask = CellMask::from_bits_truncate(self.arg2); + if cell_mask.contains(CellMask::CAPACITY) { + let data = reader.load_cell_field(index, source, OpenTxCellField::Capacity)?; + cache.update(&data); + } + if cell_mask.intersects( + CellMask::LOCK_CODE_HASH + | CellMask::LOCK_HASH_TYPE + | CellMask::LOCK_ARGS + | CellMask::TYPE_CODE_HASH + | CellMask::TYPE_HASH_TYPE + | CellMask::TYPE_ARGS, + ) { + let cell = reader.get_cell(index, is_input)?; + let lock = cell.lock(); + if cell_mask.contains(CellMask::LOCK_CODE_HASH) { + cache.update(lock.code_hash().as_slice()); + } + if cell_mask.contains(CellMask::LOCK_HASH_TYPE) { + cache.update(lock.hash_type().as_slice()); + } + if cell_mask.contains(CellMask::LOCK_ARGS) { + let args = lock.args().raw_data().to_vec(); + cache.update(args.as_slice()); + } + + if let Some(type_) = cell.type_().to_opt() { + if cell_mask.contains(CellMask::TYPE_CODE_HASH) { + cache.update(type_.code_hash().as_slice()); + } + if cell_mask.contains(CellMask::TYPE_HASH_TYPE) { + cache.update(type_.hash_type().as_slice()); + } + if cell_mask.contains(CellMask::TYPE_ARGS) { + let args = type_.args().raw_data().to_vec(); + cache.update(&args); + } + } + } + if cell_mask.contains(CellMask::CELL_DATA) { + let data = reader.load_cell_data(index, source)?; + cache.update(data.as_slice()); + } + + if cell_mask.contains(CellMask::TYPE_SCRIPT_HASH) { + let cell = reader.get_cell(index, is_input)?; + if let Some(script) = cell.type_().to_opt() { + let hash = script.calc_script_hash(); + cache.update(hash.as_slice()); + } + } + + if cell_mask.contains(CellMask::LOCK_SCRIPT_HASH) { + let cell = reader.get_cell(index, is_input)?; + let hash = cell.lock().calc_script_hash(); + cache.update(hash.as_slice()); + } + + if cell_mask.contains(CellMask::WHOLE_CELL) { + let data = reader.load_cell(index, source)?; + cache.update(data.as_slice()); + } + Result::Ok(()) + } + + fn hash_input( + &self, + cache: &mut OpentxCache, + ckb_sys_call: &OpenTxReader, + with_offset: bool, + base_index: u32, + ) -> Result<(), OpenTxReaderError> { + let index = if with_offset { + self.arg1 as usize + base_index as usize + } else { + self.arg1 as usize + }; + + let input_mask = InputMask::from_bits_truncate(self.arg2); + if input_mask.contains(InputMask::TX_HASH) { + let cell = ckb_sys_call.input(index)?; + let data = cell.previous_output().tx_hash(); + cache.update(data.as_slice()); + } + + if input_mask.contains(InputMask::INDEX) { + let cell = ckb_sys_call.input(index)?; + let data = cell.previous_output().index(); + cache.update(data.as_slice()); + } + + if input_mask.contains(InputMask::SINCE) { + let data = ckb_sys_call.load_input_field_since(index)?; + + cache.update(&data); + } + + if input_mask.contains(InputMask::PREVIOUS_OUTPUT) { + let data = ckb_sys_call.load_input_field_out_point(index)?; + + cache.update(&data); + } + + if input_mask.contains(InputMask::WHOLE) { + let data = ckb_sys_call.load_input(index)?; + cache.update(&data); + } + Ok(()) + } +} +#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] +pub struct OpentxWitness { + pub base_input_index: u32, + pub base_output_index: u32, + pub inputs: Vec, +} + +impl OpentxWitness { + pub fn new_empty() -> Self { + OpentxWitness { + base_input_index: 0, + base_output_index: 0, + inputs: vec![], + } + } + pub fn new(input_index: u32, output_index: u32, input: Vec) -> Self { + OpentxWitness { + base_input_index: input_index, + base_output_index: output_index, + inputs: input, + } + } + + pub fn get_opentx_sig_len(&self) -> usize { + 4 + 4 + 4 * self.inputs.len() + } + + pub fn set_base_input_index(&mut self, index: u32) { + self.base_input_index = index; + } + + pub fn set_base_output_index(&mut self, index: u32) { + self.base_output_index = index; + } + + pub fn to_witness_data(&self) -> Vec { + let capacity = self.get_opentx_sig_len(); + let mut witness_data = Vec::with_capacity(capacity); + witness_data.extend_from_slice(&self.base_input_index.to_le_bytes()); + witness_data.extend_from_slice(&self.base_output_index.to_le_bytes()); + for inpt in &self.inputs { + witness_data.extend_from_slice(&inpt.compose().to_le_bytes()); + } + witness_data + } + + pub fn generate_message( + &self, + reader: &OpenTxReader, + ) -> Result<([u8; 32], Bytes), ScriptSignError> { + let (is_input, is_output) = (true, false); + let (relative_idx, absolute_idx) = (true, false); + + let mut cache = OpentxCache::new(); + let mut s_data = BytesMut::with_capacity(self.inputs.len() * 4); + for si in &self.inputs { + match si.cmd { + OpentxCommand::TxHash => { + let tx_hash = reader.tx_hash(); + cache.update(tx_hash.as_slice()); + } + OpentxCommand::GroupInputOutputLen => { + let input_len = reader.group_input_len()?; + cache.update(&input_len.to_le_bytes()); + let output_len = reader.group_output_len()?; + cache.update(&output_len.to_le_bytes()); + } + OpentxCommand::IndexOutput => { + si.hash_cell(&mut cache, reader, is_output, absolute_idx, 0)?; + } + OpentxCommand::OffsetOutput => { + si.hash_cell( + &mut cache, + reader, + is_output, + relative_idx, + self.base_output_index, + )?; + } + OpentxCommand::IndexInput => { + si.hash_cell(&mut cache, reader, is_input, absolute_idx, 0)?; + } + OpentxCommand::OffsetInput => { + si.hash_cell( + &mut cache, + reader, + is_input, + relative_idx, + self.base_input_index, + )?; + } + OpentxCommand::CellInputIndex => { + si.hash_input(&mut cache, reader, absolute_idx, 0)?; + } + OpentxCommand::CellInputOffset => { + si.hash_input(&mut cache, reader, is_input, self.base_input_index)?; + } + OpentxCommand::ConcatArg1Arg2 => { + let data = (si.arg1 & 0xfff) as u32 | ((si.arg2 & 0xfff) << 12) as u32; + let data = data.to_le_bytes(); + cache.update(&data[0..3]); + } + OpentxCommand::End => { + break; + } + } + s_data.extend_from_slice(&si.compose().to_le_bytes()); + } + let s_data = s_data.freeze(); + cache.update(s_data.to_vec().as_slice()); + + let msg = cache.finalize(); + Ok((msg, s_data)) + } +} + +struct OpentxCache { + blake2b: Blake2b, +} + +impl OpentxCache { + pub fn new() -> Self { + OpentxCache { + blake2b: ckb_hash::new_blake2b(), + } + } + + pub fn update(&mut self, data: &[u8]) { + self.blake2b.update(data); + } + + pub fn finalize(self) -> [u8; 32] { + let mut msg = [0u8; 32]; + self.blake2b.finalize(&mut msg); + msg + } +} diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs new file mode 100644 index 00000000..3e049a97 --- /dev/null +++ b/src/unlock/opentx/mod.rs @@ -0,0 +1,4 @@ +pub mod hasher; +pub mod reader; + +pub use hasher::OpentxWitness; diff --git a/src/unlock/opentx/reader.rs b/src/unlock/opentx/reader.rs new file mode 100644 index 00000000..a1857272 --- /dev/null +++ b/src/unlock/opentx/reader.rs @@ -0,0 +1,563 @@ +use std::cmp::Ordering; +use std::convert::TryFrom; + +use ckb_hash::blake2b_256; +use ckb_types::{ + core::{Capacity, TransactionView}, + packed::{Byte32, CellDep, CellInput, CellOutput, OutPoint, Script}, + prelude::Entity, +}; + +use crate::traits::{TransactionDependencyError, TransactionDependencyProvider}; +use thiserror::Error; + +#[derive(Copy, Clone, PartialEq)] +pub enum OpenTxSource { + Input, + GroupInput, + Outpout, + GroupOutpout, + CellDep, +} + +#[derive(Copy, Clone)] +pub enum OpenTxCellField { + Capacity, + DataHash, + Lock, + LockHash, + Type, + TypeHash, + OccupiedCapacity, +} + +#[derive(Copy, Clone)] +pub enum OpenTxInputField { + OutPoint, + Since, +} + +#[derive(Error, Debug)] +pub enum OpenTxReaderError { + #[error("Transaction read error")] + OutOfBound, + #[error("ItemMissing")] + ItemMissing, + #[error("LengthNotEnough")] + LengthNotEnough, + #[error("InvalidData")] + InvalidData, + #[error("Fail to get cell `{0}`")] + CellNotExist(#[from] TransactionDependencyError), + #[error("Unsupport data source")] + UnsupportSource, + #[error("usize(`{0}`) to u64 overflow.")] + LenOverflow(usize), +} + +pub struct OpenTxReader { + pub transaction: TransactionView, + pub provider: Box, + /// map group input index to input index + group_input_index: Vec, + /// map group output index to output index + group_output_index: Vec, + /// open tx lock hash + script_hash: Byte32, +} + +impl OpenTxReader { + pub fn new( + transaction: &TransactionView, + provider: Box, + script_hash: Byte32, + ) -> Result { + let mut group_input_index = Vec::new(); + // all lock + for index in 0..transaction.inputs().len() { + let lock_hash = provider + .get_cell(&transaction.inputs().get(index).unwrap().previous_output())? + .lock() + .calc_script_hash(); + if lock_hash.cmp(&script_hash) == Ordering::Equal { + group_input_index.push(index); + } + } + let mut group_output_index = Vec::new(); + for index in 0..transaction.outputs().len() { + let lock_hash = transaction.output(index).unwrap().lock().calc_script_hash(); + if lock_hash.cmp(&script_hash) == Ordering::Equal { + group_output_index.push(index); + } + } + Ok(OpenTxReader { + transaction: transaction.clone(), + provider, + group_input_index, + group_output_index, + script_hash, + }) + } + + /// get the group input length. + pub fn group_input_len(&self) -> Result { + let len = self.group_input_index.len(); + let len = u64::try_from(len).map_err(|_e| OpenTxReaderError::LenOverflow(len))?; + Ok(len) + } + + /// get the group output length + pub fn group_output_len(&self) -> Result { + let len = self.group_output_index.len(); + let len = u64::try_from(len).map_err(|_e| OpenTxReaderError::LenOverflow(len))?; + Ok(len) + } + + /// Get input at absolute index + pub fn input(&self, index: usize) -> Result { + self.transaction + .inputs() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound) + } + /// Get previous output of input + /// # Arguments + /// * `index` absolute index of inputs. + fn input_previous_output(&self, index: usize) -> Result { + let cell = self.input(index)?; + Ok(cell.previous_output()) + } + + /// Get CellOutput of input's cell + /// # Arguments + /// * `index` absolute index of inputs. + fn input_cell(&self, index: usize) -> Result { + let previous_output = self.input_previous_output(index)?; + let cell_output = self.provider.get_cell(&previous_output).unwrap(); + Ok(cell_output) + } + + /// Get CellOutput of input's cell + /// # Arguments + /// * `index` absolute index of inputs. + fn group_input_cell(&self, index: usize) -> Result { + if self.group_input_index.len() <= index { + return Result::Err(OpenTxReaderError::OutOfBound); + } + let index = self.group_input_index[index]; + self.input_cell(index) + } + /// Get cell data of input's cell + /// # Arguments + /// * `index` absolute index of inputs. + fn input_cell_data(&self, index: usize) -> Result { + let previous_output = self.input_previous_output(index)?; + let cell_data = self.provider.get_cell_data(&previous_output)?; + Ok(cell_data) + } + /// Get cell data of input's cell + /// # Arguments + /// * `index` absolute index of output. + fn output_cell(&self, index: usize) -> Result { + self.transaction + .output(index) + .ok_or(OpenTxReaderError::OutOfBound) + } + /// Get cell data of input's cell + /// # Arguments + /// * `index` absolute index of output group. + fn group_output_cell(&self, index: usize) -> Result { + if self.group_output_index.len() <= index { + return Result::Err(OpenTxReaderError::OutOfBound); + } + let index = self.group_output_index[index]; + self.output_cell(index) + } + /// Get cell raw data of output's cell + /// # Arguments + /// * `index` absolute index of output. + fn output_cell_data(&self, index: usize) -> Result { + Ok(self + .transaction + .outputs_data() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)? + .raw_data()) + } + /// Get CellDep of cell depends + /// # Arguments + /// * `index` absolute index of cell deps. + fn cell_dep(&self, index: usize) -> Result { + self.transaction + .cell_deps() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound) + } + /// Get CellOutput of cell depend + /// # Arguments + /// * `index` absolute index of cell deps. + fn cell_dep_cell(&self, index: usize) -> Result { + let outpoint = self.cell_dep(index)?; + let cell = self.provider.get_cell(&outpoint.out_point())?; + Ok(cell) + } + fn cell_dep_cell_data(&self, index: usize) -> Result { + let outpoint = self.cell_dep(index)?; + let cell = self.provider.get_cell_data(&outpoint.out_point())?; + + Ok(cell) + } + /// fetch the hash of the current running transaction + pub fn tx_hash(&self) -> Byte32 { + self.transaction.hash() + } + + pub fn load_transaction(&self) -> Vec { + self.transaction.data().as_slice().to_vec() + } + + pub fn load_script_hash(&self) -> Byte32 { + self.script_hash.clone() + } + + pub fn load_cell( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + let cell = match source { + OpenTxSource::Input => self.input_cell(index), + OpenTxSource::Outpout => self.output_cell(index), + OpenTxSource::CellDep => self.cell_dep_cell(index), + _ => Err(OpenTxReaderError::UnsupportSource), + }; + Ok(cell?.as_slice().to_vec()) + } + + pub fn load_cell_data( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + let data = match source { + OpenTxSource::Input => self.input_cell_data(index)?.to_vec(), + OpenTxSource::Outpout => self.output_cell_data(index)?.to_vec(), + OpenTxSource::CellDep => self.cell_dep_cell_data(index)?.to_vec(), + _ => return Err(OpenTxReaderError::UnsupportSource), + }; + Ok(data.to_vec()) + } + + pub fn load_input(&self, index: usize) -> Result, OpenTxReaderError> { + let input = self.input(index)?; + Result::Ok(input.as_slice().to_vec()) + } + + fn load_field_capacity( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + let cell = match source { + OpenTxSource::Input => self.input_cell(index)?, + OpenTxSource::Outpout => self.output_cell(index)?, + OpenTxSource::GroupInput => self.group_input_cell(index)?, + OpenTxSource::GroupOutpout => self.group_output_cell(index)?, + OpenTxSource::CellDep => self.cell_dep_cell(index)?, + }; + Ok(cell.capacity().raw_data().to_vec()) + } + + fn load_field_data_hash( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell_data(index)?; + + let data = input.to_vec(); + Result::Ok(if data.is_empty() { + [0u8; 32].to_vec() + } else { + blake2b_256(data).to_vec() + }) + } + OpenTxSource::Outpout => { + let output = self + .transaction + .outputs_data() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + // TODO: why split here? + let data = output.as_slice().split_at(4).1.to_vec(); + if data.is_empty() { + Result::Ok([0u8; 32].to_vec()) + } else { + Result::Ok(data) + } + } + OpenTxSource::CellDep => { + let outpoint = self.transaction.cell_deps().get(index); + if outpoint.is_none() { + return Result::Err(OpenTxReaderError::OutOfBound); + } + let data = self + .provider + .get_cell_data(&outpoint.unwrap().out_point())?; + Result::Ok(if data.is_empty() { + [0u8; 32].to_vec() + } else { + blake2b_256(&data).to_vec() + }) + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + fn load_field_lock( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell(index)?; + Result::Ok(input.lock().as_bytes().to_vec()) + } + OpenTxSource::Outpout => { + let output = self + .transaction + .output(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + Result::Ok(output.lock().as_bytes().to_vec()) + } + OpenTxSource::CellDep => { + let outpoint = self + .transaction + .cell_deps() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + let cell = self.provider.get_cell(&outpoint.out_point())?; + Result::Ok(cell.lock().as_bytes().to_vec()) + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + fn load_field_lock_hash( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell(index)?; + Result::Ok(input.calc_lock_hash().as_bytes().to_vec()) + } + OpenTxSource::Outpout => { + let output = self + .transaction + .output(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + Result::Ok(output.calc_lock_hash().as_bytes().to_vec()) + } + OpenTxSource::CellDep => { + let outpoint = self + .transaction + .cell_deps() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + let cell = self.provider.get_cell(&outpoint.out_point())?; + Result::Ok(cell.calc_lock_hash().as_bytes().to_vec()) + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + fn load_field_type( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell(index)?; + let d = input.type_(); + if d.is_none() { + Result::Err(OpenTxReaderError::ItemMissing) + } else { + Result::Ok(d.as_bytes().to_vec()) + } + } + OpenTxSource::Outpout => { + let output = self + .transaction + .output(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + let d = output.type_(); + if d.is_none() { + Result::Err(OpenTxReaderError::ItemMissing) + } else { + Result::Ok(d.as_bytes().to_vec()) + } + } + OpenTxSource::CellDep => { + let outpoint = self + .transaction + .cell_deps() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + let cell = self.provider.get_cell(&outpoint.out_point())?; + let d = cell.type_(); + if d.is_none() { + Result::Err(OpenTxReaderError::ItemMissing) + } else { + Result::Ok(d.as_bytes().to_vec()) + } + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + fn load_field_type_hash( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell(index)?; + let d = input.type_(); + if d.is_none() { + Result::Err(OpenTxReaderError::ItemMissing) + } else { + let d = Script::from_slice(d.as_slice()).unwrap(); + Result::Ok(d.calc_script_hash().as_slice().to_vec()) + } + } + OpenTxSource::Outpout => { + let output = self + .transaction + .output(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + let d = output + .type_() + .to_opt() + .ok_or(OpenTxReaderError::ItemMissing)?; + + Result::Ok(d.calc_script_hash().as_slice().to_vec()) + } + OpenTxSource::CellDep => { + let outpoint = self.transaction.cell_deps().get(index); + if outpoint.is_none() { + return Result::Err(OpenTxReaderError::OutOfBound); + } + let cell = self.provider.get_cell(&outpoint.unwrap().out_point())?; + let d = cell + .type_() + .to_opt() + .ok_or(OpenTxReaderError::ItemMissing)?; + + Result::Ok(d.calc_script_hash().as_slice().to_vec()) + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + fn load_field_occupied_capacity( + &self, + index: usize, + source: OpenTxSource, + ) -> Result, OpenTxReaderError> { + match source { + OpenTxSource::Input => { + let input = self.input_cell(index)?; + let data = self.input_cell_data(index)?; + Result::Ok( + input + .occupied_capacity(Capacity::bytes(data.len()).unwrap()) + .unwrap() + .as_u64() + .to_le_bytes() + .to_vec(), + ) + } + OpenTxSource::Outpout => { + let output = self.output_cell(index)?; + let output_data = self + .transaction + .outputs_data() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + + Result::Ok( + output + .occupied_capacity(Capacity::bytes(output_data.len()).unwrap()) + .unwrap() + .as_u64() + .to_le_bytes() + .to_vec(), + ) + } + OpenTxSource::CellDep => { + let cell = self + .transaction + .cell_deps() + .get(index) + .ok_or(OpenTxReaderError::OutOfBound)?; + let cell_output = self.provider.get_cell(&cell.out_point())?; + let cell_data = self.provider.get_cell_data(&cell.out_point())?; + Result::Ok( + cell_output + .occupied_capacity(Capacity::bytes(cell_data.len()).unwrap()) + .unwrap() + .as_u64() + .to_le_bytes() + .to_vec(), + ) + } + _ => Err(OpenTxReaderError::UnsupportSource), + } + } + + pub fn load_cell_field( + &self, + index: usize, + source: OpenTxSource, + field: OpenTxCellField, + ) -> Result, OpenTxReaderError> { + match field { + OpenTxCellField::Capacity => self.load_field_capacity(index, source), + OpenTxCellField::DataHash => self.load_field_data_hash(index, source), + OpenTxCellField::Lock => self.load_field_lock(index, source), + OpenTxCellField::LockHash => self.load_field_lock_hash(index, source), + OpenTxCellField::Type => self.load_field_type(index, source), + OpenTxCellField::TypeHash => self.load_field_type_hash(index, source), + OpenTxCellField::OccupiedCapacity => self.load_field_occupied_capacity(index, source), + } + } + + pub fn load_input_field_out_point(&self, index: usize) -> Result, OpenTxReaderError> { + Ok(self.input(index)?.previous_output().as_slice().to_vec()) + } + + pub fn load_input_field_since(&self, index: usize) -> Result, OpenTxReaderError> { + Ok(self.input(index)?.since().as_slice().to_vec()) + } + + pub fn get_cell(&self, index: usize, is_input: bool) -> Result { + let cell = if is_input { + self.input_cell(index)? + } else { + self.output_cell(index)? + }; + Ok(cell) + } +} diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index a7da222e..22c8c83e 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -22,6 +22,7 @@ use crate::{ use super::{ omni_lock::{ConfigError, Identity}, + opentx::reader::OpenTxReaderError, IdentityFlag, OmniLockConfig, }; @@ -48,6 +49,8 @@ pub enum ScriptSignError { #[error("there is an configuration error: `{0}`")] InvalidConfig(#[from] ConfigError), + #[error("open transaction read error: `{0}`")] + OpenTxError(#[from] OpenTxReaderError), #[error(transparent)] Other(#[from] anyhow::Error), } From c0514f102f564dd85b3a3897f3721933571295d2 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Mon, 10 Oct 2022 15:58:43 +0800 Subject: [PATCH 02/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20function=20t?= =?UTF-8?q?o=20assemble=20multiple=20opentx=20to=20a=20tx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unlock/opentx/assembler.rs | 75 ++++++++++ src/unlock/opentx/hasher.rs | 260 +++++++++++++++++++++++++++------ src/unlock/opentx/mod.rs | 28 ++++ src/unlock/opentx/reader.rs | 164 ++++++++------------- src/unlock/signer.rs | 4 +- 5 files changed, 387 insertions(+), 144 deletions(-) create mode 100644 src/unlock/opentx/assembler.rs diff --git a/src/unlock/opentx/assembler.rs b/src/unlock/opentx/assembler.rs new file mode 100644 index 00000000..fcac126f --- /dev/null +++ b/src/unlock/opentx/assembler.rs @@ -0,0 +1,75 @@ +use anyhow::anyhow; +use std::convert::TryFrom; + +use std::{cmp::Ordering, collections::HashSet}; + +use ckb_types::{ + core::TransactionView, + packed::{Byte32, WitnessArgs}, + prelude::*, +}; + +use crate::{traits::TransactionDependencyProvider, unlock::omni_lock::OmniLockFlags}; + +use super::OpenTxError; + +/// Assemble a transaction from multiple opentransaction, remove duplicate cell deps and header deps. +/// Alter base input/output index. +pub fn assemble_new_tx( + mut txes: Vec, + provider: Box, + script_hash: Byte32, +) -> Result { + if txes.len() == 1 { + return Ok(txes.remove(0)); + } + let mut builder = TransactionView::new_advanced_builder(); + let mut cell_deps = HashSet::new(); + let mut header_deps = HashSet::new(); + let mut base_input_idx = 0usize; + let mut base_output_idx = 0usize; + for tx in txes.iter() { + cell_deps.extend(tx.cell_deps()); + header_deps.extend(tx.header_deps()); + builder = builder.inputs(tx.inputs()); + // handle opentx witness + for (input, witness) in tx.inputs().into_iter().zip(tx.witnesses().into_iter()) { + let lock = provider.get_cell(&input.previous_output())?.lock(); + let lock_hash = lock.calc_script_hash(); + if lock_hash.cmp(&script_hash) == Ordering::Equal { + let args = &lock.args().raw_data(); + if args.len() >= 22 + && OmniLockFlags::from_bits_truncate(args[21]).contains(OmniLockFlags::OPENTX) + { + let mut data = (&witness.raw_data()).to_vec(); + let mut tmp = [0u8; 4]; + tmp.copy_from_slice(&data[0..4]); + let this_base_input_idx = u32::from_le_bytes(tmp) + + u32::try_from(base_input_idx).map_err(|e| anyhow!(e))?; + data[0..4].copy_from_slice(&this_base_input_idx.to_le_bytes()); + + tmp.copy_from_slice(&data[4..8]); + let this_base_output_idx = u32::from_le_bytes(tmp) + + u32::try_from(base_output_idx).map_err(|e| anyhow!(e))?; + data[4..8].copy_from_slice(&this_base_output_idx.to_le_bytes()); + let witness = WitnessArgs::from_slice(&data) + .map_err(|e| anyhow!(e))? + .as_bytes() + .pack(); + + builder = builder.witness(witness); + continue; + } + } + builder = builder.witness(witness); + } + builder = builder.outputs(tx.outputs()); + builder = builder.outputs_data(tx.outputs_data()); + + base_input_idx += tx.inputs().len(); + base_output_idx += tx.outputs().len(); + } + builder = builder.cell_deps(cell_deps).header_deps(header_deps); + + Ok(builder.build()) +} diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 71565b43..012a9664 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -1,26 +1,19 @@ +use anyhow::anyhow; use bitflags::bitflags; use bytes::Bytes; use ckb_hash::Blake2b; -use ckb_types::{bytes::BytesMut, prelude::*}; +use ckb_types::{bytes::BytesMut, core::TransactionView, prelude::*}; use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; use enum_repr_derive::{FromEnumToRepr, TryFromReprToEnum}; -use crate::unlock::ScriptSignError; +use super::reader::{OpenTxCellField, OpenTxReader, OpenTxSource}; +use super::OpenTxError; -use super::reader::{OpenTxCellField, OpenTxReader, OpenTxReaderError, OpenTxSource}; - -use thiserror::Error; const ARG1_MASK: u16 = 0xFFF; const ARG2_MASK: u16 = 0xFFF; -#[derive(Error, Debug)] -pub enum OpenTxHashError { - #[error("arg1(`{0}`) out of range")] - Arg1OutOfRange(u16), - #[error("arg2(`{0}`) out of range")] - Arg2OutOfRange(u16), -} /// Open transaction signature input command. #[derive( Clone, @@ -59,6 +52,15 @@ pub enum OpentxCommand { End = 0xF0, } +impl OpentxCommand { + pub fn is_index(&self) -> bool { + matches!( + self, + OpentxCommand::IndexOutput | OpentxCommand::IndexInput | OpentxCommand::CellInputIndex + ) + } +} + bitflags! { /// The bits control the data to generate from a cell. #[derive(Serialize, Deserialize)] @@ -87,6 +89,7 @@ bitflags! { const WHOLE_CELL = 0x400; } } + bitflags! { /// The bits control the data to generate from a CellInput structure. #[derive(Serialize, Deserialize)] @@ -135,19 +138,19 @@ impl OpenTxSigInput { } } /// new OpentxCommand::IndexOutput OpenTxSigInput, command 0x11 - pub fn new_index_output(arg1: u16, arg2: CellMask) -> Result { + pub fn new_index_output(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::IndexOutput, arg1, arg2) } /// new OpentxCommand::OffsetOutput OpenTxSigInput, command 0x12 - pub fn new_offset_output(arg1: u16, arg2: CellMask) -> Result { + pub fn new_offset_output(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::OffsetOutput, arg1, arg2) } /// new OpentxCommand::IndexInput OpenTxSigInput, command 0x13 - pub fn new_index_input(arg1: u16, arg2: CellMask) -> Result { + pub fn new_index_input(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::IndexInput, arg1, arg2) } /// new OpentxCommand::OffsetInput OpenTxSigInput, command 0x14 - pub fn new_offset_input(arg1: u16, arg2: CellMask) -> Result { + pub fn new_offset_input(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::OffsetInput, arg1, arg2) } /// new OpenTxSigInput to handle part or the whole input/output cell @@ -155,9 +158,9 @@ impl OpenTxSigInput { cmd: OpentxCommand, arg1: u16, arg2: CellMask, - ) -> Result { + ) -> Result { if arg1 > ARG1_MASK { - return Err(OpenTxHashError::Arg1OutOfRange(arg1)); + return Err(OpenTxError::Arg1OutOfRange(arg1)); } Ok(OpenTxSigInput { @@ -167,17 +170,14 @@ impl OpenTxSigInput { }) } /// new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x15 - pub fn new_cell_input_index( - arg1: u16, - arg2: InputMask, - ) -> Result { + pub fn new_cell_input_index(arg1: u16, arg2: InputMask) -> Result { Self::new_input_command(OpentxCommand::CellInputIndex, arg1, arg2) } //// new OpentxCommand::CellInputOffset OpenTxSigInput, command 0x16 pub fn new_cell_input_offset( arg1: u16, arg2: InputMask, - ) -> Result { + ) -> Result { Self::new_input_command(OpentxCommand::CellInputOffset, arg1, arg2) } /// new OpenTxSigInput to hash part or the whole cell input structure @@ -185,9 +185,9 @@ impl OpenTxSigInput { cmd: OpentxCommand, arg1: u16, arg2: InputMask, - ) -> Result { + ) -> Result { if arg1 > ARG1_MASK { - return Err(OpenTxHashError::Arg1OutOfRange(arg1)); + return Err(OpenTxError::Arg1OutOfRange(arg1)); } Ok(OpenTxSigInput { @@ -220,7 +220,7 @@ impl OpenTxSigInput { is_input: bool, with_offset: bool, base_index: u32, - ) -> Result<(), OpenTxReaderError> { + ) -> Result<(), OpenTxError> { let mut index = self.arg1 as usize; if with_offset { index += base_index as usize; @@ -298,43 +298,46 @@ impl OpenTxSigInput { fn hash_input( &self, cache: &mut OpentxCache, - ckb_sys_call: &OpenTxReader, + reader: &OpenTxReader, with_offset: bool, base_index: u32, - ) -> Result<(), OpenTxReaderError> { + ) -> Result<(), OpenTxError> { let index = if with_offset { - self.arg1 as usize + base_index as usize + usize::try_from(base_index) + .map_err(|e| anyhow!(e))? + .checked_add(self.arg1 as usize) + .ok_or_else(|| anyhow!("add {} and {} overflow", base_index, self.arg1))? } else { self.arg1 as usize }; let input_mask = InputMask::from_bits_truncate(self.arg2); if input_mask.contains(InputMask::TX_HASH) { - let cell = ckb_sys_call.input(index)?; + let cell = reader.input(index)?; let data = cell.previous_output().tx_hash(); cache.update(data.as_slice()); } if input_mask.contains(InputMask::INDEX) { - let cell = ckb_sys_call.input(index)?; + let cell = reader.input(index)?; let data = cell.previous_output().index(); cache.update(data.as_slice()); } if input_mask.contains(InputMask::SINCE) { - let data = ckb_sys_call.load_input_field_since(index)?; + let data = reader.load_input_field_since(index)?; cache.update(&data); } if input_mask.contains(InputMask::PREVIOUS_OUTPUT) { - let data = ckb_sys_call.load_input_field_out_point(index)?; + let data = reader.load_input_field_out_point(index)?; cache.update(&data); } if input_mask.contains(InputMask::WHOLE) { - let data = ckb_sys_call.load_input(index)?; + let data = reader.load_input(index)?; cache.update(&data); } Ok(()) @@ -355,16 +358,189 @@ impl OpentxWitness { inputs: vec![], } } - pub fn new(input_index: u32, output_index: u32, input: Vec) -> Self { + pub fn new(base_input_index: u32, base_output_index: u32, inputs: Vec) -> Self { OpentxWitness { - base_input_index: input_index, - base_output_index: output_index, - inputs: input, + base_input_index, + base_output_index, + inputs, + } + } + + /// Build new OpentxWitness which will sign all data. + /// + /// It will first generate the TxHash(0x00), GroupInputOutputLen(0x01), + /// then iterate the inputs to generate the relative index OpenTxSigInput with all CellMask on, and InputMask on, + /// then iterate each output to generate the relative index OpenTxSigInput with all CellMask on. + /// + /// If salt provided, it will add low 24 bits with ConcatArg1Arg2(0x20), + /// if high 8 bits not all 0, it will add another ConcatArg1Arg2 OpenTxSigInput. + /// + /// Then it will add an End(0xF0) OpenTxSigInput. + /// + /// The range of inputs/outputs are [base_input_index, end_input_index), [base_output_index, end_output_index). + /// If end_input_index bigger than the transaction inputs length, the inputs length will be used, same thing to end_output_index. + /// + pub fn new_sig_range_relative( + transaction: &TransactionView, + salt: Option, + base_input_index: usize, + end_input_index: usize, + base_output_index: usize, + end_output_index: usize, + ) -> Result { + let mut inputs = vec![ + OpenTxSigInput::new_tx_hash(), + OpenTxSigInput::new_group_input_output_len(), + ]; + + let start_input_idx = base_input_index; + let end_input_idx = end_input_index.min(transaction.inputs().len()); + if start_input_idx >= end_input_idx { + return Err(OpenTxError::BaseIndexOverFlow( + start_input_idx, + end_input_idx, + )); + } + + let start_output_idx = base_output_index; + let out_put_idx = end_output_index.min(transaction.outputs().len()); + if start_output_idx >= out_put_idx { + return Err(OpenTxError::BaseIndexOverFlow( + start_output_idx, + out_put_idx, + )); + } + let base_input_index = u32::try_from(base_input_index).map_err(|e| anyhow!(e))?; + let base_output_index = u32::try_from(base_output_index).map_err(|e| anyhow!(e))?; + for input_idx in start_input_idx..end_input_idx { + let idx = u16::try_from(input_idx - start_input_idx).map_err(|e| anyhow!(e))?; + inputs.push(OpenTxSigInput::new_offset_input( + idx as u16, + CellMask::all(), + )?); + inputs.push(OpenTxSigInput::new_cell_input_offset( + idx as u16, + InputMask::all(), + )?); } + for output_idx in start_output_idx..out_put_idx { + let idx = u16::try_from(output_idx - start_output_idx).map_err(|e| anyhow!(e))?; + inputs.push(OpenTxSigInput::new_offset_output( + idx as u16, + CellMask::all(), + )?); + } + if let Some(mut salt) = salt { + while salt > 0 { + inputs.push(OpenTxSigInput::new_concat_arg1_arg2( + salt as u16 & ARG1_MASK, + (salt >> 12) as u16 & ARG2_MASK, + )); + salt >>= 24; + } + } + + inputs.push(OpenTxSigInput::new_end()); + Ok(OpentxWitness::new( + base_input_index, + base_output_index, + inputs, + )) } + /// Same to `new_sig_range_relative`, except end_input_index and end_output_index are all usize::MAX, + /// which will be changed to the length of inputs/outputs list. + pub fn new_sig_to_end_relative( + transaction: &TransactionView, + salt: Option, + base_input_index: usize, + base_output_index: usize, + ) -> Result { + Self::new_sig_range_relative( + transaction, + salt, + base_input_index, + usize::MAX, + base_output_index, + usize::MAX, + ) + } + + /// Same to `new_sig_to_end_relative`, except base_input_index and base_output_index are all 0 + pub fn new_sig_all_relative( + transaction: &TransactionView, + salt: Option, + ) -> Result { + Self::new_sig_range_relative(transaction, salt, 0, usize::MAX, 0, usize::MAX) + } + + /// Same to new_sig_to_end_relative, but the will use the index commands. + /// The length will be limit to 0x1000, index range:[0, 4095]. + pub fn new_sig_range_absolute( + transaction: &TransactionView, + salt: Option, + base_input_index: usize, + end_input_index: usize, + base_output_index: usize, + end_output_index: usize, + ) -> Result { + let mut inputs = vec![ + OpenTxSigInput::new_tx_hash(), + OpenTxSigInput::new_group_input_output_len(), + ]; + + let start_input_idx = base_input_index; + let end_input_idx = + ((ARG1_MASK + 1) as usize).min(end_input_index.min(transaction.inputs().len())); + if start_input_idx >= end_input_idx { + return Err(OpenTxError::BaseIndexOverFlow( + start_input_idx, + end_input_idx, + )); + } + + let start_output_idx = base_output_index; + let out_put_idx = + ((ARG1_MASK + 1) as usize).min(end_output_index.min(transaction.outputs().len())); + if start_output_idx >= out_put_idx { + return Err(OpenTxError::BaseIndexOverFlow( + start_output_idx, + out_put_idx, + )); + } + let base_input_index = u32::try_from(base_input_index).map_err(|e| anyhow!(e))?; + let base_output_index = u32::try_from(base_output_index).map_err(|e| anyhow!(e))?; + for input_idx in start_input_idx..end_input_idx { + inputs.push(OpenTxSigInput::new_index_input( + input_idx as u16, + CellMask::all(), + )?); + inputs.push(OpenTxSigInput::new_cell_input_index( + input_idx as u16, + InputMask::all(), + )?); + } + for output_idx in start_output_idx..out_put_idx { + inputs.push(OpenTxSigInput::new_index_output( + output_idx as u16, + CellMask::all(), + )?); + } + if let Some(mut salt) = salt { + while salt > 0 { + inputs.push(OpenTxSigInput::new_concat_arg1_arg2( + salt as u16 & ARG1_MASK, + (salt >> 12) as u16 & ARG2_MASK, + )); + salt >>= 24; + } + } - pub fn get_opentx_sig_len(&self) -> usize { - 4 + 4 + 4 * self.inputs.len() + inputs.push(OpenTxSigInput::new_end()); + Ok(OpentxWitness::new( + base_input_index, + base_output_index, + inputs, + )) } pub fn set_base_input_index(&mut self, index: u32) { @@ -376,7 +552,7 @@ impl OpentxWitness { } pub fn to_witness_data(&self) -> Vec { - let capacity = self.get_opentx_sig_len(); + let capacity = 4 + 4 + 4 * self.inputs.len(); let mut witness_data = Vec::with_capacity(capacity); witness_data.extend_from_slice(&self.base_input_index.to_le_bytes()); witness_data.extend_from_slice(&self.base_output_index.to_le_bytes()); @@ -389,7 +565,7 @@ impl OpentxWitness { pub fn generate_message( &self, reader: &OpenTxReader, - ) -> Result<([u8; 32], Bytes), ScriptSignError> { + ) -> Result<([u8; 32], Bytes), OpenTxError> { let (is_input, is_output) = (true, false); let (relative_idx, absolute_idx) = (true, false); diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs index 3e049a97..d6266b1d 100644 --- a/src/unlock/opentx/mod.rs +++ b/src/unlock/opentx/mod.rs @@ -1,4 +1,32 @@ +pub mod assembler; pub mod hasher; pub mod reader; pub use hasher::OpentxWitness; + +use crate::traits::TransactionDependencyError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum OpenTxError { + #[error("Transaction read error, index out of bound.")] + OutOfBound, + #[error("Item not exist")] + ItemMissing, + #[error("Fail to get cell `{0}`")] + CellNotExist(#[from] TransactionDependencyError), + #[error("Unsupport data source")] + UnsupportSource, + #[error("usize(`{0}`) to u64 overflow.")] + LenOverflow(usize), + + #[error("arg1(`{0}`) out of range")] + Arg1OutOfRange(u16), + #[error("arg2(`{0}`) out of range")] + Arg2OutOfRange(u16), + #[error("base index(`{0}) bigger than end index(`{1}`)")] + BaseIndexOverFlow(usize, usize), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} diff --git a/src/unlock/opentx/reader.rs b/src/unlock/opentx/reader.rs index a1857272..7d83e4c0 100644 --- a/src/unlock/opentx/reader.rs +++ b/src/unlock/opentx/reader.rs @@ -8,8 +8,8 @@ use ckb_types::{ prelude::Entity, }; -use crate::traits::{TransactionDependencyError, TransactionDependencyProvider}; -use thiserror::Error; +use super::OpenTxError; +use crate::traits::TransactionDependencyProvider; #[derive(Copy, Clone, PartialEq)] pub enum OpenTxSource { @@ -37,24 +37,6 @@ pub enum OpenTxInputField { Since, } -#[derive(Error, Debug)] -pub enum OpenTxReaderError { - #[error("Transaction read error")] - OutOfBound, - #[error("ItemMissing")] - ItemMissing, - #[error("LengthNotEnough")] - LengthNotEnough, - #[error("InvalidData")] - InvalidData, - #[error("Fail to get cell `{0}`")] - CellNotExist(#[from] TransactionDependencyError), - #[error("Unsupport data source")] - UnsupportSource, - #[error("usize(`{0}`) to u64 overflow.")] - LenOverflow(usize), -} - pub struct OpenTxReader { pub transaction: TransactionView, pub provider: Box, @@ -71,7 +53,7 @@ impl OpenTxReader { transaction: &TransactionView, provider: Box, script_hash: Byte32, - ) -> Result { + ) -> Result { let mut group_input_index = Vec::new(); // all lock for index in 0..transaction.inputs().len() { @@ -100,30 +82,30 @@ impl OpenTxReader { } /// get the group input length. - pub fn group_input_len(&self) -> Result { + pub fn group_input_len(&self) -> Result { let len = self.group_input_index.len(); - let len = u64::try_from(len).map_err(|_e| OpenTxReaderError::LenOverflow(len))?; + let len = u64::try_from(len).map_err(|_e| OpenTxError::LenOverflow(len))?; Ok(len) } /// get the group output length - pub fn group_output_len(&self) -> Result { + pub fn group_output_len(&self) -> Result { let len = self.group_output_index.len(); - let len = u64::try_from(len).map_err(|_e| OpenTxReaderError::LenOverflow(len))?; + let len = u64::try_from(len).map_err(|_e| OpenTxError::LenOverflow(len))?; Ok(len) } /// Get input at absolute index - pub fn input(&self, index: usize) -> Result { + pub fn input(&self, index: usize) -> Result { self.transaction .inputs() .get(index) - .ok_or(OpenTxReaderError::OutOfBound) + .ok_or(OpenTxError::OutOfBound) } /// Get previous output of input /// # Arguments /// * `index` absolute index of inputs. - fn input_previous_output(&self, index: usize) -> Result { + fn input_previous_output(&self, index: usize) -> Result { let cell = self.input(index)?; Ok(cell.previous_output()) } @@ -131,7 +113,7 @@ impl OpenTxReader { /// Get CellOutput of input's cell /// # Arguments /// * `index` absolute index of inputs. - fn input_cell(&self, index: usize) -> Result { + fn input_cell(&self, index: usize) -> Result { let previous_output = self.input_previous_output(index)?; let cell_output = self.provider.get_cell(&previous_output).unwrap(); Ok(cell_output) @@ -140,9 +122,9 @@ impl OpenTxReader { /// Get CellOutput of input's cell /// # Arguments /// * `index` absolute index of inputs. - fn group_input_cell(&self, index: usize) -> Result { + fn group_input_cell(&self, index: usize) -> Result { if self.group_input_index.len() <= index { - return Result::Err(OpenTxReaderError::OutOfBound); + return Result::Err(OpenTxError::OutOfBound); } let index = self.group_input_index[index]; self.input_cell(index) @@ -150,7 +132,7 @@ impl OpenTxReader { /// Get cell data of input's cell /// # Arguments /// * `index` absolute index of inputs. - fn input_cell_data(&self, index: usize) -> Result { + fn input_cell_data(&self, index: usize) -> Result { let previous_output = self.input_previous_output(index)?; let cell_data = self.provider.get_cell_data(&previous_output)?; Ok(cell_data) @@ -158,17 +140,17 @@ impl OpenTxReader { /// Get cell data of input's cell /// # Arguments /// * `index` absolute index of output. - fn output_cell(&self, index: usize) -> Result { + fn output_cell(&self, index: usize) -> Result { self.transaction .output(index) - .ok_or(OpenTxReaderError::OutOfBound) + .ok_or(OpenTxError::OutOfBound) } /// Get cell data of input's cell /// # Arguments /// * `index` absolute index of output group. - fn group_output_cell(&self, index: usize) -> Result { + fn group_output_cell(&self, index: usize) -> Result { if self.group_output_index.len() <= index { - return Result::Err(OpenTxReaderError::OutOfBound); + return Result::Err(OpenTxError::OutOfBound); } let index = self.group_output_index[index]; self.output_cell(index) @@ -176,32 +158,32 @@ impl OpenTxReader { /// Get cell raw data of output's cell /// # Arguments /// * `index` absolute index of output. - fn output_cell_data(&self, index: usize) -> Result { + fn output_cell_data(&self, index: usize) -> Result { Ok(self .transaction .outputs_data() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)? + .ok_or(OpenTxError::OutOfBound)? .raw_data()) } /// Get CellDep of cell depends /// # Arguments /// * `index` absolute index of cell deps. - fn cell_dep(&self, index: usize) -> Result { + fn cell_dep(&self, index: usize) -> Result { self.transaction .cell_deps() .get(index) - .ok_or(OpenTxReaderError::OutOfBound) + .ok_or(OpenTxError::OutOfBound) } /// Get CellOutput of cell depend /// # Arguments /// * `index` absolute index of cell deps. - fn cell_dep_cell(&self, index: usize) -> Result { + fn cell_dep_cell(&self, index: usize) -> Result { let outpoint = self.cell_dep(index)?; let cell = self.provider.get_cell(&outpoint.out_point())?; Ok(cell) } - fn cell_dep_cell_data(&self, index: usize) -> Result { + fn cell_dep_cell_data(&self, index: usize) -> Result { let outpoint = self.cell_dep(index)?; let cell = self.provider.get_cell_data(&outpoint.out_point())?; @@ -220,16 +202,12 @@ impl OpenTxReader { self.script_hash.clone() } - pub fn load_cell( - &self, - index: usize, - source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + pub fn load_cell(&self, index: usize, source: OpenTxSource) -> Result, OpenTxError> { let cell = match source { OpenTxSource::Input => self.input_cell(index), OpenTxSource::Outpout => self.output_cell(index), OpenTxSource::CellDep => self.cell_dep_cell(index), - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), }; Ok(cell?.as_slice().to_vec()) } @@ -238,17 +216,17 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { let data = match source { OpenTxSource::Input => self.input_cell_data(index)?.to_vec(), OpenTxSource::Outpout => self.output_cell_data(index)?.to_vec(), OpenTxSource::CellDep => self.cell_dep_cell_data(index)?.to_vec(), - _ => return Err(OpenTxReaderError::UnsupportSource), + _ => return Err(OpenTxError::UnsupportSource), }; Ok(data.to_vec()) } - pub fn load_input(&self, index: usize) -> Result, OpenTxReaderError> { + pub fn load_input(&self, index: usize) -> Result, OpenTxError> { let input = self.input(index)?; Result::Ok(input.as_slice().to_vec()) } @@ -257,7 +235,7 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { let cell = match source { OpenTxSource::Input => self.input_cell(index)?, OpenTxSource::Outpout => self.output_cell(index)?, @@ -272,7 +250,7 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell_data(index)?; @@ -289,7 +267,7 @@ impl OpenTxReader { .transaction .outputs_data() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; // TODO: why split here? let data = output.as_slice().split_at(4).1.to_vec(); if data.is_empty() { @@ -301,7 +279,7 @@ impl OpenTxReader { OpenTxSource::CellDep => { let outpoint = self.transaction.cell_deps().get(index); if outpoint.is_none() { - return Result::Err(OpenTxReaderError::OutOfBound); + return Result::Err(OpenTxError::OutOfBound); } let data = self .provider @@ -312,15 +290,11 @@ impl OpenTxReader { blake2b_256(&data).to_vec() }) } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } - fn load_field_lock( - &self, - index: usize, - source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + fn load_field_lock(&self, index: usize, source: OpenTxSource) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell(index)?; @@ -330,7 +304,7 @@ impl OpenTxReader { let output = self .transaction .output(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; Result::Ok(output.lock().as_bytes().to_vec()) } OpenTxSource::CellDep => { @@ -338,11 +312,11 @@ impl OpenTxReader { .transaction .cell_deps() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; let cell = self.provider.get_cell(&outpoint.out_point())?; Result::Ok(cell.lock().as_bytes().to_vec()) } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } @@ -350,7 +324,7 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell(index)?; @@ -360,7 +334,7 @@ impl OpenTxReader { let output = self .transaction .output(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; Result::Ok(output.calc_lock_hash().as_bytes().to_vec()) } @@ -369,26 +343,22 @@ impl OpenTxReader { .transaction .cell_deps() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; let cell = self.provider.get_cell(&outpoint.out_point())?; Result::Ok(cell.calc_lock_hash().as_bytes().to_vec()) } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } - fn load_field_type( - &self, - index: usize, - source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + fn load_field_type(&self, index: usize, source: OpenTxSource) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell(index)?; let d = input.type_(); if d.is_none() { - Result::Err(OpenTxReaderError::ItemMissing) + Result::Err(OpenTxError::ItemMissing) } else { Result::Ok(d.as_bytes().to_vec()) } @@ -397,11 +367,11 @@ impl OpenTxReader { let output = self .transaction .output(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; let d = output.type_(); if d.is_none() { - Result::Err(OpenTxReaderError::ItemMissing) + Result::Err(OpenTxError::ItemMissing) } else { Result::Ok(d.as_bytes().to_vec()) } @@ -411,17 +381,17 @@ impl OpenTxReader { .transaction .cell_deps() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; let cell = self.provider.get_cell(&outpoint.out_point())?; let d = cell.type_(); if d.is_none() { - Result::Err(OpenTxReaderError::ItemMissing) + Result::Err(OpenTxError::ItemMissing) } else { Result::Ok(d.as_bytes().to_vec()) } } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } @@ -429,13 +399,13 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell(index)?; let d = input.type_(); if d.is_none() { - Result::Err(OpenTxReaderError::ItemMissing) + Result::Err(OpenTxError::ItemMissing) } else { let d = Script::from_slice(d.as_slice()).unwrap(); Result::Ok(d.calc_script_hash().as_slice().to_vec()) @@ -445,29 +415,23 @@ impl OpenTxReader { let output = self .transaction .output(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; - let d = output - .type_() - .to_opt() - .ok_or(OpenTxReaderError::ItemMissing)?; + let d = output.type_().to_opt().ok_or(OpenTxError::ItemMissing)?; Result::Ok(d.calc_script_hash().as_slice().to_vec()) } OpenTxSource::CellDep => { let outpoint = self.transaction.cell_deps().get(index); if outpoint.is_none() { - return Result::Err(OpenTxReaderError::OutOfBound); + return Result::Err(OpenTxError::OutOfBound); } let cell = self.provider.get_cell(&outpoint.unwrap().out_point())?; - let d = cell - .type_() - .to_opt() - .ok_or(OpenTxReaderError::ItemMissing)?; + let d = cell.type_().to_opt().ok_or(OpenTxError::ItemMissing)?; Result::Ok(d.calc_script_hash().as_slice().to_vec()) } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } @@ -475,7 +439,7 @@ impl OpenTxReader { &self, index: usize, source: OpenTxSource, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { match source { OpenTxSource::Input => { let input = self.input_cell(index)?; @@ -495,7 +459,7 @@ impl OpenTxReader { .transaction .outputs_data() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; Result::Ok( output @@ -511,7 +475,7 @@ impl OpenTxReader { .transaction .cell_deps() .get(index) - .ok_or(OpenTxReaderError::OutOfBound)?; + .ok_or(OpenTxError::OutOfBound)?; let cell_output = self.provider.get_cell(&cell.out_point())?; let cell_data = self.provider.get_cell_data(&cell.out_point())?; Result::Ok( @@ -523,7 +487,7 @@ impl OpenTxReader { .to_vec(), ) } - _ => Err(OpenTxReaderError::UnsupportSource), + _ => Err(OpenTxError::UnsupportSource), } } @@ -532,7 +496,7 @@ impl OpenTxReader { index: usize, source: OpenTxSource, field: OpenTxCellField, - ) -> Result, OpenTxReaderError> { + ) -> Result, OpenTxError> { match field { OpenTxCellField::Capacity => self.load_field_capacity(index, source), OpenTxCellField::DataHash => self.load_field_data_hash(index, source), @@ -544,15 +508,15 @@ impl OpenTxReader { } } - pub fn load_input_field_out_point(&self, index: usize) -> Result, OpenTxReaderError> { + pub fn load_input_field_out_point(&self, index: usize) -> Result, OpenTxError> { Ok(self.input(index)?.previous_output().as_slice().to_vec()) } - pub fn load_input_field_since(&self, index: usize) -> Result, OpenTxReaderError> { + pub fn load_input_field_since(&self, index: usize) -> Result, OpenTxError> { Ok(self.input(index)?.since().as_slice().to_vec()) } - pub fn get_cell(&self, index: usize, is_input: bool) -> Result { + pub fn get_cell(&self, index: usize, is_input: bool) -> Result { let cell = if is_input { self.input_cell(index)? } else { diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 22c8c83e..a6bd05b1 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -22,7 +22,7 @@ use crate::{ use super::{ omni_lock::{ConfigError, Identity}, - opentx::reader::OpenTxReaderError, + opentx::OpenTxError, IdentityFlag, OmniLockConfig, }; @@ -50,7 +50,7 @@ pub enum ScriptSignError { InvalidConfig(#[from] ConfigError), #[error("open transaction read error: `{0}`")] - OpenTxError(#[from] OpenTxReaderError), + OpenTxError(#[from] OpenTxError), #[error(transparent)] Other(#[from] anyhow::Error), } From d33a21fa3a3b43192e8abd8ca1299c557f52e2fd Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 13 Oct 2022 10:49:08 +0800 Subject: [PATCH 03/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20example=20to?= =?UTF-8?q?=20test=20opentx=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 408 +++++++++++++++++++++++++++++++ src/unlock/omni_lock.rs | 5 + src/unlock/opentx/hasher.rs | 2 +- src/unlock/opentx/reader.rs | 67 +---- 4 files changed, 424 insertions(+), 58 deletions(-) create mode 100644 examples/transfer_from_opentx.rs diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs new file mode 100644 index 00000000..6410bb3d --- /dev/null +++ b/examples/transfer_from_opentx.rs @@ -0,0 +1,408 @@ +use ckb_hash::blake2b_256; +use ckb_jsonrpc_types as json_types; +use ckb_sdk::{ + rpc::CkbRpcClient, + traits::{ + DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, + DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, + }, + tx_builder::{ + balance_tx_capacity, fill_placeholder_witnesses, transfer::CapacityTransferBuilder, + unlock_tx, CapacityBalancer, TxBuilder, + }, + types::NetworkType, + unlock::{OmniLockConfig, OmniLockScriptSigner, opentx::OpentxWitness}, + unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, + util::blake160, + Address, HumanCapacity, ScriptGroup, ScriptId, SECP256K1, +}; +use ckb_types::{ + bytes::Bytes, + core::{BlockView, ScriptHashType, TransactionView}, + packed::{Byte32, CellDep, CellOutput, OutPoint, Script, Transaction, WitnessArgs}, + prelude::*, + H160, H256, +}; +use clap::{Args, Parser, Subcommand}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; +use std::{collections::HashMap, error::Error as StdErr}; + +/* +# examples for the developer local node +########################### sighash omnilock example ################################# +# 1. build a omnilock address + ./target/debug/examples/transfer_from_opentx build --receiver ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 +{ + "lock-arg": "0x00b398368a8ed39448f95479c1178ff3fc5e31631810", + "lock-hash": "0x3f54ccaf46b3472b55eaa2e2c0a5cae87575b3de90a81fe60206dd5c0951ffa8", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqv8f7ak", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7" +} +# 2. transfer capacity to the address +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 99 --skip-check-to-address + # 0x937deeb989bbd7f4bd0273bf2049d7614615dd58a32090b0093f23a692715871 +# 3. generate the transaction +./target/debug/examples/transfer_from_opentx gen --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 \ + --tx-file tx.json +# 4. sign the transaction +./target/debug/examples/transfer_from_opentx sign --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --omnilock-tx-hash 34e39e16a285d951b587e88f74286cbdb09c27a5c7e86aa1b1c92058a3cbcc52 --omnilock-index 0 \ + --tx-file tx.json +# 5. send transaction +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +*/ +const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; +const OPENTX_TX_IDX: &str = "0"; + +#[derive(Args)] +struct BuildOmniLockAddrArgs { + /// The receiver address + #[clap(long, value_name = "ADDRESS")] + receiver: Address, + + /// omnilock script deploy transaction hash + #[clap( + long, + value_name = "H256", + default_value = OPENTX_TX_HASH + )] + omnilock_tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER", default_value = OPENTX_TX_IDX)] + omnilock_index: usize, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, +} +#[derive(Args)] +struct GenOpenTxArgs { + /// The sender private key (hex string) + #[clap(long, value_name = "KEY")] + sender_key: H256, + /// The receiver address + #[clap(long, value_name = "ADDRESS")] + receiver: Address, + + /// omnilock script deploy transaction hash + #[clap(long, value_name = "H256", default_value = OPENTX_TX_HASH)] + omnilock_tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER", default_value = OPENTX_TX_IDX)] + omnilock_index: usize, + + /// The capacity to transfer (unit: CKB, example: 102.43) + #[clap(long, value_name = "CKB")] + capacity: HumanCapacity, + + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, + + /// CKB indexer rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8116")] + ckb_indexer: String, +} + +#[derive(Args)] +struct SignTxArgs { + /// The sender private key (hex string) + #[clap(long, value_name = "KEY")] + sender_key: H256, + + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, + + /// omnilock script deploy transaction hash + #[clap(long, value_name = "H256", default_value = OPENTX_TX_HASH)] + omnilock_tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER", default_value = OPENTX_TX_IDX)] + omnilock_index: usize, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, +} +#[derive(Subcommand)] +enum Commands { + /// build omni lock address + Build(BuildOmniLockAddrArgs), + /// Generate the transaction + Gen(GenOpenTxArgs), + /// Sign the transaction + Sign(SignTxArgs), + /// Send the transaction + Send { + /// The transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, + }, +} +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Serialize, Deserialize)] +struct TxInfo { + tx: json_types::TransactionView, + omnilock_config: OmniLockConfig, +} + +struct OmniLockInfo { + type_hash: H256, + script_id: ScriptId, + cell_dep: CellDep, +} + +fn main() -> Result<(), Box> { + // Parse arguments + let cli = Cli::parse(); + match cli.command { + Commands::Build(build_args) => build_omnilock_addr(&build_args)?, + Commands::Gen(gen_args) => { + gen_omnilock_tx(&gen_args)?; + } + Commands::Sign(args) => { + let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; + let tx = Transaction::from(tx_info.tx.inner).into_view(); + let key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err))?; + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); + let hash160 = &blake2b_256(&pubkey.serialize()[..])[0..20]; + if tx_info.omnilock_config.id().auth_content().as_bytes() != hash160 { + return Err(format!("key {:#x} is not in omnilock config", args.sender_key).into()); + } + let (tx, _) = sign_tx(&args, tx, &tx_info.omnilock_config, key)?; + let witness_args = + WitnessArgs::from_slice(tx.witnesses().get(0).unwrap().raw_data().as_ref())?; + let lock_field = witness_args.lock().to_opt().unwrap().raw_data(); + if lock_field != tx_info.omnilock_config.zero_lock(OmniUnlockMode::Normal)? { + println!("> transaction ready to send!"); + } else { + println!("failed to sign tx"); + } + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config: tx_info.omnilock_config, + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + } + Commands::Send { tx_file, ckb_rpc } => { + // Send transaction + let tx_info: TxInfo = serde_json::from_slice(&fs::read(&tx_file)?)?; + println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + let outputs_validator = Some(json_types::OutputsValidator::Passthrough); + let _tx_hash = CkbRpcClient::new(ckb_rpc.as_str()) + .send_transaction(tx_info.tx.inner, outputs_validator) + .expect("send transaction"); + println!(">>> tx sent! <<<"); + } + } + + Ok(()) +} + +fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box> { + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let cell = + build_omnilock_cell_dep(&mut ckb_client, &args.omnilock_tx_hash, args.omnilock_index)?; + let arg = H160::from_slice(&args.receiver.payload().args()).unwrap(); + let mut config = OmniLockConfig::new_pubkey_hash(arg); + config.set_opentx_mode(); + let address_payload = { + let args = config.build_args(); + ckb_sdk::AddressPayload::new_full(ScriptHashType::Type, cell.type_hash.pack(), args) + }; + let lock_script = Script::from(&address_payload); + let resp = serde_json::json!({ + "mainnet": Address::new(NetworkType::Mainnet, address_payload.clone(), true).to_string(), + "testnet": Address::new(NetworkType::Testnet, address_payload.clone(), true).to_string(), + "lock-arg": format!("0x{}", hex_string(address_payload.args().as_ref())), + "lock-hash": format!("{:#x}", lock_script.calc_script_hash()) + }); + println!("{}", serde_json::to_string_pretty(&resp)?); + Ok(()) +} + +fn gen_omnilock_tx(args: &GenOpenTxArgs) -> Result<(), Box> { + let (tx, omnilock_config) = build_transfer_tx(args)?; + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config, + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + Ok(()) +} + +fn build_transfer_tx( + args: &GenOpenTxArgs, +) -> Result<(TransactionView, OmniLockConfig), Box> { + let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err))?; + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let cell = + build_omnilock_cell_dep(&mut ckb_client, &args.omnilock_tx_hash, args.omnilock_index)?; + + let pubkey_hash = blake160(&pubkey.serialize()); + let mut omnilock_config = OmniLockConfig::new_pubkey_hash(pubkey_hash); + omnilock_config.set_opentx_mode(); + // Build CapacityBalancer + let sender = Script::new_builder() + .code_hash(cell.type_hash.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(omnilock_config.build_args().pack()) + .build(); + let placeholder_witness = omnilock_config.placeholder_witness(OmniUnlockMode::Normal)?; + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, 0); + + // Build: + // * CellDepResolver + // * HeaderDepResolver + // * CellCollector + // * TransactionDependencyProvider + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let genesis_block = ckb_client.get_block_by_number(0.into())?.unwrap(); + let genesis_block = BlockView::from(genesis_block); + let mut cell_dep_resolver = DefaultCellDepResolver::from_genesis(&genesis_block)?; + cell_dep_resolver.insert(cell.script_id, cell.cell_dep, "Omni Lock".to_string()); + let header_dep_resolver = DefaultHeaderDepResolver::new(args.ckb_rpc.as_str()); + let mut cell_collector = + DefaultCellCollector::new(args.ckb_indexer.as_str(), args.ckb_rpc.as_str()); + let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); + + // Build base transaction + let unlockers = build_omnilock_unlockers(Vec::new(), omnilock_config.clone(), cell.type_hash); + let output = CellOutput::new_builder() + .lock(sender.clone()) + .capacity(args.capacity.0.pack()) + .build(); + let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); + + let base_tx = builder.build_base( + &mut cell_collector, + &cell_dep_resolver, + &header_dep_resolver, + &tx_dep_provider, + )?; + + let secp256k1_data_dep = { + // pub const SECP256K1_DATA_OUTPUT_LOC: (usize, usize) = (0, 3); + let tx_hash = genesis_block.transactions()[0].hash(); + let out_point = OutPoint::new(tx_hash, 3u32); + CellDep::new_builder().out_point(out_point).build() + }; + + let base_tx = base_tx + .as_advanced_builder() + .cell_dep(secp256k1_data_dep) + .build(); + let (tx, _) = + fill_placeholder_witnesses(base_tx, &tx_dep_provider, &unlockers)?; + + let tx = balance_tx_capacity( + &tx, + &balancer, + &mut cell_collector, + &tx_dep_provider, + &cell_dep_resolver, + &header_dep_resolver, + )?; + + let outputs: Vec = tx.outputs().into_iter().skip(1).collect(); + let outputs_data: Vec = + tx.outputs_data().into_iter().skip(1).collect(); + + let tx = tx + .as_advanced_builder() + .set_outputs(outputs) + .set_outputs_data(outputs_data) + .build(); + + let wit = OpentxWitness::new_sig_all_relative(&tx, Some(0xdeadbeef)).unwrap(); + omnilock_config.set_opentx_input(wit); + Ok((tx, omnilock_config)) +} + +fn build_omnilock_cell_dep( + ckb_client: &mut CkbRpcClient, + tx_hash: &H256, + index: usize, +) -> Result> { + let out_point_json = ckb_jsonrpc_types::OutPoint { + tx_hash: tx_hash.clone(), + index: ckb_jsonrpc_types::Uint32::from(index as u32), + }; + let cell_status = ckb_client.get_live_cell(out_point_json, false)?; + let script = Script::from(cell_status.cell.unwrap().output.type_.unwrap()); + + let type_hash = script.calc_script_hash(); + let out_point = OutPoint::new(Byte32::from_slice(tx_hash.as_bytes())?, index as u32); + + let cell_dep = CellDep::new_builder().out_point(out_point).build(); + Ok(OmniLockInfo { + type_hash: H256::from_slice(type_hash.as_slice())?, + script_id: ScriptId::new_type(type_hash.unpack()), + cell_dep, + }) +} + +fn build_omnilock_unlockers( + keys: Vec, + config: OmniLockConfig, + omni_lock_type_hash: H256, +) -> HashMap> { + let signer = SecpCkbRawKeySigner::new_with_secret_keys(keys); + let omnilock_signer = + OmniLockScriptSigner::new(Box::new(signer), config.clone(), OmniUnlockMode::Normal); + let omnilock_unlocker = OmniLockUnlocker::new(omnilock_signer, config); + let omnilock_script_id = ScriptId::new_type(omni_lock_type_hash); + HashMap::from([( + omnilock_script_id, + Box::new(omnilock_unlocker) as Box, + )]) +} + +fn sign_tx( + args: &SignTxArgs, + mut tx: TransactionView, + omnilock_config: &OmniLockConfig, + key: secp256k1::SecretKey, +) -> Result<(TransactionView, Vec), Box> { + // Unlock transaction + let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); + + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let cell = + build_omnilock_cell_dep(&mut ckb_client, &args.omnilock_tx_hash, args.omnilock_index)?; + + let mut _still_locked_groups = None; + let unlockers = build_omnilock_unlockers(vec![key], omnilock_config.clone(), cell.type_hash); + let (new_tx, new_still_locked_groups) = unlock_tx(tx.clone(), &tx_dep_provider, &unlockers)?; + tx = new_tx; + _still_locked_groups = Some(new_still_locked_groups); + Ok((tx, _still_locked_groups.unwrap_or_default())) +} diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 78dd0642..61126698 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -553,6 +553,11 @@ impl OmniLockConfig { self.opentx_input = Some(opentx_input); } + /// Set the opentx mode, without knowing the open transaction input data yet, can be used to generate lock script args to search avaiable live cells. + pub fn set_opentx_mode(&mut self) { + self.omni_lock_flags.set(OmniLockFlags::OPENTX, true); + } + /// Clear the open transaction input data, and clear OmniLockFlags::OPENTX from omni_lock_flags. pub fn clear_opentx_input(&mut self) { self.omni_lock_flags.set(OmniLockFlags::OPENTX, false); diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 012a9664..9ef6c1c9 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -580,7 +580,7 @@ impl OpentxWitness { OpentxCommand::GroupInputOutputLen => { let input_len = reader.group_input_len()?; cache.update(&input_len.to_le_bytes()); - let output_len = reader.group_output_len()?; + let output_len = 0u64; cache.update(&output_len.to_le_bytes()); } OpentxCommand::IndexOutput => { diff --git a/src/unlock/opentx/reader.rs b/src/unlock/opentx/reader.rs index 7d83e4c0..90d4ebca 100644 --- a/src/unlock/opentx/reader.rs +++ b/src/unlock/opentx/reader.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::convert::TryFrom; use ckb_hash::blake2b_256; @@ -9,14 +8,13 @@ use ckb_types::{ }; use super::OpenTxError; -use crate::traits::TransactionDependencyProvider; +use crate::{traits::TransactionDependencyProvider, ScriptGroup}; #[derive(Copy, Clone, PartialEq)] pub enum OpenTxSource { Input, GroupInput, Outpout, - GroupOutpout, CellDep, } @@ -37,47 +35,24 @@ pub enum OpenTxInputField { Since, } -pub struct OpenTxReader { +/// This reader can only read for only one input group, if you have multiple input group, multiple reader will be needed. +pub struct OpenTxReader<'r> { pub transaction: TransactionView, - pub provider: Box, + pub provider: &'r dyn TransactionDependencyProvider, /// map group input index to input index group_input_index: Vec, - /// map group output index to output index - group_output_index: Vec, - /// open tx lock hash - script_hash: Byte32, } -impl OpenTxReader { - pub fn new( +impl<'r> OpenTxReader<'r> { + pub fn new ( transaction: &TransactionView, - provider: Box, - script_hash: Byte32, + provider: &'r dyn TransactionDependencyProvider, + script_group: &ScriptGroup, ) -> Result { - let mut group_input_index = Vec::new(); - // all lock - for index in 0..transaction.inputs().len() { - let lock_hash = provider - .get_cell(&transaction.inputs().get(index).unwrap().previous_output())? - .lock() - .calc_script_hash(); - if lock_hash.cmp(&script_hash) == Ordering::Equal { - group_input_index.push(index); - } - } - let mut group_output_index = Vec::new(); - for index in 0..transaction.outputs().len() { - let lock_hash = transaction.output(index).unwrap().lock().calc_script_hash(); - if lock_hash.cmp(&script_hash) == Ordering::Equal { - group_output_index.push(index); - } - } Ok(OpenTxReader { transaction: transaction.clone(), provider, - group_input_index, - group_output_index, - script_hash, + group_input_index: script_group.input_indices.clone(), }) } @@ -88,13 +63,6 @@ impl OpenTxReader { Ok(len) } - /// get the group output length - pub fn group_output_len(&self) -> Result { - let len = self.group_output_index.len(); - let len = u64::try_from(len).map_err(|_e| OpenTxError::LenOverflow(len))?; - Ok(len) - } - /// Get input at absolute index pub fn input(&self, index: usize) -> Result { self.transaction @@ -115,7 +83,7 @@ impl OpenTxReader { /// * `index` absolute index of inputs. fn input_cell(&self, index: usize) -> Result { let previous_output = self.input_previous_output(index)?; - let cell_output = self.provider.get_cell(&previous_output).unwrap(); + let cell_output = self.provider.get_cell(&previous_output)?; Ok(cell_output) } @@ -145,16 +113,6 @@ impl OpenTxReader { .output(index) .ok_or(OpenTxError::OutOfBound) } - /// Get cell data of input's cell - /// # Arguments - /// * `index` absolute index of output group. - fn group_output_cell(&self, index: usize) -> Result { - if self.group_output_index.len() <= index { - return Result::Err(OpenTxError::OutOfBound); - } - let index = self.group_output_index[index]; - self.output_cell(index) - } /// Get cell raw data of output's cell /// # Arguments /// * `index` absolute index of output. @@ -198,10 +156,6 @@ impl OpenTxReader { self.transaction.data().as_slice().to_vec() } - pub fn load_script_hash(&self) -> Byte32 { - self.script_hash.clone() - } - pub fn load_cell(&self, index: usize, source: OpenTxSource) -> Result, OpenTxError> { let cell = match source { OpenTxSource::Input => self.input_cell(index), @@ -240,7 +194,6 @@ impl OpenTxReader { OpenTxSource::Input => self.input_cell(index)?, OpenTxSource::Outpout => self.output_cell(index)?, OpenTxSource::GroupInput => self.group_input_cell(index)?, - OpenTxSource::GroupOutpout => self.group_output_cell(index)?, OpenTxSource::CellDep => self.cell_dep_cell(index)?, }; Ok(cell.capacity().raw_data().to_vec()) From e3b41386bd69fd8e291e14d16efb8f494b7759fd Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 13 Oct 2022 13:47:58 +0800 Subject: [PATCH 04/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Can=20sign=20Pubke?= =?UTF-8?q?yHash=20opentx=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 7 +++--- src/unlock/opentx/hasher.rs | 37 ++++++++++++++++++++++++-------- src/unlock/opentx/reader.rs | 4 ++-- src/unlock/signer.rs | 30 ++++++++++++++++++++++---- src/unlock/unlocker.rs | 16 +++++++------- 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 6410bb3d..e806a16f 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -11,7 +11,7 @@ use ckb_sdk::{ unlock_tx, CapacityBalancer, TxBuilder, }, types::NetworkType, - unlock::{OmniLockConfig, OmniLockScriptSigner, opentx::OpentxWitness}, + unlock::{opentx::OpentxWitness, OmniLockConfig, OmniLockScriptSigner}, unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, util::blake160, Address, HumanCapacity, ScriptGroup, ScriptId, SECP256K1, @@ -297,7 +297,7 @@ fn build_transfer_tx( // Build base transaction let unlockers = build_omnilock_unlockers(Vec::new(), omnilock_config.clone(), cell.type_hash); let output = CellOutput::new_builder() - .lock(sender.clone()) + .lock(sender) .capacity(args.capacity.0.pack()) .build(); let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); @@ -320,8 +320,7 @@ fn build_transfer_tx( .as_advanced_builder() .cell_dep(secp256k1_data_dep) .build(); - let (tx, _) = - fill_placeholder_witnesses(base_tx, &tx_dep_provider, &unlockers)?; + let (tx, _) = fill_placeholder_witnesses(base_tx, &tx_dep_provider, &unlockers)?; let tx = balance_tx_capacity( &tx, diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 9ef6c1c9..d11868e8 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use bitflags::bitflags; -use bytes::Bytes; +use bytes::{BufMut, Bytes}; use ckb_hash::Blake2b; use ckb_types::{bytes::BytesMut, core::TransactionView, prelude::*}; use serde::{Deserialize, Serialize}; @@ -552,7 +552,7 @@ impl OpentxWitness { } pub fn to_witness_data(&self) -> Vec { - let capacity = 4 + 4 + 4 * self.inputs.len(); + let capacity = self.opentx_sig_data_len(); let mut witness_data = Vec::with_capacity(capacity); witness_data.extend_from_slice(&self.base_input_index.to_le_bytes()); witness_data.extend_from_slice(&self.base_output_index.to_le_bytes()); @@ -562,15 +562,13 @@ impl OpentxWitness { witness_data } - pub fn generate_message( - &self, - reader: &OpenTxReader, - ) -> Result<([u8; 32], Bytes), OpenTxError> { + pub fn generate_message(&self, reader: &OpenTxReader) -> Result<(Bytes, Bytes), OpenTxError> { let (is_input, is_output) = (true, false); let (relative_idx, absolute_idx) = (true, false); let mut cache = OpentxCache::new(); let mut s_data = BytesMut::with_capacity(self.inputs.len() * 4); + let mut has_last = false; for si in &self.inputs { match si.cmd { OpentxCommand::TxHash => { @@ -619,17 +617,38 @@ impl OpentxWitness { cache.update(&data[0..3]); } OpentxCommand::End => { + has_last = true; + s_data.extend_from_slice(&si.compose().to_le_bytes()); break; } } s_data.extend_from_slice(&si.compose().to_le_bytes()); } + // append last end command + if !has_last { + let si = OpenTxSigInput::new_end(); + s_data.extend_from_slice(&si.compose().to_le_bytes()); + } let s_data = s_data.freeze(); cache.update(s_data.to_vec().as_slice()); let msg = cache.finalize(); Ok((msg, s_data)) } + + pub fn opentx_sig_data_len(&self) -> usize { + 4 + 4 + 4 * self.inputs.len() + } + + pub fn build_opentx_sig(&self, sil_data: Bytes, sig_bytes: Bytes) -> Bytes { + let mut data = BytesMut::with_capacity(self.opentx_sig_data_len() + sig_bytes.len()); + data.put_u32_le(self.base_input_index as u32); + data.put_u32_le(self.base_output_index as u32); + + data.put(sil_data); + data.put(sig_bytes); + data.freeze() + } } struct OpentxCache { @@ -647,9 +666,9 @@ impl OpentxCache { self.blake2b.update(data); } - pub fn finalize(self) -> [u8; 32] { - let mut msg = [0u8; 32]; + pub fn finalize(self) -> Bytes { + let mut msg = vec![0u8; 32]; self.blake2b.finalize(&mut msg); - msg + Bytes::from(msg) } } diff --git a/src/unlock/opentx/reader.rs b/src/unlock/opentx/reader.rs index 90d4ebca..c0feb4ed 100644 --- a/src/unlock/opentx/reader.rs +++ b/src/unlock/opentx/reader.rs @@ -36,7 +36,7 @@ pub enum OpenTxInputField { } /// This reader can only read for only one input group, if you have multiple input group, multiple reader will be needed. -pub struct OpenTxReader<'r> { +pub struct OpenTxReader<'r> { pub transaction: TransactionView, pub provider: &'r dyn TransactionDependencyProvider, /// map group input index to input index @@ -44,7 +44,7 @@ pub struct OpenTxReader<'r> { } impl<'r> OpenTxReader<'r> { - pub fn new ( + pub fn new( transaction: &TransactionView, provider: &'r dyn TransactionDependencyProvider, script_group: &ScriptGroup, diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index a6bd05b1..419b80fd 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -13,8 +13,11 @@ use ckb_types::{ use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::types::{AddressPayload, CodeHashIndex, ScriptGroup, Since}; use crate::{constants::MULTISIG_TYPE_HASH, types::omni_lock::OmniLockWitnessLock}; +use crate::{ + traits::TransactionDependencyProvider, + types::{AddressPayload, CodeHashIndex, ScriptGroup, Since}, +}; use crate::{ traits::{Signer, SignerError}, util::convert_keccak256_hash, @@ -22,7 +25,7 @@ use crate::{ use super::{ omni_lock::{ConfigError, Identity}, - opentx::OpenTxError, + opentx::{reader::OpenTxReader, OpenTxError}, IdentityFlag, OmniLockConfig, }; @@ -67,6 +70,7 @@ pub trait ScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result; } @@ -131,6 +135,7 @@ impl ScriptSigner for SecpSighashScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); self.sign_tx_with_owner_id(args.as_ref(), tx, script_group) @@ -276,6 +281,7 @@ impl ScriptSigner for SecpMultisigScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let witness_idx = script_group.input_indices[0]; let mut witnesses: Vec = tx.witnesses().into_iter().collect(); @@ -368,6 +374,7 @@ impl ScriptSigner for AcpScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); let id = &args[0..20]; @@ -417,6 +424,7 @@ impl ScriptSigner for ChequeScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); let id = self.owner_id(args.as_ref()); @@ -745,6 +753,7 @@ impl ScriptSigner for OmniLockScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let id = match self.unlock_mode { OmniUnlockMode::Admin => self @@ -768,7 +777,16 @@ impl ScriptSigner for OmniLockScriptSigner { .build(); let zero_lock = self.config.zero_lock(self.unlock_mode)?; - let message = generate_message(&tx_new, script_group, zero_lock)?; + let (message, open_sig_data) = + if let Some(opentx_wit) = self.config.get_opentx_input() { + let reader = OpenTxReader::new(&tx_new, tx_dep_provider, script_group)?; + opentx_wit.generate_message(&reader)? + } else { + ( + generate_message(&tx_new, script_group, zero_lock)?, + Bytes::new(), + ) + }; let signature = self.signer @@ -782,7 +800,11 @@ impl ScriptSigner for OmniLockScriptSigner { WitnessArgs::from_slice(witness_data.as_ref())? }; - let lock = Self::build_witness_lock(current_witness.lock(), signature)?; + let lock = if let Some(opentx_wit) = self.config.get_opentx_input() { + opentx_wit.build_opentx_sig(open_sig_data, signature) + } else { + Self::build_witness_lock(current_witness.lock(), signature)? + }; current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); witnesses[witness_idx] = current_witness.as_bytes().pack(); diff --git a/src/unlock/unlocker.rs b/src/unlock/unlocker.rs index eccf417d..911c06ec 100644 --- a/src/unlock/unlocker.rs +++ b/src/unlock/unlocker.rs @@ -155,9 +155,9 @@ impl ScriptUnlocker for SecpSighashUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( @@ -192,9 +192,9 @@ impl ScriptUnlocker for SecpMultisigUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( @@ -429,7 +429,7 @@ impl ScriptUnlocker for AcpUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { self.clear_placeholder_witness(tx, script_group) } else { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } } @@ -575,7 +575,7 @@ impl ScriptUnlocker for ChequeUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { self.clear_placeholder_witness(tx, script_group) } else { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } } @@ -704,9 +704,9 @@ impl ScriptUnlocker for OmniLockUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( From f5ee456cf51543f00607dce91b8eadbad1f72f87 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Fri, 14 Oct 2022 16:11:27 +0800 Subject: [PATCH 05/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20command=20to?= =?UTF-8?q?=20add=20input/output=20for=20opentx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 124 +++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index e806a16f..53f15346 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -18,7 +18,7 @@ use ckb_sdk::{ }; use ckb_types::{ bytes::Bytes, - core::{BlockView, ScriptHashType, TransactionView}, + core::{BlockView, Capacity, ScriptHashType, TransactionView}, packed::{Byte32, CellDep, CellOutput, OutPoint, Script, Transaction, WitnessArgs}, prelude::*, H160, H256, @@ -54,7 +54,27 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d ./target/debug/examples/transfer_from_opentx sign --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ --omnilock-tx-hash 34e39e16a285d951b587e88f74286cbdb09c27a5c7e86aa1b1c92058a3cbcc52 --omnilock-index 0 \ --tx-file tx.json -# 5. send transaction + +# now try to use the transaction +# 5. get receive account +ckb-cli account extended-address --lock-arg 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --path "m/44'/309'/0'/1/0" +address: + mainnet: ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgz2rhxauhe4q0f8xedhhmd4cl2r77dwtqfm4sz0 + testnet: ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgz2rhxauhe4q0f8xedhhmd4cl2r77dwtq8f7lgh +address(deprecated): + mainnet: ckb1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukqrdh5ct + testnet: ckt1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukq7gft5h +lock_arg: 0x0250ee6ef2f9a81e939b2dbdf6dae3ea1fbcd72c +# 6. transfer ckb to the new account +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukq7gft5h \ + --capacity 100 --skip-check-to-address + # 0x9e80b30af11c5f142ad706b65d4a291492fcb045a169bca01c1b5d5d7b230e91 +# 7. add input +./target/debug/examples/transfer_from_opentx add-input --tx-hash 9e80b30af11c5f142ad706b65d4a291492fcb045a169bca01c1b5d5d7b230e91 --index 0 --tx-file tx.json +# 8. add output +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 --capacity 100.99 --tx-file tx.json +# 5. sign the transaction ./target/debug/examples/transfer_from_opentx send --tx-file tx.json */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; @@ -138,14 +158,58 @@ struct SignTxArgs { #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] ckb_rpc: String, } + +#[derive(Args)] +struct AddInputArgs { + /// omnilock script deploy transaction hash + #[clap(long, value_name = "H256")] + tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER")] + index: usize, + + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, + + /// omnilock script deploy transaction hash + #[clap(long, value_name = "H256", default_value = OPENTX_TX_HASH)] + omnilock_tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER", default_value = OPENTX_TX_IDX)] + omnilock_index: usize, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, +} + +#[derive(Args)] +struct AddOutputArgs { + /// --to-sighash-address ckt1qyqg7zchpds6lv3v0nr36z2msu2x9a5lkhrq7kvyww --capacity 19999.9999 --tx-file tx.json + #[clap(long, value_name = "ADDRESS")] + to_address: Address, + /// The capacity to transfer (unit: CKB, example: 102.43) + #[clap(long, value_name = "CKB")] + capacity: HumanCapacity, + + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, +} #[derive(Subcommand)] enum Commands { /// build omni lock address Build(BuildOmniLockAddrArgs), /// Generate the transaction - Gen(GenOpenTxArgs), - /// Sign the transaction - Sign(SignTxArgs), + GenOpenTx(GenOpenTxArgs), + /// Sign the open transaction + SignOpenTx(SignTxArgs), + /// Add input + AddInput(AddInputArgs), + AddOutput(AddOutputArgs), /// Send the transaction Send { /// The transaction info file (.json) @@ -182,10 +246,10 @@ fn main() -> Result<(), Box> { let cli = Cli::parse(); match cli.command { Commands::Build(build_args) => build_omnilock_addr(&build_args)?, - Commands::Gen(gen_args) => { + Commands::GenOpenTx(gen_args) => { gen_omnilock_tx(&gen_args)?; } - Commands::Sign(args) => { + Commands::SignOpenTx(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; let tx = Transaction::from(tx_info.tx.inner).into_view(); let key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) @@ -210,6 +274,33 @@ fn main() -> Result<(), Box> { }; fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; } + Commands::AddInput(args) => { + let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; + println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + let tx = Transaction::from(tx_info.tx.inner).into_view(); + let tx = add_live_cell(&args, tx)?; + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config: tx_info.omnilock_config, + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + } + Commands::AddOutput(args) => { + let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; + println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + let tx = Transaction::from(tx_info.tx.inner).into_view(); + let lock_script = Script::from(args.to_address.payload()); + let output = CellOutput::new_builder() + .capacity(Capacity::shannons(args.capacity.0).pack()) + .lock(lock_script) + .build(); + let tx = tx.as_advanced_builder().output(output).build(); + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config: tx_info.omnilock_config, + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + } Commands::Send { tx_file, ckb_rpc } => { // Send transaction let tx_info: TxInfo = serde_json::from_slice(&fs::read(&tx_file)?)?; @@ -369,6 +460,25 @@ fn build_omnilock_cell_dep( }) } +fn add_live_cell( + args: &AddInputArgs, + tx: TransactionView, +) -> Result> { + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let out_point_json = ckb_jsonrpc_types::OutPoint { + tx_hash: args.tx_hash.clone(), + index: ckb_jsonrpc_types::Uint32::from(args.index as u32), + }; + ckb_client.get_live_cell(out_point_json, false)?; + let input_outpoint = OutPoint::new( + Byte32::from_slice(args.tx_hash.as_bytes())?, + args.index as u32, + ); + // since value should be provided in args + let input = ckb_types::packed::CellInput::new(input_outpoint, 0); + Ok(tx.as_advanced_builder().input(input).build()) +} + fn build_omnilock_unlockers( keys: Vec, config: OmniLockConfig, From 5beb84458ec231ec3159b1e4685dfcc8687df397 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 18 Oct 2022 17:13:02 +0800 Subject: [PATCH 06/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20OpenTx=20sign=20Pu?= =?UTF-8?q?bkeyHash=20ready?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 176 ++++++++++++++++++++++++------- src/unlock/opentx/hasher.rs | 6 +- src/unlock/signer.rs | 11 +- 3 files changed, 144 insertions(+), 49 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 53f15346..f8230c17 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -1,6 +1,7 @@ use ckb_hash::blake2b_256; use ckb_jsonrpc_types as json_types; use ckb_sdk::{ + constants::SIGHASH_TYPE_HASH, rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, @@ -11,7 +12,7 @@ use ckb_sdk::{ unlock_tx, CapacityBalancer, TxBuilder, }, types::NetworkType, - unlock::{opentx::OpentxWitness, OmniLockConfig, OmniLockScriptSigner}, + unlock::{opentx::OpentxWitness, OmniLockConfig, OmniLockScriptSigner, SecpSighashUnlocker}, unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, util::blake160, Address, HumanCapacity, ScriptGroup, ScriptId, SECP256K1, @@ -25,9 +26,9 @@ use ckb_types::{ }; use clap::{Args, Parser, Subcommand}; use serde::{Deserialize, Serialize}; -use std::fs; use std::path::PathBuf; use std::{collections::HashMap, error::Error as StdErr}; +use std::{collections::HashSet, fs}; /* # examples for the developer local node @@ -46,35 +47,20 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d --capacity 99 --skip-check-to-address # 0x937deeb989bbd7f4bd0273bf2049d7614615dd58a32090b0093f23a692715871 # 3. generate the transaction -./target/debug/examples/transfer_from_opentx gen --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ +./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 98.0 \ + --capacity 98.0 --open-capacity 1.0\ --tx-file tx.json # 4. sign the transaction -./target/debug/examples/transfer_from_opentx sign --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --omnilock-tx-hash 34e39e16a285d951b587e88f74286cbdb09c27a5c7e86aa1b1c92058a3cbcc52 --omnilock-index 0 \ +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ --tx-file tx.json - -# now try to use the transaction -# 5. get receive account -ckb-cli account extended-address --lock-arg 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --path "m/44'/309'/0'/1/0" -address: - mainnet: ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgz2rhxauhe4q0f8xedhhmd4cl2r77dwtqfm4sz0 - testnet: ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgz2rhxauhe4q0f8xedhhmd4cl2r77dwtq8f7lgh -address(deprecated): - mainnet: ckb1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukqrdh5ct - testnet: ckt1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukq7gft5h -lock_arg: 0x0250ee6ef2f9a81e939b2dbdf6dae3ea1fbcd72c -# 6. transfer ckb to the new account -ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ - --to-address ckt1qyqqy58wdme0n2q7jwdjm00kmt3758au6ukq7gft5h \ - --capacity 100 --skip-check-to-address - # 0x9e80b30af11c5f142ad706b65d4a291492fcb045a169bca01c1b5d5d7b230e91 -# 7. add input -./target/debug/examples/transfer_from_opentx add-input --tx-hash 9e80b30af11c5f142ad706b65d4a291492fcb045a169bca01c1b5d5d7b230e91 --index 0 --tx-file tx.json -# 8. add output -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 --capacity 100.99 --tx-file tx.json -# 5. sign the transaction +# add input, with capacity 98.99999588 +./target/debug/examples/transfer_from_opentx add-input --tx-hash df85d2aaa44d50b1db286bdb2fbd8682cad12d6858b269d2531403ba5e63a2eb --index 0 --tx-file tx.json +# add output, capacity is 98.99999588(original) + 1(open capacity) - 0.001(fee) +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 99.99899588 --tx-file tx.json +# sighash sign the new input +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +# send the tx ./target/debug/examples/transfer_from_opentx send --tx-file tx.json */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; @@ -122,7 +108,11 @@ struct GenOpenTxArgs { /// The capacity to transfer (unit: CKB, example: 102.43) #[clap(long, value_name = "CKB")] capacity: HumanCapacity, - + /// The open transaction capacity not decided to whom (unit: CKB, example: 102.43) + #[clap(long, value_name = "CKB")] + open_capacity: HumanCapacity, + #[clap(long, value_name = "NUMBER", default_value = "0")] + fee_rate: u64, /// The output transaction info file (.json) #[clap(long, value_name = "PATH")] tx_file: PathBuf, @@ -207,8 +197,11 @@ enum Commands { GenOpenTx(GenOpenTxArgs), /// Sign the open transaction SignOpenTx(SignTxArgs), + /// sign sighash input + SighashSignTx(SignTxArgs), /// Add input AddInput(AddInputArgs), + /// Add output AddOutput(AddOutputArgs), /// Send the transaction Send { @@ -247,7 +240,7 @@ fn main() -> Result<(), Box> { match cli.command { Commands::Build(build_args) => build_omnilock_addr(&build_args)?, Commands::GenOpenTx(gen_args) => { - gen_omnilock_tx(&gen_args)?; + gen_open_tx(&gen_args)?; } Commands::SignOpenTx(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; @@ -263,6 +256,24 @@ fn main() -> Result<(), Box> { let witness_args = WitnessArgs::from_slice(tx.witnesses().get(0).unwrap().raw_data().as_ref())?; let lock_field = witness_args.lock().to_opt().unwrap().raw_data(); + if lock_field != tx_info.omnilock_config.zero_lock(OmniUnlockMode::Normal)? { + println!("> transaction has been signed!"); + } else { + println!("failed to sign tx"); + } + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config: tx_info.omnilock_config, + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + } + Commands::SighashSignTx(args) => { + let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; + let tx = Transaction::from(tx_info.tx.inner).into_view(); + let (tx, _) = sighash_sign(&args, tx)?; + let witness_args = + WitnessArgs::from_slice(tx.witnesses().get(0).unwrap().raw_data().as_ref())?; + let lock_field = witness_args.lock().to_opt().unwrap().raw_data(); if lock_field != tx_info.omnilock_config.zero_lock(OmniUnlockMode::Normal)? { println!("> transaction ready to send!"); } else { @@ -276,7 +287,7 @@ fn main() -> Result<(), Box> { } Commands::AddInput(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; - println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + // println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); let tx = Transaction::from(tx_info.tx.inner).into_view(); let tx = add_live_cell(&args, tx)?; let tx_info = TxInfo { @@ -287,14 +298,18 @@ fn main() -> Result<(), Box> { } Commands::AddOutput(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; - println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + // println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); let tx = Transaction::from(tx_info.tx.inner).into_view(); let lock_script = Script::from(args.to_address.payload()); let output = CellOutput::new_builder() .capacity(Capacity::shannons(args.capacity.0).pack()) .lock(lock_script) .build(); - let tx = tx.as_advanced_builder().output(output).build(); + let tx = tx + .as_advanced_builder() + .output(output) + .output_data(Bytes::default().pack()) + .build(); let tx_info = TxInfo { tx: json_types::TransactionView::from(tx), omnilock_config: tx_info.omnilock_config, @@ -338,8 +353,8 @@ fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box Result<(), Box> { - let (tx, omnilock_config) = build_transfer_tx(args)?; +fn gen_open_tx(args: &GenOpenTxArgs) -> Result<(), Box> { + let (tx, omnilock_config) = build_open_tx(args)?; let tx_info = TxInfo { tx: json_types::TransactionView::from(tx), omnilock_config, @@ -348,7 +363,24 @@ fn gen_omnilock_tx(args: &GenOpenTxArgs) -> Result<(), Box> { Ok(()) } -fn build_transfer_tx( +fn get_opentx_placeholder_hash() -> H256 { + let mut ret = H256::default(); + let opentx = "opentx"; + let offset = ret.0.len() - opentx.len(); + ret.0[offset..].copy_from_slice(opentx.as_bytes()); + ret +} + +fn get_opentx_tmp_script() -> Script { + let tmp_locker = get_opentx_placeholder_hash(); + Script::new_builder() + .code_hash(tmp_locker.pack()) + .hash_type(ScriptHashType::Type.into()) + .args([0u8; 65].pack()) + .build() +} + +fn build_open_tx( args: &GenOpenTxArgs, ) -> Result<(TransactionView, OmniLockConfig), Box> { let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) @@ -368,7 +400,7 @@ fn build_transfer_tx( .args(omnilock_config.build_args().pack()) .build(); let placeholder_witness = omnilock_config.placeholder_witness(OmniUnlockMode::Normal)?; - let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, 0); + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, args.fee_rate); // Build: // * CellDepResolver @@ -391,7 +423,16 @@ fn build_transfer_tx( .lock(sender) .capacity(args.capacity.0.pack()) .build(); - let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); + + let tmp_locker = get_opentx_tmp_script(); + let output_tmp = CellOutput::new_builder() + .lock(tmp_locker.clone()) + .capacity(args.open_capacity.0.pack()) + .build(); + let builder = CapacityTransferBuilder::new(vec![ + (output, Bytes::default()), + (output_tmp, Bytes::default()), + ]); let base_tx = builder.build_base( &mut cell_collector, @@ -422,10 +463,27 @@ fn build_transfer_tx( &header_dep_resolver, )?; - let outputs: Vec = tx.outputs().into_iter().skip(1).collect(); - let outputs_data: Vec = - tx.outputs_data().into_iter().skip(1).collect(); - + let tmp_idxes: HashSet = tx + .outputs() + .into_iter() + .enumerate() + .filter(|(_, out)| out.lock() == tmp_locker) + .map(|(idx, _)| idx) + .collect(); + let outputs: Vec = tx + .outputs() + .into_iter() + .enumerate() + .filter(|(idx, _)| !tmp_idxes.contains(idx)) + .map(|(_, out)| out) + .collect(); + let outputs_data: Vec = tx + .outputs_data() + .into_iter() + .enumerate() + .filter(|(idx, _)| !tmp_idxes.contains(idx)) + .map(|(_, out)| out) + .collect(); let tx = tx .as_advanced_builder() .set_outputs(outputs) @@ -515,3 +573,39 @@ fn sign_tx( _still_locked_groups = Some(new_still_locked_groups); Ok((tx, _still_locked_groups.unwrap_or_default())) } + +fn sighash_sign( + args: &SignTxArgs, + tx: TransactionView, +) -> Result<(TransactionView, Vec), Box> { + let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err))?; + // Build ScriptUnlocker + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![sender_key]); + let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone()); + let mut unlockers = HashMap::default(); + unlockers.insert( + sighash_script_id, + Box::new(sighash_unlocker) as Box, + ); + + // Build the transaction + // let output = CellOutput::new_builder() + // .lock(Script::from(&args.receiver)) + // .capacity(args.capacity.0.pack()) + // .build(); + // let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); + // let (tx, still_locked_groups) = builder.build_unlocked( + // &mut cell_collector, + // &cell_dep_resolver, + // &header_dep_resolver, + // &tx_dep_provider, + // &balancer, + // &unlockers, + // )?; + + let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); + let (new_tx, new_still_locked_groups) = unlock_tx(tx.clone(), &tx_dep_provider, &unlockers)?; + Ok((new_tx, new_still_locked_groups)) +} diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index d11868e8..822f1246 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -389,7 +389,9 @@ impl OpentxWitness { end_output_index: usize, ) -> Result { let mut inputs = vec![ - OpenTxSigInput::new_tx_hash(), + // If the opentx will add other input/output, when sign, it can not get the correct transaction hash, + // so OpentxCommand::TxHash should not be included in an opentx will add more input(s)/output(s) + // OpenTxSigInput::new_tx_hash(), OpenTxSigInput::new_group_input_output_len(), ]; @@ -612,7 +614,7 @@ impl OpentxWitness { si.hash_input(&mut cache, reader, is_input, self.base_input_index)?; } OpentxCommand::ConcatArg1Arg2 => { - let data = (si.arg1 & 0xfff) as u32 | ((si.arg2 & 0xfff) << 12) as u32; + let data = (si.arg1 as u32 & 0xfff) | ((si.arg2 as u32 & 0xfff) << 12); let data = data.to_le_bytes(); cache.update(&data[0..3]); } diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 419b80fd..44ba2b1b 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -788,7 +788,7 @@ impl ScriptSigner for OmniLockScriptSigner { ) }; - let signature = + let mut signature = self.signer .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)?; @@ -800,11 +800,10 @@ impl ScriptSigner for OmniLockScriptSigner { WitnessArgs::from_slice(witness_data.as_ref())? }; - let lock = if let Some(opentx_wit) = self.config.get_opentx_input() { - opentx_wit.build_opentx_sig(open_sig_data, signature) - } else { - Self::build_witness_lock(current_witness.lock(), signature)? - }; + if let Some(opentx_wit) = self.config.get_opentx_input() { + signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + } + let lock = Self::build_witness_lock(current_witness.lock(), signature)?; current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); witnesses[witness_idx] = current_witness.as_bytes().pack(); From 0b16ce79843bf6e74bbba16de87040e219b932ca Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 18 Oct 2022 22:05:38 +0800 Subject: [PATCH 07/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Fix=20opentx=20exa?= =?UTF-8?q?mple=20by=20add=20sighash=20dep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index f8230c17..827e4f9b 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -527,14 +527,27 @@ fn add_live_cell( tx_hash: args.tx_hash.clone(), index: ckb_jsonrpc_types::Uint32::from(args.index as u32), }; - ckb_client.get_live_cell(out_point_json, false)?; + let cell_with_status = ckb_client.get_live_cell(out_point_json, false)?; let input_outpoint = OutPoint::new( Byte32::from_slice(args.tx_hash.as_bytes())?, args.index as u32, ); // since value should be provided in args let input = ckb_types::packed::CellInput::new(input_outpoint, 0); - Ok(tx.as_advanced_builder().input(input).build()) + let cell_dep_resolver = { + let genesis_block = ckb_client.get_block_by_number(0.into())?.unwrap(); + DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block))? + }; + let code_hash = cell_with_status.cell.unwrap().output.lock.code_hash; + let script_id = ScriptId::new_type(code_hash); + let dep = cell_dep_resolver + .get(&script_id) + .as_ref() + .unwrap() + .0 + .clone(); + + Ok(tx.as_advanced_builder().input(input).cell_dep(dep).build()) } fn build_omnilock_unlockers( @@ -606,6 +619,6 @@ fn sighash_sign( // )?; let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); - let (new_tx, new_still_locked_groups) = unlock_tx(tx.clone(), &tx_dep_provider, &unlockers)?; + let (new_tx, new_still_locked_groups) = unlock_tx(tx, &tx_dep_provider, &unlockers)?; Ok((new_tx, new_still_locked_groups)) } From 08f3f6b1db29a2f78e58883c71ebc4e0691d36ce Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 20 Oct 2022 13:34:45 +0800 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20sign=20for=20openx?= =?UTF-8?q?=20ethereum/multisig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unlock/signer.rs | 49 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 44ba2b1b..00c644dc 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -548,6 +548,7 @@ impl OmniLockScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let witness_idx = script_group.input_indices[0]; let mut witnesses: Vec = tx.witnesses().into_iter().collect(); @@ -561,7 +562,15 @@ impl OmniLockScriptSigner { let zero_lock = self.config.zero_lock(self.unlock_mode)?; let zero_lock_len = zero_lock.len(); - let message = generate_message(&tx_new, script_group, zero_lock)?; + let (message, open_sig_data) = if let Some(opentx_wit) = self.config.get_opentx_input() { + let reader = OpenTxReader::new(&tx_new, tx_dep_provider, script_group)?; + opentx_wit.generate_message(&reader)? + } else { + ( + generate_message(&tx_new, script_group, zero_lock)?, + Bytes::new(), + ) + }; let multisig_config = match self.unlock_mode { OmniUnlockMode::Admin => self @@ -600,23 +609,29 @@ impl OmniLockScriptSigner { OmniLockWitnessLock::default() }; let config_data = multisig_config.to_witness_data(); + let osdl = open_sig_data.len(); let mut omni_sig = omnilock_witnesslock .signature() .to_opt() .map(|data| data.raw_data().as_ref().to_vec()) .unwrap_or_else(|| { let mut omni_sig = - vec![0u8; config_data.len() + multisig_config.threshold() as usize * 65]; - omni_sig[..config_data.len()].copy_from_slice(&config_data); + vec![0u8; osdl + config_data.len() + multisig_config.threshold() as usize * 65]; + if osdl > 0 { + omni_sig[..osdl].copy_from_slice(&open_sig_data); + } + omni_sig[osdl..config_data.len()].copy_from_slice(&config_data); omni_sig }); for signature in signatures { - let mut idx = config_data.len(); + // every signature should start from begin in case one already exist. + let mut idx = osdl + config_data.len(); while idx < omni_sig.len() { - // Put signature into an empty place. + // signautrue already exist if omni_sig[idx..idx + 65] == signature { break; } else if omni_sig[idx..idx + 65] == [0u8; 65] { + // Put signature into an empty place. omni_sig[idx..idx + 65].copy_from_slice(signature.as_ref()); break; } @@ -642,6 +657,7 @@ impl OmniLockScriptSigner { tx: &TransactionView, script_group: &ScriptGroup, id: &Identity, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let witness_idx = script_group.input_indices[0]; let mut witnesses: Vec = tx.witnesses().into_iter().collect(); @@ -654,12 +670,20 @@ impl OmniLockScriptSigner { .build(); let zero_lock = self.config.zero_lock(self.unlock_mode())?; - let message = generate_message(&tx_new, script_group, zero_lock)?; + let (message, open_sig_data) = if let Some(opentx_wit) = self.config.get_opentx_input() { + let reader = OpenTxReader::new(&tx_new, tx_dep_provider, script_group)?; + opentx_wit.generate_message(&reader)? + } else { + ( + generate_message(&tx_new, script_group, zero_lock)?, + Bytes::new(), + ) + }; let message = convert_keccak256_hash(message.as_ref()); - let signature = self - .signer - .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)?; + let mut signature = + self.signer + .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)?; // Put signature into witness let witness_data = witnesses[witness_idx].raw_data(); @@ -669,6 +693,9 @@ impl OmniLockScriptSigner { WitnessArgs::from_slice(witness_data.as_ref())? }; + if let Some(opentx_wit) = self.config.get_opentx_input() { + signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + } let lock = Self::build_witness_lock(current_witness.lock(), signature)?; current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); witnesses[witness_idx] = current_witness.as_bytes().pack(); @@ -809,8 +836,8 @@ impl ScriptSigner for OmniLockScriptSigner { witnesses[witness_idx] = current_witness.as_bytes().pack(); Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) } - IdentityFlag::Ethereum => self.sign_ethereum_tx(tx, script_group, &id), - IdentityFlag::Multisig => self.sign_multisig_tx(tx, script_group), + IdentityFlag::Ethereum => self.sign_ethereum_tx(tx, script_group, &id, tx_dep_provider), + IdentityFlag::Multisig => self.sign_multisig_tx(tx, script_group, tx_dep_provider), IdentityFlag::OwnerLock => { // should not reach here, just return a clone for compatible reason. Ok(tx.clone()) From a10a58d5d82a4d26679b2acc1e7e27ea4a0dea63 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 20 Oct 2022 16:10:44 +0800 Subject: [PATCH 09/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20example=20about=20?= =?UTF-8?q?opentx=20ethereum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 113 +++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 827e4f9b..3ce7f223 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -1,3 +1,4 @@ +use ckb_crypto::secp::Pubkey; use ckb_hash::blake2b_256; use ckb_jsonrpc_types as json_types; use ckb_sdk::{ @@ -12,9 +13,12 @@ use ckb_sdk::{ unlock_tx, CapacityBalancer, TxBuilder, }, types::NetworkType, - unlock::{opentx::OpentxWitness, OmniLockConfig, OmniLockScriptSigner, SecpSighashUnlocker}, + unlock::{ + opentx::OpentxWitness, IdentityFlag, OmniLockConfig, OmniLockScriptSigner, + SecpSighashUnlocker, + }, unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, - util::blake160, + util::{blake160, keccak160}, Address, HumanCapacity, ScriptGroup, ScriptId, SECP256K1, }; use ckb_types::{ @@ -62,6 +66,42 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d ./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json # send the tx ./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0xebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 + +########################### ethereum omnilock example ################################# +# 1. build a omnilock address +./target/debug/examples/transfer_from_opentx build --ethereum-receiver 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d 14:03:11 +pubkey:"038d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a" +pubkey:"048d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a26b16aac1d5753e56849ea83bf795eb8b06f0b6f4e5ed7b8caca720595458039" +{ + "lock-arg": "0x01cf2485c76aff1f2b4464edf04a1c8045068cf7e010", + "lock-hash": "0x057dcd204f26621ef49346ed77d2bdbf3069b83a5ef0a2b52be5299a93507cf6", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqjzc5z5", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru" +} +# 2. transfer capacity to the address +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru \ + --capacity 99 --skip-check-to-address + # 0xbd696b87629dfe38136c52e579800a432622baf5893b61365c7a18902a9ccd60 +# 3. generate the transaction +./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 --open-capacity 1.0\ + --tx-file tx.json +# 4. sign the transaction +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --mode ethereum \ + --tx-file tx.json +# add input, with capacity 99.99899588 +./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json +# add output, capacity is 99.99899588(original) + 1(open capacity) - 0.001(fee) +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 100.99799588 --tx-file tx.json +# sighash sign the new input +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +# send the tx +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; @@ -70,7 +110,11 @@ const OPENTX_TX_IDX: &str = "0"; struct BuildOmniLockAddrArgs { /// The receiver address #[clap(long, value_name = "ADDRESS")] - receiver: Address, + receiver: Option
, + + /// The receiver's private key (hex string) + #[clap(long, value_name = "KEY")] + ethereum_receiver: Option, /// omnilock script deploy transaction hash #[clap( @@ -92,7 +136,10 @@ struct BuildOmniLockAddrArgs { struct GenOpenTxArgs { /// The sender private key (hex string) #[clap(long, value_name = "KEY")] - sender_key: H256, + sender_key: Option, + /// The sender private key (hex string) + #[clap(long, value_name = "KEY")] + ethereum_sender_key: Option, /// The receiver address #[clap(long, value_name = "ADDRESS")] receiver: Address, @@ -132,6 +179,10 @@ struct SignTxArgs { #[clap(long, value_name = "KEY")] sender_key: H256, + /// The sender private key (hex string), possible values are: pubkeyhash, ethereum, multisig + #[clap(long, value_name = "KEY", default_value = "pubkeyhash")] + mode: String, + /// The output transaction info file (.json) #[clap(long, value_name = "PATH")] tx_file: PathBuf, @@ -248,9 +299,15 @@ fn main() -> Result<(), Box> { let key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) .map_err(|err| format!("invalid sender secret key: {}", err))?; let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); - let hash160 = &blake2b_256(&pubkey.serialize()[..])[0..20]; + let hash160 = match args.mode.as_str() { + "pubkeyhash" => blake2b_256(&pubkey.serialize()[..])[0..20].to_vec(), + "ethereum" => keccak160(Pubkey::from(pubkey).as_ref()).as_bytes().to_vec(), + _ => { + return Err(format!("not supported mod {}", args.mode).into()); + } + }; if tx_info.omnilock_config.id().auth_content().as_bytes() != hash160 { - return Err(format!("key {:#x} is not in omnilock config", args.sender_key).into()); + return Err(format!("key {:#x} is not in omnilock config", key).into()); } let (tx, _) = sign_tx(&args, tx, &tx_info.omnilock_config, key)?; let witness_args = @@ -335,8 +392,19 @@ fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box Script { fn build_open_tx( args: &GenOpenTxArgs, ) -> Result<(TransactionView, OmniLockConfig), Box> { - let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) - .map_err(|err| format!("invalid sender secret key: {}", err))?; - let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); let cell = build_omnilock_cell_dep(&mut ckb_client, &args.omnilock_tx_hash, args.omnilock_index)?; - let pubkey_hash = blake160(&pubkey.serialize()); - let mut omnilock_config = OmniLockConfig::new_pubkey_hash(pubkey_hash); + let mut omnilock_config = if let Some(sender_key) = args.sender_key.as_ref() { + let sender_key = secp256k1::SecretKey::from_slice(sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err))?; + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let pubkey_hash = blake160(&pubkey.serialize()); + OmniLockConfig::new_pubkey_hash(pubkey_hash) + } else if let Some(sender_key) = args.ethereum_sender_key.as_ref() { + let sender_key = secp256k1::SecretKey::from_slice(sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err))?; + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + println!("pubkey:{:?}", hex_string(&pubkey.serialize())); + println!("pubkey:{:?}", hex_string(&pubkey.serialize_uncompressed())); + let addr = keccak160(Pubkey::from(pubkey).as_ref()); + OmniLockConfig::new_ethereum(addr) + } else { + return Err("must provide a sender-key or an ethereum-sender-key".into()); + }; omnilock_config.set_opentx_mode(); // Build CapacityBalancer let sender = Script::new_builder() @@ -555,7 +635,12 @@ fn build_omnilock_unlockers( config: OmniLockConfig, omni_lock_type_hash: H256, ) -> HashMap> { - let signer = SecpCkbRawKeySigner::new_with_secret_keys(keys); + let signer = match config.id().flag() { + IdentityFlag::PubkeyHash => SecpCkbRawKeySigner::new_with_secret_keys(keys), + IdentityFlag::Ethereum => SecpCkbRawKeySigner::new_with_ethereum_secret_keys(keys), + IdentityFlag::Multisig => todo!(), + _ => unreachable!("should not reach here!"), + }; let omnilock_signer = OmniLockScriptSigner::new(Box::new(signer), config.clone(), OmniUnlockMode::Normal); let omnilock_unlocker = OmniLockUnlocker::new(omnilock_signer, config); From 21c9a53c41dfefca8216a03e9fb12ce33a9faffd Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Mon, 24 Oct 2022 10:29:15 +0800 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Finish=20example?= =?UTF-8?q?=20about=20opentx/multisig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 194 ++++++++++++++++++++++++++----- src/unlock/opentx/hasher.rs | 8 +- 2 files changed, 167 insertions(+), 35 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 3ce7f223..5c6fff2d 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -6,7 +6,7 @@ use ckb_sdk::{ rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, - DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, + DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, TransactionDependencyProvider, }, tx_builder::{ balance_tx_capacity, fill_placeholder_witnesses, transfer::CapacityTransferBuilder, @@ -14,7 +14,7 @@ use ckb_sdk::{ }, types::NetworkType, unlock::{ - opentx::OpentxWitness, IdentityFlag, OmniLockConfig, OmniLockScriptSigner, + opentx::OpentxWitness, IdentityFlag, MultisigConfig, OmniLockConfig, OmniLockScriptSigner, SecpSighashUnlocker, }, unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, @@ -36,8 +36,8 @@ use std::{collections::HashSet, fs}; /* # examples for the developer local node -########################### sighash omnilock example ################################# -# 1. build a omnilock address +########################### sighash opentx example ################################# +# 1. build an opentx address ./target/debug/examples/transfer_from_opentx build --receiver ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 { "lock-arg": "0x00b398368a8ed39448f95479c1178ff3fc5e31631810", @@ -68,8 +68,8 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d ./target/debug/examples/transfer_from_opentx send --tx-file tx.json # 0xebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 -########################### ethereum omnilock example ################################# -# 1. build a omnilock address +########################### ethereum opentx example ################################# +# 1. build an opentx address ./target/debug/examples/transfer_from_opentx build --ethereum-receiver 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d 14:03:11 pubkey:"038d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a" pubkey:"048d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a26b16aac1d5753e56849ea83bf795eb8b06f0b6f4e5ed7b8caca720595458039" @@ -91,7 +91,6 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d --tx-file tx.json # 4. sign the transaction ./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ - --mode ethereum \ --tx-file tx.json # add input, with capacity 99.99899588 ./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json @@ -102,20 +101,80 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d # send the tx ./target/debug/examples/transfer_from_opentx send --tx-file tx.json # 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b + +########################### multisig opentx example ################################# +# 1. build an opentx address +./target/debug/examples/transfer_from_opentx build --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 14:03:11 +{ + "lock-arg": "0x065d7d0128eeaa6f9656a229b42aadd0b177d387eb10", + "lock-hash": "0xf5202949800af0b454b2e4806c57da1d0f3ae87f7b9f4b698d9f3b71162ec196", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqmtamce", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3" +} +# 2. transfer capacity to the address +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3 \ + --capacity 99 --skip-check-to-address + # 0xf993b27a0129f72ec0a889cb016987c3cef00f7819461e51d5755464da6adf1b +# 3. generate the transaction +./target/debug/examples/transfer_from_opentx gen-open-tx \ + --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 --open-capacity 1.0 \ + --tx-file tx.json +# 4. sign the transaction +./target/debug/examples/transfer_from_opentx sign-open-tx \ + --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ + --tx-file tx.json +# add input, with capacity 99.99899588 +./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json +# add output, capacity is 99.99899588(original) + 1(open capacity) - 0.001(fee) +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 100.99799588 --tx-file tx.json +# sighash sign the new input +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +# send the tx +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; +#[derive(Args)] +struct MultiSigArgs { + /// Require first n signatures of corresponding pubkey + #[clap(long, value_name = "NUM", default_value = "1")] + require_first_n: u8, + + /// Multisig threshold + #[clap(long, value_name = "NUM", default_value = "1")] + threshold: u8, + + /// Normal sighash address + #[clap(long, value_name = "ADDRESS")] + sighash_address: Vec
, +} #[derive(Args)] struct BuildOmniLockAddrArgs { /// The receiver address - #[clap(long, value_name = "ADDRESS")] + #[clap(long, value_name = "ADDRESS", group = "algorithm")] receiver: Option
, /// The receiver's private key (hex string) - #[clap(long, value_name = "KEY")] + #[clap(long, value_name = "KEY", group = "algorithm")] ethereum_receiver: Option, + #[clap(flatten)] + multis_args: MultiSigArgs, + /// omnilock script deploy transaction hash #[clap( long, @@ -140,6 +199,10 @@ struct GenOpenTxArgs { /// The sender private key (hex string) #[clap(long, value_name = "KEY")] ethereum_sender_key: Option, + + #[clap(flatten)] + multis_args: MultiSigArgs, + /// The receiver address #[clap(long, value_name = "ADDRESS")] receiver: Address, @@ -177,11 +240,7 @@ struct GenOpenTxArgs { struct SignTxArgs { /// The sender private key (hex string) #[clap(long, value_name = "KEY")] - sender_key: H256, - - /// The sender private key (hex string), possible values are: pubkeyhash, ethereum, multisig - #[clap(long, value_name = "KEY", default_value = "pubkeyhash")] - mode: String, + sender_key: Vec, /// The output transaction info file (.json) #[clap(long, value_name = "PATH")] @@ -296,20 +355,33 @@ fn main() -> Result<(), Box> { Commands::SignOpenTx(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; let tx = Transaction::from(tx_info.tx.inner).into_view(); - let key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) - .map_err(|err| format!("invalid sender secret key: {}", err))?; - let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); - let hash160 = match args.mode.as_str() { - "pubkeyhash" => blake2b_256(&pubkey.serialize()[..])[0..20].to_vec(), - "ethereum" => keccak160(Pubkey::from(pubkey).as_ref()).as_bytes().to_vec(), - _ => { - return Err(format!("not supported mod {}", args.mode).into()); + let keys = args + .sender_key + .iter() + .map(|sender_key| { + secp256k1::SecretKey::from_slice(sender_key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap() + }) + .collect(); + if tx_info.omnilock_config.is_pubkey_hash() || tx_info.omnilock_config.is_ethereum() { + for key in &keys { + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, key); + let hash160 = match tx_info.omnilock_config.id().flag() { + IdentityFlag::PubkeyHash => { + blake2b_256(&pubkey.serialize()[..])[0..20].to_vec() + } + IdentityFlag::Ethereum => { + keccak160(Pubkey::from(pubkey).as_ref()).as_bytes().to_vec() + } + _ => unreachable!(), + }; + if tx_info.omnilock_config.id().auth_content().as_bytes() != hash160 { + return Err(format!("key {:#x} is not in omnilock config", key).into()); + } } - }; - if tx_info.omnilock_config.id().auth_content().as_bytes() != hash160 { - return Err(format!("key {:#x} is not in omnilock config", key).into()); } - let (tx, _) = sign_tx(&args, tx, &tx_info.omnilock_config, key)?; + let (tx, _) = sign_tx(&args, tx, &tx_info.omnilock_config, keys)?; let witness_args = WitnessArgs::from_slice(tx.witnesses().get(0).unwrap().raw_data().as_ref())?; let lock_field = witness_args.lock().to_opt().unwrap().raw_data(); @@ -388,6 +460,32 @@ fn main() -> Result<(), Box> { Ok(()) } +fn build_multisig_config( + sighash_address: &[Address], + require_first_n: u8, + threshold: u8, +) -> Result> { + if sighash_address.is_empty() { + return Err("Must have at least one sighash_address".to_string().into()); + } + let mut sighash_addresses = Vec::with_capacity(sighash_address.len()); + for addr in sighash_address { + let lock_args = addr.payload().args(); + if addr.payload().code_hash(None).as_slice() != SIGHASH_TYPE_HASH.as_bytes() + || addr.payload().hash_type() != ScriptHashType::Type + || lock_args.len() != 20 + { + return Err(format!("sighash_address {} is not sighash address", addr).into()); + } + sighash_addresses.push(H160::from_slice(lock_args.as_ref()).unwrap()); + } + Ok(MultisigConfig::new_with( + sighash_addresses, + require_first_n, + threshold, + )?) +} + fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box> { let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); let cell = @@ -402,6 +500,11 @@ fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box = tx + .input_pts_iter() + .enumerate() + .filter(|(_, output)| tx_dep_provider.get_cell(output).unwrap().lock() == sender) + .map(|(idx, _)| idx) + .collect(); + let witnesses: Vec<_> = tx + .witnesses() + .into_iter() + .enumerate() + .map(|(i, w)| { + if tmp_idxes.contains(&i) { + placeholder_witness.as_bytes().pack() + } else { + w + } + }) + .collect(); + let tx = tx.as_advanced_builder().set_witnesses(witnesses).build(); Ok((tx, omnilock_config)) } @@ -638,7 +767,7 @@ fn build_omnilock_unlockers( let signer = match config.id().flag() { IdentityFlag::PubkeyHash => SecpCkbRawKeySigner::new_with_secret_keys(keys), IdentityFlag::Ethereum => SecpCkbRawKeySigner::new_with_ethereum_secret_keys(keys), - IdentityFlag::Multisig => todo!(), + IdentityFlag::Multisig => SecpCkbRawKeySigner::new_with_secret_keys(keys), _ => unreachable!("should not reach here!"), }; let omnilock_signer = @@ -655,7 +784,7 @@ fn sign_tx( args: &SignTxArgs, mut tx: TransactionView, omnilock_config: &OmniLockConfig, - key: secp256k1::SecretKey, + keys: Vec, ) -> Result<(TransactionView, Vec), Box> { // Unlock transaction let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); @@ -665,7 +794,7 @@ fn sign_tx( build_omnilock_cell_dep(&mut ckb_client, &args.omnilock_tx_hash, args.omnilock_index)?; let mut _still_locked_groups = None; - let unlockers = build_omnilock_unlockers(vec![key], omnilock_config.clone(), cell.type_hash); + let unlockers = build_omnilock_unlockers(keys, omnilock_config.clone(), cell.type_hash); let (new_tx, new_still_locked_groups) = unlock_tx(tx.clone(), &tx_dep_provider, &unlockers)?; tx = new_tx; _still_locked_groups = Some(new_still_locked_groups); @@ -676,7 +805,10 @@ fn sighash_sign( args: &SignTxArgs, tx: TransactionView, ) -> Result<(TransactionView, Vec), Box> { - let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) + if args.sender_key.is_empty() { + return Err("must provide sender-key to sign".into()); + } + let sender_key = secp256k1::SecretKey::from_slice(args.sender_key[0].as_bytes()) .map_err(|err| format!("invalid sender secret key: {}", err))?; // Build ScriptUnlocker let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![sender_key]); diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 822f1246..1c447feb 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -569,7 +569,9 @@ impl OpentxWitness { let (relative_idx, absolute_idx) = (true, false); let mut cache = OpentxCache::new(); - let mut s_data = BytesMut::with_capacity(self.inputs.len() * 4); + let mut s_data = BytesMut::with_capacity(self.opentx_sig_data_len()); + s_data.put_u32_le(self.base_input_index as u32); + s_data.put_u32_le(self.base_output_index as u32); let mut has_last = false; for si in &self.inputs { match si.cmd { @@ -632,7 +634,7 @@ impl OpentxWitness { s_data.extend_from_slice(&si.compose().to_le_bytes()); } let s_data = s_data.freeze(); - cache.update(s_data.to_vec().as_slice()); + cache.update(s_data[8..].to_vec().as_slice()); let msg = cache.finalize(); Ok((msg, s_data)) @@ -644,8 +646,6 @@ impl OpentxWitness { pub fn build_opentx_sig(&self, sil_data: Bytes, sig_bytes: Bytes) -> Bytes { let mut data = BytesMut::with_capacity(self.opentx_sig_data_len() + sig_bytes.len()); - data.put_u32_le(self.base_input_index as u32); - data.put_u32_le(self.base_output_index as u32); data.put(sil_data); data.put(sig_bytes); From 1f0f95ea9cc3c444063b95ffd4000b9c155e0c15 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Mon, 24 Oct 2022 10:32:32 +0800 Subject: [PATCH 11/29] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20complete=20d?= =?UTF-8?q?ocs=20about=20opentx/multisig=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 5c6fff2d..829ac4fd 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -135,15 +135,15 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ --tx-file tx.json -# add input, with capacity 99.99899588 -./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json -# add output, capacity is 99.99899588(original) + 1(open capacity) - 0.001(fee) -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 100.99799588 --tx-file tx.json +# add input, with capacity 100.99799588 +./target/debug/examples/transfer_from_opentx add-input --tx-hash 621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b --index 1 --tx-file tx.json +# add output, capacity is 100.99799588(original) + 1(open capacity) - 0.001(fee) +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 101.99699588 --tx-file tx.json # sighash sign the new input ./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json # send the tx ./target/debug/examples/transfer_from_opentx send --tx-file tx.json -# 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b +# 0x577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; From e81a357cf6b60b7eb176c85c4b2614ce5427b83f Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Mon, 24 Oct 2022 14:04:59 +0800 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20can=20put=20multi?= =?UTF-8?q?=20opentxes=20into=20one=20open=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. merge multi opentx into one opentx; 2. put build open out code into builder. 3. finish example about merge --- examples/transfer_from_opentx.rs | 222 +++++++++++++++++++------------ src/tx_builder/omni_lock.rs | 113 +++++++++++++++- src/unlock/opentx/assembler.rs | 46 +++++-- src/unlock/opentx/mod.rs | 9 ++ 4 files changed, 292 insertions(+), 98 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 829ac4fd..63d79c1b 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -6,16 +6,16 @@ use ckb_sdk::{ rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, - DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, TransactionDependencyProvider, + DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, }, tx_builder::{ - balance_tx_capacity, fill_placeholder_witnesses, transfer::CapacityTransferBuilder, + balance_tx_capacity, fill_placeholder_witnesses, omni_lock::OmniLockTransferBuilder, unlock_tx, CapacityBalancer, TxBuilder, }, types::NetworkType, unlock::{ - opentx::OpentxWitness, IdentityFlag, MultisigConfig, OmniLockConfig, OmniLockScriptSigner, - SecpSighashUnlocker, + opentx::{assembler::assemble_new_tx, OpentxWitness}, + IdentityFlag, MultisigConfig, OmniLockConfig, OmniLockScriptSigner, SecpSighashUnlocker, }, unlock::{OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker}, util::{blake160, keccak160}, @@ -30,9 +30,7 @@ use ckb_types::{ }; use clap::{Args, Parser, Subcommand}; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::{collections::HashMap, error::Error as StdErr}; -use std::{collections::HashSet, fs}; +use std::{collections::HashMap, error::Error as StdErr, fs, path::PathBuf}; /* # examples for the developer local node @@ -130,7 +128,7 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ --capacity 98.0 --open-capacity 1.0 \ --tx-file tx.json -# 4. sign the transaction +# 4. sign the transaction, this step can sign seperately with each sender-key ./target/debug/examples/transfer_from_opentx sign-open-tx \ --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ @@ -144,6 +142,74 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d # send the tx ./target/debug/examples/transfer_from_opentx send --tx-file tx.json # 0x577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 + +########################### multiple opentxes put together ################################# +# 1. build/sign sighash opentx +./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1\ + --tx-file tx-sighash.json +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --tx-file tx-sighash.json + +# 2. build/sign sighash opentx +./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1\ + --tx-file tx-ethereum.json +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --tx-file tx-ethereum.json + +# 3. build/sign multisig opentx +./target/debug/examples/transfer_from_opentx gen-open-tx \ + --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1.0 \ + --tx-file tx-multisig.json +./target/debug/examples/transfer_from_opentx sign-open-tx \ + --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ + --tx-file tx-multisig.json + +# 4. merge into one transaction +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx-ethereum.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +# the other way get the same merge result: +# merge first 2, then merge the last +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx-ethereum.json \ + --tx-file tx.json +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +# merge last 2, then merge the first +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-ethereum.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx.json \ + --tx-file tx.json + +# add input, with capacity 101.99699588 +./target/debug/examples/transfer_from_opentx add-input --tx-hash 577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 --index 1 --tx-file tx.json +# add output, capacity is 101.99699588(original) + 3(1 open capacity each) - 0.001(fee) +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 104.99599588 --tx-file tx.json +# sighash sign the new input +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +# send the tx +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x4fd5d4adfb009a6e342a9e8442ac54989e28ef887b1fec60c3703e4c4d223b39 */ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; @@ -299,6 +365,29 @@ struct AddOutputArgs { #[clap(long, value_name = "PATH")] tx_file: PathBuf, } + +#[derive(Args)] +struct MergeOpenTxArgs { + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + in_tx_file: Vec, + + /// The output transaction info file (.json) + #[clap(long, value_name = "PATH")] + tx_file: PathBuf, + /// omnilock script deploy transaction hash + #[clap(long, value_name = "H256", default_value = OPENTX_TX_HASH)] + omnilock_tx_hash: H256, + + /// cell index of omnilock script deploy transaction's outputs + #[clap(long, value_name = "NUMBER", default_value = OPENTX_TX_IDX)] + omnilock_index: usize, + + /// CKB rpc url + #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] + ckb_rpc: String, +} + #[derive(Subcommand)] enum Commands { /// build omni lock address @@ -309,6 +398,8 @@ enum Commands { SignOpenTx(SignTxArgs), /// sign sighash input SighashSignTx(SignTxArgs), + /// merge opentx together + MergeOpenTx(MergeOpenTxArgs), /// Add input AddInput(AddInputArgs), /// Add output @@ -455,6 +546,33 @@ fn main() -> Result<(), Box> { .expect("send transaction"); println!(">>> tx sent! <<<"); } + Commands::MergeOpenTx(args) => { + let mut txes = vec![]; + let mut omnilock_config = None; + for in_tx in &args.in_tx_file { + let tx_info: TxInfo = serde_json::from_slice(&fs::read(in_tx)?)?; + // println!("> tx: {}", serde_json::to_string_pretty(&tx_info.tx)?); + let tx = Transaction::from(tx_info.tx.inner).into_view(); + txes.push(tx); + omnilock_config = Some(tx_info.omnilock_config); + } + if !txes.is_empty() { + let mut ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); + let cell = build_omnilock_cell_dep( + &mut ckb_client, + &args.omnilock_tx_hash, + args.omnilock_index, + )?; + let tx_dep_provider = + DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); + let tx = assemble_new_tx(txes, &tx_dep_provider, cell.type_hash.pack())?; + let tx_info = TxInfo { + tx: json_types::TransactionView::from(tx), + omnilock_config: omnilock_config.unwrap(), + }; + fs::write(&args.tx_file, serde_json::to_string_pretty(&tx_info)?)?; + } + } } Ok(()) @@ -534,23 +652,6 @@ fn gen_open_tx(args: &GenOpenTxArgs) -> Result<(), Box> { Ok(()) } -fn get_opentx_placeholder_hash() -> H256 { - let mut ret = H256::default(); - let opentx = "opentx"; - let offset = ret.0.len() - opentx.len(); - ret.0[offset..].copy_from_slice(opentx.as_bytes()); - ret -} - -fn get_opentx_tmp_script() -> Script { - let tmp_locker = get_opentx_placeholder_hash(); - Script::new_builder() - .code_hash(tmp_locker.pack()) - .hash_type(ScriptHashType::Type.into()) - .args([0u8; 65].pack()) - .build() -} - fn build_open_tx( args: &GenOpenTxArgs, ) -> Result<(TransactionView, OmniLockConfig), Box> { @@ -612,15 +713,12 @@ fn build_open_tx( .capacity(args.capacity.0.pack()) .build(); - let tmp_locker = get_opentx_tmp_script(); - let output_tmp = CellOutput::new_builder() - .lock(tmp_locker.clone()) - .capacity(args.open_capacity.0.pack()) - .build(); - let builder = CapacityTransferBuilder::new(vec![ - (output, Bytes::default()), - (output_tmp, Bytes::default()), - ]); + let builder = OmniLockTransferBuilder::new_open( + args.open_capacity, + vec![(output, Bytes::default())], + omnilock_config.clone(), + None, + ); let base_tx = builder.build_base( &mut cell_collector, @@ -651,56 +749,16 @@ fn build_open_tx( &header_dep_resolver, )?; - let tmp_idxes: HashSet = tx - .outputs() - .into_iter() - .enumerate() - .filter(|(_, out)| out.lock() == tmp_locker) - .map(|(idx, _)| idx) - .collect(); - let outputs: Vec = tx - .outputs() - .into_iter() - .enumerate() - .filter(|(idx, _)| !tmp_idxes.contains(idx)) - .map(|(_, out)| out) - .collect(); - let outputs_data: Vec = tx - .outputs_data() - .into_iter() - .enumerate() - .filter(|(idx, _)| !tmp_idxes.contains(idx)) - .map(|(_, out)| out) - .collect(); - let tx = tx - .as_advanced_builder() - .set_outputs(outputs) - .set_outputs_data(outputs_data) - .build(); - + let tx = OmniLockTransferBuilder::remove_open_out(tx); let wit = OpentxWitness::new_sig_all_relative(&tx, Some(0xdeadbeef)).unwrap(); omnilock_config.set_opentx_input(wit); - // after set opentx config, need to update the witness field - let placeholder_witness = omnilock_config.placeholder_witness(OmniUnlockMode::Normal)?; - let tmp_idxes: Vec<_> = tx - .input_pts_iter() - .enumerate() - .filter(|(_, output)| tx_dep_provider.get_cell(output).unwrap().lock() == sender) - .map(|(idx, _)| idx) - .collect(); - let witnesses: Vec<_> = tx - .witnesses() - .into_iter() - .enumerate() - .map(|(i, w)| { - if tmp_idxes.contains(&i) { - placeholder_witness.as_bytes().pack() - } else { - w - } - }) - .collect(); - let tx = tx.as_advanced_builder().set_witnesses(witnesses).build(); + let tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &omnilock_config, + OmniUnlockMode::Normal, + &tx_dep_provider, + &sender, + )?; Ok((tx, omnilock_config)) } diff --git a/src/tx_builder/omni_lock.rs b/src/tx_builder/omni_lock.rs index 2662f030..b69a9e9d 100644 --- a/src/tx_builder/omni_lock.rs +++ b/src/tx_builder/omni_lock.rs @@ -2,17 +2,18 @@ use std::collections::HashSet; use ckb_types::{ bytes::Bytes, - core::{DepType, TransactionBuilder, TransactionView}, - packed::{CellDep, CellInput, CellOutput, OutPoint}, + core::{DepType, ScriptHashType, TransactionBuilder, TransactionView}, + packed::{CellDep, CellInput, CellOutput, OutPoint, Script}, prelude::*, + H256, }; use super::{TxBuilder, TxBuilderError}; -use crate::types::ScriptId; use crate::{ traits::{CellCollector, CellDepResolver, HeaderDepResolver, TransactionDependencyProvider}, - unlock::OmniLockConfig, + unlock::{omni_lock::ConfigError, OmniLockConfig, OmniUnlockMode}, }; +use crate::{types::ScriptId, HumanCapacity}; /// A builder to build an omnilock transfer transaction. pub struct OmniLockTransferBuilder { @@ -33,6 +34,110 @@ impl OmniLockTransferBuilder { rce_cells, } } + + /// Create an OmniLockTransferBuilder with open out in the output list. + /// After the transaction built, the open out should be removed. + pub fn new_open( + open_capacity: HumanCapacity, + mut outputs: Vec<(CellOutput, Bytes)>, + cfg: OmniLockConfig, + rce_cells: Option>, + ) -> OmniLockTransferBuilder { + let tmp_out = OmniLockTransferBuilder::build_tmp_open_out(open_capacity); + outputs.push((tmp_out, Bytes::default())); + OmniLockTransferBuilder { + outputs, + cfg, + rce_cells, + } + } + + fn build_opentx_placeholder_hash() -> H256 { + let mut ret = H256::default(); + let opentx = "opentx"; + let offset = ret.0.len() - opentx.len(); + ret.0[offset..].copy_from_slice(opentx.as_bytes()); + ret + } + + fn build_opentx_tmp_script() -> Script { + let tmp_locker = Self::build_opentx_placeholder_hash(); + Script::new_builder() + .code_hash(tmp_locker.pack()) + .hash_type(ScriptHashType::Type.into()) + .args([0xffu8; 65].pack()) + .build() + } + + pub fn build_tmp_open_out(open_capacity: HumanCapacity) -> CellOutput { + let tmp_locker = Self::build_opentx_tmp_script(); + CellOutput::new_builder() + .lock(tmp_locker) + .capacity(open_capacity.0.pack()) + .build() + } + + /// remove the open output + pub fn remove_open_out(tx: TransactionView) -> TransactionView { + let tmp_locker = Self::build_opentx_tmp_script(); + let tmp_idxes: HashSet = tx + .outputs() + .into_iter() + .enumerate() + .filter(|(_, out)| out.lock() == tmp_locker) + .map(|(idx, _)| idx) + .collect(); + let outputs: Vec = tx + .outputs() + .into_iter() + .enumerate() + .filter(|(idx, _)| !tmp_idxes.contains(idx)) + .map(|(_, out)| out) + .collect(); + let outputs_data: Vec = tx + .outputs_data() + .into_iter() + .enumerate() + .filter(|(idx, _)| !tmp_idxes.contains(idx)) + .map(|(_, out)| out) + .collect(); + tx.as_advanced_builder() + .set_outputs(outputs) + .set_outputs_data(outputs_data) + .build() + } + + /// after the open transaction input list updated(exclude base input/output), the witness should be updated + pub fn update_opentx_witness( + tx: TransactionView, + omnilock_config: &OmniLockConfig, + unlock_mode: OmniUnlockMode, + tx_dep_provider: &dyn TransactionDependencyProvider, + sender: &Script, + ) -> Result { + // after set opentx config, need to update the witness field + let placeholder_witness = omnilock_config.placeholder_witness(unlock_mode)?; + let tmp_idxes: Vec<_> = tx + .input_pts_iter() + .enumerate() + .filter(|(_, output)| tx_dep_provider.get_cell(output).unwrap().lock() == *sender) + .map(|(idx, _)| idx) + .collect(); + let witnesses: Vec<_> = tx + .witnesses() + .into_iter() + .enumerate() + .map(|(i, w)| { + if tmp_idxes.contains(&i) { + placeholder_witness.as_bytes().pack() + } else { + w + } + }) + .collect(); + let tx = tx.as_advanced_builder().set_witnesses(witnesses).build(); + Ok(tx) + } } impl TxBuilder for OmniLockTransferBuilder { diff --git a/src/unlock/opentx/assembler.rs b/src/unlock/opentx/assembler.rs index fcac126f..be0b1e1d 100644 --- a/src/unlock/opentx/assembler.rs +++ b/src/unlock/opentx/assembler.rs @@ -1,14 +1,15 @@ use anyhow::anyhow; -use std::convert::TryFrom; -use std::{cmp::Ordering, collections::HashSet}; +use std::{cmp::Ordering, collections::HashSet, convert::TryFrom}; use ckb_types::{ + bytes::Bytes, core::TransactionView, packed::{Byte32, WitnessArgs}, prelude::*, }; +use crate::types::omni_lock::OmniLockWitnessLock; use crate::{traits::TransactionDependencyProvider, unlock::omni_lock::OmniLockFlags}; use super::OpenTxError; @@ -17,8 +18,8 @@ use super::OpenTxError; /// Alter base input/output index. pub fn assemble_new_tx( mut txes: Vec, - provider: Box, - script_hash: Byte32, + provider: &dyn TransactionDependencyProvider, + opentx_code_hash: Byte32, ) -> Result { if txes.len() == 1 { return Ok(txes.remove(0)); @@ -35,13 +36,30 @@ pub fn assemble_new_tx( // handle opentx witness for (input, witness) in tx.inputs().into_iter().zip(tx.witnesses().into_iter()) { let lock = provider.get_cell(&input.previous_output())?.lock(); - let lock_hash = lock.calc_script_hash(); - if lock_hash.cmp(&script_hash) == Ordering::Equal { + let code_hash = lock.code_hash(); + if code_hash.cmp(&opentx_code_hash) == Ordering::Equal { let args = &lock.args().raw_data(); if args.len() >= 22 && OmniLockFlags::from_bits_truncate(args[21]).contains(OmniLockFlags::OPENTX) { - let mut data = (&witness.raw_data()).to_vec(); + // parse lock data + let witness_data = witness.raw_data(); + let current_witness: WitnessArgs = + WitnessArgs::from_slice(witness_data.as_ref())?; + let lock_field = current_witness + .lock() + .to_opt() + .map(|data| data.raw_data()) + .ok_or(OpenTxError::WitnessLockMissing)?; + let omnilock_witnesslock = + OmniLockWitnessLock::from_slice(lock_field.as_ref())?; + + let mut data = omnilock_witnesslock + .signature() + .to_opt() + .map(|data| data.raw_data().as_ref().to_vec()) + .ok_or(OpenTxError::SignatureMissing)?; + let mut tmp = [0u8; 4]; tmp.copy_from_slice(&data[0..4]); let this_base_input_idx = u32::from_le_bytes(tmp) @@ -52,12 +70,16 @@ pub fn assemble_new_tx( let this_base_output_idx = u32::from_le_bytes(tmp) + u32::try_from(base_output_idx).map_err(|e| anyhow!(e))?; data[4..8].copy_from_slice(&this_base_output_idx.to_le_bytes()); - let witness = WitnessArgs::from_slice(&data) - .map_err(|e| anyhow!(e))? - .as_bytes() - .pack(); - builder = builder.witness(witness); + let omnilock_witnesslock = omnilock_witnesslock + .as_builder() + .signature(Some(Bytes::from(data)).pack()) + .build(); + let witness = current_witness + .as_builder() + .lock(Some(omnilock_witnesslock.as_bytes()).pack()) + .build(); + builder = builder.witness(witness.as_bytes().pack()); continue; } } diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs index d6266b1d..c24fcae2 100644 --- a/src/unlock/opentx/mod.rs +++ b/src/unlock/opentx/mod.rs @@ -2,6 +2,7 @@ pub mod assembler; pub mod hasher; pub mod reader; +use ckb_types::error::VerificationError; pub use hasher::OpentxWitness; use crate::traits::TransactionDependencyError; @@ -27,6 +28,14 @@ pub enum OpenTxError { #[error("base index(`{0}) bigger than end index(`{1}`)")] BaseIndexOverFlow(usize, usize), + #[error(transparent)] + VerificationError(#[from]VerificationError), + #[error("witness field not exist")] + WitnessMissing, + #[error("lock field of witness not exist")] + WitnessLockMissing, + #[error("signature not exist")] + SignatureMissing, #[error(transparent)] Other(#[from] anyhow::Error), } From 3e0805dd08e3d5b4fcedcd3660c6f9e7852a1854 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 25 Oct 2022 10:00:45 +0800 Subject: [PATCH 13/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20compile=20pro?= =?UTF-8?q?blems=20after=20merge=20from=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 7 +------ src/unlock/opentx/mod.rs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 63d79c1b..8706c405 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -296,10 +296,6 @@ struct GenOpenTxArgs { /// CKB rpc url #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] ckb_rpc: String, - - /// CKB indexer rpc url - #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8116")] - ckb_indexer: String, } #[derive(Args)] @@ -702,8 +698,7 @@ fn build_open_tx( let mut cell_dep_resolver = DefaultCellDepResolver::from_genesis(&genesis_block)?; cell_dep_resolver.insert(cell.script_id, cell.cell_dep, "Omni Lock".to_string()); let header_dep_resolver = DefaultHeaderDepResolver::new(args.ckb_rpc.as_str()); - let mut cell_collector = - DefaultCellCollector::new(args.ckb_indexer.as_str(), args.ckb_rpc.as_str()); + let mut cell_collector = DefaultCellCollector::new(args.ckb_rpc.as_str()); let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); // Build base transaction diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs index c24fcae2..cac1bc99 100644 --- a/src/unlock/opentx/mod.rs +++ b/src/unlock/opentx/mod.rs @@ -29,7 +29,7 @@ pub enum OpenTxError { BaseIndexOverFlow(usize, usize), #[error(transparent)] - VerificationError(#[from]VerificationError), + VerificationError(#[from] VerificationError), #[error("witness field not exist")] WitnessMissing, #[error("lock field of witness not exist")] From 3bb4ea3522c1a76cfb9f6bcbc41c97cbc7dc8eec Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 1 Nov 2022 17:19:54 +0800 Subject: [PATCH 14/29] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20Move=20the?= =?UTF-8?q?=20big=20docs=20from=20source=20file=20to=20another=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.md | 252 +++++++++++++++++++++++++++++++ examples/transfer_from_opentx.rs | 182 +--------------------- 2 files changed, 255 insertions(+), 179 deletions(-) create mode 100644 examples/transfer_from_opentx.md diff --git a/examples/transfer_from_opentx.md b/examples/transfer_from_opentx.md new file mode 100644 index 00000000..65560347 --- /dev/null +++ b/examples/transfer_from_opentx.md @@ -0,0 +1,252 @@ + +This document is about how to use the transfer_from_opentx example to do open transaction operation. +All the addresses and keys are all in my development local node, you should not use in the production environment. +# Singhash open transaction example +1. Build an opentx address +```bash + ./target/debug/examples/transfer_from_opentx build --receiver ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 + ``` + The output: + ```json +{ + "lock-arg": "0x00b398368a8ed39448f95479c1178ff3fc5e31631810", + "lock-hash": "0x3f54ccaf46b3472b55eaa2e2c0a5cae87575b3de90a81fe60206dd5c0951ffa8", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqv8f7ak", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7" +} +``` +2. Transfer capacity to the address +```bash +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 99 --skip-check-to-address +# 0x937deeb989bbd7f4bd0273bf2049d7614615dd58a32090b0093f23a692715871 +``` +3. Generate the transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 --open-capacity 1.0\ + --tx-file tx.json +``` +4. Sign the transaction +```bash +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --tx-file tx.json +``` +5. Add input, with capacity 98.99999588 +```bash +./target/debug/examples/transfer_from_opentx add-input --tx-hash df85d2aaa44d50b1db286bdb2fbd8682cad12d6858b269d2531403ba5e63a2eb --index 0 --tx-file tx.json +``` +6. Add output, capacity is 98.99999588(original) + 1(open capacity) - 0.001(fee) +```bash +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 99.99899588 --tx-file tx.json +``` +7. Sign the new input +```bash +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +``` +8. send the tx +```bash +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0xebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 +``` + +# Ethereum open transaction example +1. build an opentx address +```bash +./target/debug/examples/transfer_from_opentx build --ethereum-receiver 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d +``` +output: +```json +pubkey:"038d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a" +pubkey:"048d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a26b16aac1d5753e56849ea83bf795eb8b06f0b6f4e5ed7b8caca720595458039" +{ + "lock-arg": "0x01cf2485c76aff1f2b4464edf04a1c8045068cf7e010", + "lock-hash": "0x057dcd204f26621ef49346ed77d2bdbf3069b83a5ef0a2b52be5299a93507cf6", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqjzc5z5", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru" +} +``` +2. Transfer capacity to the address +```bash +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru \ + --capacity 99 --skip-check-to-address +# 0xbd696b87629dfe38136c52e579800a432622baf5893b61365c7a18902a9ccd60 +``` +3. Generate the transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 --open-capacity 1.0\ + --tx-file tx.json +``` +4. Sign the transaction +```bash +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --tx-file tx.json +``` +5. Add input, with capacity 99.99899588 +```bash +./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json +``` +6. Add output, capacity is 99.99899588(original) + 1(open capacity) - 0.001(fee) +```bash +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 100.99799588 --tx-file tx.json +``` +7. Sighash sign the new input +```bash +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +``` +8. Send the transaction +```bash +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b +``` +# Multisig open transaction example +1. build an opentx address +```bash +./target/debug/examples/transfer_from_opentx build --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 14:03:11 +``` +The output: +```json +{ + "lock-arg": "0x065d7d0128eeaa6f9656a229b42aadd0b177d387eb10", + "lock-hash": "0xf5202949800af0b454b2e4806c57da1d0f3ae87f7b9f4b698d9f3b71162ec196", + "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqmtamce", + "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3" +} +``` +2. Transfer capacity to the address +```bash +ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ + --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3 \ + --capacity 99 --skip-check-to-address +# 0xf993b27a0129f72ec0a889cb016987c3cef00f7819461e51d5755464da6adf1b +``` +3. Generate the transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx \ + --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 98.0 --open-capacity 1.0 \ + --tx-file tx.json +``` +4. Sign the transaction, this step can sign seperately with each sender-key +```bash +./target/debug/examples/transfer_from_opentx sign-open-tx \ + --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ + --tx-file tx.json +``` +5. Add input, with capacity 100.99799588 +```bash +./target/debug/examples/transfer_from_opentx add-input --tx-hash 621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b --index 1 --tx-file tx.json +``` +6. Add output, capacity is 100.99799588(original) + 1(open capacity) - 0.001(fee) +```bash +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 101.99699588 --tx-file tx.json +``` +7. Sighash sign the new input +```bash +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +``` +8. Send the tx +```bash +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 +``` +# Put multiple open transactions together +1. Build/sign sighash open transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1\ + --tx-file tx-sighash.json +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --tx-file tx-sighash.json +``` +2. Build/sign sighash open transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1\ + --tx-file tx-ethereum.json +./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ + --tx-file tx-ethereum.json +``` +3. Build/sign multisig open transaction +```bash +./target/debug/examples/transfer_from_opentx gen-open-tx \ + --require-first-n 0 \ + --threshold 2 \ + --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ + --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ + --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ + --capacity 97 --open-capacity 1.0 \ + --tx-file tx-multisig.json +./target/debug/examples/transfer_from_opentx sign-open-tx \ + --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ + --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ + --tx-file tx-multisig.json +``` +4. merge into one transaction + +You can merge them in one command: +```bash +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx-ethereum.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +``` + The other way get the same merge result: ++ Merge first 2, then merge the last +```bash +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx-ethereum.json \ + --tx-file tx.json +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +``` ++ Merge last 2, then merge the first +```bash +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-ethereum.json \ + --in-tx-file tx-multisig.json \ + --tx-file tx.json +./target/debug/examples/transfer_from_opentx merge-open-tx \ + --in-tx-file tx-sighash.json \ + --in-tx-file tx.json \ + --tx-file tx.json +``` +5. Add input, with capacity 101.99699588 +```bash +./target/debug/examples/transfer_from_opentx add-input --tx-hash 577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 --index 1 --tx-file tx.json +``` +6. Add output, capacity is 101.99699588(original) + 3(1 open capacity each) - 0.001(fee) +```bash +./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 104.99599588 --tx-file tx.json +``` +7. Sighash sign the new input +```bash +./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json +``` +8. Send the transaction +```bash +./target/debug/examples/transfer_from_opentx send --tx-file tx.json +# 0x4fd5d4adfb009a6e342a9e8442ac54989e28ef887b1fec60c3703e4c4d223b39 +``` \ No newline at end of file diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 8706c405..d2dc0439 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -1,3 +1,6 @@ +/* +How to use the example transfer_from_opentx, see the file transfer_from_opentx.md +*/ use ckb_crypto::secp::Pubkey; use ckb_hash::blake2b_256; use ckb_jsonrpc_types as json_types; @@ -32,185 +35,6 @@ use clap::{Args, Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, error::Error as StdErr, fs, path::PathBuf}; -/* -# examples for the developer local node -########################### sighash opentx example ################################# -# 1. build an opentx address - ./target/debug/examples/transfer_from_opentx build --receiver ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 -{ - "lock-arg": "0x00b398368a8ed39448f95479c1178ff3fc5e31631810", - "lock-hash": "0x3f54ccaf46b3472b55eaa2e2c0a5cae87575b3de90a81fe60206dd5c0951ffa8", - "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqv8f7ak", - "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7" -} -# 2. transfer capacity to the address -ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ - --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 99 --skip-check-to-address - # 0x937deeb989bbd7f4bd0273bf2049d7614615dd58a32090b0093f23a692715871 -# 3. generate the transaction -./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 98.0 --open-capacity 1.0\ - --tx-file tx.json -# 4. sign the transaction -./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --tx-file tx.json -# add input, with capacity 98.99999588 -./target/debug/examples/transfer_from_opentx add-input --tx-hash df85d2aaa44d50b1db286bdb2fbd8682cad12d6858b269d2531403ba5e63a2eb --index 0 --tx-file tx.json -# add output, capacity is 98.99999588(original) + 1(open capacity) - 0.001(fee) -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 99.99899588 --tx-file tx.json -# sighash sign the new input -./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json -# send the tx -./target/debug/examples/transfer_from_opentx send --tx-file tx.json -# 0xebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 - -########################### ethereum opentx example ################################# -# 1. build an opentx address -./target/debug/examples/transfer_from_opentx build --ethereum-receiver 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d 14:03:11 -pubkey:"038d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a" -pubkey:"048d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a26b16aac1d5753e56849ea83bf795eb8b06f0b6f4e5ed7b8caca720595458039" -{ - "lock-arg": "0x01cf2485c76aff1f2b4464edf04a1c8045068cf7e010", - "lock-hash": "0x057dcd204f26621ef49346ed77d2bdbf3069b83a5ef0a2b52be5299a93507cf6", - "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqjzc5z5", - "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru" -} -# 2. transfer capacity to the address -ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ - --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgpeujgt3m2lu0jk3ryahcy58yqg5rgealqzqf4vcru \ - --capacity 99 --skip-check-to-address - # 0xbd696b87629dfe38136c52e579800a432622baf5893b61365c7a18902a9ccd60 -# 3. generate the transaction -./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 98.0 --open-capacity 1.0\ - --tx-file tx.json -# 4. sign the transaction -./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ - --tx-file tx.json -# add input, with capacity 99.99899588 -./target/debug/examples/transfer_from_opentx add-input --tx-hash ebb9d9ff39efbee5957d6f7d19a4a17f1ac2e69dbc9289e4931cef6b832f4d57 --index 1 --tx-file tx.json -# add output, capacity is 99.99899588(original) + 1(open capacity) - 0.001(fee) -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 100.99799588 --tx-file tx.json -# sighash sign the new input -./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json -# send the tx -./target/debug/examples/transfer_from_opentx send --tx-file tx.json -# 0x621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b - -########################### multisig opentx example ################################# -# 1. build an opentx address -./target/debug/examples/transfer_from_opentx build --require-first-n 0 \ - --threshold 2 \ - --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ - --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ - --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 14:03:11 -{ - "lock-arg": "0x065d7d0128eeaa6f9656a229b42aadd0b177d387eb10", - "lock-hash": "0xf5202949800af0b454b2e4806c57da1d0f3ae87f7b9f4b698d9f3b71162ec196", - "mainnet": "ckb1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqmtamce", - "testnet": "ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3" -} -# 2. transfer capacity to the address -ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \ - --to-address ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgxt47sz28w4fhev44z9x6z4twsk9ma8pltzqqufhe3 \ - --capacity 99 --skip-check-to-address - # 0xf993b27a0129f72ec0a889cb016987c3cef00f7819461e51d5755464da6adf1b -# 3. generate the transaction -./target/debug/examples/transfer_from_opentx gen-open-tx \ - --require-first-n 0 \ - --threshold 2 \ - --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ - --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ - --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 98.0 --open-capacity 1.0 \ - --tx-file tx.json -# 4. sign the transaction, this step can sign seperately with each sender-key -./target/debug/examples/transfer_from_opentx sign-open-tx \ - --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ - --tx-file tx.json -# add input, with capacity 100.99799588 -./target/debug/examples/transfer_from_opentx add-input --tx-hash 621077216f3bf7861beacd3cdda44f7a5854454fcd133922b89f0addd0330e6b --index 1 --tx-file tx.json -# add output, capacity is 100.99799588(original) + 1(open capacity) - 0.001(fee) -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 101.99699588 --tx-file tx.json -# sighash sign the new input -./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json -# send the tx -./target/debug/examples/transfer_from_opentx send --tx-file tx.json -# 0x577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 - -########################### multiple opentxes put together ################################# -# 1. build/sign sighash opentx -./target/debug/examples/transfer_from_opentx gen-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 97 --open-capacity 1\ - --tx-file tx-sighash.json -./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --tx-file tx-sighash.json - -# 2. build/sign sighash opentx -./target/debug/examples/transfer_from_opentx gen-open-tx --ethereum-sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 97 --open-capacity 1\ - --tx-file tx-ethereum.json -./target/debug/examples/transfer_from_opentx sign-open-tx --sender-key 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d \ - --tx-file tx-ethereum.json - -# 3. build/sign multisig opentx -./target/debug/examples/transfer_from_opentx gen-open-tx \ - --require-first-n 0 \ - --threshold 2 \ - --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ - --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ - --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 \ - --receiver ckt1qqwmhmsv9cmqhag4qxguaqux05rc4qlyq393vu45dhxrrycyutcl6qgqkwvrdz5w6w2y372508q30rlnl30rzccczqhsaju7 \ - --capacity 97 --open-capacity 1.0 \ - --tx-file tx-multisig.json -./target/debug/examples/transfer_from_opentx sign-open-tx \ - --sender-key 8dadf1939b89919ca74b58fef41c0d4ec70cd6a7b093a0c8ca5b268f93b8181f \ - --sender-key d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc \ - --tx-file tx-multisig.json - -# 4. merge into one transaction -./target/debug/examples/transfer_from_opentx merge-open-tx \ - --in-tx-file tx-sighash.json \ - --in-tx-file tx-ethereum.json \ - --in-tx-file tx-multisig.json \ - --tx-file tx.json -# the other way get the same merge result: -# merge first 2, then merge the last -./target/debug/examples/transfer_from_opentx merge-open-tx \ - --in-tx-file tx-sighash.json \ - --in-tx-file tx-ethereum.json \ - --tx-file tx.json -./target/debug/examples/transfer_from_opentx merge-open-tx \ - --in-tx-file tx.json \ - --in-tx-file tx-multisig.json \ - --tx-file tx.json -# merge last 2, then merge the first -./target/debug/examples/transfer_from_opentx merge-open-tx \ - --in-tx-file tx-ethereum.json \ - --in-tx-file tx-multisig.json \ - --tx-file tx.json -./target/debug/examples/transfer_from_opentx merge-open-tx \ - --in-tx-file tx-sighash.json \ - --in-tx-file tx.json \ - --tx-file tx.json - -# add input, with capacity 101.99699588 -./target/debug/examples/transfer_from_opentx add-input --tx-hash 577101b031d709992af99bd0715172bdb4d2eb7be9f11e84d6fb24ac3e1ac675 --index 1 --tx-file tx.json -# add output, capacity is 101.99699588(original) + 3(1 open capacity each) - 0.001(fee) -./target/debug/examples/transfer_from_opentx add-output --to-address ckt1qyqy68e02pll7qd9m603pqkdr29vw396h6dq50reug --capacity 104.99599588 --tx-file tx.json -# sighash sign the new input -./target/debug/examples/transfer_from_opentx sighash-sign-tx --sender-key 7068b4dc5289353c688e2e67b75207eb5574ba4938091cf5626a4d0f5cc91668 --tx-file tx.json -# send the tx -./target/debug/examples/transfer_from_opentx send --tx-file tx.json -# 0x4fd5d4adfb009a6e342a9e8442ac54989e28ef887b1fec60c3703e4c4d223b39 -*/ const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; From 348b8d9919f47ebbef8c29bf7d6eda24a537ba2e Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 1 Nov 2022 18:00:03 +0800 Subject: [PATCH 15/29] =?UTF-8?q?style:=20=F0=9F=92=84=20make=20code=20mor?= =?UTF-8?q?e=20readable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unlock/opentx/assembler.rs | 12 +++++----- src/unlock/opentx/hasher.rs | 40 ++++++++++++++++++++++------------ src/unlock/opentx/reader.rs | 5 ++--- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/unlock/opentx/assembler.rs b/src/unlock/opentx/assembler.rs index be0b1e1d..31635558 100644 --- a/src/unlock/opentx/assembler.rs +++ b/src/unlock/opentx/assembler.rs @@ -17,23 +17,23 @@ use super::OpenTxError; /// Assemble a transaction from multiple opentransaction, remove duplicate cell deps and header deps. /// Alter base input/output index. pub fn assemble_new_tx( - mut txes: Vec, + mut transactions: Vec, provider: &dyn TransactionDependencyProvider, opentx_code_hash: Byte32, ) -> Result { - if txes.len() == 1 { - return Ok(txes.remove(0)); + if transactions.len() == 1 { + return Ok(transactions.remove(0)); } let mut builder = TransactionView::new_advanced_builder(); let mut cell_deps = HashSet::new(); let mut header_deps = HashSet::new(); let mut base_input_idx = 0usize; let mut base_output_idx = 0usize; - for tx in txes.iter() { + for tx in transactions.iter() { cell_deps.extend(tx.cell_deps()); header_deps.extend(tx.header_deps()); builder = builder.inputs(tx.inputs()); - // handle opentx witness + // Handle opentx witness for (input, witness) in tx.inputs().into_iter().zip(tx.witnesses().into_iter()) { let lock = provider.get_cell(&input.previous_output())?.lock(); let code_hash = lock.code_hash(); @@ -42,7 +42,7 @@ pub fn assemble_new_tx( if args.len() >= 22 && OmniLockFlags::from_bits_truncate(args[21]).contains(OmniLockFlags::OPENTX) { - // parse lock data + // Parse lock data let witness_data = witness.raw_data(); let current_witness: WitnessArgs = WitnessArgs::from_slice(witness_data.as_ref())?; diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 1c447feb..bbd4758e 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -121,7 +121,7 @@ impl OpenTxSigInput { + (((self.arg2 & ARG2_MASK) as u32) << 20) } - /// new OpentxCommand::TxHash OpenTxSigInput, command 0x00 + /// Build new OpentxCommand::TxHash OpenTxSigInput, command 0x00 pub fn new_tx_hash() -> OpenTxSigInput { OpenTxSigInput { cmd: OpentxCommand::TxHash, @@ -129,7 +129,7 @@ impl OpenTxSigInput { arg2: 0, } } - // new OpentxCommand::GroupInputOutputLen OpenTxSigInput, command 0x01 + // Build new OpentxCommand::GroupInputOutputLen OpenTxSigInput, command 0x01 pub fn new_group_input_output_len() -> OpenTxSigInput { OpenTxSigInput { cmd: OpentxCommand::GroupInputOutputLen, @@ -137,23 +137,23 @@ impl OpenTxSigInput { arg2: 0, } } - /// new OpentxCommand::IndexOutput OpenTxSigInput, command 0x11 + /// Build new OpentxCommand::IndexOutput OpenTxSigInput, command 0x11 pub fn new_index_output(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::IndexOutput, arg1, arg2) } - /// new OpentxCommand::OffsetOutput OpenTxSigInput, command 0x12 + /// Build new OpentxCommand::OffsetOutput OpenTxSigInput, command 0x12 pub fn new_offset_output(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::OffsetOutput, arg1, arg2) } - /// new OpentxCommand::IndexInput OpenTxSigInput, command 0x13 + /// Build new OpentxCommand::IndexInput OpenTxSigInput, command 0x13 pub fn new_index_input(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::IndexInput, arg1, arg2) } - /// new OpentxCommand::OffsetInput OpenTxSigInput, command 0x14 + /// Build new OpentxCommand::OffsetInput OpenTxSigInput, command 0x14 pub fn new_offset_input(arg1: u16, arg2: CellMask) -> Result { Self::new_cell_command(OpentxCommand::OffsetInput, arg1, arg2) } - /// new OpenTxSigInput to handle part or the whole input/output cell + /// Build new OpenTxSigInput to handle part or the whole input/output cell pub fn new_cell_command( cmd: OpentxCommand, arg1: u16, @@ -169,18 +169,18 @@ impl OpenTxSigInput { arg2: arg2.bits, }) } - /// new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x15 + /// Build new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x15 pub fn new_cell_input_index(arg1: u16, arg2: InputMask) -> Result { Self::new_input_command(OpentxCommand::CellInputIndex, arg1, arg2) } - //// new OpentxCommand::CellInputOffset OpenTxSigInput, command 0x16 + //// Build new OpentxCommand::CellInputOffset OpenTxSigInput, command 0x16 pub fn new_cell_input_offset( arg1: u16, arg2: InputMask, ) -> Result { Self::new_input_command(OpentxCommand::CellInputOffset, arg1, arg2) } - /// new OpenTxSigInput to hash part or the whole cell input structure + /// Build new OpenTxSigInput to hash part or the whole cell input structure pub fn new_input_command( cmd: OpentxCommand, arg1: u16, @@ -197,7 +197,7 @@ impl OpenTxSigInput { }) } - /// new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x20 + /// Build new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x20 pub fn new_concat_arg1_arg2(arg1: u16, arg2: u16) -> OpenTxSigInput { OpenTxSigInput { cmd: OpentxCommand::ConcatArg1Arg2, @@ -205,7 +205,7 @@ impl OpenTxSigInput { arg2: arg2 & ARG2_MASK, } } - /// new OpentxCommand::End OpenTxSigInput, command 0xF0 + /// Build new OpentxCommand::End OpenTxSigInput, command 0xF0 pub fn new_end() -> OpenTxSigInput { OpenTxSigInput { cmd: OpentxCommand::End, @@ -564,6 +564,12 @@ impl OpentxWitness { witness_data } + /// Generate message for sign. + /// + /// # Arguments + /// * `reader` the read object can fetch data from blockchain. + /// + /// Return a tuple with first one is the message, the second is open transaction data exclude signature in witness. pub fn generate_message(&self, reader: &OpenTxReader) -> Result<(Bytes, Bytes), OpenTxError> { let (is_input, is_output) = (true, false); let (relative_idx, absolute_idx) = (true, false); @@ -640,14 +646,20 @@ impl OpentxWitness { Ok((msg, s_data)) } + /// The byte length of base_input_index, base_output_index, and signature input list in the witness field. pub fn opentx_sig_data_len(&self) -> usize { 4 + 4 + 4 * self.inputs.len() } - pub fn build_opentx_sig(&self, sil_data: Bytes, sig_bytes: Bytes) -> Bytes { + /// Build open transaction signature by combile open_sig_data and real signatures sig_bytes + /// + /// # Arguments + /// * `open_sig_data` open transaction data, include base_input_index, base_output_index, and signature input list. + /// * `sig_bytes` real signature bytes. + pub fn build_opentx_sig(&self, open_sig_data: Bytes, sig_bytes: Bytes) -> Bytes { let mut data = BytesMut::with_capacity(self.opentx_sig_data_len() + sig_bytes.len()); - data.put(sil_data); + data.put(open_sig_data); data.put(sig_bytes); data.freeze() } diff --git a/src/unlock/opentx/reader.rs b/src/unlock/opentx/reader.rs index c0feb4ed..baebcbe0 100644 --- a/src/unlock/opentx/reader.rs +++ b/src/unlock/opentx/reader.rs @@ -147,7 +147,7 @@ impl<'r> OpenTxReader<'r> { Ok(cell) } - /// fetch the hash of the current running transaction + /// Fetch the hash of the current running transaction pub fn tx_hash(&self) -> Byte32 { self.transaction.hash() } @@ -221,8 +221,7 @@ impl<'r> OpenTxReader<'r> { .outputs_data() .get(index) .ok_or(OpenTxError::OutOfBound)?; - // TODO: why split here? - let data = output.as_slice().split_at(4).1.to_vec(); + let data = output.raw_data().to_vec(); if data.is_empty() { Result::Ok([0u8; 32].to_vec()) } else { From 2c7add31726c45160ef8737052d952e2179e3c9e Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Wed, 2 Nov 2022 18:56:48 +0800 Subject: [PATCH 16/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20tests=20abou?= =?UTF-8?q?t=20opentx=20simple=20hash=20transfer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test-data/omni_lock | Bin 107168 -> 111264 bytes src/tests/mod.rs | 1 + src/tests/omni_lock.rs | 11 +-- src/tests/opentx.rs | 183 +++++++++++++++++++++++++++++++++++++ src/tx_builder/transfer.rs | 47 ++++++++++ 5 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 src/tests/opentx.rs diff --git a/src/test-data/omni_lock b/src/test-data/omni_lock index 96c90c33b92bb455ccedbec6b3e31a50cbeeffdd..fffcc75ee768ef9b72dbb23c52b402c862ebc81e 100755 GIT binary patch delta 15392 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 >rxLZDk8(-_A?70Tc9K(5BgRv zDObvERAnFd37s^^^81I?C=yJ zNa2Xt%luYwr3i!TimSO!?up@?mcx!317Yc4DjqiaQB8Mu)y=;@GykK0`9^p5Yqz?)`&)0N;-grBIfSe~ zN%%#(!y_^?8h8}M)=E;UIt%;zm(=4#7DUB9Etk~kL@@8U~ljAgS|h@t&##h q!sG4#`gyLj<0I_ztm<1J75_)|?E>l1M|c`8kok_A_^O_rijJUIRfNDywK#YFN z=#(mL$e=Z^a6tZ>Y55O8QPJLUQ(w)2mafyYCvdWk%21WuEV7P+BI{&Ov^V|T75WGL zq4x!ZHk%UFp!inF^}3djrwbn$I>I``J4~jYez(1XQN2gGT1~#?u|d73l1xo}&@b>k z{fU`kTp+9@U+V=`f<7idP{!@T`%VM%%o+}}IoLLFS0bs(4#D$bFdsc|`G9Yy>TwYq z?W#`P!Ul3eC3E!N7dJjYLD#qHgW`6rZpBS-q^@Fwk9@!-*6`Rc?MlR6@?ca6df+-2NabrH%aM!7IsPfjZnctq( z2h9P3a-ypEyFFNG8!IT)n-fWxp`I;ej{e1K;FSYM(NTVfIeONFR(oZ~m)|^> zH<9>e2+Bn}&C#E&Xth&z#2lS!z-!TD=aHJ^WoawWl(JLkhFdTiw*cWMqZ~@M+uy)p#I=G-oSoHc1 zP`~xS%CMQIQ+d>#Nvj&6IMoIDW_o2(uSP&DlJh*Rd*KD-2nKF1mIM`0*g!LQYa6~Z zWI~Wdlp76V;E$jSzmjqwg;*e`8vd0co^A!!-i?M=Qt9cQXFcWlf2HCKo{Pahr;;Sb z;qi|e@Cw(FJZqyFf1Sa}u49HKGI*u>r_yYQ=cShCIA>qWc408&T~|lzT}?yz2nevD zZ8S}u@+}+_37I!U+UtXOkhWaOA6ItHq%Pe+3@r!0q~={CY#bYJ+R@EZf8^wu$gAyp zJjZ(xzk&_KjcUY2-h1tO>|2SF^`!%L)Mv&EkYCTJI(~JYF7ZiM&_T1zkY6mXb8rT> znLWkYj(iKxnf>w5cNknTDtKps7w?r*tqb7QghTT}T!3!fY5uk)wFR|4djD_1d0 z1{PGct8i_1Kig|yzuYSWi3T79J?tlQ8CJZ_s=ud~EXLd4dY@~?xbdy8`5>lL(`j#- zvT&O{Kf|K!C~Cj-MUMAl9Y6CD>hmG5kURS7)S)zZq1E$muT@3o$`2km$7+#a7O7$ok*WhR%Bg$1QKt?TV)+59v$hdxUct8b7)4QLO(r`6B8OaG*yj+d{+>13%d>lz3&UZo&oxaEWQnM z0p7Yt^qWRb+?D`@cjqA_CPKY;BI8vJah4OPO~TlXxq(_il^-D?^JgHg_Jqnn&KYyg zuQ_)JjrI^tNkpO7#Tjh8DF99CTxcLuWj&0!Nc$x)uI1%{Dy2b1V^?u_$WMUY`T^En z7s6jhrBBD*@9NYwwX~E&rM_m=mPJow zL4hqtAH{>(`zKdzEuedP*)p+FqC*cSPVeEo3%YV>-zC`H5xb6Z@n~IkShng@S}_f^ zr;FbdL(wN5(dvFKFpo~V0|K+%v&E`97+ipd)B~MxAs8*sZz^mn6fUc0i#Klpn%IPJ z)>$C7&xM|=yOVJ9d?f683WV?wL@Sz5W)3|Z24bV=fF3R|-#K5jd%`v5ugjs8A3(e$ z7}3`%A%0&y8$W#t=p@GCj!Uq3o!~Yc)IFkT4d+lV}N%a%}(&Xni3h7;U^fVguF6xY51DgA^ITnygAVr3(0DD1;sm?nM#PHqyJ zvW0>=AV%H+_);)4qa9pWPD7UHy9VO)xxhldYdNHT0}$;%}5E)<*ubd@{A4gN^9)pFvDX3jmEiaS6s5vPxV;!Y!Dfcj~i8b4 zE5w*-D`f@}Z?*ziTNK$K@%RkXg~h7xU_ln8+kw~<4=hX0utDLupX5^Yd8Ua=IxG}0 z^I!{JTP_<2^*Yam66(#&<-H&zerg1w!!*>FS)PM4>j3?0E0pEy$`T*O18Q*M#Ht^l zaD_=?^FBw*oVeOdkvcQk0@2kJT<8q?GUfl}onBYPVefNM*TremkSg!wYPUS>dP}%t3 z_kZAai#lFL<%NA&w7eMzvO=pLLD^6RJOl*dYpW=oj&iPJR{^-)5WOFBqR0$AZy0jR z@yb!4qH;fbS`~dOgH<*MyTy45@ z0o|GcXvH9imojsl6b^&}Phe1aJ@V?2eSsR9kRgRm9L6!R^sPvh9he=FU6+ot*tVJ< z!Pr&Z$@13HiNUBKlTzJQAo_Kn<-0`n6HZn57>hHWa9%mo>k?R)C3??=!n@C;9zF!@ z%<}*95-!I%m_|1-|8>v7&dlik^n^kfvwP;iv^)>hrqK!~NPRCO|FxpyL$17_J4=oX zszN$(ICxwrR-0KYU{=zf3a~D7I7oDiY7zBMxXP^BG(-C1bb&Blwc9{oq|B*TFSTi9 zPRW!u@(&LX z+uwvVIt;nz2!b2PxuI6m$}lAMMgx3qCZdj74rej5Y#9QISK&W`QB#X3YB}1)Bx4fd z;movKb)cXFFJ+5SThw=7q_+^=a|^j+nq-Tp4)t<(a_Kk6VNO1E^g{)?lzs|?-(N@c zNfHQuN_}h)o$iN3yAG}`TSQG9^=ab5*fwb)ozcOmc2TtrYRe7J6<_P+`f_OXV_08E zle)Nd`R!Q9XO^}46-(%)Hm6ZIrZYCN;KTy)COycxMH6(5oScAavuTAZ6n#%45nV>> z3IYp?x5w@lU%QM#G4*~MwinXFU5vcyW12b_T}sc)#px`WbfiQ4$LmN;>VY)1lcUpT zz?{OUY-+=lP1ggwa2}$5Jy7_-cDA_V7_4jY*dgrh1@turFYDyi6?EycBi0$RN;cH8 z-4kv5xPqL+bA7OnKJvI%}d(Q*wO|B^?v_Q0LM!^ML z*+PMJN&GwW726=TY2xwaEP68Ee-aC*Z!@aPTE1Qg+&{*aJ$NW%tI<0DenR)c^`#_MR;#0OU%#`ZVy3-z~`L8#mi^h8# zW=Tx!NP%Gq7M(h>4f9=(DhoY65=~}c^%8;Q5&Bq{2n=dU?{VF@5>sHu{4xqZ)(bAo zan=MuIUv&%mIbP(BSQf#a|hxyJ0v^>v%tKgkFhzg{RuOz8Dm-)oh|0{&+=+j8nWbyXB5Z=bD#VN0XU?L=ZL=b_3QNr_D7GANg4 z)se2Wu#70u$7Zm_QclgH2E#1TE^?M=tC^)K8w@l@218AS0>*dbb=pHgisQCEi0JX^ zy>zwihB@u4y+@X{t%{8b-xDNcEk^O1leeess#vjl2z`1H(gYQ(brItdX13*%v5b+5 zxv&1|QqEWqI*oUG)ML$O~pG%b7BtC966#K zwBzt*9M*RfrI}55Q2*!wY37z-Ly7c-lR3KiDW20mdQ95cE0{4h$~D;F$xcFaJtdN- z6k%ZFk4mCcE6Iy&?PInsIIVxTUD|}(Z+<(LW2|>%41u37O`DK^LVZW_m3*(8)7Iw| zM`AlTg!K*KGNsmahBk?vNhrY}KC%qL-2p@Rew3M=`sf{5@f>Uopx7(WzJcw?r5YF?P*gP^We4ITU?1hQ@C2UmCZ;7b9(DeD68%%<7a zcE}v|+I7K>|AGy~uZpFs+V3GW)6a~769h18%c+Zhj(n ze29|b4bAmE0hOvC+~C!O|u6tp8H)WeZ#w#{wS|>7><9O8`XX9~mPf$=Xry zAAA%Wa~K~%(ni7A{1&F!Dau9U(kS?frUgy6=jxab*~&ZBf+pK@no(cQ!Ab=uDCdka zkI*%faiie`l;-#gd4Duq&Q-;en$d94^p?^>!8YNspin%*2?@O=GEXh*8a)jb-dpPP z!wkhzFoTWH4>w@uqk=MlGq;4~kq}S#>VzGPi|O-x$Y$9r=lHm=Cz?FRhiqbfI@^bQ z!k&lyeTa@dtLMLLD)oe)Bc5Riiv5qtl5y~XwR64Q^v+9gf)XAhaTB2k%SpvV=wbii zmt4Th00x@RIhags6X8#YT$uuGxzm4=6O*AE7x}vB;$*0Vc1gt>TzFY^k(JgF{zsF- z3l89iUg%@z-H}CEtuB^UP*K)RD`rqGDfNZUB+(B#kOi+nexL<`&wGw~GncG-4NkFw z50d9#BO#@)!NOZt=G*9GK4e zL8w4rej538Hmq=t`V8Abo<-uCrlMlDr>?+Up2IXafWfUDJdQ-YoT zsI9$=1u9vfKbrmX7Q*!O^XnIq0z z6JAG_1;FXTO+gJ2mK0owVWD(*j=3hPjr=nJPUPHglBNJS<0T7o2ZmYLsD6L<+7F+x zYk_=h`Wtd%44D%M_rmj}F%Zsv;Wj&KcS|HVQGH*O7Q+Un5BCf*J_z~^UCgX(>di^o zbr*mZx2ceczfTy9ot#Y~@j-AB=ldf$!4O9-VZaKTmv%1>%pjKb595R-r$Ozr-l}nA z*jzXRO+0HRVRK=oF0~c&_H0o>W!vtX$sAcSpSzRv3}$EB>6;rS$&7?w(VG;VB$TD$bIwd9WWI zCtbmC+d!sYER7zzP|}p~(A?QqOm@BwcZ}>=Zm5?Xw=DeI<<_iUR#k(gByPMEY%rJg zRg=MQz`ct=>euqQd7Yez&-HgI14fqry(24+$h&ss#NUupE0sq{^_A95iVP4CQ?9iY=8i!6B? zCdd*A$^(zeiB-X>N2ac~p}_G0vZO0tA@-rr)!Mo7GI==^&h&S0a+%Mx<~M(e3qwLv zPEB!{D{Juwk@(9I=;S04!k$$jQ(V}Sm9ww9%w|vVyGexMu_944m4?Dvq%O$!V6om@ zELHXYF)r@ot0Zg&)cxYB-AX`xoKX zz*+nhS+)baN$?uDnOpi9saXS8qL!4;$=HwJ=z-_PUv&MR?GtCIm}+!{gntCfxaM=j zX)O%nhW|v?u7#mo;VIMiYaxOGooByib|WMbDs6-EJodG&rk@JVb{tHP2@sUQ(0s0` z--^SW_jXmr$UqsV`fCqXi^;9wUlaRvED1ONN`lwHDX8k3`WvUR{?u7J-1cIl{S<*uIK#gBC6p$L z(oKMsfIA##?)1B?{F(Tyhh8LYJskdi&4fm5u%CkY%aal_z4nbW`bzR#tU5C(k-Qa% zrTv`7VFlFqTyN4${Ix|)90ScY=7vK?SwgH#JR>LvBhBRdE#{ikU!&@qUjjhhgJNla|ICOm!C(np=VIks-n(VdxAlW;!ckv}w32S`3f6`2Re8zlE z_f4z2vc1?K#L3bm{G{H8rl`;C3DChnWl%A}Hf1`NAb}(3bcCbleKTca@KwPUa-KID z9vzAspBOC33i9}~{%PZV)$L22hZD;ayqUUmC&$H4zaiK`1!e+=v2+Z$V2Rr|E?#;2 zm$@T=ESNgJ*eLLTyJUdr4N_oBZ1%W#+uOy)4L15(#Y+I4=7FS;cx_=DXw5p^*S;)x z#i14I)onWhcb4o_ed;d__%JqFx#Wh4oY@F>>I9nwNUP$Zz(*^*rP%0^8^c-!yBHhK zhlk?ABvrr1li+(HX0L>We}&#GO{hwolxXfujT2N0*t-O)Bx)wyAK@hLPO+udnZH+% z1_KY{<{|pIJV0}mK*=kWT4IA*hg|5>2iS6<2D#Q~{&}3y_24HooEHTGX0sdV~UPfxeapN?VC zSq^Xcs)P*M4A*e1M2*@kJN#j?e=~gb)pLbDK$^rk*Y3C1y_Ih>H+sx>MdFz=+m ziVX^_qT7LK@Zak5lqxsN>XYVbQ(X9L`Tq;prp8g5>f(ZJS{$`T7hCUmfj{TCcTVfR zn@=&{g0yaroUuB_CRFlDV5}V#R4qETv2u7wJrL(qV&RVUTB=Vf6(!QIL(pj@&<801 z`{Jh14tqGLHS4#&V3voNmO3Z&9*UEyzdrSx!PY_D-7XI;b@aDd_SUuSSm~r#DAjgg8z(NX#e8n*12*d) z$`1BsY9sGg^+7Jew$}2FS~H&&0)W-kGQ9AJVVhF1VYQ9#^^$GAw`v#rp78<9cA`(W z>jM=Q$5JxKYK?E}lipcI7>=d>!%nD@RN&PWcQU;RIaz6QOt+}kfLM{VjIm~5#6<9f#g$!$NDRZL1 z3K`p6R#;y09INH*dD6WbrcP~{e?l3El^X%$wXFaDrnsAC4nY@72J{#$EPW-(^=$fC-tFx@0$ zX)qJj+N(}tJ!V0&*7_5ze8?0geDPxSb83#+e^XoJV=mLh|4psh$dt#ZiB@vWEQ-56 zBRAZ@4A+vb>tsE*IDY=YQ31k(t2eQEWIvbY4~l;w9nqw zO#b?9uOu=v29BK&^^K&NpL&}MuNI2rV&)U95V&-wTV&?c> z7YK=g{}`E@s07;851m}b$VKcJyF|W{7cdLr8%FIpf?We{za6I`8@a+(u_BvjZDRkyA-4?=pl}Aa7e)5hwZW3{3G*%S_^No zyG^Q)lUp`!VSFg1KiIvdP925;A0E}F*m~#24dz-u&TX>_Txw)jtGJOxtd7`oVG>yz z2mgU;N-vY-I5>aUA*Do?9%sJCq!x`^VR>G01QVO>f|$(85A)(?E0!#_ zZ&sV+FZr|5t-g_4Wfk0?iQ@R=zXj)3{af@H{^e^155q}|=!Jz=(H)6Kf&)j&^1gSh zwPnTWt4A>poU|=dUwI~J)Wb&o3P=`7aMIA4lrwrSdr*>zB^4)2rBYa=O73a~an2|ws|WNNuuyq$|IHtJ9DtZcDo^M5-j@sfRO*1>*! zVKTTw^g3oLxO+1*l?%*Nes8^I@Mc~S$aK0iuGmsvl$!n)$Y57Tq)}D5nyg$V5EJG9 zY>$T;i6=`VhLT^O`%`svYgYd8VvBj!(_%Tsu-LMXIdw2Gu#mw;CXC7*-~u#RrekAT zTUQ+H`C8s0t96>6Zah}WY}LwyNwB)hvGaW)l*gj64!Y6E2>E~k zINx+N8SYoU`Kp7`H)Gg`!>(a|8K)e^k!!Z4L};(yw9zUb+Kk(#HQ!We)elv52A6VK6RL+& z0GrX-TrZbQ&!Hs`NKO{KFr`2-NW1>tV1ai)f=zs{nuxYMj0p&4?>KQ!H6!l#najS4 z*A7-X+Xl+HHLlNGwDch<$%YejyAmC+@?D_472v3iIO3oWng=w|JwOYNIAC=))OG|9 z(5`uBz*~WW#16L7{K~ZGodH<=CDfXO?X(}iW2X32?J_)~G^FKvdMz1EF{=fFg_-xW+3#q8*a+fv=Ax=w?fo1;QS%=z0y37_TX!(JDzO~J&g}8-4#dzQm!V+dC+yF&6hFH-{y+9<|>rH#U!6O z9{fh$Qg*9NL2C2h1~`?xRse^x$J_aE+E5cSOXHa2ylceLx|y2SW^EnBq$D3^{*C@_ z0UYJJd&o5xhR9NLOs=E?L)xspgK;FY0D9_J;h|b6CEJ*bQXjQ#TPWpeV~JeU%57G_ z+va`5PU&pei<^Bc!NwTu!n}fA)k{cgwYCBa4jIN9({46M*1jP*R7(5H>yh_UudCiS zeE&2T_2(Yi7AoaM#u5$K59Zy#YG_v%DIXQEwR9vU(mMD#8ys-T()%cu95RkK3ZY7W zto$B^NmVy0|71p6Q56J7+nmwvJ*cRQl#kQ$80H<;l8Jxtt)=x}a~1yapsJ;keJiUz zlF8<3#_ylmntHGaD>}I=#}935l*Y5kAITg&BgCXHHL`cSz($EpyDqh-)cB$ z&lo5porQ4dl(e(w?nGXa)NEak>{#Lvc``CKLNoc#Sk1JaSk1)WW7+or_@YTOUNQ6N zarp|i{Acnlg6^C`ZjzQ_IBax$Yk&XyEr0(SuKnF!*4*EJ>1uy}fobID@GD4M4?=$q za;9JR!wf9~h0TqtV!U`8$ow`WjBO m{r44cvh!;=^?7$%5vlx-?vf(%_-p6~i-`AO%VXzZ==uL%r7w&C diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 73a46a43..59a8884e 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1087,3 +1087,4 @@ fn test_udt_transfer() { pub mod omni_lock; pub mod omni_lock_util; +pub mod opentx; diff --git a/src/tests/omni_lock.rs b/src/tests/omni_lock.rs index cdcd3c73..29e2626b 100644 --- a/src/tests/omni_lock.rs +++ b/src/tests/omni_lock.rs @@ -40,9 +40,9 @@ use rand::Rng; use crate::tx_builder::{unlock_tx, CapacityBalancer, TxBuilder}; -const OMNILOCK_BIN: &[u8] = include_bytes!("../test-data/omni_lock"); +pub const OMNILOCK_BIN: &[u8] = include_bytes!("../test-data/omni_lock"); -fn build_omnilock_script(cfg: &OmniLockConfig) -> Script { +pub fn build_omnilock_script(cfg: &OmniLockConfig) -> Script { let omnilock_data_hash = H256::from(blake2b_256(OMNILOCK_BIN)); Script::new_builder() .code_hash(omnilock_data_hash.pack()) @@ -51,7 +51,7 @@ fn build_omnilock_script(cfg: &OmniLockConfig) -> Script { .build() } -fn build_omnilock_unlockers( +pub fn build_omnilock_unlockers( key: secp256k1::SecretKey, config: OmniLockConfig, unlock_mode: OmniUnlockMode, @@ -117,13 +117,12 @@ fn test_omnilock_simple_hash(cfg: OmniLockConfig) { CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); let mut cell_collector = ctx.to_live_cells_context(); - let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); - let unlockers = build_omnilock_unlockers(account2_key, cfg.clone(), unlock_mode); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account0_key, cfg.clone(), unlock_mode); let mut tx = builder .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); - let unlockers = build_omnilock_unlockers(account2_key, cfg, unlock_mode); let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); assert!(new_locked_groups.is_empty()); tx = new_tx; diff --git a/src/tests/opentx.rs b/src/tests/opentx.rs new file mode 100644 index 00000000..866f9887 --- /dev/null +++ b/src/tests/opentx.rs @@ -0,0 +1,183 @@ +use ckb_jsonrpc_types as json_types; +use std::collections::HashMap; + +use crate::{ + constants::{ONE_CKB, SIGHASH_TYPE_HASH}, + test_util::random_out_point, + tests::{ + build_sighash_script, init_context, + omni_lock::{build_omnilock_script, build_omnilock_unlockers, OMNILOCK_BIN}, + omni_lock_util::generate_rc, + ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, + ACCOUNT3_ARG, ACCOUNT3_KEY, ALWAYS_SUCCESS_BIN, FEE_RATE, SUDT_BIN, + }, + traits::{CellCollector, CellDepResolver, CellQueryOptions, SecpCkbRawKeySigner}, + tx_builder::{ + acp::{AcpTransferBuilder, AcpTransferReceiver}, + balance_tx_capacity, fill_placeholder_witnesses, + omni_lock::OmniLockTransferBuilder, + transfer::{CapacityTransferBuilder, CapacityTransferBuilderWithTransaction}, + udt::{UdtTargetReceiver, UdtTransferBuilder}, + CapacityProvider, TransferAction, + }, + types::xudt_rce_mol::SmtProofEntryVec, + unlock::{ + omni_lock::{AdminConfig, Identity}, + opentx::OpentxWitness, + IdentityFlag, InfoCellData, MultisigConfig, OmniLockAcpConfig, OmniLockConfig, + OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker, + SecpSighashUnlocker, + }, + util::{blake160, keccak160}, + ScriptId, Since, +}; + +use ckb_crypto::secp::{Pubkey, SECP256K1}; +use ckb_hash::blake2b_256; +use ckb_types::{ + bytes::Bytes, + core::{FeeRate, ScriptHashType}, + packed::{Byte32, CellInput, CellOutput, Script, WitnessArgs}, + prelude::*, + H160, H256, +}; +use rand::Rng; + +use crate::tx_builder::{unlock_tx, CapacityBalancer, TxBuilder}; +const ZERO_FEE_RATE: u64 = 0; + +#[test] +fn test_omnilock_transfer_from_sighash() { + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())); + test_omnilock_simple_hash(cfg); +} + +#[test] +fn test_omnilock_transfer_from_ethereum() { + 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())); + test_omnilock_simple_hash(cfg); +} + +/// account0(200) => account0(exchange 199) + open pay 1, +/// account2(100) => account2(101 - transaction fee) +fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { + cfg.set_opentx_mode(); + let unlock_mode = OmniUnlockMode::Normal; + let sender = build_omnilock_script(&cfg); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let ctx = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB)), + (receiver.clone(), Some(100 * ONE_CKB)), + (receiver.clone(), Some(200 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((199 * ONE_CKB).pack()) + .lock(receiver.clone()) + .build(); + let builder = OmniLockTransferBuilder::new_open( + (1 * ONE_CKB).into(), + vec![(output.clone(), Bytes::default())], + cfg.clone(), + None, + ); + let placeholder_witness = cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), ZERO_FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account0_key, cfg.clone(), unlock_mode); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + tx = OmniLockTransferBuilder::remove_open_out(tx); + + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let wit = OpentxWitness::new_sig_all_relative(&tx, Some(salt)).unwrap(); + cfg.set_opentx_input(wit); + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &cfg, + OmniUnlockMode::Normal, + &ctx, + &sender, + ) + .unwrap(); + // config updated, so unlockers must rebuilt. + let unlockers = build_omnilock_unlockers(account0_key, cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert!(new_locked_groups.is_empty()); + tx = new_tx; + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + // use the opentx + + // Build ScriptUnlocker + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account2_key]); + let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone()); + let mut unlockers = HashMap::default(); + unlockers.insert( + sighash_script_id, + Box::new(sighash_unlocker) as Box, + ); + + // Build CapacityBalancer + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(receiver.clone(), placeholder_witness.clone(), 1000); + // // Build the transaction + let query = CellQueryOptions::new_lock(receiver.clone()); + let (inputs, total_capacity) = cell_collector.collect_live_cells(&query, false).unwrap(); + let input = &inputs[0]; + let input_output = &input.out_point; + println!("{:#x} total_capacity: {}", input_output, total_capacity); + // let output = CellOutput::new_builder() + // .lock(receiver.clone()) + // .capacity((100 * ONE_CKB).pack()) + // .build(); + let builder = CapacityTransferBuilderWithTransaction::new( + vec![/*(output.clone(), Bytes::default())*/], + tx, + ); + let (tx, still_locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + assert_eq!(1, still_locked_groups.len()); + + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), receiver); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 2); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tx_builder/transfer.rs b/src/tx_builder/transfer.rs index 7f960e6b..b9d419b6 100644 --- a/src/tx_builder/transfer.rs +++ b/src/tx_builder/transfer.rs @@ -57,3 +57,50 @@ impl TxBuilder for CapacityTransferBuilder { .build()) } } + + +/// It's like CapacityTransferBuilder, except with a predefined transaction, it can be used when an open transaction is avaiable. +pub struct CapacityTransferBuilderWithTransaction { + pub outputs: Vec<(CellOutput, Bytes)>, + pub transaction: TransactionView, +} + +impl CapacityTransferBuilderWithTransaction { + pub fn new(outputs: Vec<(CellOutput, Bytes)>, transaction: TransactionView) -> CapacityTransferBuilderWithTransaction { + CapacityTransferBuilderWithTransaction { outputs, transaction } + } +} + +impl TxBuilder for CapacityTransferBuilderWithTransaction { + fn build_base( + &self, + _cell_collector: &mut dyn CellCollector, + cell_dep_resolver: &dyn CellDepResolver, + _header_dep_resolver: &dyn HeaderDepResolver, + _tx_dep_provider: &dyn TransactionDependencyProvider, + ) -> Result { + #[allow(clippy::mutable_key_type)] + let mut cell_deps = HashSet::new(); + let mut outputs = Vec::new(); + let mut outputs_data = Vec::new(); + for (output, output_data) in &self.outputs { + outputs.push(output.clone()); + outputs_data.push(output_data.pack()); + if let Some(type_script) = output.type_().to_opt() { + let script_id = ScriptId::from(&type_script); + if !script_id.is_type_id() { + let cell_dep = cell_dep_resolver + .resolve(&type_script) + .ok_or(TxBuilderError::ResolveCellDepFailed(type_script))?; + cell_deps.insert(cell_dep); + } + } + } + Ok(self.transaction.as_advanced_builder() + .cell_deps(cell_deps) + .outputs(outputs) + .outputs_data(outputs_data) + .build()) + } +} + From a15938abf8aa7472666128bf0ee48c52d15e092f Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Wed, 2 Nov 2022 19:57:39 +0800 Subject: [PATCH 17/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20test=20about?= =?UTF-8?q?=20opentx=20simple=20multisig=20transfer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/opentx.rs | 141 +++++++++++++++++++++++++++++++++++- src/tx_builder/omni_lock.rs | 41 ++++++++++- src/unlock/omni_lock.rs | 5 ++ 3 files changed, 181 insertions(+), 6 deletions(-) diff --git a/src/tests/opentx.rs b/src/tests/opentx.rs index 866f9887..1125a1b9 100644 --- a/src/tests/opentx.rs +++ b/src/tests/opentx.rs @@ -101,8 +101,6 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { let mut tx = builder .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); - tx = OmniLockTransferBuilder::remove_open_out(tx); - let mut rng = rand::thread_rng(); let salt: u32 = rng.gen(); let wit = OpentxWitness::new_sig_all_relative(&tx, Some(salt)).unwrap(); @@ -172,12 +170,149 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { assert_eq!(tx.inputs().len(), 2); assert_eq!(tx.outputs().len(), 2); assert_eq!(tx.output(0).unwrap(), output); - assert_eq!(tx.output(1).unwrap().lock(), receiver); + let output1 = tx.output(1).unwrap(); + assert_eq!(output1.lock(), receiver); + let receiver_capacity: u64 = output1.capacity().unpack(); + assert!(receiver_capacity - 100 * ONE_CKB < ONE_CKB ); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 2); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +/// multisig(200) => multisig(exchange 199) + open pay 1, locked by account0, account1, account2 +/// account3(400) => account2(401 - transaction fee) +#[test] +fn test_omnilock_transfer_from_multisig() { + let unlock_mode = OmniUnlockMode::Normal; + 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.set_opentx_mode(); + + let sender = build_omnilock_script(&cfg); + let receiver = build_sighash_script(ACCOUNT3_ARG); + + let ctx = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + (receiver.clone(), Some(400 * ONE_CKB)), + (receiver.clone(), Some(500 * ONE_CKB)), + (receiver.clone(), Some(600 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((199 * ONE_CKB).pack()) + .lock(receiver.clone()) + .build(); + let builder = OmniLockTransferBuilder::new_open( + (1 * ONE_CKB).into(), + vec![(output.clone(), Bytes::default())], + cfg.clone(), + None, + ); + let placeholder_witness = cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), ZERO_FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account0_key, cfg.clone(), unlock_mode); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + // add opentx hash data + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let wit = OpentxWitness::new_sig_all_relative(&tx, Some(salt)).unwrap(); + cfg.set_opentx_input(wit); + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &cfg, + OmniUnlockMode::Normal, + &ctx, + &sender, + ) + .unwrap(); + for key in [account0_key, account2_key] { + let unlockers = build_omnilock_unlockers(key, cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert!(new_locked_groups.is_empty()); + tx = new_tx; + } + + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + // use the opentx + + // Build ScriptUnlocker + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account3_key]); + let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone()); + let mut unlockers = HashMap::default(); + unlockers.insert( + sighash_script_id, + Box::new(sighash_unlocker) as Box, + ); + + // Build CapacityBalancer + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(receiver.clone(), placeholder_witness.clone(), 1000); + // // Build the transaction + let query = CellQueryOptions::new_lock(receiver.clone()); + let (inputs, total_capacity) = cell_collector.collect_live_cells(&query, false).unwrap(); + let input = &inputs[0]; + let input_output = &input.out_point; + println!("{:#x} total_capacity: {}", input_output, total_capacity); + // let output = CellOutput::new_builder() + // .lock(receiver.clone()) + // .capacity((100 * ONE_CKB).pack()) + // .build(); + let builder = CapacityTransferBuilderWithTransaction::new( + vec![/*(output.clone(), Bytes::default())*/], + tx, + ); + let (tx, still_locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + assert_eq!(1, still_locked_groups.len()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + let output1 = tx.output(1).unwrap(); + assert_eq!(output1.lock(), receiver); + let receiver_capacity: u64 = output1.capacity().unpack(); + assert!(receiver_capacity - 400 * ONE_CKB < ONE_CKB ); let witnesses = tx .witnesses() .into_iter() .map(|w| w.raw_data()) .collect::>(); assert_eq!(witnesses.len(), 2); + assert_eq!(witnesses[1].len(), placeholder_witness.as_slice().len()); ctx.verify(tx, FEE_RATE).unwrap(); } diff --git a/src/tx_builder/omni_lock.rs b/src/tx_builder/omni_lock.rs index b69a9e9d..d8b027f1 100644 --- a/src/tx_builder/omni_lock.rs +++ b/src/tx_builder/omni_lock.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use ckb_types::{ bytes::Bytes, @@ -8,10 +8,10 @@ use ckb_types::{ H256, }; -use super::{TxBuilder, TxBuilderError}; +use super::{TxBuilder, TxBuilderError, CapacityBalancer, fill_placeholder_witnesses, balance_tx_capacity}; use crate::{ traits::{CellCollector, CellDepResolver, HeaderDepResolver, TransactionDependencyProvider}, - unlock::{omni_lock::ConfigError, OmniLockConfig, OmniUnlockMode}, + unlock::{omni_lock::ConfigError, OmniLockConfig, OmniUnlockMode, ScriptUnlocker}, }; use crate::{types::ScriptId, HumanCapacity}; @@ -206,4 +206,39 @@ impl TxBuilder for OmniLockTransferBuilder { .set_outputs_data(outputs_data) .build()) } + + /// Build balanced transaction that ready to sign: + /// * Build base transaction + /// * Fill placeholder witness for lock script + /// * balance the capacity + fn build_balanced( + &self, + cell_collector: &mut dyn CellCollector, + cell_dep_resolver: &dyn CellDepResolver, + header_dep_resolver: &dyn HeaderDepResolver, + tx_dep_provider: &dyn TransactionDependencyProvider, + balancer: &CapacityBalancer, + unlockers: &HashMap>, + ) -> Result { + let base_tx = self.build_base( + cell_collector, + cell_dep_resolver, + header_dep_resolver, + tx_dep_provider, + )?; + let (tx_filled_witnesses, _) = + fill_placeholder_witnesses(base_tx, tx_dep_provider, unlockers)?; + let mut tx = balance_tx_capacity( + &tx_filled_witnesses, + balancer, + cell_collector, + tx_dep_provider, + cell_dep_resolver, + header_dep_resolver, + )?; + if self.cfg.is_opentx_mode() { + tx = OmniLockTransferBuilder::remove_open_out(tx); + } + Ok(tx) + } } diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 61126698..8642c55c 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -558,6 +558,11 @@ impl OmniLockConfig { self.omni_lock_flags.set(OmniLockFlags::OPENTX, true); } + /// Check if it contains open transaction mode + pub fn is_opentx_mode(&self) -> bool { + self.omni_lock_flags.contains(OmniLockFlags::OPENTX) + } + /// Clear the open transaction input data, and clear OmniLockFlags::OPENTX from omni_lock_flags. pub fn clear_opentx_input(&mut self) { self.omni_lock_flags.set(OmniLockFlags::OPENTX, false); From a3e869cb624ee4c7a736a2f55b327b3e7abc9c26 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 3 Nov 2022 17:53:40 +0800 Subject: [PATCH 18/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Tests=20with=20ind?= =?UTF-8?q?ex=20opentx=20input=20to=20end=20a=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/omni_lock.rs | 2 +- src/tests/opentx.rs | 226 +++++++++++++++++++++++++++++------- src/tx_builder/omni_lock.rs | 6 +- src/tx_builder/transfer.rs | 16 ++- src/unlock/omni_lock.rs | 23 ++++ src/unlock/opentx/hasher.rs | 40 ++++++- src/unlock/signer.rs | 4 + 7 files changed, 264 insertions(+), 53 deletions(-) diff --git a/src/tests/omni_lock.rs b/src/tests/omni_lock.rs index 29e2626b..6961fa53 100644 --- a/src/tests/omni_lock.rs +++ b/src/tests/omni_lock.rs @@ -118,7 +118,7 @@ fn test_omnilock_simple_hash(cfg: OmniLockConfig) { let mut cell_collector = ctx.to_live_cells_context(); let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); - let unlockers = build_omnilock_unlockers(account0_key, cfg.clone(), unlock_mode); + let unlockers = build_omnilock_unlockers(account0_key, cfg, unlock_mode); let mut tx = builder .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); diff --git a/src/tests/opentx.rs b/src/tests/opentx.rs index 1125a1b9..b3661d46 100644 --- a/src/tests/opentx.rs +++ b/src/tests/opentx.rs @@ -3,43 +3,29 @@ use std::collections::HashMap; use crate::{ constants::{ONE_CKB, SIGHASH_TYPE_HASH}, - test_util::random_out_point, tests::{ build_sighash_script, init_context, omni_lock::{build_omnilock_script, build_omnilock_unlockers, OMNILOCK_BIN}, - omni_lock_util::generate_rc, - ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, - ACCOUNT3_ARG, ACCOUNT3_KEY, ALWAYS_SUCCESS_BIN, FEE_RATE, SUDT_BIN, + ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT2_ARG, ACCOUNT2_KEY, ACCOUNT3_ARG, + ACCOUNT3_KEY, FEE_RATE, }, - traits::{CellCollector, CellDepResolver, CellQueryOptions, SecpCkbRawKeySigner}, + traits::{CellCollector, CellQueryOptions, SecpCkbRawKeySigner}, tx_builder::{ - acp::{AcpTransferBuilder, AcpTransferReceiver}, - balance_tx_capacity, fill_placeholder_witnesses, - omni_lock::OmniLockTransferBuilder, - transfer::{CapacityTransferBuilder, CapacityTransferBuilderWithTransaction}, - udt::{UdtTargetReceiver, UdtTransferBuilder}, - CapacityProvider, TransferAction, + omni_lock::OmniLockTransferBuilder, transfer::CapacityTransferBuilderWithTransaction, }, - types::xudt_rce_mol::SmtProofEntryVec, unlock::{ - omni_lock::{AdminConfig, Identity}, - opentx::OpentxWitness, - IdentityFlag, InfoCellData, MultisigConfig, OmniLockAcpConfig, OmniLockConfig, - OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker, + opentx::OpentxWitness, MultisigConfig, OmniLockConfig, OmniUnlockMode, ScriptUnlocker, SecpSighashUnlocker, }, util::{blake160, keccak160}, - ScriptId, Since, + ScriptId, }; use ckb_crypto::secp::{Pubkey, SECP256K1}; -use ckb_hash::blake2b_256; use ckb_types::{ bytes::Bytes, - core::{FeeRate, ScriptHashType}, - packed::{Byte32, CellInput, CellOutput, Script, WitnessArgs}, + packed::{CellOutput, WitnessArgs}, prelude::*, - H160, H256, }; use rand::Rng; @@ -83,17 +69,16 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { let output = CellOutput::new_builder() .capacity((199 * ONE_CKB).pack()) - .lock(receiver.clone()) + .lock(sender.clone()) .build(); let builder = OmniLockTransferBuilder::new_open( - (1 * ONE_CKB).into(), + ONE_CKB.into(), vec![(output.clone(), Bytes::default())], cfg.clone(), None, ); let placeholder_witness = cfg.placeholder_witness(unlock_mode).unwrap(); - let balancer = - CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), ZERO_FEE_RATE); + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, ZERO_FEE_RATE); let mut cell_collector = ctx.to_live_cells_context(); let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); @@ -139,8 +124,7 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { let placeholder_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); - let balancer = - CapacityBalancer::new_simple(receiver.clone(), placeholder_witness.clone(), 1000); + let balancer = CapacityBalancer::new_simple(receiver.clone(), placeholder_witness, 1000); // // Build the transaction let query = CellQueryOptions::new_lock(receiver.clone()); let (inputs, total_capacity) = cell_collector.collect_live_cells(&query, false).unwrap(); @@ -173,13 +157,8 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { let output1 = tx.output(1).unwrap(); assert_eq!(output1.lock(), receiver); let receiver_capacity: u64 = output1.capacity().unpack(); - assert!(receiver_capacity - 100 * ONE_CKB < ONE_CKB ); - let witnesses = tx - .witnesses() - .into_iter() - .map(|w| w.raw_data()) - .collect::>(); - assert_eq!(witnesses.len(), 2); + assert!(receiver_capacity - 100 * ONE_CKB < ONE_CKB); + assert_eq!(tx.witnesses().len(), 2); ctx.verify(tx, FEE_RATE).unwrap(); } @@ -213,17 +192,16 @@ fn test_omnilock_transfer_from_multisig() { let output = CellOutput::new_builder() .capacity((199 * ONE_CKB).pack()) - .lock(receiver.clone()) + .lock(sender.clone()) .build(); let builder = OmniLockTransferBuilder::new_open( - (1 * ONE_CKB).into(), + ONE_CKB.into(), vec![(output.clone(), Bytes::default())], cfg.clone(), None, ); let placeholder_witness = cfg.placeholder_witness(unlock_mode).unwrap(); - let balancer = - CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), ZERO_FEE_RATE); + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, ZERO_FEE_RATE); let mut cell_collector = ctx.to_live_cells_context(); let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); @@ -306,7 +284,7 @@ fn test_omnilock_transfer_from_multisig() { let output1 = tx.output(1).unwrap(); assert_eq!(output1.lock(), receiver); let receiver_capacity: u64 = output1.capacity().unpack(); - assert!(receiver_capacity - 400 * ONE_CKB < ONE_CKB ); + assert!(receiver_capacity - 400 * ONE_CKB < ONE_CKB); let witnesses = tx .witnesses() .into_iter() @@ -316,3 +294,173 @@ fn test_omnilock_transfer_from_multisig() { assert_eq!(witnesses[1].len(), placeholder_witness.as_slice().len()); ctx.verify(tx, FEE_RATE).unwrap(); } + +#[test] +fn test_omnilock_transfer_from_sighash_absolute_from_start() { + test_omnilock_transfer_from_sighash_absolute(true); +} +#[test] +fn test_omnilock_transfer_from_sighash_absolute_self() { + test_omnilock_transfer_from_sighash_absolute(false); +} +fn test_omnilock_transfer_from_sighash_absolute(from_start: bool) { + let cfgs: Vec = [ACCOUNT0_KEY, ACCOUNT2_KEY] + .iter() + .map(|key| { + let priv_key = secp256k1::SecretKey::from_slice(key.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &priv_key); + OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())) + }) + .collect(); + test_omnilock_simple_hash_absolute(cfgs[0].clone(), cfgs[1].clone(), from_start); +} + +#[test] +fn test_omnilock_transfer_from_ethereum_absolute_from_start() { + test_omnilock_transfer_from_ethereum_absolute(true); +} +#[test] +fn test_omnilock_transfer_from_ethereum_absolute_from_self() { + test_omnilock_transfer_from_ethereum_absolute(false); +} +fn test_omnilock_transfer_from_ethereum_absolute(from_start: bool) { + let cfgs: Vec = [ACCOUNT0_KEY, ACCOUNT2_KEY] + .iter() + .map(|key| { + let priv_key = secp256k1::SecretKey::from_slice(key.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &priv_key); + OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())) + }) + .collect(); + test_omnilock_simple_hash_absolute(cfgs[0].clone(), cfgs[1].clone(), from_start); +} + +/// account0(200) => account0(exchange 199) + open pay 1, +/// account2(100) => account2(101 - transaction fee) +fn test_omnilock_simple_hash_absolute( + mut sender_cfg: OmniLockConfig, + mut receiver_cfg: OmniLockConfig, + from_start: bool, +) { + sender_cfg.set_opentx_mode(); + receiver_cfg.set_opentx_mode(); + let unlock_mode = OmniUnlockMode::Normal; + let sender = build_omnilock_script(&sender_cfg); + let receiver = build_omnilock_script(&receiver_cfg); + + let ctx = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB)), + (receiver.clone(), Some(100 * ONE_CKB)), + (receiver.clone(), Some(200 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((199 * ONE_CKB).pack()) + .lock(sender.clone()) + .build(); + let builder = OmniLockTransferBuilder::new_open( + (ONE_CKB).into(), + vec![(output.clone(), Bytes::default())], + sender_cfg.clone(), + None, + ); + let placeholder_witness = sender_cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, ZERO_FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account0_key, sender_cfg.clone(), unlock_mode); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let wit = OpentxWitness::new_sig_all_absolute(&tx, Some(salt)).unwrap(); + sender_cfg.set_opentx_input(wit); + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &sender_cfg, + OmniUnlockMode::Normal, + &ctx, + &sender, + ) + .unwrap(); + // config updated, so unlockers must rebuilt. + let unlockers = build_omnilock_unlockers(account0_key, sender_cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert!(new_locked_groups.is_empty()); + tx = new_tx; + + // use the opentx + let opentx_input_len = tx.inputs().len(); + let opentx_output_len = tx.outputs().len(); + receiver_cfg.set_opentx_reserve_bytes_by_commands(20); + // Build ScriptUnlocker + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account2_key, receiver_cfg.clone(), unlock_mode); + + // Build CapacityBalancer + let placeholder_witness = receiver_cfg.placeholder_witness(unlock_mode).unwrap(); + // why + 100? After update openwitness input list, will need tens of bytes more, if not +100, after update, should calculate adjust the fee again. + // If adjust the transaction fee later, the exchange may mot be enough to maintain the minimal capacity. + let balancer = CapacityBalancer::new_simple(receiver.clone(), placeholder_witness, FEE_RATE); + + let builder = CapacityTransferBuilderWithTransaction::new( + vec![/*(output.clone(), Bytes::default())*/], + tx, + ); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + assert_eq!(opentx_input_len + 1, tx.inputs().len()); + assert_eq!(opentx_output_len + 1, tx.outputs().len()); + + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let mut wit = if from_start { + OpentxWitness::new_sig_all_absolute(&tx, Some(salt)) + } else { + OpentxWitness::new_sig_to_end_absolute(&tx, Some(salt), opentx_input_len, opentx_output_len) + } + .unwrap(); //OpentxWitness::new_sig_all_absolute(&tx, Some(salt)).unwrap(); + wit.add_tx_hash_input(); + receiver_cfg.set_opentx_input(wit); + + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &receiver_cfg, + OmniUnlockMode::Normal, + &ctx, + &receiver, + ) + .unwrap(); + + // config updated, so unlockers must rebuilt. + let unlockers = build_omnilock_unlockers(account2_key, receiver_cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + + assert_eq!(1, new_locked_groups.len()); + tx = new_tx; + + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + let output1 = tx.output(1).unwrap(); + assert_eq!(output1.lock(), receiver); + let receiver_capacity: u64 = output1.capacity().unpack(); + assert!(receiver_capacity - 100 * ONE_CKB < ONE_CKB); + assert_eq!(tx.witnesses().len(), 2); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tx_builder/omni_lock.rs b/src/tx_builder/omni_lock.rs index d8b027f1..d8cbc808 100644 --- a/src/tx_builder/omni_lock.rs +++ b/src/tx_builder/omni_lock.rs @@ -1,4 +1,4 @@ -use std::collections::{HashSet, HashMap}; +use std::collections::{HashMap, HashSet}; use ckb_types::{ bytes::Bytes, @@ -8,7 +8,9 @@ use ckb_types::{ H256, }; -use super::{TxBuilder, TxBuilderError, CapacityBalancer, fill_placeholder_witnesses, balance_tx_capacity}; +use super::{ + balance_tx_capacity, fill_placeholder_witnesses, CapacityBalancer, TxBuilder, TxBuilderError, +}; use crate::{ traits::{CellCollector, CellDepResolver, HeaderDepResolver, TransactionDependencyProvider}, unlock::{omni_lock::ConfigError, OmniLockConfig, OmniUnlockMode, ScriptUnlocker}, diff --git a/src/tx_builder/transfer.rs b/src/tx_builder/transfer.rs index b9d419b6..2f884f56 100644 --- a/src/tx_builder/transfer.rs +++ b/src/tx_builder/transfer.rs @@ -58,7 +58,6 @@ impl TxBuilder for CapacityTransferBuilder { } } - /// It's like CapacityTransferBuilder, except with a predefined transaction, it can be used when an open transaction is avaiable. pub struct CapacityTransferBuilderWithTransaction { pub outputs: Vec<(CellOutput, Bytes)>, @@ -66,8 +65,14 @@ pub struct CapacityTransferBuilderWithTransaction { } impl CapacityTransferBuilderWithTransaction { - pub fn new(outputs: Vec<(CellOutput, Bytes)>, transaction: TransactionView) -> CapacityTransferBuilderWithTransaction { - CapacityTransferBuilderWithTransaction { outputs, transaction } + pub fn new( + outputs: Vec<(CellOutput, Bytes)>, + transaction: TransactionView, + ) -> CapacityTransferBuilderWithTransaction { + CapacityTransferBuilderWithTransaction { + outputs, + transaction, + } } } @@ -96,11 +101,12 @@ impl TxBuilder for CapacityTransferBuilderWithTransaction { } } } - Ok(self.transaction.as_advanced_builder() + Ok(self + .transaction + .as_advanced_builder() .cell_deps(cell_deps) .outputs(outputs) .outputs_data(outputs_data) .build()) } } - diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 8642c55c..86ba6af3 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -425,6 +425,8 @@ pub struct OmniLockConfig { /// open tx config opentx_input: Option, + /// When do placeholder_witness_lock, opentx_input will be used if it's not None, or this field is used to reserve more capacity. + opentx_reserve_bytes: u32, } impl OmniLockConfig { @@ -449,6 +451,7 @@ impl OmniLockConfig { time_lock_config: None, info_cell: None, opentx_input: None, + opentx_reserve_bytes: 0, } } /// Create an ethereum algorithm omnilock with pubkey @@ -496,6 +499,7 @@ impl OmniLockConfig { time_lock_config: None, info_cell: None, opentx_input: None, + opentx_reserve_bytes: 0, } } @@ -573,6 +577,22 @@ impl OmniLockConfig { self.opentx_input.as_ref() } + /// Set opentx reserve bytes for placeholder witness. + /// # Arguments + /// * `bytes` number of bytes to reserve, the minimual should be 12, and be multiple of 4. + pub fn set_opentx_reserve_bytes(&mut self, bytes: u32) { + self.opentx_reserve_bytes = bytes; + } + + /// Set opentx reserve bytes by possible command numbers. + /// + /// If all data are hashed, the command number verifies depend on input/output numbers, + /// if random salt is used for OpenTxSigInput::new_concat_arg1_arg2, salt bigger than 0xFFFFFF will be 2 commands, + /// salt <= 0xFFFFFF will be one 1 command. + pub fn set_opentx_reserve_bytes_by_commands(&mut self, commands: u32) { + self.set_opentx_reserve_bytes(4 + 4 + 4 * commands); + } + pub fn id(&self) -> &Identity { &self.id } @@ -687,6 +707,9 @@ impl OmniLockConfig { let mut buf = BytesMut::new(); if let Some(optx) = self.opentx_input.as_ref() { buf.extend_from_slice(&optx.to_witness_data()); + } else if self.is_opentx_mode() { + let new_len = buf.len() + self.opentx_reserve_bytes as usize; + buf.resize(new_len, 0u8); } let mut builder = match self.id.flag { IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => { diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index bbd4758e..1c0ca9c3 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -169,7 +169,7 @@ impl OpenTxSigInput { arg2: arg2.bits, }) } - /// Build new OpentxCommand::ConcatArg1Arg2 OpenTxSigInput, command 0x15 + /// Build new OpentxCommand::CellInputIndex OpenTxSigInput, command 0x15 pub fn new_cell_input_index(arg1: u16, arg2: InputMask) -> Result { Self::new_input_command(OpentxCommand::CellInputIndex, arg1, arg2) } @@ -368,7 +368,7 @@ impl OpentxWitness { /// Build new OpentxWitness which will sign all data. /// - /// It will first generate the TxHash(0x00), GroupInputOutputLen(0x01), + /// It will first generate the GroupInputOutputLen(0x01), /// then iterate the inputs to generate the relative index OpenTxSigInput with all CellMask on, and InputMask on, /// then iterate each output to generate the relative index OpenTxSigInput with all CellMask on. /// @@ -485,10 +485,7 @@ impl OpentxWitness { base_output_index: usize, end_output_index: usize, ) -> Result { - let mut inputs = vec![ - OpenTxSigInput::new_tx_hash(), - OpenTxSigInput::new_group_input_output_len(), - ]; + let mut inputs = vec![OpenTxSigInput::new_group_input_output_len()]; let start_input_idx = base_input_index; let end_input_idx = @@ -544,6 +541,37 @@ impl OpentxWitness { inputs, )) } + /// Same to `new_sig_range_absolute`, except end_input_index and end_output_index are all usize::MAX, + /// which will be changed to the length of inputs/outputs list. + pub fn new_sig_to_end_absolute( + transaction: &TransactionView, + salt: Option, + base_input_index: usize, + base_output_index: usize, + ) -> Result { + Self::new_sig_range_absolute( + transaction, + salt, + base_input_index, + usize::MAX, + base_output_index, + usize::MAX, + ) + } + + /// Same to `new_sig_to_end_absolute`, except base_input_index and base_output_index are all 0 + pub fn new_sig_all_absolute( + transaction: &TransactionView, + salt: Option, + ) -> Result { + Self::new_sig_range_absolute(transaction, salt, 0, usize::MAX, 0, usize::MAX) + } + + /// Add OpentxCommand to the first of the input list, this should only be called if the opentx is ready to sent. + /// If the open transaction will be add input/output, this function should not be called. + pub fn add_tx_hash_input(&mut self) { + self.inputs.insert(0, OpenTxSigInput::new_tx_hash()); + } pub fn set_base_input_index(&mut self, index: u32) { self.base_input_index = index; diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 00c644dc..6c50a71b 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -728,6 +728,10 @@ impl ScriptSigner for OmniLockScriptSigner { return false; } + if args != self.config.build_args() { + return false; + } + if self.unlock_mode == OmniUnlockMode::Admin { if let Some(admin_config) = self.config.get_admin_config() { if args.len() < 54 { From a2045517e30dc9b2da455676dfaec5023339162a Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Mon, 7 Nov 2022 08:55:14 +0800 Subject: [PATCH 19/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20test=20about?= =?UTF-8?q?=20multisig=20opentx=20test=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/opentx.rs | 155 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) diff --git a/src/tests/opentx.rs b/src/tests/opentx.rs index b3661d46..d0b47d00 100644 --- a/src/tests/opentx.rs +++ b/src/tests/opentx.rs @@ -6,8 +6,8 @@ use crate::{ tests::{ build_sighash_script, init_context, omni_lock::{build_omnilock_script, build_omnilock_unlockers, OMNILOCK_BIN}, - ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT2_ARG, ACCOUNT2_KEY, ACCOUNT3_ARG, - ACCOUNT3_KEY, FEE_RATE, + ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, + ACCOUNT3_ARG, ACCOUNT3_KEY, FEE_RATE, }, traits::{CellCollector, CellQueryOptions, SecpCkbRawKeySigner}, tx_builder::{ @@ -420,7 +420,6 @@ fn test_omnilock_simple_hash_absolute( assert_eq!(opentx_input_len + 1, tx.inputs().len()); assert_eq!(opentx_output_len + 1, tx.outputs().len()); - let mut rng = rand::thread_rng(); let salt: u32 = rng.gen(); let mut wit = if from_start { OpentxWitness::new_sig_all_absolute(&tx, Some(salt)) @@ -464,3 +463,153 @@ fn test_omnilock_simple_hash_absolute( assert_eq!(tx.witnesses().len(), 2); ctx.verify(tx, FEE_RATE).unwrap(); } +#[test] +fn test_omnilock_transfer_from_multisig_absolute_from_start() { + test_omnilock_transfer_from_multisig_absolute(true); +} + +#[test] +fn test_omnilock_transfer_from_multisig_absolute_from_self() { + test_omnilock_transfer_from_multisig_absolute(false); +} + +/// multisig(200) => multisig(exchange 199) + open pay 1, locked by account0, account1, account2 +/// account3(400) => account2(401 - transaction fee) +fn test_omnilock_transfer_from_multisig_absolute(from_start: bool) { + let unlock_mode = OmniUnlockMode::Normal; + 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 sender_cfg = OmniLockConfig::new_multisig(multi_cfg); + sender_cfg.set_opentx_mode(); + + let sender = build_omnilock_script(&sender_cfg); + let lock_args = vec![ + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ACCOUNT3_ARG.clone(), + ]; + let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + let mut receiver_cfg = OmniLockConfig::new_multisig(multi_cfg); + receiver_cfg.set_opentx_mode(); + let receiver = build_omnilock_script(&receiver_cfg); + + let ctx = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + (receiver.clone(), Some(400 * ONE_CKB)), + (receiver.clone(), Some(500 * ONE_CKB)), + (receiver.clone(), Some(600 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((199 * ONE_CKB).pack()) + .lock(sender.clone()) + .build(); + let builder = OmniLockTransferBuilder::new_open( + ONE_CKB.into(), + vec![(output.clone(), Bytes::default())], + sender_cfg.clone(), + None, + ); + let placeholder_witness = sender_cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, ZERO_FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account0_key, sender_cfg.clone(), unlock_mode); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + // add opentx hash data + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let wit = OpentxWitness::new_sig_all_absolute(&tx, Some(salt)).unwrap(); + sender_cfg.set_opentx_input(wit); + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &sender_cfg, + OmniUnlockMode::Normal, + &ctx, + &sender, + ) + .unwrap(); + for key in [account0_key, account2_key] { + let unlockers = build_omnilock_unlockers(key, sender_cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert!(new_locked_groups.is_empty()); + tx = new_tx; + } + + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + // use the opentx + let opentx_input_len = tx.inputs().len(); + let opentx_output_len = tx.outputs().len(); + receiver_cfg.set_opentx_reserve_bytes_by_commands(20); + // Build ScriptUnlocker + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(account1_key, receiver_cfg.clone(), unlock_mode); + // Build CapacityBalancer + let placeholder_witness = receiver_cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = CapacityBalancer::new_simple(receiver.clone(), placeholder_witness, FEE_RATE); + + let builder = CapacityTransferBuilderWithTransaction::new(vec![], tx); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + assert_eq!(opentx_input_len + 1, tx.inputs().len()); + assert_eq!(opentx_output_len + 1, tx.outputs().len()); + + let salt: u32 = rng.gen(); + let mut wit = if from_start { + OpentxWitness::new_sig_all_absolute(&tx, Some(salt)) + } else { + OpentxWitness::new_sig_to_end_absolute(&tx, Some(salt), opentx_input_len, opentx_output_len) + } + .unwrap(); //OpentxWitness::new_sig_all_absolute(&tx, Some(salt)).unwrap(); + wit.add_tx_hash_input(); + receiver_cfg.set_opentx_input(wit); + + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &receiver_cfg, + OmniUnlockMode::Normal, + &ctx, + &receiver, + ) + .unwrap(); + + for key in [account1_key, account3_key] { + let unlockers = build_omnilock_unlockers(key, receiver_cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert_eq!(1, new_locked_groups.len()); + tx = new_tx; + } + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + let output1 = tx.output(1).unwrap(); + assert_eq!(output1.lock(), receiver); + let receiver_capacity: u64 = output1.capacity().unpack(); + assert!(receiver_capacity - 400 * ONE_CKB < ONE_CKB); + + assert_eq!(tx.witnesses().len(), 2); + ctx.verify(tx, FEE_RATE).unwrap(); +} From 56cd5f281d7bab94d429958d11d905f9bfbc8b9b Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 8 Nov 2022 15:56:32 +0800 Subject: [PATCH 20/29] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20test=20about?= =?UTF-8?q?=20open=20transaction=20buy=20udt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/mod.rs | 6 +- src/tests/omni_lock.rs | 6 +- src/tests/opentx.rs | 226 +++++++++++++++++++++++++++++------- src/tx_builder/omni_lock.rs | 4 +- src/tx_builder/transfer.rs | 64 +++------- src/tx_builder/udt/mod.rs | 68 ++++++++++- 6 files changed, 262 insertions(+), 112 deletions(-) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 59a8884e..bce3ff3e 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1029,11 +1029,7 @@ fn test_udt_transfer() { ctx.add_live_cell(receiver_input, receiver_output.clone(), receiver_data, None); let udt_receiver = UdtTargetReceiver::new(TransferAction::Update, receiver_acp_lock, 300); - let builder = UdtTransferBuilder { - type_script, - sender: sender.clone(), - receivers: vec![udt_receiver], - }; + let builder = UdtTransferBuilder::new(type_script, sender.clone(), vec![udt_receiver]); let placeholder_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); diff --git a/src/tests/omni_lock.rs b/src/tests/omni_lock.rs index 6961fa53..1fce0292 100644 --- a/src/tests/omni_lock.rs +++ b/src/tests/omni_lock.rs @@ -1171,11 +1171,7 @@ fn test_omnilock_udt_transfer() { ctx.add_live_cell(receiver_input, receiver_output.clone(), receiver_data, None); let udt_receiver = UdtTargetReceiver::new(TransferAction::Update, receiver_acp_lock, 300); - let builder = UdtTransferBuilder { - type_script, - sender: sender.clone(), - receivers: vec![udt_receiver], - }; + let builder = UdtTransferBuilder::new(type_script, sender.clone(), vec![udt_receiver]); let placeholder_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); diff --git a/src/tests/opentx.rs b/src/tests/opentx.rs index d0b47d00..bdbdd646 100644 --- a/src/tests/opentx.rs +++ b/src/tests/opentx.rs @@ -1,17 +1,20 @@ +use ckb_hash::blake2b_256; use ckb_jsonrpc_types as json_types; use std::collections::HashMap; use crate::{ constants::{ONE_CKB, SIGHASH_TYPE_HASH}, + test_util::random_out_point, tests::{ build_sighash_script, init_context, omni_lock::{build_omnilock_script, build_omnilock_unlockers, OMNILOCK_BIN}, ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, - ACCOUNT3_ARG, ACCOUNT3_KEY, FEE_RATE, + ACCOUNT3_ARG, ACCOUNT3_KEY, FEE_RATE, SUDT_BIN, }, traits::{CellCollector, CellQueryOptions, SecpCkbRawKeySigner}, tx_builder::{ - omni_lock::OmniLockTransferBuilder, transfer::CapacityTransferBuilderWithTransaction, + omni_lock::OmniLockTransferBuilder, transfer::CapacityTransferBuilder, + udt::UdtTransferBuilder, }, unlock::{ opentx::OpentxWitness, MultisigConfig, OmniLockConfig, OmniUnlockMode, ScriptUnlocker, @@ -24,35 +27,40 @@ use crate::{ use ckb_crypto::secp::{Pubkey, SECP256K1}; use ckb_types::{ bytes::Bytes, - packed::{CellOutput, WitnessArgs}, + core::{Capacity, ScriptHashType}, + packed::{CellInput, CellOutput, Script, WitnessArgs}, prelude::*, + H160, H256, }; use rand::Rng; use crate::tx_builder::{unlock_tx, CapacityBalancer, TxBuilder}; const ZERO_FEE_RATE: u64 = 0; -#[test] -fn test_omnilock_transfer_from_sighash() { - let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) +fn build_simple_config(key: H256) -> OmniLockConfig { + let priv_key = secp256k1::SecretKey::from_slice(key.as_bytes()) .map_err(|err| format!("invalid sender secret key: {}", err)) .unwrap(); - let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); - let cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())); - test_omnilock_simple_hash(cfg); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &priv_key); + OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())) +} +#[test] +fn test_opentx_pay_from_sighash() { + let cfg = build_simple_config(ACCOUNT0_KEY); + test_opentx_pay_simple_hash(cfg); } #[test] -fn test_omnilock_transfer_from_ethereum() { +fn test_opentx_pay_from_ethereum() { 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())); - test_omnilock_simple_hash(cfg); + test_opentx_pay_simple_hash(cfg); } /// account0(200) => account0(exchange 199) + open pay 1, /// account2(100) => account2(101 - transaction fee) -fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { +fn test_opentx_pay_simple_hash(mut cfg: OmniLockConfig) { cfg.set_opentx_mode(); let unlock_mode = OmniUnlockMode::Normal; let sender = build_omnilock_script(&cfg); @@ -135,7 +143,7 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { // .lock(receiver.clone()) // .capacity((100 * ONE_CKB).pack()) // .build(); - let builder = CapacityTransferBuilderWithTransaction::new( + let builder = CapacityTransferBuilder::new_with_transaction( vec![/*(output.clone(), Bytes::default())*/], tx, ); @@ -165,7 +173,7 @@ fn test_omnilock_simple_hash(mut cfg: OmniLockConfig) { /// multisig(200) => multisig(exchange 199) + open pay 1, locked by account0, account1, account2 /// account3(400) => account2(401 - transaction fee) #[test] -fn test_omnilock_transfer_from_multisig() { +fn test_opentx_pay_from_multisig() { let unlock_mode = OmniUnlockMode::Normal; let lock_args = vec![ ACCOUNT0_ARG.clone(), @@ -263,7 +271,7 @@ fn test_omnilock_transfer_from_multisig() { // .lock(receiver.clone()) // .capacity((100 * ONE_CKB).pack()) // .build(); - let builder = CapacityTransferBuilderWithTransaction::new( + let builder = CapacityTransferBuilder::new_with_transaction( vec![/*(output.clone(), Bytes::default())*/], tx, ); @@ -296,36 +304,28 @@ fn test_omnilock_transfer_from_multisig() { } #[test] -fn test_omnilock_transfer_from_sighash_absolute_from_start() { - test_omnilock_transfer_from_sighash_absolute(true); +fn test_opentx_pay_receive_sighash_absolute_from_start() { + test_opentx_pay_receive_sighash_absolute(true); } #[test] -fn test_omnilock_transfer_from_sighash_absolute_self() { - test_omnilock_transfer_from_sighash_absolute(false); +fn test_opentx_pay_receive_sighash_absolute_self() { + test_opentx_pay_receive_sighash_absolute(false); } -fn test_omnilock_transfer_from_sighash_absolute(from_start: bool) { - let cfgs: Vec = [ACCOUNT0_KEY, ACCOUNT2_KEY] - .iter() - .map(|key| { - let priv_key = secp256k1::SecretKey::from_slice(key.as_bytes()) - .map_err(|err| format!("invalid sender secret key: {}", err)) - .unwrap(); - let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &priv_key); - OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())) - }) - .collect(); - test_omnilock_simple_hash_absolute(cfgs[0].clone(), cfgs[1].clone(), from_start); +fn test_opentx_pay_receive_sighash_absolute(from_start: bool) { + let sender_cfg = build_simple_config(ACCOUNT0_KEY); + let receiver_cfg = build_simple_config(ACCOUNT2_KEY); + test_opentx_pay_receive_simple_hash_absolute(sender_cfg, receiver_cfg, from_start); } #[test] -fn test_omnilock_transfer_from_ethereum_absolute_from_start() { - test_omnilock_transfer_from_ethereum_absolute(true); +fn test_opentx_pay_receive_ethereum_absolute_from_start() { + test_opentx_pay_receive_ethereum_absolute(true); } #[test] -fn test_omnilock_transfer_from_ethereum_absolute_from_self() { - test_omnilock_transfer_from_ethereum_absolute(false); +fn test_opentx_pay_receive_ethereum_absolute_from_self() { + test_opentx_pay_receive_ethereum_absolute(false); } -fn test_omnilock_transfer_from_ethereum_absolute(from_start: bool) { +fn test_opentx_pay_receive_ethereum_absolute(from_start: bool) { let cfgs: Vec = [ACCOUNT0_KEY, ACCOUNT2_KEY] .iter() .map(|key| { @@ -334,12 +334,12 @@ fn test_omnilock_transfer_from_ethereum_absolute(from_start: bool) { OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())) }) .collect(); - test_omnilock_simple_hash_absolute(cfgs[0].clone(), cfgs[1].clone(), from_start); + test_opentx_pay_receive_simple_hash_absolute(cfgs[0].clone(), cfgs[1].clone(), from_start); } /// account0(200) => account0(exchange 199) + open pay 1, /// account2(100) => account2(101 - transaction fee) -fn test_omnilock_simple_hash_absolute( +fn test_opentx_pay_receive_simple_hash_absolute( mut sender_cfg: OmniLockConfig, mut receiver_cfg: OmniLockConfig, from_start: bool, @@ -410,7 +410,7 @@ fn test_omnilock_simple_hash_absolute( // If adjust the transaction fee later, the exchange may mot be enough to maintain the minimal capacity. let balancer = CapacityBalancer::new_simple(receiver.clone(), placeholder_witness, FEE_RATE); - let builder = CapacityTransferBuilderWithTransaction::new( + let builder = CapacityTransferBuilder::new_with_transaction( vec![/*(output.clone(), Bytes::default())*/], tx, ); @@ -464,18 +464,18 @@ fn test_omnilock_simple_hash_absolute( ctx.verify(tx, FEE_RATE).unwrap(); } #[test] -fn test_omnilock_transfer_from_multisig_absolute_from_start() { - test_omnilock_transfer_from_multisig_absolute(true); +fn test_opentx_pay_receive_multisig_absolute_from_start() { + test_opentx_pay_receive_multisig_absolute(true); } #[test] -fn test_omnilock_transfer_from_multisig_absolute_from_self() { - test_omnilock_transfer_from_multisig_absolute(false); +fn test_opentx_pay_receive_multisig_absolute_from_self() { + test_opentx_pay_receive_multisig_absolute(false); } /// multisig(200) => multisig(exchange 199) + open pay 1, locked by account0, account1, account2 /// account3(400) => account2(401 - transaction fee) -fn test_omnilock_transfer_from_multisig_absolute(from_start: bool) { +fn test_opentx_pay_receive_multisig_absolute(from_start: bool) { let unlock_mode = OmniUnlockMode::Normal; let lock_args = vec![ ACCOUNT0_ARG.clone(), @@ -564,7 +564,7 @@ fn test_omnilock_transfer_from_multisig_absolute(from_start: bool) { let placeholder_witness = receiver_cfg.placeholder_witness(unlock_mode).unwrap(); let balancer = CapacityBalancer::new_simple(receiver.clone(), placeholder_witness, FEE_RATE); - let builder = CapacityTransferBuilderWithTransaction::new(vec![], tx); + let builder = CapacityTransferBuilder::new_with_transaction(vec![], tx); let mut tx = builder .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); @@ -613,3 +613,139 @@ fn test_omnilock_transfer_from_multisig_absolute(from_start: bool) { assert_eq!(tx.witnesses().len(), 2); ctx.verify(tx, FEE_RATE).unwrap(); } + +#[test] +fn test_opentx_udt_open_buy() { + // ACCOUNT1(alice) will spend 50.01 CKB with fee to buy 1,000,000 SUDT + // ACCOUNT2(bob) collect the 50 CKB with the transfer 1,000,000 SUDT + let unlock_mode = OmniUnlockMode::Normal; + let mut alice_cfg = build_simple_config(ACCOUNT1_KEY); + alice_cfg.set_opentx_mode(); + let alice = build_omnilock_script(&alice_cfg); + let bob = build_sighash_script(ACCOUNT2_ARG); + + let mut ctx = init_context( + vec![(OMNILOCK_BIN, true), (SUDT_BIN, false)], + vec![ + (alice.clone(), Some(300 * ONE_CKB)), + (bob.clone(), Some(400 * ONE_CKB)), + ], + ); + let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); + let owner = build_sighash_script(H160::default()); + let type_script = Script::new_builder() + .code_hash(sudt_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(owner.calc_script_hash().as_bytes().pack()) + .build(); + let sudt_input = CellInput::new(random_out_point(), 0); + let sudt_output = CellOutput::new_builder() + .capacity(ONE_CKB.pack()) + .lock(bob.clone()) + .type_(Some(type_script.clone()).pack()) + .build(); + let sudt_capacity = sudt_output + .occupied_capacity(Capacity::bytes(16).unwrap()) + .unwrap() + .as_u64(); + println!("sudt_capacity: {}", sudt_capacity); + let sudt_output = sudt_output + .as_builder() + .capacity(sudt_capacity.pack()) + .build(); + let sudt_data = Bytes::from(1_000_000u128.to_le_bytes().to_vec()); + ctx.add_live_cell(sudt_input, sudt_output, sudt_data.clone(), None); + + let fee = 100_0000u64; + // build opentx alice's input + let builder = OmniLockTransferBuilder::new_open( + (50 * ONE_CKB + sudt_capacity + fee).into(), + vec![], + alice_cfg.clone(), + None, + ); + let placeholder_witness = alice_cfg.placeholder_witness(unlock_mode).unwrap(); + let balancer = CapacityBalancer::new_simple(alice.clone(), placeholder_witness, ZERO_FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let alice_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let unlockers = build_omnilock_unlockers(alice_key, alice_cfg.clone(), unlock_mode); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + // add sudt output + let sudt_output = CellOutput::new_builder() + .capacity((sudt_capacity).pack()) + .lock(alice.clone()) + .type_(Some(type_script.clone()).pack()) + .build(); + tx = tx + .as_advanced_builder() + .output(sudt_output.clone()) + .output_data(sudt_data.pack()) + .build(); + // update opentx input list + let mut rng = rand::thread_rng(); + let salt: u32 = rng.gen(); + let wit = OpentxWitness::new_sig_all_relative(&tx, Some(salt)).unwrap(); + alice_cfg.set_opentx_input(wit); + tx = OmniLockTransferBuilder::update_opentx_witness( + tx, + &alice_cfg, + OmniUnlockMode::Normal, + &ctx, + &alice, + ) + .unwrap(); + // config updated, so unlockers must rebuilt. + let unlockers = build_omnilock_unlockers(alice_key, alice_cfg.clone(), unlock_mode); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + assert!(new_locked_groups.is_empty()); + tx = new_tx; + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + // use opentx + let builder = UdtTransferBuilder::new_with_transaction(type_script, bob.clone(), vec![], tx); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = CapacityBalancer::new_simple(bob, placeholder_witness, FEE_RATE); + + let bob_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![bob_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + println!( + "> tx: {}", + serde_json::to_string_pretty(&json_types::TransactionView::from(tx.clone())).unwrap() + ); + assert_eq!(locked_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 3); + assert_eq!(tx.inputs().len(), 3); + let outputs = tx.outputs().into_iter().collect::>(); + assert_eq!(outputs.len(), 4); + assert_eq!(outputs[1], sudt_output); + let expected_outputs_data = vec![ + Bytes::from(1_000_000u128.to_le_bytes().to_vec()), + Bytes::from(0u128.to_le_bytes().to_vec()), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data[1..3], expected_outputs_data); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tx_builder/omni_lock.rs b/src/tx_builder/omni_lock.rs index d8cbc808..57206503 100644 --- a/src/tx_builder/omni_lock.rs +++ b/src/tx_builder/omni_lock.rs @@ -40,12 +40,12 @@ impl OmniLockTransferBuilder { /// Create an OmniLockTransferBuilder with open out in the output list. /// After the transaction built, the open out should be removed. pub fn new_open( - open_capacity: HumanCapacity, + open_out_capacity: HumanCapacity, mut outputs: Vec<(CellOutput, Bytes)>, cfg: OmniLockConfig, rce_cells: Option>, ) -> OmniLockTransferBuilder { - let tmp_out = OmniLockTransferBuilder::build_tmp_open_out(open_capacity); + let tmp_out = OmniLockTransferBuilder::build_tmp_open_out(open_out_capacity); outputs.push((tmp_out, Bytes::default())); OmniLockTransferBuilder { outputs, diff --git a/src/tx_builder/transfer.rs b/src/tx_builder/transfer.rs index 2f884f56..f1053ca0 100644 --- a/src/tx_builder/transfer.rs +++ b/src/tx_builder/transfer.rs @@ -17,66 +17,29 @@ use crate::types::ScriptId; /// will resolve the type script's cell_dep if given. pub struct CapacityTransferBuilder { pub outputs: Vec<(CellOutput, Bytes)>, + pub transaction: Option, } impl CapacityTransferBuilder { pub fn new(outputs: Vec<(CellOutput, Bytes)>) -> CapacityTransferBuilder { - CapacityTransferBuilder { outputs } - } -} - -impl TxBuilder for CapacityTransferBuilder { - fn build_base( - &self, - _cell_collector: &mut dyn CellCollector, - cell_dep_resolver: &dyn CellDepResolver, - _header_dep_resolver: &dyn HeaderDepResolver, - _tx_dep_provider: &dyn TransactionDependencyProvider, - ) -> Result { - #[allow(clippy::mutable_key_type)] - let mut cell_deps = HashSet::new(); - let mut outputs = Vec::new(); - let mut outputs_data = Vec::new(); - for (output, output_data) in &self.outputs { - outputs.push(output.clone()); - outputs_data.push(output_data.pack()); - if let Some(type_script) = output.type_().to_opt() { - let script_id = ScriptId::from(&type_script); - if !script_id.is_type_id() { - let cell_dep = cell_dep_resolver - .resolve(&type_script) - .ok_or(TxBuilderError::ResolveCellDepFailed(type_script))?; - cell_deps.insert(cell_dep); - } - } + CapacityTransferBuilder { + outputs, + transaction: None, } - Ok(TransactionBuilder::default() - .set_cell_deps(cell_deps.into_iter().collect()) - .set_outputs(outputs) - .set_outputs_data(outputs_data) - .build()) } -} -/// It's like CapacityTransferBuilder, except with a predefined transaction, it can be used when an open transaction is avaiable. -pub struct CapacityTransferBuilderWithTransaction { - pub outputs: Vec<(CellOutput, Bytes)>, - pub transaction: TransactionView, -} - -impl CapacityTransferBuilderWithTransaction { - pub fn new( + pub fn new_with_transaction( outputs: Vec<(CellOutput, Bytes)>, transaction: TransactionView, - ) -> CapacityTransferBuilderWithTransaction { - CapacityTransferBuilderWithTransaction { + ) -> CapacityTransferBuilder { + CapacityTransferBuilder { outputs, - transaction, + transaction: Some(transaction), } } } -impl TxBuilder for CapacityTransferBuilderWithTransaction { +impl TxBuilder for CapacityTransferBuilder { fn build_base( &self, _cell_collector: &mut dyn CellCollector, @@ -101,9 +64,12 @@ impl TxBuilder for CapacityTransferBuilderWithTransaction { } } } - Ok(self - .transaction - .as_advanced_builder() + let builder = if let Some(tx) = self.transaction.as_ref() { + tx.as_advanced_builder() + } else { + TransactionBuilder::default() + }; + Ok(builder .cell_deps(cell_deps) .outputs(outputs) .outputs_data(outputs_data) diff --git a/src/tx_builder/udt/mod.rs b/src/tx_builder/udt/mod.rs index 4c600f1e..93af71d2 100644 --- a/src/tx_builder/udt/mod.rs +++ b/src/tx_builder/udt/mod.rs @@ -265,6 +265,57 @@ pub struct UdtTransferBuilder { /// The transfer receivers pub receivers: Vec, + + /// The exist transaction + pub transaction: Option, +} + +impl UdtTransferBuilder { + pub fn new(type_script: Script, sender: Script, receivers: Vec) -> Self { + UdtTransferBuilder { + type_script, + sender, + receivers, + transaction: None, + } + } + + pub fn new_with_transaction( + type_script: Script, + sender: Script, + receivers: Vec, + transaction: TransactionView, + ) -> Self { + UdtTransferBuilder { + type_script, + sender, + receivers, + transaction: Some(transaction), + } + } + + fn tx_output_amount(&self) -> u128 { + if self.transaction.is_none() { + return 0; + } + let tx = self.transaction.as_ref().unwrap(); + tx.outputs_with_data_iter() + .filter_map(|(output, data)| { + if data.len() >= 16 + && output.type_().is_some() + && output.type_().to_opt().unwrap() == self.type_script + // && output.lock() == self.sender + { + let mut amount_bytes = [0u8; 16]; + amount_bytes.copy_from_slice(&data.as_ref()[0..16]); + let amount = u128::from_le_bytes(amount_bytes); + Some(amount) + } else { + None + } + }) + .sum() + } } impl TxBuilder for UdtTransferBuilder { @@ -302,6 +353,7 @@ impl TxBuilder for UdtTransferBuilder { amount_bytes.copy_from_slice(&sender_cell.output_data.as_ref()[0..16]); let input_total = u128::from_le_bytes(amount_bytes); let output_total: u128 = self.receivers.iter().map(|receiver| receiver.amount).sum(); + let output_total = output_total + self.tx_output_amount(); if input_total < output_total { return Err(TxBuilderError::Other(anyhow!( "sender udt amount not enough, expected at least: {}, actual: {}", @@ -334,12 +386,16 @@ impl TxBuilder for UdtTransferBuilder { outputs.push(output); outputs_data.push(output_data.pack()); } - - Ok(TransactionBuilder::default() - .set_cell_deps(cell_deps.into_iter().collect()) - .set_inputs(inputs) - .set_outputs(outputs) - .set_outputs_data(outputs_data) + let builder = if let Some(tx) = self.transaction.as_ref() { + tx.as_advanced_builder() + } else { + TransactionBuilder::default() + }; + Ok(builder + .cell_deps(cell_deps) + .inputs(inputs) + .outputs(outputs) + .outputs_data(outputs_data) .build()) } } From 840f9d7bdb17462acd25f16e0633e0c63c533b65 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 8 Nov 2022 20:27:26 +0800 Subject: [PATCH 21/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20compile=20err?= =?UTF-8?q?or=20after=20rebase=20master=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index d2dc0439..47b2df93 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -266,7 +266,7 @@ fn main() -> Result<(), Box> { Commands::SignOpenTx(args) => { let tx_info: TxInfo = serde_json::from_slice(&fs::read(&args.tx_file)?)?; let tx = Transaction::from(tx_info.tx.inner).into_view(); - let keys = args + let keys: Vec = args .sender_key .iter() .map(|sender_key| { @@ -276,7 +276,7 @@ fn main() -> Result<(), Box> { }) .collect(); if tx_info.omnilock_config.is_pubkey_hash() || tx_info.omnilock_config.is_ethereum() { - for key in &keys { + for (i, key) in keys.iter().enumerate() { let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, key); let hash160 = match tx_info.omnilock_config.id().flag() { IdentityFlag::PubkeyHash => { @@ -288,7 +288,11 @@ fn main() -> Result<(), Box> { _ => unreachable!(), }; if tx_info.omnilock_config.id().auth_content().as_bytes() != hash160 { - return Err(format!("key {:#x} is not in omnilock config", key).into()); + return Err(format!( + "key {:#x} is not in omnilock config", + args.sender_key[i] + ) + .into()); } } } From 65c3c625b638fd53c3f45c61cf2afff9c194420f Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Fri, 18 Nov 2022 19:46:07 +0800 Subject: [PATCH 22/29] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Add=20Err=20for?= =?UTF-8?q?=20not=20configure=20open=20transaction=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unlock/opentx/mod.rs | 4 ++-- src/unlock/signer.rs | 34 +++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs index cac1bc99..46fc39f4 100644 --- a/src/unlock/opentx/mod.rs +++ b/src/unlock/opentx/mod.rs @@ -30,8 +30,8 @@ pub enum OpenTxError { #[error(transparent)] VerificationError(#[from] VerificationError), - #[error("witness field not exist")] - WitnessMissing, + #[error("Open transaction input list not configured.")] + InputListConfigMissing, #[error("lock field of witness not exist")] WitnessLockMissing, #[error("signature not exist")] diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 6c50a71b..b5a8c66d 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -562,9 +562,15 @@ impl OmniLockScriptSigner { let zero_lock = self.config.zero_lock(self.unlock_mode)?; let zero_lock_len = zero_lock.len(); - let (message, open_sig_data) = if let Some(opentx_wit) = self.config.get_opentx_input() { - let reader = OpenTxReader::new(&tx_new, tx_dep_provider, script_group)?; - opentx_wit.generate_message(&reader)? + let (message, open_sig_data) = if self.config.is_opentx_mode() { + if let Some(opentx_wit) = self.config.get_opentx_input() { + let reader = OpenTxReader::new(&tx_new, tx_dep_provider, script_group)?; + opentx_wit.generate_message(&reader)? + } else { + return Err(ScriptSignError::OpenTxError( + OpenTxError::InputListConfigMissing, + )); + } } else { ( generate_message(&tx_new, script_group, zero_lock)?, @@ -692,9 +698,14 @@ impl OmniLockScriptSigner { } else { WitnessArgs::from_slice(witness_data.as_ref())? }; - - if let Some(opentx_wit) = self.config.get_opentx_input() { - signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + if self.config.is_opentx_mode() { + if let Some(opentx_wit) = self.config.get_opentx_input() { + signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + } else { + return Err(ScriptSignError::OpenTxError( + OpenTxError::InputListConfigMissing, + )); + } } let lock = Self::build_witness_lock(current_witness.lock(), signature)?; current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); @@ -830,9 +841,14 @@ impl ScriptSigner for OmniLockScriptSigner { } else { WitnessArgs::from_slice(witness_data.as_ref())? }; - - if let Some(opentx_wit) = self.config.get_opentx_input() { - signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + if self.config.is_opentx_mode() { + if let Some(opentx_wit) = self.config.get_opentx_input() { + signature = opentx_wit.build_opentx_sig(open_sig_data, signature); + } else { + return Err(ScriptSignError::OpenTxError( + OpenTxError::InputListConfigMissing, + )); + } } let lock = Self::build_witness_lock(current_witness.lock(), signature)?; From 65750b87113c8c6a76595a5ccd80772ab8d6714a Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Fri, 18 Nov 2022 20:01:38 +0800 Subject: [PATCH 23/29] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Remove=20unnecess?= =?UTF-8?q?ary=20output=20and=20add=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.md | 2 -- examples/transfer_from_opentx.rs | 8 ++++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/transfer_from_opentx.md b/examples/transfer_from_opentx.md index 65560347..88530ae5 100644 --- a/examples/transfer_from_opentx.md +++ b/examples/transfer_from_opentx.md @@ -59,8 +59,6 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d ``` output: ```json -pubkey:"038d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a" -pubkey:"048d3cfceea4f9c2e76c5c4f5e99aec74c26d6ac894648b5700a0b71f91f9b5c2a26b16aac1d5753e56849ea83bf795eb8b06f0b6f4e5ed7b8caca720595458039" { "lock-arg": "0x01cf2485c76aff1f2b4464edf04a1c8045068cf7e010", "lock-hash": "0x057dcd204f26621ef49346ed77d2bdbf3069b83a5ef0a2b52be5299a93507cf6", diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 47b2df93..794363af 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -35,6 +35,10 @@ use clap::{Args, Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, error::Error as StdErr, fs, path::PathBuf}; +// The transaction hash i deployed my omnilock script with open transaction function on my test environment. +// You should not use this hash to find your open transaction code_hash at any circumstances, +// or you will lost your CKB for ever, or not make the example work. +// You should replace this hash with your own transaction hash or provide one with command line parameter for this example. const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; const OPENTX_TX_IDX: &str = "0"; @@ -438,8 +442,8 @@ fn build_omnilock_addr(args: &BuildOmniLockAddrArgs) -> Result<(), Box Date: Mon, 21 Nov 2022 10:49:31 +0800 Subject: [PATCH 24/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Handle=20opentx=20s?= =?UTF-8?q?criptgroup=20for=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unlock/opentx/assembler.rs | 43 ++++++++++++++++++++++++++++++---- src/unlock/opentx/mod.rs | 6 +++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/unlock/opentx/assembler.rs b/src/unlock/opentx/assembler.rs index 31635558..b0d02eec 100644 --- a/src/unlock/opentx/assembler.rs +++ b/src/unlock/opentx/assembler.rs @@ -1,5 +1,4 @@ use anyhow::anyhow; - use std::{cmp::Ordering, collections::HashSet, convert::TryFrom}; use ckb_types::{ @@ -9,11 +8,27 @@ use ckb_types::{ prelude::*, }; -use crate::types::omni_lock::OmniLockWitnessLock; use crate::{traits::TransactionDependencyProvider, unlock::omni_lock::OmniLockFlags}; +use crate::{ + tx_builder::{gen_script_groups, ScriptGroups}, + types::omni_lock::OmniLockWitnessLock, +}; use super::OpenTxError; +/// Check if different +fn check_script_groups(group_vec: &[ScriptGroups]) -> Result<(), OpenTxError> { + let mut keys = HashSet::new(); + for group in group_vec.iter() { + let len = keys.len(); + keys.extend(group.lock_groups.keys().clone()); + if len + group.lock_groups.len() > keys.len() { + return Err(OpenTxError::SameLockInDifferentOpenTx); + } + } + Ok(()) +} + /// Assemble a transaction from multiple opentransaction, remove duplicate cell deps and header deps. /// Alter base input/output index. pub fn assemble_new_tx( @@ -29,21 +44,33 @@ pub fn assemble_new_tx( let mut header_deps = HashSet::new(); let mut base_input_idx = 0usize; let mut base_output_idx = 0usize; + let mut base_input_cap = 0usize; + let mut base_output_cap = 0usize; + let group_vec: Result, _> = transactions + .iter() + .map(|tx| gen_script_groups(tx, provider)) + .collect(); + let group_vec = group_vec?; + check_script_groups(&group_vec)?; for tx in transactions.iter() { cell_deps.extend(tx.cell_deps()); header_deps.extend(tx.header_deps()); builder = builder.inputs(tx.inputs()); + base_input_cap += tx.inputs().len(); + base_output_cap += tx.outputs().len(); // Handle opentx witness for (input, witness) in tx.inputs().into_iter().zip(tx.witnesses().into_iter()) { let lock = provider.get_cell(&input.previous_output())?.lock(); let code_hash = lock.code_hash(); - if code_hash.cmp(&opentx_code_hash) == Ordering::Equal { + // empty witness should be in a script group + if !witness.is_empty() && code_hash.cmp(&opentx_code_hash) == Ordering::Equal { let args = &lock.args().raw_data(); - if args.len() >= 22 + let witness_data = witness.raw_data(); + if witness_data.len() > 8 // sizeof base_input + sizeof base_output + && args.len() >= 22 && OmniLockFlags::from_bits_truncate(args[21]).contains(OmniLockFlags::OPENTX) { // Parse lock data - let witness_data = witness.raw_data(); let current_witness: WitnessArgs = WitnessArgs::from_slice(witness_data.as_ref())?; let lock_field = current_witness @@ -64,11 +91,17 @@ pub fn assemble_new_tx( tmp.copy_from_slice(&data[0..4]); let this_base_input_idx = u32::from_le_bytes(tmp) + u32::try_from(base_input_idx).map_err(|e| anyhow!(e))?; + if this_base_input_idx as usize > base_input_cap { + return Err(OpenTxError::BaseInputIndexOverFlow); + } data[0..4].copy_from_slice(&this_base_input_idx.to_le_bytes()); tmp.copy_from_slice(&data[4..8]); let this_base_output_idx = u32::from_le_bytes(tmp) + u32::try_from(base_output_idx).map_err(|e| anyhow!(e))?; + if this_base_output_idx as usize > base_output_cap { + return Err(OpenTxError::BaseOutputIndexOverFlow); + } data[4..8].copy_from_slice(&this_base_output_idx.to_le_bytes()); let omnilock_witnesslock = omnilock_witnesslock diff --git a/src/unlock/opentx/mod.rs b/src/unlock/opentx/mod.rs index 46fc39f4..bcdb68c3 100644 --- a/src/unlock/opentx/mod.rs +++ b/src/unlock/opentx/mod.rs @@ -36,6 +36,12 @@ pub enum OpenTxError { WitnessLockMissing, #[error("signature not exist")] SignatureMissing, + #[error("Cells in different partital open transaction can not be merged")] + SameLockInDifferentOpenTx, + #[error("Base input index is overflow")] + BaseInputIndexOverFlow, + #[error("Base output index is overflow")] + BaseOutputIndexOverFlow, #[error(transparent)] Other(#[from] anyhow::Error), } From 860aa195946b6ec2bc6531f8fc9fe015b570c73b Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Wed, 30 Nov 2022 20:15:31 +0800 Subject: [PATCH 25/29] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Fix=20document=20?= =?UTF-8?q?typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/transfer_from_opentx.md b/examples/transfer_from_opentx.md index 88530ae5..b076b231 100644 --- a/examples/transfer_from_opentx.md +++ b/examples/transfer_from_opentx.md @@ -1,7 +1,7 @@ This document is about how to use the transfer_from_opentx example to do open transaction operation. All the addresses and keys are all in my development local node, you should not use in the production environment. -# Singhash open transaction example +# Sighash open transaction example 1. Build an opentx address ```bash ./target/debug/examples/transfer_from_opentx build --receiver ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 @@ -109,7 +109,7 @@ ckb-cli wallet transfer --from-account 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d --threshold 2 \ --sighash-address ckt1qyqt8xpk328d89zgl928nsgh3lelch33vvvq5u3024 \ --sighash-address ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37 \ - --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 14:03:11 + --sighash-address ckt1qyqywrwdchjyqeysjegpzw38fvandtktdhrs0zaxl4 ``` The output: ```json From 40ed79da9164cf380fd8603b9e478658c3ea3bce Mon Sep 17 00:00:00 2001 From: EthanYuan Date: Wed, 7 Dec 2022 17:10:52 +0800 Subject: [PATCH 26/29] use last version for open tx. --- src/test-data/omni_lock | Bin 111264 -> 111264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/test-data/omni_lock b/src/test-data/omni_lock index fffcc75ee768ef9b72dbb23c52b402c862ebc81e..9689bbaff038e24dd808ce7503839fb3c72554b4 100755 GIT binary patch delta 29 lcmZ4Rlx@LNwuUW?Y4Ou<#WV6uFN$Z(V4S-Bc0A+JAOOL-3|;^L delta 29 lcmZ4Rlx@LNwuUW?Y4Ou<#WV6uFN$Z(VC>y~JD%}q5CFk~3|0UD From 3c1503a33e09eb9d6e13d0f6a6f3c810c184e002 Mon Sep 17 00:00:00 2001 From: EthanYuan Date: Wed, 7 Dec 2022 17:33:20 +0800 Subject: [PATCH 27/29] follow the Omni lock CellMask definition adjust --- src/unlock/opentx/hasher.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/unlock/opentx/hasher.rs b/src/unlock/opentx/hasher.rs index 1c0ca9c3..7d986d14 100644 --- a/src/unlock/opentx/hasher.rs +++ b/src/unlock/opentx/hasher.rs @@ -82,9 +82,9 @@ bitflags! { /// Cell data const CELL_DATA = 0x80; /// Lock script hash - const TYPE_SCRIPT_HASH = 0x100; + const LOCK_SCRIPT_HASH = 0x100; /// Type script hash - const LOCK_SCRIPT_HASH = 0x200; + const TYPE_SCRIPT_HASH = 0x200; /// The whole cell const WHOLE_CELL = 0x400; } @@ -274,6 +274,12 @@ impl OpenTxSigInput { cache.update(data.as_slice()); } + if cell_mask.contains(CellMask::LOCK_SCRIPT_HASH) { + let cell = reader.get_cell(index, is_input)?; + let hash = cell.lock().calc_script_hash(); + cache.update(hash.as_slice()); + } + if cell_mask.contains(CellMask::TYPE_SCRIPT_HASH) { let cell = reader.get_cell(index, is_input)?; if let Some(script) = cell.type_().to_opt() { @@ -282,12 +288,6 @@ impl OpenTxSigInput { } } - if cell_mask.contains(CellMask::LOCK_SCRIPT_HASH) { - let cell = reader.get_cell(index, is_input)?; - let hash = cell.lock().calc_script_hash(); - cache.update(hash.as_slice()); - } - if cell_mask.contains(CellMask::WHOLE_CELL) { let data = reader.load_cell(index, source)?; cache.update(data.as_slice()); From 379c84c0993045130dd4e9442c051589c4e60525 Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Thu, 8 Dec 2022 18:23:35 +0800 Subject: [PATCH 28/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Use=20modified=20b?= =?UTF-8?q?alancer=20to=20replace=20tmp=20opentx=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/transfer_from_opentx.rs | 1 - src/tx_builder/mod.rs | 39 +++++++++++++++-- src/tx_builder/omni_lock.rs | 74 ++++---------------------------- 3 files changed, 45 insertions(+), 69 deletions(-) diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 794363af..058cb982 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -576,7 +576,6 @@ fn build_open_tx( &header_dep_resolver, )?; - let tx = OmniLockTransferBuilder::remove_open_out(tx); let wit = OpentxWitness::new_sig_all_relative(&tx, Some(0xdeadbeef)).unwrap(); omnilock_config.set_opentx_input(wit); let tx = OmniLockTransferBuilder::update_opentx_witness( diff --git a/src/tx_builder/mod.rs b/src/tx_builder/mod.rs index ae26f0f8..4981f52e 100644 --- a/src/tx_builder/mod.rs +++ b/src/tx_builder/mod.rs @@ -164,6 +164,14 @@ pub fn tx_fee( tx: TransactionView, tx_dep_provider: &dyn TransactionDependencyProvider, header_dep_resolver: &dyn HeaderDepResolver, +) -> Result { + tx_fee_with_open(tx, tx_dep_provider, header_dep_resolver, 0) +} +pub fn tx_fee_with_open( + tx: TransactionView, + tx_dep_provider: &dyn TransactionDependencyProvider, + header_dep_resolver: &dyn HeaderDepResolver, + open_capacity: u64, ) -> Result { let mut input_total: u64 = 0; for input in tx.inputs() { @@ -218,7 +226,7 @@ pub fn tx_fee( }; input_total += capacity; } - let output_total = tx.outputs_capacity()?.as_u64(); + let output_total = tx.outputs_capacity()?.as_u64() + open_capacity; #[allow(clippy::unnecessary_lazy_evaluations)] input_total .checked_sub(output_total) @@ -359,6 +367,26 @@ pub fn balance_tx_capacity( tx_dep_provider: &dyn TransactionDependencyProvider, cell_dep_resolver: &dyn CellDepResolver, header_dep_resolver: &dyn HeaderDepResolver, +) -> Result { + balance_tx_capacity_with_open( + tx, + balancer, + cell_collector, + tx_dep_provider, + cell_dep_resolver, + header_dep_resolver, + 0, + ) +} +/// Fill more inputs to balance the transaction capacity +pub fn balance_tx_capacity_with_open( + tx: &TransactionView, + balancer: &CapacityBalancer, + cell_collector: &mut dyn CellCollector, + tx_dep_provider: &dyn TransactionDependencyProvider, + cell_dep_resolver: &dyn CellDepResolver, + header_dep_resolver: &dyn HeaderDepResolver, + open_capacity: u64, ) -> Result { let capacity_provider = &balancer.capacity_provider; if capacity_provider.lock_scripts.is_empty() { @@ -403,6 +431,7 @@ pub fn balance_tx_capacity( let cell = tx_dep_provider.get_cell(&input.previous_output())?; if cell.lock() == *lock_script { has_provider = true; + break; } } while tx.witnesses().item_count() + witnesses.len() @@ -430,8 +459,12 @@ pub fn balance_tx_capacity( let tx_size = new_tx.data().as_reader().serialized_size_in_block(); let min_fee = balancer.fee_rate.fee(tx_size).as_u64(); let mut need_more_capacity = 1; - let fee_result: Result = - tx_fee(new_tx.clone(), tx_dep_provider, header_dep_resolver); + let fee_result: Result = tx_fee_with_open( + new_tx.clone(), + tx_dep_provider, + header_dep_resolver, + open_capacity, + ); match fee_result { Ok(fee) if fee == min_fee => { return Ok(new_tx); diff --git a/src/tx_builder/omni_lock.rs b/src/tx_builder/omni_lock.rs index 57206503..ac5d0607 100644 --- a/src/tx_builder/omni_lock.rs +++ b/src/tx_builder/omni_lock.rs @@ -2,14 +2,14 @@ use std::collections::{HashMap, HashSet}; use ckb_types::{ bytes::Bytes, - core::{DepType, ScriptHashType, TransactionBuilder, TransactionView}, + core::{DepType, TransactionBuilder, TransactionView}, packed::{CellDep, CellInput, CellOutput, OutPoint, Script}, prelude::*, - H256, }; use super::{ - balance_tx_capacity, fill_placeholder_witnesses, CapacityBalancer, TxBuilder, TxBuilderError, + balance_tx_capacity_with_open, fill_placeholder_witnesses, CapacityBalancer, TxBuilder, + TxBuilderError, }; use crate::{ traits::{CellCollector, CellDepResolver, HeaderDepResolver, TransactionDependencyProvider}, @@ -22,6 +22,7 @@ pub struct OmniLockTransferBuilder { pub outputs: Vec<(CellOutput, Bytes)>, pub cfg: OmniLockConfig, pub rce_cells: Option>, + pub open_out_capacity: HumanCapacity, } impl OmniLockTransferBuilder { @@ -34,6 +35,7 @@ impl OmniLockTransferBuilder { outputs, cfg, rce_cells, + open_out_capacity: HumanCapacity(0), } } @@ -41,74 +43,18 @@ impl OmniLockTransferBuilder { /// After the transaction built, the open out should be removed. pub fn new_open( open_out_capacity: HumanCapacity, - mut outputs: Vec<(CellOutput, Bytes)>, + outputs: Vec<(CellOutput, Bytes)>, cfg: OmniLockConfig, rce_cells: Option>, ) -> OmniLockTransferBuilder { - let tmp_out = OmniLockTransferBuilder::build_tmp_open_out(open_out_capacity); - outputs.push((tmp_out, Bytes::default())); OmniLockTransferBuilder { outputs, cfg, rce_cells, + open_out_capacity, } } - fn build_opentx_placeholder_hash() -> H256 { - let mut ret = H256::default(); - let opentx = "opentx"; - let offset = ret.0.len() - opentx.len(); - ret.0[offset..].copy_from_slice(opentx.as_bytes()); - ret - } - - fn build_opentx_tmp_script() -> Script { - let tmp_locker = Self::build_opentx_placeholder_hash(); - Script::new_builder() - .code_hash(tmp_locker.pack()) - .hash_type(ScriptHashType::Type.into()) - .args([0xffu8; 65].pack()) - .build() - } - - pub fn build_tmp_open_out(open_capacity: HumanCapacity) -> CellOutput { - let tmp_locker = Self::build_opentx_tmp_script(); - CellOutput::new_builder() - .lock(tmp_locker) - .capacity(open_capacity.0.pack()) - .build() - } - - /// remove the open output - pub fn remove_open_out(tx: TransactionView) -> TransactionView { - let tmp_locker = Self::build_opentx_tmp_script(); - let tmp_idxes: HashSet = tx - .outputs() - .into_iter() - .enumerate() - .filter(|(_, out)| out.lock() == tmp_locker) - .map(|(idx, _)| idx) - .collect(); - let outputs: Vec = tx - .outputs() - .into_iter() - .enumerate() - .filter(|(idx, _)| !tmp_idxes.contains(idx)) - .map(|(_, out)| out) - .collect(); - let outputs_data: Vec = tx - .outputs_data() - .into_iter() - .enumerate() - .filter(|(idx, _)| !tmp_idxes.contains(idx)) - .map(|(_, out)| out) - .collect(); - tx.as_advanced_builder() - .set_outputs(outputs) - .set_outputs_data(outputs_data) - .build() - } - /// after the open transaction input list updated(exclude base input/output), the witness should be updated pub fn update_opentx_witness( tx: TransactionView, @@ -230,17 +176,15 @@ impl TxBuilder for OmniLockTransferBuilder { )?; let (tx_filled_witnesses, _) = fill_placeholder_witnesses(base_tx, tx_dep_provider, unlockers)?; - let mut tx = balance_tx_capacity( + let tx = balance_tx_capacity_with_open( &tx_filled_witnesses, balancer, cell_collector, tx_dep_provider, cell_dep_resolver, header_dep_resolver, + self.open_out_capacity.into(), )?; - if self.cfg.is_opentx_mode() { - tx = OmniLockTransferBuilder::remove_open_out(tx); - } Ok(tx) } } From e540e16ef2ab5d6cdf77a96569de6391cdd2a00b Mon Sep 17 00:00:00 2001 From: Liu Chuankai Date: Tue, 13 Dec 2022 19:13:20 +0800 Subject: [PATCH 29/29] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20Add=20change?= =?UTF-8?q?s=20into=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ examples/transfer_from_opentx.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d02abd01..cd92e77a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ +# 2.4.0 +* **BREAKING CHANGE**: add parameter `tx_dep_provider: &dyn TransactionDependencyProvider` to `ScriptSigner::sing_tx`. +* Support open transaction. + # 2.3.0 * Update ckb to v0.105.1 * **BREAKING CHANGE**: `get_transaction` rpc now return `TransactionWithStatusResponse` diff --git a/examples/transfer_from_opentx.rs b/examples/transfer_from_opentx.rs index 058cb982..882dee5d 100644 --- a/examples/transfer_from_opentx.rs +++ b/examples/transfer_from_opentx.rs @@ -39,7 +39,7 @@ use std::{collections::HashMap, error::Error as StdErr, fs, path::PathBuf}; // You should not use this hash to find your open transaction code_hash at any circumstances, // or you will lost your CKB for ever, or not make the example work. // You should replace this hash with your own transaction hash or provide one with command line parameter for this example. -const OPENTX_TX_HASH: &str = "d7697f6b3684d1451c42cc538b3789f13b01430007f65afe74834b6a28714a18"; +const OPENTX_TX_HASH: &str = "cfcc8aa04d963cb40d79eab17e6f8def536defca203b71a8fce9cfb950dd04fc"; const OPENTX_TX_IDX: &str = "0"; #[derive(Args)]