본문 바로가기
Block Chain/Solidity

[Solidity] 머클트리 Merkle Tree Root & Proof || Solidity 0.8 | Openzeppelin ||

by 개발이 체질인 나그네 2023. 1. 29.
반응형

안녕하세요. 스마트 컨트렉트 개발자 개발이 체질의 최원혁입니다.

최근 2주 동안 코드스테이츠에서 진행한 커리어 챌린지 4기에 참여하게 되어 게시글 작성을 하지 못했네요.

이번 게시글에서 해당 프로젝트에서 사용했던 Merkle Tree Root & Proof 설명드리고자 합니다.

 


 What is Merkle Tree & Merkle Root

머클트리(Merkle Tree)란 해시함수 keccak256를 활용하여 여러 데이터를 하나의 해시값으로 나타내는 데이터 구조입니다. 쉽게 말해, 여러 개의 데이터를 하나의 bytes32 해시값으로 만드는 데이터 구조입니다.

 

머클트리 데이터는 해시함수의 특성상 압축된 데이터 중 하나라도 다르게 되면 결과값이 다르게 나타나게 됩니다(충돌저항성). 때문에 머클트리를 활용하면, 데이터 위변조를 방지하고, 데이터가 변하지 않았음을 증명할 수 있습니다.

 

Solidity에 여러 데이터를 저장할 때, EVM 특성상 수수료(가스비)가 발생합니다. 하지만 예외처리를 하기 위해 여러 개의 지갑주소나 데이터를 저장해야 는 경우가 많습니다. 이때, 머클트리를 사용하여 하나의 데이터를 만들어, 머클트리 데이터 하나만 저장하여 효율적이고 보안성이 높은 로직을 구현할 수 있습니다.

 

Example.

(1)

6개의 지갑주소 데이터를 머클트리 데이터로 암호화 하는 과정을 설명하겠습니다.

(2)

먼저 머클트리로 변환시킬 데이터를 Keccak256 해시함수를 통해 암호화시킵니다.

(3)

왼쪽부터 두 개의 데이터를 합쳐서 다시 Keccak256 해시함수로 데이터를 암호화시킵니다. 이 과정을 왼쪽부터 데이터 오프셋(offset)까지 반복하면, 최종적으로 하나의 데이터가 만들어집니다. 이러한 일련의 데이터 인덱스(index) 작업을 Merkle Tree라고하며, 압축된 하나의 데이터는 Merkle Root라고 부릅니다.


 What is Merkle Proof

머클트리 증명(Merkle Proof) 증명이란, 압축된 데이터 중에 데이터가 포함이 되었는지를 증명하는 기능입니다.

머클루트(Merkle Root)는 눈으로 봤을 때 64바이트 데이터 형태입니다. 압축된 데이터에 중요한 데이터 또는 꼭 포함되야 하는 데이터가 포함되었는지 확인할 수 없다면, 머클트리로 압축시킨 의미가 없습니다. 

 

예를 들어, 위 사진의 보라색 데이터가 머클루트(Merkle Root)에 포함이 됐는지 증명한다고 하면, 머클트리를 만드는 과정에서 주황색으로 표시된 데이터를 머클증명(Merkle Proof)으로 데이터를 만듭니다. 이렇게 되면, 주황색데이터보라색 데이터로 최상위 데이터인 머클루트(Merkle Root)를 만들어 낼 수 있습니다. 이렇게 만들어낸 머클루트의 값이 기존의 머클루트의 값과 같다면, 보라색 데이터는 포함된 데이터임을 증명할 수 있고, 만약 머클루트의 값이 다르다면 해시함수의 특성에 따라 보라색 데이터는 포함되지 않았음을 증명할 수 있습니다.

자세한 내용은 아래 링크를 통해 시각적 설명과 함께 확인할 수 있습니다.

https://www.youtube.com/watch?v=2kPFSoknlUU


📝 Example By Code 

이번 예시 코드는 Solidity에서 Openzeppelin에서 제공하는 MerkleProof.sol 라이브러리를 활용하여 머클증명(Merkle Proof) 과정을 진행해 보겠습니다.

 

(1) 모듈 설치

먼저 예제코드에서 사용할 node 모듈을 설치합니다.

npm i ethers merkletreejs keccak256 crypto

 

(2) 더미데이터 + 증명 데이터 생성

머클트리 과정과 머클루트(Merkle Root)를 만들기 위해 지갑주소(address) 15개와 마지막 증명할 데이터 1개를 만듭니다.

const { Wallet } = require("ethers");
const { randomBytes } = require("crypto");

// 더미데이터 15개 생성
const addressList = new Array(15)
  .fill(0)
  .map(() => new Wallet(randomBytes(32).toString("hex")).address);
  
// Merkle Proof로 증명될 지갑주소
const myAddress = "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"
addressList.push(myAddress);

 

(3) 머클루트(Merkle Root)와 머클증명(Merkle Proof) 생성

생성된 데이터를 머클트리를 통해 머클루트를 만들고, 증명할 데이터를 통해 머클 증명을 만듭니다.

const { MerkleTree } = require("merkletreejs");
const keccak256 = require("keccak256");

const merkleTree = new MerkleTree(
  addressList.concat(_address),
  keccak256,
  { hashLeaves: true, sortPairs: true }
);

const root = merkleTree.getHexRoot()
console.log(root);

const proof = merkleTree.getHexProof(keccak256(myAddress))
console.log(proof);

 

(4) Solidity Openzeppelin MerkleProof.sol

//SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract Merkle {
    bytes32 public _merkleRoot;

    function setMerkleRoot(bytes32 root) external {
        _merkleRoot = root;
    }

    function canClaim(address claimer, bytes32[] calldata merkleProof)
        public
        view
        returns (bool)
    {
        return MerkleProof.verify(
                merkleProof,
                _merkleRoot,
                keccak256(abi.encodePacked(claimer))
            );
    }
}

 

먼저 위에서 더미데이터를 통해 만든 머클루트를 setMerkle()함수를 통해 저장합니다.

위에서 마지막에 증명할 데이터로 넣은 지갑주소와 생성된 머클증명(Merkle Proof) 배열 데이터를 canClaim()함수에 넣으면 Ture결과값을 받으며 머클루트에 해당 증명할 데이터가 포함되었는지 증명할 수 있습니다.

 

 🔎 전체 코드(Github) : https://github.com/imelon2/My-Solidity-Playground/tree/main/Merkletree

 

GitHub - imelon2/My-Solidity-Playground

Contribute to imelon2/My-Solidity-Playground development by creating an account on GitHub.

github.com

 


지금까지 머클트리(Merkle Tree)와 머클루트(Merkle Root), 머클증명(Merkle Proof)에 대해 알아봤습니다.

다음시간에는 머클루트와 머클증명을 활용하여 ERC20 토큰 AirDrop Smart Contract를 구현해 보겠습니다.

 

감사합니다!

반응형

댓글