Skip to main content

bitbox_api/
eth.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use crate::runtime::Runtime;
4
5use crate::error::Error;
6use crate::pb::{
7    self,
8    eth_sign_typed_message_request::{DataType, Member, MemberType, StructType},
9    eth_typed_message_value_response::RootObject,
10    request::Request,
11    response::Response,
12};
13use crate::Keypath;
14use crate::PairedBitBox;
15
16use std::collections::HashMap;
17use std::str::FromStr;
18
19use num_bigint::{BigInt, BigUint};
20//use num_traits::ToPrimitive;
21use serde_json::Value;
22
23/// Threshold above which transaction data is streamed in chunks.
24/// Transactions with data larger than this use streaming mode.
25const STREAMING_THRESHOLD: usize = 6144;
26
27impl<R: Runtime> PairedBitBox<R> {
28    async fn query_proto_eth(
29        &self,
30        request: pb::eth_request::Request,
31    ) -> Result<pb::eth_response::Response, Error> {
32        match self
33            .query_proto(Request::Eth(pb::EthRequest {
34                request: Some(request),
35            }))
36            .await?
37        {
38            Response::Eth(pb::EthResponse {
39                response: Some(response),
40            }) => Ok(response),
41            _ => Err(Error::UnexpectedResponse),
42        }
43    }
44
45    /// Does this device support ETH functionality? Currently this means BitBox02 Multi.
46    pub fn eth_supported(&self) -> bool {
47        self.is_multi_edition()
48    }
49
50    /// Query the device for an xpub.
51    pub async fn eth_xpub(&self, keypath: &Keypath) -> Result<String, Error> {
52        match self
53            .query_proto_eth(pb::eth_request::Request::Pub(pb::EthPubRequest {
54                keypath: keypath.to_vec(),
55                coin: 0,
56                output_type: pb::eth_pub_request::OutputType::Xpub as _,
57                display: false,
58                contract_address: vec![],
59                chain_id: 0,
60            }))
61            .await?
62        {
63            pb::eth_response::Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
64            _ => Err(Error::UnexpectedResponse),
65        }
66    }
67
68    /// Query the device for an Ethereum address.
69    pub async fn eth_address(
70        &self,
71        chain_id: u64,
72        keypath: &Keypath,
73        display: bool,
74    ) -> Result<String, Error> {
75        match self
76            .query_proto_eth(pb::eth_request::Request::Pub(pb::EthPubRequest {
77                keypath: keypath.to_vec(),
78                coin: 0,
79                output_type: pb::eth_pub_request::OutputType::Address as _,
80                display,
81                contract_address: vec![],
82                chain_id,
83            }))
84            .await?
85        {
86            pb::eth_response::Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
87            _ => Err(Error::UnexpectedResponse),
88        }
89    }
90}
91
92#[cfg_attr(
93    feature = "wasm",
94    derive(serde::Deserialize),
95    serde(rename_all = "camelCase")
96)]
97pub struct Transaction {
98    /// Nonce must be big-endian encoded, no trailing zeroes.
99    pub nonce: Vec<u8>,
100    /// Gas price must be big-endian encoded, no trailing zeroes.
101    pub gas_price: Vec<u8>,
102    /// Gas limit must be big-endian encoded, no trailing zeroes.
103    pub gas_limit: Vec<u8>,
104    pub recipient: [u8; 20],
105    /// Value must be big-endian encoded, no trailing zeroes.
106    pub value: Vec<u8>,
107    pub data: Vec<u8>,
108}
109
110#[cfg_attr(
111    feature = "wasm",
112    derive(serde::Deserialize),
113    serde(rename_all = "camelCase")
114)]
115pub struct EIP1559Transaction {
116    pub chain_id: u64,
117    /// Nonce must be big-endian encoded, no trailing zeroes.
118    pub nonce: Vec<u8>,
119    /// Max priority fee must be big-endian encoded, no trailing zeroes.
120    pub max_priority_fee_per_gas: Vec<u8>,
121    /// max fee must be big-endian encoded, no trailing zeroes.
122    pub max_fee_per_gas: Vec<u8>,
123    /// Gas limit must be big-endian encoded, no trailing zeroes.
124    pub gas_limit: Vec<u8>,
125    pub recipient: [u8; 20],
126    /// Value must be big-endian encoded, no trailing zeroes.
127    pub value: Vec<u8>,
128    pub data: Vec<u8>,
129}
130
131/// Identifies the case of the recipient address given as hexadecimal string.
132/// This function exists as a convenience to help clients to determine the case of the
133/// recipient address.
134pub fn eth_identify_case(recipient_address: &str) -> pb::EthAddressCase {
135    if recipient_address
136        .chars()
137        .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_uppercase())
138    {
139        pb::EthAddressCase::Upper
140    } else if recipient_address
141        .chars()
142        .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_lowercase())
143    {
144        pb::EthAddressCase::Lower
145    } else {
146        pb::EthAddressCase::Mixed
147    }
148}
149
150#[cfg(feature = "rlp")]
151impl TryFrom<&[u8]> for Transaction {
152    type Error = ();
153
154    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
155        let [nonce, gas_price, gas_limit, recipient, value, data, _, _, _]: [Vec<u8>; 9] =
156            rlp::decode_list(value).try_into().map_err(|_| ())?;
157        Ok(Transaction {
158            nonce,
159            gas_price,
160            gas_limit,
161            recipient: recipient.try_into().map_err(|_| ())?,
162            value,
163            data,
164        })
165    }
166}
167
168#[cfg(feature = "rlp")]
169impl TryFrom<&[u8]> for EIP1559Transaction {
170    type Error = ();
171
172    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
173        let [mut chain_id_vec, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, recipient, value, data, _, _, _]: [Vec<u8>; 11] =
174            rlp::decode_list(value).try_into().map_err(|_| ())?;
175        while chain_id_vec.len() < 8 {
176            chain_id_vec.insert(0, 0);
177        }
178        let chain_id = u64::from_be_bytes(chain_id_vec.try_into().map_err(|_| ())?);
179        Ok(EIP1559Transaction {
180            chain_id,
181            nonce,
182            max_priority_fee_per_gas,
183            max_fee_per_gas,
184            gas_limit,
185            recipient: recipient.try_into().map_err(|_| ())?,
186            value,
187            data,
188        })
189    }
190}
191
192#[derive(Debug, PartialEq, serde::Deserialize)]
193struct Eip712TypeMember {
194    name: String,
195    r#type: String,
196}
197
198#[derive(Debug, PartialEq, serde::Deserialize)]
199#[serde(rename_all = "camelCase")]
200struct Eip712Message {
201    types: HashMap<String, Vec<Eip712TypeMember>>,
202    primary_type: String,
203    domain: HashMap<String, Value>,
204    message: HashMap<String, Value>,
205}
206
207fn parse_type(
208    typ: &str,
209    types: &HashMap<String, Vec<Eip712TypeMember>>,
210) -> Result<MemberType, String> {
211    if typ.ends_with(']') {
212        let index = typ
213            .rfind('[')
214            .ok_or(format!("Invalid type format: {typ}"))?;
215        let (rest, size) = (&typ[..index], &typ[index + 1..typ.len() - 1]);
216        let size_int = if !size.is_empty() {
217            u32::from_str(size).map_err(|e| format!("Error parsing size: {e}"))?
218        } else {
219            0
220        };
221        let array_type = Box::new(parse_type(rest, types)?);
222        Ok(MemberType {
223            r#type: DataType::Array.into(),
224            size: size_int,
225            struct_name: String::new(),
226            array_type: Some(array_type),
227        })
228    } else if let Some(size) = typ.strip_prefix("bytes") {
229        let size_int = if !size.is_empty() {
230            u32::from_str(size).map_err(|e| format!("Error parsing size: {e}"))?
231        } else {
232            0
233        };
234        Ok(MemberType {
235            r#type: DataType::Bytes.into(),
236            size: size_int,
237            struct_name: String::new(),
238            array_type: None,
239        })
240    } else if let Some(size) = typ.strip_prefix("uint") {
241        if size.is_empty() {
242            return Err("uint must be sized".to_string());
243        }
244        let size_int = u32::from_str(size).map_err(|e| format!("Error parsing size: {e}"))? / 8;
245        Ok(MemberType {
246            r#type: DataType::Uint.into(),
247            size: size_int,
248            struct_name: String::new(),
249            array_type: None,
250        })
251    } else if let Some(size) = typ.strip_prefix("int") {
252        if size.is_empty() {
253            return Err("int must be sized".to_string());
254        }
255        let size_int = u32::from_str(size).map_err(|e| format!("Error parsing size: {e}"))? / 8;
256        Ok(MemberType {
257            r#type: DataType::Int.into(),
258            size: size_int,
259            struct_name: String::new(),
260            array_type: None,
261        })
262    } else if typ == "bool" {
263        Ok(MemberType {
264            r#type: DataType::Bool.into(),
265            size: 0,
266            struct_name: String::new(),
267            array_type: None,
268        })
269    } else if typ == "address" {
270        Ok(MemberType {
271            r#type: DataType::Address.into(),
272            size: 0,
273            struct_name: String::new(),
274            array_type: None,
275        })
276    } else if typ == "string" {
277        Ok(MemberType {
278            r#type: DataType::String.into(),
279            size: 0,
280            struct_name: String::new(),
281            array_type: None,
282        })
283    } else if types.contains_key(typ) {
284        Ok(MemberType {
285            r#type: DataType::Struct.into(),
286            size: 0,
287            struct_name: typ.to_string(),
288            array_type: None,
289        })
290    } else {
291        Err(format!("Can't recognize type: {typ}"))
292    }
293}
294
295fn encode_value(typ: &MemberType, value: &Value) -> Result<Vec<u8>, String> {
296    match DataType::try_from(typ.r#type).unwrap() {
297        DataType::Bytes => {
298            if let Value::String(v) = value {
299                if v.starts_with("0x") || v.starts_with("0X") {
300                    hex::decode(&v[2..]).map_err(|e| e.to_string())
301                } else {
302                    Ok(v.as_bytes().to_vec())
303                }
304            } else {
305                Err("Expected a string for bytes type".to_string())
306            }
307        }
308        DataType::Uint => match value {
309            Value::String(v) => {
310                if v.starts_with("0x") || v.starts_with("0X") {
311                    Ok(BigUint::parse_bytes(&v.as_bytes()[2..], 16)
312                        .ok_or(format!("could not parse {v} as hex"))?
313                        .to_bytes_be())
314                } else {
315                    Ok(BigUint::from_str(v)
316                        .map_err(|e| e.to_string())?
317                        .to_bytes_be())
318                }
319            }
320            Value::Number(n) => {
321                if let Some(v) = n.as_f64() {
322                    let v64: u64 = v as _;
323                    if (v64 as f64) != v {
324                        Err("Number is not an uint".to_string())
325                    } else {
326                        Ok(BigUint::from(v64).to_bytes_be())
327                    }
328                } else {
329                    Err("Number is not an uint".to_string())
330                }
331            }
332            _ => Err("Wrong type for uint".to_string()),
333        },
334        DataType::Int => match value {
335            Value::String(v) => Ok(BigInt::from_str(v)
336                .map_err(|e| e.to_string())?
337                .to_signed_bytes_be()),
338            Value::Number(n) => {
339                if let Some(v) = n.as_f64() {
340                    let v64: i64 = v as _;
341                    if (v64 as f64) != v {
342                        Err("Number is not an int".to_string())
343                    } else {
344                        let mut bytes = BigInt::from(v64).to_signed_bytes_be();
345                        // Drop leading zero. There can be at most one.
346                        if let [0, ..] = bytes.as_slice() {
347                            bytes.remove(0);
348                        }
349                        Ok(bytes)
350                    }
351                } else {
352                    Err("Number is not an int".to_string())
353                }
354            }
355            _ => Err("Wrong type for int".to_string()),
356        },
357        DataType::Bool => {
358            if value.as_bool().ok_or("Expected a boolean value")? {
359                Ok(vec![1])
360            } else {
361                Ok(vec![0])
362            }
363        }
364        DataType::Address | DataType::String => {
365            if let Value::String(v) = value {
366                Ok(v.as_bytes().to_vec())
367            } else {
368                Err("Expected a string value".to_string())
369            }
370        }
371        DataType::Array => {
372            if let Value::Array(arr) = value {
373                let size = arr.len() as u32;
374                Ok(size.to_be_bytes().to_vec())
375            } else {
376                Err("Expected an array".to_string())
377            }
378        }
379        _ => Err("looked up value must not be a map or array".to_string()),
380    }
381}
382
383fn get_value(
384    what: &pb::EthTypedMessageValueResponse,
385    msg: &Eip712Message,
386) -> Result<(Vec<u8>, DataType), String> {
387    enum Either<'a> {
388        HashMap(&'a HashMap<String, Value>),
389        JsonValue(Value),
390    }
391    impl Either<'_> {
392        fn get(&self, key: &str) -> Option<&Value> {
393            match self {
394                Either::HashMap(map) => map.get(key),
395                Either::JsonValue(Value::Object(map)) => map.get(key),
396                _ => None,
397            }
398        }
399    }
400
401    let (mut value, mut typ): (Either, MemberType) =
402        match RootObject::try_from(what.root_object).unwrap() {
403            RootObject::Unknown => return Err("unkown root object".into()),
404            RootObject::Domain => (
405                Either::HashMap(&msg.domain),
406                parse_type("EIP712Domain", &msg.types)?,
407            ),
408            RootObject::Message => (
409                Either::HashMap(&msg.message),
410                parse_type(&msg.primary_type, &msg.types)?,
411            ),
412        };
413    for element in what.path.iter() {
414        match DataType::try_from(typ.r#type).unwrap() {
415            DataType::Struct => {
416                let struct_member: &Eip712TypeMember = msg
417                    .types
418                    .get(&typ.struct_name)
419                    .ok_or(format!(
420                        "could not lookup type of name: {}",
421                        &typ.struct_name
422                    ))?
423                    .get(*element as usize)
424                    .ok_or(format!(
425                        "could not lookup member #{} of type: {}",
426                        *element, &typ.struct_name
427                    ))?;
428                value = Either::JsonValue(
429                    value
430                        .get(&struct_member.name)
431                        .ok_or(format!("could not lookup: {}", struct_member.name.as_str()))?
432                        .clone(),
433                );
434                typ = parse_type(&struct_member.r#type, &msg.types)?;
435            }
436            DataType::Array => {
437                if let Either::JsonValue(Value::Array(list)) = value {
438                    value = Either::JsonValue(
439                        list.get(*element as usize)
440                            .ok_or(format!("could not lookup array index: {}", *element))?
441                            .clone(),
442                    );
443                    typ = *typ.array_type.unwrap();
444                }
445            }
446            _ => return Err("path element does not point to struct or array".into()),
447        }
448    }
449    let data_type =
450        DataType::try_from(typ.r#type).map_err(|_| format!("invalid data type: {}", typ.r#type))?;
451    if let Either::JsonValue(value) = &value {
452        encode_value(&typ, value).map(|v| (v, data_type))
453    } else {
454        Err("path points to struct or array; value expected".to_string())
455    }
456}
457
458impl<R: Runtime> PairedBitBox<R> {
459    async fn handle_antiklepto(
460        &self,
461        response: &pb::eth_response::Response,
462        host_nonce: [u8; 32],
463    ) -> Result<[u8; 65], Error> {
464        match response {
465            pb::eth_response::Response::AntikleptoSignerCommitment(
466                pb::AntiKleptoSignerCommitment { commitment },
467            ) => {
468                match self
469                    .query_proto_eth(pb::eth_request::Request::AntikleptoSignature(
470                        pb::AntiKleptoSignatureRequest {
471                            host_nonce: host_nonce.to_vec(),
472                        },
473                    ))
474                    .await?
475                {
476                    pb::eth_response::Response::Sign(pb::EthSignResponse { signature }) => {
477                        crate::antiklepto::verify_ecdsa(&host_nonce, commitment, &signature)?;
478                        signature.try_into().map_err(|_| Error::UnexpectedResponse)
479                    }
480                    _ => Err(Error::UnexpectedResponse),
481                }
482            }
483            _ => Err(Error::UnexpectedResponse),
484        }
485    }
486
487    /// Handles streaming of transaction data when in streaming mode.
488    /// The device requests data chunks, and this method responds with the requested chunks.
489    async fn handle_eth_data_streaming(
490        &self,
491        data: &[u8],
492        mut response: pb::eth_response::Response,
493    ) -> Result<pb::eth_response::Response, Error> {
494        while let pb::eth_response::Response::DataRequestChunk(chunk_req) = &response {
495            let offset = chunk_req.offset as usize;
496            let length = chunk_req.length as usize;
497
498            if offset + length > data.len() {
499                return Err(Error::UnexpectedResponse);
500            }
501
502            let chunk = data[offset..offset + length].to_vec();
503            response = self
504                .query_proto_eth(pb::eth_request::Request::DataResponseChunk(
505                    pb::EthSignDataResponseChunkRequest { chunk },
506                ))
507                .await?;
508        }
509        Ok(response)
510    }
511
512    /// Signs an Ethereum transaction. It returns a 65 byte signature (R, S, and 1 byte recID).  The
513    /// `tx` param can be constructed manually or parsed from a raw transaction using
514    /// `raw_tx_slice.try_into()` (`rlp` feature required).
515    pub async fn eth_sign_transaction(
516        &self,
517        chain_id: u64,
518        keypath: &Keypath,
519        tx: &Transaction,
520        address_case: Option<pb::EthAddressCase>,
521    ) -> Result<[u8; 65], Error> {
522        // passing chainID instead of coin only since v9.10.0
523        self.validate_version(">=9.10.0")?;
524
525        let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
526        if use_streaming {
527            self.validate_version(">=9.26.0")?;
528        }
529
530        let host_nonce = crate::antiklepto::gen_host_nonce()?;
531        let request = pb::eth_request::Request::Sign(pb::EthSignRequest {
532            coin: 0,
533            keypath: keypath.to_vec(),
534            nonce: crate::util::remove_leading_zeroes(&tx.nonce),
535            gas_price: crate::util::remove_leading_zeroes(&tx.gas_price),
536            gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
537            recipient: tx.recipient.to_vec(),
538            value: crate::util::remove_leading_zeroes(&tx.value),
539            data: if use_streaming {
540                vec![]
541            } else {
542                tx.data.clone()
543            },
544            host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
545                commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
546            }),
547            chain_id,
548            address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
549            data_length: if use_streaming {
550                tx.data.len() as u32
551            } else {
552                0
553            },
554        });
555
556        let mut response = self.query_proto_eth(request).await?;
557        if use_streaming {
558            response = self.handle_eth_data_streaming(&tx.data, response).await?;
559        }
560        self.handle_antiklepto(&response, host_nonce).await
561    }
562
563    /// Signs an Ethereum type 2 transaction according to EIP 1559. It returns a 65 byte signature (R, S, and 1 byte recID).
564    /// The `tx` param can be constructed manually or parsed from a raw transaction using
565    /// `raw_tx_slice.try_into()` (`rlp` feature required).
566    pub async fn eth_sign_1559_transaction(
567        &self,
568        keypath: &Keypath,
569        tx: &EIP1559Transaction,
570        address_case: Option<pb::EthAddressCase>,
571    ) -> Result<[u8; 65], Error> {
572        // EIP1559 is suported from v9.16.0
573        self.validate_version(">=9.16.0")?;
574
575        let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
576        if use_streaming {
577            self.validate_version(">=9.26.0")?;
578        }
579
580        let host_nonce = crate::antiklepto::gen_host_nonce()?;
581        let request = pb::eth_request::Request::SignEip1559(pb::EthSignEip1559Request {
582            chain_id: tx.chain_id,
583            keypath: keypath.to_vec(),
584            nonce: crate::util::remove_leading_zeroes(&tx.nonce),
585            max_priority_fee_per_gas: crate::util::remove_leading_zeroes(
586                &tx.max_priority_fee_per_gas,
587            ),
588            max_fee_per_gas: crate::util::remove_leading_zeroes(&tx.max_fee_per_gas),
589            gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
590            recipient: tx.recipient.to_vec(),
591            value: crate::util::remove_leading_zeroes(&tx.value),
592            data: if use_streaming {
593                vec![]
594            } else {
595                tx.data.clone()
596            },
597            host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
598                commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
599            }),
600            address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
601            data_length: if use_streaming {
602                tx.data.len() as u32
603            } else {
604                0
605            },
606        });
607
608        let mut response = self.query_proto_eth(request).await?;
609        if use_streaming {
610            response = self.handle_eth_data_streaming(&tx.data, response).await?;
611        }
612        self.handle_antiklepto(&response, host_nonce).await
613    }
614
615    /// Signs an Ethereum message. The provided msg will be prefixed with "\x19Ethereum message\n" +
616    /// len(msg) in the hardware, e.g. "\x19Ethereum\n5hello" (yes, the len prefix is the ascii
617    /// representation with no fixed size or delimiter).  It returns a 65 byte signature (R, S, and
618    /// 1 byte recID). 27 is added to the recID to denote an uncompressed pubkey.
619    pub async fn eth_sign_message(
620        &self,
621        chain_id: u64,
622        keypath: &Keypath,
623        msg: &[u8],
624    ) -> Result<[u8; 65], Error> {
625        // passing chainID instead of coin only since v9.10.0
626        self.validate_version(">=9.10.0")?;
627
628        let host_nonce = crate::antiklepto::gen_host_nonce()?;
629        let request = pb::eth_request::Request::SignMsg(pb::EthSignMessageRequest {
630            coin: 0,
631            keypath: keypath.to_vec(),
632            msg: msg.to_vec(),
633            host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
634                commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
635            }),
636            chain_id,
637        });
638        let response = self.query_proto_eth(request).await?;
639        let mut signature = self.handle_antiklepto(&response, host_nonce).await?;
640        // 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey.
641        signature[64] += 27;
642        Ok(signature)
643    }
644
645    /// Signs an Ethereum EIP-712 typed message. It returns a 65 byte signature (R, S, and 1 byte
646    /// recID). 27 is added to the recID to denote an uncompressed pubkey.
647    /// If `use_antiklepto` is false, signing is deterministic and requires firmware >=9.26.0.
648    pub async fn eth_sign_typed_message(
649        &self,
650        chain_id: u64,
651        keypath: &Keypath,
652        json_msg: &str,
653        use_antiklepto: bool,
654    ) -> Result<[u8; 65], Error> {
655        self.validate_version(">=9.12.0")?;
656        if !use_antiklepto {
657            self.validate_version(">=9.26.0")?;
658        }
659
660        let msg: Eip712Message = serde_json::from_str(json_msg)
661            .map_err(|_| Error::EthTypedMessage("Could not parse EIP-712 JSON message".into()))?;
662
663        let parsed_types: Vec<StructType> = msg
664            .types
665            .iter()
666            .map(|(name, members)| {
667                Ok(StructType {
668                    name: name.clone(),
669                    members: members
670                        .iter()
671                        .map(|member| {
672                            Ok(Member {
673                                name: member.name.clone(),
674                                r#type: Some(parse_type(&member.r#type, &msg.types)?),
675                            })
676                        })
677                        .collect::<Result<Vec<Member>, String>>()?,
678                })
679            })
680            .collect::<Result<Vec<StructType>, String>>()
681            .map_err(Error::EthTypedMessage)?;
682
683        let host_nonce = if use_antiklepto {
684            Some(crate::antiklepto::gen_host_nonce()?)
685        } else {
686            None
687        };
688
689        let mut response = self
690            .query_proto_eth(pb::eth_request::Request::SignTypedMsg(
691                pb::EthSignTypedMessageRequest {
692                    chain_id,
693                    keypath: keypath.to_vec(),
694                    types: parsed_types,
695                    primary_type: msg.primary_type.clone(),
696                    host_nonce_commitment: host_nonce.as_ref().map(|host_nonce| {
697                        pb::AntiKleptoHostNonceCommitment {
698                            commitment: crate::antiklepto::host_commit(host_nonce).to_vec(),
699                        }
700                    }),
701                },
702            ))
703            .await?;
704        while let pb::eth_response::Response::TypedMsgValue(typed_msg_value) = &response {
705            let (value, data_type) =
706                get_value(typed_msg_value, &msg).map_err(Error::EthTypedMessage)?;
707            if data_type == DataType::String && value.len() > STREAMING_THRESHOLD {
708                return Err(Error::EthTypedMessage(
709                    "string value exceeds maximum size".into(),
710                ));
711            }
712            let use_streaming = value.len() > STREAMING_THRESHOLD;
713            response = self
714                .query_proto_eth(pb::eth_request::Request::TypedMsgValue(
715                    pb::EthTypedMessageValueRequest {
716                        value: if use_streaming { vec![] } else { value.clone() },
717                        data_length: if use_streaming { value.len() as u32 } else { 0 },
718                    },
719                ))
720                .await?;
721            if use_streaming {
722                response = self.handle_eth_data_streaming(&value, response).await?;
723            }
724        }
725        let mut signature = if use_antiklepto {
726            self.handle_antiklepto(&response, host_nonce.unwrap())
727                .await?
728        } else {
729            match response {
730                pb::eth_response::Response::Sign(pb::EthSignResponse { signature }) => signature
731                    .as_slice()
732                    .try_into()
733                    .map_err(|_| Error::UnexpectedResponse)?,
734                _ => return Err(Error::UnexpectedResponse),
735            }
736        };
737        // 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey.
738        signature[64] += 27;
739        Ok(signature)
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    const EIP712_MSG: &str = r#"
748        {
749    "types": {
750        "EIP712Domain": [
751            { "name": "name", "type": "string" },
752            { "name": "version", "type": "string" },
753            { "name": "chainId", "type": "uint256" },
754            { "name": "verifyingContract", "type": "address" }
755        ],
756        "Attachment": [
757            { "name": "contents", "type": "string" }
758        ],
759        "Person": [
760            { "name": "name", "type": "string" },
761            { "name": "wallet", "type": "address" },
762            { "name": "age", "type": "uint8" }
763        ],
764        "Mail": [
765            { "name": "from", "type": "Person" },
766            { "name": "to", "type": "Person" },
767            { "name": "contents", "type": "string" },
768            { "name": "attachments", "type": "Attachment[]" }
769        ]
770    },
771    "primaryType": "Mail",
772    "domain": {
773        "name": "Ether Mail",
774        "version": "1",
775        "chainId": 1,
776        "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
777    },
778    "message": {
779        "from": {
780            "name": "Cow",
781            "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
782            "age": 20
783        },
784        "to": {
785            "name": "Bob",
786            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
787            "age": "0x1e"
788        },
789        "contents": "Hello, Bob!",
790        "attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }]
791    }
792}
793    "#;
794
795    fn map_from(elements: &[(&str, Value)]) -> Value {
796        let mut m = serde_json::Map::<String, Value>::new();
797        for (k, v) in elements.iter().cloned() {
798            m.insert(k.into(), v);
799        }
800        m.into()
801    }
802
803    #[test]
804    fn test_deserialize_eip713_message() {
805        let msg: Eip712Message = serde_json::from_str(EIP712_MSG).unwrap();
806        assert_eq!(
807            msg,
808            Eip712Message {
809                types: HashMap::from([
810                    (
811                        "EIP712Domain".into(),
812                        vec![
813                            Eip712TypeMember {
814                                name: "name".into(),
815                                r#type: "string".into()
816                            },
817                            Eip712TypeMember {
818                                name: "version".into(),
819                                r#type: "string".into()
820                            },
821                            Eip712TypeMember {
822                                name: "chainId".into(),
823                                r#type: "uint256".into()
824                            },
825                            Eip712TypeMember {
826                                name: "verifyingContract".into(),
827                                r#type: "address".into()
828                            },
829                        ]
830                    ),
831                    (
832                        "Attachment".into(),
833                        vec![Eip712TypeMember {
834                            name: "contents".into(),
835                            r#type: "string".into()
836                        },]
837                    ),
838                    (
839                        "Person".into(),
840                        vec![
841                            Eip712TypeMember {
842                                name: "name".into(),
843                                r#type: "string".into()
844                            },
845                            Eip712TypeMember {
846                                name: "wallet".into(),
847                                r#type: "address".into()
848                            },
849                            Eip712TypeMember {
850                                name: "age".into(),
851                                r#type: "uint8".into()
852                            },
853                        ]
854                    ),
855                    (
856                        "Mail".into(),
857                        vec![
858                            Eip712TypeMember {
859                                name: "from".into(),
860                                r#type: "Person".into()
861                            },
862                            Eip712TypeMember {
863                                name: "to".into(),
864                                r#type: "Person".into()
865                            },
866                            Eip712TypeMember {
867                                name: "contents".into(),
868                                r#type: "string".into()
869                            },
870                            Eip712TypeMember {
871                                name: "attachments".into(),
872                                r#type: "Attachment[]".into()
873                            },
874                        ]
875                    ),
876                ]),
877                primary_type: "Mail".into(),
878                domain: HashMap::from([
879                    ("name".into(), "Ether Mail".into()),
880                    ("version".into(), "1".into()),
881                    ("chainId".into(), 1.into()),
882                    (
883                        "verifyingContract".into(),
884                        "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC".into()
885                    ),
886                ]),
887                message: HashMap::from([
888                    (
889                        "from".into(),
890                        map_from(&[
891                            ("name", "Cow".into()),
892                            (
893                                "wallet",
894                                "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826".into()
895                            ),
896                            ("age", 20.into())
897                        ]),
898                    ),
899                    (
900                        "to".into(),
901                        map_from(&[
902                            ("name", "Bob".into()),
903                            (
904                                "wallet",
905                                "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB".into()
906                            ),
907                            ("age", "0x1e".into())
908                        ]),
909                    ),
910                    ("contents".into(), "Hello, Bob!".into()),
911                    (
912                        "attachments".into(),
913                        Value::Array(vec![
914                            map_from(&[("contents", "attachment1".into())]),
915                            map_from(&[("contents", "attachment2".into())])
916                        ])
917                    ),
918                ]),
919            }
920        );
921    }
922
923    fn parse_type_no_err(typ: &str, types: &HashMap<String, Vec<Eip712TypeMember>>) -> MemberType {
924        parse_type(typ, types).unwrap()
925    }
926
927    #[test]
928    fn test_parse_type() {
929        assert_eq!(
930            MemberType {
931                r#type: DataType::String.into(),
932                ..Default::default()
933            },
934            parse_type_no_err("string", &HashMap::new())
935        );
936
937        // Bytes.
938        assert_eq!(
939            MemberType {
940                r#type: DataType::Bytes.into(),
941                ..Default::default()
942            },
943            parse_type_no_err("bytes", &HashMap::new())
944        );
945        assert_eq!(
946            MemberType {
947                r#type: DataType::Bytes.into(),
948                size: 1,
949                ..Default::default()
950            },
951            parse_type_no_err("bytes1", &HashMap::new())
952        );
953        assert_eq!(
954            MemberType {
955                r#type: DataType::Bytes.into(),
956                size: 10,
957                ..Default::default()
958            },
959            parse_type_no_err("bytes10", &HashMap::new())
960        );
961        assert_eq!(
962            MemberType {
963                r#type: DataType::Bytes.into(),
964                size: 32,
965                ..Default::default()
966            },
967            parse_type_no_err("bytes32", &HashMap::new())
968        );
969
970        assert_eq!(
971            MemberType {
972                r#type: DataType::Bool.into(),
973                ..Default::default()
974            },
975            parse_type_no_err("bool", &HashMap::new())
976        );
977
978        assert_eq!(
979            MemberType {
980                r#type: DataType::Address.into(),
981                ..Default::default()
982            },
983            parse_type_no_err("address", &HashMap::new())
984        );
985
986        // Uints.
987        assert_eq!(
988            MemberType {
989                r#type: DataType::Uint.into(),
990                size: 1,
991                ..Default::default()
992            },
993            parse_type_no_err("uint8", &HashMap::new())
994        );
995        assert_eq!(
996            MemberType {
997                r#type: DataType::Uint.into(),
998                size: 2,
999                ..Default::default()
1000            },
1001            parse_type_no_err("uint16", &HashMap::new())
1002        );
1003        assert_eq!(
1004            MemberType {
1005                r#type: DataType::Uint.into(),
1006                size: 32,
1007                ..Default::default()
1008            },
1009            parse_type_no_err("uint256", &HashMap::new())
1010        );
1011        assert!(parse_type("uint", &HashMap::new()).is_err());
1012        assert!(parse_type("uintfoo", &HashMap::new()).is_err());
1013
1014        // Ints.
1015        assert_eq!(
1016            MemberType {
1017                r#type: DataType::Int.into(),
1018                size: 1,
1019                ..Default::default()
1020            },
1021            parse_type_no_err("int8", &HashMap::new())
1022        );
1023        assert_eq!(
1024            MemberType {
1025                r#type: DataType::Int.into(),
1026                size: 2,
1027                ..Default::default()
1028            },
1029            parse_type_no_err("int16", &HashMap::new())
1030        );
1031        assert_eq!(
1032            MemberType {
1033                r#type: DataType::Int.into(),
1034                size: 32,
1035                ..Default::default()
1036            },
1037            parse_type_no_err("int256", &HashMap::new())
1038        );
1039        assert!(parse_type("int", &HashMap::new()).is_err());
1040        assert!(parse_type("intfoo", &HashMap::new()).is_err());
1041
1042        // Arrays.
1043        assert_eq!(
1044            MemberType {
1045                r#type: DataType::Array.into(),
1046                array_type: Some(Box::new(MemberType {
1047                    r#type: DataType::String.into(),
1048                    ..Default::default()
1049                })),
1050                ..Default::default()
1051            },
1052            parse_type_no_err("string[]", &HashMap::new())
1053        );
1054        assert_eq!(
1055            MemberType {
1056                r#type: DataType::Array.into(),
1057                size: 521,
1058                array_type: Some(Box::new(MemberType {
1059                    r#type: DataType::String.into(),
1060                    ..Default::default()
1061                })),
1062                ..Default::default()
1063            },
1064            parse_type_no_err("string[521]", &HashMap::new())
1065        );
1066        assert_eq!(
1067            MemberType {
1068                r#type: DataType::Array.into(),
1069                size: 521,
1070                array_type: Some(Box::new(MemberType {
1071                    r#type: DataType::Uint.into(),
1072                    size: 4,
1073                    ..Default::default()
1074                })),
1075                ..Default::default()
1076            },
1077            parse_type_no_err("uint32[521]", &HashMap::new())
1078        );
1079        assert_eq!(
1080            MemberType {
1081                r#type: DataType::Array.into(),
1082                array_type: Some(Box::new(MemberType {
1083                    r#type: DataType::Array.into(),
1084                    size: 521,
1085                    array_type: Some(Box::new(MemberType {
1086                        r#type: DataType::Uint.into(),
1087                        size: 4,
1088                        ..Default::default()
1089                    })),
1090                    ..Default::default()
1091                })),
1092                ..Default::default()
1093            },
1094            parse_type_no_err("uint32[521][]", &HashMap::new())
1095        );
1096
1097        // Structs
1098        assert!(parse_type("Unknown", &HashMap::new()).is_err());
1099
1100        assert_eq!(
1101            MemberType {
1102                r#type: DataType::Struct.into(),
1103                struct_name: "Person".to_string(),
1104                ..Default::default()
1105            },
1106            parse_type_no_err(
1107                "Person",
1108                &HashMap::from([("Person".to_string(), Vec::new())])
1109            )
1110        );
1111    }
1112
1113    #[test]
1114    fn test_encode_value() {
1115        let encoded =
1116            encode_value(&parse_type_no_err("bytes", &HashMap::new()), &"foo".into()).unwrap();
1117        assert_eq!(b"foo".to_vec(), encoded);
1118
1119        let encoded = encode_value(
1120            &parse_type_no_err("bytes3", &HashMap::new()),
1121            &"0xaabbcc".into(),
1122        )
1123        .unwrap();
1124        assert_eq!(vec![0xaa, 0xbb, 0xcc], encoded);
1125
1126        let encoded = encode_value(
1127            &parse_type_no_err("uint64", &HashMap::new()),
1128            &2983742332.0.into(),
1129        )
1130        .unwrap();
1131        assert_eq!(vec![0xb1, 0xd8, 0x4b, 0x7c], encoded);
1132
1133        let encoded = encode_value(
1134            &parse_type_no_err("uint64", &HashMap::new()),
1135            &"0xb1d84b7c".into(),
1136        )
1137        .unwrap();
1138        assert_eq!(vec![0xb1, 0xd8, 0x4b, 0x7c], encoded);
1139
1140        let encoded =
1141            encode_value(&parse_type_no_err("uint64", &HashMap::new()), &"0x1".into()).unwrap();
1142        assert_eq!(vec![0x01], encoded);
1143
1144        let encoded = encode_value(
1145            &parse_type_no_err("uint64", &HashMap::new()),
1146            &"0x0001".into(),
1147        )
1148        .unwrap();
1149        assert_eq!(vec![0x01], encoded);
1150
1151        assert!(encode_value(
1152            &parse_type_no_err("uint64", &HashMap::new()),
1153            &"0xnot correct".into(),
1154        )
1155        .is_err());
1156
1157        let encoded = encode_value(
1158            &parse_type_no_err("int64", &HashMap::new()),
1159            &2983742332.0.into(),
1160        )
1161        .unwrap();
1162        assert_eq!(vec![0xb1, 0xd8, 0x4b, 0x7c], encoded);
1163
1164        let encoded = encode_value(
1165            &parse_type_no_err("int64", &HashMap::new()),
1166            &(-2983742332.0).into(),
1167        )
1168        .unwrap();
1169        assert_eq!(vec![0xff, 0x4e, 0x27, 0xb4, 0x84], encoded);
1170
1171        let encoded =
1172            encode_value(&parse_type_no_err("string", &HashMap::new()), &"foo".into()).unwrap();
1173        assert_eq!(b"foo".to_vec(), encoded);
1174
1175        let encoded = encode_value(
1176            &parse_type_no_err("address", &HashMap::new()),
1177            &"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC".into(),
1178        )
1179        .unwrap();
1180        assert_eq!(
1181            b"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC".to_vec(),
1182            encoded
1183        );
1184
1185        let encoded =
1186            encode_value(&parse_type_no_err("bool", &HashMap::new()), &false.into()).unwrap();
1187        assert_eq!(vec![0], encoded);
1188
1189        let encoded =
1190            encode_value(&parse_type_no_err("bool", &HashMap::new()), &true.into()).unwrap();
1191        assert_eq!(vec![1], encoded);
1192
1193        // Array encodes its size.
1194        let encoded = encode_value(
1195            &parse_type_no_err("bool[]", &HashMap::new()),
1196            &Value::Array(vec![]),
1197        )
1198        .unwrap();
1199        assert_eq!(b"\x00\x00\x00\x00".to_vec(), encoded);
1200
1201        let encoded = encode_value(
1202            &parse_type_no_err("uint8[]", &HashMap::new()),
1203            &Value::Array(vec![1.into(); 10]),
1204        )
1205        .unwrap();
1206        assert_eq!(b"\x00\x00\x00\x0a".to_vec(), encoded);
1207
1208        let encoded = encode_value(
1209            &parse_type_no_err("uint8[]", &HashMap::new()),
1210            &Value::Array(vec![1.into(); 1000]),
1211        )
1212        .unwrap();
1213        assert_eq!(b"\x00\x00\x03\xe8".to_vec(), encoded);
1214    }
1215
1216    #[test]
1217    fn test_get_value() {
1218        let msg: Eip712Message = serde_json::from_str(EIP712_MSG).unwrap();
1219
1220        // DOMAIN
1221
1222        // Can't lookup domain itself.
1223        assert!(get_value(
1224            &pb::EthTypedMessageValueResponse {
1225                root_object: RootObject::Domain as _,
1226                path: vec![],
1227            },
1228            &msg,
1229        )
1230        .is_err());
1231        // Path points to nowhere.
1232        assert!(get_value(
1233            &pb::EthTypedMessageValueResponse {
1234                root_object: RootObject::Domain as _,
1235                path: vec![0, 0],
1236            },
1237            &msg,
1238        )
1239        .is_err());
1240
1241        // domain.name
1242        let (value, _) = get_value(
1243            &pb::EthTypedMessageValueResponse {
1244                root_object: RootObject::Domain as _,
1245                path: vec![0],
1246            },
1247            &msg,
1248        )
1249        .unwrap();
1250        assert_eq!(value, b"Ether Mail".to_vec());
1251
1252        // domain.version
1253        let (value, _) = get_value(
1254            &pb::EthTypedMessageValueResponse {
1255                root_object: RootObject::Domain as _,
1256                path: vec![1],
1257            },
1258            &msg,
1259        )
1260        .unwrap();
1261        assert_eq!(value, b"1".to_vec());
1262
1263        // domain.chainId
1264        let (value, _) = get_value(
1265            &pb::EthTypedMessageValueResponse {
1266                root_object: RootObject::Domain as _,
1267                path: vec![2],
1268            },
1269            &msg,
1270        )
1271        .unwrap();
1272        assert_eq!(value, b"\x01".to_vec());
1273
1274        // domain.verifyingContract
1275        let (value, _) = get_value(
1276            &pb::EthTypedMessageValueResponse {
1277                root_object: RootObject::Domain as _,
1278                path: vec![3],
1279            },
1280            &msg,
1281        )
1282        .unwrap();
1283        assert_eq!(
1284            value,
1285            b"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC".to_vec()
1286        );
1287        // No more members.
1288        assert!(get_value(
1289            &pb::EthTypedMessageValueResponse {
1290                root_object: RootObject::Domain as _,
1291                path: vec![4],
1292            },
1293            &msg,
1294        )
1295        .is_err());
1296
1297        // MESSAGE
1298
1299        // message.from.name
1300        let (value, _) = get_value(
1301            &pb::EthTypedMessageValueResponse {
1302                root_object: RootObject::Message as _,
1303                path: vec![0, 0],
1304            },
1305            &msg,
1306        )
1307        .unwrap();
1308        assert_eq!(value, b"Cow".to_vec());
1309
1310        // message.from.wallet
1311        let (value, _) = get_value(
1312            &pb::EthTypedMessageValueResponse {
1313                root_object: RootObject::Message as _,
1314                path: vec![0, 1],
1315            },
1316            &msg,
1317        )
1318        .unwrap();
1319        assert_eq!(
1320            value,
1321            b"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826".to_vec()
1322        );
1323
1324        // message.to.wallet
1325        let (value, _) = get_value(
1326            &pb::EthTypedMessageValueResponse {
1327                root_object: RootObject::Message as _,
1328                path: vec![1, 1],
1329            },
1330            &msg,
1331        )
1332        .unwrap();
1333        assert_eq!(
1334            value,
1335            b"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB".to_vec()
1336        );
1337
1338        // message.attachments.0.contents
1339        let (value, _) = get_value(
1340            &pb::EthTypedMessageValueResponse {
1341                root_object: RootObject::Message as _,
1342                path: vec![3, 0, 0],
1343            },
1344            &msg,
1345        )
1346        .unwrap();
1347        assert_eq!(value, b"attachment1".to_vec());
1348
1349        // message.attachments.1.contents
1350        let (value, _) = get_value(
1351            &pb::EthTypedMessageValueResponse {
1352                root_object: RootObject::Message as _,
1353                path: vec![3, 1, 0],
1354            },
1355            &msg,
1356        )
1357        .unwrap();
1358        assert_eq!(value, b"attachment2".to_vec());
1359
1360        // no more attachments
1361        assert!(get_value(
1362            &pb::EthTypedMessageValueResponse {
1363                root_object: RootObject::Message as _,
1364                path: vec![3, 2, 0],
1365            },
1366            &msg,
1367        )
1368        .is_err());
1369
1370        // only one field in attachment
1371        assert!(get_value(
1372            &pb::EthTypedMessageValueResponse {
1373                root_object: RootObject::Message as _,
1374                path: vec![3, 1, 1],
1375            },
1376            &msg,
1377        )
1378        .is_err());
1379    }
1380
1381    #[test]
1382    fn test_eth_identify_case() {
1383        assert_eq!(
1384            eth_identify_case("0XF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266"),
1385            pb::EthAddressCase::Upper
1386        );
1387        assert_eq!(
1388            eth_identify_case("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"),
1389            pb::EthAddressCase::Lower
1390        );
1391        assert_eq!(
1392            eth_identify_case("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
1393            pb::EthAddressCase::Mixed
1394        );
1395    }
1396}