이미지 로딩 중...

타입스크립트로 비트코인 클론하기 - 블록체인 핵심 개념 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 9. · 2 Views

타입스크립트로 비트코인 클론하기 - 블록체인 핵심 개념 완벽 가이드

타입스크립트로 직접 구현하면서 배우는 블록체인과 비트코인의 핵심 원리. 블록 구조, 해시, 체인 연결, 작업증명(PoW) 등 실제 암호화폐의 동작 원리를 단계별로 이해하고 구현해봅니다.


목차

  1. Block 클래스
  2. SHA-256 해시 함수
  3. Genesis Block
  4. Block Chain 연결
  5. Proof of Work
  6. Chain Validation
  7. Nonce와 난이도
  8. Timestamp 관리

1. Block 클래스

시작하며

여러분이 블록체인 뉴스를 볼 때마다 "도대체 블록이 뭐길래?"라는 생각을 해본 적 있나요? 블록체인을 이해하려면 먼저 '블록'이 무엇인지부터 알아야 합니다.

블록체인은 말 그대로 '블록'들이 '체인'처럼 연결된 구조입니다. 각 블록은 거래 정보를 담는 데이터 컨테이너이고, 이것들이 사슬처럼 연결되어 위변조가 불가능한 장부를 만듭니다.

바로 이럴 때 필요한 것이 Block 클래스입니다. TypeScript로 블록의 구조를 정의하면, 비트코인이 어떻게 데이터를 저장하고 관리하는지 명확하게 이해할 수 있습니다.

개요

간단히 말해서, Block 클래스는 블록체인을 구성하는 하나의 데이터 단위입니다. 마치 건물을 짓는 벽돌 하나하나처럼, 블록체인도 이 Block 객체들이 쌓여서 만들어집니다.

실제 비트코인에서는 각 블록이 약 1MB 크기의 거래 데이터를 담고 있으며, 평균 10분마다 새로운 블록이 생성됩니다. 우리가 만들 Block 클래스는 이런 실제 블록의 핵심 요소들을 담게 됩니다.

전통적인 데이터베이스에서는 관리자가 데이터를 수정할 수 있었다면, 블록체인의 블록은 한번 생성되면 수정이 불가능합니다. 이것이 블록체인의 핵심 특징입니다.

Block 클래스의 핵심 속성은 인덱스(순서), 타임스탬프(생성시간), 데이터(거래정보), 이전 해시(이전 블록과의 연결고리), 그리고 현재 해시(블록의 고유 식별자)입니다. 이 다섯 가지 요소가 모여 블록의 무결성과 연결성을 보장합니다.

코드 예제

class Block {
  public index: number;
  public timestamp: number;
  public data: string;
  public previousHash: string;
  public hash: string;
  public nonce: number;

  constructor(index: number, timestamp: number, data: string, previousHash: string = '') {
    this.index = index;
    this.timestamp = timestamp;
    this.data = data;
    this.previousHash = previousHash;
    this.nonce = 0;
    this.hash = this.calculateHash();
  }

  calculateHash(): string {
    // 해시 계산 로직 (다음 섹션에서 설명)
    return '';
  }
}

설명

이것이 하는 일: Block 클래스는 블록체인에서 하나의 블록을 표현하는 데이터 구조입니다. 각 블록은 자신의 정보와 이전 블록과의 연결 정보를 모두 담고 있어, 체인 형태로 연결될 수 있습니다.

첫 번째로, index 속성은 블록체인에서 이 블록의 위치를 나타냅니다. 0부터 시작하며, 새 블록이 추가될 때마다 1씩 증가합니다.

이를 통해 블록의 순서를 명확하게 파악할 수 있습니다. 그 다음으로, timestamp는 블록이 생성된 정확한 시간을 Unix 타임스탬프 형식으로 기록합니다.

이는 거래의 시간 순서를 증명하는 중요한 역할을 하며, 이중 지불 공격을 방지하는 데 필수적입니다. data 필드는 실제 거래 정보를 담습니다.

실제 비트코인에서는 여러 거래가 머클 트리 구조로 저장되지만, 우리의 간단한 구현에서는 문자열로 표현합니다. previousHash는 이전 블록의 해시값을 저장하여 블록들을 체인처럼 연결하고, hash는 현재 블록의 고유 식별자 역할을 합니다.

마지막으로, nonce(Number used ONCE)는 작업증명에 사용되는 숫자로, 채굴 과정에서 계속 변경되며 특정 조건을 만족하는 해시를 찾는 데 사용됩니다. constructor에서는 이 모든 값을 초기화하고 calculateHash()를 호출하여 블록의 해시를 생성합니다.

여러분이 이 클래스를 사용하면 블록체인의 기본 단위를 객체로 다룰 수 있어, 블록 생성, 체인 연결, 검증 등의 작업을 객체지향적으로 구현할 수 있습니다. 이는 코드의 가독성과 유지보수성을 크게 향상시킵니다.

실전 팁

💡 실제 프로젝트에서는 data를 string이 아닌 Transaction[] 배열로 정의하여 여러 거래를 담을 수 있게 만드세요. 이것이 실제 비트코인의 방식입니다.

💡 timestamp는 항상 서버 시간을 사용하고, 클라이언트 시간을 믿지 마세요. 시간 조작으로 인한 공격을 방지할 수 있습니다.

💡 TypeScript의 readonly 키워드를 활용하여 한번 생성된 블록의 속성이 변경되지 않도록 보호하세요. 예: public readonly hash: string

💡 개발 중에는 toString() 메서드를 추가하여 블록 정보를 쉽게 출력하고 디버깅하세요. JSON.stringify()보다 가독성이 좋습니다.

💡 프로덕션 환경에서는 data 크기를 제한하세요. 비트코인은 블록당 약 1MB 제한이 있어 네트워크 효율성을 유지합니다.


2. SHA-256 해시 함수

시작하며

여러분이 파일을 다운로드할 때 "체크섬"이나 "해시값"을 본 적 있나요? 블록체인에서 해시는 블록의 지문과 같은 역할을 합니다.

해시 함수는 어떤 크기의 데이터든 고정된 길이의 고유한 값으로 변환합니다. 심지어 한 글자만 바뀌어도 완전히 다른 해시값이 나오기 때문에, 데이터 위변조를 즉시 감지할 수 있습니다.

바로 이럴 때 필요한 것이 SHA-256 해시 함수입니다. 비트코인은 이 함수로 블록의 무결성을 보장하고, 작업증명 시스템을 구현합니다.

개요

간단히 말해서, SHA-256은 어떤 데이터든 256비트(64자리 16진수) 길이의 고유한 값으로 변환하는 암호화 해시 함수입니다. "Hello"와 "hello"는 완전히 다른 해시값을 만들어냅니다.

블록체인에서는 블록의 모든 정보(인덱스, 타임스탬프, 데이터, 이전 해시, nonce)를 합쳐서 하나의 해시값을 만듭니다. 이 해시값이 블록의 고유 식별자가 되며, 블록 내용이 조금이라도 변경되면 해시값이 완전히 달라져 위변조를 즉시 알아챌 수 있습니다.

전통적인 데이터베이스에서는 ID를 순차적으로 부여했다면, 블록체인에서는 내용 기반의 해시값을 ID로 사용합니다. 이는 같은 내용은 항상 같은 해시를 만들어내는 결정론적 특성 덕분입니다.

SHA-256의 핵심 특징은 단방향성(해시로 원본 복원 불가), 고유성(서로 다른 입력은 다른 해시), 눈사태 효과(작은 변화가 큰 차이를 만듦)입니다. 이러한 특징들이 블록체인의 보안을 보장합니다.

코드 예제

import * as crypto from 'crypto';

class Block {
  // ... 이전 코드 ...

  calculateHash(): string {
    // 블록의 모든 데이터를 하나의 문자열로 결합
    const data = this.index +
                 this.previousHash +
                 this.timestamp +
                 JSON.stringify(this.data) +
                 this.nonce;

    // SHA-256 해시 생성
    return crypto
      .createHash('sha256')
      .update(data)
      .digest('hex');
  }
}

설명

이것이 하는 일: calculateHash() 메서드는 블록의 모든 속성값을 하나로 합친 후, SHA-256 알고리즘을 적용하여 64자리 16진수 문자열을 생성합니다. 이 값이 블록의 고유 식별자가 됩니다.

첫 번째로, 블록의 모든 핵심 데이터(index, previousHash, timestamp, data, nonce)를 하나의 문자열로 결합합니다. 순서가 중요한데, 같은 순서로 결합해야 같은 해시값이 나오기 때문입니다.

JSON.stringify()를 사용하여 객체 데이터도 문자열로 변환합니다. 그 다음으로, Node.js의 crypto 모듈을 사용하여 SHA-256 해시를 생성합니다.

createHash('sha256')으로 해시 객체를 만들고, update(data)로 해시할 데이터를 입력하며, digest('hex')로 최종 해시값을 16진수 문자열로 받습니다. 이 과정에서 내부적으로는 복잡한 수학 연산이 일어납니다.

SHA-256은 입력 데이터를 512비트 블록으로 나누고, 64라운드의 암호화 연산을 거쳐 최종 256비트 해시를 생성합니다. 이 과정은 완전히 결정론적이어서 같은 입력은 항상 같은 출력을 만들어냅니다.

마지막으로, 생성된 해시값은 블록의 hash 속성에 저장됩니다. 이 해시는 다음 블록의 previousHash로 사용되어 블록들을 체인처럼 연결하는 고리 역할을 합니다.

만약 블록의 데이터가 조작되면 해시값이 변경되고, 이는 다음 블록의 previousHash와 맞지 않아 위조가 즉시 탐지됩니다. 여러분이 이 함수를 사용하면 블록의 무결성을 보장할 수 있고, 누가 데이터를 조작했는지 즉시 알아낼 수 있습니다.

실제 비트코인 네트워크에서는 매일 수십만 개의 블록이 이 방식으로 검증됩니다.

실전 팁

💡 해시 계산 순서가 매우 중요합니다. 네트워크의 모든 노드가 같은 순서로 계산해야 하므로, 한번 정한 순서는 절대 변경하지 마세요.

💡 프로덕션에서는 crypto 모듈 대신 crypto-js 같은 브라우저 호환 라이브러리를 사용하여 클라이언트에서도 검증할 수 있게 하세요.

💡 성능 최적화를 위해 자주 계산되는 해시는 캐싱하세요. 단, 블록이 변경되면 캐시를 무효화해야 합니다.

💡 디버깅 시 해시값의 앞 8자리만 출력하세요. 전체 64자리는 로그를 읽기 어렵게 만듭니다. 예: hash.substring(0, 8)

💡 테스트 시 동일한 timestamp로 블록을 생성하면 재현 가능한 해시값을 얻을 수 있어 단위 테스트 작성이 쉬워집니다.


3. Genesis Block

시작하며

여러분이 체인을 만들려고 할 때, 첫 번째 고리는 어디에 연결하나요? 블록체인도 마찬가지로 첫 번째 블록이 특별합니다.

제네시스 블록(Genesis Block)은 블록체인의 시작점입니다. 모든 블록은 이전 블록과 연결되어야 하는데, 첫 번째 블록은 연결할 이전 블록이 없죠.

이것이 제네시스 블록이 특별한 이유입니다. 바로 이럴 때 필요한 것이 제네시스 블록 생성 함수입니다.

비트코인의 첫 블록은 2009년 1월 3일에 사토시 나카모토가 직접 만들었으며, "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"라는 메시지가 담겨있습니다.

개요

간단히 말해서, 제네시스 블록은 블록체인의 첫 번째 블록으로, previousHash가 없거나 '0'으로 설정된 특별한 블록입니다. 이 블록부터 모든 블록체인이 시작됩니다.

실제 비트코인 제네시스 블록은 50 BTC의 보상을 포함하지만, 이 코인은 사용할 수 없도록 하드코딩되어 있습니다. 제네시스 블록은 네트워크의 모든 참여자가 동일하게 가지고 있어야 하는 신뢰의 기준점 역할을 합니다.

전통적인 데이터베이스에서는 테이블 생성 시 스키마만 정의했다면, 블록체인에서는 제네시스 블록이라는 실제 데이터가 있는 첫 번째 레코드가 필요합니다. 이것이 분산 시스템에서 모든 노드가 같은 시작점을 공유하는 방법입니다.

제네시스 블록의 핵심 특징은 하드코딩된 값(코드에 직접 작성), previousHash가 '0'이나 빈 문자열, index가 0, 그리고 특별한 의미를 가진 data입니다. 이러한 특징들이 블록체인의 시작점을 명확하게 만듭니다.

코드 예제

class Blockchain {
  public chain: Block[];

  constructor() {
    this.chain = [this.createGenesisBlock()];
  }

  private createGenesisBlock(): Block {
    // 제네시스 블록 생성: 이전 해시는 "0"으로 설정
    return new Block(
      0,
      Date.parse('2025-01-01'),
      'Genesis Block - 첫 번째 블록',
      '0'
    );
  }

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

설명

이것이 하는 일: createGenesisBlock() 메서드는 블록체인의 시작점이 되는 특별한 블록을 생성합니다. 이 블록은 Blockchain 클래스가 생성될 때 자동으로 만들어져 체인에 추가됩니다.

첫 번째로, 제네시스 블록의 index는 항상 0입니다. 이는 첫 번째 위치를 나타내며, 이후 추가되는 모든 블록은 1, 2, 3으로 증가합니다.

이를 통해 블록의 순서를 명확하게 관리할 수 있습니다. 그 다음으로, timestamp는 프로젝트의 시작 시점이나 의미 있는 날짜로 설정합니다.

실제 비트코인은 2009년 1월 3일이 제네시스 블록의 타임스탬프입니다. Date.parse()를 사용하여 특정 날짜를 Unix 타임스탬프로 변환합니다.

data 필드에는 "Genesis Block"이라는 표시와 함께 특별한 메시지를 넣을 수 있습니다. 사토시 나카모토는 당시 신문 헤드라인을 넣어 블록이 그 날짜 이후에 만들어졌음을 증명했습니다.

이는 블록체인의 투명성을 보여주는 상징적인 행위였습니다. 마지막으로, previousHash는 '0' 문자열로 설정됩니다.

이전 블록이 없기 때문에 특별한 값을 사용하며, 이를 통해 제네시스 블록임을 쉽게 식별할 수 있습니다. constructor에서는 이 제네시스 블록을 생성하여 chain 배열에 추가함으로써 블록체인을 초기화합니다.

여러분이 이 패턴을 사용하면 블록체인이 항상 일관된 시작점을 가지게 되어, 여러 노드가 동일한 체인을 공유할 수 있습니다. 이는 분산 합의의 기초가 됩니다.

실전 팁

💡 제네시스 블록의 타임스탬프와 데이터는 절대 변경하지 마세요. 변경하면 전체 체인의 해시가 달라져 호환성이 깨집니다.

💡 프로덕션 환경에서는 제네시스 블록을 별도의 설정 파일에 저장하여 여러 노드가 동일한 제네시스 블록을 사용하도록 보장하세요.

💡 테스트 환경에서는 createGenesisBlock()에 파라미터를 추가하여 다양한 시나리오를 테스트할 수 있게 만드세요.

💡 제네시스 블록의 해시값을 상수로 저장해두고, 블록체인 검증 시 첫 블록의 해시가 이 값과 일치하는지 확인하세요.

💡 의미 있는 메시지를 data에 포함시켜 프로젝트의 목적이나 생성 시점을 기록하세요. 이는 나중에 블록체인의 역사를 추적하는 데 유용합니다.


4. Block Chain 연결

시작하며

여러분이 레고 블록을 쌓을 때, 각 블록이 어떻게 서로 연결되나요? 블록체인도 각 블록이 이전 블록과 정확하게 연결되어야 합니다.

블록체인의 핵심은 '체인'입니다. 각 블록은 이전 블록의 해시를 저장하고 있어, 체인처럼 연결됩니다.

만약 중간의 한 블록이라도 변경되면, 그 이후의 모든 블록의 연결이 깨집니다. 바로 이럴 때 필요한 것이 블록 추가 메서드입니다.

새 블록을 생성할 때 이전 블록의 해시를 가져와 연결하는 로직이 블록체인의 위변조 방지 메커니즘의 핵심입니다.

개요

간단히 말해서, 블록 체인 연결은 새 블록의 previousHash에 이전 블록의 hash를 저장하는 방식으로 이루어집니다. 이렇게 하면 블록들이 사슬처럼 연결되어 하나의 블록체인을 형성합니다.

실제 비트코인에서는 약 80만 개 이상의 블록이 이렇게 연결되어 있으며, 제네시스 블록부터 최신 블록까지 하나의 체인으로 이어져 있습니다. 이 연결 구조 덕분에 과거 거래를 조작하는 것이 사실상 불가능합니다.

전통적인 연결 리스트에서는 포인터로 이전 노드를 참조했다면, 블록체인에서는 암호화 해시로 이전 블록을 참조합니다. 이는 훨씬 강력한 보안을 제공하며, 데이터 무결성을 수학적으로 보장합니다.

블록 연결의 핵심 특징은 단방향 연결(이전 블록만 참조), 암호화 연결(해시 사용), 불변성(한번 연결되면 변경 불가)입니다. 이러한 특징들이 블록체인을 "신뢰할 수 없는 환경에서 신뢰를 만드는" 기술로 만듭니다.

코드 예제

class Blockchain {
  public chain: Block[];

  constructor() {
    this.chain = [this.createGenesisBlock()];
  }

  // 새로운 블록을 체인에 추가
  addBlock(data: string): void {
    const previousBlock = this.getLatestBlock();
    const newIndex = previousBlock.index + 1;
    const newTimestamp = Date.now();

    // 이전 블록의 해시를 새 블록에 연결
    const newBlock = new Block(
      newIndex,
      newTimestamp,
      data,
      previousBlock.hash
    );

    this.chain.push(newBlock);
  }

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

설명

이것이 하는 일: addBlock() 메서드는 새로운 블록을 생성하여 블록체인에 추가합니다. 핵심은 이전 블록의 해시값을 가져와 새 블록의 previousHash로 설정하는 것입니다.

첫 번째로, getLatestBlock()을 호출하여 현재 체인의 마지막 블록을 가져옵니다. 배열의 마지막 요소를 반환하는 간단한 로직이지만, 이 블록의 정보가 새 블록 생성에 필수적입니다.

이전 블록의 index와 hash가 필요하기 때문입니다. 그 다음으로, 새 블록의 index는 이전 블록의 index + 1로 설정됩니다.

이렇게 하면 블록의 순서가 자동으로 관리되며, 블록이 추가된 순서를 명확하게 알 수 있습니다. timestamp는 현재 시각(Date.now())으로 설정하여 블록 생성 시점을 기록합니다.

가장 중요한 부분은 새 블록을 생성할 때 previousBlock.hash를 previousHash 파라미터로 전달하는 것입니다. 이것이 바로 블록들을 체인처럼 연결하는 핵심 메커니즘입니다.

이렇게 하면 블록 B는 블록 A의 해시를 가지고 있고, 블록 C는 블록 B의 해시를 가지는 식으로 연결됩니다. 마지막으로, 생성된 블록을 this.chain 배열에 push()하여 체인에 추가합니다.

배열에 순서대로 저장되므로, 블록체인의 전체 히스토리를 쉽게 탐색할 수 있습니다. 만약 중간의 블록이 변경되면 그 블록의 해시가 바뀌고, 다음 블록의 previousHash와 맞지 않아 위조를 즉시 감지할 수 있습니다.

여러분이 이 메서드를 사용하면 간단하게 새 거래를 블록체인에 추가할 수 있으며, 자동으로 이전 블록과의 연결이 보장됩니다. 이는 블록체인 애플리케이션 개발의 기본이 되는 패턴입니다.

실전 팁

💡 프로덕션에서는 addBlock() 전에 반드시 체인 유효성을 검증하세요. 손상된 체인에 블록을 추가하면 문제가 더 커집니다.

💡 동시에 여러 블록이 추가되는 것을 방지하기 위해 뮤텍스나 락을 사용하세요. 경쟁 조건(race condition)을 방지할 수 있습니다.

💡 블록 추가 시 이벤트를 발생시켜 다른 노드에 알리도록 구현하세요. EventEmitter를 활용하면 옵저버 패턴을 쉽게 구현할 수 있습니다.

💡 대용량 데이터를 처리할 때는 블록 추가 후 체인을 주기적으로 파일에 저장하여 메모리 부족을 방지하세요.

💡 트랜잭션 풀을 만들어 여러 거래를 모아서 한 블록에 담는 방식으로 구현하면 실제 비트코인에 더 가깝습니다.


5. Proof of Work

시작하며

여러분이 스팸 메일을 막으려면 어떻게 해야 할까요? 메일을 보낼 때 약간의 "작업"을 요구하면 됩니다.

블록체인도 비슷한 원리를 사용합니다. 작업증명(Proof of Work, PoW)은 블록을 생성하려면 계산 작업을 수행해야 한다는 개념입니다.

이를 통해 누구나 쉽게 블록을 만들어 체인을 조작하는 것을 방지합니다. 비트코인은 평균 10분이 걸리는 수학 문제를 풀어야 블록을 생성할 수 있습니다.

바로 이럴 때 필요한 것이 채굴(Mining) 메서드입니다. 특정 조건을 만족하는 해시를 찾을 때까지 nonce 값을 계속 증가시키며 시도하는 과정이 바로 채굴입니다.

개요

간단히 말해서, Proof of Work는 해시값이 특정 조건을 만족할 때까지 nonce를 증가시키며 반복 계산하는 메커니즘입니다. 예를 들어, 해시가 "0000"으로 시작해야 한다면, 그런 해시가 나올 때까지 수만 번의 계산을 해야 합니다.

실제 비트코인에서는 해시값이 특정 목표값(target)보다 작아야 하며, 현재 난이도에서는 약 10^20번의 계산을 해야 합니다. 전 세계 채굴자들이 경쟁적으로 이 문제를 풀고, 가장 먼저 푼 사람이 블록을 생성하고 보상을 받습니다.

전통적인 데이터베이스에서는 관리자가 레코드를 추가할 권한을 가졌다면, 블록체인에서는 계산 작업을 한 사람만 블록을 추가할 수 있습니다. 이것이 중앙 권한 없이 합의를 이루는 핵심 메커니즘입니다.

PoW의 핵심 특징은 계산하기 어렵지만 검증하기 쉬움, 난이도 조절 가능, 51% 공격 방어입니다. 이러한 특징들이 블록체인의 보안과 탈중앙화를 동시에 달성하게 합니다.

코드 예제

class Block {
  // ... 이전 코드 ...

  // 작업증명: 특정 난이도를 만족하는 해시 찾기
  mineBlock(difficulty: number): void {
    // 난이도만큼 "0"이 반복되는 문자열 생성 (예: difficulty=4 → "0000")
    const target = Array(difficulty + 1).join('0');

    // 해시가 target으로 시작할 때까지 반복
    while (this.hash.substring(0, difficulty) !== target) {
      this.nonce++;  // nonce 증가
      this.hash = this.calculateHash();  // 새 해시 계산
    }

    console.log(`블록 채굴 완료: ${this.hash}`);
  }
}

설명

이것이 하는 일: mineBlock() 메서드는 블록의 해시가 특정 난이도 조건을 만족할 때까지 nonce를 증가시키며 해시를 반복 계산합니다. 이것이 바로 비트코인의 "채굴" 과정입니다.

첫 번째로, 난이도(difficulty)에 따라 목표 문자열(target)을 생성합니다. difficulty가 4라면 "0000", 5라면 "00000"이 됩니다.

Array(difficulty + 1).join('0')은 난이도만큼 0을 반복하는 문자열을 만드는 간결한 방법입니다. 그 다음으로, while 루프를 사용하여 현재 해시의 앞 difficulty 글자가 target과 일치할 때까지 반복합니다.

예를 들어 difficulty가 4라면, 해시가 "0000abcd..."처럼 4개의 0으로 시작해야 합니다. 이는 16^4 = 65,536번 중 1번 정도의 확률로 나타납니다.

루프 내부에서는 nonce를 1씩 증가시키고, calculateHash()를 다시 호출하여 새로운 해시를 계산합니다. nonce가 바뀌면 블록의 내용이 달라지므로 완전히 다른 해시가 생성됩니다.

이 과정을 조건을 만족하는 해시가 나올 때까지 반복합니다. 마지막으로, 조건을 만족하는 해시를 찾으면 루프가 종료되고, 채굴 완료 메시지를 출력합니다.

이때의 nonce와 hash가 블록에 저장되며, 이 블록은 작업증명을 완료한 유효한 블록이 됩니다. 다른 노드들은 단 한 번의 해시 계산만으로 이 블록의 유효성을 검증할 수 있습니다.

여러분이 이 메서드를 사용하면 블록 생성에 계산 비용을 부과할 수 있어, 악의적인 사용자가 체인을 조작하려면 막대한 계산 능력이 필요하게 됩니다. 난이도를 조절하여 블록 생성 속도를 제어할 수도 있습니다.

실전 팁

💡 난이도 4-5 정도로 테스트하세요. 난이도 6 이상은 일반 PC에서 수십 초 이상 걸려 개발 속도가 느려집니다.

💡 채굴 진행 상황을 보여주려면 nonce를 10,000번마다 출력하세요. 사용자가 프로그램이 멈춘 게 아니라 작업 중임을 알 수 있습니다.

💡 프로덕션 환경에서는 Worker Thread나 별도 프로세스에서 채굴하여 메인 스레드를 블로킹하지 않도록 하세요.

💡 난이도를 동적으로 조절하는 로직을 추가하세요. 비트코인은 2016블록마다 난이도를 조정하여 평균 10분을 유지합니다.

💡 채굴 중단 기능을 구현하세요. 조건 변수나 플래그를 사용하여 언제든 채굴을 중지하고 다른 작업을 할 수 있게 만드세요.


6. Chain Validation

시작하며

여러분이 받은 돈이 진짜인지 확인하려면 어떻게 해야 할까요? 블록체인도 마찬가지로 체인의 유효성을 검증하는 메커니즘이 필요합니다.

블록체인의 가장 큰 장점은 누구나 전체 체인의 유효성을 검증할 수 있다는 것입니다. 각 블록의 해시가 올바른지, 이전 블록과 제대로 연결되어 있는지 확인하면 위변조 여부를 즉시 알 수 있습니다.

바로 이럴 때 필요한 것이 체인 검증 메서드입니다. 제네시스 블록부터 최신 블록까지 순회하며 모든 블록이 유효한지 확인하는 과정이 블록체인의 신뢰성을 보장합니다.

개요

간단히 말해서, 체인 검증은 모든 블록을 순회하며 1) 각 블록의 해시가 올바르게 계산되었는지, 2) 각 블록의 previousHash가 실제 이전 블록의 hash와 일치하는지 확인하는 과정입니다. 실제 비트코인 노드는 새로운 블록을 받을 때마다 이 검증을 수행하며, 유효하지 않은 블록은 즉시 거부합니다.

전체 네트워크가 합의한 규칙을 따르지 않는 블록은 절대 체인에 추가되지 않습니다. 전통적인 시스템에서는 중앙 서버의 데이터를 신뢰했다면, 블록체인에서는 수학적 검증으로 신뢰를 만듭니다.

누구든 코드를 실행하여 체인이 유효한지 직접 확인할 수 있습니다. 체인 검증의 핵심 특징은 독립적 검증 가능(제3자 불필요), 빠른 검증(생성보다 훨씬 빠름), 결정론적(같은 체인은 항상 같은 결과)입니다.

이러한 특징들이 블록체인을 투명하고 신뢰할 수 있는 시스템으로 만듭니다.

코드 예제

class Blockchain {
  // ... 이전 코드 ...

  // 블록체인의 유효성 검증
  isChainValid(): boolean {
    // 제네시스 블록은 검증에서 제외 (i=1부터 시작)
    for (let i = 1; i < this.chain.length; i++) {
      const currentBlock = this.chain[i];
      const previousBlock = this.chain[i - 1];

      // 1. 현재 블록의 해시가 올바른지 검증
      if (currentBlock.hash !== currentBlock.calculateHash()) {
        console.log(`블록 ${i}의 해시가 유효하지 않습니다.`);
        return false;
      }

      // 2. 이전 블록과의 연결이 올바른지 검증
      if (currentBlock.previousHash !== previousBlock.hash) {
        console.log(`블록 ${i}의 연결이 끊어졌습니다.`);
        return false;
      }
    }

    return true;
  }
}

설명

이것이 하는 일: isChainValid() 메서드는 블록체인의 모든 블록을 순회하며 각 블록의 무결성과 연결성을 검증합니다. 두 가지 핵심 검증을 수행합니다.

첫 번째로, for 루프는 인덱스 1부터 시작합니다. 제네시스 블록(index 0)은 이전 블록이 없으므로 검증에서 제외됩니다.

각 반복에서 현재 블록과 이전 블록을 가져옵니다. 이 두 블록의 관계를 검증하는 것이 핵심입니다.

그 다음으로, 첫 번째 검증은 현재 블록의 저장된 해시값이 실제 계산한 해시값과 일치하는지 확인합니다. currentBlock.calculateHash()를 호출하여 블록의 현재 데이터로 해시를 다시 계산하고, 저장된 hash와 비교합니다.

만약 다르다면 누군가 블록 데이터를 조작했다는 의미입니다. 두 번째 검증은 현재 블록의 previousHash가 실제 이전 블록의 hash와 일치하는지 확인합니다.

이것이 체인의 연결성을 보장하는 검증입니다. 만약 일치하지 않는다면 체인이 끊어졌거나 블록 순서가 조작되었다는 의미입니다.

마지막으로, 모든 검증을 통과하면 true를 반환하고, 하나라도 실패하면 즉시 false를 반환합니다. 콘솔에 어떤 블록에서 문제가 발생했는지 출력하여 디버깅을 돕습니다.

이 메서드를 통해 전체 체인의 무결성을 단 몇 밀리초 만에 확인할 수 있습니다. 여러분이 이 검증을 정기적으로 실행하면 데이터 손상이나 악의적인 조작을 즉시 감지할 수 있습니다.

실제 블록체인 네트워크에서는 노드들이 서로의 체인을 계속 검증하며 합의를 유지합니다.

실전 팁

💡 대용량 체인을 검증할 때는 비동기 처리를 고려하세요. async/await를 사용하여 UI를 블로킹하지 않도록 만들 수 있습니다.

💡 검증 실패 시 어떤 데이터가 변경되었는지 상세히 로깅하세요. 원본과 현재 값을 비교하여 출력하면 디버깅이 쉬워집니다.

💡 프로덕션에서는 블록 추가 후 자동으로 검증을 실행하는 것보다, 주기적으로 검증하는 것이 성능에 유리합니다.

💡 체크포인트 시스템을 도입하세요. 특정 블록까지는 검증된 것으로 간주하고, 그 이후만 검증하면 속도가 빨라집니다.

💡 난이도 검증도 추가하세요. PoW를 사용한다면 각 블록의 해시가 난이도 조건을 만족하는지도 확인해야 합니다.


7. Nonce와 난이도

시작하며

여러분이 주사위를 던져서 6이 나올 때까지 계속 던진다고 생각해보세요. 블록체인의 채굴도 이와 비슷합니다.

Nonce(Number used ONCE)는 채굴 과정에서 계속 변경되는 숫자입니다. 난이도(Difficulty)는 얼마나 많은 0으로 해시가 시작해야 하는지 결정합니다.

난이도가 높을수록 조건을 만족하는 해시를 찾기 어렵습니다. 바로 이럴 때 필요한 것이 난이도 조절 시스템입니다.

비트코인은 약 2주마다 난이도를 조정하여 블록 생성 시간을 평균 10분으로 유지합니다.

개요

간단히 말해서, nonce는 해시 계산에 포함되는 임의의 숫자로, 조건을 만족하는 해시가 나올 때까지 계속 증가시킵니다. 난이도는 해시의 앞부분에 있어야 하는 0의 개수로 표현됩니다.

실제 비트코인에서는 난이도가 매우 높아서 특수 채굴 장비(ASIC)를 사용해도 평균 10분이 걸립니다. 전체 네트워크의 해시파워가 증가하면 난이도도 함께 증가하여 블록 생성 속도를 일정하게 유지합니다.

전통적인 시스템에서는 서버 성능으로 처리량을 제어했다면, 블록체인에서는 난이도로 블록 생성 속도를 제어합니다. 이는 네트워크 전체의 동기화와 안정성을 위해 매우 중요합니다.

난이도 시스템의 핵심 특징은 동적 조절(해시파워에 따라 변경), 지수적 증가(난이도 1 증가 = 16배 어려움), 예측 가능성(목표 블록 시간 유지)입니다. 이러한 특징들이 블록체인의 안정적인 운영을 보장합니다.

코드 예제

class Blockchain {
  public difficulty: number;

  constructor(difficulty: number = 4) {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = difficulty;  // 기본 난이도 4
  }

  addBlock(data: string): void {
    const previousBlock = this.getLatestBlock();
    const newBlock = new Block(
      previousBlock.index + 1,
      Date.now(),
      data,
      previousBlock.hash
    );

    // 블록 채굴 (작업증명 수행)
    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
  }

  // 난이도 동적 조절 (간단한 예시)
  adjustDifficulty(): void {
    const latestBlock = this.getLatestBlock();
    const prevAdjustmentBlock = this.chain[this.chain.length - 10];

    if (!prevAdjustmentBlock) return;

    const timeExpected = 10 * 60 * 1000;  // 10블록 * 60초 * 1000ms
    const timeTaken = latestBlock.timestamp - prevAdjustmentBlock.timestamp;

    // 시간이 너무 짧으면 난이도 증가, 길면 감소
    if (timeTaken < timeExpected / 2) {
      this.difficulty++;
    } else if (timeTaken > timeExpected * 2) {
      this.difficulty--;
    }
  }
}

설명

이것이 하는 일: 난이도 시스템은 블록 생성에 필요한 계산량을 조절하여 네트워크의 블록 생성 속도를 일정하게 유지합니다. nonce는 이 과정에서 변경되는 변수입니다.

첫 번째로, Blockchain 클래스에 difficulty 속성을 추가합니다. 생성자에서 기본값을 4로 설정하는데, 이는 해시가 "0000"으로 시작해야 한다는 의미입니다.

난이도 4는 테스트에 적당하며, 일반 PC에서 몇 초 안에 채굴할 수 있습니다. 그 다음으로, addBlock() 메서드를 수정하여 블록 생성 시 mineBlock(this.difficulty)를 호출합니다.

이렇게 하면 모든 새 블록이 작업증명을 거쳐야 체인에 추가됩니다. 채굴 과정에서 nonce가 0부터 시작하여 조건을 만족할 때까지 증가합니다.

adjustDifficulty() 메서드는 난이도를 동적으로 조절합니다. 최근 10개 블록의 생성 시간을 측정하여, 예상 시간(10분 × 10블록)보다 너무 빠르면 난이도를 높이고, 너무 느리면 낮춥니다.

이는 비트코인의 난이도 조절 알고리즘을 단순화한 버전입니다. 마지막으로, 난이도가 1 증가할 때마다 평균 채굴 시간은 16배 증가합니다.

이는 16진수를 사용하기 때문인데, 난이도 4(0000)는 16^4 = 65,536번 중 1번, 난이도 5(00000)는 16^5 = 1,048,576번 중 1번의 확률입니다. 이런 지수적 증가가 네트워크 보안을 강력하게 만듭니다.

여러분이 이 시스템을 사용하면 네트워크의 전체 해시파워가 변해도 블록 생성 속도를 일정하게 유지할 수 있습니다. 이는 블록체인의 예측 가능성과 안정성에 필수적입니다.

실전 팁

💡 난이도 조절 주기를 신중하게 선택하세요. 너무 자주 조절하면 불안정하고, 너무 드물게 조절하면 해시파워 변화에 대응이 느립니다.

💡 난이도의 최소값과 최대값을 설정하세요. 난이도 0은 의미가 없고, 너무 높으면 네트워크가 멈출 수 있습니다.

💡 채굴 시간을 로깅하여 난이도 조절의 효과를 모니터링하세요. 평균 블록 시간이 목표에 가까운지 확인할 수 있습니다.

💡 테스트 환경에서는 낮은 난이도(2-3)를 사용하고, 프로덕션에서만 높은 난이도를 적용하세요. 개발 속도가 크게 향상됩니다.

💡 난이도 변경 이벤트를 발생시켜 채굴자들에게 알리세요. 채굴 전략을 실시간으로 조정할 수 있습니다.


8. Timestamp 관리

시작하며

여러분이 계약서에 날짜를 기록하는 이유는 무엇인가요? 블록체인도 각 블록의 생성 시간을 정확하게 기록해야 합니다.

타임스탬프는 블록이 언제 생성되었는지 증명하는 핵심 요소입니다. 이를 통해 거래의 시간 순서를 확정하고, 이중 지불 같은 공격을 방지할 수 있습니다.

또한 난이도 조절 알고리즘에도 사용됩니다. 바로 이럴 때 필요한 것이 타임스탬프 관리 시스템입니다.

정확하고 조작 불가능한 시간 정보가 블록체인의 신뢰성을 보장합니다.

개요

간단히 말해서, 타임스탬프는 블록 생성 시각을 Unix 타임스탬프 형식(1970년 1월 1일 이후 경과한 밀리초)으로 저장한 숫자입니다. 이 형식은 시간대에 상관없이 일관된 시간을 표현할 수 있습니다.

실제 비트코인에서는 타임스탬프가 이전 11개 블록의 중간값보다 크고, 네트워크 조정 시간보다 2시간 이상 미래가 아니어야 한다는 규칙이 있습니다. 이런 검증 규칙이 시간 조작을 방지합니다.

전통적인 시스템에서는 서버의 시계를 신뢰했다면, 블록체인에서는 네트워크 전체의 합의로 시간을 결정합니다. 분산 환경에서 시간 동기화는 매우 중요한 문제입니다.

타임스탬프 시스템의 핵심 특징은 단조 증가(항상 증가해야 함), 상대적 정확성(절대 시간보다 순서가 중요), 조작 저항성(과거로 되돌릴 수 없음)입니다. 이러한 특징들이 블록체인의 시간적 무결성을 보장합니다.

코드 예제

class Block {
  // ... 이전 코드 ...

  // 타임스탬프 검증
  static isTimestampValid(newBlock: Block, previousBlock: Block): boolean {
    // 1. 새 블록의 타임스탬프가 이전 블록보다 커야 함
    if (newBlock.timestamp <= previousBlock.timestamp) {
      console.log('타임스탬프가 이전 블록보다 작거나 같습니다.');
      return false;
    }

    // 2. 타임스탬프가 현재 시간으로부터 너무 미래가 아니어야 함
    const now = Date.now();
    const maxFutureTime = 2 * 60 * 60 * 1000;  // 2시간
    if (newBlock.timestamp > now + maxFutureTime) {
      console.log('타임스탬프가 너무 미래입니다.');
      return false;
    }

    return true;
  }
}

class Blockchain {
  // ... 이전 코드 ...

  addBlock(data: string): void {
    const previousBlock = this.getLatestBlock();
    const newBlock = new Block(
      previousBlock.index + 1,
      Date.now(),  // 현재 시간을 타임스탬프로 사용
      data,
      previousBlock.hash
    );

    // 타임스탬프 검증
    if (!Block.isTimestampValid(newBlock, previousBlock)) {
      console.log('블록의 타임스탬프가 유효하지 않습니다.');
      return;
    }

    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
  }
}

설명

이것이 하는 일: 타임스탬프 관리 시스템은 블록의 생성 시간을 기록하고, 시간 조작을 방지하기 위한 검증 규칙을 적용합니다. 블록체인의 시간적 일관성을 보장합니다.

첫 번째로, isTimestampValid() 정적 메서드를 정의하여 타임스탬프의 유효성을 검증합니다. 정적 메서드로 만든 이유는 블록 객체뿐 아니라 다른 곳에서도 이 검증 로직을 사용할 수 있기 때문입니다.

두 가지 주요 검증을 수행합니다. 그 다음으로, 첫 번째 검증은 새 블록의 타임스탬프가 이전 블록보다 커야 한다는 규칙입니다.

이는 블록체인이 시간 순서대로 생성되었음을 보장합니다. 만약 작거나 같다면 시간을 되돌리려는 시도이므로 거부합니다.

이는 이중 지불 공격을 방지하는 데 중요합니다. 두 번째 검증은 타임스탬프가 현재 시간으로부터 2시간 이상 미래가 아니어야 한다는 규칙입니다.

악의적인 채굴자가 타임스탬프를 미래로 설정하여 난이도 조절을 조작하는 것을 방지합니다. 2시간의 여유는 네트워크 노드들 간의 시계 차이를 허용하기 위함입니다.

마지막으로, addBlock() 메서드에서 새 블록 생성 시 Date.now()를 사용하여 현재 시간을 타임스탬프로 설정합니다. 그 후 isTimestampValid()를 호출하여 검증하고, 유효하지 않으면 블록 추가를 거부합니다.

이렇게 하면 잘못된 타임스탬프를 가진 블록이 체인에 추가되는 것을 방지할 수 있습니다. 여러분이 이 시스템을 사용하면 블록체인의 시간적 무결성을 보장하고, 시간 기반 공격을 효과적으로 방어할 수 있습니다.

또한 난이도 조절과 같은 시간 의존적 기능이 정확하게 작동하도록 만듭니다.

실전 팁

💡 서버 시간을 정기적으로 NTP 서버와 동기화하세요. 시스템 시계가 부정확하면 블록이 거부될 수 있습니다.

💡 타임스탬프를 사람이 읽을 수 있는 형식으로 변환하는 유틸리티 함수를 만드세요. new Date(timestamp).toISOString()을 사용할 수 있습니다.

💡 로그에 타임스탬프를 함께 기록하여 블록 생성 패턴을 분석하세요. 이상한 패턴은 공격의 징후일 수 있습니다.

💡 테스트 시 고정된 타임스탬프를 사용할 수 있는 옵션을 추가하세요. 재현 가능한 테스트 작성이 쉬워집니다.

💡 블록체인 탐색기를 만들 때 타임스탬프를 로컬 시간대로 변환하여 표시하세요. 사용자 경험이 크게 향상됩니다.


#TypeScript#Blockchain#Bitcoin#CryptographicHash#ProofOfWork#typescript

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.