[카테고리:] 미분류

  • 이더리움 서명

    이더리움(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()서명 포함하여 전송 가능 상태