def serialize(self): ''' Serializes the transaction. ''' nVersion = self.version.to_bytes(4, 'little') # version (little-endian) nLocktime = self.locktime.to_bytes( 4, 'little') # lock time (little-endian) # Transactions inputs txins = var_int(len(self._inputs)) + bytes().join( self.serialize_input(txin) for txin in self._inputs) # Transaction outputs txouts = var_int(len(self._outputs)) + bytes().join( self.serialize_output(txout) for txout in self._outputs) return nVersion + txins + txouts + nLocktime
def send_addr(self, peer_addresses): count = var_int(len(peer_addresses)) payload = count + bytes().join( serialize_network_address(pa, with_timestamp=True) for pa in peer_addresses) self.outgoing_data_queue.append(wrap_network_message("addr", bytes())) print("send addr to {}".format(self.address['host'])) print(" ", payload.hex())
def serialize_legacy_preimage(self): ''' Serializes the preimage of the transaction.''' nVersion = self.version.to_bytes(4, 'little') nLocktime = self.locktime.to_bytes(4, 'little') nHashtype = self.hashtype.to_bytes( 4, 'little') # signature hashtype (little-endian) txins = var_int(len(self._inputs)) for txin in self._inputs: outpoint = self.serialize_outpoint(txin) prevLockingScript = self.get_preimage_script(txin) prevLockingScriptSize = var_int(len(prevLockingScript)) nSequence = txin['sequence'].to_bytes(4, 'little') txins += outpoint + prevLockingScriptSize + prevLockingScript + nSequence txouts = var_int(len(self._outputs)) + bytes().join( self.serialize_output(txo) for txo in self._outputs) return (nVersion + txins + txouts + nLocktime + nHashtype)
def send_getheaders(self, block_locators): version = Constants.PROTOCOL_VERSION.to_bytes(4, 'little') length_block_locators = var_int(len(block_locators)) ser_block_locators = bytes().join(bl[::-1] for bl in block_locators) stop = bytes(32) payload = version + length_block_locators + ser_block_locators + stop self.outgoing_data_queue.append( wrap_network_message("getheaders", payload)) print("send getheaders to {}".format(self.address['host']))
def serialize_input(self, txin): ''' Serializes an input: outpoint (previous output tx id + previous output index) + unlocking script (scriptSig) with its size + sequence number. ''' outpoint = self.serialize_outpoint(txin) if 'unlocking_script' in txin: unlockingScript = bytes.fromhex(txin['unlocking_script']) else: signatures = [bytes.fromhex(sig) for sig in txin['signatures']] if txin['address'].kind == Constants.CASH_P2PKH: unlockingScript = p2pkh_unlocking_script( txin['address'], txin['pubkeys'], signatures) elif txin['address'].kind == Constants.CASH_P2SH: # We assume this is a multisig input ibits = 0 jpub = 0 checkbits = 0 is_ecdsa = False for sig in signatures: for pubkey in txin['pubkeys'][jpub:]: if pubkey.verify_signature(sig[:-1], self.prehash, alg="schnorr"): checkbits = checkbits | (1 << ibits) ibits += 1 jpub = txin['pubkeys'].index(pubkey) + 1 break try: pubkey.verify_signature(sig[:-1], self.prehash, alg="ecdsa") except: pass else: is_ecdsa = True print("signature match") jpub = txin['pubkeys'].index(pubkey) + 1 break ibits += 1 dummy = 0 if is_ecdsa else checkbits if is_ecdsa: dummy = 0 else: dummy = checkbits unlockingScript = p2sh_unlocking_script( txin['address'], txin['redeem_script'], txin['pubkeys'], signatures, [dummy]) else: raise TransactionError("cannot parse type") unlockingScriptSize = var_int(len(unlockingScript)) nSequence = txin['sequence'].to_bytes(4, 'little') return outpoint + unlockingScriptSize + unlockingScript + nSequence
def serialize_output(self, txout): ''' Serializes an output: value + locking script (scriptPubkey) with its size.''' nAmount = txout['value'].to_bytes(8, 'little') if 'locking_script' in txout: lockingScript = bytes.fromhex(txout['locking_script']) else: if 'address' in txout: lockingScript = locking_script(txout['address']) elif txout['type'] == "p2pk": raise TransactionError("cannot serialize p2pk output") elif txout['type'] == "nulldata": lockingScript = nulldata_script(txout['data']) lockingScriptSize = var_int(len(lockingScript)) return nAmount + lockingScriptSize + lockingScript
def serialize_input(self, txin): ''' Serializes an input: outpoint (previous output tx id + previous output index) + unlocking script (scriptSig) with its size + sequence number. ''' outpoint = self.serialize_outpoint(txin) if 'unlocking_script' in txin: unlockingScript = bytes.fromhex(txin['unlocking_script']) else: signatures = [bytes.fromhex(sig) for sig in txin['signatures']] if txin['address'].kind == Constants.CASH_P2PKH: unlockingScript = p2pkh_unlocking_script( txin['address'], txin['pubkeys'], signatures) elif txin['address'].kind == Constants.CASH_P2SH: unlockingScript = p2sh_unlocking_script( txin['address'], txin['redeem_script'], txin['pubkeys'], signatures) else: raise TransactionError("cannot parse type") unlockingScriptSize = var_int(len(unlockingScript)) nSequence = txin['sequence'].to_bytes(4, 'little') return outpoint + unlockingScriptSize + unlockingScript + nSequence
def serialize_preimage(self, txin): ''' Serializes the preimage of the transaction (BIP-143).''' nVersion = self.version.to_bytes(4, 'little') nLocktime = self.locktime.to_bytes(4, 'little') nHashtype = self.hashtype.to_bytes( 4, 'little') # signature hashtype (little-endian) hashPrevouts = dsha256(bytes().join( self.serialize_outpoint(txi) for txi in self._inputs)) hashSequence = dsha256(bytes().join( txi['sequence'].to_bytes(4, 'little') for txi in self._inputs)) outpoint = self.serialize_outpoint(txin) prevLockingScript = self.get_preimage_script(txin) prevLockingScriptSize = var_int(len(prevLockingScript)) prevValue = txin['value'].to_bytes(8, 'little') nSequence = txin['sequence'].to_bytes(4, 'little') hashOutputs = dsha256(bytes().join( self.serialize_output(txo) for txo in self._outputs)) return (nVersion + hashPrevouts + hashSequence + outpoint + prevLockingScriptSize + prevLockingScript + prevValue + nSequence + hashOutputs + nLocktime + nHashtype)
def send_getdata(self, invs): payload = var_int(len(invs)) + bytes().join(inv.serialize() for inv in invs) self.outgoing_data_queue.append( wrap_network_message("getdata", payload)) print("send getdata to {}".format(self.address['host']))
def send_inv(self, invs): # Advertise our knowledge of one or more objects. payload = var_int(len(invs)) + bytes().join(inv.serialize() for inv in invs) self.outgoing_data_queue.append(wrap_network_message("inv", payload)) print("send inv to {}".format(self.address['host']))
def get(self): coind_type = self.get_request_coind_type() # パラメータを取得する try: params = json.loads(self.request.get('params')) except ValueError as e: raise ValidationError('params', e.message) # パラメータを分解する params_from = params['from'] params_to = params['to'] req_sigs = params['req_sigs'] value = params['value'] fee = params['fee'] data = params.get('data', None) # from を検査してからハッシュ値に変換する from_hash = [] from_addr = [] from_pk = [] if not isinstance(params_from, list): raise ValidationError('from', 'list') for e in params_from: try: # コインアドレスとしてパースを試みる from_hash.append(decode_coin_address(e, coind_type, 'from')) from_addr.append(e) from_pk.append(None) except ValidationError: # ダメだったら公開鍵としてパースを試みる pk = parse_pub_key(e, 'from') addr = encode_coin_address(pk, coind_type) from_hash.append(decode_coin_address(addr, coind_type, 'from')) from_addr.append(addr) from_pk.append(pk) # MULTISIG の場合は公開鍵がなければならない if len(params_from) != 1: for e in from_pk: if e is None: raise ValidationError('from', 'pub_key') # to を検査してからハッシュ値に変換する to_hash = [] to_addr = [] to_pk = [] if not isinstance(params_to, list): raise ValidationError('to', 'list') for e in params_to: try: # コインアドレスとしてパースを試みる to_hash.append(decode_coin_address(e, coind_type, 'to')) to_addr.append(e) to_pk.append(None) except ValidationError: # ダメだったら公開鍵としてパースを試みる pk = parse_pub_key(e, 'to') addr = encode_coin_address(pk, coind_type) to_hash.append(decode_coin_address(addr, coind_type, 'to')) to_addr.append(addr) to_pk.append(pk) # MULTISIG の場合は公開鍵がなければならない if len(params_to) != 1: for e in to_pk: if e is None: raise ValidationError('to', 'pub_key') # req_sigs の検査 if not isinstance(req_sigs, int): raise ValidationError('req_sigs', 'int') if req_sigs > len(params_to): raise ValidationError('req_sigs', 'len') # 送金額の確認 if value < 0.0: raise ValidationError('value', '0') # トランザクション手数料の確認 if fee <= 0.0: raise ValidationError('fee', '0') # OP_RETURN データの検査 if data is not None: try: # 16進数文字列で... data = unhexlify(data) # ...75 Byte 以下 if len(data) > 75: raise ValidationError('data', 'len') except TypeError as e: raise ValidationError('data', e.message) # 入力トランザクションの検索 input_value = 0 vin_txid = [] vin_idx = [] vin_type = '' vin_reqSigs = [] db = CloudSQL(coind_type) with db as c: c.execute( 'SELECT * FROM transaction_link WHERE addresses = %s AND ISNULL(vin_txid)', (' '.join(from_addr), )) for e in c.fetchall(): # トランザクションの生データ取得 c.execute( 'SELECT json FROM transaction WHERE txid = %s AND height = %s', (e['vout_txid'], e['vout_height'])) raw = json.loads( bz2.decompress(base64.b64decode(c.fetchone()['json']))) # P2PKH か MULTISIG でなければスキップ type = raw['vout'][e['vout_idx']]['scriptPubKey']['type'] if len(from_addr) == 1: if type != 'pubkeyhash': continue else: if type != 'multisig': continue input_value += e['value'] vin_txid.append(e['vout_txid']) vin_idx.append(e['vout_idx']) vin_type = type vin_reqSigs.append( raw['vout'][e['vout_idx']]['scriptPubKey']['reqSigs']) if input_value >= (value + fee) * SATOSHI_COIN: break # 残高が足りるか確認 if input_value < (value + fee) * SATOSHI_COIN: raise Exception("You don't have enough money.") vout_n = 0 vout_lt = bytearray() # vout[0] (送金出力) の作成 vout_lt = vout_lt + self.make_vout( self.make_script(to_hash, to_pk, req_sigs), value * SATOSHI_COIN) vout_n += 1 # vout[1] (おつり出力) の作成 rem = long(input_value - (value + fee) * SATOSHI_COIN) if rem != 0: vout_lt = vout_lt + self.make_vout( self.make_script(from_hash, from_pk, max(vin_reqSigs)), rem) vout_n += 1 # vout[2] (OP_RETURN の作成) if data is not None: vout_lt = vout_lt + self.make_vout(self.make_data_script(data), 0) vout_n += 1 # vout~locktime 区間のバイナリ組み立て vout_lt = var_int(vout_n) + vout_lt + bytearray(pack('<I', 0)) # 入力トランザクションごとに署名対象のハッシュ値を求める sign_hash = [] for i in range(0, len(vin_txid)): # 入力トランザクションの出力スクリプト # - 送金元アドレスから P2PKH を仮定して生成する script = self.make_script(from_hash, from_pk, vin_reqSigs[i]) # 先頭は vin の個数 vin = bytearray(var_int(len(vin_txid))) # 全入力トランザクションを結合 for j in range(0, len(vin_txid)): # 入力トランザクションを追加 vin = vin + bytearray( unhexlify(vin_txid[j])[::-1] + pack('<I', vin_idx[j])) # 該当する入力のときだけスクリプトを挿入 if i == j: vin = vin + var_int(len(script)) + script else: vin = vin + var_int(0) # シーケンスは 0 固定 vin = vin + bytearray(pack('<I', 0)) # txCopy を完成させる tx = bytearray(pack('<i', 2)) + vin + vout_lt + bytearray( pack('<I', 1)) # SHA256 ハッシュを計算して署名対象のハッシュ値とする sign_hash.append({ 'txid': vin_txid[i], 'hash': sha256(sha256(tx).digest()).hexdigest(), 'reqSigs': vin_reqSigs[i] }) b64_pk = [] for e in from_pk: if e is not None: b64_pk.append(b64encode(e)) else: b64_pk.append(None) # 後半ステージに送る情報をペイロードにまとめる payload_body = json.dumps({ 'hash': sign_hash, 'from_pk': b64_pk, 'vin_txid': vin_txid, 'vin_idx': vin_idx, 'vin_type': vin_type, 'vin_reqSigs': vin_reqSigs, 'vout_lt': hexlify(vout_lt), 'log_data': { 'params_from': params_from, 'params_to': params_to, 'sign_hash': sign_hash } }) # さらにハッシュをつけて包む payload = { 'body': payload_body, 'hash': sha256(payload_body).hexdigest() } self.write_json({ 'sign': sign_hash, 'payload': b64encode(bz2.compress(json.dumps(payload))) })
def make_vout(script, value): return bytearray(pack('<Q', value)) + var_int(len(script)) + script
def post( self ): coind_type = self.get_request_coind_type() # パラメータを取得する try: params = json.loads( self.request.get( 'params' ) ) except ValueError as e: raise ValidationError( 'params', e.message ) sign = params['sign'] pub_key = params.get( 'pub_key', u'' ) payload = params['payload'] # payload をパースする try: payload = json.loads( bz2.decompress( b64decode( payload ) ) ) except ValueError as e: raise ValidationError( 'params', e.message ) except Exception as e: raise ValidationError( 'params', 'decompress' ) # payload のハッシュ値検査 if sha256( payload['body'] ).hexdigest() != payload['hash']: raise ValidationError( 'params', 'sha256' ) # payload の本体をパースする try: payload = json.loads( payload['body'] ) except ValueError as e: raise ValidationError( 'params', e.message ) # payload を分解 vin_txid = payload['vin_txid'] vin_idx = payload['vin_idx'] vin_type = payload['vin_type'] vin_reqSigs = payload['vin_reqSigs'] vout_lt = unhexlify( payload['vout_lt'] ) hash = payload['hash'] from_pk = payload['from_pk'] log_data = payload['log_data'] # sign の検証 if not isinstance( sign, list ): raise ValidationError( 'sign', 'list' ) if len( sign ) != len( hash ): raise ValidationError( 'sign', 'n' ) for i in range( 0, len( hash ) ): if not isinstance( sign[i], list ): raise ValidationError( 'sign', 'list' ) if len( sign[i] ) != vin_reqSigs[i]: raise ValidationError( 'sign', 'reqSigs' ) for e in sign[i]: if len( e ) != 128: raise ValidationError( 'sign', 'len' ) # 形式検査 : 16進としてパースできれば OK try: unhexlify( e ) except TypeError as e: raise ValidationError( 'sign', e.message ) # pub_key の検証 if not isinstance( pub_key, unicode ): raise ValidationError( 'pub_key', 'unicode' ) if vin_type == 'pubkeyhash': # 形式検査とパース pub_key = parse_pub_key( pub_key, 'pub_key' ) # 電子署名の検証 if vin_type == 'pubkeyhash': for i in range( 0, len( hash ) ): # 署名対象ハッシュ値を数値に h = long( hash[i]['hash'], 16 ) # 署名をパース sig_r = long( sign[i][0][0:64], 16 ) sig_s = long( sign[i][0][64:128], 16 ) # 公開鍵をパース pk_x, pk_y = ecdsa.decompress( pub_key ) if not ecdsa.verify( h, sig_r, sig_s, pk_x, pk_y ): raise ValidationError( 'sign', 'verify' ) elif vin_type == 'multisig': for i in range( 0, len( hash ) ): # 署名対象ハッシュ値を数値に h = long( hash[i]['hash'], 16 ) # 何番目の公開鍵まで走査したか k = 0 for e in sign[i]: # 署名をパース sig_r = long( e[0:64], 16 ) sig_s = long( e[64:128], 16 ) while True: # 有効な公開鍵があるか if k == len( from_pk ): raise ValidationError( 'sign', 'verify' ) # preparetx に送った公開鍵をパース pk_x, pk_y = ecdsa.decompress( bytearray( b64decode( from_pk[k] ) ) ) k = k + 1 # 署名検証に成功したら次へ進む if ecdsa.verify( h, sig_r, sig_s, pk_x, pk_y ): break # トランザクションデータの先頭はバージョン番号から始まる tx = bytearray( pack( '<i', 2 ) ) # vin の組み立て tx = tx + bytearray( var_int( len( vin_txid ) ) ) for i in range( 0, len( vin_txid ) ): # アンロックスクリプト (入力スクリプト) の作成 script = self.make_script( sign[i], pub_key, vin_type ) # 入力トランザクションを追加 tx = tx + bytearray( unhexlify( vin_txid[i] )[::-1] + pack( '<I', vin_idx[i] ) ) tx = tx + var_int( len( script ) ) + script + bytearray( pack( '<I', 0 ) ) # vout~locktime 区間を連結 tx = tx + vout_lt # ログデータに追記 log_data['sign'] = sign log_data['tx'] = hexlify( tx ) # キューに投げるデータを payload としてまとめる payload_body = json.dumps( { 'tx': hexlify( tx ), 'log_data': log_data } ) # さらにハッシュをつけて包む payload = { 'body': payload_body, 'hash': sha256( payload_body ).hexdigest() } # taskqueue に積む taskqueue.add( url = '/maintain/sendrawtransaction', params = { 'coind_type': coind_type, 'payload': b64encode( bz2.compress( json.dumps( payload ) ) ) }, queue_name = 'send-tx' ) # 作成した TXID を返す self.write_json( { 'result': hexlify( sha256( sha256( tx ).digest() ).digest()[::-1] ) } )