def decode_base58(encoded_str): """ base58 기반 디코딩 함수 - 비트코인 주소 생성시 Base58로 인코딩 - 해당 주소로부터 20바이트 해시를 얻는 방법 :param encoded_str: Base58로 인코딩된 비트코인 주소 :return: 디코딩된 바이트의 중간 20바이트를 공개키의 hash160 해시값으로 반환 """ num = 0 # 각 자리수 계산 for c in encoded_str: num *= 58 num += BASE58_ALPHABET.index(c) # 25 바이트 빅엔디언으로 변환 combined = num.to_bytes(25, byteorder="big") # 마지막 4바이트는 체크섬 checksum = combined[-4:] # hash256 해시값의 체크섬과 비교 if hash256(combined[:-4])[:4] != checksum: raise ValueError("Bad adress : {} {}".format( checksum, hash256(combined[:-4])[:4])) # 첫 1바이트는 메인넷/테스트넷, 중간 20바이트는 공개키 return combined[1:-4]
def hash_prevouts(self): if self._hash_prevouts is None: all_prevouts = b'' all_sequence = b'' for tx_in in self.tx_inputs: all_prevouts += tx_in.prev_tx[::-1] + int_to_little_endian( tx_in.prev_index, 4) all_sequence += int_to_little_endian(tx_in.sequence, 4) self._hash_prevouts = hash256(all_prevouts) self._hash_sequence = hash256(all_sequence) return self._hash_prevouts
def example1(): hash0 = bytes.fromhex( 'c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38bdc15d9eb64cf5') hash1 = bytes.fromhex( 'c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea7a08d672d7cc5') parent = hash256(hash0 + hash1) print(parent.hex())
def my_address(): from ecc.secp256k1 import PrivateKey my_message = b'YJ Choi secret' secret_key = little_endian_to_int(hash256(my_message)) private_key = PrivateKey(secret_key) address = private_key.public_key.address(testnet=True) print(address)
def hash_outputs(self): if self._hash_outputs is None: all_outputs = b'' for tx_out in self.tx_outputs: all_outputs += tx_out.serialize() self._hash_outputs = hash256(all_outputs) return self._hash_outputs
def sig_hash_bip143(self, input_index, redeem_script=None, witness_script=None): '''Returns the integer representation of the hash that needs to get signed for index input_index''' tx_in = self.tx_inputs[input_index] # per BIP143 spec s = int_to_little_endian(self.version, 4) s += self.hash_prevouts() + self.hash_sequence() s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4) if witness_script: script_code = witness_script.serialize() elif redeem_script: script_code = p2pkh_script(redeem_script.cmds[1]).serialize() else: script_code = p2pkh_script( tx_in.script_pubkey(self.testnet).cmds[1]).serialize() s += script_code s += int_to_little_endian(tx_in.value(), 8) s += int_to_little_endian(tx_in.sequence, 4) s += self.hash_outputs() s += int_to_little_endian(self.locktime, 4) s += int_to_little_endian(SIGHASH_ALL, 4) return int.from_bytes(hash256(s), 'big')
def hash(self): """ 직렬화하여 hash256으로 해시값을 계산한 뒤, 리틀엔디언으로 변환 - 트랜잭션 ID의 경우, 일반적인 직렬화 사용(?) :return: 트랜잭션 자체의 해시 """ return hash256(self.serialize_legacy())[::-1]
def parse(cls, stream, testnet=False): # 매직 넘버 : 4바이트 magic = stream.read(4) # 올바른 스트림인지 확인 if magic == b"": raise RuntimeError("Connection Reset") if testnet: expected_magic = TESTNET_NETWORK_MAGIC else: expected_magic = NETWORK_MAGIC if magic != expected_magic: raise RuntimeError("magic is not right {} (expected : {}".format( magic, expected_magic)) # 커맨드 필드 : 12바이트(빈공간 0x00포함) command = stream.read(12) command = command.strip(b"\x00") # 페이로드 길이 : 4바이트 리틀엔디언 payload_length = little_endian_to_int(stream.read(4)) # 체크섬 필드 : 4바이트 checksum = stream.read(4) # 페이로드 필드 payload = stream.read(payload_length) # 체크섬을 통한 에러 확인 payload_checksum = hash256(payload)[:4] if payload_checksum != checksum: raise RuntimeError("checksum error") return cls(command, payload, testnet)
def make_merkle_parent(hash1, hash2): """ 두 노드를 이용하여 머클부모를 반환 - 말단 노드 : 블록의 트랜잭션 해시값 :param hash1: 왼쪽 자식 노드 :param hash2: 오른쪽 자식 노드 :return: 머클 부모(부모 해시값) """ return hash256(hash1 + hash2)
def sig_hash(self, input_index): """ 해제 스크립트에 서명을 포함시켜 트랜잭션을 변형 step1 : 해제 스크립트 삭제 - 서명 검증시 트랜잭션의 해제 스크립트 삭제 - 서명 생성시 동일 - 입력이 여러 개인 경우, 해당 입력의 해제 스크립트 삭제 step2 : 이전 트랜잭션 출력의 잠금 스크립트를 입력의 해제 스크립트에 삽입 - 트랜잭션 입력에 대해 TxIn.script_pubkey를 사용하여 해제 스크립트 입수 가능 step3 : 해시 유형 추가 - 서명해시를 구하기 위한 메시지에 포함시킬 필드 결정 - SIGHASH_ALL : 현재 입력과 다른 모든 입출력을 모두 포함 step4 : 해시 계산 - 최종 변경된 트랜잭션의 hash256 해시값 계산 - 32바이트 빅엔디언 정수로 변환 :param input_index: 현재 입력에 대한 인덱스 :return: 변형된 트랜잭션의 서명해시 i.e 서명을 포함시킨 직렬화 정보 """ # 버전 : 4바이트 리틀 엔디언 result = int_to_little_endian(self.version, 4) # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 # 서명을 포함시키기 위해 serialize()와 다르게 직렬화 result += encode_varint(len(self.tx_inputs)) for idx, tx_in in enumerate(self.tx_inputs): # 현재 입력인 경우 if idx == input_index: # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트로 스크립트 대체 tx_in_copy = TxIn(prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, script_sig=tx_in.script_pubkey(self.testnet), sequence=tx_in.sequence) result += tx_in_copy.serialize() # 다른 입력 else: # 해제 스크립트는 포함시키지 않는다 tx_in_copy = TxIn(prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, sequence=tx_in.sequence) result += tx_in_copy.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 result += encode_varint(len(self.tx_outputs)) for tx_output in self.tx_outputs: result += tx_output.serialize() # 록타임 : 4바이트의 리틀엔디언 result += int_to_little_endian(self.locktime, 4) # 해시 유형 덧붙임 : SIGHASH_ALL 사용함 result += int_to_little_endian(SIGHASH_ALL, 4) # 서명 해시 생성 result256 = hash256(result) sig_message_hash = int.from_bytes(result256, "big") return sig_message_hash
def example1(): bit_field_size = 10 bit_field = [0] * bit_field_size # 해시 + 나머지연산 이용 h = hash256(b"hello world") bit = int.from_bytes(h, "big") % bit_field_size bit_field[bit] = 1 print(bit_field)
def exercise4(): from io import BytesIO from ecc.secp256k1 import S256Point, Signature from ecc.utils import encode_varint, hash256, int_to_little_endian from ecc.transaction import Tx, SIGHASH_ALL # 문제 : 주어진 p2sh 다중 서명 스크립트와 서명이 주어질 때, 해당 서명 검증 hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c568700000000' hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb71' hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022' hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152ae' sec = bytes.fromhex(hex_sec) der = bytes.fromhex(hex_der) # 해답 # sec 공개키와 der 서명을 통해 해당 객체 생성 point = S256Point.parse(sec) signature = Signature.parse(der) redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script))) stream = BytesIO(bytes.fromhex(hex_tx)) tx = Tx.parse(stream) # 두번째 서명을 만들기 위한 트랜잭션의 해시 계산 sig = int_to_little_endian(tx.version, 4) # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 sig += encode_varint(len(tx.tx_inputs)) # 서명 : 트랜잭션 입력의 해제 스크립트를 리딤 스크립트로 대체 cur_input = tx.tx_inputs[0] tx_in_copy = TxIn(prev_tx=cur_input.prev_tx, prev_index=cur_input.prev_index, script_sig=redeem_script, sequence=cur_input.sequence) sig += tx_in_copy.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 sig += encode_varint(len(tx.tx_outputs)) for tx_output in tx.tx_outputs: sig += tx_output.serialize() # 록타임 : 4바이트의 리틀엔디언 sig += int_to_little_endian(tx.locktime, 4) # 해시 유형 덧붙임 : SIGHASH_ALL 사용함 sig += int_to_little_endian(SIGHASH_ALL, 4) # 서명 해시 생성 result256 = hash256(sig) sig_message_hash = int.from_bytes(result256, "big") print(point.verify(sig_message_hash, signature))
def serialize(self): # 매직 넘버 : 4바이트 ret = self.magic # 커맨드 필드 : 12바이트(빈공간 0x00포함) ret += self.command ret += b"\x00" * (12 - len(self.command)) # 페이로드 길이 : 4바이트 리틀엔디언 ret += int_to_little_endian(len(self.payload), 4) # 체크섬 필드 : 4바이트 ret += hash256(self.payload)[:4] # 페이로드 필드 ret += self.payload return ret
def check_pow(self): """ 작업 증명 - 작업 증명으로 탈중앙화 방식의 비트코인 채굴이 가능 => 전체 네트워크 수준에서 비트코인 보안이 유지 - 특정 조건을 만족하는 작은 값을 찾는 것 - 특정 조건 : 목표값(target) - 작은 값 : 블록 헤더의 해시값 - 블록의 해시값을 리틀엔디언 정수로 표현하여 비교 - 블록 헤더의 해시 변경 방법 - 채굴자가 논스값을 변경 - 코인베이스 트랜잭션 변경 => 머클루트가 변경되어 새로운 해시 - 버전 필드 변경 :return: 자격 증명 결과 """ block_hash = hash256(self.serialize()) block_hash_int = little_endian_to_int(block_hash) return block_hash_int < self.target()
def example4(): print("목표값 설정 : 블록헤더의 해시값이 목표값보다 작을 때 작업 증명 유효") # 목표값 계산 bits = bytes.fromhex("e93c0118") exponent = bits[-1] coef = little_endian_to_int(bits[:-1]) target = coef * 256**(exponent - 3) print("exponent: {}, coef: {}".format(exponent, coef)) print("target: ") print("{:x}".format(target).zfill(64)) # 작업 증명 : 블록 헤더의 해시값을 리틀엔디언으로 표현 block_hash = hash256( bytes.fromhex( '020000208ec39428b17323fa0ddec8e887b4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d' )) block_hash_int = little_endian_to_int(block_hash) print("block_hash:") print("{:x}".format(block_hash_int).zfill(64)) print("proof of work : {}".format(block_hash_int < target))
def example2(): from ecc.secp256k1 import S256Point, Signature from ecc.utils import hash256 modified_tx = bytes.fromhex( '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c56870000000001000000' ) h256 = hash256(modified_tx) z = int.from_bytes(h256, 'big') # sec 공개키는 리딤 스크립트에서 구할 수 있음 sec = bytes.fromhex( '022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb70') point = S256Point.parse(sec) # der 서명은 해제 스크립트에서 구할 수 있음 der = bytes.fromhex( '3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a89937' ) sig = Signature.parse(der) print(point.verify(z, sig))
def hash(self): """ 직렬화하여 hash256으로 해시값을 계산한 뒤, 리틀엔디언으로 변환 :return: 블록 ID """ return hash256(self.serialize())[::-1]
def op_hash256(stack): if len(stack) < 1: return False element = stack.pop() stack.append(hash256(element)) return True