Cute Bow Tie Hearts Blinking Pink Pointer

블록체인

[Typesript] 암호화폐 지갑 웹으로 구현해보기(트랜잭션&정보입력)

청포도 에이드 2022. 6. 17. 17:23
728x90

 

넌작스 다운로드 해주기.

wallet/server.ts
import express from 'express'
import nunjucks from 'nunjucks'
import { Wallet } from './wallet'
import axios from 'axios'

const app = express()

const userid = process.env.USERID || 'podo'
const userpw = process.env.USERPW || '1234'
const baseurl = process.env.BASEURL || 'http://localhost:3000'

const baseAuth = Buffer.from(userid + ':' + userpw).toString('base64')
const request = axios.create({
    baseURL: 'http://localhost:3000',
    headers: {
        Authorization: 'Basic' + baseAuth,
        'Content-type': 'application/json',
    },
})

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.post('/walletList', (req, res) => {
    console.log('wallet List')
    const list = Wallet.getWalletList()
    res.json(list)
})

app.get('/wallet/:account', (req, res) => {
    const { account } = req.params
    const privateKey = Wallet.getWalletPrivateKey(account)
    res.json(new Wallet(privateKey))
})

app.post('/sendTransaction', async (req, res) => {
    console.log(req.body)
    const {
        sender: { account, publicKey },
        received,
        amount,
    } = req.body

    const signature = Wallet.createSign(req.body)
    //서명 받을 때 필요한 값 sha256( 보낼사람: 공개키 + 받는사람:계정, 보낼금액).toString
    //hash + privateKEy => 서명
    const txObject = {
        sender: publicKey,
        received,
        amount,
        signature,
    }

    console.log(txObject)

    const response = await request.post('/sendTransaction', txObject)
    console.log(response.data)
    res.json({})
})

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

//sendtransaction

 

 

+ 루트디렉토리에 data라는 폴더 만들기.

여기에 지갑명을 저장할것임.

 

wallet/wallet.ts
import { randomBytes } from 'crypto'
import elliptic from 'elliptic'
import fs from 'fs'
import path from 'path'
import { SHA256 } from 'crypto-js'

const dir = path.join(__dirname, '../data')

const ec = new elliptic.ec('secp256k1')

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

    constructor(_privateKey: string = '') {
        this.privateKey = this.getPrivateKey()
        this.publicKey = this.getPublicKey()
        this.account = this.getAccount()
        this.balance = 0

        Wallet.createWallet(this)
    }

    static createWallet(myWallet: Wallet) {
        const filename = path.join(dir, myWallet.account)
        const filecontent = myWallet.privateKey
        //파일명을 account
        // 내용을 privatekey
        fs.writeFileSync(filename, filecontent)
    }

    static getWalletList(): string[] {
        const files: string[] = fs.readdirSync(dir)
        return files
    }

    static getWalletPrivateKey(_account: string) {
        const filepath = path.join(dir, _account)
        const filecontent = fs.readFileSync(filepath)
        return filecontent.toString()
    }

    static createSign(_obj: any): elliptic.ec.Signature {
        const {
            sender: { account, publicKey },
            received,
            amount,
        } = _obj

        const hash: string = SHA256([publicKey, received, amount].join('')).toString()
        const privateKey: string = Wallet.getWalletPrivateKey(account)
        const keyPair: elliptic.ec.KeyPair = ec.keyFromPrivate(privateKey)
        return keyPair.sign(hash, 'hex')
    }

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

    public getPublicKey(): string {
        //개인키 -> 공개키
        //개인키 :string임.
        const keyPair = ec.keyFromPrivate(this.privateKey)
        return keyPair.getPublic().encode('hex', true)
    }

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

                       

         

wallet/transaction.ts
// 비트코인

interface ITxIn {
    txOutId: String
    txOutIndex: number
    signature?: any
}

interface ITxOut {
    address: string
    amount: number
}

interface ITransaction {
    hash: string
    txtins: ITxIn[]
    txouts: ITxOut[]
}

 

 

src/core/wallet/wallet.ts
import elliptic from 'elliptic'
import { StringifyOptions } from 'querystring'
import { SHA256 } from 'crypto-js'
const ec = new elliptic.ec('secp256k1')

export type Signature = elliptic.ec.Signature
export interface ReceivedTx {
    sender: string
    received: string
    amount: number
    signature: Signature
}

export class Wallet {
    public publicKey: string
    public account: String
    public balance: number
    public signature: Signature

    constructor(_sender: string, _stinature: Signature) {
        this.publicKey = _sender
        this.account = this.getAccount()
        this.balance = 0
        this.signature = _stinature
    }

    static sendTransaction(_receivedTx: ReceivedTx) {
        //ToDo :서명검증
        // 공개키, 보내는 사람:공개키, 받는사람:계정, 보낼금액
        //todo : 보내는 사람의 지갑정보 최신화 //publickey
        const verify = Wallet.getVerify(_receivedTx)
        if (verify.isError) throw new Error(verify.error)
        const myWallet = new this(_receivedTx.sender, _receivedTx.signature)
        //todo : balance 확인
        // todo: 트랜잭션 만드는 과정

        console.log(verify.isError)
    }

    static getVerify(_receivedTx: ReceivedTx): Failable<undefined, string> {
        const { sender, received, amount, signature } = _receivedTx
        const data: any[] = [sender, received, amount]
        const hash: string = SHA256(data.join('')).toString()

        //todo: 타원곡선알고리즘 사용
        const keyPair = ec.keyFromPublic(sender, 'hex')
        const isVerify = keyPair.verify(hash, signature)

        if (!isVerify) return { isError: true, error: '서명이 잘못되었습니다.' }

        return { isError: false, value: undefined }
    }

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

 

 

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" />
        <title>Document</title>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    </head>
    <body>
        <h1>Hello Wallet</h1>
        <button id="wallet_btn">지갑생성</button>
        <ul id="wallet_list">
            <li>CoinName: gyuCoin</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>
            <h3>Transaction</h3>
            <form id="transaction_frm">
                <ul>
                    <li>받는 사람: <input id="received" placeholder="보낼 계정" /></li>
                    <li>amount: <input id="amount" placeholder="보낼 금액" /></li>
                </ul>
                <input type="submit" value="전송" />
            </form>
        </div>

        <script type="text/javascript">
            const walletBtn = document.querySelector('#wallet_btn')
            const transaction_frm = document.querySelector('#transaction_frm')
            const WalletListBtn = document.querySelector('#wallet_list_btn')
            const createWallet = async () => {
                const response = await axios.post('/newWallet', null)
                console.log(response.data)
            }
            const submitHandler = async (e) => {
                e.preventDefault()

                const publicKey = document.querySelector('.publicKey').innerHTML
                const account = document.querySelector('.account').innerHTML
                const data = {
                    sender: {
                        publicKey,
                        account,
                    },
                    received: e.target.received.value,
                    amount: parseInt(e.target.amount.value),
                }

                const response = await axios.post('/sendTransaction', data)
            }
            const view = (wallet) => {
                const account = document.querySelector('.account')
                const publicKey = document.querySelector('.publicKey')
                const privateKey = document.querySelector('.privateKey')
                const balance = document.querySelector('.balance')

                account.innerHTML = wallet.account
                publicKey.innerHTML = wallet.publicKey
                privateKey.innerHTML = wallet.privateKey
                balance.innerHTML = wallet.balance
            }
            const getView = async (account) => {
                const response = await axios.get(`/wallet/${account}`)
                console.log(response.data)
                view(response.data)
            }

            const getWalletList = async () => {
                const walletList = document.querySelector('.wallet_list2 > ul')
                const response = await axios.post('/walletList', null)
                const list = response.data.map((account) => {
                    return `<li onClick="getView('${account}')">${account}</li>`
                })

                walletList.innerHTML = list
            }
            walletBtn.addEventListener('click', createWallet)
            WalletListBtn.addEventListener('click', getWalletList)
            transaction_frm.addEventListener('submit', submitHandler)
        </script>
    </body>
</html>

 

server.ts와 index.ts 서버 모두 실행해주어야함!!

 

지갑 생성 버튼 누르면, data 디렉토리에 지갑주소 하나 추가됨!

 

 

지갑목록 버튼을 누르면 여태 생성한 지갑주소 목록이 모두 나온다.

 

여기서 개별 지갑주소를 누르면? 

 

 

지갑생성 아래에 클릭한 정보가 입력되고,

트랜잭션에서, 선택한 지갑주소를 제외하고 아무거나 선택해서 코인을 보낼 수 있다.

 

근데 나는 이상하게, 계좌를 선택해도 다른 계좌(처음보는)가... 입력된다.. 뭐지.

728x90