Example #1
0
    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
Example #2
0
 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())
Example #3
0
    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)
Example #4
0
    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']))
Example #5
0
    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
Example #6
0
 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
Example #7
0
 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
Example #8
0
    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)
Example #9
0
 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']))
Example #10
0
 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']))
Example #11
0
    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)))
        })
Example #12
0
 def make_vout(script, value):
     return bytearray(pack('<Q', value)) + var_int(len(script)) + script
Example #13
0
	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] )
		} )