본문 바로가기

블록체인_9기/⛓ BlockChain

59강_230912_Blockchain(블록체인 지갑, 디지털 서명, ECC를 활용한 지갑 생성)

728x90

 

 

 


블록체인 지갑(Blockchain Wallet)

블록체인 지갑은 암호화폐를 저장하고 관리하기 위한 디지털 도구이다. 정보를 기록하는 탈중앙화 시스템인 블록체인 기술을 기반으로 한다. 기존 지갑과 달리 암호화폐 지갑은 화폐 자체를 보관하지 않으며, 암호화폐 토큰이 장부와 같은 역할을 하는 블록체인에 남게된다. 블록체인 지갑을 통해 암호화폐 사용자는 블록체인에 저장된 암호화폐 코인과 상호작용하고 거래를 완료할 수 있다.

 


지갑의 구성 및 역할

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

 


금융과 분산원장의 차이점

금융의 경우, 장부를 가지고 장부에 모든 거래내역을 기록하는 방식을 사용한다. 중앙집권 방식으로 금융을 관리하는 기관인 은행의 거래내역에 의존할 수 밖에 없다.  

분산원장의 경우, 금융과 반대로 거래에 참여하는 모든 참여자가 장부를 가진다. 거래가 발생했을 때, 해당 거래내역을 각자의 장부에 기록하는 방식이다. 중앙이 아닌 모두가 장부를 갖고 관리하는 탈중앙화 방식으로 위조를 방지할 수 있다.

 


신원 인증 방식 : 개인키, 공개키, 서명(영수증)

개인키

  • 개인키와 공개키는 쌍(Pair)의 관계이며 개인키는 본인만 알고있어야 하는 키이다.
  • 암호문을 복호화할 때 사용한다.
  • 실제 블록체인 네트워크에 개인키를 생성하는 방식은 2진수로 랜덤 값을 64자리의 16진수 값으로 만든것이다.
  • 개인키는 겹칠 수 있으나, 그 확률이 천문학적으로 낮기 때문에 겹칠 수 없다고 보고있다.
    • 개인키로 만들 수 있는 경우의 수는 2^256개로, 지구상의 존재하는 원자의 수보다 많다.

공개키

  • 공개키와 개인키는 쌍(pair)의 관계이며 공개키는 외부에 공개 가능한 키이다.
  • 정보를 암호화힐 때 사용한다.

 

개인키와 공개키의 역할

  • 개인키 소유자가 개인키로 데이터를 암호화하고 상대방에게 암호문과 공개키를 같이 전달한다.
  • 공개키와 암호문을 받은 사람은 공개키를 이용해서 검증이 가능하다.
    • 해당 설명은 디지털 서명 검증하는 과정을 의미한다. 
      • 디지털 서명은 원본 메시지의 해시값에 송신자의 개인키를 이용해 암호화한다. 따라서 송신자의 공개키를 이용해 암호문을 복호화할 수 있다. 복호화된 결과와 원본 메시지의 해시값을 비교해 해당 메시지가 해당 송신자로부터 왔음을 확인할 수 있다. 
    • 이런 위험성이 있어 보이는 방법을 사용하는 이유는 데이터의 보호 목적보다는 공개키 데이터를 제공한 사람의 신원을 보장해주기 위해서이다.
  • 암호화된 데이터가 공개키로 복호화 된다는 것은 공개키와 쌍으로 이루는 비밀키에 의해서 암호화가 되었다는 것이다.
    • 데이터의 제공자가 맞는지, 이 사람이 이 서명을 한 사람인지 검증하는 것이다.
  • 이 방법은 공인 인증 체계의 기본 바탕인 전자서명의 방식이다.

 


암호화 방식

대칭키 암호

출처 : https://raonctf.com/essential/study/web/symmetric_key

  • 대칭키는 암호화 키와 복호화 키가 동일한 키를 말한다.
  • 송신자(암호자)는 평문을 대칭키로 암호화하고 암호문과 대칭키를 함께 수신자에게 송부한다. 수신자는 수신한 대칭키로 암호문을 복호화한다.
  • 암호화한 사람과 수신하는 사람이 같은 키 1개를 사용하는 것이다.
  • 한계점
    • 대칭키는 암호문과 대칭키를 함께 전송한다. 전송 과정에서 악의적 해커가 암호문과 키를 탈취할 수 있다.

 

비대칭키 암호

출처 : https://raonctf.com/essential/study/web/asymmetric_key
출처 : https://raonctf.com/essential/study/web/asymmetric_key
출처 : https://raonctf.com/essential/study/web/asymmetric_key

  • 대칭키의 키 배송문제를 해결하기 위해 비대칭키 암호는 암호화 키와 복호화 키가 서로 다른 키를 사용한다.
  • 비대칭키는 수학적으로 아주 밀접하게 연관된 쌍(pair)으로 구성된 2개의 키를 발행하며, 하나의 키로 암호화하면 반드시 그 키와 쌍을 이루는 다른 키로만 복호화가 가능하게 설계된 암호이다.

 

🔑 비대칭키 암호화 순서

  1. 먼저 수신자는 수학적으로 쌍을 이룬 비대칭키(개인키와 공개키)를 발행한다.
  2. 2개의 키 중 공개키를 송신자에게 전송한다.
  3. 송신자는 평문을 암호문으로 암호화한다.
  4. 암호문이 전송되면 수신자는 개인키로 복호화한다.

 

🔑 비대칭키의 특징

  • 2개의 키는 수학적으로 밀접하게 연관된 쌍(pair)을 이룬다.
  • 한 개는 외부에 공개(공개키)되어도 좋지만, 다른 한 개는 외부에 공개되어서는 절대 안된다.
  • 공개키로 암호화했다면 공개키와 쌍을 이룬 개인키로만 해독이 가능하다.
  • 개인키로 암호화했자면 개인키와 쌍을 이룬 공개키로만 해독이 가능하다.
  • 외부에 공개된 공개키를 통해서 쌍을 이룬 개인키의 유추는 어려워야 한다.

 


타원 곡선 암호화(ECC)

출처 : https://velog.io/@dogfootbirdfoot/ECC

  • 타원 곡선 수학을 기반으로 디지털 서명을 보호하기 위해 사용하는 암호화 기술이다.
  • 타원 곡선 알고리즘은 어려운 수학문제로, y^2 = x^3 + ax + b 의 형태를 가진다.
  • 기준점이 있고 기준점에서 개인키를 가지고 공개키를 구하는데, 이 때 기준점과 공개키로 개인키를 역산하기 힘들다.
  • 타원 곡선 암호화(ECC)에 사용되는 타원 곡선의 보안성은 타원 곡선 이산 대수 문제(ECDLP)의 어려움에 의존한다.
  • 타원 곡선 이산 대수 문제(ECDLP, Elliptic Curve Discrete Logarithm Problem)
    • 타원 곡선 상의 알려진 점 (P)을 더하여 새로운 점을 계산하는 회수를 나타내는 값(k)을 개인키로 하고, P를 k번 더해 생성되는 새로운 점에 해당하는 값(kP)을 공개키로 정의할 때, 공개키(kP)로부터 개인키(k)를 계산하는 문제이다.
    • 현재까지 빠른 계산을 도울 수 있는 방법이 거의 알려지지 않았다.
    • 비밀로 유지되는 개인키를 알아내기 위해서는 오로지 전사 공격(일일이 대입)에 의존해야 한다.
    • 일부 빠른 계산 기법들의 도움을 받을 수 있는 소인수 분해나 지수 함수 이산 대수 연산(RSA에 활용)에 비해 같은 크기의 키를 사용할 때 훨씬 많은 수의 연산 수행이 필요하다. 

 

 

 


디지털 서명 (Digital Signatures)

디지털 서명(Digital Signatures)이란 일종의 검증 알고리즘이다. 블록체인에서 디지털자산의 트랜잭션에 서명하고 승인하는 데 사용되기도 하며, 데이터 전송, 계약, 분산신원인증(DID, Decentralized Identity) 등에도 사용된다.

출처 : https://m.upbitcare.com/academy/education/blockchain/94

디지털 서명은 일반적으로 해싱, 서명, 검증 세 가지 알고리즘으로 이루어진 전자 서명의 일종이라 할 수 있다.

 

개인키와 공개키 생성

  • 보내야 할 데이터, 문서, 메시지 등을 해시 알고리즘으로 암호화하여 개인키와 공개키를 생성한다.
  • 데이터는 해시 함수를 사용하여 암호화되기 때문에 데이터의 길이에 상관없이 고정된 크기의 해시를 생성하는데, 이렇게 해싱을 통해 개인키와 이에 상응하는 공개키를 생성된다.
  • 이때 단 1비트라도 변하게 된다면 완전 다른 해시값을 내놓기 때문에 역산해서 암호를 푸는 것은 사실상 불가능하다.

 

서명

  • 서명은 데이터나 문서에 발신자의 개인키가 포함됨으로써 서명을 하게 됩니다.
  • 또한 발신자는 수신자에게 개인키에 부합하는 공개키를 함께 보낸다. 데이터가 해시화되었기 때문에 메시지 별로 모두 다른 디지털 서명을 갖게 된다.

 

검증

  • 수신자는 발신자가 보낸 공개키를 통해 디지털 서명의 유효성을 확인할 수 있다.

 

디지털 서명이 갖는 기술적 의의

1) 무결성 보장

무결성 보장이란 데이터나 문서 등이 전송되는 동안 변경되지 않았음을 의미한다. 만약 해킹 공격으로 인해 데이터가 변경이 된다면 서명이 무효화가 되기 때문에 데이터가 위변조된 것을 바로 알 수 있다.

2) 인증

발신자 인증이란 데이터를 전송하는 주체에 대한 인증이다. 서명이 유효하다면 개인키를 보유한 사람이 발신했다는 것을 확인할 수 있다. 또한 서명한 발신자가 다른 사람이 아니라는 것을 확인시켜주기 때문에 서명을 하지 않았다고 부인하는 것을 방지할 수 있다.

 

 

 


ECC를 활용한 지갑 생성_테스트 코드

1. 필요한 모듈을 가져온다.

VSC 터미널에서 install 한다.

# crypto, elliptic, crypto-js install
npm i -D crypto @types/elliptic elliptic @types/crypto-js crypto-js

각 모듈에서 필요한 함수들을 불러온다.

  • randomBytes : 임의의 바이트 값을 생성하는데 사용된다.
  • elliptic : 타원 곡선 암호화 관련 기능을 제공한다.
  • SHA256 : crypto-js에서 해시함수를 가져온다.
import { randomBytes } from "crypto";
import elliptic from "elliptic";
import { SHA256 } from "crypto-js";

 

2. 타원 곡선 암호화를 위한 변수와 개인키, 공개키, 서명을 저장하기 위한 변수의 타입을 선언한다.

  • privKey : 개인키(private key)를 저장할 변수이다. 문자열 형식(string)으로 저장한다.
  • PubKey : 공개키(public key)를 저장할 변수이다. 문자열 형식(string)으로 저장한다.
  • signature : 서명을 저장할 변수이다. elliptic.ec.Signature 타입으로 저장된다.
const ec = new elliptic.ec("secp256k1");

// '지갑 만들기' 테스트 그룹(describe)에서 선언한 변수다.
  let privKey: string;
  let PubKey: string;
  let signature: elliptic.ec.Signature;

 

3. 지갑에 대한 테스트 그룹 ' 지갑 만들기 '를 생성한다. 

지갑에 대한 테스트 그룹을 describe 함수로 선언한다. 개인키, 공개키, 서명을 생성하고 검증과 지갑주소를 생성하는 테스트를 it 함수를 이용하여 작성한다. 다음은 '지갑 만들기' 그룹의 전체적인 구조다. 자세한 테스트 함수(it함수)도 순서대로 자세히 살펴보고자 한다.

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

  it("개인키 생성", () => {
    .....
  });

  it("공개키 생성", () => {
    .....
  });

  it("서명 만들기", () => {
    .....
  });

  it("검증하기", () => {
    .....
  });

  it("지갑 주소 생성", ()=>{
    .....
  })
});

 

4. 개인키 생성 함수

privKey는 randomBytes 함수로 랜덤한 바이트 값을 생성한다. 매개변수로 생성될 바이트의 수를 전달한다. ' randomBytes(32) ' 는 32바이트의 랜덤 값을 생성하는 코드이며, ' toString('hex') '로 랜덤 값을 16진수의 문자열로 반환한다.

이 값이 어떻게 길이가 64개가 되는 걸까?

1 byte == 8 bit
=> 32 byte == 256 bit

2진수 한 자리 == 1 bit
16진수 한 자리 == 4 bit
=> 256 bit == 16진수 64자리

먼저, randomBytes(32)로 랜덤한 32바이트의 값이 생성되었다. 이 32byte의 값은 256비트(== 32*8)의 랜덤 값을 의미한다. toString('hex') 메서드로 이 랜덤한 값을 16진수로 변경되며 256비트의 값을 16진수로 변환하며 64자리의 값을 갖게 되는 것이다.

  it("개인키 생성", () => {
    privKey = randomBytes(32).toString("hex");
    console.log("개인키 : " + privKey);
    /* 결과
       개인키 : 9aab25d0cd646c7e367a445b7a913518317e27f155cdfcbd579d31ae8badb434 */
    console.log("개인키의 길이 : " + privKey.length);
    /* 결과
       개인키의 길이 : 64 */
  });

 

5. 공개키 생성 함수

공개키를 생성하는 함수이다.

ec.keyFromPrivate() 메소드를 사용해 타원 곡선 키페어(key pair) 객체를 생성한다. 매개변수로 개인키(privKey)를 전달하여 개인키를 기반으로 키 페어를 생성하게 된다. 이렇게 생성된 키 페어에는 개인키와 그에 대응하는 공개키를 포함하고 있다.

keyPair.getPublic() 메소드는 키페어에서 공개키를 반환한다. 이렇게 반환된 값을 encode() 메소드를 사용해 공개키의 출력형식을 새롭게 지정한다. ' .encode('hex', false) '는 공개키를 16진수로 인코딩하는 코드이다. 따라서, ' keyPair.getPublic().encode('hex', false) '는 keyPair의 공개키를 반환받는데 16진수의 값으로 반환받는다. (' keyPair.getPublic() ' : elliptic에서 제공하는 메서드로, 개인키로 생성한 키페어의 공개키를 반환한다. , ' encode(enc, compact) ' :  elliptic에서 제공하는 메서드로, 공개키를 특정 형식의 문자열로 인코딩한다. 출력형식을 지정(enc)하고(주로 hex나 base63등이 사용된다.), 공개키를 압축할지 여부를 지정(compact)한다.(true면 압축된 공개키가 false면 압축되지 않은 공개키가 반환된다.))

  it("공개키 생성", () => {
    const keyPair = ec.keyFromPrivate(privKey);
    // false 문자열. 압축 여부 중요하지 않음
    // 개인키로 공개키를 생성
    PubKey = keyPair.getPublic().encode("hex", false);
    console.log("공개키 : ", PubKey);
    /* 결과
       공개키 :  0498c3f26e80cb8e922ffd37fcee34b998eb445d935479a6c069e3ef9db764fc3a43e2211b4a79f82dd5520a6a1eab0822fb53ac1d2963f593f057fc1f11c7afcd */

    console.log("공개키의 길이 : ", PubKey.length);
    /* 결과
       공개키의 길이 :  130 */
  });

 

6. 서명 생성 함수

이 함수에서는 특정 데이터에 대한 디지털 서명을 생성한다. (이 테스트의 특정 데이터는 ' transaction data '이다.)

변수 keyPair는 keyFromPrivate()메서드를 통해 개인키로부터 생성된 타원 곡선 키페어를 반환받는다.

변수 hash는 ' transaction data '라는 특정 데이터를 SHA256 해시 함수를 적용하여 해시 값으로 계산한다. 이 계산된 값을 toString() 메서드를 통해 문자열의 형태로 반환한다. 따라서, ' transaction data '라는 데이터가 문자열 형태의 해시값으로 반환된다.

변수 signature는 keyPair.sign() 메서드를 사용하여 첫 번째 매개변수로 받은 값을 개인키를 사용하여 디지털 서명을 생성한다. 두 번째 매개변수는 반환할 값의 형태를 지정한다. 따라서, ' keyPair.sign(hash, 'hex') '는 특정 데이터를 해시화했던 값이 담긴 변수 hash에 대한 값을 개인키를 사용해 16진수 형태의 디지털 서명을 생성하여 반환한다.

  it("서명 만들기", () => {
    const keyPair = ec.keyFromPrivate(privKey);
    // 임시 트랜잭션 내용
    const hash = SHA256("transaction data").toString();
    // sign 서명 생성
    signature = keyPair.sign(hash, "hex");
    console.log("서명 : ", signature);
    /* 결과

    //// BN : BigNumber. 무척 큰 number 타입.
    //// negative : 양수라는 의미. 0
    //// words : r서명이나 s서명의 값을 32비트 정수 배열로 표시한 값
    //// length : 배열의 길이

      서명 :  Signature {
      r: BN {
        negative: 0,
        words: [
           3779726, 35725574,
          44349962, 24282336,
          44701986, 41482599,
          11850099, 52266407,
          30297944,  1114374
        ],
        length: 10,
        red: null
      },
      s: BN {
        negative: 0,
        words: [
          45365455,  7465147, 22076319, 24082460,
          60587035, 52444199,  7021816, 40868643,
          53267649,  2886692,        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
    }
    */
  });

 

7. 검증 함수

변수 verify는 매개변수로 검증할 해시 값과 서명, 공개키를 매개변수로 전달해 해당 디지털 서명이 유효한지 검사한다. 따라서, ' ec.verify(hash, signature, ec.keyFromPublic(pubKey, "hex")) '는 주어진 데이터(hash), 그레 대한 디지털 서명(signature),개인키에 대응하는 공개키(publickey)가 제공되었을 때 유효하게 생성되는 것인지 확인하는데 사용한다. 반환되는 값은 boolean 타입이다. (' ec.verify(hash, signature, publickey)) ' : elliptic에서 제공하는 메서드로, 서명검증 메소드이다. hash : 검증할 데이터의 해시 값 / signature : 검증할 디지털 서명 / publickey : 해당 디지털 서명을 생성한 개인키에 대응하는 공개키)

  it("검증하기", () => {
    const hash = SHA256("transaction data").toString();
    const verify = ec.verify(hash, signature, ec.keyFromPublic(PubKey, "hex"));
    console.log("검증됨?", verify);
    // 결과 : true
  });

 

8. 지갑 주소 생성

지갑주소 (계정)을 만드는 방법은 만든 공개키의 26번째 문자부터 끝까지 잘라내어 지갑의 주소로 사용한다. 이 방법은 불필요한 부분을 제거하고 가독성을 위해 ' 0x '를 붙이는 방법이다.

  it("지갑 주소 생성", ()=>{
    // 주소의 앞에는 0x를 붙이는 것이 일반적이다. (16진수의 주소다 라는 뜻)
    const addres = PubKey.slice(26).toString()
    console.log("주소 : ", `0x${addres}`)
    // 결과 :  0x253fec83bbea9b115889cf228716dd3ad7c73a692394b54d02a0a398aba499b35daf40d0e1d39292ec95d1507c88557b0b17e9a4
  })

 

 

 


ECC를 활용한 지갑 생성

이제까지 테스트 코드를 이용해서 지갑을 생성하는 과정을 익혀보았다. 이번에는 실제로 지갑을 생성하여 파일로 저장하고, 생성된 지갑의 리스트를 불러오며 리스트에 특정 지갑의 주소를 클릭했을 경우 해당 지갑의 정보를 띄우는 코드를 작성하고자 한다.

#. 폴더의 경로는 다음과 같다.

 

1. src/core/wallet/index.ts_블록체인 지갑을 생성하고 관리하는 클래스를 생성한다.

우선 작업에 필요한 모듈들을 불러온다. elliptic 인스턴스를 생성하는 변수 ec와 지갑의 기본 저장경로를 담는 변수 dir을 생성한다.

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");

 

Wallet 지갑 클래스를 정의한다. 해당 클래스의 전체적인 구조는 다음과 같다.

export class Wallet {
  public account: string;		// 지갑 주소
  public privateKey: string;		// 개인 키
  public publicKey: string;		// 공개 키
  public balance: number;		// 잔액

  constructor(privateKey: string = "") {
    // 생성 시 비밀 키(privateKey) 값이 주어지지 않으면 랜덤으로 생성
    this.privateKey = privateKey || this.getPrivateKey();
    this.publicKey = this.getPublicKey();		// 공개 키 생성
    this.account = this.getAccount();			// 지갑 주소 생성
    this.balance = 0;

	// 매개변수를 받지 못했을 경우, 지갑을 생성한다.
    if (privateKey == "") Wallet.createWallet(this);
  }

  // 지갑을 생성하는 메소드
  static createWallet(myWallet: Wallet) {
    .....
  }

  // 생성된 지갑들을 불러오는 메소드
  static getWalletList(): string[] {
    .....
  }

  // data 폴더 안에 해당하는 지갑 주소 찾아서 반환
  static getWalletPrivateKey(account: string): string {
    .....
  }

  // 개인키를 만들어주는 메소드
  public getPrivateKey(): string {
    .....
  }

  // 공개키를 만들어주는 메소드
  public getPublicKey(): string {
    .....
  }

  // 지갑 주소를 생성하는 메소드
  public getAccount(): string {
    .....
  }
}

 

지갑 생성 시, 객체에 들어가야 할 비밀키, 공개키, 주소를 생성하기 위한 메서드를 살펴보고자 한다.  getPrivateKey 메서드getPublicKey 메서드, getAccount 메서드는 이전에 테스트 코드에서 생성했던 방식과 동일하므로 별도의 설명은 생략한다.

  // 개인키를 만들어주는 메소드
  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()}`;
  }

 

지갑을 생성하는 메서드인 createWallet이다. 해당 메서드는 서버에서 매개변수를 받지 못한 상태로 Wallet 클래스의 새로운 인스턴스를 생성하면 constructor에서 생성된 내용을 ' createWallet(this) '로 전달해 새로운 지갑을 생성한다.

// server.ts - 클라이언트 측에서 지갑 생성 요청을 받아, 생성된 지갑을 반환한다.
app.post("/newWallet", (req, res) => {
  res.json(new Wallet());
});

// ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

// index.ts - constructor에서 매개변수를 받지 못해, Wallet.createWallet(this)를 실행한다.
if (privateKey == "") Wallet.createWallet(this);

( fs.writeFileSync(file, data[, options])은 비동기로 파일을 셍성하고 파일에 데이터를 기입하는 메서드이다.  file : 파일의 경로 및 이름 지정(문자열), data : 파일에 쓸 데이터 지정(문자열, 버퍼), options : 파일쓰기 옵션을 지정)

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

 

getWalletList 메서드는 생성된 지갑의 목록을 반환하는 메서드이다. fs.readdirSync 메서드를 이용하여 폴더 내의 파일 이름을 문자열 배열로 반환한다. 폴더의 경로는 매개변수로 전달받는다.

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

 

getWalletPrivateKey 메서드는 찾고싶은 지갑의 주소(account)를 매개변수로 전달받는다. ' path.join(dir, account) '로 전달받은 주소를 사용하여 원하는 지갑 파일을 찾는다. 찾은 파일의 내용을 ' fs.readFileSync(filename) '로 변수에 저장해 문자열로 변환하여 반환한다.  

  static getWalletPrivateKey(account: string): string {
    const filename = path.join(dir, account);
    const fileContent = fs.readFileSync(filename);
    return fileContent.toString();
  }

 

 

2. src/core/wallet/server.ts_express를 이용해 지갑을 동작시킬 웹 서버 생성

먼저 서버 구현에 필요한 모듈을 가져오고 서버 인스턴스를 생성한다.

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

const app = express();

 

미들웨어를 설정하여 body 객체 사용을 설정하고, JSON 형식의 데이터를 파싱한다.

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

 

클라이언트에서 보낸 axios 요청을 처리하는 함수를 생성한다. 

' / '경로 get요청 함수는 루트 경로의 웹 페이지를 설정하고 설정된 웹 페이지를 텍스트 형식으로 읽어 보내준다. fs.readFileSync 메서드를 사용해 서버의 파일 시스템에서 웹 페이지 파일을 동기적으로 읽어온다. 매개변수로 현재 디렉토리(__dirname)에 있는 ' view/index.html ' 파일의 절대 경로를 생성한다. utf8인코딩을 사용해 index.html 파일의 내용을 텍스트 형식 변수 page에 담는다. res.send로 변수 page에 담겨있던 내용을 응답으로 보내준다.

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

 

' /newWallet ' 경로 post 요청 함수는 해당 경로로 post 요청이 들어오면 랜덤한 값으로 지갑을 생성한다. new Wallet()으로 반환받은 값은 Wallet.createWallet()메소드를 실행시켜 새로 생성된 지갑 객체이다. 반환받은 값으로 새로 생성된 지갑에 대한 세부 내용을 렌더한다.

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

 

' /walletList ' 경로 post 요청 함수는 해당 경로로 post 요청이 들어오면 Wallet.getWalletList() 메소드가 실행된다. 해당 메소드에서 반환받은 지갑 리스트의 파일 이름들을 문자열 배열 형태로 변수 list에 받는다. 해당 값을 다시 JSON 형식으로 반환한다.

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

 

' /walletSelect ' 경로 post 요청 함수는 해당 경로로 post 요청이 들어왔을 때, 지갑 생성 시 반환받은 지갑 객체를 변수 resp에 넣었고, resp.account 의 값을 지갑 리스트를 그릴 때 각 지갑의 주소마다 클릭을 했을 때 주소 데이터를 같이 보내게된다.

Wallet.getWalletPrivatekey() 메소드는 매개변수로 받은 주소(account)를 이용해 생성된 지갑 정보를 읽어와 비밀키를 변수 privateKey에 담는다.

' new Wallet '으로 Wallet 클래스의 constructor로 이동해 해당 주소의 지갑의 내용을 그려주고, 해당 지갑의 내용을 JSON 객체로 반환한다.

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

 

 

 

2. src/core/wallet/veiw/index.html_생성된 지갑 주소를 확인 및 지갑리스트, 지갑찾기를 수행한다.

  <body>
    <h1>지갑 튜토리얼</h1>
    <button id="walletBtn">지갑 생성</button>
    <ul id="walletList">
      <li>비트코인 지갑</li>
      <li>account : <span id="account"></span></li>
      <li>private Key : <span id="privateKey"></span></li>
      <li>public Key : <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", null);
      console.log(resp);
      render(resp);
    };

    const getView = async (account) => {
      //   console.log(account.toString("hex"));
      const { data: resp } = await axios.post("/walletSelect", { account });
      console.log(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>

 

 

 

 

 

728x90