미니 해커톤 - 잔고 증명 ZK

Jake Kim·2024년 8월 17일

PSE2024

목록 보기
13/17

이번 주는 기존의 방식에서 벗어나, 직접 프로덕트를 만들어 보는 시간을 가졌다. 모든 조원들이 각자의 관심분야에 대하여 구현했고, 나는 계좌 정보 없이 잔고를 보여주는 방법을 고민해 보았다.
이 외에도 Turing Complete 한 Circom Based VM,
Anonymous Voting, Auction, Code Auditing without revealing the code 등 흥미로운 주제가 많았다.
하루 동안 완성하기에는 어려웠지만, 모두들 지금까지의 이론 공부에만 지쳐(?) 있다가 새로운 자극에 필요한 시점에 적절한 활동이었던 것 같다. 간만에 다들 아이패드를 놓고 노트북을 꺼내 코딩을 하니 눈빛이 살아나는 느낌이었다.

내가 구현하고자 하는 시나리오는 다음과 같다.
Alice 와 Bob이 NFT 거래를 (혹은 모종의 거래를) 원한다. Alice 는 Bob이 기준치 이상 (ex, 5 ETH) 의 잔고가 있어야 대화를 지속하고자 한다. Bob 은 Wallet 주소를 공개하고 싶지 않다. 하지만 꼭 5 ETH 이상을 보유하고 있으며, 이를 관리하는 Secret Key 도 보유하고 있다는 것을 증명하고 싶다.

Prerequisites

  1. Node.js
  2. Web3.js, Ethers.js, Snark.js
  3. Infura API Key (Sepolia Ethereum Mainnet)
  4. Wallet and Key
const { ethers } = require('ethers');
const { Web3 }  = require('web3');

// Replace with your private key 
const walletSecretKey = 'YOUR_KEY';

// Create a wallet instance using the private key
const wallet = new ethers.Wallet(walletSecretKey);

// Message to sign
const message = `I am proving ownership of wallet and want to negotiate with Alice ${wallet.address}`;

async function signMessage() {
    // Sign the message
    const signature = await wallet.signMessage(message);
    
    console.log("Message:", message);
    console.log("Signature:", signature);
    console.log("Address:", wallet.address);

    return signature;
}

// Function to verify the signed message
function verifySignature(message, signature) {
    // Recover the address from the signature
    const recoveredAddress = ethers.verifyMessage(message, signature);

    console.log("Recovered Address:", recoveredAddress);

    // Compare the recovered address with the expected address
    if (recoveredAddress.toLowerCase() === wallet.address.toLowerCase()) {
        console.log("Signature is valid. The message was signed by:", recoveredAddress);
    } else {
        console.log("Signature verification failed. Recovered address:", recoveredAddress);
    }
}

// Function to get the balance of the wallet
async function getBalance() {
    // Connect to an Ethereum node (e.g., Infura)
    const web3 = new Web3('YOUR-INFURA-KEY');

    try {
        // Get the balance in Wei (the smallest unit of Ether)
        const balance = await web3.eth.getBalance(wallet.address);

        // Convert the balance from Wei to Ether
        const balanceInEth = web3.utils.fromWei(balance, 'ether');

        console.log(`Balance of ${wallet.address}: ${balanceInEth} ETH`);

        return balanceInEth;
    } catch (error) {
        console.error(`Error fetching balance: ${error}`);
    }
}

// Main function to execute all steps
async function main() {
    // Step 1: Sign the message
    const signature = await signMessage();

    // Step 2: Verify the signed message
    verifySignature(message, signature);

    // Step 3: Get and display the wallet balance
    await getBalance();
}

// Execute the main function
main();

Run the Script

Run the script using Node.js:

node getBalance.js

Explanation

  1. 각각의 Component 들이 Input과 Output을 인수인계 하는 방식으로 작동한다.
    - SignMessage : Private Key 와 Message를 받고, Message와 Signature를 출력한다.
    - VerifySignature : Message와 Signature를 받고, Wallet Address를 출력한다
    - getBalance : Wallet Address를 받고, Balance를 출력한다
  1. 이 과정에서 Message는 Alice가 Bob에게 Challange를 주는 방식으로 사용되며, (Non-Interactive 하게는 Random Hash로 대체될 수 있다. )

  2. 시간 안에 구현하지는 못했지만, 최종적으로 출력되는 Balance값을 받고, Alice의 요구사항 (ex: 5 ETH)보다 같거나 크다는 것이 증명되면 OK를 내보낸다 (Snark.js)

  3. 이 모든 것을 StreamLine화 할 수 있도록 App.js를 만들어 Bob 과 Alice에게 차례로 부여한다. Bob은 Secret Key를 입력하고, Alice는 요구사항 (5 ETH)과 Challange Message를 입력한다.

  4. Alice가 입력을 마치고 Enter를 누르면, Snark OK와 함께 본인의 Message가 함께 출력된다.

Summary

첫 과정이다 보니 부족한 점이 많은데, 아직 실제로 구현된 적이 없는 Service라는 점에서 보람을 느꼈다. Circom까지 작업을 해보려 했는데 ... Circom은 아직 나에게는 어렵다. 또한 Ethers.js 라이브러리를 사용한다는 점에서 정합성을 100% 보장하기도 어렵지만, 향후 Javascript까지 실행 가능한 ZK VM이 있다면 한번 시도해 보고 싶다.
현재까지 개발된 많은 ZK 기법들이 어떤 고민을 거쳐 현재의 이르게 되었는지 대략이나마 가늠해 볼 수 있었던 소중한 시간이었다.

profile
세일즈 출신 개발자 제이크입니다.

0개의 댓글