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