만족

[Blockchain] js로 이해하는 블록체인의 기본 구조 본문

[Blockchain] js로 이해하는 블록체인의 기본 구조

BlockChain/이론 Satisfaction 2021. 9. 13. 21:07

https://www.youtube.com/watch?v=zVqczFZr124&list=LL&index=4&ab_channel=SimplyExplained
위 영상을 참고하여 작성된 포스트임을 먼저 밝힌다.

블록체인이란?

여러 개의 블록이 체인처럼 엮여있는 형상을 가지고 있으며,
분산 네트워크를 통해 이 연결된 블록들이 훼손되지 않는다는 것을 보장하는 개념이다.

여기서 블록은 우리가 원하는 데이터들을 포함할 수 있으며,
이전 블록의 해시도 함께 포함함으로써 이전 블록과의 관계를 가질 수 있다.

이런 관계를 보고 "블록끼리 서로 체인으로 연결된것 같다"라고 해서 블록체인이라고 명명한다.

블록에 저장되는 정보

블록의 번호(index)
현재 블록의 해시값 (해시함수에 블록번호+이전해시값+기타데이터 정보를 넣고 반환된 값)
이전 블록의 해시값
기타 데이터(ex: 거래 정보)

를 기본적으로 포함한다.

제네시스 블록(Genesis Block)

블록체인상에서 처음 생성된 블록(0번 블록)을 말한다.

이 블록 이전에 존재하는 블록은 존재하지 않는다.

따라서 블록의 번호는 0번, 이번 블록의 해시값은 임의의 값을 고정해두고
현재 블록의 해시값을 계산해 블록체인에 넣는다.

블록체인이 생성된 순간에 제네시스 블록은 가장 처음 존재하는 블록이 된다.

해시

해시함수는 어떤 입력을 받아, 출력으로부터 입력을 유추할 수 없어야 하며, 입력이 다를 경우에는 출력도 반드시 달라야 하는 함수이다.

예를 들어, 해시함수를 h, 인풋을 i라고 할 경우
어떤 i에 대해서도, h(i1)!== h(i2)여야 한다는 의미이다.
(만약 h(i1)!== h(i2)인 서로 다른 i1,i2가 존재할 경우 이를 '해시 충돌(hash collision)이라고 하며,
블록체인에 적용하기에 부적합한 해시함수이다)

이번 포스트에서 블록의 해시를 계산하기 위해 SHA-256을 사용할 것이다
(왜 SHA-256에서 해시 충돌로부터 안전한지 궁금하다면 이곳을 참조하라)

아무튼 각각의 블록은 자기 자신을 해시 함수의 인풋으로 두어 해시를 계산하고 그 값을 블록에 포함시키며,
그 다음 블록은 이전 블록의 해시값을 포함하여 다시 해시를 계산하는 것을 모든 블록이 반복한다.

js로 블록체인 기본 구조 만들기

const SHA256 = require("crypto-js/sha256");

//Block
const Block = function (index, timestamp, data, prevHash = "") {
  this.index = index;
  this.timestamp = timestamp;
  this.data = data;
  this.prevHash = prevHash;
  this.hash = "";
};
Block.prototype.calcHash = function () {
  //index, prevHash, timestamp, data를 입력으로 해시값을 계산한다
  return SHA256(
    this.index + this.prevHash + this.timestamp + JSON.stringify(this.data)
  ).toString();
};

//Blockchain
const Blockchain = function () {
  this.chain = [this.createGenesisBlock()];
};

Blockchain.prototype.createGenesisBlock = function () {
  //번호 0번, 이전 해시 "0", data를 "GenesisBlock"으로 임의로 지정
  return new Block(0, "2021/09/13", "GenesisBlock", "0");
};
Blockchain.prototype.getLatestBlock = function () {
  return this.chain[this.chain.length - 1];
};
Blockchain.prototype.addBlock = function (newBlock) {
  //새로운 블록이 생성되면 가장 최근 블록의 해시값을 새로운 블록의 prevHash에 복사한다
  newBlock.prevHash = this.getLatestBlock().hash;
  newBlock.hash = newBlock.calcHash();

  //해시 계산이 완료되면 블록체인에 연결시킨다
  this.chain.push(newBlock);
};

//test
const testCoin = new Blockchain();
testCoin.addBlock(new Block(1, "2020/09/14", { foo: "bar" }));
testCoin.addBlock(new Block(2, "2020/09/15", { foo2: "bar2" }));

console.log(testCoin.chain)


코드를 실행시키면 아래와 같은 값을 볼 수 있다.

2번째 블록을 보면, prevHash의 값은 1번째 블록의 hash와 일치한다는 것을 확인할 수 있다.

블록체인 유효성 검증

그렇다면 공격자가 블록체인 내의 임의의 블록 하나를 악의적으로 변경시킨다면 어떻게 될까?

우리는 이런 공격을 막기 위해 블록체인의 유효성을 검증할 수 있다.

각각의 블록은 자기 자신의 해시값과 더불어 이전 블록의 해시값도 가지고 있기 때문에 쉽게 검증할 수 있다.

제네시스 블록의 다음 블록부터 다시 해시를 계산하면서,
저장된 해시와 다시 계산된 해시를 비교하여 일치하지 않다면 유효한 블록이 아니라고 판단하면 된다.

또한 n번째 블록이 가진 prevHash가 n-1번째 블록의 이전 해시값과 일치하지 않는 경우에도 유효한 블록체인이 아니라고 판단할 수 있다.

Blockchain.prototype.isValid = function () {
  //제네시스 블록은 이전 블록이 없어 검사를 건너뛰기 위해 1부터 시작한다.
  for (let i = 1; i < this.chain.length; i++) {
    const currentBlock = this.chain[i];
    const prevHash = this.chain[i - 1].hash;

    if (currentBlock.prevHash !== prevHash) {
      //현재 블록의 이전 해시값이 일치하지 않음
      return false;
    } else if (currentBlock.calcHash() !== currentBlock.hash) {
      //현재 블록에 저장된 해시값과 다시 계산한 해시값이 일치하지 않음
      return false;
    }
  }
  return true;
};


실제로 검증되는지 확인해보자.

//test
const testCoin = new Blockchain();
testCoin.addBlock(new Block(1, "2020/09/14", { foo: "bar" }));
testCoin.addBlock(new Block(2, "2020/09/15", { foo2: "bar2" }));

console.log("blockchain is valid?", testCoin.isValid());
//악의적인 사용자가 블록 중간의 내용을 바꾸는 경우
testCoin.chain[1].data = "hahaha";
console.log("blockchain is valid?", testCoin.isValid());

악의적인 사용자가 1번 블록의 내용을 바꾸는 경우이다.

이 경우 이전에 계산했던 해시값과, 블록의 내용을 바꾼 후 계산된 해시값은 다르기 때문에 유효성 검사를 통과할 수 없다.

그렇다면 1번 블록의 해시값까지 다시 계산하면 어떻게 될까?

//test
const testCoin = new Blockchain();
testCoin.addBlock(new Block(1, "2020/09/14", { foo: "bar" }));
testCoin.addBlock(new Block(2, "2020/09/15", { foo2: "bar2" }));

console.log("blockchain is valid?", testCoin.isValid());
//악의적인 사용자가 블록 중간의 내용을 바꾸는 경우
testCoin.chain[1].data = "hahaha";
//해시값까지 다시 계산한다면?
testCoin.chain[1].hash = testCoin.chain[1].calcHash();
console.log("blockchain is valid?", testCoin.isValid());

 


1번 블록의 해시를 재계산한다고 해도, 그 해시값이 2번 블록의 prevHash와 일치하지 않기 때문에 유효성 검사에 실패한다.

전체 코드

const SHA256 = require("crypto-js/sha256");

//Block
const Block = function (index, timestamp, data, prevHash = "") {
  this.index = index;
  this.timestamp = timestamp;
  this.data = data;
  this.prevHash = prevHash;
  this.hash = "";
};
Block.prototype.calcHash = function () {
  //index, prevHash, timestamp, data를 입력으로 해시값을 계산한다
  return SHA256(
    this.index + this.prevHash + this.timestamp + JSON.stringify(this.data)
  ).toString();
};

//Blockchain
const Blockchain = function () {
  this.chain = [this.createGenesisBlock()];
};

Blockchain.prototype.createGenesisBlock = function () {
  //번호 0번, 이전 해시 "0", data를 "GenesisBlock"으로 임의로 지정
  return new Block(0, "2021/09/13", "GenesisBlock", "0");
};
Blockchain.prototype.getLatestBlock = function () {
  return this.chain[this.chain.length - 1];
};
Blockchain.prototype.addBlock = function (newBlock) {
  //새로운 블록이 생성되면 가장 최근 블록의 해시값을 새로운 블록의 prevHash에 복사한다
  newBlock.prevHash = this.getLatestBlock().hash;
  newBlock.hash = newBlock.calcHash();

  //해시 계산이 완료되면 블록체인에 연결시킨다
  this.chain.push(newBlock);
};


//test
const testCoin = new Blockchain();
testCoin.addBlock(new Block(1, "2020/09/14", { foo: "bar" }));
testCoin.addBlock(new Block(2, "2020/09/15", { foo2: "bar2" }));

console.log("blockchain is valid?", testCoin.isValid());
//악의적인 사용자가 블록 중간의 내용을 바꾸는 경우
testCoin.chain[1].data = "hahaha";
//해시값까지 다시 계산한다면?
testCoin.chain[1].hash = testCoin.chain[1].calcHash();
console.log("blockchain is valid?", testCoin.isValid());
console.log(testCoin.chain)

생각해 볼 문제

방금 전 유효성 검사 부분에서 치명적인 문제가 있는 것을 알 수 있다.

만약 공격자가 바꾸려는 블록인 1번 블록에서부터 맨 마지막 블록까지 전부 다시 해시를 계산하고 블록을 유효하게 만들면
블록체인의 유효성 검사를 통과할 수 있다.

따라서 블록체인의 '불변성'을 충분히 고려하지 못하고 있다는 의미가 된다.

또한 블록을 추가하려 할 때는 prevHash만 고려되면 되므로, 가짜 블록을 추가하려 할 때는 어떤 블록이 올바른 블록인지 판단할 수 없다.

이것은 어떻게 막을 수 있을까?

다음 포스트에서 알아보자.



Comments