forked from nervosnetwork/ckb-sdk-rust
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 Support sphincs plus lock script
Add Support sphincs plus lock script, a quantum-resistant-lock-script ✅ Closes: nervosnetwork#57
- Loading branch information
Liu Chuankai
committed
Mar 30, 2023
1 parent
5f7aa5a
commit 7e56a4f
Showing
12 changed files
with
703 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
[submodule "deps/quantum-resistant-lock-script"] | ||
path = deps/quantum-resistant-lock-script | ||
url = [email protected]:cryptape/quantum-resistant-lock-script.git | ||
branch = main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use std::path::PathBuf; | ||
|
||
fn get_hash_size() -> usize { | ||
128 | ||
} | ||
|
||
fn get_hash_option() -> &'static str { | ||
"f" | ||
} | ||
|
||
fn get_hash_info() -> (&'static str, Vec<&'static str>) { | ||
let thash_file = "../deps/sphincsplus/ref/thash_shake_simple.c"; | ||
|
||
( | ||
"shake", | ||
vec![ | ||
"../deps/sphincsplus/ref/fips202.c", | ||
"../deps/sphincsplus/ref/hash_shake.c", | ||
thash_file, | ||
], | ||
) | ||
} | ||
|
||
fn main() { | ||
let mut source_list = vec![ | ||
"../deps/sphincsplus/ref/address.c", | ||
"../deps/sphincsplus/ref/merkle.c", | ||
"../deps/sphincsplus/ref/wots.c", | ||
"../deps/sphincsplus/ref/wotsx1.c", | ||
"../deps/sphincsplus/ref/utils.c", | ||
"../deps/sphincsplus/ref/utilsx1.c", | ||
"../deps/sphincsplus/ref/fors.c", | ||
"../deps/sphincsplus/ref/sign.c", | ||
"../deps/sphincsplus/ref/randombytes.c", | ||
"ckb-sphincsplus.c", | ||
]; | ||
|
||
let (hash_name, mut hash_src_files) = get_hash_info(); | ||
|
||
source_list.append(&mut hash_src_files); | ||
let define_param = format!( | ||
"sphincs-{}-{}{}", | ||
hash_name, | ||
get_hash_size(), | ||
get_hash_option() | ||
); | ||
|
||
let c_src_dir = PathBuf::from("deps/quantum-resistant-lock-script/c/"); | ||
|
||
let mut builder = cc::Build::new(); | ||
builder.define("PARAMS", define_param.as_str()); | ||
builder.include(&c_src_dir); | ||
builder.include( | ||
&c_src_dir | ||
.join("..") | ||
.join("deps") | ||
.join("sphincsplus") | ||
.join("ref"), | ||
); | ||
|
||
for source in source_list { | ||
builder.file(c_src_dir.join(source)); | ||
} | ||
builder.compile("sphincsplus"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule quantum-resistant-lock-script
added at
1825f2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
use bytes::Bytes; | ||
use ckb_sdk::{ | ||
constants::ONE_CKB, | ||
traits::{ | ||
DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, | ||
DefaultTransactionDependencyProvider, | ||
}, | ||
tx_builder::{ | ||
sphincsplus::SphincsPlusEnv, transfer::CapacityTransferBuilder, CapacityBalancer, TxBuilder, | ||
}, | ||
unlock::{sphincsplus::SphincsPlusPrivateKey, SphincsPlus}, | ||
Address, CkbRpcClient, NetworkType, | ||
}; | ||
use ckb_types::{ | ||
core::{BlockView, DepType, ScriptHashType, TransactionView}, | ||
h256, | ||
packed::{CellOutput, Script}, | ||
prelude::*, | ||
}; | ||
|
||
use std::{convert::TryFrom, error::Error as StdErr, str::FromStr}; | ||
|
||
pub const SK: [u8; 64] = [ | ||
244, 229, 172, 97, 118, 43, 186, 182, 5, 191, 38, 224, 223, 57, 251, 84, // sk.seed | ||
29, 7, 44, 250, 108, 236, 220, 216, 161, 162, 99, 146, 46, 4, 34, 125, // sk.prf | ||
152, 145, 159, 50, 118, 81, 12, 134, 27, 52, 214, 210, 91, 84, 65, 42, // pubkey seed | ||
252, 12, 85, 58, 222, 186, 58, 189, 25, 133, 144, 79, 103, 177, 27, 76, // pubkey root | ||
]; | ||
|
||
fn build_transfer_tx( | ||
env: &SphincsPlusEnv, | ||
sender: Script, | ||
sender_key: SphincsPlusPrivateKey, | ||
) -> Result<TransactionView, Box<dyn StdErr>> { | ||
// Build ScriptUnlocker | ||
let unlockers = env.build_unlockers(vec![sender_key]); | ||
|
||
// Build CapacityBalancer | ||
let placeholder_witness = SphincsPlus::placeholder_witness(); | ||
let mut balancer = CapacityBalancer::new_simple(sender, placeholder_witness, 1000); | ||
balancer.force_small_change_as_fee = Some(ONE_CKB); | ||
|
||
// Build: | ||
// * CellDepResolver | ||
// * HeaderDepResolver | ||
// * CellCollector | ||
// * TransactionDependencyProvider | ||
let ckb_rpc = "https://testnet.ckb.dev"; | ||
let mut ckb_client = CkbRpcClient::new(ckb_rpc); | ||
let cell_dep_resolver = { | ||
let genesis_block = ckb_client.get_block_by_number(0.into())?.unwrap(); | ||
let mut cell_dep_resolver = | ||
DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block))?; | ||
env.add_cell_dep(&mut cell_dep_resolver); | ||
cell_dep_resolver | ||
}; | ||
let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc); | ||
let mut cell_collector = DefaultCellCollector::new(ckb_rpc); | ||
let tx_dep_provider = DefaultTransactionDependencyProvider::new(ckb_rpc, 10); | ||
|
||
let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche").unwrap(); | ||
// Build the transaction | ||
let output = CellOutput::new_builder() | ||
.lock(Script::from(&receiver)) | ||
.capacity(99_9990_0000u64.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, | ||
)?; | ||
assert!(still_locked_groups.is_empty()); | ||
Ok(tx) | ||
} | ||
|
||
fn build_env() -> SphincsPlusEnv { | ||
SphincsPlusEnv { | ||
tx_hash: h256!("0x35f51257673c7a7edd009fa2166e6f8645156207c9da38202f04ba4d94d9e519"), | ||
tx_idx: 0, | ||
dep_type: DepType::Code, | ||
code_hash: h256!("0x989ab456455509a1c2ad1cb8116b7d209df228144445c741b101ec3e55ee8351"), | ||
hash_type: ScriptHashType::Data1, | ||
network_type: NetworkType::Testnet, | ||
} | ||
} | ||
|
||
/* | ||
1. build address, | ||
ckt1qzvf4dzkg42sngwz45wtsytt05sfmu3gz3zyt36pkyq7c0j4a6p4zqkur4fuxjyh4fzphavynhdgptuwqsyhdjns028ugqy5jgnesdy8wslj3elk | ||
2. transfer to address: | ||
wallet transfer --from-account 0x946c32d287a3544d5450f0cf5d43ca24dd37f55e \ | ||
--to-address ckt1qzvf4dzkg42sngwz45wtsytt05sfmu3gz3zyt36pkyq7c0j4a6p4zqkur4fuxjyh4fzphavynhdgptuwqsyhdjns028ugqy5jgnesdy8wslj3elk \ | ||
--capacity 100 --skip-check-to-address | ||
0x7fb5afa7c0bdc9cbbc2b65b523581c2bb3ed43ced114f759651ae407dee3d0c9 | ||
3. unlock the cell, and transfer the capacity to address ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche | ||
*/ | ||
fn main() -> Result<(), Box<dyn StdErr>> { | ||
let sk = SphincsPlusPrivateKey::try_from(SK.to_vec()).unwrap(); | ||
let pk = sk.pub_key(); | ||
let env = build_env(); | ||
let address = env.build_address(&pk); | ||
let resp = serde_json::json!({ | ||
"address": address.to_string(), | ||
}); | ||
println!("{}", serde_json::to_string_pretty(&resp).unwrap()); | ||
let sender = env.script(&pk); | ||
|
||
let tx = build_transfer_tx(&env, sender, sk)?; | ||
|
||
// Send transaction | ||
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx); | ||
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); | ||
let outputs_validator = Some(ckb_jsonrpc_types::OutputsValidator::Passthrough); | ||
|
||
let ckb_rpc = "https://testnet.ckb.dev"; | ||
let _tx_hash = CkbRpcClient::new(ckb_rpc) | ||
.send_transaction(json_tx.inner, outputs_validator) | ||
.expect("send transaction"); | ||
// example tx_hash: 0xf83fd6c2fe511a9c39795624b7e0be2157e1543d9f1b1a1cbb676896e31c2b1b | ||
println!(">>> tx sent! <<<"); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use std::collections::HashMap; | ||
|
||
use bytes::Bytes; | ||
use ckb_types::{ | ||
core::{DepType, ScriptHashType}, | ||
packed::{Byte32, CellDep, OutPoint, Script}, | ||
prelude::*, | ||
H256, | ||
}; | ||
|
||
use crate::{ | ||
traits::DefaultCellDepResolver, | ||
unlock::{ | ||
sphincsplus::{SphincsPlusPrivateKey, SphincsPlusPublicKey}, | ||
ScriptUnlocker, SphincsPlusRawKeysSigner, SphincsPlusUnlocker, | ||
}, | ||
Address, AddressPayload, NetworkType, ScriptId, | ||
}; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct SphincsPlusEnv { | ||
/// transaction hash where the code is deployed | ||
pub tx_hash: H256, | ||
/// transaction index where the code is deployed | ||
pub tx_idx: u32, | ||
/// cell dependency type | ||
pub dep_type: DepType, | ||
/// the code hash | ||
pub code_hash: H256, | ||
/// the code hash's hash type, | ||
pub hash_type: ScriptHashType, | ||
/// the network type | ||
pub network_type: NetworkType, | ||
} | ||
|
||
impl SphincsPlusEnv { | ||
/// build script id | ||
pub fn script_id(&self) -> ScriptId { | ||
ScriptId { | ||
code_hash: self.code_hash.clone(), | ||
hash_type: self.hash_type, | ||
} | ||
} | ||
|
||
pub fn script(&self, pk: &SphincsPlusPublicKey) -> Script { | ||
Script::new_builder() | ||
.code_hash(self.code_hash.pack()) | ||
.hash_type(self.hash_type.into()) | ||
.args(Bytes::from(pk.lock_args().to_vec()).pack()) | ||
.build() | ||
} | ||
/// add cell dependency to DefaultCellDepResolver | ||
pub fn add_cell_dep(&self, cell_dep_resolver: &mut DefaultCellDepResolver) { | ||
let out_point = OutPoint::new( | ||
Byte32::from_slice(self.tx_hash.as_bytes()).unwrap(), | ||
self.tx_idx, | ||
); | ||
|
||
let cell_dep = CellDep::new_builder().out_point(out_point).build(); | ||
cell_dep_resolver.insert(self.script_id(), cell_dep, "Sphincs plus".to_string()); | ||
} | ||
|
||
/// build address from public key | ||
pub fn build_address(&self, pk: &SphincsPlusPublicKey) -> Address { | ||
let args = Bytes::from(pk.lock_args().to_vec()); | ||
let address_payload = AddressPayload::new_full( | ||
self.hash_type, | ||
Byte32::from_slice(self.code_hash.as_bytes()).unwrap(), | ||
args, | ||
); | ||
Address::new(self.network_type, address_payload, true) | ||
} | ||
|
||
/// build unlockers from private keys | ||
pub fn build_unlockers( | ||
&self, | ||
sks: Vec<SphincsPlusPrivateKey>, | ||
) -> HashMap<ScriptId, Box<dyn ScriptUnlocker>> { | ||
let signer = SphincsPlusRawKeysSigner::new_with_private_keys(sks); | ||
let sighash_unlocker = SphincsPlusUnlocker::from(Box::new(signer) as Box<_>); | ||
let sighash_script_id = ScriptId::new_data1(self.code_hash.clone()); | ||
let mut unlockers = HashMap::default(); | ||
unlockers.insert( | ||
sighash_script_id, | ||
Box::new(sighash_unlocker) as Box<dyn ScriptUnlocker>, | ||
); | ||
unlockers | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.