본문으로 바로가기

Libbitcoin 라이브러리를 사용하면 원하는 거래를 쉽게 생성할수 있습니다.

실제 비트코인의 mainnet을 사용하여 Transaction을 확인하는것은 비트코인을 구입해야 하기 때문에 이번 포스팅에서는 mainnet이 아닌 testnet을 사용하여 Transaction을 생성해보도록 하겠습니다.


본 예제는 하기의 Tutorial을 참고로 사용하였습니다.

http://aaronjaramillo.org/libbitcoin-building-a-raw-transaction


그리고 하기의 작성코드는 다음의 github에서 다운로드 받을수 있습니다.

https://github.com/ihpark92/Libbitcoin_Tutorial/


이전 포스팅에서 Testnet에서 비트코인을 요청하여 받는 방법에 대해서 설명을 하였는데, 본 예제를 실행하기 위해서는 Testnet 지갑의 연상기호 단어열(Mnemonic code words)가 필요하기 떄문에, bitcoin core를 사용하지않고, testnet 사이트에서 지갑을 생성하여 사용하도록 하겠습니다.


Testnet에서 다음 상단의 Get a free wallet 을 선택하여 지갑을 생성합니다.



지갑을 생성한후 Setting 에서 생성한 지갑의 연상기호 단어를 확인해 놓습니다.

하기의 Security  의 Backup Phrase 메뉴를 통해서 12개의 연상기호 단어열을 확인할수 있습니다.



그리고 이전 포스팅을 참고하여 생성한 지갑을 사용하여 Testnet 비트코인을 요청합니다.

요청한 비트코인의 거래목록을 다음과 같이 확인할수 있으며, 해당 거래의 Transaction hash 값이 이후 예제에서 UTXO로 사용됩니다.

여기까지 Transaction 을 생성하기 위한 사전작업이 완료되었습니다. 그럼 앞으로 생성하게 될 Transaction의 포맷을 먼저 확인해보도록 하겠습니다. 

거래는 다음과 같이 입력부와 출력부로 구성이 됩니다. 라이브러리를 사용하여 하기의 포맷으로 비트배열을 생성하도록 하겠습니다.





먼저 Testnet을 사용하기 위해서 이전에 작성한 HD_Wallet.cpp을 변경하여 HD_Wallet_Testnet.cpp로 저장하도록 하겠습니다.

이전에 작성한 3개의 생성자에서 testnet을 추가하여 testnet용 개인키를 생성하도록 변경을 합니다.


HD_Wallet()
{
entropy = data_chunk(16);
pseudo_random_fill(entropy);
mnemonic = wallet::create_mnemonic(entropy);
seed = to_chunk(wallet::decode_mnemonic(mnemonic));
privateKey = wallet::hd_private(seed, wallet::hd_private::testnet);
publicKey = privateKey.to_public();
}


다음의 자식키의 주소를 생성하는 부분도 0x6f 를 추가하여 testnet임을 명시합니다.


wallet::payment_address childAddress(int index)
{
return wallet::payment_address(wallet::ec_public(childPublicKey(index).point()), 0x6f);
}


HD_Wallet_Testnet.cpp의 작성을 완료한후에 rawTX.cpp로 main 함수의 작성부분을 저장하도록 합니다.

먼저 다음과 같이 선언합니다.


#include <bitcoin/bitcoin.hpp>
#include "HD_Wallet_Testnet.cpp"
#include <string.h>
using namespace bc;
using namespace bc::wallet;
using namespace bc::machine;
using namespace bc::chain;


그리고 사용자 입력을 받기위한 함수를 정의합니다.


std::string getInput2()
{
std::string input;
getline(cin, input);
return input;
}


그리고 다음과 같이 지갑을 생성한후에 공개키를 생성합니다.

여기에서 입력받는 연상기호가 위에서 생성한 testnet 지갑의 연상기호 단어열입니다.

연상기호 단어열을 사용하여 개인키와 공개키를 생성하는 과정은 다음 사이트에서 확인 가능하니 참고하시기 바랍니다.


int main()
{
//import Wallet
std::cout << "Import Wallet Via Mnemonic: " << std::endl;
std::string Mnemonic1 = getInput2();
HD_Wallet wallet1(split(Mnemonic1));
std::cout << "\nChild Index To Spend From: " << std::endl;
int child = atoi(getInput2().c_str());
data_chunk pubkey1 = to_chunk(wallet1.childPublicKey(child).point());


다음으로 비트코인을 전송할 주소를 입력받고 주소를 사용하여 잠금 스크립트를 생성합니다.

여기서 전송할 주소는 testnet 지갑에서 Request 메뉴를 통해 생성한 주소를 입력합니다.

일반적으로 잠금 스크립트는 scriptPubKey 라고 불려집니다.

Public key를 기반으로 권한설정을 한것으로 이 거래를 잠금해제하고 소비하기 위해서 개인의 Private key가 필요하게 됩니다.


//Make Output//
std::cout << "\nEnter Destination Address: " << std::endl;
std::string Destination = getInput2(); //"";
payment_address destinationAddy(Destination);
script outputScript = script().to_pay_key_hash_pattern(destinationAddy.hash());


다음으로 전송할 비트코인갯수를 입력합니다.

입력된 비트코인 갯수는 사토시단위(1억분의1 비트코인)로 변환됩니다.

여기까지 진행하면 거래의 출력부분이 완성됩니다.


std::cout << "\nEnter Amount(BTC) To Send: " << std::endl;
std::string BTC = getInput2();
uint64_t Satoshis;
decode_base10(Satoshis, BTC, 8);
output output1(Satoshis, outputScript);
std::cout << "\nAmount: " << encode_base10(output1.value(), 8) << "BTC : Output Script: " << output1.script().to_string(0) << "\n" << std::endl;


여기까지 진행하면 위의 그림에서처럼 Transaction의 출력부가 완성됩니다.


다음은 거래의 입력부분의 생성부분입니다.


먼저 UTXO를 생성하기 위해 거래 hash 값과 해당 거래의 인덱스를 입력받습니다.

이전에 testnet에서 비트코인을 요청하여 받은경우 해당 transaction의 hash 값을 하기 UTXO hash 값으로 입력하면 됩니다.

이전 거래의 출력값과 인덱스를 사용하여 UTXO를 생성합니다.


//Get UTXO
std::cout << "\nEnter UTXO Hash To Spend: " << std::endl;
std::string hashString = getInput2(); //"";
std::cout << "\nEnter Output Index: " << std::endl;
std::string index = getInput2();
uint32_t index1 = atoi(index.c_str());
hash_digest utxoHash;
decode_hash(utxoHash, hashString);
output_point utxo(utxoHash, index1);


다음으로 UTXO에 포함되어있는 잠금 스크립트를 가져옵니다.


//Previous Locking Script
script lockingScript = script().to_pay_key_hash_pattern(bitcoin_short_hash(pubkey1));


다음으로 UTXO와 sequence 값을 사용하여 거래의 입력을 생성합니다.

거래의 입력값은 이전거래의 출력값을 의미하며, 이것은 위에서 생성한 UTXO가 사용되어집니다.

거래의 입력값과 출력값에 대해서는 이전 포스팅을 참고하기기 바랍니다.


//make Input
input input1 = input();
input1.set_previous_output(utxo);
input1.set_sequence(0xffffffff);


그리고 다음과 같이 잠금스크립트를 화면상에 출력해줍니다.


std::cout << "\nPrevious Locking Script: " << std::endl;
std::cout << lockingScript.to_string(0) << "\n" << std::endl;


이제 입력과 출력값이 모두 생성되었기 떄문에 이것을 사용하여 거래를 생성합니다.


//build TX
transaction tx = transaction();
tx.inputs().push_back(input1);
tx.outputs().push_back(output1);


다음으로 완성된 거래를 개인키와 잠금 스크립트를 사용하여 서명을 합니다.


//Endorse TX
endorsement sig;
if(lockingScript.create_endorsement(sig, wallet1.childPrivateKey(1).secret(), lockingScript, tx, 0u, all))
{
std::cout << "Signature: " << std::endl;
std::cout << encode_base16(sig) << "\n" << std::endl;
}


마지막으로 생성한 서명을 사용하여 서명이 추가된 거래를 생성합니다.

여기까지 완료하게 되면 실제 비트코인 네트워크에 전송이 가능한 거래가 완성됩니다.


//make Sig Script
operation::list sigScript;
sigScript.push_back(operation(sig));
sigScript.push_back(operation(pubkey1));
script unlockingScript(sigScript);
//Make Signed TX
tx.inputs()[0].set_script(unlockingScript);
std::cout << "Raw Transaction: " << std::endl;
std::cout << encode_base16(tx.to_data()) << std::endl;


이와같이 완성된 잠금스크립트와 해제스크립트는 다음과 같은 형식으로 스택에서 계산이 되어 유효성을 체크하게 됩니다.


출력결과는 다음과 같습니다.

연상기호 단어열을 입력받는 부분은 동일한 단어열을 사용할경우 제가 생성한 지갑을 사용하게 되기 떄문에 제외하였습니다.

이 부분은 각자 Testnet에서 지갑을 생성한후 단어열을 확인하여 입력하면 됩니다.

하기에 결과로 나온 Raw Transaction이 실제 비트코인 블록에 포함되는 거래가 됩니다.