관리 메뉴

HAMA 블로그

[비트코인] Raw Transaction 만들기 (sig 는 어떻게 만들어 지는가?) 본문

블록체인

[비트코인] Raw Transaction 만들기 (sig 는 어떻게 만들어 지는가?)

[하마] 이승현 (wowlsh93@gmail.com) 2018. 4. 7. 20:53


scriptSig 스크립트의 시작에 나오는 sig 가 어떻게 만들어지는지 궁금하진 않는가? 즉 무엇을 서명한 것인가? 
그에 대한 답이 아래 코드에 있다. 이렇게 복잡하게 만들어 질 줄이야 ㅎㅎ 

관련 레퍼런스: http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html
코드출처: https://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx

트랜잭션이 만들어지는 단계 (19단계) :

  1. 4바이트의 버전 필드 추가 : 01000000
  2. 1바이트의 몇번째 input 인지 추가: 01
  3. 우리가 소비하고자 하는 지난번 트랜잭션의 32-byte 해시: 
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
  4. 4바이트의 3번의 트랜잭션에서의 output 인덱스의 UTXO (output number 2 = output index 1):
     01000000
  5. 트랜잭션을 서명할 목적으로 임시 scriptSig 를 만드는데, 1바이트의 길이 (0x19=25bytes) 를 채운다 :
     19
  6.  우리가 사용 할 UXTO의  scriptPubKey 를 이용해서 임시 scriptSig 를 만든다:
     76a914010966776006953d5567439e5e39f86a0d273bee88ac
  7. 4바이트 시퀀스 필드를 채운다: ffffffff
  8. 1바이트의 새로운 트랜잭션의 아웃풋 넘버를 채운다 : 01
  9. 거래 할 금액에 대해 8바이트 필드를 쓴다. 우리가 소비할 수 있는 총 UTXO 에서 fee 를 위한 0.001BTC 를 제외한다. (0.999 BTC, or 99900000 Satoshis): 605af40500000000
  10. output 스크립트의 길이를 1바이트로 채운다.(0x19 or 25 bytes): 19
  11. 실제 아웃풋 스크립트를 작성한다(
    대략  OP_DUP OP_HASH160 pubKeyHash  OP_EQUALVERIFY OP_CHECKSIG 이런것이 들어간다.pubKeyHash 는 public key 를 해쉬한 값인데, 내가 돈을 보내는 사람의 주소에서 생성 될 수 있다.):
     76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
  12. 4바이트 "lock time" 필드를 채운다: 00000000
  13. 4바이트의 "hash code type" 를 쓴다.(우리 경우는 1이며 SIGHASH_ALL): 01000000

    (일단 여기까지 완전한 트랜잭션이 만들어 졌다. 이후에는 이 트랜잭션에 대한 서명을 할 차례이다.) 

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    19
    76a914010966776006953d5567439e5e39f86a0d273bee88ac
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000
    01000000
    
  14. 이중-SHA256 해시로 이 전체 구조에 대해서 해쉬한다.:
     9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e

  15. 개인키로 private/public 키 쌍을 생성한다. 그리고 14번의 해시를 서명한다:
     30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06 이 서명에 뒤에 1바이트 해시코드를 넣는다. 01. 공개키는 다음과 같다: 0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  16. 진짜 scriptSig 를 구축한다: <One-byte script OPCODE containing the length of the DER-encoded signature plus 1 (the length of the one-byte hash code type)>|< The actual DER-encoded signature plus the one-byte hash code type>|< One-byte script OPCODE containing the length of the public key>|<The actual public key>
  17. 스텝 5에서의 scriptSig 길이를 진짜 scriptSig 의 길이로 변경한다 : 8c
  18. 스텝 6에서 생성한 임시 scriptSig 를 스텝16의 진짜 scriptSig 로 변경한다:
     4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  19. 마지막으로 스텝 13에서 추가한 4바이트 해시 코드타입을 제거한다. 

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    8c
    4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000

https://hamait.tistory.com/1003  <-- 여기로 가면 위의 과정을 좀 더 비쥬얼하게 만들어 놓았다.

위 19단계에 해당하는 파이썬 코드는 다음과 같다.

#bitcointools
from deserialize import parse_Transaction, opcodes
from BCDataStream import BCDataStream
from base58 import bc_address_to_hash_160, b58decode, public_key_to_bc_address, hash_160_to_bc_address

import ecdsa_ssl

import Crypto.Hash.SHA256 as sha256
import Crypto.Random

#transaction, from which we want to redeem an output
HEX_TRANSACTION="010000000126c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb000000008b48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078ffffffff02b06c191e010000001976a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac00e1f505000000001976a914010966776006953d5567439e5e39f86a0d273bee88ac00000000"

#output to redeem. must exist in HEX_TRANSACTION
OUTPUT_INDEX=1

#address we want to send the redeemed coins to.
#REPLACE WITH YOUR OWN ADDRESS, unless you're feeling generous
SEND_TO_ADDRESS="1L4xtXCdJNiYnyqE6UsB8KSJvqEuXjz6aK"

#fee we want to pay (in BTC)
TX_FEE=0.001

#constant that defines the number of Satoshis per BTC
COIN=100000000

#constant used to determine which part of the transaction is hashed.
SIGHASH_ALL=1

#private key whose public key hashes to the hash contained in scriptPubKey of output number *OUTPUT_INDEX* in the transaction described in HEX_TRANSACTION
PRIVATE_KEY=0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

def dsha256(data):
return sha256.new(sha256.new(data).digest()).digest()

tx_data=HEX_TRANSACTION.decode('hex_codec')
tx_hash=dsha256(tx_data)

#here we use bitcointools to parse a transaction. this gives easy access to the various fields of the transaction from which we want to redeem an output
stream = BCDataStream()
stream.write(tx_data)
tx_info = parse_Transaction(stream)

if len(tx_info['txOut']) < (OUTPUT_INDEX+1):
raise RuntimeError, "there are only %d output(s) in the transaction you're trying to redeem from. you want to redeem output index %d" % (len(tx_info['txOut']), OUTPUT_INDEX)

#this dictionary is used to store the values of the various transaction fields
# this is useful because we need to construct one transaction to hash and sign
# and another that will be the final transaction
tx_fields = {}

##here we start creating the transaction that we hash and sign
sign_tx = BCDataStream()

##first we write the version number, which is 1
tx_fields['version'] = 1
sign_tx.write_int32(tx_fields['version'])

##then we write the number of transaction inputs, which is one
tx_fields['num_txin'] = 1
sign_tx.write_compact_size(tx_fields['num_txin'])

##then we write the actual transaction data
#'prevout_hash'
tx_fields['prevout_hash'] = tx_hash
sign_tx.write(tx_fields['prevout_hash']) #hash of the the transaction from which we want to redeem an output
#'prevout_n'
tx_fields['output_index'] = OUTPUT_INDEX
sign_tx.write_uint32(tx_fields['output_index']) #which output of the transaction with tx id 'prevout_hash' do we want to redeem?

##next comes the part of the transaction input. here we place the script of the *output* that we want to redeem
tx_fields['scriptSigHash'] = tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey']
#first write the size
sign_tx.write_compact_size(len(tx_fields['scriptSigHash']))
#then the data
sign_tx.write(tx_fields['scriptSigHash'])

#'sequence'
tx_fields['sequence'] = 0xffffffff
sign_tx.write_uint32(tx_fields['sequence'])

##then we write the number of transaction outputs. we'll just use a single output in this example
tx_fields['num_txout'] = 1
sign_tx.write_compact_size(tx_fields['num_txout'])

##then we write the actual transaction output data
#we'll redeem everything from the original output minus TX_FEE
tx_fields['value'] = tx_info['txOut'][OUTPUT_INDEX]['value']-(TX_FEE*COIN)
sign_tx.write_int64(tx_fields['value'])

##this is where our scriptPubKey goes (a script that pays out to an address)
#we want the following script:
#"OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG"
address_hash = bc_address_to_hash_160(SEND_TO_ADDRESS)

#chr(20) is the length of the address_hash (20 bytes or 160 bits)
scriptPubKey = chr(opcodes.OP_DUP) + chr(opcodes.OP_HASH160) + \
chr(20) + address_hash + chr(opcodes.OP_EQUALVERIFY) + chr(opcodes.OP_CHECKSIG)

#first write the length of this lump of data
tx_fields['scriptPubKey'] = scriptPubKey
sign_tx.write_compact_size(len(tx_fields['scriptPubKey']))

#then the data
sign_tx.write(tx_fields['scriptPubKey'])

#write locktime (0)
tx_fields['locktime'] = 0
sign_tx.write_uint32(tx_fields['locktime'])

#and hash code type (1)
tx_fields['hash_type'] = SIGHASH_ALL
sign_tx.write_int32(tx_fields['hash_type'])

#then we obtain the hash of the signature-less transaction (the hash that we sign using our private key)
hash_scriptless = dsha256(sign_tx.input)

##now we begin with the ECDSA stuff.
## we create a private key from the provided private key data, and sign hash_scriptless with it
## we also check that the private key's corresponding public key can actually redeem the specified output

k = ecdsa_ssl.KEY()
k.generate(('%064x' % PRIVATE_KEY).decode('hex'))

#here we retrieve the public key data generated from the supplied private key
pubkey_data = k.get_pubkey()

#then we create a signature over the hash of the signature-less transaction
sig_data=k.sign(hash_scriptless)

#a one byte "hash type" is appended to the end of the signature (https://en.bitcoin.it/wiki/OP_CHECKSIG)
sig_data = sig_data + chr(SIGHASH_ALL)

#let's check that the provided privat key can actually redeem the output in question
if (bc_address_to_hash_160(public_key_to_bc_address(pubkey_data)) != tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2]):
bytes = b58decode(SEND_TO_ADDRESS, 25)
raise RuntimeError, "The supplied private key cannot be used to redeem output index %d\nYou need to supply the private key for address %s" % \
(OUTPUT_INDEX, hash_160_to_bc_address(tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2], bytes[0]))

##now we begin creating the final transaction. this is a duplicate of the signature-less transaction,
## with the scriptSig filled out with a script that pushes the signature plus one-byte hash code type, and public key from above, to the stack

final_tx = BCDataStream()
final_tx.write_int32(tx_fields['version'])
final_tx.write_compact_size(tx_fields['num_txin'])
final_tx.write(tx_fields['prevout_hash'])
final_tx.write_uint32(tx_fields['output_index'])

##now we need to write the actual scriptSig.
## this consists of the DER-encoded values r and s from the signature, a one-byte hash code type, and the public key in uncompressed format
## we also need to prepend the length of these two data pieces (encoded as a single byte
## containing the length), before each data piece. this length is a script opcode that tells the
## Bitcoin script interpreter to push the x following bytes onto the stack

scriptSig = chr(len(sig_data)) + sig_data + chr(len(pubkey_data)) + pubkey_data

#first write the length of this data
final_tx.write_compact_size(len(scriptSig))

#then the data
final_tx.write(scriptSig)

##and then we simply write the same data after the scriptSig that is in the signature-less transaction,
# leaving out the four-byte hash code type (as this is encoded in the single byte following the signature data)

final_tx.write_uint32(tx_fields['sequence'])
final_tx.write_compact_size(tx_fields['num_txout'])
final_tx.write_int64(tx_fields['value'])
final_tx.write_compact_size(len(tx_fields['scriptPubKey']))
final_tx.write(tx_fields['scriptPubKey'])
final_tx.write_uint32(tx_fields['locktime'])

#prints out the final transaction in hex format (can be used as an argument to bitcoind's sendrawtransaction)
print final_tx.input.encode('hex')




Comments