이더리움 서명

이더리움(Ethereum)에서의 서명은 ECDSA (Elliptic Curve Digital Signature Algorithm) 를 기반으로 하며, secp256k1 타원곡선을 사용합니다.
서명은 트랜잭션을 승인하고, 체인에 이를 정당하게 제출할 수 있도록 하는 “소유 증명 메커니즘”으로 작동합니다.

아래에서 구체적으로 구조와 프로세스를 설명드릴게요.


🧱 1. 서명의 구조 (Ethereum 트랜잭션 서명)

이더리움 서명은 (v, r, s)로 구성되어 있습니다.

필드설명
rECDSA 서명의 x좌표 일부 (32바이트)
s서명의 계산된 값 (32바이트)
v복구 아이디 (1바이트 또는 1~3바이트, 서명자 공개키를 찾는 데 사용)
  • 이 3개 값은 모두 트랜잭션에 포함되며 블록체인에 기록됩니다.

🔐 2. 서명 생성 절차 (클라이언트 지갑 측)

① 메시지 구성

  • 서명 대상은 RLP 인코딩된 트랜잭션 구조체의 바이트입니다.
  • EIP-155 이후에는 chainId도 포함됨.

예:

RLP([nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0])

② 해시 계산

  • Keccak-256 으로 메시지를 해시 → msgHash
msgHash = keccak256(RLP_encoded_transaction)

③ ECDSA 서명

  • 개인키 dmsgHash에 대해 (r, s) 서명 생성
  • v는 서명 복원용: 일반적으로 27 또는 28, 또는 chainId * 2 + 35 또는 +36 (EIP-155)

📤 3. 서명된 트랜잭션 전송

  • 최종적으로 트랜잭션 객체에 (r, s, v)를 포함
  • 이를 다시 RLP 인코딩하여 네트워크에 전파

🧪 4. 노드에서의 서명 검증 (블록체인 상에서)

  • 수신된 서명 트랜잭션으로부터:
    1. r, s, v를 추출
    2. msgHash 재계산
    3. 공개키 복원 (ECRecover(msgHash, v, r, s))
    4. 복원된 공개키 → 주소로 변환 (keccak256(pubkey)[-20:])
    5. 이 주소가 from 필드의 계정과 일치하는지 확인

✅ 일치하면 → 유효한 서명
❌ 불일치하면 → 서명 위조 또는 오류


📄 5. 예시: 서명된 이더리움 트랜잭션

{
  "nonce": "0x01",
  "gasPrice": "0x04A817C800",
  "gas": "0x5208",
  "to": "0xAbc123...",
  "value": "0xDE0B6B3A7640000",
  "data": "0x",
  "v": "0x25",
  "r": "0x6b4c...",
  "s": "0x4b7a..."
}

📚 추가 정보

항목내용
서명 알고리즘ECDSA on secp256k1
해시 알고리즘Keccak-256 (SHA3와 유사하지만 다름)
주소 유도address = keccak256(pubkey)[12:]
복구 알고리즘ecrecover() — EVM 내장 함수, 스마트컨트랙트에서도 사용 가능

✅ 요약

항목설명
서명 목적트랜잭션 소유자 증명 및 위조 방지
사용 알고리즘ECDSA (secp256k1), Keccak-256
서명 구성(r, s, v)
공개키 복원ecrecover(msgHash, v, r, s) 함수로 누구나 가능
보안 근거타원곡선 이산 로그 문제 (ECDLP) 기반의 일방향성

이더리움에서 트랜잭션 서명 방식은 진화해왔습니다. 각 개선 제안(EIP)은 보안, 확장성, 가스 효율, 기능 확장을 위해 도입되었고, 이에 따라 트랜잭션 형식과 서명 방식도 변화했습니다.

아래에서 EIP-155 / EIP-2718 / EIP-2930을 중심으로 트랜잭션 타입별 서명 방식 차이를 정리해드립니다.


🔢 기본 구조 정리: EIP 별 핵심 목적

EIP 번호주요 내용트랜잭션 타입서명 방식 변화
EIP-155리플레이 공격 방지 (체인 ID 포함)Legacy (type 0)vchainId 포함
EIP-2718트랜잭션 형식 분리 (Envelope)Typed Tx: type 0x01, 0x02v, r, s 유지, 구조만 변경
EIP-2930접근 목록 트랜잭션 (AccessList)Type 1 (0x01)RLP 대신 구조화된 서명
EIP-1559동적 수수료 TxType 2 (0x02)동일 구조 확장 적용

📜 1. EIP-155: Legacy 트랜잭션에 체인ID를 서명에 포함

🔹 문제점

  • 같은 서명을 여러 체인(BSC, ETH 등)에서 재사용 가능 → 리플레이 공격 위험

🔹 해결책

  • 트랜잭션에 chainId를 포함하여 서명
  • 서명 v값이:
    v = chainId * 2 + 35 or 36

🧪 구조 예 (EIP-155 적용 전후)

항목Before (EIP-155 이전)After (EIP-155 이후)
v27 또는 28chainId×2+35 or +36
서명 대상 msgRLP(Tx)RLP(Tx + chainId + 0 + 0)

🧳 2. EIP-2718: 트랜잭션 타입 구조 도입 (Typed Transactions)

🔹 목적

  • 다양한 트랜잭션 구조를 병렬적으로 도입 가능하게 하기 위해 → 버전관리 개념

🔹 주요 변화

  • 모든 이후 트랜잭션은 바이트 prefix로 타입 명시
    • 0x01: EIP-2930 (AccessList Tx)
    • 0x02: EIP-1559 (Fee Market Tx)
  • RLP 대신 고정 구조 사용

🧩 구조 예

0x01 || rlp([chainId, nonce, gasPrice, ..., accessList, v, r, s])

📦 3. EIP-2930: Access List 트랜잭션 (Type 0x01)

🔹 목적

  • 특정 주소/스토리지 슬롯만 접근하는 것을 명시하여 가스 최적화

🔹 특징

  • accessList:
    리스트 형태의 주소 + 슬롯 배열 구조
  • 서명 대상은 rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList])
  • 서명 결과: (v, r, s)

🔁 4. EIP-1559: 동적 수수료 트랜잭션 (Type 0x02)

🔹 목적

  • BaseFee + MaxPriorityFee 구조로 가스 수수료 예측 가능하게 개선

🔹 구조

0x02 || rlp([
  chainId, nonce,
  maxPriorityFeePerGas, maxFeePerGas,
  gasLimit, to, value, data,
  accessList, v, r, s
])
  • 여전히 (v, r, s) 구조의 서명
  • 서명 대상은 prefix 0x02 포함 구조화된 tx

✍️ 트랜잭션 타입별 서명 비교 요약

항목Legacy (EIP-155)EIP-2930 (0x01)EIP-1559 (0x02)
타입바이트없음 (default)0x010x02
서명 대상 해시RLP(Tx + chainId + 0 + 0)RLP 구조된 필드들RLP 구조된 필드들
서명 구조(v, r, s)(v, r, s)(v, r, s)
수수료 구조gasPricegasPricemaxFee, priorityFee
accessList
backwards compat✅ (pre-EIP-2718)

✅ 요약

개념정리
EIP-155기존 트랜잭션에 chainId를 추가하여 리플레이 공격 방지
EIP-2718트랜잭션을 “타입”별로 구분 가능하게 함 (확장성)
EIP-2930Gas 최적화를 위해 access list 추가
EIP-1559수수료 구조 개편 (base fee + tip) + access list 포함


아래는 **JavaScript (Node.js 환경)**에서 이더리움 트랜잭션 서명 해시를 계산하는 실제 코드 예시입니다.
EIP-155 (Legacy Tx) 기준으로 먼저 설명드리고, EIP-1559 (Type 0x02)용 코드도 추가로 제공합니다.


📦 필요 라이브러리

npm install ethereumjs-tx ethereumjs-util

🧪 1. EIP-155 기준 서명 해시 계산 (Legacy Tx)

const { Transaction } = require('@ethereumjs/tx');
const { keccak256 } = require('ethereumjs-util');

const Common = require('@ethereumjs/common').default;
const common = new Common({ chain: 'mainnet', hardfork: 'london' });

// 트랜잭션 데이터 (RLP 구조)
const txData = {
  nonce: '0x00',
  gasPrice: '0x09184e72a000', // 10000000000000
  gasLimit: '0x2710',          // 10000
  to: '0x3535353535353535353535353535353535353535',
  value: '0x00',
  data: '0x',
  chainId: 1 // EIP-155 적용
};

// 트랜잭션 객체 생성
const tx = Transaction.fromTxData(txData, { common });

// 서명 대상 해시 계산
const msgHash = tx.getMessageToSign(false); // EIP-155용 getMessageToSign
console.log('Message hash (to sign):', '0x' + msgHash.toString('hex'));

// 실제 서명을 하려면 개인키로 sign(msgHash)

🔥 2. EIP-1559 (Type 0x02 Tx) 해시 계산

const { FeeMarketEIP1559Transaction } = require('@ethereumjs/tx');

const txData = {
  nonce: '0x00',
  maxPriorityFeePerGas: '0x01',
  maxFeePerGas: '0x09184e72a000',
  gasLimit: '0x2710',
  to: '0x3535353535353535353535353535353535353535',
  value: '0x00',
  data: '0x',
  chainId: 1,
  accessList: []
};

// Type 0x02 트랜잭션
const tx = FeeMarketEIP1559Transaction.fromTxData(txData, { common });

// 서명 대상 해시 계산
const msgHash = tx.getMessageToSign();
console.log('Message hash (to sign):', '0x' + msgHash.toString('hex'));

🔑 서명 (선택적으로)

개인키가 있다면 다음처럼 서명까지 가능:

const privateKey = Buffer.from('your_private_key_hex', 'hex');
const signedTx = tx.sign(privateKey);
console.log('Signed tx:', '0x' + signedTx.serialize().toString('hex'));

✅ 요약

항목함수설명
Legacy Tx 서명 해시tx.getMessageToSign(false)EIP-155까지 포함한 서명용 해시
1559 Tx 서명 해시FeeMarketEIP1559Transaction.getMessageToSign()타입 0x02 트랜잭션 해시
실제 서명tx.sign(privateKey)개인키로 서명 수행
전체 트랜잭션 직렬화tx.serialize()서명 포함하여 전송 가능 상태

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다