use candid::Principal;
use ethers_core::abi::ethereum_types::{Address, U256};
use ethers_core::types::transaction::eip1559::Eip1559TransactionRequest;
use ethers_core::types::Signature;
use ethers_core::utils::{hex, keccak256};
use ic_cdk::api::management_canister::ecdsa::{
ecdsa_public_key, sign_with_ecdsa, EcdsaKeyId, EcdsaPublicKeyArgument, SignWithEcdsaArgument,
};
#[derive(Debug, Clone)]
pub struct SignedTransaction {
pub tx_hex: String,
pub tx_hash: String,
}
pub async fn get_canister_public_key(
key_id: EcdsaKeyId,
canister_id: Option<Principal>,
derivation_path: Vec<Vec<u8>>,
) -> Vec<u8> {
let (key,) = ecdsa_public_key(EcdsaPublicKeyArgument {
canister_id,
derivation_path,
key_id,
})
.await
.expect("failed to get public key");
key.public_key
}
pub async fn sign_eip1559_transaction(
tx: Eip1559TransactionRequest,
key_id: EcdsaKeyId,
derivation_path: Vec<Vec<u8>>,
) -> SignedTransaction {
const EIP1559_TX_ID: u8 = 2;
let ecdsa_pub_key =
get_canister_public_key(key_id.clone(), None, derivation_path.clone()).await;
let mut unsigned_tx_bytes = tx.rlp().to_vec();
unsigned_tx_bytes.insert(0, EIP1559_TX_ID);
let txhash = keccak256(&unsigned_tx_bytes);
let signature = sign_with_ecdsa(SignWithEcdsaArgument {
message_hash: txhash.to_vec(),
derivation_path,
key_id,
})
.await
.expect("failed to sign the transaction")
.0
.signature;
let signature = Signature {
v: y_parity(&txhash, &signature, &ecdsa_pub_key),
r: U256::from_big_endian(&signature[0..32]),
s: U256::from_big_endian(&signature[32..64]),
};
let mut signed_tx_bytes = tx.rlp_signed(&signature).to_vec();
signed_tx_bytes.insert(0, EIP1559_TX_ID);
SignedTransaction {
tx_hex: format!("0x{}", hex::encode(&signed_tx_bytes)),
tx_hash: format!("0x{}", hex::encode(keccak256(&signed_tx_bytes))),
}
}
pub fn pubkey_bytes_to_address(pubkey_bytes: &[u8]) -> String {
use ethers_core::k256::elliptic_curve::sec1::ToEncodedPoint;
use ethers_core::k256::PublicKey;
let key =
PublicKey::from_sec1_bytes(pubkey_bytes).expect("failed to parse the public key as SEC1");
let point = key.to_encoded_point(false);
let point_bytes = point.as_bytes();
assert_eq!(point_bytes[0], 0x04);
let hash = keccak256(&point_bytes[1..]);
ethers_core::utils::to_checksum(&Address::from_slice(&hash[12..32]), None)
}
fn y_parity(prehash: &[u8], sig: &[u8], pubkey: &[u8]) -> u64 {
use ethers_core::k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
let orig_key = VerifyingKey::from_sec1_bytes(pubkey).expect("failed to parse the pubkey");
let signature = Signature::try_from(sig).unwrap();
for parity in [0u8, 1] {
let recid = RecoveryId::try_from(parity).unwrap();
let recovered_key = VerifyingKey::recover_from_prehash(prehash, &signature, recid)
.expect("failed to recover key");
if recovered_key == orig_key {
return parity as u64;
}
}
panic!(
"failed to recover the parity bit from a signature; sig: {}, pubkey: {}",
hex::encode(sig),
hex::encode(pubkey)
)
}