Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
Add the tron sign tx and other api
Browse files Browse the repository at this point in the history
  • Loading branch information
XuNeal committed Dec 4, 2019
1 parent 735911c commit 6b06b44
Show file tree
Hide file tree
Showing 14 changed files with 600 additions and 314 deletions.
2 changes: 1 addition & 1 deletion tcx-chain/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl HdKeystore {
Self::derive_account_from_coin::<A, E>(coin_info, seed.as_bytes())
}

fn derive_account_from_coin<A: Address, E: Extra>(
pub fn derive_account_from_coin<A: Address, E: Extra>(
coin_info: &CoinInfo,
seed: &[u8],
) -> Result<Account> {
Expand Down
1 change: 1 addition & 0 deletions tcx-constants/src/coin_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub fn coin_info_from_symbol(symbol: &str) -> Result<CoinInfo> {

const NETWORK_COINS: [&str; 3] = ["BITCOINCASH", "LITECOIN", "BITCOIN"];

// todo: remove
pub fn coin_symbol_with_param(
chain_type: &str,
network: &str,
Expand Down
8 changes: 8 additions & 0 deletions tcx-proto/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ fn main() {
)
.unwrap();

// // tcx-chain
// env::set_var("OUT_DIR", "../tcx-chain/src");
// prost_build::compile_protos(&["src/tron.proto"], &["src/"]).unwrap();

// tcx-tron
env::set_var("OUT_DIR", "../tcx-tron/src");
prost_build::compile_protos(&["src/tron.proto"], &["src/"]).unwrap();

// tcx-btc-fork
env::set_var("OUT_DIR", "../tcx-btc-fork/src");
prost_build::compile_protos(&["src/btc_fork.proto"], &["src/"]).unwrap();
Expand Down
6 changes: 6 additions & 0 deletions tcx-proto/src/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ message TcxAction {
string method = 1;
google.protobuf.Any param = 2;
}


message Response {
bool isSuccess = 1;
string error = 2;
}
27 changes: 24 additions & 3 deletions tcx-proto/src/api_params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,23 @@ message HdStoreImportParam {
message HdStoreDeriveParam {
string id = 1;
string password = 2;
repeated string chainTypes = 3;
repeated string paths = 4;
repeated google.protobuf.Any extras = 5;
message Derivation {
string chainType = 1;
string path = 2;
string network = 3;
string segWit = 4;
string chainId = 5;
}
repeated Derivation derivations= 3;
}



message BtcForkDeriveExtraParam {
string network = 1;
string segWit = 2;
}

message AccountResponse {
string chainType = 1;
string address = 2;
Expand Down Expand Up @@ -76,6 +87,16 @@ message WalletKeyParam {
string password = 2;
}

message KeystoreCommonExportResult {
string id = 1;
enum ExportType {
MNEMONIC = 0;
PRIVATE_KEY = 1;
}
ExportType type = 2;
string value = 3;
}

/// Sign Transaction
message SignTxParam {
string id = 1;
Expand Down
10 changes: 10 additions & 0 deletions tcx-proto/src/tron.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";
package transaction;

message TronTxInput {
bytes rawData = 1;
}

message TronTxOutput {
bytes signature = 1;
}
4 changes: 3 additions & 1 deletion tcx-tron/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ tcx-chain = { path = "../tcx-chain" }
tcx-primitive = { path = "../tcx-primitive" }
tcx-constants = { path = "../tcx-constants" }

prost = "0.5.0"
bytes = "0.4.12"

hex-literal = "0.1.4"
bitcoin = "0.20.0"
secp256k1 = {version ="0.15", features = ["recovery"] }
Expand All @@ -25,7 +28,6 @@ num-integer = "0.1"
byteorder = "1.3.2"
bytebuffer = "0.2.1"
hex = "0.3.1"
protobuf = { version = "2", features = ["with-bytes"] }
sha3 = "0.8.2"
digest = "0.8.1"

3 changes: 2 additions & 1 deletion tcx-tron/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pub mod address;
pub mod signer;
pub mod transaction;

pub use crate::address::Address as TrxAddress;
pub use crate::transaction::{
pub use crate::signer::{
Message as TrxMessage, SignedMessage as TrxSignedMessage,
SignedTransaction as TrxSignedTransaction, Transaction as TrxTransaction,
};
Expand Down
230 changes: 230 additions & 0 deletions tcx-tron/src/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use crate::transaction::{TronTxInput, TronTxOutput};
use tcx_chain::{
HdKeystore, Message as TraitMessage, MessageSigner as TraitMessageSigner, Result,
SignedMessage as TraitSignedMessage, TransactionSigner as TraitTransactionSigner,
};

use bitcoin_hashes::sha256::Hash;
use bitcoin_hashes::Hash as TraitHash;

use serde_json::Value;
use std::convert::{TryFrom, TryInto};
use tcx_primitive::{PrivateKey, Secp256k1PrivateKey};

use failure::format_err;

use crate::keccak;
use serde::{Deserialize, Serialize};
use serde_json::json;

// http://jsoneditoronline.org/index.html?id=2b86a8503ba641bebed73f32b4ac9c42
//{
//"visible": false,
//"txID": "88817b9c6276e3c535e4f8f15baf546292ca6ad9d44a7d97857bd6f8909d63d4",
//"raw_data": {
//"contract": [
//{
//"parameter": {
//"value": {
//"amount": 100000,
//"owner_address": "415c68cc82c87446f602f019e5fd797437f5b79cc2",
//"to_address": "4156a6076cd1537fa317c2606e4edfa4acd3e8e92e"
//},
//"type_url": "type.googleapis.com/protocol.TransferContract"
//},
//"type": "TransferContract"
//}
//],
//"ref_block_bytes": "02a2",
//"ref_block_hash": "e216e254e43ee108",
//"expiration": 1571898861000,
//"timestamp": 1571898802704
//},
//"raw_data_hex": "0a0202a22208e216e254e43ee10840c8cbe4e3df2d5a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a15415c68cc82c87446f602f019e5fd797437f5b79cc212154156a6076cd1537fa317c2606e4edfa4acd3e8e92e18a08d06709084e1e3df2d",
//"chainId": "1",
//"id": "d5ca6979-2586-4b6f-88f2-09a3d8b833b0",
//"password": "123123123",
//"chainType": "TRON"
//}

pub struct Transaction {
raw: Value,
}

impl TryFrom<Value> for Transaction {
type Error = failure::Error;

fn try_from(tx: Value) -> Result<Self> {
Ok(Transaction { raw: tx })
}
}

//impl TraitTransaction for Transaction {}

pub struct SignedTransaction {
raw: Value,
}

impl TryInto<Value> for SignedTransaction {
type Error = failure::Error;

fn try_into(self) -> Result<Value> {
Ok(self.raw)
}
}

//impl TraitSignedTransaction for SignedTransaction {}

impl TraitTransactionSigner<TronTxInput, TronTxOutput> for HdKeystore {
fn sign_transaction(&self, tx: &TronTxInput) -> Result<TronTxOutput> {
// let mut raw = tx.raw.clone();
let hash = Hash::hash(&tx.raw_data);
let account = self
.account(&"TRON")
.ok_or_else(|| format_err!("account_not_found"))?;
let path = &account.derivation_path;
let sk = &self.get_private_key(path)?;
let sign_result = sk.sign_recoverable(&hash[..]);

match sign_result {
Ok(r) => Ok(TronTxOutput { signature: r }),
Err(_e) => Err(format_err!("{}", "can not format error")),
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Message {
value: String,
is_hex: bool,
is_tron_header: bool,
}

impl TraitMessage for Message {}

pub struct SignedMessage {
pub signature: String,
}

impl TraitSignedMessage for SignedMessage {}

impl TraitMessageSigner<Message, SignedMessage> for HdKeystore {
fn sign_message(&self, message: &Message) -> Result<SignedMessage> {
let data = match message.is_hex {
true => {
let mut raw_hex: String = message.value.to_owned();
if raw_hex.to_uppercase().starts_with("0X") {
raw_hex.replace_range(..2, "")
}
hex::decode(&raw_hex)?
}
false => message.value.as_bytes().to_vec(),
};
let header = match message.is_tron_header {
true => "\x19TRON Signed Message:\n32".as_bytes(),
false => "\x19Ethereum Signed Message:\n32".as_bytes(),
};
let to_hash = [header, &data].concat();

let hash = keccak(&to_hash);
let account = self
.account(&"TRON")
.ok_or_else(|| format_err!("account_not_found"))?;
let path = &account.derivation_path;
let sk = &self.get_private_key(path)?;
let mut sign_result = sk.sign_recoverable(&hash[..])?;
sign_result[64] = sign_result[64] + 27;
Ok(SignedMessage {
signature: hex::encode(sign_result),
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::address::Address;
use bitcoin::util::contracthash::Error::Secp;
use bitcoin::util::misc::hex_bytes;
use digest::Digest;
use serde_json::Value;
use std::convert::TryFrom;
use tcx_chain::keystore::EmptyExtra;
use tcx_chain::keystore_guard::KeystoreGuard;
use tcx_chain::{Metadata, TransactionSigner};
use tcx_constants::CoinInfo;
use tcx_constants::CurveType;

static PASSWORD: &'static str = "Insecure Pa55w0rd";
static MNEMONIC: &'static str =
"inject kidney empty canal shadow pact comfort wife crush horse wife sketch";

#[test]
fn sign_transaction() -> core::result::Result<(), failure::Error> {
let json: Value = serde_json::from_str(
r#" {
"visible": false,
"txID": "dc74fc99076e7638067753c5c9c3aa61f9ce208707ef6940e4ab8a4944b5d69f",
"raw_data": {
"contract": [
{
"parameter": {
"value": {
"amount": 100,
"owner_address": "41a1e81654258bf14f63feb2e8d1380075d45b0dac",
"to_address": "410b3e84ec677b3e63c99affcadb91a6b4e086798f"
},
"type_url": "type.googleapis.com/protocol.TransferContract"
},
"type": "TransferContract"
}
],
"ref_block_bytes": "0831",
"ref_block_hash": "b02efdc02638b61e",
"expiration": 1565866902000,
"timestamp": 1565866844064
},
"raw_data_hex": "0a0208312208b02efdc02638b61e40f083c3a7c92d5a65080112610a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412300a1541a1e81654258bf14f63feb2e8d1380075d45b0dac1215410b3e84ec677b3e63c99affcadb91a6b4e086798f186470a0bfbfa7c92d"
} "#,
)?;

let tx = Transaction::try_from(json)?;

let meta = Metadata::default();
let mut keystore = HdKeystore::from_mnemonic(&MNEMONIC, &PASSWORD, meta);

let coin_info = CoinInfo {
symbol: "TRON".to_string(),
derivation_path: "m/44'/145'/0'/0/0".to_string(),
curve: CurveType::SECP256k1,
};
let mut guard = KeystoreGuard::unlock_by_password(&mut keystore, PASSWORD).unwrap();

let _ = guard
.keystore_mut()
.derive_coin::<Address, EmptyExtra>(&coin_info);

let signed_tx = guard.keystore_mut().sign_transaction(&tx)?;

assert_eq!(signed_tx.raw["signature"][0].as_str().unwrap(), "beac4045c3ea5136b541a3d5ec2a3e5836d94f28a1371440a01258808612bc161b5417e6f5a342451303cda840f7e21bfaba1011fad5f63538cb8cc132a9768800", "signature must be correct");

Ok(())
}

#[test]
fn sign_message() {
let sk =
Secp256k1PrivateKey::from_wif("L2hfzPyVC1jWH7n2QLTe7tVTb6btg9smp5UVzhEBxLYaSFF7sCZB")
.unwrap();
let message =
hex_bytes("645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76").unwrap();
let header = "\x19TRON Signed Message:\n32".as_bytes();
let to_signed = [header.to_vec(), message].concat();

let hash = keccak(&to_signed);
let mut signed = sk.sign_recoverable(&hash).unwrap();
signed[64] = signed[64] + 27;
assert_eq!("7209610445e867cf2a36ea301bb5d1fbc3da597fd2ce4bb7fa64796fbf0620a4175e9f841cbf60d12c26737797217c0082fdb3caa8e44079e04ec3f93e86bbea1c", hex::encode(&signed))
}
}
Loading

0 comments on commit 6b06b44

Please sign in to comment.