Blockchain - 웹서버를 이용한 Mnemonic Wallet 개발하기

프동프동·2022년 6월 9일
1

Blockchain

목록 보기
1/12
post-thumbnail

Mnemonic Wallet

Mnemonic

  • 어떤 것을 기억하는 데 쉽게 하도록 도움을 주는 것, 또는 쉽게 기억되는 성질.
  • 기억하는, 기억을 돕는

주로 지갑생성 시, 지갑 복구 시 사용합니다.
ex) Metamask, Samsung Blockchain Wallet

니모닉이 필요한 이유

암호화폐 지갑은 비대칭 키 암호 방식을 사용합니다. 이때 공개 키와 비밀키가 사용이되는데 이러한 복잡한 이야기를 알 필요 없이 사람이 쓰기 편하게 만들어 진 것이 바로 니모닉(mnemonic) 입니다.
비밀 키 암호화 알고리즘 보안 성능은 2^256(2의 256승)입니다. 사람이 사용하기에 엄청나게 큰 수이기 때문에 16진수 64글자로 바꿉니다.

니모닉은 순서대로 맞춰진 단어가 암호화된 보안 비밀번호 방식으로 문자 배열을 알지 못하면 아무도 지갑을 열 수 없습니다.

니모닉은 12개 또는 24개의 랜덤한 영어 단어로 이루어져 있습니다.

니모닉 wallet

니모닉 wallet은 니모닉을 사용하여 비밀 키 관리를 용이하게 해주는 암호화폐 지갑입니다.
사실 암호화폐 지갑에는 돈이 들어있지 않습니다. 암호화폐 지갑은 키를 관리하는 키 매니지먼트 시스템이라고 할 수 있습니다.
예를 들어, 내 계정에 있는 이더리움을 내것이라고 증명할 수 있는 비밀 키를 지갑이 관리한다고 보면 됩니다. 하지만 사람은 비밀 키를 잘 잃어버릴 수 있어서 비밀 키를 복구할 때 니모닉을 사용하게 됩니다.
즉, 암호화폐 지갑은 비밀 키가 핵심인데, 니모닉 wallet는 비밀 키 관리를 용이하게 해주고, 모든 비밀 키는 니모닉으로 괸리하는 지갑입니다.

니모닉 코드와 시드 생성 9단계

니모닉 코드는 BIP-39(Bitcoin Improvement Proposal 39)에 정의되어 있습니다.

"개인 키의 안전한 백업과 검색를 위한 인코딩 방법은 여러 가지다. 현재 가장 선호하는 방법은 단어 시퀀스를 이용하는 것으로, 올바른 순서로 단어 시퀀스를 입력하면 개인 키를 다시 만들 수 있다. 이것을 니모닉(mnemonic)이라고 부르며 BIP39에서 표준화되었다."

  1. 암호학적으로 랜덤한 128~256 bits의 시퀀스 S를 만든다.
  2. S의 SHA-256 해시값 중에서 앞(왼쪽)에서 S의 길이 / 32비트만큼을 체크섬으로 만든다.
  3. 2번에서 만든 체크섬을 S의 끝에 추가한다.
  4. 3번에서 만든 시퀀스와 체크섬의 연결을 11 bits 단위로 자른다.
  5. 각 각의 11비트를 2048(2^11)개의 미리 정의된 단어로 치환한다.
  6. 단어 시퀀스로부터 순서를 유지하면서 니모닉 코드를 생성한다.


해시넷-BIP39

Mnemonic wallet 개발하기

사전 준비

APT 테스트를 하기 위한 Postman
개발 환경을 위한 node.js

1. Project 폴더 생성 후 "npm init" 명령어로 환경 구축

> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (mnemonic_wallet) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/fdongfdong/Mnemonic_wallet/package.json:

{
  "name": "mnemonic_wallet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 

2. 필요한 모듈 설치

npm install nodemon	// 코드 변화가 감지될 때마다 자동으로 서버를 재시작해줌
npm install express	//Node.js를 이용하여 웹 서비스를 쉽게 개발할 수 있는 프레임워크
npm install cors	// cors 미들웨어 
npm install cookie-parser	// 요청된 쿠키를 쉽게 추출할 수 있도록 도와주는 미들웨어
npm install eth-lightwallet // 랜덤한 니모닉 코드를 생성, 니모닉을 시드로 키스토어 생성하기 위함
npm install morgan	// 로그 관리를 위한 미들웨어
npm install debug // 디버깅 모듈

3. 디렉터리 구성


1) routes 폴더를 생성하고 하위에 wallet.js 파일을 생성해주세요
2) Mnemonic_wallet 폴더 하위에 app.js 파일을 생성해주세요
3) pakage.json 파일을 수정해주세요

{
  "name": "mnemonic_wallet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon ./app.js"		//	추가 
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "debug": "^4.3.4",
    "eth-lightwallet": "^4.0.0",
    "express": "^4.18.1",
    "morgan": "^1.10.0",
    "nodemon": "^2.0.16"
  }
}

코드 작성

1. app.js 파일에 서버 구성

const express = require('express');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const cors = require('cors');

const walletRouter = require('./routes/wallet');

const app = express();
const port = 3000;

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(
  cors({
    origin: ['http://localhost:3000'],
    methods: ['GET', 'POST'],
    credentials: true
  })
);

app.get('/', function(req, res, next) {
  res.status(200).send({"message": "Mnemonic server is running..."});
});
app.use('/wallet', walletRouter);

// catch 404 and forward to error handler
app.use((req, res, next) => {
  const err = new Error('Not Found');
  err['status'] = 404;
  next(err);
});

// error handler
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.json({
      errors: {
          message: err.message,
      },
  });
});

app.listen(port, () => {
  console.log(`
  🛡️  Server listening on port: ${port} 🛡️
  http://localhost:${port}
  `);
});

module.exports = app;

2. Postman으로 서버 테스트하기

1) Mnemonic_wallet 폴더 경로에서 터미널창으로 서버 실행합니다.

> npm run start

> mnemonic_wallet@1.0.0 start
> nodemon ./app.js

[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./app.js`

  ################################################
  🛡️  Server listening on port: 3000 🛡️
  http://localhost:3000
  ################################################

2) Postman 실행 후 get 메서드를 사용하여 서버 연결을 확인합니다.

서버 연결이 성공하면 Status code 200과 'Mnemonic server is running...'라는 문자열이 전달되며 서버에서도 확인할 수 있습니다.

> mnemonic_wallet@1.0.0 start
> nodemon ./app.js

[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./app.js`

  ################################################
  🛡️  Server listening on port: 3000 🛡️
  http://localhost:3000
  ################################################
  
GET /wallet/newWallet 404 15.599 ms - 34
[nodemon] restarting due to changes...
[nodemon] starting `node ./app.js`

  ################################################
  🛡️  Server listening on port: 3000 🛡️
  http://localhost:3000
  ################################################
  
GET /newWallet 404 16.715 ms - 34
GET / 200 1.498 ms - 43
GET / 200 0.745 ms - 43

Status code나 위 문자열이 확인되지 않으면 소스코드를 확인하세요

3. newMnemonic API 만들기

lightwallet 모듈을 이용해 랜덤한 니모닉 코드를 얻습니다.

1) wallet.js 파일을 수정

const express = require('express');
const router = express.Router();
const lightwallet = require('eth-lightwallet');
const fs = require('fs');

// lightwallet 모듈을 이용해 랜덤한 니모닉 코드를 얻습니다.
router.post('/newMnemonic', async (req, res) => {
  let mnemonic;
  try {
    mnemonic = lightwallet.keystore.generateRandomSeed();
    res.json({ mnemonic });
  } catch (err) {
    console.log(err);
  }
});

2) 수정 후 Postman을 통해 랜덤한 니모닉 코드를 얻습니다.

Method: Post
연결할 엔드포인트: http://localhost:3000/wallet/newMnemonic

3) Postman -> [Send] 버튼을 누를 때 마다 새로운 니모닉 코드를 얻는 것을 확인할 수 있습니다.

4. newWallet API 만들기

앞서 발행한 니모닉 코드와 패스워드를 이용해 keystore와 address를 생성합니다.

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

  try {
    lightwallet.keystore.createVault(	// eth-lightwallet 모듈을 이용해 키스토어를 생성합니다.
      {
        password: password,
        seedPhrase: mnemonic,
        hdPathString: "m/0'/0'/0'",
      },
      function (err, ks) {
        ks.keyFromPassword(password, function (err, pwDerivedKey) {
          ks.generateNewAddress(pwDerivedKey, 1);

          let address = ks.getAddresses().toString();
          let keystore = ks.serialize();

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

lightwallet.keystore.createVault()
해당 함수를 통해 키스토어를 만듭니다.
첫번째 인자에는 password, seedPhrase, hdPathString이 들어갑니다.
password: 솔트값
seedPhrase: 니모닉 코드
hdPathString: HD지갑경로

두번째 인자에는 키스토어를 인자로 사용하는 함수가 들어갑니다.

5. Postman을 이용해 keystore와 address의 응답 API 테스트

Method: Post
연결할 엔드포인트: http://localhost:3000/wallet/newWallet
Body: 앞에서 얻은 니모닉 코드, 임의의 비밀번호

1) 성공 메시지가 반환됩니다.
2) Mnemonic_wallet 경로에 wallet.json 파일이 생성되며 생성된 파일에는 keystore 값과 address가 출력됩니다.

{"encSeed":{"encStr":"vcSN11diz1Mm2lXj7kLBSoZAXre3xlH1HLh/Mktmnbv/wTD+M1vre7LCflF9uwa7cZ2o4oelR+VRFAFgmVQL0YCId4DcwtLMDWo9iVH+E3vqAKJdsQAEoCLg7aw/hnob7XDq1pbCNkWtgZlLiN3ShZ4zLIowPAkava/pYCugp0A4aQ9M+ikyDA==","nonce":"AEbog6aC8rbusonECdFzzfz3/509bp9E"},"encHdRootPriv":{"encStr":"t7jEuGDLci0cUHJCXTVw/LqEa5kaTX0Ts2MEdQPiSsnJgdHtCLHyllb+2B/1nvTGUdh5dSva5lx6C7sN+S7N6h32BKfKbBT7/U5ZLMGblN99nNRWQztiyXHruNJjG61wPEqmllqbjIjycMvoDGlXGoGq/J2tp9Htl88voQ2PWw==","nonce":"6OkWgOorKLhwdJQin30z4iRyqLAwdhq/"},"addresses":["3c3782b0af5b9b16a72c28f3571762beb90c7347"],"encPrivKeys":{"3c3782b0af5b9b16a72c28f3571762beb90c7347":{"key":"ptShXbukTfJMepvcX5TEUVGvnstsjGr8NJTFblr2Suq9CS+n9y5Dmvey+0hLdL52","nonce":"8Skljo0HAKeuBgTs29k+7xxkHI9piMdX"}},"hdPathString":"m/0'/0'/0'","salt":"mLRaFD+NeG0K1m/KpVCFq7PkTkBNWH8qk6BVpGNSs98=","hdIndex":1,"version":3}

회고

메타마스크나 삼성 스마트폰으로 지갑을 생성하거나 잃어버린 지갑을 찾을 때 니모닉 코드를 사용하여 매번 찾아왔었다. 단순히 나열되어 있는 단어들이 무슨 암호화되어 있다는 건지 미처 검색해볼 생각도 하지 못했다 하지만 이번 실습을 통해 그러한 단어들의 중요성 및 보안성에 대해 자세히 알게되는 시간이었다.

지갑에 사용되는 각각의 모듈의 사용법에 대해 알게되었다.
또한 각종 코인지갑의 원리에 대해 대략적으로 나마 알게되었다.

추후 다양한 기능이 들어간 지갑을 개발해보고 싶다는 생각을 하였다.

GitHub

FdongFdong Github

profile
좋은 개발자가 되고싶은

0개의 댓글