일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- ws 라이브러리
- javascript기초
- express실행
- 라우터와 미들웨어
- 라우터 분리
- mysql wsl
- css기초
- node.js path
- 세션으로 로그인 구현
- 라우트 매개변수
- buffer.from
- JWT 만들어보기
- useEffect clean up
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- next 매개변수
- JWT 로그인 기능 구현
- express.static
- nodejs파일업로드
- express router
- 블록 만들기
- 아이디 중복체크기능
- FormData()
- 시퀄라이즈 기본설정
- 라우터미들웨어 분리
- cookie-parser 만들어보기
- JWT 하드코딩
- useContext
- OAuth 카카오
- 비동기파일업로드
- express session
- Today
- Total
즐코
지갑, 트랜잭션 구현 (3) - 지갑 저장, 트랜잭션 보내기 본문
https://yjleekr.tistory.com/80
이전 포스팅에서는 지갑 생성까지만 했었다면, 이번엔 fs를 이용해서 지갑을 생성할 때마다 특정 폴더에 저장할 것이다.
그리고 저장된 지갑의 목록을 불러와서 지갑 디테일 확인 및 해당 지갑의 개인키를 이용하여 서명도 만들고, 서명과 함께 트랜잭션을 블록체인 서버쪽에 보낼 것이다.
지갑과 지갑서버
1. 지갑 저장 및 생성된 지갑 내용 화면에 뿌리기
파일 저장 시엔 각 지갑의 계정을 파일명으로, 파일 내용엔 개인키가 들어가게끔 코드를 작성했다.
Wallet 클래스를 만들 때 생성자메소드의 인자를 받지 않았었는데, 추후 지갑들 목록에서 지갑을 가져올 때 개인키를 넣고 나머지 값을 불러올 것이므로, 인자로 개인키를 받아주는 걸로 수정한다.
(실제론 요청/응답 상에 개인키가 왔다갔다하는건 위험하기때문에 이렇게 하지 않지만, 우린 확인 차 해본것)
fs.writeFileSync('저장할 파일경로를 포함한 파일명', '저장할 파일 컨텐츠') : 특정 디렉토리 상에 파일을 저장해주는 메소드 사용함
// wallet/wallet.ts
import { randomBytes } from "crypto";
import { SHA256 } from "crypto-js";
import elliptic from "elliptic";
import fs from "fs";
import path from "path";
const ec = new elliptic.ec("secp256k1");
// 어떤 운영체제에서든 똑같은 data 폴더에 지갑이 저장되게끔 폴더 위치 설정
const dir = path.join(__dirname, "../data")
export class Wallet {
public privateKey: string;
public publicKey: string;
public account: string;
public balance: number;
constructor(_privateKey: string = ""){
this.privateKey = _privateKey || this.getPrivateKey();
this.publicKey = this.getPublicKey();
this.account = this.getAccount();
this.balance = 0;
Wallet.createWallet(this);
}
static createWallet(myWallet: Wallet): void {
const filename = path.join(dir, myWallet.account);
const fileContent = myWallet.privateKey;
fs.writeFileSync(filename, fileContent);
}
}
index.html, index.js 수정
아래에서 지갑생성 버튼 (wallet_btn)을 클릭하면,
새로운 지갑이 생성됨과 동시에 data 폴더 상에 계정을 파일명으로, 개인키를 내용으로 가진 파일이 새로 들어가게 된다.
// 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>
<script defer src="/index.js"></script>
<title>Document</title>
</head>
<body>
<h1>hello wallet</h1>
<button id="wallet_btn">지갑생성</button>
<form id="transaction_form">
<ul>
<li>recipient: <input id="to" placeholder="보낼 계정" /></li>
<li>amount: <input id="amount" placeholder="보낼 금액" /></li>
</ul>
<input type="submit" value="전송" />
</form>
<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>지갑목록</h1>
<button id="wallet_list_btn">지갑목록 버튼</button>
<div class="wallet_list2">
<ul></ul>
</div>
</body>
</html>
지갑 생성 버튼 클릭 시 createWallet 이벤트가 발동하면서 '/newWallet' 으로 POST 요청이 들어간다.
받은 응답 데이터로 화면 그려주기
// public/index.js
const walletBtn = document.querySelector("#wallet_btn");
walletBtn.addEventListener('click', createWallet);
const createWallet = async () => {
const response = await axios.post('/newWallet', null);
view(response.data);
}
const view = (wallet) => {
const { privateKey, publicKey, account, balance } = wallet;
privKeySpan.innerHTML = privateKey;
pubKeySpan.innerHTML = publicKey;
acctSpan.innerHTML = account;
balSpan.innerHTML = balance;
};
// wallet/server.ts
app.post("/newWallet", (req, res) => {
res.json(new Wallet());
});
아래처럼 지갑을 생성하면 data 폴더 상에 지갑계정이 저장된다!
2. 생성된 지갑 목록 가져오기
- 지갑 목록 가져오는 버튼 만들어주고, 클릭 시 /walletList로 POST 요청이 가게끔 코드 작성
- 받은 response.data는 ['파일명(계정명)', '파일명(계정명)', '파일명(계정명)'...] 일 것이고 이걸 화면상에 리스트로 쌓이게끔 map 메서드 사용
- 각 계정명을 클릭할때마다 지갑 디테일(개인키/공개키/지갑주소/잔고)이 찍히게끔 onClick 함수 설정
// public/index.js
const walletListBtn = document.querySelector("#wallet_list_btn");
walletListBtn.addEventListener("click", getWalletList);
const getWalletList = async () => {
const walletList = document.querySelector(".wallet_list2 > ul");
const response = await axios.post("/walletList", null);
const list = response.data.map((acct) => {
return `<li onClick="getView('${acct}')">${acct}</li>`;
});
walletList.innerHTML = list;
};
// wallet/server.ts
app.post("/walletList", (req, res) => {
const list = Wallet.getWalletList();
res.json(list);
});
fs.readdirSync('파일 목록을 가져올 디렉토리경로') : 디렉토리 내부 파일 목록들이 배열 안에 담겨져서 나온다
// wallet/wallet.ts - Wallet 클래스 내부
const dir = path.join(__dirname, "../data")
static getWalletList(): string[] {
const files: string[] = fs.readdirSync(dir);
return files;
}
3. 목록 내 지갑 계정 클릭 시 해당 지갑 디테일 가져오기
각 리스트 onClick 시 getView() 함수 발동
이 때, 라우트 매개변수 사용해 get 요청, 즉 url로 account명을 전달해서 정보를 가져온다.
// public/index.js
const getView = async (account) => {
const response = await axios.get(`/wallet/${account}`);
view(response.data);
};
req.params 로 account를 받아와서, Wallet 클래스 내부의 파일 내용을 가져오는 함수의 인자로 넣고 개인키를 가져온다.
즉, 파일명이 지갑주소(account)이므로, 파일명을 찾아 해당 내용인 개인키를 가져오는 것이다.
// wallet/server.ts
app.post("/wallet/:account", (req, res) => {
const { account } = req.params;
const privateKey = Wallet.getWalletPrivKey(account);
res.json(new Wallet(privateKey));
});
fs.readFileSync('읽어올 파일 경로를 포함한 파일명') : 파일 내부 내용 가져와준다.
// wallet/wallet.ts - Wallet 클래스 내부
const dir = path.join(__dirname, "../data")
static getWalletPrivKey(_account: string): string {
const filepath = path.join(dir, _account);
const filecont = fs.readFileSync(filepath);
return filecont.toString();
}
FS (FileSystem) 메소드 정리
import fs from 'fs';
fs.writeFileSync('저장할 파일경로를 포함한 파일명', '저장할 파일 컨텐츠') : 특정 디렉토리에 파일 만들어주는 메소드
fs.readdirSync('파일 목록을 가져올 디렉토리경로') : 디렉토리 내부 파일리스트를 가져와주는 메소드
fs.readFileSync('읽어올 파일 경로를 포함한 파일명') : 파일 내부 내용 가져와주는 메소드
트랜잭션 블록체인 서버에 넘기기
1/ 트랜잭션 내용 만들기
트랜잭션 시 블록체인 서버에 보내는 정보는 아래와 같다.
- 보내는 사람의 정보(sender) : 공개키(publicKey), 지갑주소(account)
- 받는 사람의 정보 : 지갑주소(recipient)
- 얼마나 보낼지(amount)
// public/index.js
const txForm = document.querySelector("#transaction_form");
const submitHandler = async (e) => {
e.preventDefault();
const publicKey = document.querySelector(".publicKey").innerText;
const account = document.querySelector(".account").innerText;
const { to, amount } = e.target;
const data = {
sender: {
publicKey,
account,
},
recipient: to.value,
amount: parseInt(amount.value),
};
await axios.post("/sendTransaction", data);
};
txForm.addEventListener("submit", submitHandler);
2/ 블록체인 서버에게 트랜잭션 전송
1/ 트랜잭션 전송 전 서명을 만들어서 트랜잭션 내용 업데이트
트랜잭션은 결국 블록체인 네트워크 상의 노드에게 보내줘야한다.
이 때, 해당 네트워크에 접속하려면 블록체인 서버쪽 authorization이 필요하다.
( http 기본 인증 관련 포스팅 : https://yjleekr.tistory.com/80?category=1284408)
2/ '서명+트랜잭션내용'을 블록체인 인터페이스를 관리하는 http 서버로 전송!
우린 axios를 통해서 블록체인 네트워크 서버 쪽에 트랜잭션 전송 요청을 할건데,
- 매번 트랜잭션 요청마다 headers를 설정해주기엔 번거롭기도 하고,
- 매번 기본url인 'http://localhost:3000'을 반복해서 쓰기 귀찮으므로,
=> axios에서 제공해주는 메소드인 create()를 사용하여 기본 baseURL 과 headers를 가지고 있는 axios 인스턴스를 만들어준다.
이렇게 하면, 매번 일일이 axios.post('요청할 url', 데이터, header설정)를 해줄 필요가 없고, request.post(데이터) 이렇게 쓰면 된다!
// wallet/server.ts
const userid = process.env.USERID || "yjleeinkr";
const userpw = process.env.USERPW || "1234";
const baseURL = process.env.BASEURL || "http://localhost:3000";
const baseAuth = Buffer.from(userid + ":" + userpw).toString("base64");
// axios.create 로 axios 인스턴스 생성
const request = axios.create({
baseURL,
headers: {
Authorization: "Basic " + baseAuth,
"Content-type": "application/json"
}
})
app.post("/sendTransaction", async (req, res) => {
const { sender: { account, publicKey }, recipient, amount} = req.body;
// 트랜잭션 송신 시 서명을 만들어서 보내준다
const signature = Wallet.createSign(req.body);
const txObject = {
sender: publicKey,
recipient,
amount,
signature
};
console.log("트랜잭션오브젝트", txObject);
// 위에서 만든 axios 인스턴스 사용
const response = await request.post("/sendTransaction", txObject);
res.json({});
});
createSign() : 트랜잭션으로 서명 만들기
트랜잭션 내용 중 공개키 + 수신인지갑주소 + 송금어마운트 합쳐서 hash값을 만들고, 개인키로 해쉬화해서 서명을 만든다.
// wallet/wallet.ts - Wallet 클래스 내부
const dir = path.join(__dirname, "../data")
static createSign(_obj: any): elliptic.ec.Signature {
const {
sender: { account, publicKey },
recipient,
amount
} = _obj;
// hash 만들기 (수신인계좌 + 송금amount + 공개키)
const hash: string = SHA256([publicKey, recipient, amount].join("")).toString();
// 개인키를 통해서 hash로 서명을 만듬
const privateKey: string = Wallet.getWalletPrivKey(account);
const keyPair: elliptic.ec.KeyPair = ec.keyFromPrivate(privateKey);
return keyPair.sign(hash, "hex");
}
블록체인 서버 쪽에서 트랜잭션 검증하기
이제 트랜잭션이 지갑서버에서 블록체인 인터페이스 관리 서버로 넘어왔다.
받은 트랜잭션 내용은 아래와 같다.
1/ 트랜잭션 검증하기
트랜잭션을 받아서 무조건 트랜잭션 풀에 던지는 게 아니라 받은 트랜잭션을 검증하기 위한 절차가 필요하다.
자세히 말하자면, 트랜잭션 내용이 바꼈는지 (sender, recipient, amount 가 수정된 적이 없는지) 확인하는 절차를 거친다.
// index.ts (블록체인 서버)
app.post("/sendTransaction", (req, res) => {
console.log("reqbody", req.body); // 상기 스크린샷 참고(지갑서버에서 트랜잭션이 넘어옴)
try{
const rcvdTx: ReceivedTx = req.body;
Wallet.sendTransaction(rcvdTx);
}catch(e){
if( e instanceof Error) console.log(e.message);
}
res.json([]);
})
트랜잭션 검증 메소드인 sendTransaction()은 Wallet 클래스 내부에 작성하였다.
여기서의 Wallet은 블록체인 인터페이스의 core 내의 Wallet 클래스이다.
프론트단의 Wallet과 블록체인 인터페이스의 Wallet의 역할은 다르다.
- 프론트단의 Wallet 클래스 : 클라이언트용 인터페이스로, 클라이언트가 트랜잭션을 만들고 블록체인 서버로 전송해주기 위한 용도
- 블록체인 인터페이스의 Wallet 클래스 : 서명에 대한 검증을 하고 트랜잭션 내용을 만들어주는 용도
sendTransaction()에선 아래와 같은 역할을 해줘야한다.
1/ 서명 검증 getVerify()
2/ 보내는 사람의 지갑정보(공개키, 계정, 잔고)를 가질 수 있어야 한다. (지갑정보 최신화)
왜냐면, 블록체인 서버가 받은 트랜잭션(위 코드 상에서의 req.body)의 sender 정보는 오직 공개키뿐이기 때문에, 이 공개키를 이용해서 보낸이의 다른 정보들도 가져올 수 있어야한다!
const myWallet = new this(_rcvdTx.sender, _rcvdTx.signature); 해당 Wallet 클래스 (this) 를 가져오면 된다.
3/ 보내는 사람의 잔고 확인
4/ 트랜잭션을 만드는 과정
잔고 확인과 트랜잭션 만드는 과정은 다음 포스팅에서 정리할 예정이다.
// src/core/wallet/wallet.ts
export type Signature = elliptic.ec.Signature; // 서명 타입 미리 선언
// 받은 트랜잭션 인터페이스도 미리 선언
export interface ReceivedTx {
sender: string;
recipient: string;
amount: number;
signature: Signature;
}
export class Wallet {
public publicKey: string;
public account: string;
public balance: number;
public signature: Signature;
constructor(_sender: string, _signature: Signature) {
this.publicKey = _sender;
this.account = this.getAcct();
this.balance = 0;
this.signature = _signature;
}
// 보낸이의 공개키를 가지고 계정얻기
getAcct(): string(){
return Buffer.from(this.publicKey).slice(26).toString();
}
static sendTransaction(_rcvdTx: ReceivedTx) {
// 1. 서명 검증
const verify = Wallet.getVerify(_rcvdTx);
if (verify.isError) throw new Error(verify.error);
// 2. 보내는 사람의 지갑정보(공개키/계정/잔고)를 가질 수 있어야함(지갑정보 최신화)
const myWallet = new this(_rcvdTx.sender, _rcvdTx.signature);
// 3. todo : balance 확인
// 4. todo : transaction 만드는 과정
}
static getVerify(_rcvdTx: ReceivedTx): Failable<undefined, string> {
const { sender, recipient, amount, signature } = _rcvdTx;
const hash: string = SHA256([sender, recipient, amount].join("")).toString();
const keyPair = ec.keyFromPublic(sender, "hex");
const isVerified = keyPair.verify(hash, signature);
if (!isVerified) return { isError: true, error: "서명이 올바르지 않습니다." };
return { isError: false, value: undefined };
}
}
'BlockChain' 카테고리의 다른 글
지갑, 트랜잭션 구현 (5) - 트랜잭션, utxo 업데이트! (0) | 2022.06.23 |
---|---|
지갑, 트랜잭션 구현 (4) - txIn, txOut, UTXO (0) | 2022.06.22 |
지갑, 트랜잭션 구현 (2) / 트랜잭션 풀 (0) | 2022.06.19 |
HTTP 기본 인증 / 지갑, 트랜잭션 구현 (1) (0) | 2022.06.19 |
지갑과 트랜잭션에서의 서명/검증 (Ft.개인키,공개키) (0) | 2022.06.16 |