Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- Adapter 패턴
- Play2
- 블록체인
- Hyperledger fabric gossip protocol
- 그라파나
- 엔터프라이즈 블록체인
- play2 강좌
- 하이퍼레저 패브릭
- 이더리움
- 플레이프레임워크
- Actor
- CORDA
- 하이브리드앱
- 스위프트
- play 강좌
- 파이썬 강좌
- 파이썬 머신러닝
- 안드로이드 웹뷰
- 스칼라 동시성
- 파이썬 동시성
- Golang
- 주키퍼
- Play2 로 웹 개발
- 스칼라
- 파이썬
- 스칼라 강좌
- 파이썬 데이터분석
- Akka
- akka 강좌
- hyperledger fabric
Archives
- Today
- Total
HAMA 블로그
[비트코인] Raw Transaction 만들기 (sig 는 어떻게 만들어 지는가?) 본문
블록체인
[비트코인] Raw Transaction 만들기 (sig 는 어떻게 만들어 지는가?)
[하마] 이승현 (wowlsh93@gmail.com) 2018. 4. 7. 20:53scriptSig 스크립트의 시작에 나오는 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단계) :
- 4바이트의 버전 필드 추가 :
01000000
- 1바이트의 몇번째 input 인지 추가:
01
- 우리가 소비하고자 하는 지난번 트랜잭션의 32-byte 해시:
eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
- 4바이트의 3번의 트랜잭션에서의 output 인덱스의 UTXO (output number 2 = output index 1):
01000000
- 트랜잭션을 서명할 목적으로 임시 scriptSig 를 만드는데, 1바이트의 길이 (0x19=25bytes) 를 채운다 :
19
- 우리가 사용 할 UXTO의 scriptPubKey 를 이용해서 임시 scriptSig 를 만든다:
76a914010966776006953d5567439e5e39f86a0d273bee88ac
- 4바이트 시퀀스 필드를 채운다:
ffffffff
- 1바이트의 새로운 트랜잭션의 아웃풋 넘버를 채운다 :
01
- 거래 할 금액에 대해 8바이트 필드를 쓴다. 우리가 소비할 수 있는 총 UTXO 에서 fee 를 위한 0.001BTC 를 제외한다. (0.999 BTC, or 99900000 Satoshis):
605af40500000000
- output 스크립트의 길이를 1바이트로 채운다.(0x19 or 25 bytes):
19
- 실제 아웃풋 스크립트를 작성한다(
대략 OP_DUP OP_HASH160 pubKeyHash OP_EQUALVERIFY OP_CHECKSIG 이런것이 들어간다.pubKeyHash 는 public key 를 해쉬한 값인데, 내가 돈을 보내는 사람의 주소에서 생성 될 수 있다.):
76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
- 4바이트 "lock time" 필드를 채운다:
00000000
4바이트의 "hash code type" 를 쓴다.(우리 경우는 1이며 SIGHASH_ALL):
01000000
(일단 여기까지 완전한 트랜잭션이 만들어 졌다. 이후에는 이 트랜잭션에 대한 서명을 할 차례이다.)
01000000 01 eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2 01000000 19 76a914010966776006953d5567439e5e39f86a0d273bee88ac ffffffff 01 605af40500000000 19 76a914097072524438d003d23a2f23edb65aae1bb3e46988ac 00000000 01000000
이중-SHA256 해시로 이 전체 구조에 대해서 해쉬한다.:
9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e
- 개인키로 private/public 키 쌍을 생성한다. 그리고 14번의 해시를 서명한다:
30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06
이 서명에 뒤에 1바이트 해시코드를 넣는다.01
. 공개키는 다음과 같다:0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
- 진짜 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>
- 스텝 5에서의 scriptSig 길이를 진짜 scriptSig 의 길이로 변경한다 :
8c
- 스텝 6에서 생성한 임시 scriptSig 를 스텝16의 진짜 scriptSig 로 변경한다:
4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
마지막으로 스텝 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')
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')
'블록체인' 카테고리의 다른 글
[블록체인] TPS 그리고 Disruptor 패턴 (0) | 2018.05.12 |
---|---|
[이더리움] Merkle Patricia Tree (MPT) 를 이해하기 위한 여정 (4) | 2018.05.10 |
[블록체인] 개발자를 위한 블록체인 로드맵 (1) | 2018.04.15 |
[비트코인] 머클패스의 주체는 누구? 거래검증? 거래확정? (0) | 2018.03.12 |
Economy of Things 와 블록체인 (0) | 2018.01.31 |
Comments