Skip to content

Commit

Permalink
eth: add signing function parameter to specify receive address case
Browse files Browse the repository at this point in the history
  • Loading branch information
Tomasvrba committed Oct 30, 2024
1 parent 861af3f commit 5061668
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-npm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions examples/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async fn eth_demo<R: bitbox_api::runtime::Runtime>() {
1,
&"m/44'/60'/0'/0/0".try_into().unwrap(),
&raw_tx.as_slice().try_into().unwrap(),
None,
)
.await
.unwrap();
Expand All @@ -92,6 +93,7 @@ async fn eth_demo<R: bitbox_api::runtime::Runtime>() {
.eth_sign_1559_transaction(
&"m/44'/60'/0'/0/0".try_into().unwrap(),
&raw_tx.as_slice().try_into().unwrap(),
None,
)
.await
.unwrap();
Expand Down
6 changes: 4 additions & 2 deletions sandbox/src/Ethereum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
41 changes: 39 additions & 2 deletions src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ pub struct EIP1559Transaction {
pub data: Vec<u8>,
}

/// 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 = ();
Expand Down Expand Up @@ -465,6 +484,7 @@ impl<R: Runtime> PairedBitBox<R> {
chain_id: u64,
keypath: &Keypath,
tx: &Transaction,
address_case: Option<pb::EthAddressCase>,
) -> Result<[u8; 65], Error> {
// passing chainID instead of coin only since v9.10.0
self.validate_version(">=9.10.0")?;
Expand All @@ -483,7 +503,7 @@ impl<R: Runtime> PairedBitBox<R> {
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
Expand All @@ -496,6 +516,7 @@ impl<R: Runtime> PairedBitBox<R> {
&self,
keypath: &Keypath,
tx: &EIP1559Transaction,
address_case: Option<pb::EthAddressCase>,
) -> Result<[u8; 65], Error> {
// EIP1559 is suported from v9.16.0
self.validate_version(">=9.16.0")?;
Expand All @@ -516,7 +537,7 @@ impl<R: Runtime> PairedBitBox<R> {
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
Expand Down Expand Up @@ -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
);
}
}
20 changes: 18 additions & 2 deletions src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -410,10 +415,16 @@ impl PairedBitBox {
chain_id: u64,
keypath: types::TsKeypath,
tx: types::TsEthTransaction,
address_case: Option<types::TsEthAddressCase>,
) -> Result<types::TsEthSignature, JavascriptError> {
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])
Expand All @@ -433,10 +444,15 @@ impl PairedBitBox {
&self,
keypath: types::TsKeypath,
tx: types::TsEth1559Transaction,
address_case: Option<types::TsEthAddressCase>,
) -> Result<types::TsEthSignature, JavascriptError> {
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 {
Expand Down
17 changes: 17 additions & 0 deletions src/wasm/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -283,6 +286,20 @@ impl TryFrom<TsEth1559Transaction> for crate::eth::EIP1559Transaction {
}
}

impl TryFrom<TsEthAddressCase> for crate::pb::EthAddressCase {
type Error = JavascriptError;
fn try_from(value: TsEthAddressCase) -> Result<Self, Self::Error> {
serde_wasm_bindgen::from_value(value.into())
.map_err(|_| JavascriptError::InvalidType("wrong type for EthAddressCase"))
}
}

impl From<crate::pb::EthAddressCase> for TsEthAddressCase {
fn from(case: crate::pb::EthAddressCase) -> Self {
serde_wasm_bindgen::to_value(&case).unwrap().into()
}
}

impl TryFrom<TsCardanoNetwork> for crate::pb::CardanoNetwork {
type Error = JavascriptError;
fn try_from(value: TsCardanoNetwork) -> Result<Self, Self::Error> {
Expand Down

0 comments on commit 5061668

Please sign in to comment.