-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathutils7702.test.js
More file actions
106 lines (96 loc) · 3.6 KB
/
Copy pathutils7702.test.js
File metadata and controls
106 lines (96 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Unit tests for issue #128: the legacy transaction v field was computed
// with Number(chainId), losing precision for chain IDs above 2^53 and
// producing an unrecoverable signature. Verified independently by parsing
// the raw transaction with ethers and checking the recovered sender.
const ak = require("../dist/index.cjs");
const { Transaction, Wallet } = require("ethers");
const DESTINATION = "0x1111111111111111111111111111111111111111";
function signAndParse(chainId) {
const wallet = Wallet.createRandom();
const raw = ak.createAndSignLegacyRawTransaction(
chainId,
1n, // nonce
1000000000n, // gas price
21000n, // gas limit
DESTINATION,
0n, // value
"0x",
wallet.privateKey,
);
return { wallet, tx: Transaction.from(raw) };
}
describe("createAndSignLegacyRawTransaction v computation (#128)", () => {
test("chainId above 2^53 keeps exact EIP-155 v and recovers the sender", () => {
const chainId = 4337433743374337433n; // > 2^53, not float-representable
const { wallet, tx } = signAndParse(chainId);
expect(tx.chainId).toBe(chainId);
expect(tx.from.toLowerCase()).toBe(wallet.address.toLowerCase());
});
test("small chainId still signs and recovers correctly", () => {
const chainId = 11155111n; // Sepolia
const { wallet, tx } = signAndParse(chainId);
expect(tx.chainId).toBe(chainId);
expect(tx.from.toLowerCase()).toBe(wallet.address.toLowerCase());
});
});
describe("createAndSignLegacyRawTransaction canonical RLP r/s", () => {
// These keys deterministically (RFC 6979) produce a signature whose r or s
// has a leading zero byte for this payload; the raw tx must still encode
// r/s as minimal integers or nodes reject it as non-canonical RLP.
test.each([38, 63])(
"key %i with a leading-zero r/s component encodes minimally and recovers",
(i) => {
const { decodeRlp, getBytes } = require("ethers");
const pk = "0x" + i.toString(16).padStart(64, "0");
const raw = ak.createAndSignLegacyRawTransaction(
1n,
0n,
1000000000n,
21000n,
"0x" + "aa".repeat(20),
0n,
"0x",
pk,
);
const fields = decodeRlp(raw);
const r = getBytes(fields[7]);
const s = getBytes(fields[8]);
expect(r.length === 0 || r[0] !== 0).toBe(true);
expect(s.length === 0 || s[0] !== 0).toBe(true);
const tx = Transaction.from(raw);
expect(tx.from.toLowerCase()).toBe(new Wallet(pk).address.toLowerCase());
},
);
});
describe("createAndSignEip7702DelegationAuthorization low-s normalization", () => {
const N = BigInt(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
);
test("normalizes a high-s callback signature to the low-s complement", async () => {
const { SigningKey } = require("ethers");
const wallet = new Wallet("0x" + "11".repeat(32));
const chainId = 1n;
const delegatee = "0x" + "aa".repeat(20);
const nonce = 0n;
const authHash = ak.createEip7702DelegationAuthorizationHash(chainId, delegatee, nonce);
const lowSig = wallet.signingKey.sign(authHash); // ethers always low-s
// construct the complementary high-s signature with flipped parity
const highS = N - BigInt(lowSig.s);
const highV = lowSig.yParity === 0 ? 28 : 27;
const highSig =
"0x" +
lowSig.r.slice(2) +
highS.toString(16).padStart(64, "0") +
highV.toString(16).padStart(2, "0");
const auth = await ak.createAndSignEip7702DelegationAuthorization(
chainId,
delegatee,
nonce,
async () => highSig,
);
// must come back as the original low-s signature
expect(BigInt(auth.s)).toBe(BigInt(lowSig.s));
expect(Number(BigInt(auth.yParity))).toBe(lowSig.yParity);
expect(BigInt(auth.s) <= N / 2n).toBe(true);
});
});