이더리움(Ethereum)에서의 서명은 ECDSA (Elliptic Curve Digital Signature Algorithm) 를 기반으로 하며, secp256k1 타원곡선을 사용합니다.
서명은 트랜잭션을 승인하고, 체인에 이를 정당하게 제출할 수 있도록 하는 “소유 증명 메커니즘”으로 작동합니다.
아래에서 구체적으로 구조와 프로세스를 설명드릴게요.
🧱 1. 서명의 구조 (Ethereum 트랜잭션 서명)
이더리움 서명은 (v, r, s)
로 구성되어 있습니다.
필드 | 설명 |
---|---|
r | ECDSA 서명의 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 서명
- 개인키
d
로msgHash
에 대해(r, s)
서명 생성 v
는 서명 복원용: 일반적으로27
또는28
, 또는chainId * 2 + 35
또는+36
(EIP-155)
📤 3. 서명된 트랜잭션 전송
- 최종적으로 트랜잭션 객체에
(r, s, v)
를 포함 - 이를 다시 RLP 인코딩하여 네트워크에 전파
🧪 4. 노드에서의 서명 검증 (블록체인 상에서)
- 수신된 서명 트랜잭션으로부터:
r
,s
,v
를 추출msgHash
재계산- 공개키 복원 (
ECRecover(msgHash, v, r, s)
) - 복원된 공개키 → 주소로 변환 (
keccak256(pubkey)[-20:]
) - 이 주소가
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) | v 에 chainId 포함 |
EIP-2718 | 트랜잭션 형식 분리 (Envelope) | Typed Tx: type 0x01, 0x02 등 | v, r, s 유지, 구조만 변경 |
EIP-2930 | 접근 목록 트랜잭션 (AccessList) | Type 1 (0x01) | RLP 대신 구조화된 서명 |
EIP-1559 | 동적 수수료 Tx | Type 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 이후) |
---|---|---|
v | 27 또는 28 | chainId×2+35 or +36 |
서명 대상 msg | RLP(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) | 0x01 | 0x02 |
서명 대상 해시 | RLP(Tx + chainId + 0 + 0) | RLP 구조된 필드들 | RLP 구조된 필드들 |
서명 구조 | (v, r, s) | (v, r, s) | (v, r, s) |
수수료 구조 | gasPrice | gasPrice | maxFee , priorityFee |
accessList | ❌ | ✅ | ✅ |
backwards compat | ✅ (pre-EIP-2718) | ✅ | ✅ |
✅ 요약
개념 | 정리 |
---|---|
EIP-155 | 기존 트랜잭션에 chainId 를 추가하여 리플레이 공격 방지 |
EIP-2718 | 트랜잭션을 “타입”별로 구분 가능하게 함 (확장성) |
EIP-2930 | Gas 최적화를 위해 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() | 서명 포함하여 전송 가능 상태 |
답글 남기기