mnemonic wallet dev

Enzo·2022년 4월 7일
0

블록체인

목록 보기
19/21

시드

결정적 지갑은 마스터 시드로부터 개인키를 계층적으로 생성한다. 그렇기 때문에 지갑 내의 모든 키를 기억할 필요가 없이 마스터 시드만 기억하면 모든 하위 키들을 재생성하여 지갑 전체를 복원 가능하다.

니모닉이란?

결정적 지갑에서 난수를 12개에서 24개의 영단어로 인코딩한 영단어 그룹이다. 숫자와 문자로 구성된 난수인 시드키는 사용자가 기억하기 어렵지만 니모닉 코드는 사용자가 기억하고 사용하기 쉬운 형태로 구성되게 된다.

니모닉 코드 생성

  1. 128bit 혹은 256bit 길이의 난수를 생성
  2. 생성된 난수를 SHA-256으로 해싱하고, 이 값에서 (시드 키의 길이)/ 32 만큼 떼어내어 체크섬을 만듬
  3. 1에서 만든 난수 뒤에 2에서 만든 체크섬을 붙임
  4. 체크섬을 붙인 난수를 11bit 길이 세그먼트로 나눔
  5. 각 11bit 값을 사전에 정의된 단어 리스트에 매핑

시드 생성

BIP-39의 표준에 의해 키 스트레칭 과정을 거쳐 마스터 시드를 생성하게 된다.

키 스트레칭이란 입력한 패스워드에 대해 특정 해시 함수를 통해 다이제스트를 생성하는 것을 재귀적으로 반복함을 말한다.
BIP-39에서는 키 스트레칭 함수인 PBKDF2를 사용한다.

  1. PBKDF2 함수에 니모닉 코드와 솔트를 입력한다.(이때 솔트는 "Mnemonic"에 임의의 암호문을 붙인 형태)
  2. PBKDF2 함수는 입력받은 니모닉 코드와 솔트로 HMAC-SHA512로 2048번 해싱하여 512bit의 값을 만들게 되고, 이 값이 시드키가 된다.

개발

이번 프로젝트에서는 메타마스크의 니모닉 생성과정을 클론 코딩하여 프론트를 만들고, 니모닉 생성과 니모닉과 비밀번호를 이용한 마스터 시드를 만드는 서버를 개발해보았다.

니모닉 지갑을 만드는 간단한 앱이다.

Client

메타마스크의 니모닉 생성과정과 이를 통한 마스터 시드 발급을 보고 클론코딩하는 방식으로 진행을 하였다.

리액트를 사용하였으며, mui라는 컴포넌트 라이브러리를 사용하여 간편하게 구성하였다.
mui는 공식홈페이지에 들어가면 사용법에 대해 친절하게 설명되어있어 쉽게 사용할 수 있다.
https://mui.com/

컴포넌트 구성

라우터를 통해 APP에서 모든 컴포넌트를 엔드포인트에 따라 바뀔 수 있게 SPA로 구성하였다.

또한 니모닉 코드, 비밀번호, 생성된 주소의 상태를 app에서 관리하고 props로 필요한 컴포넌트에 넘겨주는 방식을 사용하였다.

1-1. APP.jsx

import React, { useState } from "react";
import { Box, Stack } from "@mui/material";
import { Route, Switch } from "react-router-dom";
import CreatePw from "./pages/CreatePw";
import Main from "./pages/Main";
import MakeWallet from "./pages/MakeWallet";
import Mnemonic from "./pages/Mnemonic";
import NewWallet from "./pages/NewWallet";
import ValiMnemonic from "./pages/ValiMnemonic";
import KeyIcon from "@mui/icons-material/Key";

function App() {
  const [password, setPassword] = useState("");
  const [mnemonic, setMnemonic] = useState({});
  const [address, setAddress] = useState("");

  const updateMnemonic = (mnemo) => {
    setMnemonic(mnemo);
  };

  const updatePw = (pw) => {
    setPassword(pw);
  };

  const updateAddress = (add) => {
    setAddress(add);
  };

  return (
    <Switch>
      <Route exact path="/">
        <Main />
      </Route>

      <Stack sx={{ height: "100vh" }}>
        <Stack sx={{ height: 40 }}>
          <Box sx={{ fontSize: 40 }}>
            <KeyIcon color="primary" sx={{ margin: 1 }} />
            BEBMASK
          </Box>
        </Stack>
        <Stack sx={{ flexGrow: 1 }}>
          <Route path="/makewallet">
            <MakeWallet updateMnemonic={updateMnemonic} />
          </Route>
          <Route path="/mnemonic">
            <Mnemonic mnemonic={mnemonic} />
          </Route>
          <Route path="/createpw">
            <CreatePw updatePw={updatePw} password={password} />
          </Route>
          <Route path="/valimnemonic">
            <ValiMnemonic
              mnemonic={mnemonic}
              password={password}
              updateAddress={updateAddress}
            />
          </Route>
          <Route path="/newwallet">
            <NewWallet address={address} />
          </Route>
        </Stack>
      </Stack>
    </Switch>
  );
}

export default App;

GIF설명

해당 개발은 리액트가 주된 내용이 아니므로 코드보다는 GIF를 통해 구현 내용을 설명하고자 한다.

처음 페이지를 들어가게 되면 시작하기 버튼이 나오고 버튼을 누르면 지갑을 새로 생성할 것인지 니모닉을 통해 지갑을 가져올 것인지를 묻게 된다.

새로 생성하기를 누르게 되면 axios를 통해 서버에 니모닉 생성 api를 실행하고 생성된 니모닉 코드를 받아 렌더링 해주게 된다.

다음 페이지는 암호 생성으로 새로운 비밀번호를 입력하고 한번 더 확인하는 과정을 거치게 된다. 이때 비밀번호를 다르게 입력하면 다음으로 넘어가는 버튼이 활성화 되지 않기 때문에 두개 다 정확한 암호를 입력해야 한다.

마지막으로 아까 생성한 니모닉 코드를 입력하여 키스토어 파일을 생성하고 주소를 부여받게 되는데 이때도 역시 생성한 니모닉코드와 다른 값을 입력할 경우 다음으로 넘어갈 수 없게 설계하였다.

Server

서버는 node.js의 express를 사용하였고, eth-lightwallet모듈을 이용하여 니모닉 코드 발행 및 시드생성을 하였다.

니모닉 코드 생성 함수

const lightwallet = require("eth-lightwallet");

router.post("/newMnemonic", async (req, res) => {
  let mnemonic;
  try {
    mnemonic = lightwallet.keystore.generateRandomSeed();
    res.json({ mnemonic });
  } catch (err) {
    console.log(err);
  }
});

클라이언트에서 요청을 받을 시에 eth-lightwallet모듈에 keystore.generateRandomSeed()함수를 통해 니모닉 코드를 생성하고 이를 응답으로 보내주는 코드이다.
generateRandomSeed()함수는 니모닉 코드를 발행하기 위해 필요한 일련의 과정들을 처리해주는 함수이다.
영단어들을 어떻게 생성하는지 의문이 들어 시간이 생기면 한번 연구해보려고 한다.

키스토어 파일 생성과 주소 생성 함수

const lightwallet = require("eth-lightwallet");

router.post("/newWallet", async (req, res) => {
  let password = req.body.password;
  let mnemonic = req.body.mnemonic;

  try {
    lightwallet.keystore.createVault(
      {
        password: password,
        seedPhrase: mnemonic,
        hdPathString: "m/0'/0'/0'",
      },
      function (err, ks) {
        ks.keyFromPassword(password, function (err, pwDerivedKey) {
          ks.generateNewAddress(pwDerivedKey, 1);

          let addresses = ks.getAddresses();
          let keystore = ks.serialize();

          fs.writeFile("wallet.json", keystore, function (err, data) {
            if (err) {
              res.json({ code: 999, message: "실패" });
            } else {
              res.json({ address: addresses[0], message: "성공" });
            }
          });
        });
      }
    );
  } catch (exception) {
    console.log("NewWallet ==>>>> " + exception);
  }
});

클라이언트에서 발급받은 니모닉 코드와 본인이 생성한 비밀번호를 담아 post 요청을 하게 되고 이를 받게되면 각 변수에 저장해두고 lightwallet.keystore.createVault()함수를 실행한다. 이 함수는 콜백 함수가 다른 콜백 함수를 부르는 구조로 되어있어 처음에는 이해하기 난해하였다.

createVault함수는 객체형태의 옵션 니모닉코드, 솔트값, HD지갑경로를 인자로 받아 keystore를 생성하고 콜백함수를 실행한다.

keyFromPassword에서 우리가 입력한 password를 받아서 key-pair를 생성하고 콜백함수를 실행한다.

generateNewAddress에서 keyFromPassword로 생성한 키를 통해 주소를 발행하게 되고 배열형태로 addresses 변수안에 담기게 된다.

keystore파일은 로컬에 저장하기 위해 serialize를 통해 json형식으로 직렬화 시켜준다.

이후 fs모듈로 키스토어 파일을 저장하고 클라이언트에 주소를 응답으로 전달하면 된다.

전체 코드

전체코드는 깃헙 주소를 첨부한다.
https://github.com/sujin96/mnemonic-wallet

회고

좋았던 점

리액트를 다루는 법을 많이 잊어버린 것 같았는데 이번 기회를 통해 구조잡는 것부터 모든 것을 혼자 다 해보게 되면서 좀 더 자신감을 얻었던 것 같다. 서버 쪽에서는 이미 누군가 만들어놓은 모듈을 가져다 쓰게 되면서 쉽게 니모닉 지갑 개발을 마칠 수 있었다.

부족한 점

리액트를 다루면서 유효성 체크에서 상당히 시간을 많이 잡아먹었다. 여전히 완벽한 답을 찾지 못하였고 그럴듯하게만 만들어 놓은 상태이다. 이를 좀 더 유연하게 구현할 수 있는 연습이 필요할 것 같다. 그리고 모듈을 가져다쓰면서 그 모듈에 공식문서를 제대로 읽지않고 기능구현에만 매달려서 실제 함수의 동작을 이해하는데 시간이 많이 걸렸다. 기능 구현보다는 이해를 목적으로 두고 천천히 공식문서를 읽는 습관을 들여야할 것 같다.

개선점

간단한 기능 구현과 프론트엔드만 붙여놓은 것이기 때문에 좀 더 실제 지갑과 가까운 모양새를 갖출 수 있도록 개선하고 싶다.

profile
고통수집가

0개의 댓글