즐코

HTTP 기본 인증 / 지갑, 트랜잭션 구현 (1) 본문

BlockChain

HTTP 기본 인증 / 지갑, 트랜잭션 구현 (1)

YJLEE_KR 2022. 6. 19. 11:24

 

1. HTTP 기본 인증 - Basic

서버는 사용자를 식별하여 작업이나 리소스에 접근할 권한을 결정한다.

보통은 사용자 이름과 비밀번호를 입력해서 인증하는데, HTTP는 uri로 자체적인 인증 관련 기능을 제공한다.

밑의 uri 구조에서 auth과 관련된 부분이 있는데, 사용자명:비밀번호를 입력하여 특정 영역의 접근 권한을 얻을 수 있다.

현재 chrome 브라우저에선 이런 Basic 이 보안에 좋지않아 사용하지 못한다고하지만, 블록체인 네트워크의 인증에선 아직까지도 이 basic 방식이 쓰인다고 한다.

 

uri 구조

HTTP 기본 인증 흐름 (Basic)

 

1. 클라이언트->서버 : GET 요청으로 url을 던진다. (인증 정보가 없는 상태)

 

2. 서버->클라이언트 : 사용자에게 아이디와 비밀번호를 제공하라는 의미로 401 에러 응답과 함께 WWW-Authenticate 헤더 상에 인증이 필요한 해당 영역을 설명해준다. 이 때 realm은 요청 받은 문서 집합의 정보라고 한다..

WWW-Authenticate : Basic realm="localhost" 

 

3. 클라이언트->서버 : 위의 요청을 받아서 사용자에게 아이디와 패스워드를 ID:PW 형식으로 (콜론으로 구분) 받아서 이를 base64로 encoding 하여 요청 헤더의 Authorization에 넣고 요청을 보낸다. 

(현재 크롬에선 작동하지 않기때문에 포스트맨이나 썬더클라이언트를 사용해서 확인해본다)

Authorization: Basic eWpsZWVpbmtyOjEyMzQ=

 

4. 서버->클라이언트 : 다시 디코딩해서 인증정보 체크 후 성공하면 200 응답을 보내준다.

 

Basic 인증에 쓰이는 base-64방식은 별도의 키 없이 복호화가 가능하므로 보안에는 취약하다. 따라서, 보통 Bearer Token방식을 쓴다. 이 경우, 헤더와 페이로드 정보를 합쳐서 비밀키로 시그니처를 만들기 때문에 데이터 위변조 방지가 가능하다. 

 

아무튼 위의 과정을 간단하게 코드로 구현해보면 아래와 같다. 

클라이언트가 사용자id:pw를 복호화하여 헤더 authorization 에 실어 보낸 건 req.headers.authorization으로 출력이 가능하다.

이걸 다시 base64로 디코딩하여 ip,pw를 검사해준다. 

app.use((req, res, next) => {
  console.log(req.headers.authorization); // Basic eWpsZWVpbmtyOjEyMzQ=
  
  // const baseAuth: string | undefined = req.headers.authorization;
  const baseAuth: string = (req.headers.authorization || "").split(" ")[1];

  if (baseAuth === "") return res.status(401).send();
  console.log(baseAuth); // eWpsZWVpbmtyOjEyMzQ=
  
  const decodeAuth = Buffer.from(baseAuth, "base64").toString();
  console.log(decodeAuth); // yjleeinkr:1234

  const [userid, userpw] = decodeAuth.split(":");
  if (userid !== "yjleeinkr" || userpw !== "1234") return res.status(401).send();
  console.log(userid, userpw); // yjleeinkr 1234
  next();
});

그래서 이 Basic authorization으로 지갑에 접근할 수 있는 시스템을 만들건데, 지갑/트랜잭션 구조를 다 만들고나서 적용할 예정

 

2. 지갑 만들기

지갑과 트랜잭션을 제공할 서버를 하나 만들어준다. 

지갑의 기본 역할은 총 3가지로 보면 된다. 해당 포스팅은 우선 계정 생성만 정리한다.

 

1/ 계정 생성

2/ 계정 가져오기

3/ 트랜잭션 보내기

계정 생성

1/ 서버 만들어주기 

"/newWallet" POST 요청으로 지갑을 생성해온다. 해당 Wallet 클래스는 아래서 설명한다. 

// src/wallet/server.ts

import express from "express";
import nunjucks from "nunjucks";
import { Wallet } from "./wallet";

const app = express();

app.use(express.json());
app.set("view engine", "html");
nunjucks.configure("views", { express: app, watch: true });

app.get("/", (req, res) => {
  res.render("index");
});

app.post("/newWallet", (req, res) => {
  res.json(new Wallet());
});

app.listen(3005, () => {
  console.log("서버시작 3005");
});

 

화면 그려주기 

// src/views/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://unpkg.com/axios/dist/axios.min.js"></script>
    <title>Document</title>
  </head>
  <body>
    <h1>hello wallet</h1>
    <button id="wallet_btn">지갑생성</button>

    <ul id="wallet_list">
      <li>Coin : hoochuCoin</li>
      <li>
        account :<span class="account"></span>
      </li>
      <li>
        private key :<span class="privateKey"></span>
      </li>
      <li>
        public key :<span class="publicKey"></span>
      </li>
      <li>
        balance :<span class="balance"></span>
      </li>
    </ul>
    <h1>Transaction</h1>
    <form id="transaction_form">
      <ul>
        <li>To: <input id="to" /></li>
        <li>From: <input id="from" /></li>
        <li>Value: <input id="value" /></li>
      </ul>
      <input type="submit" value="전송" />
    </form>
    <script type="text/javascript">
      const walletBtn = document.querySelector("#wallet_btn");
      const walletList = document.querySelector("#wallet_list");
      const privKeySpan = walletList.querySelector(".privateKey");
      const pubKeySpan = walletList.querySelector(".publicKey");
      const acctSpan = walletList.querySelector(".account");
      const balSpan = walletList.querySelector(".balance");
      const txForm = document.querySelector("#transaction_form");

      const createWallet = async () => {
        const response = await axios.post("/newWallet", null);
        console.log(response.data);
        /* 
           account: "ad22af614c1b853cd49114c687808902c954399d"
           balance: 0
           privateKey: "258f645ed8aefebb91084113e1e4c26cc314b147b741c991439bae8284cc9950"
           publicKey: "03fb7a857dc391335723e00c4aad22af614c1b853cd49114c687808902c954399d"
        */
        const { privateKey, publicKey, account, balance } = response.data;
        privKeySpan.innerText = privateKey;
        pubKeySpan.innerText = publicKey;
        acctSpan.innerText = account;
        balSpan.innerText = balance;
      };

      const submitHandler = async (e) => {
        e.preventDefault();
        const { to, from, value } = e.target;
      };

      walletBtn.addEventListener("click", createWallet);
      txForm.addEventListener("submit", submitHandler);
    </script>
  </body>
</html>

 

우선 지갑은 기본적으로 4가지 정보를 제공하고자 한다.

 

- 개인키

- 공개키

- 계정

- 잔고

 

개인키/공개키/계정을 만드는 방법은 이전 포스팅에서 정리하였다. (https://yjleekr.tistory.com/79)

// src/wallet/wallet.ts

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

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

export class Wallet {
  public privateKey: string;
  public publicKey: string;
  public account: string;
  public balance: number;

  constructor() {
    // 순서 매우 중요...
    this.privateKey = this.getPrivateKey();
    this.publicKey = this.getPublicKey();
    this.account = this.getAccount();
    this.balance = 0;
  }

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

  public getPublicKey(): string {
    const keyPair = ec.keyFromPrivate(this.privateKey);
    const publicKey = keyPair.getPublic().encode("hex", true);
    return publicKey;
  }

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

 

TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined

 

위의 코드엔 이미 account 속성이 밑으로 내려와 있지만, 첨에 this.account 가 맨 위쪽에 있어서 this (Wallet) 상에 account 를 만들 때 필요한 publicKey와 privateKey 속성자체가 만들어지지 않은 상태라 위와 같은 에러가 났다. 즉, class 를 만들 때 this를 쓴다면 속성의 순서가 중요하다.

 

지갑 생성을 클릭하면 다음과 같이 지갑이 생성된다!  (잔고부분은 아직 구현되지 않았으니 기본 0이 뜨게끔 되있다.)

 

 

잔고 구현은 미리 설명하자면, account, 즉 내 지갑계정/주소를 블록체인네트워크 상에 보내서 내 계정과 일치하는 모든 트랜잭션 내용을 긁어와서 입출금내역을 비교, 그 차액을 balance에 찍히게끔 할 것이다.

 

트랜잭션은 다음 포스팅에서 이어서 작성한다. 

Comments