def verify_input(self, input_index): """ 현재 트랜잭션 입력에 대한 검증 step 1 : 현재 입력에 대응하는 결합 스크립트 생성 - 입력에 대응하는 이전 트랜잭션 출력의 잠금 스크립트 - 입력의 해제 스크립트 step 2 : 현재 입력에 대응하는 서명 생성 step 3 : 결합스크립트를 이용하여 서명 계산 :param input_index: 현재 입력에 대한 인덱스 :return: 검증 boolean """ # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트 current_intput_tx = self.tx_inputs[input_index] current_pubkey = current_intput_tx.script_pubkey(testnet=self.testnet) # 스크립트 형식에 따라 리딤 스크립트 생성 if current_pubkey.is_p2sh_script_pubkey(): command = current_intput_tx.script_sig.commands[-1] redeem_script_raw = encode_varint(len(command)) + command redeem_script = Script.parse(BytesIO(redeem_script_raw)) else: redeem_script = None # 현재 트랜잭션 입력에 대한 결합 스크립트 combined = current_intput_tx.script_sig + current_pubkey # 현재 트랜잭션 입력에 대한 서명 생성 message_hash = self.sig_hash(input_index, redeem_script) # 결합 스크립트를 통한 서명 검증 return combined.evaluate(message_hash)
def parse(cls, stream): # 금액 : 8바이트 리틀엔디언 amount = little_endian_to_int(stream.read(8)) # 해제 스크립트 script_pubkey = Script.parse(stream) return cls(amount, script_pubkey)
def example2(): print("Example : Height of Transaction in the block") stream = BytesIO( bytes.fromhex( '5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00' )) script_sig = Script.parse(stream) print("Height : {}".format(little_endian_to_int(script_sig.commands[0])))
def p2pkh_script(h160): """ encoder.decode_base58로 구한 해시값을 잠금 스크립트로 변환하는 함수 - [OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSUM] 형태로 구성 :param h160: 공개키의 hash160 해시값 :return: 잠금 스크립트 객체 """ return Script([0x76, 0xa9, h160, 0x88, 0xac])
def example1(): print("Example : Miner-defined ScriptSig") stream = BytesIO( bytes.fromhex( '4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73' )) script = Script.parse(stream) for cmd in script.commands: print(cmd)
def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff): """ :param prev_tx: 이전 트랜잭션의 해시값/ID :param prev_index: 이전 트랜잭션의 출력 번호 :param script_sig: 해제 스크립트 :param sequence: 시퀀스 필드 """ self.prev_tx = prev_tx self.prev_index = prev_index if script_sig is None: self.script_sig = Script() else: self.script_sig = script_sig self.sequence = sequence
def parse(cls, stream): # 이전 트랜잭션 해시값 : 32바이트 리틀엔디언 prev_tx = stream.read(32)[::-1] # 이전 트랜잭션 번호 : 4바이트 리틀엔디언 prev_index = little_endian_to_int(stream.read(4)) # 해제 스크립트 script_sig = Script.parse(stream) # 시퀀스 필드 : 4바이트 리틀엔디언 seqeunce = little_endian_to_int(stream.read(4)) return cls(prev_tx, prev_index, script_sig, seqeunce)
def sign_input(self, input_index, private_key): """ 현재 트랜잭션 입력에 해제 스크립트 생성 :param input_index: 현재 입력에 대한 인덱스 :param private_key: 해제 스크립트에 사용할 비밀키 :return: 추가한 해제 스크립트 검증 결과 boolean """ # 트랜잭션의 서명 해시 message_hash = self.sig_hash(input_index) # 트랜잭션의 서명 해시에 대한 서명 der 직렬화 der = private_key.sign(message_hash).der() # 스크립트에 들어가는 서명은 DER형식 서명과 1바이트의 해시 유형으로 구성 sig = der + SIGHASH_ALL.to_bytes(1, "big") sec = private_key.public_key.sec() # 해당 트랜잭션의 입력의 해제 스크립트 추가 script_sig = Script([sig, sec]) self.tx_inputs[input_index].script_sig = script_sig # 추가한 해제 스크립트 검증 return self.verify_input(input_index)
def example4(): from ecc.secp256k1 import PrivateKey print("트랜잭션 해제 스크립트 생성") raw_tx = ( '0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600' ) stream = BytesIO(bytes.fromhex(raw_tx)) tx = Tx.parse(stream) z = tx.sig_hash(0) # 비밀키 생성 및 트랜잭션의 서명 해시에 대한 서명 der 직렬화 private_key = PrivateKey(secret=8675309) der = private_key.sign(z).der() # 스크립트에 들어가는 서명은 DER형식 서명과 1바이트의 해시 유형으로 구성 sig = der + SIGHASH_ALL.to_bytes(1, "big") sec = private_key.public_key.sec() # 해당 트랜잭션의 입력의 해제 스크립트 추가 script_sig = Script([sig, sec]) tx.tx_inputs[0].script_sig = script_sig print(tx.serialize().hex())
class TxIn: """ 비트코인 입력(트랜잭션 입력) - 트랜잭션 입력은 이전 트랜잭션의 출력 내용과 연관이 있음 - 본인이 소유한 비트코인을 정확히 가리키기 위한 두가지 필요 사항 1. 이전에 내가 수신한 비트코인을 가리키는 참조 정보 2. 해당 비트코인이 나의 소유라는 증명 : 타원곡선 서명 알고리즘(3장) 사용 - 4가지 필드 존재 - 이전 트랜잭션의 출력 정보 1. 이전 트랜잭션의 해시값/ID 2. 이전 트랜잭션의 출력 번호 - 이전 트랜잭션의 출력을 사용하는 방법 정의 3. 해제 스크립트 4. 시퀀스 1. 이전 트랜잭션의 해시값/ID - 이전 트랜잭션 해시값으로 hash256 사용 - 해시 충돌이 거의 없어 이전 트랜잭션을 unique하게 특정 가능 - 32바이트 리틀엔디언 2. 이전 트랜잭션의 출력 번호 - 각각의 트랜잭션은 하나 이상의 출력을 가진다 i.e 사용하려는 이전 트랜잭션에서 출력이 여러 개일 가능성 존재 - 몇번째 출력인지에 대한 정보 필요 - 4바이트 리틀엔디언 3. 해제 스크립트 - 비트코인의 스마트 계약 언어인 Script를 구성하는 한 부분 - 트랜잭션 출력의 소유자만이 할 수 있는 무엇인가를 나타냄 : 금고를 열기 위한 열쇠 - 가변 길이 필드로 시작 - 해당 필드의 길이를 알아야 파싱 가능하기 때문 4. 시퀀스 - 빈번한 거래를 위해 록타임 필드와 함께 표기했던 필드 - 록타임이 유요하는 동안 발생한 여러 거래를 하나의 트랜잭션으로 블록체인에 기록 가능 - 최종 정산 결과만 기록하면 됨 => 채굴자가 악용하기 쉬워 이런 방식으로 사용되지 않음 - RBF(Replace-By-Fee)와 OP_CHECKSEQUENCEVERIFY로 사용 - 4바이트 리틀엔디언 """ def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff): """ :param prev_tx: 이전 트랜잭션의 해시값/ID :param prev_index: 이전 트랜잭션의 출력 번호 :param script_sig: 해제 스크립트 :param sequence: 시퀀스 필드 """ self.prev_tx = prev_tx self.prev_index = prev_index if script_sig is None: self.script_sig = Script() else: self.script_sig = script_sig self.sequence = sequence def __repr__(self): return "{}:{}".format(self.prev_tx.hex(), self.prev_index) @classmethod def parse(cls, stream): # 이전 트랜잭션 해시값 : 32바이트 리틀엔디언 prev_tx = stream.read(32)[::-1] # 이전 트랜잭션 번호 : 4바이트 리틀엔디언 prev_index = little_endian_to_int(stream.read(4)) # 해제 스크립트 script_sig = Script.parse(stream) # 시퀀스 필드 : 4바이트 리틀엔디언 seqeunce = little_endian_to_int(stream.read(4)) return cls(prev_tx, prev_index, script_sig, seqeunce) def serialize(self): # 이전 트랜잭션 해시값 : 32바이트 리틀엔디언 result = self.prev_tx[::-1] # 이전 트랜잭션 번호 : 4바이트 리틀엔디언 result += int_to_little_endian(self.prev_index, 4) # 해제 스크립트 result += self.script_sig.serialize() # 시퀀스 필드 : 4바이트 리틀엔디언 result += int_to_little_endian(self.sequence, 4) return result def fetch_tx(self, testnet=False): """ :param testnet: 테스트넷 여부 :return: 이전 트랜잭션 해시값(id)에 대응되는 트랜잭션(Tx 객체) """ return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet) def value(self, testnet=False): """ :param testnet: 테스트넷 여부 :return: 이전 트랜잭션 해시값에 대응되는 트랜잭션의 비용 """ tx = self.fetch_tx(testnet=testnet) return tx.tx_outputs[self.prev_index].amount def script_pubkey(self, testnet=False): """ :param testnet: 테스트넷 여부 :return: 이전 트랜잭션 해시값에 대응되는 트랜잭션의 해제 스크립트 """ tx = self.fetch_tx(testnet=testnet) return tx.tx_outputs[self.prev_index].script_pubkey
def verify_input(self, input_index): """ 현재 트랜잭션 입력에 대한 검증 step 1 : 현재 입력에 대응하는 결합 스크립트 생성 - 입력에 대응하는 이전 트랜잭션 출력의 잠금 스크립트 - 입력의 해제 스크립트 - p2sh의 경우 리딤 스크립트 - 호환성을 위해 p2wpkh/p2wsh 또한 리딤 스크립트에 포함 - p2wpkh 스크립트의 경우, 증인필드(서명, 공개키) step 2 : 현재 입력에 대응하는 서명 생성 step 3 : 결합 스크립트를 이용하여 서명 계산 :param input_index: 현재 입력에 대한 인덱스 :return: 검증 boolean """ # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트 current_intput_tx = self.tx_inputs[input_index] current_pubkey = current_intput_tx.script_pubkey(testnet=self.testnet) # 스크립트 형식에 따라 리딤 스크립트 생성 if current_pubkey.is_p2sh_script_pubkey(): command = current_intput_tx.script_sig.commands[-1] redeem_script_raw = encode_varint(len(command)) + command redeem_script = Script.parse(BytesIO(redeem_script_raw)) # 리딤 스크립트 : p2sh-p2wpkh 이거나 p2sh-p2wsh 이다 # p2wpkh인 경우 : if redeem_script.is_p2wpkh_script_pubkey(): message_hash = self.sig_hash_bip143(input_index, redeem_script) witness = current_intput_tx.witness # p2wsh인 경우 elif redeem_script.is_p2wsh_script_pubkey(): # 증인 스크립트 추출 cmd = current_intput_tx.witness[-1] # 가변 길이의 증인 스크립트 파싱 raw_witness = encode_varint(len(cmd)) + cmd witness_script = Script.parse(BytesIO(raw_witness)) message_hash = self.sig_hash_bip143( input_index, witness_script=witness_script) witness = current_intput_tx.witness # p2sh인 경우 else: message_hash = self.sig_hash(input_index, redeem_script) witness = None else: # 리딤 스크립트 : p2sh-p2wpkh 이거나 p2sh-p2wsh 이다 # p2wpkh인 경우 : if current_pubkey.is_p2wpkh_script_pubkey(): message_hash = self.sig_hash_bip143(input_index) witness = current_intput_tx.witness # p2wsh인 경우 : elif current_pubkey.is_p2wsh_script_pubkey(): # 증인 스크립트 추출 cmd = current_intput_tx.witness[-1] # 가변 길이의 증인 스크립트 파싱 raw_witness = encode_varint(len(cmd)) + cmd witness_script = Script.parse(BytesIO(raw_witness)) message_hash = self.sig_hash_bip143( input_index, witness_script=witness_script) witness = current_intput_tx.witness # p2sh인 경우 else: message_hash = self.sig_hash(input_index) witness = None # 현재 트랜잭션 입력에 대한 결합 스크립트 combined = current_intput_tx.script_sig + current_pubkey # 결합 스크립트를 통한 서명 검증 return combined.evaluate(message_hash, witness)