From 5061668af628f20dbd79483d7129c6b1e83fc317 Mon Sep 17 00:00:00 2001 From: Tomas Vrba Date: Sat, 28 Sep 2024 11:16:19 +0200 Subject: [PATCH] eth: add signing function parameter to specify receive address case --- CHANGELOG-npm.md | 1 + CHANGELOG-rust.md | 1 + examples/eth.rs | 2 ++ sandbox/src/Ethereum.tsx | 6 ++++-- src/eth.rs | 41 ++++++++++++++++++++++++++++++++++++++-- src/wasm/mod.rs | 20 ++++++++++++++++++-- src/wasm/types.rs | 17 +++++++++++++++++ 7 files changed, 82 insertions(+), 6 deletions(-) diff --git a/CHANGELOG-npm.md b/CHANGELOG-npm.md index cbf6953..37e0067 100644 --- a/CHANGELOG-npm.md +++ b/CHANGELOG-npm.md @@ -6,6 +6,7 @@ - btc: handle error when an input's previous transaction is required but missing - btc: add support for regtest - btc: add support for Taproot wallet policies +- eth: add method to help clients identify and specify address case (upper/lower/mixed) ## 0.6.0 diff --git a/CHANGELOG-rust.md b/CHANGELOG-rust.md index 609926f..fa2aac1 100644 --- a/CHANGELOG-rust.md +++ b/CHANGELOG-rust.md @@ -7,6 +7,7 @@ - btc: add support for regtest - btc: add support for Taproot wallet policies - cardano: added support for vote delegation +- eth: add method to help clients identify and specify address case (upper/lower/mixed) ## 0.5.0 diff --git a/examples/eth.rs b/examples/eth.rs index 323a107..c8c934d 100644 --- a/examples/eth.rs +++ b/examples/eth.rs @@ -81,6 +81,7 @@ async fn eth_demo() { 1, &"m/44'/60'/0'/0/0".try_into().unwrap(), &raw_tx.as_slice().try_into().unwrap(), + None, ) .await .unwrap(); @@ -92,6 +93,7 @@ async fn eth_demo() { .eth_sign_1559_transaction( &"m/44'/60'/0'/0/0".try_into().unwrap(), &raw_tx.as_slice().try_into().unwrap(), + None, ) .await .unwrap(); diff --git a/sandbox/src/Ethereum.tsx b/sandbox/src/Ethereum.tsx index 02ca9f7..74d73af 100644 --- a/sandbox/src/Ethereum.tsx +++ b/sandbox/src/Ethereum.tsx @@ -143,7 +143,8 @@ function EthSignTransaction({ bb02 } : Props) { value: new Uint8Array(hexToArrayBuffer(parsed.value)), data: new Uint8Array(hexToArrayBuffer(parsed.data)), }; - setResult(await bb02.ethSignTransaction(BigInt(chainID), keypath, tx)); + const addressCase = await bitbox.ethIdentifyCase(parsed.recipient); + setResult(await bb02.ethSignTransaction(BigInt(chainID), keypath, tx, addressCase)); } catch (err) { setErr(bitbox.ensureError(err)); } finally { @@ -224,7 +225,8 @@ function EthSignEIP1559Transaction({ bb02 } : Props) { value: new Uint8Array(hexToArrayBuffer(parsed.value)), data: new Uint8Array(hexToArrayBuffer(parsed.data)), }; - setResult(await bb02.ethSign1559Transaction(keypath, tx)); + const addressCase = bitbox.ethIdentifyCase(parsed.recipient); + setResult(await bb02.ethSign1559Transaction(keypath, tx, addressCase)); } catch (err) { setErr(bitbox.ensureError(err)); } finally { diff --git a/src/eth.rs b/src/eth.rs index bc68fa9..31dad8b 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -122,6 +122,25 @@ pub struct EIP1559Transaction { pub data: Vec, } +/// Identifies the case of the recipient address given as hexadecimal string. +/// This function exists as a convenience to help clients to determine the case of the +/// recipient address. +pub fn eth_identify_case(recipient_address: &str) -> pb::EthAddressCase { + if recipient_address + .chars() + .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_uppercase()) + { + pb::EthAddressCase::Upper + } else if recipient_address + .chars() + .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_lowercase()) + { + pb::EthAddressCase::Lower + } else { + pb::EthAddressCase::Mixed + } +} + #[cfg(feature = "rlp")] impl TryFrom<&[u8]> for Transaction { type Error = (); @@ -465,6 +484,7 @@ impl PairedBitBox { chain_id: u64, keypath: &Keypath, tx: &Transaction, + address_case: Option, ) -> Result<[u8; 65], Error> { // passing chainID instead of coin only since v9.10.0 self.validate_version(">=9.10.0")?; @@ -483,7 +503,7 @@ impl PairedBitBox { commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(), }), chain_id, - address_case: pb::EthAddressCase::Mixed as _, + address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(), }); let response = self.query_proto_eth(request).await?; self.handle_antiklepto(&response, host_nonce).await @@ -496,6 +516,7 @@ impl PairedBitBox { &self, keypath: &Keypath, tx: &EIP1559Transaction, + address_case: Option, ) -> Result<[u8; 65], Error> { // EIP1559 is suported from v9.16.0 self.validate_version(">=9.16.0")?; @@ -516,7 +537,7 @@ impl PairedBitBox { host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment { commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(), }), - address_case: pb::EthAddressCase::Mixed as _, + address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(), }); let response = self.query_proto_eth(request).await?; self.handle_antiklepto(&response, host_nonce).await @@ -1252,4 +1273,20 @@ mod tests { ) .is_err()); } + + #[test] + fn test_eth_identify_case() { + assert_eq!( + eth_identify_case("0XF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266"), + pb::EthAddressCase::Upper + ); + assert_eq!( + eth_identify_case("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + pb::EthAddressCase::Lower + ); + assert_eq!( + eth_identify_case("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + pb::EthAddressCase::Mixed + ); + } } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index da58d44..46eb3eb 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -100,6 +100,11 @@ pub fn is_user_abort(err: types::TsError) -> bool { } } +#[wasm_bindgen(js_name = ethIdentifyCase)] +pub fn eth_identify_case(recipient_address: &str) -> types::TsEthAddressCase { + crate::eth::eth_identify_case(recipient_address).into() +} + #[wasm_bindgen(raw_module = "./webhid")] extern "C" { async fn jsSleep(millis: f64); @@ -410,10 +415,16 @@ impl PairedBitBox { chain_id: u64, keypath: types::TsKeypath, tx: types::TsEthTransaction, + address_case: Option, ) -> Result { let signature = self .device - .eth_sign_transaction(chain_id, &keypath.try_into()?, &tx.try_into()?) + .eth_sign_transaction( + chain_id, + &keypath.try_into()?, + &tx.try_into()?, + address_case.map(TryInto::try_into).transpose()?, + ) .await?; let v: u64 = compute_v(chain_id, signature[64]) @@ -433,10 +444,15 @@ impl PairedBitBox { &self, keypath: types::TsKeypath, tx: types::TsEth1559Transaction, + address_case: Option, ) -> Result { let signature = self .device - .eth_sign_1559_transaction(&keypath.try_into()?, &tx.try_into()?) + .eth_sign_1559_transaction( + &keypath.try_into()?, + &tx.try_into()?, + address_case.map(TryInto::try_into).transpose()?, + ) .await?; Ok(serde_wasm_bindgen::to_value(&types::EthSignature { diff --git a/src/wasm/types.rs b/src/wasm/types.rs index bfc889d..d8c5b5e 100644 --- a/src/wasm/types.rs +++ b/src/wasm/types.rs @@ -75,6 +75,7 @@ type EthSignature = { s: Uint8Array; v: Uint8Array; }; +type EthAddressCase = 'upper' | 'lower' | 'mixed'; type CardanoXpub = Uint8Array; type CardanoXpubs = CardanoXpub[]; type CardanoNetwork = 'mainnet' | 'testnet'; @@ -186,6 +187,8 @@ extern "C" { pub type TsEth1559Transaction; #[wasm_bindgen(typescript_type = "EthSignature")] pub type TsEthSignature; + #[wasm_bindgen(typescript_type = "EthAddressCase")] + pub type TsEthAddressCase; #[wasm_bindgen(typescript_type = "CardanoXpub")] pub type TsCardanoXpub; #[wasm_bindgen(typescript_type = "CardanoXpubs")] @@ -283,6 +286,20 @@ impl TryFrom for crate::eth::EIP1559Transaction { } } +impl TryFrom for crate::pb::EthAddressCase { + type Error = JavascriptError; + fn try_from(value: TsEthAddressCase) -> Result { + serde_wasm_bindgen::from_value(value.into()) + .map_err(|_| JavascriptError::InvalidType("wrong type for EthAddressCase")) + } +} + +impl From for TsEthAddressCase { + fn from(case: crate::pb::EthAddressCase) -> Self { + serde_wasm_bindgen::to_value(&case).unwrap().into() + } +} + impl TryFrom for crate::pb::CardanoNetwork { type Error = JavascriptError; fn try_from(value: TsCardanoNetwork) -> Result {