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