지갑 구성 및 지갑의 역할

  • 기본적인 역할로 암호화폐를 안전하게 보관할 수 있고 암호화폐를 전달하거나 받을 수 있다.
  • 자산의 소유권을 암호화 방식으로 증명 지갑의 개인키를 사용해서 거래의 서명을 검증하고 본인의 자산을 관리
  • 본인의 암호화폐의 보유량 확인
  • 지갑의 거래내역 확인
  • 복구 문구를 사용해서 지갑 복원 (복구 문구를 가지고 특정 알고리즘을 거쳐서 개인키를 추출한다.)

금융

  • 금융쪽에서는 장부를 가지고 모든 거래내역을 기록하는 방식
  • 은행이라는 금융 기관은 장부의 거래내역을 의존할수 밖에 없는 중앙 집권 방식

분산원장

  • 분산원장은 금융과 반대로 거래에 참여하는 모든 참여자가 장부를 가지고 있고
  • 거래가 발생했을 때 해당 거래내역을 각자의 장부에 기록하는 방식
  • 중앙에서 가지고 있는게 아니라 모두 장부를 가지고 관리한다.

신원 인증 방식 분산 원장 기술로 개인키,공개키,서명(영수증)

암호화 방식

  • 대칭형:암호화 복호화 할 때 사용하는 키가 동일한 키 1개를 사용한다.
    암호화 한 사람과 수신하는 사람이 같은키를 사용한다.

  • 비대칭형:사용하는 키와 복호화 할 때 사용하는 키가 다른것. 키 2개
    다른 사람에게 공개되어서는 절대 안되는 키(비밀 키) 비밀키를 토대로 만든 공개키 한쌍

대칭형

  • 나 -> 원본 -> 대칭키로 원본 암호화 -> 암호문
  • 너 -> 암호문 -> 대칭키로 복호화 -> 원본
  • 대칭형의 형태는 암호문을 대칭키를 가지고 있는 사람은 누구나 전달이 가능하다.

비대칭형

  • 비대칭형의 경우는 암호화 할 때 공개키로 암호화
  • 나 -> 원본 -> 너의 공개키로 암호화 -> 암호문
  • 너 -> 암호 -> 너의 비밀키로 복호화 -> 원본

지갑의 공개키, 비밀키

  • 공개키:사람들에게 공개할 키 정보를 암호화할 때 사용
  • 비밀키:본인만 알고있어야 하는 키 암호문을 복호화할 때 사용

비밀키와 공개키의 역할

  • 비밀키 소유자가 비밀키로 데이터를 암호화 하고 공개키를 같이 전달한다.

    공개키와 암호문을 받은 사람은 공개키를 이용해서 검증이 가능하고

    이렇게 위험성이 있어 보이는 방법을 사용하는 이유가 데이터의 보호 목적보다는 공개키 데이터를 제공한 사람의 신원을 보장해주기 위해서

    암호화된 데이터가 공개키로 복호화 된다는 것이 공개키와 쌍으로 이루는 비밀키에 의해서
    암호화가 되었다는 뜻 데이터의 제공자가 맞는지 이 사람이 이 서명을 한 사람인지 검증

    이 방법이 공인 인증 체계의 기본 바탕의 전자 서명

개인키

  • 실제 블록체인 네트워크에 개인키를 생성하는 방식은
    2진수로 랜덤값을 64자리의 16진수 값으로 만든 것이 개인키
  • 개인키가 겹칠수는 있는데 불가능한건 아닌데 확률이 천문학적으로 낮아서
  • 나올 수 있는 확률이 개인키의 경우의 수는 2^256개의 개인키

순서

  • 1.암호화 하고 싶은 데이터를 SHA256 방식으로 해싱하고
  • 2.개인키를사용해서 해시값으로 서명을 생성
  • 3.서명이랑 공개키는 제 3자에게 전달이 되고
  • 4.받은 제 3자는 공개키를 이용해서 서명을 검증

개인키 공개키 한쌍 키를 생성할 때 ECC타원 곡선을 사용

  • 타원 곡선 수학을 기반으로 디지털 서명을 보호하기 위해 사용하는 암호화 기술

  • 공개키와 개인키의 개념이 있고, 공개키는 암호화와 검증에 사용, 개인키는 복호화 서명을만들 때 사용된다.

  • 디지털 서명은 본인이 개인키로 메시지를 서명을 할 수 있고 공개키는 받은사람이 누구나 서명을 확인해 볼수있다.

  • 쉽게 말해서 암호화 및 해독에 사용

  • 다른사람의 공개키로 암호화해서 전달을 하면 그 사람의 개인키로만 복호화가 가능하고
    당사자끼리의 데이터 기밀성 보장

  • 디지털 서명을 본인의 개인키로 생성하고 다른 사람한테 전달해서 공개키와 함께 서명을 검증할 수 있는 무결성 보장

  • y^2=x^3+ax+b

  • 기준점이 있고 기준점에서 개인키를 가지고 공개키를 구하는데 기준점과 공개키로 개인키를 역산하기 힘들다. 그냥 그렇다.

테스트 코드로 공개키,개인키,서명 구현하기


// 지갑 구성
// 개인키 ,공개키, 서명
//  지갑주소 /계정 만들기

// 개인키와 공개키와 서명을 이용한 신원 인증 방식은 분산원장이라는 이해가 필요

// 분산원장 : 장부를 모두가 관리하는것. 데이터의 합의 기술

// crypto.elliptic,crypto-js

// npm i -D crypto
// npm i -D elliptic @types/elliptic
// npm i -D crypto-js @types/crypto-js

import { randomBytes } from "crypto";
import elliptic from "elliptic";
import { SHA256 } from "crypto-js";

const ec = new elliptic.ec("secp256k1");

// secp256k1은 비트코인과 이더리움에서 사용되는 알고리즘
// 키 생성 및 디지털 서명 (너가 이걸 한게 맞는지 검증하기 위해 영수증),주소 생성
// 타원 곡선의 별명

// 전달하는 사람과 받는사람등 모든 사람들은 공통적으로 타원곡선의 한점을 알고있어야 하는데
// 이 점을 타원곡선의 기준점 G라고 부른다.

// 타원곡선의 기준점 좌표가 뭐냐에 따라 타원곡선에 별명을 붙여준다.
// 비트코인과 이더리움에서 사용되는 타원곡선 별명은 secp256k1

// y^2=x^2 + ax + b
// 이 방정식에 만족하는 곡선
// a가 0, b가 7 기준점 G는 x및 y좌표를 16진수로 표현한 것
// y^2=x^3+7

// A가 트랜잭션 만들고 서명을 만들고(영수증)
// 본인들 볼펜이 하나씩 있어서(개인키)
// 볼펜 준비 타원 곡선의 지정 범위내의 값으로 정한다(1~ n-1 까지의 정수 범위)(범위내의 정수).
// secp256k1 의 n은 1.157920892373162e+77값(과학 표기법)

// 위 수를 16진수로 변화한 값에 -1한 값이 n-1임
//  이 범위중에 하나를 사용해야함

// 개인키를 하나 임시로 지정을 해보면
// 전자 서명을 만들 때
// 2개의 서명이 필요하다.

// 서명 r:트랜잭션을 보낼 때 개인키 처럼 정수를 뽑아서 이 값을 k라고 한다. 서명 r=k*G(기준점)
// 서명 s: s=k^-1 ×(z+r×개인키) mod n k를 역수 계산 z+r* 게인키 나머지 n
// k=서명 r을 만들 때 뽑은 랜덤값
// z=만든 트랜잭션 정보
// r=서명 r
// 개인키 =범윙에서 지정하고 본인만 알고있는 개인키
// mod n=n으로 나눈 나머지

// 중요한건 서명 s를 만드는데 개인키를 사용했다는 개념

// w 동일한 서명을 만들지 않기 위해서 임의의 값을 추가 nonce값이라 보면됨
// U1=z*w mod n
// U2=r * w mod n
//  U1 *G+U2 공개키 값을 구해서 서명 r과 같은지 비교해서 검증

// 이거를 전부 이해할 필요가 없고
// 중요한 중심만 이해를 하면된다. 개인키로 서명을 만든거고
// 이 서명을 가지고 공개키를 통해서 서명을 검증 할 수 있다.

// 데이터 전송
// 1. 트랜잭션 생성
// 2.개인키 생성
// 3.서명 r, 서명 s생성

// 데이터 수신
//  U1 *G+U2 공개키 이 식으로 값을 구해서 서명 r과 비교(검증)

describe("지갑 만들기", () => {
  let priveKey: string;
  let publicKey: string;
  let signature: elliptic.ec.Signature;

  it("개인키 생성", () => {
    // 2진수의 랜덤값을 만들자
    // 16진수로 나타냄
    priveKey = randomBytes(32).toString("hex");

    console.log("개인키:" + priveKey);
    // 개인키의 길이 64
    console.log("개인키의 길이:" + priveKey.length);
  });

  // false 문자열 압축 여부 중요하지 않음
  // 개인키로 공개키를 생성
  it("공개키 생성", () => {
    const keyPair = ec.keyFromPrivate(priveKey);
    publicKey = keyPair.getPublic().encode("hex", false);
    console.log("공개키:", publicKey);
    // 공개키는 130자의 문자열
    console.log("공개키의 길이:", publicKey.length);
  });

  it("서명 만들기", () => {
    const keyPair = ec.keyFromPrivate(priveKey);
    // 임시 트랜잭션 내용
    const hash = SHA256("transaction data").toString();
    // sign 서명 생성
    signature = keyPair.sign(hash, "hex");
    console.log("서명:", signature);
  });
  // r 서명,s서명
  // BN=BigNumber 무척 큰 number타입

  // negative: 0 =>양수

  // words:r 서명이나 s서명의 값을 32비트 정수 배열로 표시한 값
  // length:배열의 길이

  // 서명: Signature {
  //   r: BN {
  //     negative: 0,
  //     words: [
  //       43348871, 20617926,
  //       52805612, 40572145,
  //       42203394, 50772247,
  //       60751715, 62974710,
  //       58451527,   423078
  //     ],
  //     length: 10,
  //     red: null
  //   },
  //   s: BN {
  //     negative: 0,
  //     words: [
  //        8906980,  2268512, 54377430, 40918456,
  //       64234097, 46754793,  2550136,  7621258,
  //       65804558,  1847326,        0,        0,
  //              0,        0,        0,        0,
  //              0,        0,        0,        0,
  //              0,        0,        0,        0,
  //              0,        0,        0,        0,
  //              0,        0
  //     ],
  //     length: 10,
  //     red: null
  //   },
  //   recoveryParam: 1
  // }
  it("검증하기", () => {
    const hash = SHA256("transaction data").toString();
    const verify = ec.verify(
      hash,
      signature,
      ec.keyFromPublic(publicKey, "hex")
    );

    console.log("검증됨?", verify);
  });
  // 지갑 주소 생성
  it("지갑 주소", () => {
    // 계정을 만드는 방법은 만든 공개키의 값에서 26개의 문자열을 앞에서 잘라서
    // 40자리 만큼을 남겨서 주소로 사용한다.
    // 불필요한 부분 제거하고 앞에 0x

    // 가독성 때문에.... 주소의 앞에는 0x붙이는 것이 일반적 (16진수)
    const address = publicKey.slice(26).toString();
    console.log("지갑주소:", `0x${address}`);
  });
});
  • keyFromPrivate
    개인키를 파라미터로 주어 키 쌍(key pair)를 생성한다.
  • keyPair.getPublic().encode("hex", false);
    그 후 키 쌍으로 공개키를 생성하고 이를 16진수로 표현한다.
  • keyPair.sign(hash, "hex");
    키 쌍을 이용해 특정 hash를 파라미터로 주어 서명도 생성한다.
 const verify = ec.verify(
      hash,
      signature,
      ec.keyFromPublic(publicKey, "hex")
    );
  • 개인키에서 생성된 공개키로 서명을 복호화하여 유효한 서명인지 판별한다.

wallet 구현하기


import { randomBytes } from "crypto";
import elliptic from "elliptic";
import { SHA256 } from "crypto-js";
import fs from "fs";
import path from "path";

// 지갑 클래스 만들고 페이지에서 지갑 한 번 확인 해보기

// elliptic인스턴스 생성

const ec = new elliptic.ec("secp256k1");

// 지갑정보 저장 기본 경로
const dir = path.join(__dirname, "..", "..", "data");
// 지갑 클래스 정의
class Wallet {
  public account: string;
  public privateKey: string;
  public publicKey: string;
  public balance: number;

  constructor(privateKey: string = "") {
    // 생성 단계에서 개인키 값이 없으면 만들어 넣자
    this.privateKey = privateKey || this.getPrivateKey();
    this.publicKey = this.getPublicKey();
    this.account = this.getAccount();
    this.balance = 0;
    if (privateKey === "") {
      Wallet.createWallet(this);
    }
  }
  public getPrivateKey(): string {
    return randomBytes(32).toString("hex");
  }

  public getPublicKey(): string {
    // 개인키로 공개키를 만들자
    const keyPair = ec.keyFromPrivate(this.privateKey);
    return keyPair.getPublic().encode("hex", false);
  }
  public getAccount(): string {
    return `0x${this.publicKey.slice(26).toString()}`;
  }

  static createWallet(mywallet: Wallet) {
    // fs 모듈로 파일 생성
    // 지갑을 생성하면 주소를 저장할 것
    const filename = path.join(dir, mywallet.account);
    const fileContent = mywallet.privateKey;
    fs.writeFileSync(filename, fileContent);
  }

  // data폴더안에 있는 지갑주소를 찾아서 반환
  static getWalletPrivateKey(account: string): string {
    const filename = path.join(dir, account);
    const fileContent = fs.readFileSync(filename);
    return fileContent.toString();
  }
  static getWalletList(): string[] {
    // readFileSync 폴더를 읽어서 안에있는 파일 내용을 문자열로 가져온다.
    const files: string[] = fs.readdirSync(dir);
    return files;
  }
}

export default Wallet;

  • account 경우에는 계정을 만드는 방법은 만든 공개키의 값에서 26개의 문자열을 앞에서 잘라서 40자리 만큼을 남겨서 주소로 사용한다.

server

// 지갑 서버
import express from "express";
import Wallet from "@coreWallet/wallet/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, res) => {
  const page = fs.readFileSync(
    path.join(__dirname, "..", "wallet", "view.html"),
    "utf-8"
  );
  res.send(page);
});

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

// 지갑들 불러오기
app.post("/walletList", (req, res) => {
  const list = Wallet.getWalletList();

  res.json(list);
});

// 해당 지갑 주소로 지갑 찾기
app.post("/walletSelect", (req, res) => {
  const { account } = req.body;
  console.log(account);
  const privateKey = Wallet.getWalletPrivateKey(account);
  res.json(new Wallet(privateKey));
});

app.listen(4000, () => {
  console.log("gogo");
});
  • getWalletPrivateKey 에서 account를 통해 해당하는 privatekey를 new로 동적할당을 했지만, 생성자에서 매개변수가 있다면 새로운 월렛을 만들지 않는 조건문을 걸었기 때문에 새로운 파일은 생성되진 않지만 개인키는 랜덤 바이트이므로 새로운 값이 할당된다.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.5.0/axios.min.js"
      integrity="sha512-aoTNnqZcT8B4AmeCFmiSnDlc4Nj/KPaZyB5G7JnOnUEkdNpCZs1LCankiYi01sLTyWy+m2P+W4XM+BuQ3Q4/Dg=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>
  </head>
  <body>
    <h1>지갑 듀토리얼</h1>

    <button id="walletBtn">지갑 생성</button>
    <ul id="walletList">
      <li>비트코인 지갑</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>생성된 지갑 목록</h1>
    <button id="walletListBtn">지갑 목록 조회</button>
    <div>
      <ul id="walletListData">
        지갑 조회 눌렁
      </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");
      console.log(resp);
      render(resp);
    };
    const getView = async (account) => {
      console.log(account);
      const { data: resp } = await axios.post("/walletSelect", { account });
      render(resp);
    };
    walletListBtn.onclick = async () => {
      const { data: resp } = await axios.post("/walletList");
      const list = resp.map((account) => {
        return `<litoken interpolation">${account}')"">${account} </li>`;
      });
      walletListData.innerHTML = list;
    };
  </script>
</html>

  • 주의해야 할 점은 <li">${account} </li> 이부분에서 onclick 부분을 double quotes로 감싸야한다는 것이다. account는 파라미터로 전달될 때 string으로 전달되어야 하므로 '${account}' => single quote로 감싸야 한다는 점이다.

느낀점

메타마스크에서 사용되는 월렛이 이런 원리로 생성되고 인증된다는 점을 처음 알았다. 특히 비대칭키는 기사공부를 할 때 많이 봐왔지만 실제로 사용한적은 처음이다. 비록 라이브러리이지만, 개인키로 키쌍과 공개키를 생성하고 이런 공개키로 메시지 데이터를 암호화 시키고 개인키를 자신만이 복호화가 가능하고, 다른 사람들은 공개키와 서명으로 유효한지를 판별하는 과정은 매우 흥미로웠다.

profile
개발당시에 직면한 이슈를 정리하는 곳

0개의 댓글

Powered by GraphCDN, the GraphQL CDN