Skip to content

Commit dc537c3

Browse files
committed
eth: add option to disable antiklepto in eth_sign_typed_msg
1 parent 15f6243 commit dc537c3

7 files changed

Lines changed: 156 additions & 7 deletions

File tree

CHANGELOG-npm.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44
- eth: add support for streaming transactions with large data
5+
- eth: add optional `useAntiklepto` argument to `ethSignTypedMessage()` (set to `false` for
6+
deterministic typed-message signatures, firmware >=9.26.0)
57

68
## 0.12.0
79
- btc: add support for OP_RETURN outputs

CHANGELOG-rust.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44
- eth: add support for streaming transactions with large data
5+
- eth: add `use_antiklepto` toggle to `eth_sign_typed_message()` (set `false` for deterministic
6+
typed-message signatures, firmware >=9.26.0)
57

68
## 0.11.0
79
- btc: add support for OP_RETURN outputs

examples/eth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async fn eth_demo<R: bitbox_api::runtime::Runtime>() {
110110

111111
println!("Signign typed message...");
112112
let signature = paired_bitbox
113-
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), EIP712_MSG)
113+
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), EIP712_MSG, true)
114114
.await
115115
.unwrap();
116116
println!("Signature: {}", hex::encode(signature));

sandbox/src/Ethereum.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ function EthSignTypedMessage({ bb02 } : Props) {
392392
const [chainID, setChainID] = useState(1);
393393
const [keypath, setKeypath] = useState('m/44\'/60\'/0\'/0/0');
394394
const [msg, setMsg] = useState(exampleMsg);
395+
const [useAntiklepto, setUseAntiklepto] = useState(true);
395396
const [result, setResult] = useState<bitbox.EthSignature | undefined>();
396397
const [running, setRunning] = useState(false);
397398
const [err, setErr] = useState<bitbox.Error>();
@@ -404,7 +405,7 @@ function EthSignTypedMessage({ bb02 } : Props) {
404405
setResult(undefined);
405406
setErr(undefined);
406407
try {
407-
setResult(await bb02.ethSignTypedMessage(BigInt(chainID), keypath, JSON.parse(msg)));
408+
setResult(await bb02.ethSignTypedMessage(BigInt(chainID), keypath, JSON.parse(msg), useAntiklepto));
408409
} catch (err) {
409410
setErr(bitbox.ensureError(err));
410411
} finally {
@@ -424,6 +425,10 @@ function EthSignTypedMessage({ bb02 } : Props) {
424425
Keypath
425426
<input type='text' value={keypath} onChange={e => setKeypath(e.target.value)} />
426427
</label>
428+
<label>
429+
Use anti-klepto
430+
<input type='checkbox' checked={useAntiklepto} onChange={e => setUseAntiklepto(e.target.checked)} />
431+
</label>
427432
<label>
428433
EIP-712 typed message
429434
</label>

src/eth.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,13 +642,18 @@ impl<R: Runtime> PairedBitBox<R> {
642642

643643
/// Signs an Ethereum EIP-712 typed message. It returns a 65 byte signature (R, S, and 1 byte
644644
/// recID). 27 is added to the recID to denote an uncompressed pubkey.
645+
/// If `use_antiklepto` is false, signing is deterministic and requires firmware >=9.26.0.
645646
pub async fn eth_sign_typed_message(
646647
&self,
647648
chain_id: u64,
648649
keypath: &Keypath,
649650
json_msg: &str,
651+
use_antiklepto: bool,
650652
) -> Result<[u8; 65], Error> {
651653
self.validate_version(">=9.12.0")?;
654+
if !use_antiklepto {
655+
self.validate_version(">=9.26.0")?;
656+
}
652657

653658
let msg: Eip712Message = serde_json::from_str(json_msg)
654659
.map_err(|_| Error::EthTypedMessage("Could not parse EIP-712 JSON message".into()))?;
@@ -673,7 +678,11 @@ impl<R: Runtime> PairedBitBox<R> {
673678
.collect::<Result<Vec<StructType>, String>>()
674679
.map_err(Error::EthTypedMessage)?;
675680

676-
let host_nonce = crate::antiklepto::gen_host_nonce()?;
681+
let host_nonce = if use_antiklepto {
682+
Some(crate::antiklepto::gen_host_nonce()?)
683+
} else {
684+
None
685+
};
677686

678687
let mut response = self
679688
.query_proto_eth(pb::eth_request::Request::SignTypedMsg(
@@ -682,8 +691,10 @@ impl<R: Runtime> PairedBitBox<R> {
682691
keypath: keypath.to_vec(),
683692
types: parsed_types,
684693
primary_type: msg.primary_type.clone(),
685-
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
686-
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
694+
host_nonce_commitment: host_nonce.as_ref().map(|host_nonce| {
695+
pb::AntiKleptoHostNonceCommitment {
696+
commitment: crate::antiklepto::host_commit(host_nonce).to_vec(),
697+
}
687698
}),
688699
},
689700
))
@@ -696,7 +707,18 @@ impl<R: Runtime> PairedBitBox<R> {
696707
))
697708
.await?;
698709
}
699-
let mut signature = self.handle_antiklepto(&response, host_nonce).await?;
710+
let mut signature = if use_antiklepto {
711+
self.handle_antiklepto(&response, host_nonce.unwrap())
712+
.await?
713+
} else {
714+
match response {
715+
pb::eth_response::Response::Sign(pb::EthSignResponse { signature }) => signature
716+
.as_slice()
717+
.try_into()
718+
.map_err(|_| Error::UnexpectedResponse)?,
719+
_ => return Err(Error::UnexpectedResponse),
720+
}
721+
};
700722
// 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey.
701723
signature[64] += 27;
702724
Ok(signature)

src/wasm/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,17 +526,24 @@ impl PairedBitBox {
526526

527527
/// Signs an Ethereum EIP-712 typed message. It returns a 65 byte signature (R, S, and 1 byte
528528
/// recID). 27 is added to the recID to denote an uncompressed pubkey.
529+
/// `use_antiklepto` defaults to `true` if omitted.
529530
#[wasm_bindgen(js_name = ethSignTypedMessage)]
530531
pub async fn eth_sign_typed_message(
531532
&self,
532533
chain_id: u64,
533534
keypath: types::TsKeypath,
534535
msg: JsValue,
536+
use_antiklepto: Option<bool>,
535537
) -> Result<types::TsEthSignature, JavascriptError> {
536538
let json_msg: String = js_sys::JSON::stringify(&msg).unwrap().into();
537539
let signature = self
538540
.device
539-
.eth_sign_typed_message(chain_id, &keypath.try_into()?, &json_msg)
541+
.eth_sign_typed_message(
542+
chain_id,
543+
&keypath.try_into()?,
544+
&json_msg,
545+
use_antiklepto.unwrap_or(true),
546+
)
540547
.await?;
541548

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

tests/test_eth.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,54 @@ use bitcoin::secp256k1;
1414
use tiny_keccak::{Hasher, Keccak};
1515
use util::test_initialized_simulators;
1616

17+
const EIP712_MSG: &str = r#"
18+
{
19+
"types": {
20+
"EIP712Domain": [
21+
{ "name": "name", "type": "string" },
22+
{ "name": "version", "type": "string" },
23+
{ "name": "chainId", "type": "uint256" },
24+
{ "name": "verifyingContract", "type": "address" }
25+
],
26+
"Attachment": [
27+
{ "name": "contents", "type": "string" }
28+
],
29+
"Person": [
30+
{ "name": "name", "type": "string" },
31+
{ "name": "wallet", "type": "address" },
32+
{ "name": "age", "type": "uint8" }
33+
],
34+
"Mail": [
35+
{ "name": "from", "type": "Person" },
36+
{ "name": "to", "type": "Person" },
37+
{ "name": "contents", "type": "string" },
38+
{ "name": "attachments", "type": "Attachment[]" }
39+
]
40+
},
41+
"primaryType": "Mail",
42+
"domain": {
43+
"name": "Ether Mail",
44+
"version": "1",
45+
"chainId": 1,
46+
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
47+
},
48+
"message": {
49+
"from": {
50+
"name": "Cow",
51+
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
52+
"age": 20
53+
},
54+
"to": {
55+
"name": "Bob",
56+
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
57+
"age": "0x1e"
58+
},
59+
"contents": "Hello, Bob!",
60+
"attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }]
61+
}
62+
}
63+
"#;
64+
1765
fn keccak256(data: &[u8]) -> [u8; 32] {
1866
let mut hasher = Keccak::v256();
1967
hasher.update(data);
@@ -201,3 +249,66 @@ async fn test_eth_sign_1559_transaction_streaming() {
201249
})
202250
.await
203251
}
252+
253+
#[tokio::test]
254+
async fn test_eth_sign_typed_message_antiklepto_enabled() {
255+
test_initialized_simulators(async |paired_bitbox| {
256+
let signature_antiklepto_1 = paired_bitbox
257+
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), EIP712_MSG, true)
258+
.await
259+
.unwrap();
260+
let signature_antiklepto_2 = paired_bitbox
261+
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), EIP712_MSG, true)
262+
.await
263+
.unwrap();
264+
assert_eq!(signature_antiklepto_1.len(), 65);
265+
assert_eq!(signature_antiklepto_2.len(), 65);
266+
assert_ne!(signature_antiklepto_1, signature_antiklepto_2);
267+
})
268+
.await
269+
}
270+
271+
#[tokio::test]
272+
async fn test_eth_sign_typed_message_antiklepto_disabled() {
273+
test_initialized_simulators(async |paired_bitbox| {
274+
if semver::VersionReq::parse(">=9.26.0")
275+
.unwrap()
276+
.matches(paired_bitbox.version())
277+
{
278+
let signature_no_antiklepto_1 = paired_bitbox
279+
.eth_sign_typed_message(
280+
1,
281+
&"m/44'/60'/0'/0/0".try_into().unwrap(),
282+
EIP712_MSG,
283+
false,
284+
)
285+
.await
286+
.unwrap();
287+
let signature_no_antiklepto_2 = paired_bitbox
288+
.eth_sign_typed_message(
289+
1,
290+
&"m/44'/60'/0'/0/0".try_into().unwrap(),
291+
EIP712_MSG,
292+
false,
293+
)
294+
.await
295+
.unwrap();
296+
assert_eq!(signature_no_antiklepto_1.len(), 65);
297+
assert_eq!(signature_no_antiklepto_2.len(), 65);
298+
assert_eq!(signature_no_antiklepto_1, signature_no_antiklepto_2);
299+
return;
300+
}
301+
302+
let err = paired_bitbox
303+
.eth_sign_typed_message(
304+
1,
305+
&"m/44'/60'/0'/0/0".try_into().unwrap(),
306+
EIP712_MSG,
307+
false,
308+
)
309+
.await
310+
.unwrap_err();
311+
assert!(matches!(err, bitbox_api::error::Error::Version(">=9.26.0")));
312+
})
313+
.await
314+
}

0 commit comments

Comments
 (0)