Skip to content

Commit

Permalink
feat: 🎸 Support sphincs plus lock script
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 12 changed files with 703 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ clap = { version = "4.1.8", features = ["derive"] }
httpmock = "0.6"
async-global-executor = "2.3.1"
hex = "0.4"

[build-dependencies]
cc = "1.0"
65 changes: 65 additions & 0 deletions build.rs
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");
}
10 changes: 5 additions & 5 deletions check-cargotoml.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ esac

function check_package_name() {
local regex_to_cut_pkgname='s/^\[\(package\)\]\nname\(\|[ ]\+\)=\(\|[ ]\+\)"\(.\+\)"/\4/p'
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*'); do
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*' -not -path './deps/*'); do
local pkgname=$(${SED} -n -e N -e "${regex_to_cut_pkgname}" "${cargo_toml}")
if [ -z "${pkgname}" ]; then
printf "Error: No package name in <%s>\n" "${cargo_toml}"
Expand All @@ -43,7 +43,7 @@ function check_package_name() {
function check_version() {
local regex_to_cut_version='s/^version = "\(.*\)"$/\1/p'
local expected=$(${SED} -n "${regex_to_cut_version}" "${SRC_ROOT}/Cargo.toml")
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*'); do
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*' -not -path './deps/*'); do
local tmp=$(${SED} -n "${regex_to_cut_version}" "${cargo_toml}")
if [ "${expected}" != "${tmp}" ]; then
printf "Error: Version in <%s> is not right (expect: '%s', actual: '%s')\n" \
Expand All @@ -62,7 +62,7 @@ function check_version() {
function check_license() {
local regex_to_cut_license='s/^license = "\(.*\)"$/\1/p'
local expected=$(${SED} -n "${regex_to_cut_license}" "${SRC_ROOT}/Cargo.toml")
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*'); do
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*' -not -path './deps/*'); do
local tmp=$(${SED} -n "${regex_to_cut_license}" "${cargo_toml}")
if [ "${expected}" != "${tmp}" ]; then
printf "Error: License in <%s> is not right (expect: '%s', actual: '%s')\n" \
Expand All @@ -73,7 +73,7 @@ function check_license() {
}

function check_cargo_publish() {
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*'); do
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*' -not -path './deps/*'); do
if ! grep -q '^description =' "${cargo_toml}"; then
echo "Error: Require description in <${cargo_toml}>"
ERRCNT=$((ERRCNT + 1))
Expand Down Expand Up @@ -115,7 +115,7 @@ function search_crate() {

function check_dependencies_for() {
local deptype="$1"
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*'); do
for cargo_toml in $(find "${SRC_ROOT}" -type f -name "Cargo.toml" -not -path '*/target/*' -not -path './deps/*'); do
local pkgroot=$(dirname "${cargo_toml}")
for dependency_original in $(${SED} -n "/^\[${deptype}\]/,/^\[/p" "${cargo_toml}" \
| { ${GREP} -v "^\(\[\|[ ]*$\|[ ]*#\)" || true; } \
Expand Down
1 change: 1 addition & 0 deletions deps/quantum-resistant-lock-script
127 changes: 127 additions & 0 deletions examples/unlock_sphincsplucs.rs
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(())
}
1 change: 1 addition & 0 deletions src/tx_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod acp;
pub mod cheque;
pub mod dao;
pub mod omni_lock;
pub mod sphincsplus;
pub mod transfer;
pub mod udt;

Expand Down
89 changes: 89 additions & 0 deletions src/tx_builder/sphincsplus/mod.rs
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
}
}
5 changes: 5 additions & 0 deletions src/unlock/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub(crate) mod omni_lock;
pub mod rc_data;
mod signer;
pub mod sphincsplus;
mod unlocker;

pub use signer::{
Expand All @@ -14,3 +15,7 @@ pub use unlocker::{
};

pub use omni_lock::{IdentityFlag, InfoCellData, OmniLockAcpConfig, OmniLockConfig};

pub use sphincsplus::{
signer::SphincsPlusSigner, unlocker::SphincsPlusUnlocker, SphincsPlus, SphincsPlusRawKeysSigner,
};
Loading

0 comments on commit 7e56a4f

Please sign in to comment.