Skip to content

Commit bb6c286

Browse files
Integrate the EIP 7702 authorization signature into the message signature framework (#4365)
* Integrate the EIP 7702 authorization signature into the message signature framework * Adjust the code according to the comments --------- Co-authored-by: Sergei Boiko <[email protected]>
1 parent 3823392 commit bb6c286

File tree

13 files changed

+181
-112
lines changed

13 files changed

+181
-112
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
//
3+
// Copyright © 2017 Trust Wallet.
4+
5+
use crate::message::{EthMessage, MessageSigningErrorKind, MessageSigningResult};
6+
use crate::rlp::list::RlpList;
7+
use crate::transaction::authorization_list::Authorization;
8+
use std::iter;
9+
use std::str::FromStr;
10+
use tw_hash::sha3::keccak256;
11+
use tw_hash::H256;
12+
13+
pub const EIP_7702_MAGIC_PREFIX: u8 = 0x05;
14+
15+
impl FromStr for Authorization {
16+
type Err = MessageSigningErrorKind;
17+
18+
fn from_str(s: &str) -> Result<Self, Self::Err> {
19+
serde_json::from_str(s).map_err(|_| MessageSigningErrorKind::TypeValueMismatch)
20+
}
21+
}
22+
23+
impl EthMessage for Authorization {
24+
fn hash(&self) -> MessageSigningResult<H256> {
25+
let mut list = RlpList::new();
26+
list.append(&self.chain_id)
27+
.append(&self.address)
28+
.append(&self.nonce);
29+
30+
let to_hash: Vec<_> = iter::once(EIP_7702_MAGIC_PREFIX)
31+
.chain(list.finish())
32+
.collect();
33+
34+
let hash_data = keccak256(&to_hash);
35+
Ok(H256::try_from(hash_data.as_slice()).expect("Expected 32-byte hash"))
36+
}
37+
}
38+
39+
#[cfg(test)]
40+
mod tests {
41+
use super::*;
42+
use crate::address::Address;
43+
use std::str::FromStr;
44+
use tw_encoding::hex::ToHex;
45+
use tw_keypair::ecdsa::secp256k1;
46+
use tw_keypair::traits::SigningKeyTrait;
47+
use tw_number::U256;
48+
49+
#[test]
50+
fn test_pre_hash() {
51+
let authorization = Authorization {
52+
chain_id: U256::from(1_u32),
53+
address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(),
54+
nonce: U256::from(1_u32),
55+
};
56+
assert_eq!(
57+
Authorization::hash(&authorization).unwrap().to_hex(),
58+
"3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"
59+
);
60+
}
61+
62+
#[test]
63+
fn test_sign_authorization() {
64+
let authorization = Authorization {
65+
chain_id: U256::from(1_u32),
66+
address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(),
67+
nonce: U256::from(1_u32),
68+
};
69+
let private_key = secp256k1::PrivateKey::try_from(
70+
"0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8",
71+
)
72+
.unwrap();
73+
let pre_hash = authorization.hash().unwrap();
74+
let signature = private_key.sign(pre_hash).unwrap();
75+
76+
assert_eq!(
77+
signature.r().to_hex(),
78+
"2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710"
79+
);
80+
assert_eq!(
81+
signature.s().to_hex(),
82+
"5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c"
83+
);
84+
assert_eq!(signature.v(), 1);
85+
}
86+
}

rust/tw_evm/src/message/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use tw_hash::H256;
77

88
pub mod eip191;
99
pub mod eip712;
10+
pub mod eip7702_authorization;
1011
pub mod signature;
1112

1213
pub type EthMessageBoxed = Box<dyn EthMessage>;

rust/tw_evm/src/modules/authorization_signer.rs

Lines changed: 0 additions & 91 deletions
This file was deleted.

rust/tw_evm/src/modules/message_signer.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::message::eip191::Eip191Message;
66
use crate::message::eip712::eip712_message::Eip712Message;
77
use crate::message::signature::{MessageSignature, SignatureType};
88
use crate::message::{to_signing, EthMessage, EthMessageBoxed};
9+
use crate::transaction::authorization_list::Authorization;
910
use std::borrow::Cow;
1011
use std::str::FromStr;
1112
use tw_coin_entry::coin_context::CoinContext;
@@ -123,13 +124,23 @@ impl EthMessageSigner {
123124
.map_err(to_signing)?
124125
.into_boxed()),
125126
},
127+
Proto::MessageType::MessageType_eip7702_authorization => {
128+
Ok(Authorization::from_str(&input.message)
129+
.map_err(|e| to_signing(e.into()))?
130+
.into_boxed())
131+
},
126132
}
127133
}
128134

129135
fn message_from_str(user_message: &str) -> SigningResult<EthMessageBoxed> {
130136
match Eip712Message::new(user_message) {
137+
// Try to parse as an EIP712 message
131138
Ok(typed_data) => Ok(typed_data.into_boxed()),
132-
Err(_) => Ok(Eip191Message::new(user_message).into_boxed()),
139+
Err(_) => match Authorization::from_str(user_message) {
140+
// Try to parse as an EIP7702 authorization tuple
141+
Ok(authorization) => Ok(authorization.into_boxed()),
142+
Err(_) => Ok(Eip191Message::new(user_message).into_boxed()),
143+
},
133144
}
134145
}
135146

@@ -138,7 +149,8 @@ impl EthMessageSigner {
138149
maybe_chain_id: Option<Proto::MaybeChainId>,
139150
) -> SignatureType {
140151
match msg_type {
141-
Proto::MessageType::MessageType_immutable_x => SignatureType::Standard,
152+
Proto::MessageType::MessageType_immutable_x
153+
| Proto::MessageType::MessageType_eip7702_authorization => SignatureType::Standard,
142154
Proto::MessageType::MessageType_legacy | Proto::MessageType::MessageType_typed => {
143155
SignatureType::Legacy
144156
},

rust/tw_evm/src/modules/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// Copyright © 2017 Trust Wallet.
44

55
pub mod abi_encoder;
6-
pub mod authorization_signer;
76
pub mod compiler;
87
pub mod message_signer;
98
pub mod rlp_encoder;

rust/tw_evm/src/modules/tx_builder.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::abi::prebuild::erc721::Erc721;
1111
use crate::abi::prebuild::ExecuteArgs;
1212
use crate::address::{Address, EvmAddress};
1313
use crate::evm_context::EvmContext;
14-
use crate::modules::authorization_signer::AuthorizationSigner;
14+
use crate::message::{to_signing, EthMessage};
1515
use crate::transaction::access_list::{Access, AccessList};
1616
use crate::transaction::authorization_list::{
1717
Authorization, AuthorizationList, SignedAuthorization,
@@ -29,6 +29,7 @@ use tw_encoding::hex::DecodeHex;
2929
use tw_hash::H256;
3030
use tw_keypair::ecdsa::secp256k1;
3131
use tw_keypair::ecdsa::secp256k1::Signature;
32+
use tw_keypair::traits::SigningKeyTrait;
3233
use tw_keypair::KeyPairError;
3334
use tw_memory::Data;
3435
use tw_number::U256;
@@ -657,7 +658,7 @@ impl<Context: EvmContext> TxBuilder<Context> {
657658
.into_tw()
658659
.context("Invalid authority address")?;
659660

660-
let signed_authorization =
661+
let (authorization, signature) =
661662
if let Some(other_auth_fields) = &eip7702_authorization.custom_signature {
662663
// If field `custom_signature` is provided, it means that the authorization is already signed.
663664
let chain_id = U256::from_big_endian_slice(&other_auth_fields.chain_id)
@@ -676,16 +677,14 @@ impl<Context: EvmContext> TxBuilder<Context> {
676677
.tw_err(SigningErrorType::Error_invalid_params)
677678
.context("Invalid signature")?;
678679

679-
SignedAuthorization {
680-
authorization: Authorization {
680+
(
681+
Authorization {
681682
chain_id,
682683
address,
683684
nonce,
684685
},
685-
y_parity: signature.v(),
686-
r: U256::from_big_endian(signature.r()),
687-
s: U256::from_big_endian(signature.s()),
688-
}
686+
signature,
687+
)
689688
} else {
690689
// If field `custom_signature` is not provided, the authorization will be signed with the provided private key, nonce and chainId
691690
let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())
@@ -707,17 +706,26 @@ impl<Context: EvmContext> TxBuilder<Context> {
707706
.into_tw()
708707
.context("Invalid nonce")?;
709708

710-
AuthorizationSigner::sign(
711-
&signer_key,
712-
Authorization {
713-
chain_id,
714-
address,
715-
// `authorization.nonce` must be incremented by 1 over `transaction.nonce`.
716-
nonce: nonce + 1,
717-
},
718-
)?
709+
let authorization = Authorization {
710+
chain_id,
711+
address,
712+
// `authorization.nonce` must be incremented by 1 over `transaction.nonce`.
713+
nonce: nonce + 1,
714+
};
715+
716+
let pre_hash = authorization.hash().map_err(to_signing)?;
717+
let signature = signer_key.sign(pre_hash)?;
718+
719+
(authorization, signature)
719720
};
720721

722+
let signed_authorization = SignedAuthorization {
723+
authorization,
724+
y_parity: signature.v(),
725+
r: U256::from_big_endian(signature.r()),
726+
s: U256::from_big_endian(signature.s()),
727+
};
728+
721729
Ok(AuthorizationList::from(vec![signed_authorization]))
722730
}
723731
}

rust/tw_evm/src/transaction/authorization_list.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
55
use crate::address::Address;
66
use crate::rlp::buffer::RlpBuffer;
77
use crate::rlp::RlpEncode;
8+
use serde::Deserialize;
89
use tw_number::U256;
910

1011
/// Authorization for 7702 txn support.
12+
#[derive(Deserialize)]
13+
#[serde(rename_all = "camelCase")]
1114
pub struct Authorization {
1215
/// The chain ID of the authorization.
16+
#[serde(deserialize_with = "U256::from_hex_or_decimal_str")]
1317
pub chain_id: U256,
1418
/// The address of the authorization.
1519
pub address: Address,
1620
/// The nonce for the authorization.
21+
#[serde(deserialize_with = "U256::from_hex_or_decimal_str")]
1722
pub nonce: U256,
1823
}
1924

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chainId": "0x310c5",
3+
"address": "0x3535353535353535353535353535353535353535",
4+
"nonce": "0xabc"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chainId": "200901",
3+
"address": "0x3535353535353535353535353535353535353535",
4+
"nonce": "2748"
5+
}

rust/tw_evm/tests/message_signer.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const EIP712_GREENFIELD: &str = include_str!("data/eip712_greenfield.json");
2020
const EIP712_FIXED_BYTES: &str = include_str!("data/eip712_fixed_bytes.json");
2121
const EIP712_LONG_BYTES: &str = include_str!("data/eip712_long_bytes.json");
2222
const EIP712_DIFFERENT_BYTES: &str = include_str!("data/eip712_different_bytes.json");
23+
const EIP7702_AUTHORIZATION: &str = include_str!("data/eip7702_authorization.json");
24+
const EIP7702_AUTHORIZATION_DECIMAL_STRING: &str =
25+
include_str!("data/eip7702_authorization_decimal_string.json");
2326

2427
struct SignVerifyTestInput {
2528
private_key: &'static str,
@@ -312,3 +315,25 @@ fn test_message_signer_sign_verify_eip712_different_bytes() {
312315
signature: "48dc667cd8a53beb58ea6b1745f98c21b12e1a57587ce28bae07689dba3600d40cef2685dc8a68028d38f3e63289891868ecdf05e8affc275fee3001e51d6c581c",
313316
});
314317
}
318+
319+
#[test]
320+
fn test_message_signer_sign_eip7702_authorization() {
321+
test_message_signer_sign_verify(SignVerifyTestInput {
322+
private_key: "6f96f3aa7e8052170f1864f72a9a53606ee9c0d185188266cab895512a4bcf84",
323+
msg: EIP7702_AUTHORIZATION,
324+
msg_type: Proto::MessageType::MessageType_eip7702_authorization,
325+
chain_id: None,
326+
signature: "a2c790e864ff524e703c6a476609e0b738cdcd3a96628be0ef74cb5610a29a473908f07f762f4da896e7f8d3cdaad567c58aa81412ac6dd864eb5d3956a1f04c00",
327+
});
328+
}
329+
330+
#[test]
331+
fn test_message_signer_sign_eip7702_authorization_decimal_string() {
332+
test_message_signer_sign_verify(SignVerifyTestInput {
333+
private_key: "6f96f3aa7e8052170f1864f72a9a53606ee9c0d185188266cab895512a4bcf84",
334+
msg: EIP7702_AUTHORIZATION_DECIMAL_STRING,
335+
msg_type: Proto::MessageType::MessageType_eip7702_authorization,
336+
chain_id: None,
337+
signature: "a2c790e864ff524e703c6a476609e0b738cdcd3a96628be0ef74cb5610a29a473908f07f762f4da896e7f8d3cdaad567c58aa81412ac6dd864eb5d3956a1f04c00",
338+
});
339+
}

0 commit comments

Comments
 (0)