Created
May 3, 2025 15:08
-
-
Save lucaspere/7377e5b0c0fb50ee331f04ed3771947c to your computer and use it in GitHub Desktop.
Demonstration of manually signing a legacy Ethereum transaction in TypeScript using RLP, Keccak256 and secp256k1, comparing against the viem library.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { secp256k1 } from "@noble/curves/secp256k1"; | |
import keccak256 from "keccak256"; | |
import { assert } from "node:console"; | |
import RLP, { Input } from "rlp"; | |
import * as viem from "viem"; | |
import { privateKeyToAccount } from "viem/accounts"; | |
const messageToSign = { | |
nonce: 0, | |
gasPrice: viem.parseGwei("20"), | |
value: viem.parseEther("1"), | |
gas: 21000n, | |
to: "0x3535353535353535353535353535353535353535", | |
data: "0x", | |
}; | |
const privateKey = | |
"0xd76e578c0264d4bf7cb6f3e23a8c39feea8d16e39059b4fe69b7ac8e86ed8f5e"; | |
const account = privateKeyToAccount(privateKey); | |
const signedPrivate = await account.signTransaction({type: "legacy", ...messageToSign} as any); | |
console.log(`Signed Transaction:`, signedPrivate); | |
const [rlpBytes,] = inputToRlp([ | |
messageToSign.nonce, | |
messageToSign.gasPrice, | |
messageToSign.gas, | |
messageToSign.to, | |
messageToSign.value, | |
messageToSign.data, | |
]); | |
const hash = keccak256(Buffer.from(rlpBytes)); | |
const { r, s, recovery} = secp256k1.sign(hash, viem.hexToBytes(privateKey)); | |
const v = 27 + recovery; | |
const finalListToSign = { | |
...messageToSign, | |
r, | |
s, | |
v, | |
}; | |
const [, finalRlpSigned] = inputToRlp([ | |
finalListToSign.nonce, | |
finalListToSign.gasPrice, | |
finalListToSign.gas, | |
finalListToSign.to, | |
finalListToSign.value, | |
finalListToSign.data, | |
finalListToSign.v, | |
finalListToSign.r, | |
finalListToSign.s, | |
]); | |
function inputToRlp(value: Input) { | |
const rlp = RLP.encode(value); | |
const rlpHex = viem.toHex(rlp); | |
return [rlp, rlpHex]; | |
} | |
assert(signedPrivate === finalRlpSigned, "Signed transaction does not match"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment