use aws_sdk_kms::{
primitives::Blob,
types::{KeySpec, KeyUsageType, MessageType, SigningAlgorithmSpec},
Client, Config,
};
use ethcontract_common::hash::keccak256;
use primitive_types::U256;
use rlp::{Rlp, RlpStream};
use secp256k1::constants::CURVE_ORDER;
use web3::{
signing::{self, Signature},
types::{Address, Bytes, SignedTransaction, TransactionParameters, H256},
Transport, Web3,
};
use crate::errors::ExecutionError;
#[derive(Clone, Debug)]
pub struct Account {
client: Client,
key_id: String,
address: Address,
}
impl Account {
pub async fn new(config: Config, key_id: &str) -> Result<Self, Error> {
let client = Client::from_conf(config);
let key_id = key_id.to_string();
let key = client
.get_public_key()
.key_id(&key_id)
.send()
.await
.map_err(aws_sdk_kms::Error::from)?;
if !matches!(
(key.key_spec(), key.key_usage()),
(
Some(&KeySpec::EccSecgP256K1),
Some(&KeyUsageType::SignVerify),
),
) {
return Err(Error::InvalidKey);
}
let info = key.public_key().unwrap().as_ref();
let uncompressed = &info[info.len().checked_sub(64).ok_or(Error::InvalidKey)?..];
let address = {
let mut buffer = Address::default();
let hash = keccak256(uncompressed);
buffer.0.copy_from_slice(&hash[12..]);
buffer
};
Ok(Self {
client,
key_id,
address,
})
}
pub fn public_address(&self) -> Address {
self.address
}
pub async fn sign(&self, message: [u8; 32]) -> Result<Signature, Error> {
let output = self
.client
.sign()
.key_id(&self.key_id)
.message(Blob::new(message))
.message_type(MessageType::Digest)
.signing_algorithm(SigningAlgorithmSpec::EcdsaSha256)
.send()
.await
.map_err(aws_sdk_kms::Error::from)?;
let signature = secp256k1::ecdsa::Signature::from_der(
output.signature().ok_or(Error::InvalidSignature)?.as_ref(),
)
.map_err(|_| Error::InvalidSignature)?;
let compact = signature.serialize_compact();
let mut r = H256::default();
r.0.copy_from_slice(&compact[..32]);
let mut s_tentative: U256 = U256::from_big_endian(&compact[32..]);
let secp256k1_n = U256::from_big_endian(&CURVE_ORDER);
if s_tentative > secp256k1_n / 2 {
s_tentative = secp256k1_n - s_tentative;
}
let mut s = H256::default();
s_tentative.to_big_endian(&mut s.0);
let v = if signing::recover(&message, &[r.as_bytes(), s.as_bytes()].concat(), 0).ok()
== Some(self.address)
{
0
} else if signing::recover(&message, &[r.as_bytes(), s.as_bytes()].concat(), 1).ok()
== Some(self.address)
{
1
} else {
return Err(Error::InvalidSignature);
};
Ok(Signature { v, r, s })
}
pub async fn sign_transaction<T>(
&self,
web3: Web3<T>,
params: TransactionParameters,
) -> Result<SignedTransaction, Error>
where
T: Transport,
{
let transaction = web3.accounts().sign_transaction(params, Key(self)).await?;
let signature = self.sign(transaction.message_hash.0).await?;
let v = transaction.v + signature.v;
let (id, raw) = match transaction.raw_transaction.0.first().copied() {
Some(x) if x < 0x80 => (Some(x), &transaction.raw_transaction.0[1..]),
_ => (None, &transaction.raw_transaction.0[..]),
};
let len = match Rlp::new(raw).prototype()? {
rlp::Prototype::List(len) => len
.checked_sub(3)
.ok_or(rlp::DecoderError::Custom("transaction fields too short"))?,
_ => return Err(rlp::DecoderError::RlpExpectedToBeList.into()),
};
let mut encoder = RlpStream::new_list(len + 3);
for item in Rlp::new(raw).iter().take(len) {
encoder.append_raw(item.as_raw(), 1);
}
encoder.append(&v);
encoder.append(&U256::from_big_endian(signature.r.as_bytes()));
encoder.append(&U256::from_big_endian(signature.s.as_bytes()));
let raw_transaction = Bytes(match id {
Some(id) => [&[id], encoder.as_raw()].concat(),
None => encoder.out().to_vec(),
});
let transaction_hash = H256(keccak256(&raw_transaction.0));
Ok(SignedTransaction {
message_hash: transaction.message_hash,
v,
r: signature.r,
s: signature.s,
raw_transaction,
transaction_hash,
})
}
}
struct Key<'a>(&'a Account);
impl web3::signing::Key for Key<'_> {
fn sign(
&self,
message: &[u8],
chain_id: Option<u64>,
) -> Result<Signature, web3::signing::SigningError> {
let signature = self.sign_message(message)?;
Ok(Signature {
v: if let Some(chain_id) = chain_id {
signature.v + 35 + chain_id * 2
} else {
signature.v + 27
},
..signature
})
}
fn sign_message(&self, _: &[u8]) -> Result<Signature, web3::signing::SigningError> {
Ok(Signature {
r: H256::default(),
s: H256::default(),
v: 0,
})
}
fn address(&self) -> Address {
self.0.public_address()
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Kms(#[from] aws_sdk_kms::Error),
#[error("invalid key")]
InvalidKey,
#[error("invalid signature")]
InvalidSignature,
#[error(transparent)]
Web3(#[from] web3::error::Error),
#[error(transparent)]
Rlp(#[from] rlp::DecoderError),
}
impl From<Error> for ExecutionError {
fn from(_: Error) -> Self {
web3::error::Error::Internal.into()
}
}