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