Skip to content

Commit

Permalink
feat: sign eos using rfc6979+nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
XuNeal committed Jan 25, 2024
1 parent e465bbc commit 77e0cbe
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 21 deletions.
185 changes: 170 additions & 15 deletions token-core/tcx-eos/src/signer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::vec;

use crate::transaction::{EosMessageInput, EosMessageOutput, EosTxInput, EosTxOutput, SigData};
use tcx_keystore::{
tcx_ensure, Keystore, MessageSigner, Result, SignatureParameters, Signer, TransactionSigner,
Expand All @@ -14,6 +16,40 @@ fn serial_eos_sig(sig: &[u8]) -> String {
format!("SIG_K1_{}", base58::encode_slice(&data))
}

fn is_canonical(sig: &[u8]) -> bool {
!(sig[0] & 0x80 != 0)
&& !(sig[0] == 0 && !(sig[1] & 0x80 != 0))
&& !(sig[32] & 0x80 != 0)
&& !(sig[32] == 0 && !(sig[33] & 0x80 != 0))
}

fn i32_to_u8_array(value: i32) -> [u8; 32] {
let bytes = value.to_be_bytes();
let mut result = [0u8; 32];
result[..4].copy_from_slice(&bytes);
result
}

fn eos_sign(keystore: &mut Keystore, hashed: &[u8], path: &str) -> Result<String> {
let mut sign_result: Vec<u8> = vec![];
let mut is_canon = false;
for nonce in 0..1000 {
sign_result = keystore.secp256k1_ecdsa_sign_recoverable_with_noncedata(
hashed,
&path,
&i32_to_u8_array(nonce),
)?;
if is_canonical(&sign_result) {
is_canon = true;
break;
}
}
tcx_ensure!(is_canon, anyhow!("cannot generate a eos canon sig"));
sign_result[64] += 27 + 4;
let sig = [sign_result[64..].to_vec(), sign_result[..64].to_vec()].concat();
Ok(serial_eos_sig(&sig))
}

impl TransactionSigner<EosTxInput, EosTxOutput> for Keystore {
fn sign_transaction(
&mut self,
Expand All @@ -33,12 +69,11 @@ impl TransactionSigner<EosTxInput, EosTxOutput> for Keystore {
]
.concat();
let hashed_tx = sha256(&tx_with_chain_id);
let sign_result = self
.secp256k1_ecdsa_sign_recoverable(hashed_tx.as_slice(), &params.derivation_path)?;
let eos_sig = eos_sign(self, &hashed_tx, &params.derivation_path)?;
// EOS need v r s
let eos_sig = [sign_result[64..].to_vec(), sign_result[..64].to_vec()].concat();

eos_sigs.push(SigData {
signature: serial_eos_sig(&eos_sig),
signature: eos_sig,
hash: tx_hash.to_0x_hex(),
});
}
Expand All @@ -63,18 +98,138 @@ impl MessageSigner<EosMessageInput, EosMessageOutput> for Keystore {
data_hashed.len() == 32,
anyhow!("{}", "hashed data must be 32 bytes")
);
let sign_result =
self.secp256k1_ecdsa_sign_recoverable(data_hashed.as_slice(), &params.derivation_path)?;
// EOS need v r s
let eos_sig = [sign_result[64..].to_vec(), sign_result[..64].to_vec()].concat();
Ok(EosMessageOutput {
signature: serial_eos_sig(&eos_sig),
})

let eos_sig = eos_sign(self, &data_hashed, &params.derivation_path)?;
Ok(EosMessageOutput { signature: eos_sig })
}
}

// TODO: sign eos using RFC 6979 need new testcase
// #[cfg(test)]
// mod tests {
#[cfg(test)]
mod tests {
use std::vec;

use bitcoin::hashes::hex::ToHex;
use tcx_constants::{TEST_MNEMONIC, TEST_PASSWORD};
use tcx_keystore::{
HdKeystore, Keystore, MessageSigner, Metadata, PrivateKeystore, SignatureParameters,
TransactionSigner,
};
use tcx_primitive::{PrivateKey, Secp256k1PrivateKey};

use crate::transaction::{EosMessageInput, EosTxInput};

#[test]
fn test_eos_sign_tx() {
let meta = Metadata::default();

let hd_keystore = HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, meta).unwrap();
let mut keystore = Keystore::Hd(hd_keystore);
keystore.unlock_by_password(TEST_PASSWORD).unwrap();
let sign_param = SignatureParameters {
chain_type: "EOS".to_string(),
derivation_path: "m/44'/194'/0'/0/0".to_string(),
..SignatureParameters::default()
};
let tx_input = EosTxInput {
chain_id: "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906".to_string(),
tx_hexs: vec!["2b03b26547b625edc1c6000000000100a6823403ea3055000000572d3ccdcd0130069b34b2a9a48b00000000a8ed32322130069b34b2a9a48b10425e79aa47b374640000000000000004454f53000000000000".to_string(),
"8c05b2650abbf70ba628000000000100a6823403ea3055000000572d3ccdcd0130069b34b2a9a48b00000000a8ed32322130069b34b2a9a48b10425e79aa47b374e80300000000000004454f53000000000000".to_string(),
"5a09b265a5c205ed6bea000000000100a6823403ea3055000000572d3ccdcd0110425e79aa47b37400000000a8ed32322110425e79aa47b37430069b34b2a9a48b640000000000000004454f53000000000000".to_string(),
"db0bb265a7c74d49719c000000000100a6823403ea3055000000572d3ccdcd0110425e79aa47b37400000000a8ed32322110425e79aa47b37430069b34b2a9a48be80300000000000004454f53000000000000".to_string(),
"c578065b93aec6a7c811000000000100a6823403ea3055000000572d3ccdcd01000000602a48b37400000000a8ed323225000000602a48b374208410425c95b1ca80969800000000000453595300000000046d656d6f00".to_string()]

};
let ret = keystore.sign_transaction(&sign_param, &tx_input).unwrap();
assert_eq!(ret.sig_data[0].signature, "SIG_K1_KYgqnbZkL57TAJtgZ4ntrCxQ38B313WWpZEDyGwA7s4sjmyHissY6WAeCdyYHukBWp2QsqEH8hdtQLchR2LZSzMhvvHmCm");
assert_eq!(
ret.sig_data[0].hash,
"0x0461387aa99644399b1c8c876805fc775f96e6a00ce18ffbe4eaa930ad6e7af8"
);
assert_eq!(ret.sig_data[1].signature, "SIG_K1_Jy7PBEwCpvvf5k4yzrqq1KeBCZi5qru7mp3CspNw8n8xENpN8Ar6s3ckuEeH66Rd9QFbUZzrD4pAemkBEWMyBM7PBdDR4t");
assert_eq!(
ret.sig_data[1].hash,
"0xe36d9a49ca7768198a092c5b3f9b9766343ff14eb1fde851bed9cbda2ef1ab58"
);
assert_eq!(ret.sig_data[2].signature, "SIG_K1_K1iY4LUoLwnYVFMaWZddr74NSmLcCBDEysybA7oTLMn7dYtFdeHpy8oSv4rEdGYoa8rzsE17QaPJikSyjDY4t3EeK2m1ir");
assert_eq!(ret.sig_data[3].signature, "SIG_K1_Ki7M5TB9Di3i2orD1ntym5xmhh5rAeJPK8XxNfAUjeNc3SQyMA9UZ37ptfTLjngb9cfhdBG3j2DQXrderrXH59t5DcHwgT");
assert_eq!(ret.sig_data[4].signature, "SIG_K1_K7EUD2iuUi4QFgTNuondGqjaWJ4AWzp1EMhqKg4t1oGoSKhjvTpfqv6EcD6M2R8qQvJjf7f2mV8zHEXHgLKH985DU1JPyf");
}

#[test]
fn test_pk_store_eos_sign_msg() {
let meta = Metadata::default();

// }
let hex_sec_key =
Secp256k1PrivateKey::from_wif("5KAigHMamRhN7uwHFnk3yz7vUTyQT1nmXoAA899XpZKJpkqsPFp")
.unwrap()
.to_bytes()
.to_hex();
let pk_keystore = PrivateKeystore::from_private_key(
&hex_sec_key,
TEST_PASSWORD,
tcx_constants::CurveType::SECP256k1,
meta,
None,
)
.unwrap();
let mut keystore = Keystore::PrivateKey(pk_keystore);
keystore.unlock_by_password(TEST_PASSWORD).unwrap();
let sign_param = SignatureParameters {
chain_type: "EOS".to_string(),
..SignatureParameters::default()
};
let tx_input = EosTxInput {
chain_id: "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906".to_string(),
tx_hexs: vec!["2b03b26547b625edc1c6000000000100a6823403ea3055000000572d3ccdcd0130069b34b2a9a48b00000000a8ed32322130069b34b2a9a48b10425e79aa47b374640000000000000004454f53000000000000".to_string(),
"8c05b2650abbf70ba628000000000100a6823403ea3055000000572d3ccdcd0130069b34b2a9a48b00000000a8ed32322130069b34b2a9a48b10425e79aa47b374e80300000000000004454f53000000000000".to_string(),
"5a09b265a5c205ed6bea000000000100a6823403ea3055000000572d3ccdcd0110425e79aa47b37400000000a8ed32322110425e79aa47b37430069b34b2a9a48b640000000000000004454f53000000000000".to_string(),
"db0bb265a7c74d49719c000000000100a6823403ea3055000000572d3ccdcd0110425e79aa47b37400000000a8ed32322110425e79aa47b37430069b34b2a9a48be80300000000000004454f53000000000000".to_string(),
"c578065b93aec6a7c811000000000100a6823403ea3055000000572d3ccdcd01000000602a48b37400000000a8ed323225000000602a48b374208410425c95b1ca80969800000000000453595300000000046d656d6f00".to_string()]

};
let ret = keystore.sign_transaction(&sign_param, &tx_input).unwrap();
assert_eq!(ret.sig_data[0].signature, "SIG_K1_KYgqnbZkL57TAJtgZ4ntrCxQ38B313WWpZEDyGwA7s4sjmyHissY6WAeCdyYHukBWp2QsqEH8hdtQLchR2LZSzMhvvHmCm");
assert_eq!(
ret.sig_data[0].hash,
"0x0461387aa99644399b1c8c876805fc775f96e6a00ce18ffbe4eaa930ad6e7af8"
);
assert_eq!(ret.sig_data[1].signature, "SIG_K1_Jy7PBEwCpvvf5k4yzrqq1KeBCZi5qru7mp3CspNw8n8xENpN8Ar6s3ckuEeH66Rd9QFbUZzrD4pAemkBEWMyBM7PBdDR4t");
assert_eq!(
ret.sig_data[1].hash,
"0xe36d9a49ca7768198a092c5b3f9b9766343ff14eb1fde851bed9cbda2ef1ab58"
);
assert_eq!(ret.sig_data[2].signature, "SIG_K1_K1iY4LUoLwnYVFMaWZddr74NSmLcCBDEysybA7oTLMn7dYtFdeHpy8oSv4rEdGYoa8rzsE17QaPJikSyjDY4t3EeK2m1ir");
assert_eq!(ret.sig_data[3].signature, "SIG_K1_Ki7M5TB9Di3i2orD1ntym5xmhh5rAeJPK8XxNfAUjeNc3SQyMA9UZ37ptfTLjngb9cfhdBG3j2DQXrderrXH59t5DcHwgT");
assert_eq!(ret.sig_data[4].signature, "SIG_K1_K7EUD2iuUi4QFgTNuondGqjaWJ4AWzp1EMhqKg4t1oGoSKhjvTpfqv6EcD6M2R8qQvJjf7f2mV8zHEXHgLKH985DU1JPyf");
}

#[test]
fn test_eos_sign_msg() {
let meta = Metadata::default();

let hex_sec_key =
Secp256k1PrivateKey::from_wif("5HxQKWDznancXZXm7Gr2guadK7BhK9Zs8ejDhfA9oEBM89ZaAru")
.unwrap()
.to_bytes()
.to_hex();
let pk_keystore = PrivateKeystore::from_private_key(
&hex_sec_key,
TEST_PASSWORD,
tcx_constants::CurveType::SECP256k1,
meta,
None,
)
.unwrap();
let mut keystore = Keystore::PrivateKey(pk_keystore);
keystore.unlock_by_password(TEST_PASSWORD).unwrap();
let sign_param = SignatureParameters {
chain_type: "EOS".to_string(),
..SignatureParameters::default()
};
let tx_input = EosMessageInput {
data: "0x6cb75bc5a46a7fdb64b92efefca01ed7b060ab5e0d625226e8efbc0980c3ddc1".to_string(),
};
let ret = keystore.sign_message(&sign_param, &tx_input).unwrap();
assert_eq!(ret.signature, "SIG_K1_KkkPJXMxGUUeS6b5FmKrXE448N1Gc4x87j4JLVuENuba5QRUmFczGe9EmzeoCajRH5YLGEGcYjWSXxxfR5b6RTCoNUdCVy");
}
}
16 changes: 16 additions & 0 deletions token-core/tcx-keystore/src/keystore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,22 @@ impl Signer for Keystore {
private_key.as_secp256k1()?.sign_recoverable(hash)
}

fn secp256k1_ecdsa_sign_recoverable_with_noncedata(
&mut self,
hash: &[u8],
derivation_path: &str,
noncedata: &[u8; 32],
) -> Result<Vec<u8>> {
let private_key = match self {
Keystore::PrivateKey(ks) => ks.get_private_key(CurveType::SECP256k1)?,
Keystore::Hd(ks) => ks.get_private_key(CurveType::SECP256k1, derivation_path)?,
};

private_key
.as_secp256k1()?
.sign_recoverable_with_noncedata(hash, noncedata)
}

fn bls_sign(&mut self, hash: &[u8], derivation_path: &str, dst: &str) -> Result<Vec<u8>> {
let private_key = match self {
Keystore::PrivateKey(ks) => ks.get_private_key(CurveType::BLS)?,
Expand Down
2 changes: 1 addition & 1 deletion token-core/tcx-keystore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub use keystore::{
PrivateKeystore, PublicKeyEncoder, Source,
};

pub use signer::{HashSigner, MessageSigner, SignatureParameters, Signer, TransactionSigner};
pub use signer::{MessageSigner, SignatureParameters, Signer, TransactionSigner};

use thiserror::Error;

Expand Down
12 changes: 7 additions & 5 deletions token-core/tcx-keystore/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ pub trait MessageSigner<Input, Output> {
fn sign_message(&mut self, params: &SignatureParameters, message: &Input) -> Result<Output>;
}

// The ec_sign
pub trait HashSigner {
fn sign(&self, ks: &mut Keystore, symbol: &str, address: &str, hash: &[u8]) -> Result<Vec<u8>>;
}

pub trait Signer {
fn sign_hash(
&mut self,
Expand All @@ -52,6 +47,13 @@ pub trait Signer {
derivation_path: &str,
) -> Result<Vec<u8>>;

fn secp256k1_ecdsa_sign_recoverable_with_noncedata(
&mut self,
hash: &[u8],
derivation_path: &str,
noncedata: &[u8; 32],
) -> Result<Vec<u8>>;

fn bls_sign(&mut self, hash: &[u8], derivation_path: &str, dst: &str) -> Result<Vec<u8>>;

fn sr25519_sign(&mut self, hash: &[u8], derivation_path: &str) -> Result<Vec<u8>>;
Expand Down
13 changes: 13 additions & 0 deletions token-core/tcx-primitive/src/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ impl Secp256k1PrivateKey {
let signed_bytes = [sign[..].to_vec(), vec![(recover_id.to_i32()) as u8]].concat();
Ok(signed_bytes)
}

pub fn sign_recoverable_with_noncedata(
&self,
data: &[u8],
noncedata: &[u8; 32],
) -> Result<Vec<u8>> {
let msg = secp256k1::Message::from_slice(data).map_err(transform_secp256k1_error)?;
let signature =
SECP256K1_ENGINE.sign_ecdsa_recoverable_with_noncedata(&msg, &self.0.inner, noncedata);
let (recover_id, sign) = signature.serialize_compact();
let signed_bytes = [sign[..].to_vec(), vec![(recover_id.to_i32()) as u8]].concat();
Ok(signed_bytes)
}
}

impl TraitPrivateKey for Secp256k1PrivateKey {
Expand Down
4 changes: 4 additions & 0 deletions token-core/tcx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ mod tests {
);

assert_eq!("", derived_accounts.accounts[10].address);
assert_eq!(
"EOS7Nf9TU1vZaQQgZA3cELTHJf1nnDJ6xVvqHvVzbHehsgcjrzNkq",
derived_accounts.accounts[10].public_key
);
assert_eq!(
"0x37c6713aa848bCdeE372A620eEbCdcCBA55c695F",
derived_accounts.accounts[11].address
Expand Down

0 comments on commit 77e0cbe

Please sign in to comment.