2023-09-22 16:50:37

 

Wallet

말 그대로 지갑을 말하며 기본적인 역활은 암호화폐를 안전하게 보관하거나 암호화폐를 전달하고 받을 때 사용됩니다.

자산의 소유권을 암호화 방식으로 증명하며 지갑의 개인키를 사용하여 거래의 서명을 검증하고 본인의 자산을 관리합니다.

블록체인 지갑은 사용자의 디지털 자산을 관리하는 핵심 도구입니다.

 

암호화

암호화는 데이터나 정보를 원래의 형태에서 읽을 수 없는 형태로 변환하는 과정을 말합니다. 

이 변환된 데이터는 특정 키를 사용하여 복호화되기 전까지 원래의 형태로 복원할 수 없기 때문에 악의적인 사용자로 부터 데이터를 안전하게 보호 합니다.

암호화 방식에는 크게 2가지가 있으며 대칭키, 비대칭키로 나뉩니다.

 

블록체인 암호화 기술

해시 함수 : 블록체인에서 트랜잭션의 무결성과 블록의 무결성을 보장하기 위해 해시 함수를 사용합니다. 해시 함수는 1개의 문자로 이루어진 데이터를 받거나 10개의 문자로 이루진 데이터를 받아도 항상 고정된 길이의 문자열을 출력합니다. 즉, 점 하나를 찍어도 해시값이 완전히 바뀌게 됩니다. 비트코인에서는 SHA-256 해시 알고리즘을 사용합니다.

디지털 서명 : 사용자가 트랜잭션을 생성할 때 해당 트랜잭션은 사용자의 개인키로 서명됩니다. 이 서명은 트랜잭션이 해당 사용자에 의해 승인 되었다는 것을 증명하는 것으로 트랜잭션이 악의적인 사용자에 의해 위변조하면 해시값이 변경되어 서명은 무효가 됩니다.

키 쌍 : 블록체인 지갑은 개인키와 공개키로 구성된 키 쌍을 사용하며 개인키는 거래를 서명, 공개키는 거래를 검증할때 사용됩니다.

영지식 증명 : 몇몇 블록체인에서는 영지식 증명을 사용하여 거래 내용을 숨기면서 거래의 유효성을 증명합니다. 다시 말해 금액을 전송했다는 사실은 증명하지만 금액, 송금자, 수신자의 정보는 얻을 수 없습니다.

멀티 시그 : 특정 거래 또는 작업을 승인하기 위해 필요한 서명의 수를 정의하는 방법으로 예를 들어 3명의 사용자가 공동으로 보유하는 지갑에서 거래를 승인하려면 2명 이상의 사용자의 서명이 필요할 수 있는데 이러한 설정을 2-of-3 멀티시그라고 합니다.

 

 

대칭형 암호화

암호화 및 암호화된 데이터를 복호화 할때 사용하는 키가 1개인 암호화를 말하며 암호화한 사람과 수신하는 사람이 같은 키를 사용합니다.

쉽게 말하면 암호화와 복호화에 필요한 키가 똑같습니다.

 

비대칭형 암호화

사용하는 키와 복호화 할때 사용하는 키가 다른 방식을 말하며 2개의 키가 필요합니다. 

공개키는 데이터를 암호화하는데 사용되며 개인키는 암호화된 데이터를 복호화하는데 사용됩니다.

 

개인키(Private Key) : 디지털 자산에 대한 소유권을 증명하고 거래를 승인하는데 사용되는 암호화된 문자열입니다. 개인 키는 절대로 다른 사람과 공유해서는 안되며 읽어버리게 되면 해당 지갑에 있는 자산에 대한 접근이 불가능 할 수 있습니다.

공개키(Public Key) : 다른 사람에게 자신의 디지털 자산을 전송할때 사용되는 주소를 말합니다.

 

ECC

타원 곡선 암호화는 공개 키 암호화의 한 형태로 타원 곡선의 수학적 특성을 기반으로 하며 전통적인 공개키 암호화 방식에 비해 키의 길이가 짧으면서 동일한 보안 수준을 제공하기 때문에 효율성과 성능 측명에서 많은 장점을 가집니다.

 

주요 특징

공개키와 개인키 : 사용자는 개인키와 공개키를 가지는데 개인키와 공개키 사이에는 수학적인 관계가 존재합니다.

키 교환(ECDH) : 두 사용자가 각자의 개인키와 상대방의 공개키를 사용하여 공통의 비밀 값을 생성 할 수 있습니다.

디지털 서명(ECDSA) : 메시지에 대한 디지털 서명을 생성하고 검증하는데 사용됩니다.

 

secp256k1

타원 곡선 암호화에서 사용되는 특정 타원 곡선을 나타내며 이 곡선은 Koblitz 곡선으로 알려진 특별한 유형의 타원 곡선 중 하나입니다.

secp256k1은 특히 비트코인에서 사용되며 이 곡선 위에서 ECDSA가 비트코인의 주요 서명 메커니즘으로 사용됩니다.

 

 

Wallet 실습

지갑의 기본 구조를 구현하는 실습

추후에는 테스트 환경에서 지갑에서 지갑으로 금액을 전송하는 작업을 하겠지만 지금은 단순한 파일을 만들어 가상의 지갑을 만들 예정

 

프로젝트 구조

타입스크립트 설정 파일이나 npm 설정들은 전편의 설정과 동일하며 필요한 설정들은 실습을 통해 설치

설치 모듈

// ECC 암호화를 사용하기 위한 라이브러리
npm install elliptic
npm install @types/express express

 

view/index.html

지갑을 생성하고 생성된 지갑 리스트를 확인 할 수 있는 브라우저

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <title>Document</title>
  </head>
  <body>
    <h1>wallet tutorial</h1>
    <button id="walletBtn">create wallet</button>
    <ul id="walletList">
      <li>bitcoin wallet</li>
      <li>account : <span id="account"></span></li>
      <li>privateKey : <span id="privateKey"></span></li>
      <li>publicKey : <span id="publicKey"></span></li>
      <li>balance : <span id="balance"></span></li>
    </ul>

    <h1>wallet list</h1>
    <button id="walletListBtn">search wallet list</button>
    <div>
      <ul id="walletListData">
        push the search wallet list
      </ul>
    </div>
  </body>
  <script>
    const render = (wallet) => {
      account.innerHTML = wallet.account;
      privateKey.innerHTML = wallet.privateKey;
      publicKey.innerHTML = wallet.publicKey;
      balance.innerHTML = wallet.balance;
    };

    walletBtn.onclick = async () => {
      const { data: resp } = await axios.post("/newWallet", null);
      console.log("walletBtn.resp : ", resp);
      render(resp);
    };

    const getView = async (account) => {
      console.log(account);
      const { data: resp } = await axios.post("/walletSelect", { account });
      console.log("getView.resp : ", resp);
      render(resp);
    };

    walletListBtn.onclick = async () => {
      const { data: resp } = await axios.post("/walletList", null);
      const list = resp.map((account) => {
        return `<li onclick="getView('${account}')">${account}</li>`;
      });

      walletListData.innerHTML = list;
    };
  </script>
</html>

 

index.ts

지갑의 구조를 정의하고 생성하고 생성된 지갑주소(임의)를 파일로 생성

secp256k1 타원 곡선 암호화 사용

import { randomBytes } from "crypto";
import elliptic from "elliptic";
import fs from "fs";
import path from "path";

// 지갑 클래스 생성 후 브라우저에서 확인하기

// create elliptic instance
const ec = new elliptic.ec("secp256k1");

// 기본 지갑 정보 저장 경로
const dir = path.join(__dirname, "../../data");

// 지갑 클래서 정의
export class Wallet {
  public account: string;
  public privateKey: string;
  public publicKey: string;
  public balance: number;

  constructor(privateKey: string = "") {
    // 생성단계에서 개인키 값이 없으면 만듬
    // privateKey의 값이 빈문자열이기 때문에 this.getPrivateKey() 실행
    this.privateKey = privateKey || this.getPrivateKey();
    this.publicKey = this.getPublicKey();
    this.account = this.getAccount();
    this.balance = 0;
    // privateKey: string = "" 이기 때문에 Wallet.createWallet(this) 실행
    if (privateKey == "") Wallet.createWallet(this);
  }

  static createWallet(myWallet: Wallet) {
    // fs 모듈로 파일 생성
    // 지갑을 생성하면 주소를 저장
    // 주소 안에는 개인키를 넣음
    const filename = path.join(dir, myWallet.account);
    const filecontent = myWallet.privateKey;
    console.log("createWallet");
    fs.writeFileSync(filename, filecontent);
  }

  static getWalletList(): string[] {
    // readdirSync 폴더를 읽어서 안에 있는 파일 내용을 문자열로 가져옴
    const files: string[] = fs.readdirSync(dir);
    return files;
  }

  // data 폴더 안에 해당하는 지갑 주소를 찾아서 반환
  static getWalletPrivateKey(account: string): string {
    const filename = path.join(dir, account);
    const filecontent = fs.readFileSync(filename);
    return filecontent.toString();
  }

  public getPrivateKey(): string {
    console.log("getPrivateKey()");
    return randomBytes(32).toString("hex");
  }

  public getPublicKey(): string {
    // 개인키로 공개키를 만듬
    const keyPair = ec.keyFromPrivate(this.privateKey);
    return keyPair.getPublic().encode("hex", false);
  }

  public getAccount(): string {
    return `${this.publicKey.slice(26).toString()}`;
  }
}

 

server.ts

간단한 서버 설정 

import express, { Request, Response } from "express";
import { Wallet } from "./index";
import path from "path";
import fs from "fs";

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// 지갑 페이지 접속
app.get("/", (req: Request, res: Response) => {
  const page = fs.readFileSync(
    path.join(__dirname, "/view/index.html"),
    "utf8"
  );
  res.send(page);
});

// 지갑 생성 요청
app.post("/newWallet", (req: Request, res: Response) => {
  res.json(new Wallet());
});

// 지갑 정보 불러오기
app.post("/walletList", (req: Request, res: Response) => {
  const list = Wallet.getWalletList();
  res.json(list);
});

// 지갑 주소 찾기
app.post("/walletSelect", (req: Request, res: Response) => {
  const { account } = req.body;
  const privateKey = Wallet.getWalletPrivateKey(account);
  res.json(new Wallet(privateKey));
});

app.listen(4000, () => {
  console.log("Server On");
});

 

결과

 

create wallet 버튼을 누르면 지갑이 생성되고 리스트에 추가되는 것을 확인 할 수 있습니다. 지갑이 어떻게 생성되는 것 보다는 어떻게 이루어져있는지를 확인하면 좋을 거 같습니다.

 

728x90