Bitcoin Raw Transaction 사용
Raw Transaction
이전 아티클에서 사용한 sendtoaddress 명령은 비트코인을 Send하는 편리한 방법이지만, 기술적으로 트랜잭션을 훨씬 유연하게 구성하기 위해서는 Raw Transaction을 사용할 수 있다. Raw Transaction은 가장 낮은 수준에서 트랜잭션을 다루므로, 만약 실수로 잘못된 Raw Transaction을 생성하면 코인을 잃어 버릴 수 있으므로 주의해야 한다.
Bitcoin 트랜잭션의 생성은 UTXO (Unspent Transaction Output)로부터 시작한다. UTXO는 아직 사용하지 않은 코인에 대한 정보를 가지고 있는데, UTXO는 TransactionID + vout 으로 구성된다. 어떤 사람이 나에게 코인을 보냈다면, 이는 어떤 사람이 자신의 Transaction의 Output에 내 주소를 적고 BTC 금액을 적었다는 것을 의미한다. 이때 Transaction Outputs은 배열로서 복수 개를 지정할 수 있으므로, 배열의 인덱스를 나타내는 vout 에 내 주소와 금액이 적힐 것이다. 따라서, 내가 사용할 수 있는 코인은 해당 Transaction ID와 vout 에 있는 코인이 되고, 이것이 나의 UTXO가 된다. 내가 가진 비트코인의 총량은 (내가 private key를 가지고 있으며) 아직 사용하지 않은 UTXO들의 총량과 같다.
앞의 sendtoaddress 명령에서 내가 가진 UTXO들 중 어떤 UTXO를 사용할 지를 지정할 수 없었지만(월렛이 자동처리), Raw Transaction에서는 어떤 UTXO를 골라서 사용할 지를 지정할 수 있다. 단순히 옵션이 아니라 실제 Raw Transaction을 생성하기 위해서는, 어떤 UTXO를 사용할 지를 먼저 골라야 한다.
현재 내가 가지고 있는 월렛에 어떤 UTXO가 있는지를 체크하기 위해서는 listunspent 명령을 사용한다.
$ bitcoin-cli listunspent [ { "txid": "07e61497582c3a041be8c3ba360433dcc9519d89495181c2e5959d88128d7504", "vout": 0, "address": "tb1qnr5uz52wwam5sc0g8m4c2mm3smtk67rqtcknmu", "scriptPubKey": "001498e9c1514e77774861e83eeb856f7186d76d7860", "amount": 0.01869117, "confirmations": 351, "spendable": true, "solvable": true, "desc": "wpkh([9d1456ef/0'/1'/1']02209eb376b2b140dee515269fdb91a6e07c7845cd9b74dacd2e6b16615afe7dc0)#gkmeft2p", "safe": true }, { "txid": "c23a75e6ad2a767b32224306b3704ee51b032be6bf861a7285e0561700c6de18", "vout": 0, "address": "tb1qqjev5uc6vjgudvjnwfz99llgj02cw5g5yavmjw", "label": "", "scriptPubKey": "001404b2ca731a6491c6b253724452ffe893d5875114", "amount": 0.00100000, "confirmations": 414, "spendable": true, "solvable": true, "desc": "wpkh([9d1456ef/0'/0'/2']0391e5e3c31a19cab56b2be5b36a19eac2d673620ef83e60c0551752dba2726e1e)#3at37evr", "safe": true } ]
위의 UTXO들 중 첫번째 UTXO를 사용하려면 txid + vout 즉, 07e61497582c3a041be8c3ba360433dcc9519d89495181c2e5959d88128d7504 와 0 을 사용하면 된다. 이것이 새로 생성하는 Raw Transaction의 Transaction Input이 된다. 일반적으로 Transaction Inputs 안에 여러 개의 UTXO들이 들어갈 수 있지만, 여기서는 하나의 UTXO만을 사용해 본다.
createrawtransaction 명령
bitcoin-cli에서 Raw Transaction은 createrawtransaction 명령을 사용하여 생성한다. createrawtransaction 명령에는 기본적으로 어떤 UTXO를 사용할 지를 transaction inputs 에 넣게 되고, 누구에게 얼마만큼의 코인을 줄 지를 transaction outputs 에 넣으며 트랜잭션을 만들게 된다.
Raw Transaction의 Transaction Outputs에는 어떤 사람에게 얼마만큼을 보내는지를 지정하게 된다. Transaction Outputs에는 하나 이상의 Output들을 지정하게 되는데, 이들 Output들에 있는 금액들의 합계는 Transaction Inputs의 합계보다 작아야 한다. Transaction Inputs - Transaction Outputs 의 잔액은 Transaction Fee로 취급되어 Miner가 갖게 된다.
UTXO를 사용할 때 주의할 점은 UTXO는 항상 그 UTXO 전체를 사용하게 된다는 점이다. 위의 첫번째 UTXO는 0.01869117 BTC를 가지고 있는데, Transaction Output에서 B 라는 사람에게 0.00869117 BTC를 보내도록 하였다면, 트랜잭션 잔액은 0.01 BTC가 된다. 만약 이렇게 트랜잭션을 생성하면, 0.01 BTC는 내 지갑으로 돌아 오는 것이 아니라, Miner에게 트랜잭션 Fee로 자동 지불되게 된다. 따라서, 0.01 BTC를 내 지갑이 가지고 있는 Bitcoin 주소로 보내야 하는데, 이를 흔히 "change address" 라고 부른다. 다만, 실제로 트랜잭션을 생성할 때 Transaction Fee를 넣어야 하므로, 0.01 BTC에서 약간의 Transaction Fee를 뺀 값을 넣게 될 것이다.
다음은 createrawtransaction 명령을 사용하여, 위의 첫번째 UTXO를 사용하여 (즉, transaction input으로 하여) 수신자(tb1qqjev5uc6vjgudvjnwfz99llgj02cw5g5yavmjw)에게 0.001 BTC를 보내고, 잔액을 나의 change address (tb1q9ryusv7lpd4ucmjpu3anmeyqjqk335sznm2fnt)에 보내는 것을 예시한 것이다.
$ bitcoin-cli getnewaddress tb1q9ryusv7lpd4ucmjpu3anmeyqjqk335sznm2fnt (change address) $ bitcoin-cli createrawtransaction '[ { "txid": "07e61497582c3a041be8c3ba360433dcc9519d89495181c2e5959d88128d7504", "vout": 0 } ]' '[ { "tb1qqjev5uc6vjgudvjnwfz99llgj02cw5g5yavmjw": 0.001 }, { "tb1q9ryusv7lpd4ucmjpu3anmeyqjqk335sznm2fnt": 0.0176 } ]' 020000000104758d12889d95e5c2815149899d51c9dc330436bac3e81b043a2c589714e6070000000000ffffffff02a08601000000000016001404b2ca731a6491c6b253724452ffe893d587511400db1a000000000016001428c9c833df0b6bcc6e41e47b3de480902d18d20200000000
createrawtransaction은 inputs, outputs, locktime(default=0), replacable(default=false) 등 4개의 파라미터를 갖는데, 위의 예는 inputs과 outputs 만을 사용한 것이다. inputs은 Transaction Input들을 말하는 것으로 어떤 UTXO들을 사용할 지를 지정하게 되고, outputs는 Transaction Output들로서, 수신자 주소와 코인수량을 지정하게 된다. 위의 bitcoin-cli createrawtransaction 명령은 하나의 라인으로 적게 되는데, inputs과 outputs 파라미터는 JSON Array를 갖는다. 위의 inputs은 하나의 (현재의) UTXO를 가지고 있고, outputs는 2개의 (미래의) UTXO를 가지고 있다. 이때, inputs (0.01869117) - outputs (0.001 + 0.0176) = 0.00009117 BTC가 Transaction Fee가 된다.
생성된 Raw Transaction을 디코딩해서 살펴보기 위해서는 decoderawtransaction 명령을 사용할 수 있다.
$ bitcoin-cli decoderawtransaction 020000000104758d12889d95e5c2815149899d51c9dc330436bac3e81b043a2c589714e6070000000000ffffffff02a08601000000000016001404b2ca731a6491c6b253724452ffe893d587511400db1a000000000016001428c9c833df0b6bcc6e41e47b3de480902d18d20200000000 { "txid": "8c7be9a9f5f713aac0a5eaed4991fd1827912b6a294225f1dd8c7c2f7dbb6a64", "hash": "8c7be9a9f5f713aac0a5eaed4991fd1827912b6a294225f1dd8c7c2f7dbb6a64", "version": 2, "size": 113, "vsize": 113, "weight": 452, "locktime": 0, "vin": [ { "txid": "07e61497582c3a041be8c3ba360433dcc9519d89495181c2e5959d88128d7504", "vout": 0, "scriptSig": { "asm": "", "hex": "" }, "sequence": 4294967295 } ], "vout": [ { "value": 0.00100000, "n": 0, "scriptPubKey": { "asm": "0 04b2ca731a6491c6b253724452ffe893d5875114", "hex": "001404b2ca731a6491c6b253724452ffe893d5875114", "reqSigs": 1, "type": "witness_v0_keyhash", "addresses": [ "tb1qqjev5uc6vjgudvjnwfz99llgj02cw5g5yavmjw" ] } }, { "value": 0.01760000, "n": 1, "scriptPubKey": { "asm": "0 28c9c833df0b6bcc6e41e47b3de480902d18d202", "hex": "001428c9c833df0b6bcc6e41e47b3de480902d18d202", "reqSigs": 1, "type": "witness_v0_keyhash", "addresses": [ "tb1q9ryusv7lpd4ucmjpu3anmeyqjqk335sznm2fnt" ] } } ] }
signrawtransactionwithwallet 명령
Raw Transaction을 생성한 후에, 이 트랜잭션 데이타를 자신의 private key로 서명(Sign)하여야 하는데, 이를 위해 signrawtransactionwithwallet 명령을 사용한다. (주: v0.18 이전에는 signrawtransaction 명령을 지원하였으나 deprecate 되었다.) signrawtransactionwithwallet 명령은 자신의 월렛으로부터 트랜잭션을 서명하는 것이고, private key를 지정해서 서명하기 위해서는 signrawtransactionwithkey 명령을 사용할 수 있다.
signrawtransactionwithwallet 뒤에는 Raw Transaction을 나타내는 Hex String 을 지정하고, 이를 월렛의 private key를 사용하여 서명한 후, 서명된 트랜잭션 데이타를 출력하게 된다.
$ bitcoin-cli signrawtransactionwithwallet 020000000104758d12889d95e5c2815149899d51c9dc330436bac3e81b043a2c589714e6070000000000ffffffff02a08601000000000016001404b2ca731a6491c6b253724452ffe893d587511400db1a000000000016001428c9c833df0b6bcc6e41e47b3de480902d18d20200000000 { "hex": "0200000000010104758d12889d95e5c2815149899d51c9dc330436bac3e81b043a2c589714e6070000000000ffffffff02a08601000000000016001404b2ca731a6491c6b253724452ffe893d587511400db1a000000000016001428c9c833df0b6bcc6e41e47b3de480902d18d20202473044022063ccecb2e4f5f52d5105229f21a6c341fbb138666d6ef848eb073316b6eb847c022056eab1e158966f5c44c5a24343d421a7ba641946aada4fe1d9779a7d3baccd42012102209eb376b2b140dee515269fdb91a6e07c7845cd9b74dacd2e6b16615afe7dc000000000", "complete": true }
sendrawtransaction 명령
트랜잭션이 서명되었으면, 이를 다른 노드에 Broadcasting 하게 되는데, 이를 위해 sendrawtransaction 명령을 사용할 수 있다. sendrawtransaction 명령은 서명된 트랜잭션 Hex String을 파라미터로 가지며, Broadcasting 후에 Transaction ID를 출력한다. 아래 예에서 8c7be9a9f5f713aac0a5eaed4991fd1827912b6a294225f1dd8c7c2f7dbb6a64 은 새로 생성된 Transaction ID이다.
$ bitcoin-cli sendrawtransaction 0200000000010104758d12889d95e5c2815149899d51c9dc330436bac3e81b043a2c589714e6070000000000ffffffff02a08601000000000016001404b2ca731a6491c6b253724452ffe893d587511400db1a000000000016001428c9c833df0b6bcc6e41e47b3de480902d18d20202473044022063ccecb2e4f5f52d5105229f21a6c341fbb138666d6ef848eb073316b6eb847c022056eab1e158966f5c44c5a24343d421a7ba641946aada4fe1d9779a7d3baccd42012102209eb376b2b140dee515269fdb91a6e07c7845cd9b74dacd2e6b16615afe7dc000000000 8c7be9a9f5f713aac0a5eaed4991fd1827912b6a294225f1dd8c7c2f7dbb6a64
트랜잭션이 Send 되면, 사용된 UTXO는 listspent 에서 삭제되고, 약간의 시간이 경과한 후 새 Transaction에 자신의 UTXO가 있으면 이것이 새 UTXO set에 표시된다.
fundrawtransaction 명령
위에서 createrawtransaction 명령을 사용할 때, 개발자는 항상 UTXO input과 change address를 지정해 주어야 했다. 이 명령보다 좀 더 간편하게 Raw Transaction을 만드는 방법으로 fundrawtransaction 명령을 사용할 수 있는데, 이 fundrawtransaction은 UTXO input과 change address를 bitcoind 에서 자동으로 채워넣게 하기 때문에, 수신자 주소와 보내는 BTC 수량만을 지정하여 Raw Transaction을 만들 수 있다.