Skip to content

Commit 5f22d90

Browse files
committed
Merge commit 'refs/pull/90/head' of github.com:BitBoxSwiss/bitbox-api-rs
2 parents 861af3f + 5061668 commit 5f22d90

File tree

7 files changed

+82
-6
lines changed

7 files changed

+82
-6
lines changed

CHANGELOG-npm.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- btc: handle error when an input's previous transaction is required but missing
77
- btc: add support for regtest
88
- btc: add support for Taproot wallet policies
9+
- eth: add method to help clients identify and specify address case (upper/lower/mixed)
910

1011
## 0.6.0
1112

CHANGELOG-rust.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- btc: add support for regtest
88
- btc: add support for Taproot wallet policies
99
- cardano: added support for vote delegation
10+
- eth: add method to help clients identify and specify address case (upper/lower/mixed)
1011

1112
## 0.5.0
1213

examples/eth.rs

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async fn eth_demo<R: bitbox_api::runtime::Runtime>() {
8181
1,
8282
&"m/44'/60'/0'/0/0".try_into().unwrap(),
8383
&raw_tx.as_slice().try_into().unwrap(),
84+
None,
8485
)
8586
.await
8687
.unwrap();
@@ -92,6 +93,7 @@ async fn eth_demo<R: bitbox_api::runtime::Runtime>() {
9293
.eth_sign_1559_transaction(
9394
&"m/44'/60'/0'/0/0".try_into().unwrap(),
9495
&raw_tx.as_slice().try_into().unwrap(),
96+
None,
9597
)
9698
.await
9799
.unwrap();

sandbox/src/Ethereum.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ function EthSignTransaction({ bb02 } : Props) {
143143
value: new Uint8Array(hexToArrayBuffer(parsed.value)),
144144
data: new Uint8Array(hexToArrayBuffer(parsed.data)),
145145
};
146-
setResult(await bb02.ethSignTransaction(BigInt(chainID), keypath, tx));
146+
const addressCase = await bitbox.ethIdentifyCase(parsed.recipient);
147+
setResult(await bb02.ethSignTransaction(BigInt(chainID), keypath, tx, addressCase));
147148
} catch (err) {
148149
setErr(bitbox.ensureError(err));
149150
} finally {
@@ -224,7 +225,8 @@ function EthSignEIP1559Transaction({ bb02 } : Props) {
224225
value: new Uint8Array(hexToArrayBuffer(parsed.value)),
225226
data: new Uint8Array(hexToArrayBuffer(parsed.data)),
226227
};
227-
setResult(await bb02.ethSign1559Transaction(keypath, tx));
228+
const addressCase = bitbox.ethIdentifyCase(parsed.recipient);
229+
setResult(await bb02.ethSign1559Transaction(keypath, tx, addressCase));
228230
} catch (err) {
229231
setErr(bitbox.ensureError(err));
230232
} finally {

src/eth.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,25 @@ pub struct EIP1559Transaction {
122122
pub data: Vec<u8>,
123123
}
124124

125+
/// Identifies the case of the recipient address given as hexadecimal string.
126+
/// This function exists as a convenience to help clients to determine the case of the
127+
/// recipient address.
128+
pub fn eth_identify_case(recipient_address: &str) -> pb::EthAddressCase {
129+
if recipient_address
130+
.chars()
131+
.all(|c| !c.is_ascii_alphabetic() || c.is_ascii_uppercase())
132+
{
133+
pb::EthAddressCase::Upper
134+
} else if recipient_address
135+
.chars()
136+
.all(|c| !c.is_ascii_alphabetic() || c.is_ascii_lowercase())
137+
{
138+
pb::EthAddressCase::Lower
139+
} else {
140+
pb::EthAddressCase::Mixed
141+
}
142+
}
143+
125144
#[cfg(feature = "rlp")]
126145
impl TryFrom<&[u8]> for Transaction {
127146
type Error = ();
@@ -465,6 +484,7 @@ impl<R: Runtime> PairedBitBox<R> {
465484
chain_id: u64,
466485
keypath: &Keypath,
467486
tx: &Transaction,
487+
address_case: Option<pb::EthAddressCase>,
468488
) -> Result<[u8; 65], Error> {
469489
// passing chainID instead of coin only since v9.10.0
470490
self.validate_version(">=9.10.0")?;
@@ -483,7 +503,7 @@ impl<R: Runtime> PairedBitBox<R> {
483503
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
484504
}),
485505
chain_id,
486-
address_case: pb::EthAddressCase::Mixed as _,
506+
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
487507
});
488508
let response = self.query_proto_eth(request).await?;
489509
self.handle_antiklepto(&response, host_nonce).await
@@ -496,6 +516,7 @@ impl<R: Runtime> PairedBitBox<R> {
496516
&self,
497517
keypath: &Keypath,
498518
tx: &EIP1559Transaction,
519+
address_case: Option<pb::EthAddressCase>,
499520
) -> Result<[u8; 65], Error> {
500521
// EIP1559 is suported from v9.16.0
501522
self.validate_version(">=9.16.0")?;
@@ -516,7 +537,7 @@ impl<R: Runtime> PairedBitBox<R> {
516537
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
517538
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
518539
}),
519-
address_case: pb::EthAddressCase::Mixed as _,
540+
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
520541
});
521542
let response = self.query_proto_eth(request).await?;
522543
self.handle_antiklepto(&response, host_nonce).await
@@ -1252,4 +1273,20 @@ mod tests {
12521273
)
12531274
.is_err());
12541275
}
1276+
1277+
#[test]
1278+
fn test_eth_identify_case() {
1279+
assert_eq!(
1280+
eth_identify_case("0XF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266"),
1281+
pb::EthAddressCase::Upper
1282+
);
1283+
assert_eq!(
1284+
eth_identify_case("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"),
1285+
pb::EthAddressCase::Lower
1286+
);
1287+
assert_eq!(
1288+
eth_identify_case("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
1289+
pb::EthAddressCase::Mixed
1290+
);
1291+
}
12551292
}

src/wasm/mod.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ pub fn is_user_abort(err: types::TsError) -> bool {
100100
}
101101
}
102102

103+
#[wasm_bindgen(js_name = ethIdentifyCase)]
104+
pub fn eth_identify_case(recipient_address: &str) -> types::TsEthAddressCase {
105+
crate::eth::eth_identify_case(recipient_address).into()
106+
}
107+
103108
#[wasm_bindgen(raw_module = "./webhid")]
104109
extern "C" {
105110
async fn jsSleep(millis: f64);
@@ -410,10 +415,16 @@ impl PairedBitBox {
410415
chain_id: u64,
411416
keypath: types::TsKeypath,
412417
tx: types::TsEthTransaction,
418+
address_case: Option<types::TsEthAddressCase>,
413419
) -> Result<types::TsEthSignature, JavascriptError> {
414420
let signature = self
415421
.device
416-
.eth_sign_transaction(chain_id, &keypath.try_into()?, &tx.try_into()?)
422+
.eth_sign_transaction(
423+
chain_id,
424+
&keypath.try_into()?,
425+
&tx.try_into()?,
426+
address_case.map(TryInto::try_into).transpose()?,
427+
)
417428
.await?;
418429

419430
let v: u64 = compute_v(chain_id, signature[64])
@@ -433,10 +444,15 @@ impl PairedBitBox {
433444
&self,
434445
keypath: types::TsKeypath,
435446
tx: types::TsEth1559Transaction,
447+
address_case: Option<types::TsEthAddressCase>,
436448
) -> Result<types::TsEthSignature, JavascriptError> {
437449
let signature = self
438450
.device
439-
.eth_sign_1559_transaction(&keypath.try_into()?, &tx.try_into()?)
451+
.eth_sign_1559_transaction(
452+
&keypath.try_into()?,
453+
&tx.try_into()?,
454+
address_case.map(TryInto::try_into).transpose()?,
455+
)
440456
.await?;
441457

442458
Ok(serde_wasm_bindgen::to_value(&types::EthSignature {

src/wasm/types.rs

+17
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type EthSignature = {
7575
s: Uint8Array;
7676
v: Uint8Array;
7777
};
78+
type EthAddressCase = 'upper' | 'lower' | 'mixed';
7879
type CardanoXpub = Uint8Array;
7980
type CardanoXpubs = CardanoXpub[];
8081
type CardanoNetwork = 'mainnet' | 'testnet';
@@ -186,6 +187,8 @@ extern "C" {
186187
pub type TsEth1559Transaction;
187188
#[wasm_bindgen(typescript_type = "EthSignature")]
188189
pub type TsEthSignature;
190+
#[wasm_bindgen(typescript_type = "EthAddressCase")]
191+
pub type TsEthAddressCase;
189192
#[wasm_bindgen(typescript_type = "CardanoXpub")]
190193
pub type TsCardanoXpub;
191194
#[wasm_bindgen(typescript_type = "CardanoXpubs")]
@@ -283,6 +286,20 @@ impl TryFrom<TsEth1559Transaction> for crate::eth::EIP1559Transaction {
283286
}
284287
}
285288

289+
impl TryFrom<TsEthAddressCase> for crate::pb::EthAddressCase {
290+
type Error = JavascriptError;
291+
fn try_from(value: TsEthAddressCase) -> Result<Self, Self::Error> {
292+
serde_wasm_bindgen::from_value(value.into())
293+
.map_err(|_| JavascriptError::InvalidType("wrong type for EthAddressCase"))
294+
}
295+
}
296+
297+
impl From<crate::pb::EthAddressCase> for TsEthAddressCase {
298+
fn from(case: crate::pb::EthAddressCase) -> Self {
299+
serde_wasm_bindgen::to_value(&case).unwrap().into()
300+
}
301+
}
302+
286303
impl TryFrom<TsCardanoNetwork> for crate::pb::CardanoNetwork {
287304
type Error = JavascriptError;
288305
fn try_from(value: TsCardanoNetwork) -> Result<Self, Self::Error> {

0 commit comments

Comments
 (0)