BlockchainDevGuide0002
프로그래밍 기초 완전 정복 A-Z
JavaScript로 해시·암호화·블록체인 기초를 직접 구현하는 실전 가이드
📋 학습 목차 — 코드를 치면서 블록체인을 이해합니다
| 챕터 | 주제 | 핵심 기술 |
|---|---|---|
| 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 | 코딩 테스트 + 개념 면접 완전 정복 |
개발 환경 세팅
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 이력 시각화 | 🟡 권장 |
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개 동시 요청 → 더 빠름!
해시 함수 직접 구현
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 라이브러리 사용 권장 (더 안전)
비대칭 암호화 구현
공개키/개인키 생성, 디지털 서명, 서명 검증까지 — 지갑의 핵심 원리
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자리)
// 주소에서 공개키를 역산할 수 없음! (단방향)
// 주소에서 개인키를 역산할 수 없음! (단방향)
머클 트리 직접 구현
트랜잭션 무결성 검증의 핵심 자료구조를 코드로 만들어봅니다
💡 머클 트리란?
블록에 담긴 수천 개 트랜잭션을 하나의 해시(머클 루트)로 요약합니다.
특정 트랜잭션이 블록에 포함됐는지를 전체 블록체인 다운로드 없이 증명할 수 있습니다 (SPV).
5-1 머클 트리 구조 이해
머클 루트
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 변조 후 머클 루트가 완전히 달라짐을 확인!
트랜잭션 구조 구현
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)
💡 비트코인은 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 accident12개 단어 → 256비트 시드 생성 (BIP-39)
파생 경로 (Derivation Path)
m/44'/60'/0'/0/0purpose / 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
블록체인 개발자 취업 면접에서 실제로 물어보는 질문들
A: JS는 싱글 스레드이지만 비동기 작업을 위해 콜 스택(Call Stack), Web APIs, 콜백 큐(Callback Queue), 마이크로태스크 큐(Microtask Queue)로 구성된 이벤트 루프를 사용합니다. Promise의 .then()은 마이크로태스크 큐에, setTimeout은 매크로태스크 큐에 들어가며, 마이크로태스크가 먼저 처리됩니다.
A: SHA-256은 단방향 해시 함수로, 블록 해시 계산과 PoW에 사용됩니다(되돌릴 수 없음). ECDSA는 비대칭 암호화로, 개인키로 서명하고 공개키로 검증 — 트랜잭션 위조 방지에 사용됩니다. 해시는 데이터 무결성, ECDSA는 소유권 증명이 주 목적입니다.
A: 수천 개 트랜잭션을 하나의 Merkle Root 해시로 표현하여 블록 헤더에 저장합니다. 특정 트랜잭션이 블록에 포함되었는지 O(log n) 시간에 검증 가능(Merkle Proof). 전체 블록 다운로드 없이 SPV(Simple Payment Verification) 구현에 필수입니다.
| 구분 | PoW | PoS |
|---|---|---|
| 합의 방식 | 컴퓨팅 파워 경쟁 | 보유 코인 담보 |
| 에너지 | 매우 높음 ⚡ | 낮음 🌿 |
| 보안성 | 51% 공격 비용 높음 | Nothing-at-Stake 위험 |
| 대표 체인 | 비트코인 | 이더리움2.0 |
A: async/await은 Promise의 문법적 설탕(syntactic sugar)입니다. async 함수는 항상 Promise를 반환하며, await은 Promise가 resolve될 때까지 함수 실행을 일시 중지합니다. 에러 처리는 try/catch로 하며, Promise 체이닝의 .catch()와 동일합니다. 블록체인 네트워크 요청, DB 쿼리 등 모든 I/O에 사용합니다.
A: UTXO(비트코인)는 "사용되지 않은 출력"의 집합으로 잔액을 표현합니다. 프라이버시가 높고 병렬 처리에 유리하지만 상태가 복잡합니다. Account 모델(이더리움)은 은행 계좌처럼 잔액을 직접 저장 — 스마트 컨트랙트 상태 관리에 유리하고 직관적이지만 이중 지불 방지를 nonce로 해결합니다.
A: ①채굴자가 새 블록 생성 → ②연결된 모든 피어에게 broadcast → ③각 노드는 블록 유효성 검증(해시, 서명, 이전 해시) → ④유효하면 로컬 체인에 추가 → ⑤다시 자신의 피어들에게 전파(gossip protocol). 더 긴 체인 발견 시 즉시 교체합니다.
A: ①비동기 I/O — P2P 네트워크 수백 개 연결을 싱글 스레드로 처리 가능 ②풍부한 crypto 내장 모듈 ③npm 생태계(web3.js, ethers.js) ④JSON 네이티브 처리 ⑤스마트 컨트랙트 프론트엔드와 언어 통일(JS). 특히 WebSocket 서버와 HTTP API를 동시에 운영하는 블록체인 노드에 최적화되어 있습니다.
A: 개인키 분실 시 해당 주소의 자산에 영구적으로 접근 불가합니다 — 탈중앙화의 양면입니다. 복구는 니모닉 시드 구문(BIP-39, 12-24 단어)으로만 가능합니다. HD Wallet의 니모닉만 안전하게 보관하면 개인키/주소를 모두 재파생할 수 있습니다. 페이퍼 백업 또는 하드웨어 월렛 사용을 권장합니다.
A: 클로저는 함수가 자신이 선언된 렉시컬 환경을 기억하는 것입니다. 블록체인에서는 노드의 체인 상태를 캡슐화하거나, 채굴 난이도 설정을 팩토리 함수로 주입할 때 활용합니다. 예: const createMiner = (difficulty) => (block) => block.mine(difficulty); — difficulty를 클로저로 캡처하여 여러 블록에 재사용합니다.
A: ①스팸 방지 — 무한 트랜잭션 전송 억제 ②채굴자/검증자 인센티브 ③리소스 사용량 반영(이더리움 Gas). 비트코인은 tx 크기(bytes) × sat/byte, 이더리움은 Gas Limit × Gas Price(Gwei)로 계산합니다. EIP-1559 이후 base fee + priority fee(tip) 구조로 변경되었습니다.
A: Fork는 체인이 두 갈래로 분기되는 것입니다. Hard Fork: 이전 버전과 호환 불가 — 모든 노드가 업그레이드해야 함(비트코인캐시, 이더리움클래식). Soft Fork: 이전 버전과 호환 — 과반수 노드 업그레이드로 적용(비트코인 SegWit). 일시적인 포크(우연한 체인 분기)는 긴 체인 규칙으로 자연해소됩니다.
A: REST는 요청-응답(stateless), 주로 블록 조회/트랜잭션 제출 API에 사용합니다. WebSocket은 지속 연결(stateful), 실시간 양방향 — P2P 노드 간 블록 전파, 가격 실시간 업데이트에 사용합니다. 블록체인 노드는 REST(HTTP)로 외부 인터페이스, WebSocket으로 내부 P2P 통신을 담당합니다.
A: Mempool(Memory Pool)은 아직 블록에 포함되지 않은 미확인 트랜잭션들의 대기 공간입니다. 채굴자는 Mempool에서 수수료가 높은 트랜잭션을 우선 선택하여 블록에 포함시킵니다. 네트워크 혼잡 시 Mempool이 커지고 수수료 경쟁이 심화됩니다. 우리 구현에서는 pendingTransactions 배열이 Mempool 역할을 합니다.
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 네트워크까지 완전 정복했습니다!
📌 다음 편 예고: BlockchainDevGuide0003
블록체인 구조 직접 만들기 — 이더리움 호환 체인 구현
Solidity 기초 · EVM 구조 · ERC-20 토큰 구현 · Hardhat 개발환경 · 트러플 마이그레이션