diff --git a/devtools/playground/random_transaction.rb b/devtools/playground/random_transaction.rb deleted file mode 100755 index 94abc7c579..0000000000 --- a/devtools/playground/random_transaction.rb +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env ruby -require 'json' -require 'net/http' -require 'uri' - -URL = ARGV[0] || "http://localhost:8114" -CKB_BIN = ARGV[1] || "./target/debug/ckb" -ACCOUNTS = [ - { - name: "miner", - type_hash: "321c1ca2887fb8eddaaa7e917399f71e63e03a1c83ff75ed12099a01115ea2ff", - private_key: "e79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3", - utxo: [] - }, - { - name: "alice", - type_hash: "67295822d1f852602a7d398edfe9ad42734cf41999c084ea8a401ff7cc994cfa", - private_key: "76e853efa8245389e33f6fe49dcbd359eb56be2f6c3594e12521d2a806d32156", - utxo: [] - }, - { - name: "bob", - type_hash: "45996d79e9403f99eb2d79c4f649d45be4b5ab8458e359f5421ff1ab4e88e9f4", - private_key: "9f7fd78dffeda83b77c5c2d7eeaccb05120457787defdbb46da6d2186bf28f13", - utxo: [] - } -] - -class Fixnum - def random_split(set = nil, repeats = false) - set ||= 1..self - set = [*set] - return if set.empty? || set.min > self || set.inject(0, :+) < self - tried_numbers = [] - while (not_tried = (set - tried_numbers).select {|n| n <= self }).any? - tried_numbers << number = not_tried.sample - return [number] if number == self - new_set = set.dup - new_set.delete_at(new_set.index(number)) unless repeats - randomized_rest = (self-number).random_split(new_set, repeats) - return [number] + randomized_rest if randomized_rest - end - end -end - -def pull_transactions - number = rpc('get_tip_header')[:raw][:number] - block_hash = rpc('get_block_hash', "[#{number}]") - block = rpc('get_block', "[\"#{block_hash}\"]") - block[:transactions].each do |tx| - tx[:transaction][:outputs].each_with_index do |output, i| - if match = ACCOUNTS.find{|account| output[:lock] == "0x#{account[:type_hash]}" } - match[:utxo] << {hash: tx[:hash], index: i, capacity: output[:capacity]} - end - end - end -end - -def send_transactions - account = ACCOUNTS.sample - if account[:utxo].size > 0 - utxo = account[:utxo].sample(account[:utxo].size / 2 + 1) - total = utxo.inject(0){|s, o| s + o[:capacity]} - inputs = utxo.map do |o| - account[:utxo].delete(o) - { - previous_output: { - hash: o[:hash], - index: o[:index] - }, - unlock: { - version: 0, - args: [account[:name].bytes.to_a], - signed_args: [] - } - } - end - outputs = total.random_split(100..10000).map do |capacity| - { - capacity: capacity, - data: [0], - lock: "0x#{ACCOUNTS.sample[:type_hash]}" - } - end - transaction = { - version: 0, - deps: [], - inputs: inputs, - outputs: outputs, - } - signed = `#{CKB_BIN} cli sign -p #{account[:private_key]} -u '#{transaction.to_json}'` - rpc('send_transaction', "[#{signed}]") - end -end - -def rpc(method, params = "null") - puts "rpc method: #{method}, params: #{params}" - response = Net::HTTP.post( - URI(URL), - "{\"id\": 1, \"jsonrpc\": \"2.0\", \"method\": \"#{method}\", \"params\": #{params}}", - "Content-Type" => "application/json") - JSON.parse(response.body, symbolize_names: true)[:result] -end - -10.times do - pull_transactions - send_transactions - sleep(5) -end diff --git a/nodes_template/default.json b/nodes_template/default.json index b1e6527d10..24fe527932 100644 --- a/nodes_template/default.json +++ b/nodes_template/default.json @@ -34,7 +34,7 @@ }, "miner": { "new_transactions_threshold": 8, - "type_hash": "0x321c1ca2887fb8eddaaa7e917399f71e63e03a1c83ff75ed12099a01115ea2ff", + "type_hash": "0x0da2fe99fe549e082d4ed483c2e968a89ea8d11aabf5d79e5cbf06522de6e674", "rpc_url": "http://127.0.0.1:8114/", "poll_interval": 5, "max_transactions": 10000, diff --git a/nodes_template/spec/dev.json b/nodes_template/spec/dev.json index 66253ac45b..aa848a97cc 100644 --- a/nodes_template/spec/dev.json +++ b/nodes_template/spec/dev.json @@ -35,7 +35,6 @@ "initial_block_reward": 50000 }, "system_cells": [ - {"path": "cells/verify"}, {"path": "cells/always_success"} ], "pow": { diff --git a/script/src/verify.rs b/script/src/verify.rs index 873bf5c367..a04404f151 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -204,7 +204,7 @@ mod tests { fn open_cell_verify() -> File { File::open( - Path::new(env!("CARGO_MANIFEST_DIR")).join("../nodes_template/spec/cells/verify"), + Path::new(env!("CARGO_MANIFEST_DIR")).join("../script/testdata/verify"), ) .unwrap() } diff --git a/nodes_template/spec/cells/verify b/script/testdata/verify similarity index 100% rename from nodes_template/spec/cells/verify rename to script/testdata/verify diff --git a/script/testdata/verify.c b/script/testdata/verify.c new file mode 100644 index 0000000000..176ef8dd66 --- /dev/null +++ b/script/testdata/verify.c @@ -0,0 +1,135 @@ +#include +#include "sha3.h" + +#define SHA3_BLOCK_SIZE 32 + +#define CUSTOM_ABORT 1 +#define CUSTOM_PRINT_ERR 1 + +#include "syscall.h" +void custom_abort() +{ + syscall_errno(93, 10, 0, 0, 0, 0, 0); +} + +int custom_print_err(const char * arg, ...) +{ + (void) arg; + return 0; +} + +#include +/* + * We are including secp256k1 implementation directly so gcc can strip + * unused functions. For some unknown reasons, if we link in libsecp256k1.a + * directly, the final binary will include all functions rather than those used. + */ +#include + +int char_to_int(char ch) +{ + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } + return -1; +} + +int hex_to_bin(char* buf, size_t buf_len, const char* hex) +{ + int i = 0; + + for (; i < buf_len && hex[i * 2] != '\0' && hex[i * 2 + 1] != '\0'; i++) { + int a = char_to_int(hex[i * 2]); + int b = char_to_int(hex[i * 2 + 1]); + + if (a < 0 || b < 0) { + return -1; + } + + buf[i] = ((a & 0xF) << 4) | (b & 0xF); + } + + if (i == buf_len && hex[i * 2] != '\0') { + return -1; + } + return i; +} + +#define CHECK_LEN(x) if ((x) <= 0) { return x; } + +/* + * Arguments are listed in the following order: + * 0. Program name, ignored here, only preserved for compatibility reason + * 1. Pubkey in hex format, a maximum of 130 bytes will be processed + * 2. Signature in hex format, a maximum of 512 bytes will be processed + * 3. Current script hash in hex format, which is 128 bytes. While this program + * cannot verify the hash directly, this ensures the script is include in + * signature calculation + * 4. Other additional parameters that might be included. Notice only ASCII + * characters are included, so binary should be passed as binary format. + * + * This program will run double sha256 on all arguments excluding pubkey and + * signature(also for simplicity, we are running sha256 on ASCII chars directly, + * not deserialized raw bytes), then it will use sha256 result calculated as the + * message to verify the signature. It returns 0 if the signature works, and + * a non-zero value otherwise. + * + * Note all hex values passed in as arguments must have lower case letters for + * deterministic behavior. + */ +int main(int argc, char* argv[]) +{ + char buf[256]; + int len; + + if (argc < 4) { + return -1; + } + + secp256k1_context context; + int ret = secp256k1_context_initialize(&context, SECP256K1_CONTEXT_VERIFY); + if (ret == 0) { + return 4; + } + + len = hex_to_bin(buf, 65, argv[1]); + CHECK_LEN(len); + secp256k1_pubkey pubkey; + + ret = secp256k1_ec_pubkey_parse(&context, &pubkey, buf, len); + if (ret == 0) { + return 1; + } + + len = hex_to_bin(buf, 256, argv[2]); + CHECK_LEN(len); + secp256k1_ecdsa_signature signature; + secp256k1_ecdsa_signature_parse_der(&context, &signature, buf, len); + if (ret == 0) { + return 3; + } + + sha3_ctx_t sha3_ctx; + unsigned char hash[SHA3_BLOCK_SIZE]; + sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); + for (int i = 3; i < argc; i++) { + sha3_update(&sha3_ctx, argv[i], strlen(argv[i])); + } + sha3_final(hash, &sha3_ctx); + + sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); + sha3_update(&sha3_ctx, hash, SHA3_BLOCK_SIZE); + sha3_final(hash, &sha3_ctx); + + ret = secp256k1_ecdsa_verify(&context, &signature, hash, &pubkey); + if (ret == 1) { + ret = 0; + } else { + ret = 2; + } + + return ret; +} diff --git a/src/cli/args.rs b/src/cli/args.rs index de8abbf94c..8aecfe73c7 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -75,38 +75,13 @@ fn import() -> App<'static, 'static> { ) } -fn arg_private_key() -> Arg<'static, 'static> { - Arg::with_name("private-key") - .short("p") - .long("private-key") - .value_name("H256") - .help("Specify the private key") - .takes_value(true) - .required(true) -} - fn cli() -> App<'static, 'static> { SubCommand::with_name("cli") .about("Running ckb cli") .setting(AppSettings::ArgRequiredElseHelp) - .subcommand( - SubCommand::with_name("sign") - .about("Sign transaction using sha3-secp256k1 defined in system cell") - .arg(arg_private_key()) - .arg( - Arg::with_name("unsigned-transaction") - .short("u") - .long("unsigned-transaction") - .value_name("JSON") - .help("Specify the unsigned transaction json string") - .takes_value(true) - .required(true), - ), - ) .subcommand( SubCommand::with_name("type_hash") - .about("Generate script type hash using sha3-secp256k1 defined in system cell") - .arg(arg_private_key()), + .about("Generate lock script type hash using the first system cell, which by default is always_success"), ) .subcommand(SubCommand::with_name("keygen").about("Generate new key")) } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2733475bbf..1a5b24f14e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -8,4 +8,4 @@ pub use self::args::get_matches; pub use self::export::export; pub use self::import::import; pub use self::miner::miner; -pub use self::run_impl::{keygen, run, sign, type_hash}; +pub use self::run_impl::{keygen, run, type_hash}; diff --git a/src/cli/run_impl.rs b/src/cli/run_impl.rs index 6ab1316cc1..5e5765190e 100644 --- a/src/cli/run_impl.rs +++ b/src/cli/run_impl.rs @@ -2,7 +2,6 @@ use crate::helper::wait_for_exit; use crate::Setup; use ckb_chain::chain::{ChainBuilder, ChainController}; use ckb_core::script::Script; -use ckb_core::transaction::{CellInput, OutPoint, Transaction, TransactionBuilder}; use ckb_db::diskdb::RocksDB; use ckb_miner::{Agent, AgentController}; use ckb_network::CKBProtocol; @@ -17,14 +16,11 @@ use ckb_shared::index::ChainIndex; use ckb_shared::shared::{ChainProvider, Shared, SharedBuilder}; use ckb_shared::store::ChainKVStore; use ckb_sync::{Relayer, Synchronizer, RELAY_PROTOCOL_ID, SYNC_PROTOCOL_ID}; -use clap::{value_t, ArgMatches}; -use crypto::secp::{Generator, Privkey}; -use faster_hex::{hex_string, hex_to}; -use hash::sha3_256; +use crypto::secp::Generator; +use faster_hex::hex_string; use log::info; use numext_fixed_hash::H256; use serde_json; -use std::io::Write; use std::sync::Arc; use std::thread; @@ -152,89 +148,19 @@ fn setup_rpc( }); } -pub fn sign(setup: &Setup, matches: &ArgMatches) { +pub fn type_hash(setup: &Setup) { let consensus = setup.chain_spec.to_consensus().unwrap(); let system_cell_tx = &consensus.genesis_block().commit_transactions()[0]; let system_cell_data_hash = system_cell_tx.outputs()[0].data_hash(); - let system_cell_tx_hash = system_cell_tx.hash(); - let system_cell_out_point = OutPoint::new(system_cell_tx_hash.clone(), 0); - - let privkey: Privkey = value_t!(matches.value_of("private-key"), H256) - .unwrap_or_else(|e| e.exit()) - .into(); - let pubkey = privkey.pubkey().unwrap(); - let json = - value_t!(matches.value_of("unsigned-transaction"), String).unwrap_or_else(|e| e.exit()); - let transaction: Transaction = serde_json::from_str(&json).unwrap(); - let mut inputs = Vec::new(); - for unsigned_input in transaction.inputs() { - let mut bytes = vec![]; - for argument in &unsigned_input.unlock.args { - bytes.write_all(argument).unwrap(); - } - let hash1 = sha3_256(&bytes); - let hash2 = sha3_256(hash1); - let signature = privkey.sign_recoverable(&hash2.into()).unwrap(); - let signature_der = signature.serialize_der(); - let mut hex_signature = vec![0; signature_der.len() * 2]; - hex_to(&signature_der, &mut hex_signature).expect("hex signature"); - - let mut new_args = vec![hex_signature]; - new_args.extend_from_slice(&unsigned_input.unlock.args); - - let pubkey_ser = pubkey.serialize(); - let mut hex_pubkey = vec![0; pubkey_ser.len() * 2]; - hex_to(&pubkey_ser, &mut hex_pubkey).expect("hex pubkey"); - let script = Script::new( - 0, - new_args, - Some(system_cell_data_hash.clone()), - None, - vec![hex_pubkey], - ); - let signed_input = CellInput::new(unsigned_input.previous_output.clone(), script); - inputs.push(signed_input); - } - // First, add verify system cell as a dep - // Then, sign each input - let result = TransactionBuilder::default() - .transaction(transaction) - .dep(system_cell_out_point) - .inputs_clear() - .inputs(inputs) - .build(); - println!("{}", serde_json::to_string(&result).unwrap()); -} - -pub fn type_hash(setup: &Setup, matches: &ArgMatches) { - let consensus = setup.chain_spec.to_consensus().unwrap(); - let system_cell_tx = &consensus.genesis_block().commit_transactions()[0]; - let system_cell_data_hash = system_cell_tx.outputs()[0].data_hash(); - - let privkey: Privkey = value_t!(matches.value_of("private-key"), H256) - .unwrap_or_else(|e| e.exit()) - .into(); - let pubkey = privkey.pubkey().unwrap(); - - let pubkey_ser = pubkey.serialize(); - let mut hex_pubkey = vec![0; pubkey_ser.len() * 2]; - hex_to(&pubkey_ser, &mut hex_pubkey).expect("hex pubkey"); - - let script = Script::new( - 0, - Vec::new(), - Some(system_cell_data_hash), - None, - vec![hex_pubkey], - ); + let script = Script::new(0, vec![], Some(system_cell_data_hash), None, vec![]); println!( - "{}", + "0x{}", hex_string(script.type_hash().as_bytes()).expect("hex string") ); } pub fn keygen() { let result: H256 = Generator::new().random_privkey().into(); - println!("{:?}", result) + println!("0x{}", hex_string(result.as_bytes()).expect("hex string")); } diff --git a/src/main.rs b/src/main.rs index 09f6c8f911..7102ee554b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,7 @@ fn main() { match matches.subcommand() { ("cli", Some(cli_matches)) => match cli_matches.subcommand() { - ("sign", Some(sign_matches)) => cli::sign(&setup, sign_matches), - ("type_hash", Some(type_hash_matches)) => cli::type_hash(&setup, type_hash_matches), + ("type_hash", _) => cli::type_hash(&setup), ("keygen", _) => cli::keygen(), _ => unreachable!(), },