[Ethereum] 우분투에 node 설치

execution layer client로 geth를, consensus layer client로 Prysm을 사용한다.

geth 설치

sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install -y geth

확인

geth version

부팅 시 자동 실행

sudo vi /etc/systemd/system/geth.service

[Unit]
Description=Ethereum Geth Node
After=network.target

[Service]
Type=simple
User=dasomoli
ExecStart=/usr/bin/geth --mainnet --syncmode "snap" --datadir "/home/dasomoli/geth/data" --http --http.api web3,eth,txpool --http.addr 0.0.0.0
Restart=always
RestartSec=5
LimitNOFILE=8192

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start geth
sudo systemctl enable geth
sudo systemctl status geth

위에서는 “snap” 방식을 사용했다. sync mode로 “full”도 가능.

도메인을 연결한다면 설정에 다음 설정을 포함해야 한다.

	--http.corsdomain "*" \
	--http.vhosts "*"

IPC로 geth console 연결

geth attach ipc:/home/dasomoli/geth/data/geth.ipc

로그 확인

journalctl -u geth -f

Prysm 설치

mkdir prysm
cd prysm/
sudo apt update
sudo apt install -y wget
wget <https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh>
chmod +x prysm.sh

부팅 시 자동 실행

sudo vi /etc/systemd/system/prysm-beacon.service

[Unit]
Description=Prysm Beacon Chain Client
After=network.target

[Service]
Type=simple
User=dasomoli
ExecStart=/home/dasomoli/prysm/prysm.sh beacon-chain \
    --execution-endpoint=http://localhost:8551 \
    --datadir=/home/dasomoli/prysm/data \
    --jwt-secret=/home/dasomoli/geth/data/geth/jwtsecret \
    --accept-terms-of-use \
    --mainnet \
    --checkpoint-sync-url=https://beaconstate.info --genesis-beacon-api-url=https://beaconstate.info
Restart=always
RestartSec=5
LimitNOFILE=8192

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start prysm-beacon
sudo systemctl enable prysm-beacon
sudo systemctl status prysm-beacon

로그 확인

journalctl -u prysm-beacon -f

[ethers.js] HD wallet으로 주소 파생

12개의 seed wallet이 있는 경우, 다음과 같이 파생 주소를 확인할 수 있다.

$ npm install ethers@^5.0.0

const { ethers } = require('ethers');

// 12개의 시드 단어를 여기에 입력하세요
const mnemonic = "Your seed 12 words here";

// HD Node 생성
const hdNode = ethers.utils.HDNode.fromMnemonic(mnemonic);

// 파생 경로 설정
const derivationPath = "m/44'/60'/0'/0/"; // "m/44'/905956'/0'/0/" 와 같이 사용도 가능

// 파생 주소 생성 및 출력 함수
function generateAddresses(count) {
    const addresses = [];
    for (let i = 0; i < count; i++) {
        const walletNode = hdNode.derivePath(derivationPath + i);
        console.log(i, walletNode.address);
        addresses.push({
            index: i,
            address: walletNode.address,
        });
    }
    return addresses;
}

// 원하는 주소 개수를 설정하세요
const numAddresses = 85535;
const addresses = generateAddresses(numAddresses);

[Ethereum] 트랜잭션 해시로부터 Public key 및 주소 얻기

Ethereum의 경우 버전에 따라 트랜잭션에 gasPrice를 쓸 지, maxFeePerGas/maxPriorityFeePerGas 를 쓸 지가 정해진다. https://ethereum.stackexchange.com/questions/147692/how-to-get-public-key-by-the-transaction-hash 를 참고.

주소를 얻을 때, Public key에서 앞의 한 바이트(0x04)를 빼고, keccak256을 돌리는 부분에 유의하자.

const { ethers } = require("hardhat");

// 트랜잭션 서명 값
// const r = "0xe680637b83a1dd102364503bd77979b87c92ba651132a4df8e839af69c20af95";
// const s = "0x65becfa32384747adb05f543397baaf23d1c55fa28d0c9a38924912dae6df28f";
// const v = 12266684226873;
// const chainId = 6133342113419;

// 트랜잭션 해시
const txHash = "0xc7159866fb5b94d291e8624bdc731b7a6d46533b398065de7aa7dd1af7b6aa36";
const ethermainHash = "0xef120deb6ff2e516ad44724d220c0cd73166d169a2a0b0f66dfb5613d2d6169c"
const sepoliaHash = "0xfe4ddad4cae9ea353fc91441ee5db6c70bd5468673d907926be92dd4b8a63f63"

// 실제 트랜잭션에 사용된 private key. 아무거나 넣자.
const privateKey = "0x0000000000000000000000000000000000000000000000000000000000000001";

// Web3 프로바이더 설정 (Hardhat 로컬 노드 사용 예시)
const provider = ethers.provider;

// 서명된 트랜잭션 정보 가져오기
async function getPublicKeyFromTransactionHash(provider, txHash) {
  // Fetch the transaction using the transaction hash and provier
  const tx = await provider.getTransaction(txHash);

  console.log(tx);

  // Extract the all the relevant fields from the transaction (We need all of them)
  const unsignedTx = {
    gasPrice: tx.gasPrice,
    gasLimit: tx.gasLimit,
    value: tx.value,
    nonce: tx.nonce,
    data: tx.data,
    chainId: tx.chainId,
    to: tx.to,
    type: tx.type,
    // maxFeePerGas: tx.maxFeePerGas,
    // maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
  };

  // Serializing tx without the signature
  const serializedTx = ethers.utils.serializeTransaction(unsignedTx);

  // Extract the signature (v, r, s) from the transaction
  const { v, r, s } = tx;

  // Join splitted signature
  const signature = ethers.utils.joinSignature({ v, r, s });

  const recoveredPublicKey = ethers.utils.recoverPublicKey(
    ethers.utils.keccak256(serializedTx),
    signature,
  );
  console.log("recoveredPublicKey:", recoveredPublicKey);
  const keccak = ethers.utils.keccak256("0x" + recoveredPublicKey.slice(4))
  console.log("keccak256 of recoveredPublicKey:", keccak, ethers.utils.keccak256("0x" + recoveredPublicKey.slice(4)));

  // Recover the address or public key with (replace recoverAddress by recoverPublicKey) associated with the transaction
  return ethers.utils.recoverAddress(
    ethers.utils.keccak256(serializedTx),
    signature
  );
}

async function main() {
  const address = await getPublicKeyFromTransactionHash(provider, txHash);

  console.log("Address:", address);
}

main()
.then(() => process.exit(0))
.catch((error) => {
    console.error(error);
    process.exit(1);
});