블록체인 Block-Chain - 비트코인 일반적인 Transaction

dev_swan·2022년 6월 25일
0

블록체인

목록 보기
9/36
post-thumbnail

지난 시간에 블록채굴에 대한 보상 내용이 담긴 코인베이스 Transaction을 생성해보았으니 오늘은 일반적인 거래내역이 담긴 Transaction을 만들어 보겠습니다.

보내는 사람의 UTXO 가져오기

export class unspentTxOut {
    public txOutId: string;
    public txOutIndex: number;
    public account: string;
    public amount: number;

    constructor(_txOutId: string, _txOutIndex: number, _account: string, _amount: number) {
        this.txOutId = _txOutId; // Transaction의 해쉬값
        this.txOutIndex = _txOutIndex; // Transaction의 Output에 있는 각 객체의 Index번호
        this.account = _account; // 지갑 주소
        this.amount = _amount; // 금액
    }

    // 내 UTXO 내용을 가져옴
    static getMyUspentTxOuts(_account: string, _unspentTxOuts: unspentTxOut[]): unspentTxOut[] {
        // 전체 UTXO와 내 계정정보를 가지고 내 UTXO 정보를 가져와야합니다.
        return _unspentTxOuts.filter((utxo: unspentTxOut) => {
            return utxo.account === _account;
        });
    }
}

지난번에 만든 unspentTxOut Class에 나의 UTXO 내용을 가져오는 getMyUspentTxOuts 메서드를 만들어 내 지갑주소애 해당하는 UTXO 내용을 가져오게 하였습니다.

TxIn 내용 생성하기

export class TxIn {
    public txOutId: string; // Transaction 해시값
    public txOutIndex: number; // Transaction Output 리스트의 배열의 인덱스 값
    public signature?: string; // signature가 있을수도 있고 없을수도 있다.

    constructor(_txOutId: string, _txOutIndex: number, _signature: string | undefined = undefined) {
        this.txOutId = _txOutId;
        this.txOutIndex = _txOutIndex;
        this.signature = _signature;
    }

    static createTxIns(_receivedTx: any, _myUTXO: unspentTxOut[]) {
        let sum = 0;
        let txins: TxIn[] = [];

        for (let i = 0; i < _myUTXO.length; i++) {
            const { txOutId, txOutIndex, amount } = _myUTXO[i];
            const item: TxIn = new TxIn(txOutId, txOutIndex, _receivedTx.signature);

            txins.push(item);
            sum += amount;
            if (sum >= _receivedTx.amount) return { sum, txins };
        }

        return { sum, txins };
    }
}

Transactiontxin 내용을 만들기 위해 txin class에서 createTxIns 메서드를 추가하여 내가 보내려는 금액보다 내 UTXO안에 있는 객체들의 amout를 합친 값이 더 클 경우에 바로 반복문을 종료하고 지금까지 txinspush한 내용들만 return해줍니다.
조금 풀어서 설명하면 만약 내가 보내려는 금액이 100BTC 이고 내 UTXO에 60BTC, 60BTC, 50BTC가 있다고하면 반복문을 통해 60BTC , 60BTC만 txins에 추가하고 return하는 함수입니다.

TxOut 내용 생성하기

import { Wallet } from '../wallet/wallet';

export class TxOut {
    public account: string; // 지갑 주소
    public amount: number; // 금액

    constructor(_account: string, _amount: number) {
        this.account = _account;
        this.amount = _amount;
    }

    // 받는 사람 지갑 주소, 보내는 사람 지갑 주소, sum , amount
    static createTxOut(_sum: number, _receivedTx: any): TxOut[] {
        const { sender, received, amount } = _receivedTx;
        const senderAccount: string = Wallet.getAccount(sender);

        // 보내는 사람 txOut
        const senderTxOut = new TxOut(senderAccount, _sum - amount);
        // 받는 사람 txOut
        const receivedTxOut = new TxOut(received, amount);

        if (senderTxOut.amount === 0) return [receivedTxOut];
        return [senderTxOut, receivedTxOut];
    }
}

TransactionOutput 내용을 만들기 위해 txout Class 에서 createTxOut 메서드를 추가하여 보내는 사람의 TxOut 내용은 UTXO에서 가져온 TxIns의 금액에서 보내려는 금액 _sum을 뺀 값으로 TxOut을 생성해주고 받는 사람의 TxOut내용에는 보내는 사람이 보내려는 금액을 넣어 TxOut을 생성하도록 하였습니다.
만약 USER A가 보내려는 금액이 USER A의 TxIns의 금액과 같을경우에는 보내는 USER A의 TxOut의 금액이 0이기 때문에 이럴 경우에는 받는 사람의 UTXOreturn하도록 처리해주었습니다.

Transaction 생성하기

import { SHA256 } from 'crypto-js';
import { TxIn } from './txin';
import { TxOut } from './txout';
import { unspentTxOut } from './unspentTxOut';

export class Transaction {
    public hash: string;
    public txIns: TxIn[];
    public txOuts: TxOut[];

    constructor(_txIns: TxIn[], _txOuts: TxOut[]) {
        this.txIns = _txIns; // Transaction의 input의 내용들
        this.txOuts = _txOuts; // Transaction의 output의 내용들
        this.hash = this.createTransactionHash(); // Transaction의 고유한 값
    }

    // Transaction hash 만들기
    createTransactionHash(): string {
        // txoutput 내용의 각 객체의 value값들을 스트링으로 이어붙임
        const txoutConmtent: string = this.txOuts.map((v) => Object.values(v).join('')).join('');
        // txinput 내용의 각 객체의 value값들을 스트링으로 이어붙임
        const txinContent: string = this.txIns.map((v) => Object.values(v).join('')).join('');
        // 이어붙인 txoutConmtent + txinContent 값을 해쉬화하여 리턴
        return SHA256(txoutConmtent + txinContent).toString();
    }

    // UTXO 생성하기 ->  배열안에 객체 형태로 저장
    createUTXO(): unspentTxOut[] {
        // txOutId = Transaction의 해쉬값
        // txOutIndex = Transaction의 Output에 있는 각 객체의 Index번호
        // account = 지갑 주소
        // amount = 금액

        return this.txOuts.map((v, i) => {
            return new unspentTxOut(this.hash, i, v.account, v.amount);
        });
    }

    static createTransaction(_receivedTx: any, _myUTXO: unspentTxOut[]): Transaction {
        // Todo : 본인의 해당하는 UTXO -> uspentTxOut.ts -> getMyUspentTxOuts()
        // UTXO -> TxIn 내용 생성 -> txin.ts -> createTxIns()
        const { sum, txins } = TxIn.createTxIns(_receivedTx, _myUTXO);

        // TxIn -> TxOut 내용 생성 -> txout.ts -> createTxOut()
        const txOuts: TxOut[] = TxOut.createTxOut(sum, _receivedTx);

        // 새로운 Transaction 생성 new Transaction()
        const transaction = new Transaction(txins, txOuts);

        return transaction;
    }
}

마지막으로 Transaction Class에서 Transaction을 생성하는 createTransaction 메서드를 생성해주었습니다. TxIn ClasscreateTxIns 메서드를 가지고 TxIns를 생성하고 TxOut ClasscreateTxOut 메서드를 가지고 TxOut을 생성해준뒤 TxIns 내용과 TxOuts 내용을 가지고 새로운 Transaction을 생성해주고 만든 Transactionreturn하도록 처리하였습니다.

Wallet에서 블록체인 HTTP 서버로 요청보내기

/* 지갑 서버 */

// 트랜잭션 보내기
app.post('/sendTransaction', async (req, res) => {
    const {
        sender: { account, publicKey },
        received,
        amount,
    } = req.body;

    const signature = Wallet.createSign(req.body);

    const txObj = {
        sender: publicKey, // 공개키
        received, // 받는 지갑주소
        amount, // 금액
        signature, // 서명
    };

    const response = await request.post('/sendTransaction', txObj);

    res.json({});
});

Wallet 서버에서 req.body로 받은 보내는 지갑 주소와 공개키, 받는 지갑 주소, 보낼 금액을 가지고 signature을 생성해준뒤 공개키, 받는지갑 주소, 금액, 서명을 txObj 객체에 담아서 블록체인 HTTP 서버로 요청을 보내줍니다.

/* 블록체인 HTTP 서버 */

app.post('/sendTransaction', (req, res) => {
    try {
        const receivedTx: ReceviedTx = req.body;
        // 새로운 Transaction을 생성합니다.

        const transaction = Wallet.sendTransaction(receivedTx, ws.getUnspentTxOuts());

        // TransactionPool에 Transaction 내용 추가
        ws.appendTransactionPoll(transaction);
        // UTXO 내용 수정 / UTXO 내용을 최신화하는 함수를 생성합니다. 인자값 Transaction
        ws.updateUTXO(transaction);
        // 트랜잭션이 발동할때마다 브로드 캐스트해주어야 합니다.
        const mesasge: Message = {
            type: MessageType.receivedTx,
            payload: transaction,
        };
        // 다른 노드에게 브로드 캐스트로 트랜잭션 내용을 전달해줍니다.
        ws.broadcast(mesasge);
    } catch (e: any) {
        if (e instanceof Error) console.error(e.message);
    }
    res.json([]);
});
/* wallet.ts - sendTransaction 메서드 */

static sendTransaction(_receivedTx: any, _unspentTxOuts: unspentTxOut[]): Transaction {
        // Todo : 서명 검증
        // 보내는사람:공개키, 받는사람:계정, 보낼금액
        const verify = Wallet.getVerify(_receivedTx);
        if (verify.isError) throw new Error(verify.error);

        console.log(verify.isError);
        // Todo : 보내는 사람의 지갑 정보 최신화
        const myWallet = new this(_receivedTx.sender, _receivedTx.signature, _unspentTxOuts);
        // Todo : Balance 확인
        if (myWallet.balance < _receivedTx.amount) throw new Error('잔액이 부족합니다.');
        // Todo : Transaction 만드는 과정
        const myUTXO: unspentTxOut[] = unspentTxOut.getMyUspentTxOuts(myWallet.account, _unspentTxOuts);
        const transaction: Transaction = Transaction.createTransaction(_receivedTx, myUTXO);

        return transaction;
    }

/* wallet.ts - getVerify 메서드  */

// 서명 검증
    static getVerify(_receivedTx: ReceviedTx): Failable<undefined, string> {
        const { sender, received, amount, signature } = _receivedTx;
        const data: [string, string, number] = [sender, received, amount];
        const hash: string = SHA256(data.join('')).toString(); // data를 hash화

        // Todo : 타원곡선 알고리즘 사용
        const keyPair = ec.keyFromPublic(sender, 'hex');
        // 서명 검증하는 부분 ( 데이터 위.변조 , 신원증명 )
        const isVerify = keyPair.verify(hash, signature);

        if (!isVerify) return { isError: true, error: '서명이 올바르지 않습니다.' };

        return { isError: false, value: undefined };
    }

블록체인 HTTP서버에서 Wallet 서버에서 받은 txObj 객체와 보내려는 사람의 UTXO 내용을가지고 sendTransactrion 메소드로 Trnasaction을 생성해줍니다.
Wallet Class에 있는 sendTransaction 메소드를 보면 우선 signaturegetVerify 메소드를 사용하여 검증을 해주고 보내려는 사람의 최신화된 지갑정보를 만들고 혹시 보내려는 사람의 지갑에 금액이 보내고 싶은 금액보다 적을경우에는 Error로 빠지도록 처리하였습니다.
이제 보내려는 사람의 해당하는 UTXO 정보와 인자값으로 받은 txObj 내용을 가지고 Transaction Class에서 미리 만들어둔 createTransaction 메서드로 Transaction을 생성해줍니다.

0개의 댓글