Guider/Blockchain/BlockchainDevGuide0002
Blockchain#02

BlockchainDevGuide0002

프로그래밍 기초 완전 정복

 

⛓️ BLOCKCHAIN DEV GUIDE

BlockchainDevGuide0002

프로그래밍 기초 완전 정복 A-Z

JavaScript로 해시·암호화·블록체인 기초를 직접 구현하는 실전 가이드

⏱ 예상 학습: 20~25시간  |  💻 난이도: 입문~초급  |  🎯 목표: 코드로 블록체인 이해
🗺️ BlockchainDevGuide 시리즈 진행 현황
✅ 0001 블록체인 개념 🔥 0002 프로그래밍 기초 ← 지금 여기! ⏳ 0003 블록체인 구조 구현 ⏳ 0004 스마트 컨트랙트 ⏳ 0005 배포 & 개발환경 ⏳ 0006 NFT & DeFi ⏳ 0007 실전 프로젝트

 

📋 학습 목차 — 코드를 치면서 블록체인을 이해합니다

챕터 주제 핵심 기술
01 개발 환경 세팅 (Node.js) Node.js, npm, VSCode 설치 및 설정
02 JavaScript 핵심 문법 변수, 함수, 클래스, async/await, 모듈
03 해시 함수 직접 구현 crypto 모듈, SHA-256, 파일 해시
04 비대칭 암호화 구현 공개키/개인키 생성, 서명, 검증
05 머클 트리 직접 구현 이진 트리, 머클 루트, 증명
06 트랜잭션 구조 구현 UTXO 개념, 트랜잭션 서명 & 검증
07 PoW 채굴 알고리즘 구현 Nonce, 난이도 조절, 채굴 시뮬레이션
08 지갑(Wallet) 구현 키 페어 생성, 주소 도출, 잔액 조회
09 P2P 네트워크 기초 WebSocket, 노드 간 통신, 블록 동기화
10 현업 면접 Q&A TOP 15 코딩 테스트 + 개념 면접 완전 정복
CHAPTER 01

개발 환경 세팅

Node.js · VSCode · npm — 블록체인 개발의 첫 번째 관문

1-1 필수 설치 도구

도구 버전 역할 다운로드
Node.js 20 LTS 이상 JavaScript 실행 환경, npm 패키지 관리자 포함 nodejs.org
VSCode 최신 버전 코드 편집기 (무료, 가장 많이 쓰임) code.visualstudio.com
Git 최신 버전 버전 관리, GitHub 연동 git-scm.com
Postman 최신 버전 API 테스트 도구 (나중에 필요) postman.com
# 설치 확인 명령어 (터미널/PowerShell에서 실행)
node --version   # v20.x.x 출력되면 OK
npm --version    # 10.x.x 출력되면 OK
git --version    # git version 2.x.x 출력되면 OK

# 첫 프로젝트 만들기
mkdir blockchain-study   # 폴더 생성
cd blockchain-study
npm init -y              # package.json 자동 생성

# 필수 패키지 설치
npm install elliptic     # 타원곡선 암호화 (지갑 생성)
npm install merkletreejs # 머클 트리
npm install ws           # WebSocket (P2P 네트워크)

1-2 VSCode 필수 익스텐션

익스텐션명 용도 중요도
Solidity (Nomic Foundation) Solidity 문법 강조, 자동 완성 🔴 필수
Prettier 코드 자동 포맷터 🔴 필수
ESLint JavaScript 코드 품질 검사 🔴 필수
GitLens Git 이력 시각화 🟡 권장
CHAPTER 02

JavaScript 핵심 문법

블록체인 개발에 반드시 필요한 JS 문법만 빠르게 정복합니다

2-1 변수 & 데이터 타입

// ✅ 변수 선언 — const(상수), let(변수), var(쓰지 말 것)
const blockHash = '0x1234abcd...';   // 재할당 불가 (권장)
let nonce = 0;                         // 재할당 가능

// ✅ 블록체인에서 자주 쓰는 데이터 타입
const amount = BigInt('1000000000000000000');  // 1 ETH (Wei 단위, 일반 숫자로 못 표현)
const txData = {                        // 객체 (트랜잭션 구조)
  from: '0xAbc...',
  to: '0xDef...',
  amount: 1000n,                        // BigInt 리터럴
  timestamp: Date.now()
};
const txList = [txData, txData2];       // 배열 (블록의 트랜잭션 목록)

// ✅ 16진수 / 2진수 / Buffer 다루기 (블록체인 핵심!)
const hex = 'ff';
console.log(parseInt(hex, 16));           // 255 (16진수 → 10진수)
console.log((255).toString(16));        // 'ff' (10진수 → 16진수)
console.log((255).toString(2));         // '11111111' (10진수 → 2진수)

// Buffer: 바이너리 데이터 처리 (해시값, 서명 등에 필수)
const buf = Buffer.from('hello', 'utf8');
console.log(buf.toString('hex'));         // '68656c6c6f'

2-2 함수 & 클래스 (객체지향)

// ✅ 화살표 함수 (블록체인 코드에서 가장 많이 쓰임)
const double = (x) => x * 2;
const greet = (name) => {
  return `Hello, ${name}!`;              // 템플릿 리터럴
};

// ✅ 클래스 — 블록(Block), 블록체인(Blockchain) 구조 만들 때 사용
class Block {
  constructor(index, data, previousHash) {
    this.index = index;
    this.timestamp = Date.now();
    this.data = data;
    this.previousHash = previousHash;
    this.nonce = 0;
    this.hash = this.calculateHash();
  }

  calculateHash() {
    // 다음 챕터에서 실제 구현!
    return `${this.index}${this.timestamp}${JSON.stringify(this.data)}${this.nonce}`;
  }

  // getter: 외부에서 hash처럼 접근 가능
  get isValid() {
    return this.hash === this.calculateHash();
  }
}

// 상속 — 특수 블록 만들 때
class GenesisBlock extends Block {
  constructor() {
    super(0, 'Genesis Block', '0000000000');
  }
}

2-3 비동기 프로그래밍 (async/await) — 블록체인 필수!

블록체인과 통신할 때 (이더리움 노드 조회, 트랜잭션 전송 등)는 시간이 걸리는 작업입니다. async/await 없이는 코드가 멈추거나 순서가 뒤바뀝니다!

// ❌ 나쁜 예: 콜백 지옥 (블록체인 개발 시 절대 쓰지 말 것)
getBlock(1, (block) => {
  getTransaction(block.txId, (tx) => {
    verifySignature(tx, (result) => {
      console.log(result);  // 콜백 지옥!
    });
  });
});

// ✅ 좋은 예: async/await (현업 표준)
async function processBlock() {
  try {
    const block = await getBlock(1);
    const tx = await getTransaction(block.txId);
    const result = await verifySignature(tx);
    console.log(result);    // 깔끔!
  } catch (error) {
    console.error('블록 처리 실패:', error);
  }
}

// Promise.all — 여러 비동기 작업 동시 실행
const [block1, block2, block3] = await Promise.all([
  getBlock(1),
  getBlock(2),
  getBlock(3)
]);  // 3개 동시 요청 → 더 빠름!
CHAPTER 03

해시 함수 직접 구현

Node.js crypto 모듈로 SHA-256 해시를 만들고 실제 데이터에 적용합니다

3-1 SHA-256 해시 완전 구현

// hash.js — 해시 유틸리티 모음
const crypto = require('crypto');    // Node.js 내장 모듈, 설치 불필요!

// 1. 기본 SHA-256
function sha256(data) {
  return crypto
    .createHash('sha256')
    .update(data)
    .digest('hex');         // 'hex': 16진수 문자열 반환
}

// 2. 객체를 해시로 (블록 해시에 사용)
function hashObject(obj) {
  const json = JSON.stringify(obj, Object.keys(obj).sort());
  // 키를 정렬해야 { a:1, b:2 }와 { b:2, a:1 }이 같은 해시가 됨!
  return sha256(json);
}

// 3. 파일 해시 (대용량 파일도 처리 가능)
const fs = require('fs');
function hashFile(filePath) {
  const fileBuffer = fs.readFileSync(filePath);
  return crypto.createHash('sha256').update(fileBuffer).digest('hex');
}

// 4. HMAC: 비밀키가 있는 해시 (API 서명에 사용)
function hmacSha256(data, secretKey) {
  return crypto
    .createHmac('sha256', secretKey)
    .update(data)
    .digest('hex');
}

// 테스트
console.log(sha256('Hello Blockchain!'));
// d29e6f09b4c5a...  (항상 같은 결과)

const block = { index: 1, data: 'Transfer 1 ETH', timestamp: 1710000000000 };
console.log(hashObject(block));
// 객체의 모든 필드를 포함한 해시

3-2 해시 활용 실습 — 비밀번호 안전 저장

// ❌ 절대 금지: 평문 비밀번호 저장
// DB: { username: 'alice', password: 'mypassword123' } — 해킹 시 그대로 노출!

// ✅ 올바른 방법: Salt + Hash (현업 표준)
function hashPassword(password) {
  const salt = crypto.randomBytes(16).toString('hex');  // 랜덤 소금값
  const hash = crypto
    .createHash('sha256')
    .update(salt + password)                       // salt를 앞에 붙여 레인보우 테이블 공격 방지
    .digest('hex');
  return `${salt}:${hash}`;  // DB에 저장
}

function verifyPassword(password, stored) {
  const [salt, hash] = stored.split(':');
  const inputHash = crypto.createHash('sha256').update(salt + password).digest('hex');
  return hash === inputHash;
}

const stored = hashPassword('mypassword123');
console.log(verifyPassword('mypassword123', stored));  // true
console.log(verifyPassword('wrongpassword', stored)); // false
// 💡 현업에서는 bcrypt, argon2 라이브러리 사용 권장 (더 안전)
CHAPTER 04

비대칭 암호화 구현

공개키/개인키 생성, 디지털 서명, 서명 검증까지 — 지갑의 핵심 원리

4-1 타원곡선 암호화 (ECDSA) — 이더리움 지갑의 원리

💡 왜 ECDSA(타원곡선)인가?
RSA보다 훨씬 짧은 키로 동일한 보안 수준 달성. 256비트 ECDSA ≈ RSA 3072비트. 비트코인·이더리움 모두 secp256k1 곡선 사용.

// npm install elliptic
const { ec: EC } = require('elliptic');
const crypto = require('crypto');

// secp256k1: 비트코인·이더리움이 사용하는 타원곡선
const ec = new EC('secp256k1');

// ── 1. 키 페어 생성 ──────────────────────────────────────
const keyPair = ec.genKeyPair();

const privateKey = keyPair.getPrivate('hex');
const publicKey  = keyPair.getPublic('hex');

console.log('🔑 개인키 (64자리 hex):', privateKey);
console.log('📫 공개키 (130자리 hex):', publicKey);
// ⚠️ 개인키는 절대 공유하지 마세요! 이걸 가진 사람이 지갑 주인입니다.

// ── 2. 트랜잭션 서명 ─────────────────────────────────────
function signTransaction(txData, privateKeyHex) {
  const signingKey = ec.keyFromPrivate(privateKeyHex);
  
  // 트랜잭션 데이터의 해시를 서명 (데이터 전체가 아닌 해시에 서명)
  const txHash = crypto
    .createHash('sha256')
    .update(JSON.stringify(txData))
    .digest('hex');

  const signature = signingKey.sign(txHash);
  return {
    r: signature.r.toString('hex'),
    s: signature.s.toString('hex'),
    txHash
  };
}

// ── 3. 서명 검증 ─────────────────────────────────────────
function verifySignature(txData, signature, publicKeyHex) {
  const signingKey = ec.keyFromPublic(publicKeyHex, 'hex');
  const txHash = crypto
    .createHash('sha256')
    .update(JSON.stringify(txData))
    .digest('hex');
  
  return signingKey.verify(txHash, { r: signature.r, s: signature.s });
}

// ── 테스트 ───────────────────────────────────────────────
const tx = { from: publicKey, to: '0xBob...', amount: 1.5 };
const sig = signTransaction(tx, privateKey);
console.log('서명 유효?', verifySignature(tx, sig, publicKey)); // true

// 데이터 변조 시뮬레이션
const tamperedTx = { ...tx, amount: 100 };  // 금액 조작!
console.log('변조 후 유효?', verifySignature(tamperedTx, sig, publicKey)); // false!

4-2 지갑 주소 도출 원리

// 이더리움 지갑 주소 도출 과정 (단순화 버전)
// 실제: 개인키 → 공개키(secp256k1) → Keccak-256 해시 → 마지막 20바이트 = 주소

function deriveAddress(publicKeyHex) {
  // 공개키 앞의 '04' prefix 제거 (비압축 형식)
  const pubKeyNoPrefix = publicKeyHex.slice(2);
  
  // SHA-256 해시 후 앞 12바이트 제거 → 20바이트 = 이더리움 주소
  const hash = crypto.createHash('sha256').update(pubKeyNoPrefix).digest('hex');
  return '0x' + hash.slice(-40);  // 마지막 40자리 (20바이트)
}

console.log('지갑 주소:', deriveAddress(publicKey));
// 출력: 0x1a2b3c4d5e6f... (40자리 hex)

// 💡 핵심 원리:
// 개인키(64자리) → 공개키(130자리) → 주소(42자리)
// 주소에서 공개키를 역산할 수 없음! (단방향)
// 주소에서 개인키를 역산할 수 없음! (단방향)
CHAPTER 05

머클 트리 직접 구현

트랜잭션 무결성 검증의 핵심 자료구조를 코드로 만들어봅니다

💡 머클 트리란?

블록에 담긴 수천 개 트랜잭션을 하나의 해시(머클 루트)로 요약합니다.
특정 트랜잭션이 블록에 포함됐는지를 전체 블록체인 다운로드 없이 증명할 수 있습니다 (SPV).

5-1 머클 트리 구조 이해

📊 머클 트리 다이어그램 (4개 트랜잭션)

            머클 루트
           H(H12 + H34)
          /           
    H12            H34
    /             /   
   H1   H2      H3   H4
   |    |        |    |
   TX1  TX2     TX3  TX4

⚡ TX2를 검증하려면? H1, H34만 있으면 됨 (전체 불필요)
// merkle.js — 머클 트리 직접 구현
const crypto = require('crypto');

const hash = (data) => crypto.createHash('sha256').update(data).digest('hex');

function buildMerkleTree(transactions) {
  if (transactions.length === 0) return null;
  
  // 트랜잭션 → 리프 해시
  let level = transactions.map(tx => hash(JSON.stringify(tx)));
  
  while (level.length > 1) {
    const nextLevel = [];
    
    for (let i = 0; i < level.length; i += 2) {
      const left = level[i];
      const right = i + 1 < level.length ? level[i + 1] : left;  // 홀수면 복사
      nextLevel.push(hash(left + right));
    }
    level = nextLevel;
  }
  
  return level[0];  // 머클 루트
}

// 테스트
const txs = [
  { from: 'Alice', to: 'Bob',   amount: 1.5 },
  { from: 'Bob',   to: 'Carol', amount: 0.3 },
  { from: 'Carol', to: 'Dave',  amount: 2.0 },
  { from: 'Dave',  to: 'Alice', amount: 0.7 },
];

const merkleRoot = buildMerkleTree(txs);
console.log('머클 루트:', merkleRoot);
// TX1 변조 후 머클 루트가 완전히 달라짐을 확인!
CHAPTER 06

트랜잭션 구조 구현

UTXO 개념 이해, 서명된 트랜잭션 생성, 유효성 검증까지

6-1 UTXO vs 계좌 잔액 모델

구분 UTXO 모델 (비트코인) 계좌 잔액 모델 (이더리움)
저장 방식 쓰지 않은 출력(UTXO)을 추적 계좌의 잔액을 직접 저장
비유 지갑 속 지폐(다양한 단위) 은행 통장 잔액
장점 이중 지불 방지 탁월, 프라이버시 스마트 컨트랙트에 적합, 단순
사용 블록체인 비트코인, 라이트코인 이더리움, EVM 체인
// transaction.js — 서명된 트랜잭션 클래스
const crypto = require('crypto');
const { ec: EC } = require('elliptic');
const ec = new EC('secp256k1');

class Transaction {
  constructor(fromAddress, toAddress, amount) {
    this.fromAddress = fromAddress;  // 공개키 (보내는 사람)
    this.toAddress   = toAddress;    // 받는 사람 주소
    this.amount      = amount;
    this.timestamp   = Date.now();
    this.signature   = null;
  }

  // 트랜잭션의 해시 (서명 대상)
  calculateHash() {
    return crypto
      .createHash('sha256')
      .update(this.fromAddress + this.toAddress + this.amount + this.timestamp)
      .digest('hex');
  }

  // 개인키로 서명
  signTransaction(signingKey) {
    if (signingKey.getPublic('hex') !== this.fromAddress) {
      throw new Error('다른 사람 지갑으로 서명할 수 없습니다!');
    }
    const txHash = this.calculateHash();
    const sig = signingKey.sign(txHash, 'base64');
    this.signature = sig.toDER('hex');
  }

  // 서명 검증 (채굴자가 수행)
  isValid() {
    if (this.fromAddress === null) return true;  // 채굴 보상 트랜잭션
    if (!this.signature) throw new Error('서명이 없는 트랜잭션');

    const publicKey = ec.keyFromPublic(this.fromAddress, 'hex');
    return publicKey.verify(this.calculateHash(), this.signature);
  }
}

// 사용 예시
const aliceKey = ec.genKeyPair();
const bobPublicKey = ec.genKeyPair().getPublic('hex');

const tx = new Transaction(aliceKey.getPublic('hex'), bobPublicKey, 50);
tx.signTransaction(aliceKey);
console.log('트랜잭션 유효?', tx.isValid());  // true

⛏️ Chapter 07. PoW 채굴 알고리즘 구현

Proof of Work — 블록체인의 심장을 직접 만들다

📚 7.1 PoW란 무엇인가?

Proof of Work(작업 증명)는 비트코인이 채택한 합의 알고리즘입니다. "엄청난 계산을 해야만 블록을 만들 수 있다"는 원리로, 악의적인 공격자가 체인을 조작하려면 전 세계 채굴자보다 더 많은 연산 능력을 가져야 합니다.

개념 설명 비유
Nonce 해시 조건을 맞추기 위해 변경하는 숫자 자물쇠 비밀번호 맞추기
Difficulty 해시 앞에 0이 몇 개 와야 하는지 조합의 자릿수
Mining 조건을 만족하는 Nonce를 찾는 과정 금 캐기
Block Reward 채굴 성공 시 받는 코인 금 캤을 때 보상

💻 7.2 Blockchain 클래스 완성 (PoW 포함)

앞서 만든 Block, Transaction, MerkleTree를 합쳐 완전한 Blockchain 클래스를 구현합니다.

// ============================
// blockchain.js — 완전한 PoW 블록체인
// ============================
const crypto = require('crypto');

class Block {
  constructor(index, previousHash, transactions, difficulty = 4) {
    this.index          = index;
    this.timestamp      = Date.now();
    this.transactions   = transactions;
    this.previousHash   = previousHash;
    this.difficulty     = difficulty;
    this.nonce          = 0;
    this.merkleRoot     = this.computeMerkleRoot();
    this.hash           = this.mineBlock();
  }

  computeMerkleRoot() {
    const txHashes = this.transactions.map(tx =>
      crypto.createHash('sha256')
            .update(JSON.stringify(tx))
            .digest('hex')
    );
    return txHashes.reduce((a, b) =>
      crypto.createHash('sha256').update(a + b).digest('hex')
    , 'genesis');
  }

  calculateHash() {
    return crypto
      .createHash('sha256')
      .update(
        this.index +
        this.timestamp +
        this.merkleRoot +
        this.previousHash +
        this.nonce
      )
      .digest('hex');
  }

  mineBlock() {
    const target = '0'.repeat(this.difficulty);
    let hash = this.calculateHash();
    while (!hash.startsWith(target)) {
      this.nonce++;
      hash = this.calculateHash();
    }
    console.log(`⛏️  블록 ${this.index} 채굴 완료! nonce=${this.nonce} hash=${hash.substring(0,20)}...`);
    return hash;
  }
}

class Blockchain {
  constructor() {
    this.chain              = [this.createGenesisBlock()];
    this.difficulty          = 3;
    this.pendingTransactions = [];
    this.miningReward        = 12.5;
    this.blockTime           = 10000; // 목표: 10초
  }

  createGenesisBlock() {
    return new Block(0, '0000000000000000', [{
      from: 'GENESIS', to: 'GENESIS', amount: 0
    }], 0);
  }

  getLatestBlock() {
    return this.chain[this.chain.length - 1];
  }

  addTransaction(transaction) {
    if (!transaction.from || !transaction.to)
      throw new Error('Transaction must include from and to address!');
    if (transaction.amount <= 0)
      throw new Error('Transaction amount must be positive!');
    this.pendingTransactions.push(transaction);
  }

  minePendingTransactions(minerAddress) {
    // 채굴 보상 트랜잭션 추가
    const rewardTx = {
      from: 'COINBASE',
      to: minerAddress,
      amount: this.miningReward
    };
    this.pendingTransactions.push(rewardTx);

    // 새 블록 채굴
    const startTime = Date.now();
    const block = new Block(
      this.chain.length,
      this.getLatestBlock().hash,
      [...this.pendingTransactions],
      this.difficulty
    );
    const elapsed = Date.now() - startTime;

    // 난이도 자동 조절 (difficulty adjustment)
    if (elapsed < this.blockTime / 2)       this.difficulty++;
    else if (elapsed > this.blockTime * 2)  this.difficulty--;

    this.chain.push(block);
    this.pendingTransactions = [];
    return block;
  }

  getBalance(address) {
    let balance = 0;
    for (const block of this.chain) {
      for (const tx of block.transactions) {
        if (tx.from === address) balance -= tx.amount;
        if (tx.to   === address) balance += tx.amount;
      }
    }
    return balance;
  }

  isChainValid() {
    for (let i = 1; i < this.chain.length; i++) {
      const current  = this.chain[i];
      const previous = this.chain[i - 1];
      if (current.hash !== current.calculateHash())   return false;
      if (current.previousHash !== previous.hash)      return false;
    }
    return true;
  }
}

// ===== 실행 테스트 =====
const myChain = new Blockchain();
myChain.addTransaction({ from: 'Alice', to: 'Bob',   amount: 50  });
myChain.addTransaction({ from: 'Bob',   to: 'Carol', amount: 30  });
myChain.minePendingTransactions('Miner1');
console.log('Miner1 잔액:', myChain.getBalance('Miner1'));
console.log('체인 유효성:', myChain.isChainValid());

📊 7.3 난이도 조절 메커니즘 (Difficulty Adjustment)

🏃 너무 빠른 경우
elapsed < blockTime / 2
→ difficulty 증가 (+1)
앞에 0 하나 더 요구
✅ 정상 범위
blockTime/2 ~ blockTime*2
→ difficulty 유지
10초 내외 블록 생성
🐌 너무 느린 경우
elapsed > blockTime * 2
→ difficulty 감소 (-1)
앞에 0 하나 줄임

💡 비트코인은 2016 블록(약 2주)마다 조절. 목표: 10분 1블록

⛏️ PoW 채굴 시뮬레이션 (터미널 출력 예시)

$ node blockchain.js
⛏️  블록 0 채굴 완료! nonce=0    hash=0000000000000000...  (제네시스)
⛏️  블록 1 채굴 완료! nonce=1847 hash=0007a3f2e9b1c4d8...
⛏️  블록 2 채굴 완료! nonce=4521 hash=000b2c91fe730a55...
Miner1 잔액: 12.5
체인 유효성: true
난이도 조절 → difficulty: 4 (채굴 시간: 4.2s)

🔗 7.4 51% 공격 이해하기

⚠️ 51% Attack 시뮬레이션

// 공격자가 체인 데이터 변조 시도
const attacked = new Blockchain();
attacked.chain[1].transactions[0].amount = 9999;

console.log('공격 후 체인 유효성:', attacked.isChainValid());
// 출력: 공격 후 체인 유효성: false ← 즉시 탐지!

// 공격자가 51% 해시파워를 가지면?
// → 변조된 블록부터 다시 채굴해야 함 (엄청난 비용)
// → 비트코인 전체 해시파워의 51% = 수천억 원 이상의 장비 필요
공격 방어 요소 이유
해시 체인 구조 한 블록 변조 시 이후 모든 블록 재채굴 필요
높은 PoW 난이도 재채굴 비용이 천문학적으로 증가
분산 노드 수만 개 노드가 즉시 검증 및 거부
경제적 인센티브 공격보다 정직한 채굴이 더 이익

👛 Chapter 08. 지갑(Wallet) 구현

키 생성부터 잔액 조회까지 — 비트코인 지갑의 모든 것

📚 8.1 지갑(Wallet)이란?

블록체인 지갑은 실제 코인을 저장하지 않습니다. 대신 개인키(Private Key)공개키(Public Key)를 관리합니다. 잔액은 블록체인에 기록되어 있고, 지갑은 그 잔액을 사용할 수 있는 열쇠를 보관합니다.

구성요소 역할 비유 공개 여부
Private Key 거래 서명 (소유 증명) 집 열쇠 🔒 절대 비공개
Public Key 서명 검증 자물쇠 ✅ 공개 가능
Address 송금 받는 주소 계좌번호 ✅ 공개 가능
Mnemonic 사람이 읽을 수 있는 복구 코드 비상 마스터키 🔒 절대 비공개

💻 8.2 Wallet 클래스 전체 구현

// wallet.js
const { generateKeyPairSync, createSign, createVerify, createHash } = require('crypto');

class Wallet {
  constructor() {
    // EC 키 쌍 생성 (secp256k1 — 비트코인과 동일 곡선)
    const { privateKey, publicKey } = generateKeyPairSync('ec', {
      namedCurve: 'secp256k1',
      publicKeyEncoding:  { type: 'spki',  format: 'pem' },
      privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
    });
    this.privateKey = privateKey;
    this.publicKey  = publicKey;
    this.address    = this.deriveAddress();
  }

  deriveAddress() {
    // 공개키를 SHA-256 후 앞 20바이트 → 주소
    return createHash('sha256')
      .update(this.publicKey)
      .digest('hex')
      .substring(0, 40);  // 40 hex chars = 20 bytes
  }

  sign(dataHash) {
    const sign = createSign('SHA256');
    sign.update(dataHash);
    sign.end();
    return sign.sign(this.privateKey, 'hex');
  }

  static verify(dataHash, signature, publicKey) {
    const verify = createVerify('SHA256');
    verify.update(dataHash);
    verify.end();
    return verify.verify(publicKey, signature, 'hex');
  }

  createTransaction(toAddress, amount, blockchain) {
    const balance = blockchain.getBalance(this.address);
    if (balance < amount)
      throw new Error(`잔액 부족! 잔액: ${balance}, 요청: ${amount}`);

    const tx = {
      from:      this.address,
      to:        toAddress,
      amount,
      timestamp: Date.now(),
      publicKey: this.publicKey
    };
    const txHash = createHash('sha256')
      .update(JSON.stringify({from: tx.from, to: tx.to, amount: tx.amount}))
      .digest('hex');
    tx.signature = this.sign(txHash);
    return tx;
  }
}

// ===== 사용 예시 =====
const alice = new Wallet();
const bob   = new Wallet();
console.log('Alice 주소:', alice.address);
console.log('Bob   주소:', bob.address);

🔐 8.3 HD Wallet (계층적 결정론적 지갑)

MetaMask, Trust Wallet 등 현대 지갑은 모두 BIP-32/BIP-39 표준의 HD Wallet을 사용합니다.

니모닉 (Mnemonic Phrase)

abandon ability able about above absent absorb abstract absurd abuse access accident

12개 단어 → 256비트 시드 생성 (BIP-39)

파생 경로 (Derivation Path)

m/44'/60'/0'/0/0
purpose / coin / account / change / index

이더리움 주소 첫 번째 (BIP-44)

// npm install bip39 hdkey
const bip39 = require('bip39');
const HDKey = require('hdkey');

const mnemonic = bip39.generateMnemonic();
console.log('니모닉:', mnemonic);

const seed = await bip39.mnemonicToSeed(mnemonic);
const root = HDKey.fromMasterSeed(seed);

// 이더리움 첫 번째 주소
const child = root.derive("m/44'/60'/0'/0/0");
console.log('개인키:', child.privateKey.toString('hex'));

🌐 Chapter 09. P2P 네트워크 기초

WebSocket으로 탈중앙화 노드 네트워크 직접 구현

📚 9.1 P2P 네트워크 개요

블록체인은 중앙 서버 없이 모든 노드가 서로 연결되는 P2P (Peer-to-Peer) 네트워크로 운영됩니다. 새 블록이 채굴되면 전체 네트워크에 전파(broadcast)됩니다.

메시지 타입 설명 언제 사용?
QUERY_LATEST 최신 블록 요청 새 노드 연결 시
QUERY_ALL 전체 체인 요청 체인 동기화 필요 시
RESPONSE_BLOCKCHAIN 체인 데이터 응답 상대방 요청에 응답
BROADCAST_TX 새 트랜잭션 전파 지갑에서 송금 시

💻 9.2 WebSocket P2P 노드 구현

// p2p-node.js — npm install ws express
const WebSocket = require('ws');
const express   = require('express');

const MessageType = {
  QUERY_LATEST:        0,
  QUERY_ALL:           1,
  RESPONSE_BLOCKCHAIN: 2,
  BROADCAST_TX:        3
};

class P2PNode {
  constructor(httpPort, p2pPort, blockchain) {
    this.blockchain = blockchain;
    this.peers      = new Set();
    this.initHttpServer(httpPort);
    this.initP2PServer(p2pPort);
  }

  initHttpServer(port) {
    const app = express();
    app.use(express.json());

    app.get('/blocks', (req, res) =>
      res.json(this.blockchain.chain));

    app.get('/peers', (req, res) =>
      res.json([...this.peers].map(ws => ws.url)));

    app.post('/addPeer', (req, res) => {
      this.connectToPeer(req.body.peer);
      res.json({ status: 'connected' });
    });

    app.post('/mine', (req, res) => {
      const block = this.blockchain.minePendingTransactions(req.body.miner);
      this.broadcast(this.responseLatestMsg());
      res.json(block);
    });

    app.listen(port, () =>
      console.log(`🌐 HTTP API 서버: http://localhost:${port}`));
  }

  initP2PServer(port) {
    const server = new WebSocket.Server({ port });
    server.on('connection', ws => this.initConnection(ws));
    console.log(`⚡ P2P 서버 리스닝: ws://localhost:${port}`);
  }

  initConnection(ws) {
    this.peers.add(ws);
    ws.on('message', msg => this.handleMessage(ws, JSON.parse(msg)));
    ws.on('close',   ()  => this.peers.delete(ws));
    ws.on('error',   ()  => this.peers.delete(ws));
    // 연결 즉시 최신 블록 요청
    ws.send(JSON.stringify({ type: MessageType.QUERY_LATEST }));
  }

  handleMessage(ws, msg) {
    switch (msg.type) {
      case MessageType.QUERY_LATEST:
        ws.send(JSON.stringify(this.responseLatestMsg()));
        break;
      case MessageType.QUERY_ALL:
        ws.send(JSON.stringify({ type: MessageType.RESPONSE_BLOCKCHAIN, data: this.blockchain.chain }));
        break;
      case MessageType.RESPONSE_BLOCKCHAIN:
        this.handleBlockchainResponse(msg.data);
        break;
    }
  }

  handleBlockchainResponse(receivedChain) {
    const latestReceived = receivedChain[receivedChain.length - 1];
    const latestHeld     = this.blockchain.getLatestBlock();
    if (latestReceived.index > latestHeld.index) {
      console.log('더 긴 체인 발견 — 체인 교체 중...');
      if (receivedChain.length > this.blockchain.chain.length) {
        this.blockchain.chain = receivedChain;
        this.broadcast(this.responseLatestMsg());
      }
    }
  }

  connectToPeer(peerUrl) {
    const ws = new WebSocket(peerUrl);
    ws.on('open', () => this.initConnection(ws));
  }

  broadcast(msg) {
    this.peers.forEach(ws => ws.send(JSON.stringify(msg)));
  }

  responseLatestMsg() {
    return {
      type: MessageType.RESPONSE_BLOCKCHAIN,
      data: [this.blockchain.getLatestBlock()]
    };
  }
}

// ===== 실행 =====
const chain  = new Blockchain();
const node1  = new P2PNode(3001, 6001, chain);
// node1 → http://localhost:3001/mine, /blocks, /peers

🧪 9.3 멀티 노드 테스트

터미널 3개로 테스트하기

# 터미널 1 — 노드1 실행
HTTP_PORT=3001 P2P_PORT=6001 node p2p-node.js

# 터미널 2 — 노드2 실행
HTTP_PORT=3002 P2P_PORT=6002 node p2p-node.js

# 터미널 3 — 노드 연결 (노드2가 노드1에 연결)
curl -X POST http://localhost:3002/addPeer \
     -H "Content-Type: application/json" \
     -d '{"peer":"ws://localhost:6001"}'

# 노드1에서 채굴 — 노드2에 자동으로 전파됨
curl -X POST http://localhost:3001/mine \
     -d '{"miner":"Alice"}' -H "Content-Type: application/json"

# 노드2에서 체인 확인 (동기화 되었는지)
curl http://localhost:3002/blocks

🎤 Chapter 10. 현업 면접 Q&A TOP 15

블록체인 개발자 취업 면접에서 실제로 물어보는 질문들

Q1 JavaScript의 이벤트 루프(Event Loop)를 설명해주세요.

A: JS는 싱글 스레드이지만 비동기 작업을 위해 콜 스택(Call Stack), Web APIs, 콜백 큐(Callback Queue), 마이크로태스크 큐(Microtask Queue)로 구성된 이벤트 루프를 사용합니다. Promise의 .then()은 마이크로태스크 큐에, setTimeout은 매크로태스크 큐에 들어가며, 마이크로태스크가 먼저 처리됩니다.

Q2 SHA-256과 ECDSA의 차이점과 블록체인에서 각각의 역할은?

A: SHA-256은 단방향 해시 함수로, 블록 해시 계산과 PoW에 사용됩니다(되돌릴 수 없음). ECDSA는 비대칭 암호화로, 개인키로 서명하고 공개키로 검증 — 트랜잭션 위조 방지에 사용됩니다. 해시는 데이터 무결성, ECDSA는 소유권 증명이 주 목적입니다.

Q3 머클 트리(Merkle Tree)는 왜 필요한가요?

A: 수천 개 트랜잭션을 하나의 Merkle Root 해시로 표현하여 블록 헤더에 저장합니다. 특정 트랜잭션이 블록에 포함되었는지 O(log n) 시간에 검증 가능(Merkle Proof). 전체 블록 다운로드 없이 SPV(Simple Payment Verification) 구현에 필수입니다.

Q4 PoW와 PoS의 차이점을 설명하고 각각의 장단점을 말해주세요.
구분 PoW PoS
합의 방식 컴퓨팅 파워 경쟁 보유 코인 담보
에너지 매우 높음 ⚡ 낮음 🌿
보안성 51% 공격 비용 높음 Nothing-at-Stake 위험
대표 체인 비트코인 이더리움2.0
Q5 Promise와 async/await의 차이점, 에러 처리 방법은?

A: async/await은 Promise의 문법적 설탕(syntactic sugar)입니다. async 함수는 항상 Promise를 반환하며, await은 Promise가 resolve될 때까지 함수 실행을 일시 중지합니다. 에러 처리는 try/catch로 하며, Promise 체이닝의 .catch()와 동일합니다. 블록체인 네트워크 요청, DB 쿼리 등 모든 I/O에 사용합니다.

Q6 UTXO 모델과 Account 모델의 차이점은?

A: UTXO(비트코인)는 "사용되지 않은 출력"의 집합으로 잔액을 표현합니다. 프라이버시가 높고 병렬 처리에 유리하지만 상태가 복잡합니다. Account 모델(이더리움)은 은행 계좌처럼 잔액을 직접 저장 — 스마트 컨트랙트 상태 관리에 유리하고 직관적이지만 이중 지불 방지를 nonce로 해결합니다.

Q7 P2P 네트워크에서 새 블록이 전파되는 과정을 설명해주세요.

A: ①채굴자가 새 블록 생성 → ②연결된 모든 피어에게 broadcast → ③각 노드는 블록 유효성 검증(해시, 서명, 이전 해시) → ④유효하면 로컬 체인에 추가 → ⑤다시 자신의 피어들에게 전파(gossip protocol). 더 긴 체인 발견 시 즉시 교체합니다.

Q8 Node.js가 블록체인 개발에 적합한 이유는 무엇인가요?

A: ①비동기 I/O — P2P 네트워크 수백 개 연결을 싱글 스레드로 처리 가능 ②풍부한 crypto 내장 모듈 ③npm 생태계(web3.js, ethers.js) ④JSON 네이티브 처리 ⑤스마트 컨트랙트 프론트엔드와 언어 통일(JS). 특히 WebSocket 서버와 HTTP API를 동시에 운영하는 블록체인 노드에 최적화되어 있습니다.

Q9 개인키를 분실하면 어떻게 되나요? 복구 방법은?

A: 개인키 분실 시 해당 주소의 자산에 영구적으로 접근 불가합니다 — 탈중앙화의 양면입니다. 복구는 니모닉 시드 구문(BIP-39, 12-24 단어)으로만 가능합니다. HD Wallet의 니모닉만 안전하게 보관하면 개인키/주소를 모두 재파생할 수 있습니다. 페이퍼 백업 또는 하드웨어 월렛 사용을 권장합니다.

Q10 JavaScript 클로저(Closure)와 스코프를 설명하고, 블록체인 코드에서의 활용 예를 들어주세요.

A: 클로저는 함수가 자신이 선언된 렉시컬 환경을 기억하는 것입니다. 블록체인에서는 노드의 체인 상태를 캡슐화하거나, 채굴 난이도 설정을 팩토리 함수로 주입할 때 활용합니다. 예: const createMiner = (difficulty) => (block) => block.mine(difficulty); — difficulty를 클로저로 캡처하여 여러 블록에 재사용합니다.

Q11 트랜잭션 수수료(Gas/Fee)는 왜 필요하고 어떻게 계산되나요?

A: ①스팸 방지 — 무한 트랜잭션 전송 억제 ②채굴자/검증자 인센티브 ③리소스 사용량 반영(이더리움 Gas). 비트코인은 tx 크기(bytes) × sat/byte, 이더리움은 Gas Limit × Gas Price(Gwei)로 계산합니다. EIP-1559 이후 base fee + priority fee(tip) 구조로 변경되었습니다.

Q12 블록체인에서 포크(Fork)란 무엇인가요? Hard Fork vs Soft Fork?

A: Fork는 체인이 두 갈래로 분기되는 것입니다. Hard Fork: 이전 버전과 호환 불가 — 모든 노드가 업그레이드해야 함(비트코인캐시, 이더리움클래식). Soft Fork: 이전 버전과 호환 — 과반수 노드 업그레이드로 적용(비트코인 SegWit). 일시적인 포크(우연한 체인 분기)는 긴 체인 규칙으로 자연해소됩니다.

Q13 RESTful API와 WebSocket의 차이점, 블록체인 노드에서 각각 언제 사용하나요?

A: REST는 요청-응답(stateless), 주로 블록 조회/트랜잭션 제출 API에 사용합니다. WebSocket은 지속 연결(stateful), 실시간 양방향 — P2P 노드 간 블록 전파, 가격 실시간 업데이트에 사용합니다. 블록체인 노드는 REST(HTTP)로 외부 인터페이스, WebSocket으로 내부 P2P 통신을 담당합니다.

Q14 메모리풀(Mempool)이란 무엇이고 어떻게 관리되나요?

A: Mempool(Memory Pool)은 아직 블록에 포함되지 않은 미확인 트랜잭션들의 대기 공간입니다. 채굴자는 Mempool에서 수수료가 높은 트랜잭션을 우선 선택하여 블록에 포함시킵니다. 네트워크 혼잡 시 Mempool이 커지고 수수료 경쟁이 심화됩니다. 우리 구현에서는 pendingTransactions 배열이 Mempool 역할을 합니다.

Q15 블록체인 개발자로서 보안에서 가장 중요하게 생각해야 할 것은?

A: ①개인키 절대 코드에 하드코딩 금지 ②모든 입력 검증(트랜잭션 금액/주소) ③스마트 컨트랙트 재진입 공격(Reentrancy) 방지 ④정수 오버플로우 체크 ⑤서명 검증 없이 트랜잭션 허용 금지 ⑥Nonce를 통한 재전송 공격(Replay Attack) 방지. "코드가 법"인 블록체인에서 버그는 수십억 원 손실로 이어집니다.

✅ 학습 체크리스트

이 챕터를 완료했다면 아래 항목들을 모두 체크할 수 있어야 합니다:

🔧 환경 & 기초

☑ Node.js 설치 및 npm 패키지 관리

☑ var/let/const 차이 완전 이해

☑ 화살표 함수 vs 일반 함수 (this 바인딩)

☑ 클로저, 스코프, 프로토타입 이해

☑ async/await + try/catch 능숙하게 사용

☑ ES6 클래스 문법으로 OOP 구현

⛓️ 블록체인 구현

☑ SHA-256 해시 함수 직접 구현

☑ ECDSA 키 생성 / 서명 / 검증

☑ 머클 트리 구현 및 증명 생성

☑ UTXO 트랜잭션 모델 구현

☑ PoW 채굴 알고리즘 + 난이도 조절

☑ 체인 유효성 검증 (isChainValid)

🌐 네트워크 & 지갑

☑ HD Wallet 구조 이해 (BIP-39/44)

☑ 주소 파생 과정 구현

☑ WebSocket P2P 노드 구현

☑ 블록 전파 및 체인 동기화

☑ Express REST API 구현

☑ 멀티 노드 테스트 경험

🎉

BlockchainDevGuide0002 완료!

JavaScript 프로그래밍 기초부터 SHA-256, ECDSA, 머클트리,
UTXO, PoW 채굴, HD Wallet, P2P 네트워크까지 완전 정복했습니다!

✅ Guide0001 완료 ✅ Guide0002 완료 ⏳ Guide0003 예정 ⏳ Guide0004 예정

📌 다음 편 예고: BlockchainDevGuide0003

블록체인 구조 직접 만들기 — 이더리움 호환 체인 구현

Solidity 기초 · EVM 구조 · ERC-20 토큰 구현 · Hardhat 개발환경 · 트러플 마이그레이션

반응형