Skip to content

Commit a4069ee

Browse files
authored
Merge pull request #447 from oasisprotocol/ZigaMr/feature/eip1559-eip2930
Add EIP1559 and EIP2930 signer libraries.
2 parents 68206d9 + b971cb2 commit a4069ee

File tree

5 files changed

+820
-0
lines changed

5 files changed

+820
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
import {Sapphire} from "./Sapphire.sol";
4+
import {EthereumUtils, SignatureRSV} from "./EthereumUtils.sol";
5+
import {RLPWriter} from "./RLPWriter.sol";
6+
import {EIPTypes} from "./EIPTypes.sol";
7+
8+
/**
9+
* @title Ethereum EIP-1559 transaction signer & encoder
10+
*/
11+
library EIP1559Signer {
12+
struct EIP1559Tx {
13+
uint64 nonce;
14+
uint256 maxPriorityFeePerGas;
15+
uint256 maxFeePerGas;
16+
uint64 gasLimit;
17+
address to;
18+
uint256 value;
19+
bytes data;
20+
EIPTypes.AccessList accessList;
21+
uint256 chainId;
22+
}
23+
24+
/**
25+
* @notice Encode an unsigned EIP-1559 transaction for signing
26+
* @param rawTx Transaction to encode
27+
*/
28+
function encodeUnsignedTx(EIP1559Tx memory rawTx)
29+
internal
30+
pure
31+
returns (bytes memory)
32+
{
33+
bytes[] memory b = new bytes[](9);
34+
b[0] = RLPWriter.writeUint(rawTx.chainId);
35+
b[1] = RLPWriter.writeUint(rawTx.nonce);
36+
b[2] = RLPWriter.writeUint(rawTx.maxPriorityFeePerGas);
37+
b[3] = RLPWriter.writeUint(rawTx.maxFeePerGas);
38+
b[4] = RLPWriter.writeUint(rawTx.gasLimit);
39+
b[5] = RLPWriter.writeAddress(rawTx.to);
40+
b[6] = RLPWriter.writeUint(rawTx.value);
41+
b[7] = RLPWriter.writeBytes(rawTx.data);
42+
b[8] = EIPTypes.encodeAccessList(rawTx.accessList);
43+
44+
// RLP encode the transaction data
45+
bytes memory rlpEncodedTx = RLPWriter.writeList(b);
46+
47+
// Return the unsigned transaction with EIP-1559 type prefix
48+
return abi.encodePacked(hex"02", rlpEncodedTx);
49+
}
50+
51+
/**
52+
* @notice Encode a signed EIP-1559 transaction
53+
* @param rawTx Transaction which was signed
54+
* @param rsv R, S & V parameters of signature
55+
*/
56+
function encodeSignedTx(EIP1559Tx memory rawTx, SignatureRSV memory rsv)
57+
internal
58+
pure
59+
returns (bytes memory)
60+
{
61+
bytes[] memory b = new bytes[](12);
62+
b[0] = RLPWriter.writeUint(rawTx.chainId);
63+
b[1] = RLPWriter.writeUint(rawTx.nonce);
64+
b[2] = RLPWriter.writeUint(rawTx.maxPriorityFeePerGas);
65+
b[3] = RLPWriter.writeUint(rawTx.maxFeePerGas);
66+
b[4] = RLPWriter.writeUint(rawTx.gasLimit);
67+
b[5] = RLPWriter.writeAddress(rawTx.to);
68+
b[6] = RLPWriter.writeUint(rawTx.value);
69+
b[7] = RLPWriter.writeBytes(rawTx.data);
70+
b[8] = EIPTypes.encodeAccessList(rawTx.accessList);
71+
b[9] = RLPWriter.writeUint(uint256(rsv.v));
72+
b[10] = RLPWriter.writeUint(uint256(rsv.r));
73+
b[11] = RLPWriter.writeUint(uint256(rsv.s));
74+
75+
// RLP encode the transaction data
76+
bytes memory rlpEncodedTx = RLPWriter.writeList(b);
77+
78+
// Return the signed transaction with EIP-1559 type prefix
79+
return abi.encodePacked(hex"02", rlpEncodedTx);
80+
}
81+
82+
/**
83+
* @notice Sign a raw transaction
84+
* @param rawTx Transaction to sign
85+
* @param pubkeyAddr Ethereum address of secret key
86+
* @param secretKey Secret key used to sign
87+
*/
88+
function signRawTx(
89+
EIP1559Tx memory rawTx,
90+
address pubkeyAddr,
91+
bytes32 secretKey
92+
) internal view returns (SignatureRSV memory ret) {
93+
// First encode the transaction without signature fields
94+
bytes memory encoded = encodeUnsignedTx(rawTx);
95+
96+
// Hash the encoded unsigned transaction
97+
bytes32 digest = keccak256(encoded);
98+
99+
// Sign the hash
100+
ret = EthereumUtils.sign(pubkeyAddr, secretKey, digest);
101+
}
102+
103+
/**
104+
* @notice Sign a transaction, returning it in EIP-1559 encoded form
105+
* @param publicAddress Ethereum address of secret key
106+
* @param secretKey Secret key used to sign
107+
* @param transaction Transaction to sign
108+
*/
109+
function sign(
110+
address publicAddress,
111+
bytes32 secretKey,
112+
EIP1559Tx memory transaction
113+
) internal view returns (bytes memory) {
114+
SignatureRSV memory rsv = signRawTx(
115+
transaction,
116+
publicAddress,
117+
secretKey
118+
);
119+
120+
// For EIP-1559, we only need to normalize v to 0/1
121+
rsv.v = rsv.v - 27;
122+
123+
return encodeSignedTx(transaction, rsv);
124+
}
125+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
import {Sapphire} from "./Sapphire.sol";
4+
import {EthereumUtils, SignatureRSV} from "./EthereumUtils.sol";
5+
import {RLPWriter} from "./RLPWriter.sol";
6+
import {EIPTypes} from "./EIPTypes.sol";
7+
8+
/**
9+
* @title Ethereum EIP-2930 transaction signer & encoder
10+
*/
11+
library EIP2930Signer {
12+
struct EIP2930Tx {
13+
uint64 nonce;
14+
uint256 gasPrice;
15+
uint64 gasLimit;
16+
address to;
17+
uint256 value;
18+
bytes data;
19+
EIPTypes.AccessList accessList;
20+
uint256 chainId;
21+
}
22+
23+
/**
24+
* @notice Encode an unsigned EIP-2930 transaction for signing
25+
* @param rawTx Transaction to encode
26+
*/
27+
function encodeUnsignedTx(EIP2930Tx memory rawTx)
28+
internal
29+
pure
30+
returns (bytes memory)
31+
{
32+
bytes[] memory b = new bytes[](8);
33+
b[0] = RLPWriter.writeUint(rawTx.chainId);
34+
b[1] = RLPWriter.writeUint(rawTx.nonce);
35+
b[2] = RLPWriter.writeUint(rawTx.gasPrice);
36+
b[3] = RLPWriter.writeUint(rawTx.gasLimit);
37+
b[4] = RLPWriter.writeAddress(rawTx.to);
38+
b[5] = RLPWriter.writeUint(rawTx.value);
39+
b[6] = RLPWriter.writeBytes(rawTx.data);
40+
b[7] = EIPTypes.encodeAccessList(rawTx.accessList);
41+
42+
// RLP encode the transaction data
43+
bytes memory rlpEncodedTx = RLPWriter.writeList(b);
44+
45+
// Return the unsigned transaction with EIP-2930 type prefix
46+
return abi.encodePacked(hex"01", rlpEncodedTx);
47+
}
48+
49+
/**
50+
* @notice Encode a signed EIP-2930 transaction
51+
* @param rawTx Transaction which was signed
52+
* @param rsv R, S & V parameters of signature
53+
*/
54+
function encodeSignedTx(EIP2930Tx memory rawTx, SignatureRSV memory rsv)
55+
internal
56+
pure
57+
returns (bytes memory)
58+
{
59+
bytes[] memory b = new bytes[](11);
60+
b[0] = RLPWriter.writeUint(rawTx.chainId);
61+
b[1] = RLPWriter.writeUint(rawTx.nonce);
62+
b[2] = RLPWriter.writeUint(rawTx.gasPrice);
63+
b[3] = RLPWriter.writeUint(rawTx.gasLimit);
64+
b[4] = RLPWriter.writeAddress(rawTx.to);
65+
b[5] = RLPWriter.writeUint(rawTx.value);
66+
b[6] = RLPWriter.writeBytes(rawTx.data);
67+
b[7] = EIPTypes.encodeAccessList(rawTx.accessList);
68+
b[8] = RLPWriter.writeUint(uint256(rsv.v));
69+
b[9] = RLPWriter.writeUint(uint256(rsv.r));
70+
b[10] = RLPWriter.writeUint(uint256(rsv.s));
71+
72+
// RLP encode the transaction data
73+
bytes memory rlpEncodedTx = RLPWriter.writeList(b);
74+
75+
// Return the signed transaction with EIP-2930 type prefix
76+
return abi.encodePacked(hex"01", rlpEncodedTx);
77+
}
78+
79+
/**
80+
* @notice Sign a raw transaction
81+
* @param rawTx Transaction to sign
82+
* @param pubkeyAddr Ethereum address of secret key
83+
* @param secretKey Secret key used to sign
84+
*/
85+
function signRawTx(
86+
EIP2930Tx memory rawTx,
87+
address pubkeyAddr,
88+
bytes32 secretKey
89+
) internal view returns (SignatureRSV memory ret) {
90+
// First encode the transaction without signature fields
91+
bytes memory encoded = encodeUnsignedTx(rawTx);
92+
93+
// Hash the encoded unsigned transaction
94+
bytes32 digest = keccak256(encoded);
95+
96+
// Sign the hash
97+
ret = EthereumUtils.sign(pubkeyAddr, secretKey, digest);
98+
}
99+
100+
/**
101+
* @notice Sign a transaction, returning it in EIP-2930 encoded form
102+
* @param publicAddress Ethereum address of secret key
103+
* @param secretKey Secret key used to sign
104+
* @param transaction Transaction to sign
105+
*/
106+
function sign(
107+
address publicAddress,
108+
bytes32 secretKey,
109+
EIP2930Tx memory transaction
110+
) internal view returns (bytes memory) {
111+
SignatureRSV memory rsv = signRawTx(
112+
transaction,
113+
publicAddress,
114+
secretKey
115+
);
116+
117+
// For EIP-2930, we only need to normalize v to 0/1
118+
rsv.v = rsv.v - 27;
119+
120+
return encodeSignedTx(transaction, rsv);
121+
}
122+
}

contracts/contracts/EIPTypes.sol

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
import {RLPWriter} from "./RLPWriter.sol";
4+
5+
library EIPTypes {
6+
struct AccessList {
7+
AccessListItem[] items;
8+
}
9+
10+
struct AccessListItem {
11+
address addr;
12+
bytes32[] storageKeys;
13+
}
14+
15+
/**
16+
* @notice Encode an access list for EIP-1559 and EIP-2930 transactions
17+
*/
18+
function encodeAccessList(AccessList memory list)
19+
internal
20+
pure
21+
returns (bytes memory)
22+
{
23+
bytes[] memory items = new bytes[](list.items.length);
24+
25+
for (uint256 i = 0; i < list.items.length; i++) {
26+
bytes[] memory item = new bytes[](2);
27+
// Encode the address
28+
item[0] = RLPWriter.writeAddress(list.items[i].addr);
29+
30+
// Encode storage keys
31+
bytes[] memory storageKeys = new bytes[](
32+
list.items[i].storageKeys.length
33+
);
34+
for (uint256 j = 0; j < list.items[i].storageKeys.length; j++) {
35+
// Use writeBytes for the full storage key
36+
storageKeys[j] = RLPWriter.writeBytes(
37+
abi.encodePacked(list.items[i].storageKeys[j])
38+
);
39+
}
40+
item[1] = RLPWriter.writeList(storageKeys);
41+
42+
items[i] = RLPWriter.writeList(item);
43+
}
44+
45+
return RLPWriter.writeList(items);
46+
}
47+
}

0 commit comments

Comments
 (0)