1use 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};
20use serde_json::Value;
22
23const 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 pub fn eth_supported(&self) -> bool {
47 self.is_multi_edition()
48 }
49
50 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 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 pub nonce: Vec<u8>,
102 pub gas_price: Vec<u8>,
104 pub gas_limit: Vec<u8>,
106 pub recipient: [u8; 20],
107 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 pub nonce: Vec<u8>,
121 pub max_priority_fee_per_gas: Vec<u8>,
123 pub max_fee_per_gas: Vec<u8>,
125 pub gas_limit: Vec<u8>,
127 pub recipient: [u8; 20],
128 pub value: Vec<u8>,
130 pub data: Vec<u8>,
131}
132
133pub 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 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 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 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 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 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 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 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 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 signature[64] += 27;
647 Ok(signature)
648 }
649
650 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 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 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 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 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 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 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 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 assert!(get_value(
1230 &pb::EthTypedMessageValueResponse {
1231 root_object: RootObject::Domain as _,
1232 path: vec![],
1233 },
1234 &msg,
1235 )
1236 .is_err());
1237 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 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 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 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 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 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 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 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 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 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 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 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 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}