def release_funds(self, order_id): """ This function should be called to release funds from a disputed contract after the moderator has resolved the dispute and provided his signature. """ if os.path.exists(DATA_FOLDER + "purchases/in progress/" + order_id + ".json"): file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json" outpoints = pickle.loads(self.db.Purchases().get_outpoint(order_id)) elif os.path.exists(DATA_FOLDER + "store/contracts/in progress/" + order_id + ".json"): file_path = DATA_FOLDER + "store/contracts/in progress/" + order_id + ".json" outpoints = pickle.loads(self.db.Sales().get_outpoint(order_id)) with open(file_path, 'r') as filename: contract = json.load(filename, object_pairs_hook=OrderedDict) vendor_address = contract["vendor_order_confirmation"]["invoice"]["payout"]["address"] buyer_address = contract["buyer_order"]["order"]["refund_address"] for moderator in contract["vendor_offer"]["listing"]["moderators"]: if moderator["guid"] == contract["buyer_order"]["order"]["moderator"]: masterkey_m = moderator["pubkeys"]["bitcoin"]["key"] outputs = [] outputs.append({'value': contract["dispute_resolution"]["resolution"]["moderator_fee"], 'address': contract["dispute_resolution"]["resolution"]["moderator_address"]}) if "buyer_payout" in contract["dispute_resolution"]["resolution"]: outputs.append({'value': contract["dispute_resolution"]["resolution"]["buyer_payout"], 'address': buyer_address}) if "vendor_payout" in contract["dispute_resolution"]["resolution"]: outputs.append({'value': contract["dispute_resolution"]["resolution"]["vendor_payout"], 'address': vendor_address}) tx = bitcoin.mktx(outpoints, outputs) signatures = [] chaincode = contract["buyer_order"]["order"]["payment"]["chaincode"] redeem_script = str(contract["buyer_order"]["order"]["payment"]["redeem_script"]) masterkey = bitcoin.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey) childkey = derive_childkey(masterkey, chaincode, bitcoin.MAINNET_PRIVATE) mod_key = derive_childkey(masterkey_m, chaincode) valid_inputs = 0 for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, childkey) signatures.append({"input_index": index, "signature": sig}) for s in contract["dispute_resolution"]["resolution"]["tx_signatures"]: if s["input_index"] == index: if bitcoin.verify_tx_input(tx, index, redeem_script, s["signature"], mod_key): tx = bitcoin.apply_multisignatures(tx, index, str(redeem_script), sig, str(s["signature"])) valid_inputs += 1 if valid_inputs == len(outpoints): self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.protocol.multiplexer.blockchain.broadcast(tx) else: raise Exception("Failed to reconstruct transaction with moderator signature.")
def rpc_order(self, sender, pubkey, encrypted): try: box = Box(PrivateKey(self.signing_key.encode(nacl.encoding.RawEncoder)), PublicKey(pubkey)) order = box.decrypt(encrypted) c = Contract(self.db, contract=json.loads(order, object_pairs_hook=OrderedDict), testnet=self.multiplexer.testnet) if c.verify(sender.signed_pubkey[64:]): self.router.addContact(sender) self.log.info("received an order from %s, waiting for payment..." % sender) payment_address = c.contract["buyer_order"]["order"]["payment"]["address"] chaincode = c.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = c.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) amount = c.contract["buyer_order"]["order"]["payment"]["amount"] listing_hash = c.contract["buyer_order"]["order"]["ref_hash"] signature = self.signing_key.sign( str(payment_address) + str(amount) + str(listing_hash) + str(buyer_key))[:64] c.await_funding(self.multiplexer.ws, self.multiplexer.blockchain, signature, False) return [signature] else: self.log.warning("received invalid order from %s" % sender) return ["False"] except Exception: self.log.error("unable to decrypt order from %s" % sender) return ["False"]
def rpc_order(self, sender, pubkey, encrypted): try: box = Box(PrivateKey(self.signing_key.encode(nacl.encoding.RawEncoder)), PublicKey(pubkey)) order = box.decrypt(encrypted) c = Contract(self.db, contract=json.loads(order, object_pairs_hook=OrderedDict), testnet=self.multiplexer.testnet) if c.verify(sender.signed_pubkey[64:]): self.router.addContact(sender) self.log.info("received an order from %s, waiting for payment..." % sender) payment_address = c.contract["buyer_order"]["order"]["payment"]["address"] chaincode = c.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = c.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) amount = c.contract["buyer_order"]["order"]["payment"]["amount"] listing_hash = c.contract["buyer_order"]["order"]["ref_hash"] signature = self.signing_key.sign( str(payment_address) + str(amount) + str(listing_hash) + str(buyer_key))[:64] c.await_funding(self.get_notification_listener(), self.multiplexer.blockchain, signature, False) return [signature] else: self.log.warning("received invalid order from %s" % sender) return ["False"] except Exception: self.log.error("unable to decrypt order from %s" % sender) return ["False"]
def parse_response(response): try: address = contract.contract["buyer_order"]["order"]["payment"]["address"] chaincode = contract.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = contract.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) amount = contract.contract["buyer_order"]["order"]["payment"]["amount"] listing_hash = contract.contract["buyer_order"]["order"]["ref_hash"] verify_key = nacl.signing.VerifyKey(node_to_ask.signed_pubkey[64:]) verify_key.verify(str(address) + str(amount) + str(listing_hash) + str(buyer_key), response[1][0]) return response[1][0] except Exception: return False
def parse_response(response): try: address = contract.contract["buyer_order"]["order"]["payment"]["address"] chaincode = contract.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = contract.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) amount = contract.contract["buyer_order"]["order"]["payment"]["amount"] listing_hash = contract.contract["buyer_order"]["order"]["ref_hash"] verify_key = nacl.signing.VerifyKey(node_to_ask.signed_pubkey[64:]) verify_key.verify( str(address) + str(amount) + str(listing_hash) + str(buyer_key), response[1][0]) return response[1][0] except Exception: return False
def history_fetched(ec, history): outpoints = [] satoshis = 0 outputs = [] dispute_json = {"dispute_resolution": {"resolution": {}}} if ec: print ec else: for tx_type, txid, i, height, value in history: # pylint: disable=W0612 if tx_type == obelisk.PointIdent.Output: satoshis += value outpoint = txid.encode("hex") + ":" + str(i) if outpoint not in outpoints: outpoints.append(outpoint) satoshis -= TRANSACTION_FEE moderator_fee = round(float(moderator_percentage * satoshis)) satoshis -= moderator_fee outputs.append({'value': moderator_fee, 'address': moderator_address}) dispute_json["dispute_resolution"]["resolution"]["moderator_address"] = moderator_address dispute_json["dispute_resolution"]["resolution"]["moderator_fee"] = moderator_fee dispute_json["dispute_resolution"]["resolution"]["transaction_fee"] = TRANSACTION_FEE if float(buyer_percentage) > 0: amt = round(float(buyer_percentage * satoshis)) dispute_json["dispute_resolution"]["resolution"]["buyer_payout"] = amt outputs.append({'value': amt, 'address': buyer_address}) if float(vendor_percentage) > 0: amt = round(float(vendor_percentage * satoshis)) dispute_json["dispute_resolution"]["resolution"]["vendor_payout"] = amt outputs.append({'value': amt, 'address': vendor_address}) tx = bitcoin.mktx(outpoints, outputs) chaincode = contract["buyer_order"]["order"]["payment"]["chaincode"] redeem_script = str(contract["buyer_order"]["order"]["payment"]["redeem_script"]) masterkey_m = bitcoin.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey) moderator_priv = derive_childkey(masterkey_m, chaincode, bitcoin.MAINNET_PRIVATE) signatures = [] for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, moderator_priv) signatures.append({"input_index": index, "signature": sig}) dispute_json["dispute_resolution"]["resolution"]["order_id"] = order_id dispute_json["dispute_resolution"]["resolution"]["tx_signatures"] = signatures dispute_json["dispute_resolution"]["resolution"]["claim"] = self.db.Cases().get_claim(order_id) dispute_json["dispute_resolution"]["resolution"]["decision"] = resolution dispute_json["dispute_resolution"]["signature"] = \ base64.b64encode(KeyChain(self.db).signing_key.sign(json.dumps( dispute_json["dispute_resolution"]["resolution"]))[:64]) def get_node(node_to_ask, recipient_guid, public_key): def parse_response(response): if not response[0]: self.send_message(Node(unhexlify(recipient_guid)), public_key, objects.PlaintextMessage.Type.Value("DISPUTE_CLOSE"), dispute_json, order_id, store_only=True) if node_to_ask: skephem = PrivateKey.generate() pkephem = skephem.public_key.encode(nacl.encoding.RawEncoder) box = Box(skephem, PublicKey(public_key, nacl.encoding.HexEncoder)) nonce = nacl.utils.random(Box.NONCE_SIZE) ciphertext = box.encrypt(json.dumps(dispute_json, indent=4), nonce) d = self.protocol.callDisputeClose(node_to_ask, pkephem, ciphertext) return d.addCallback(parse_response) else: return parse_response([False]) self.kserver.resolve(unhexlify(vendor_guid)).addCallback(get_node, vendor_guid, vendor_enc_key) self.kserver.resolve(unhexlify(buyer_guid)).addCallback(get_node, buyer_guid, buyer_enc_key) self.db.Cases().update_status(order_id, 1)
def verify(self, sender_key): """ Validate that an order sent over by a buyer is filled out correctly. """ try: contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict) del contract_dict["buyer_order"] contract_hash = digest(json.dumps(contract_dict, indent=4)) ref_hash = unhexlify(self.contract["buyer_order"]["order"]["ref_hash"]) # verify that the reference hash matches the contract and that the contract actually exists if contract_hash != ref_hash or not self.db.HashMap().get_file(ref_hash): raise Exception("Order for contract that doesn't exist") # verify the signature on the order verify_key = nacl.signing.VerifyKey(sender_key) verify_key.verify( json.dumps(self.contract["buyer_order"]["order"], indent=4), unhexlify(self.contract["buyer_order"]["signature"]), ) # verify buyer included the correct bitcoin amount for payment price_json = self.contract["vendor_offer"]["listing"]["item"]["price_per_unit"] if "bitcoin" in price_json: asking_price = price_json["bitcoin"] else: currency_code = price_json["fiat"]["currency_code"] fiat_price = price_json["fiat"]["price"] request = Request("https://api.bitcoinaverage.com/ticker/" + currency_code.upper() + "/last") response = urlopen(request) conversion_rate = response.read() asking_price = float("{0:.8f}".format(float(fiat_price) / float(conversion_rate))) if asking_price > self.contract["buyer_order"]["order"]["payment"]["amount"]: raise Exception("Insuffient Payment") # verify a valid moderator was selected # TODO: handle direct payments valid_mod = False for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == self.contract["buyer_order"]["order"]["moderator"]: valid_mod = True if not valid_mod: raise Exception("Invalid moderator") # verify all the shipping fields exist if self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "physical good": shipping = self.contract["buyer_order"]["order"]["shipping"] keys = ["ship_to", "address", "postal_code", "city", "state", "country"] for value in map(shipping.get, keys): if value is None: raise Exception("Missing shipping field") # verify buyer ID pubkeys = self.contract["buyer_order"]["order"]["id"]["pubkeys"] keys = ["guid", "bitcoin", "encryption"] for value in map(pubkeys.get, keys): if value is None: raise Exception("Missing pubkey field") # verify redeem script chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"] for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == self.contract["buyer_order"]["order"]["moderator"]: masterkey_m = mod["pubkeys"]["bitcoin"]["key"] masterkey_v = bitcoin.bip32_extract_key(self.keychain.bitcoin_master_pubkey) masterkey_b = self.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) vendor_key = derive_childkey(masterkey_v, chaincode) moderator_key = derive_childkey(masterkey_m, chaincode) redeem_script = "75" + bitcoin.mk_multisig_script([buyer_key, vendor_key, moderator_key], 2) if redeem_script != self.contract["buyer_order"]["order"]["payment"]["redeem_script"]: raise Exception("Invalid redeem script") # verify the payment address if self.testnet: payment_address = bitcoin.p2sh_scriptaddr(redeem_script, 196) else: payment_address = bitcoin.p2sh_scriptaddr(redeem_script) if payment_address != self.contract["buyer_order"]["order"]["payment"]["address"]: raise Exception("Incorrect payment address") return True except Exception: return False
def add_purchase_info( self, quantity, ship_to=None, shipping_address=None, city=None, state=None, postal_code=None, country=None, moderator=None, options=None, ): """ Update the contract with the buyer's purchase information. """ profile = Profile(self.db).get() order_json = { "buyer_order": { "order": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"), "quantity": quantity, "id": { "guid": self.keychain.guid.encode("hex"), "pubkeys": { "guid": self.keychain.guid_signed_pubkey[64:].encode("hex"), "bitcoin": bitcoin.bip32_extract_key(self.keychain.bitcoin_master_pubkey), "encryption": self.keychain.encryption_pubkey.encode("hex"), }, }, "payment": {}, } } } if profile.HasField("handle"): order_json["buyer_order"]["order"]["id"]["blockchain_id"] = profile.handle if self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "physical good": order_json["buyer_order"]["order"]["shipping"] = {} order_json["buyer_order"]["order"]["shipping"]["ship_to"] = ship_to order_json["buyer_order"]["order"]["shipping"]["address"] = shipping_address order_json["buyer_order"]["order"]["shipping"]["city"] = city order_json["buyer_order"]["order"]["shipping"]["state"] = state order_json["buyer_order"]["order"]["shipping"]["postal_code"] = postal_code order_json["buyer_order"]["order"]["shipping"]["country"] = country if options is not None: order_json["buyer_order"]["order"]["options"] = options if moderator: # TODO: Handle direct payments chaincode = sha256(str(random.getrandbits(256))).digest().encode("hex") order_json["buyer_order"]["order"]["payment"]["chaincode"] = chaincode valid_mod = False for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == moderator: order_json["buyer_order"]["order"]["moderator"] = moderator masterkey_m = mod["pubkeys"]["bitcoin"]["key"] valid_mod = True if not valid_mod: return False masterkey_b = bitcoin.bip32_extract_key(self.keychain.bitcoin_master_pubkey) masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) vendor_key = derive_childkey(masterkey_v, chaincode) moderator_key = derive_childkey(masterkey_m, chaincode) redeem_script = "75" + bitcoin.mk_multisig_script([buyer_key, vendor_key, moderator_key], 2) order_json["buyer_order"]["order"]["payment"]["redeem_script"] = redeem_script if self.testnet: payment_address = bitcoin.p2sh_scriptaddr(redeem_script, 196) else: payment_address = bitcoin.p2sh_scriptaddr(redeem_script) order_json["buyer_order"]["order"]["payment"]["address"] = payment_address price_json = self.contract["vendor_offer"]["listing"]["item"]["price_per_unit"] if "bitcoin" in price_json: order_json["buyer_order"]["order"]["payment"]["amount"] = price_json["bitcoin"] else: currency_code = price_json["fiat"]["currency_code"] fiat_price = price_json["fiat"]["price"] try: request = Request("https://api.bitcoinaverage.com/ticker/" + currency_code.upper() + "/last") response = urlopen(request) conversion_rate = response.read() except URLError: return False order_json["buyer_order"]["order"]["payment"]["amount"] = float( "{0:.8f}".format(float(fiat_price) / float(conversion_rate)) ) self.contract["buyer_order"] = order_json["buyer_order"] order = json.dumps(self.contract["buyer_order"]["order"], indent=4) self.contract["buyer_order"]["signature"] = self.keychain.signing_key.sign( order, encoder=nacl.encoding.HexEncoder )[:128] return self.contract["buyer_order"]["order"]["payment"]["address"]
def verify(self, sender_key): """ Validate that an order sent over by a buyer is filled out correctly. """ try: contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict) del contract_dict["buyer_order"] contract_hash = digest(json.dumps(contract_dict, indent=4)) ref_hash = unhexlify( self.contract["buyer_order"]["order"]["ref_hash"]) # verify that the reference hash matches the contract and that the contract actually exists if contract_hash != ref_hash or not self.db.HashMap().get_file( ref_hash): raise Exception("Order for contract that doesn't exist") # verify the signature on the order verify_key = nacl.signing.VerifyKey(sender_key) verify_key.verify( json.dumps(self.contract["buyer_order"]["order"], indent=4), unhexlify(self.contract["buyer_order"]["signature"])) # TODO: verify the bitcoin signature after we add it # verify buyer included the correct bitcoin amount for payment price_json = self.contract["vendor_offer"]["listing"]["item"][ "price_per_unit"] if "bitcoin" in price_json: asking_price = price_json["bitcoin"] else: currency_code = price_json["fiat"]["currency_code"] fiat_price = price_json["fiat"]["price"] request = Request('https://api.bitcoinaverage.com/ticker/' + currency_code.upper() + '/last') response = urlopen(request) conversion_rate = response.read() asking_price = float("{0:.8f}".format( float(fiat_price) / float(conversion_rate))) if asking_price > self.contract["buyer_order"]["order"]["payment"][ "amount"]: raise Exception("Insuffient Payment") # verify a valid moderator was selected # TODO: handle direct payments valid_mod = False for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == self.contract["buyer_order"]["order"][ "moderator"]: valid_mod = True if not valid_mod: raise Exception("Invalid moderator") # verify all the shipping fields exist if self.contract["vendor_offer"]["listing"]["metadata"][ "category"] == "physical good": shipping = self.contract["buyer_order"]["order"]["shipping"] keys = [ "ship_to", "address", "postal_code", "city", "state", "country" ] for value in map(shipping.get, keys): if value is None: raise Exception("Missing shipping field") # verify buyer ID pubkeys = self.contract["buyer_order"]["order"]["id"]["pubkeys"] keys = ["guid", "bitcoin", "encryption"] for value in map(pubkeys.get, keys): if value is None: raise Exception("Missing pubkey field") # verify redeem script chaincode = self.contract["buyer_order"]["order"]["payment"][ "chaincode"] for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == self.contract["buyer_order"]["order"][ "moderator"]: masterkey_m = mod["pubkeys"]["bitcoin"]["key"] masterkey_b = self.contract["buyer_order"]["order"]["id"][ "pubkeys"]["bitcoin"] masterkey_v = bitcoin.bip32_extract_key( self.keychain.bitcoin_master_pubkey) buyer_key = derive_childkey(masterkey_b, chaincode) vendor_key = derive_childkey(masterkey_v, chaincode) moderator_key = derive_childkey(masterkey_m, chaincode) redeem_script = bitcoin.mk_multisig_script( [buyer_key, vendor_key, moderator_key], 2) if redeem_script != self.contract["buyer_order"]["order"][ "payment"]["redeem_script"]: raise Exception("Invalid redeem script") # verify the payment address if self.testnet: payment_address = bitcoin.p2sh_scriptaddr(redeem_script, 196) else: payment_address = bitcoin.p2sh_scriptaddr(redeem_script) if payment_address != self.contract["buyer_order"]["order"][ "payment"]["address"]: raise Exception("Incorrect payment address") return True except Exception: return False
def accept_receipt(self, ws, blockchain, receipt_json=None): """ Process the final receipt sent over by the buyer. If valid, broadcast the transaction to the bitcoin network. """ self.ws = ws self.blockchain = blockchain try: if receipt_json: self.contract["buyer_receipt"] = json.loads( receipt_json, object_pairs_hook=OrderedDict) contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict) del contract_dict["buyer_receipt"] contract_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex") ref_hash = self.contract["buyer_receipt"]["receipt"]["ref_hash"] if ref_hash != contract_hash: raise Exception("Order number doesn't match") # The buyer may have sent over this whole contract, make sure the data we added wasn't manipulated. verify_key = self.keychain.signing_key.verify_key verify_key.verify( json.dumps( self.contract["vendor_order_confirmation"]["invoice"], indent=4), unhexlify( self.contract["vendor_order_confirmation"]["signature"])) order_id = self.contract["vendor_order_confirmation"]["invoice"][ "ref_hash"] outpoints = pickle.loads(self.db.Sales().get_outpoint(order_id)) payout_address = self.contract["vendor_order_confirmation"][ "invoice"]["payout"]["address"] redeem_script = str(self.contract["buyer_order"]["order"] ["payment"]["redeem_script"]) for output in outpoints: del output["value"] value = self.contract["vendor_order_confirmation"]["invoice"][ "payout"]["value"] outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) chaincode = self.contract["buyer_order"]["order"]["payment"][ "chaincode"] masterkey_b = self.contract["buyer_order"]["order"]["id"][ "pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) vendor_sigs = self.contract["vendor_order_confirmation"][ "invoice"]["payout"]["signature(s)"] buyer_sigs = self.contract["buyer_receipt"]["receipt"]["payout"][ "signature(s)"] for index in range(0, len(outpoints)): for s in vendor_sigs: if s["input_index"] == index: sig1 = str(s["signature"]) for s in buyer_sigs: if s["input_index"] == index: sig2 = str(s["signature"]) if bitcoin.verify_tx_input(tx, index, redeem_script, sig2, buyer_key): tx = bitcoin.apply_multisignatures(tx, index, str(redeem_script), sig1, sig2) else: raise Exception("Buyer sent invalid signature") d = defer.Deferred() def on_broadcast_complete(success): if success: d.callback(order_id) else: d.callback(False) def on_validate(success): def on_fetch(ec, result): if ec: # if it's not in the blockchain, let's try broadcasting it. self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx, cb=on_broadcast_complete) else: d.callback(order_id) if success: # broadcast anyway but don't wait for callback self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx) d.callback(order_id) else: # check to see if the tx is already in the blockchain self.blockchain.fetch_transaction( unhexlify(bitcoin.txhash(tx)), on_fetch) if "txid" in self.contract["buyer_receipt"]["receipt"]["payout"] \ and bitcoin.txhash(tx) == self.contract["buyer_receipt"]["receipt"]["payout"]["txid"]: # check mempool and blockchain for tx self.blockchain.validate(tx, cb=on_validate) else: # try broadcasting self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx, cb=on_broadcast_complete) # TODO: update db and file system if successful # TODO: broadcast over websocket return d except Exception: return defer.succeed(False)
def add_receipt(self, received, libbitcoin_client, feedback=None, quality=None, description=None, delivery_time=None, customer_service=None, review="", dispute=False, claim=None, payout=True): """ Add the final piece of the contract that appends the review and payout transaction. """ self.blockchain = libbitcoin_client receipt_json = { "buyer_receipt": { "receipt": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"), "listing": { "received": received, "listing_hash": self.contract["buyer_order"]["order"]["ref_hash"] }, "dispute": { "dispute": dispute } } } } if None not in (feedback, quality, description, delivery_time, customer_service): receipt_json["buyer_receipt"]["receipt"]["rating"] = {} receipt_json["buyer_receipt"]["receipt"]["rating"][ "feedback"] = feedback receipt_json["buyer_receipt"]["receipt"]["rating"][ "quality"] = quality receipt_json["buyer_receipt"]["receipt"]["rating"][ "description"] = description receipt_json["buyer_receipt"]["receipt"]["rating"][ "delivery_time"] = delivery_time receipt_json["buyer_receipt"]["receipt"]["rating"][ "customer_service"] = customer_service receipt_json["buyer_receipt"]["receipt"]["rating"][ "review"] = review if payout: order_id = self.contract["vendor_order_confirmation"]["invoice"][ "ref_hash"] outpoints = pickle.loads( self.db.Purchases().get_outpoint(order_id)) payout_address = self.contract["vendor_order_confirmation"][ "invoice"]["payout"]["address"] redeem_script = str(self.contract["buyer_order"]["order"] ["payment"]["redeem_script"]) for output in outpoints: del output["value"] value = self.contract["vendor_order_confirmation"]["invoice"][ "payout"]["value"] outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) signatures = [] chaincode = self.contract["buyer_order"]["order"]["payment"][ "chaincode"] masterkey_b = bitcoin.bip32_extract_key( self.keychain.bitcoin_master_privkey) buyer_priv = derive_childkey(masterkey_b, chaincode, bitcoin.MAINNET_PRIVATE) masterkey_v = self.contract["vendor_offer"]["listing"]["id"][ "pubkeys"]["bitcoin"] vendor_key = derive_childkey(masterkey_v, chaincode) valid_inputs = 0 for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, buyer_priv) signatures.append({"input_index": index, "signature": sig}) for s in self.contract["vendor_order_confirmation"]["invoice"][ "payout"]["signature(s)"]: if s["input_index"] == index: if bitcoin.verify_tx_input(tx, index, redeem_script, s["signature"], vendor_key): tx = bitcoin.apply_multisignatures( tx, index, str(redeem_script), sig, str(s["signature"])) valid_inputs += 1 receipt_json["buyer_receipt"]["receipt"]["payout"] = {} if valid_inputs == len(outpoints): self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx) receipt_json["buyer_receipt"]["receipt"]["payout"][ "txid"] = bitcoin.txhash(tx) receipt_json["buyer_receipt"]["receipt"]["payout"][ "signature(s)"] = signatures receipt_json["buyer_receipt"]["receipt"]["payout"]["value"] = value if claim: receipt_json["buyer_receipt"]["receipt"]["dispute"][ "claim"] = claim receipt = json.dumps(receipt_json["buyer_receipt"]["receipt"], indent=4) receipt_json["buyer_receipt"]["signature"] = \ self.keychain.signing_key.sign(receipt, encoder=nacl.encoding.HexEncoder)[:128] self.contract["buyer_receipt"] = receipt_json["buyer_receipt"]
def add_order_confirmation(self, payout_address, comments=None, shipper=None, tracking_number=None, est_delivery=None, url=None, password=None): """ Add the vendor's order confirmation to the contract. """ if not self.testnet and not (payout_address[:1] == "1" or payout_address[:1] == "3"): raise Exception("Bitcoin address is not a mainnet address") elif self.testnet and not \ (payout_address[:1] == "n" or payout_address[:1] == "m" or payout_address[:1] == "2"): raise Exception("Bitcoin address is not a testnet address") try: bitcoin.b58check_to_hex(payout_address) except AssertionError: raise Exception("Invalid Bitcoin address") conf_json = { "vendor_order_confirmation": { "invoice": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex") } } } if self.contract["vendor_offer"]["listing"]["metadata"][ "category"] == "physical good": shipping = { "shipper": shipper, "tracking_number": tracking_number, "est_delivery": est_delivery } conf_json["vendor_order_confirmation"]["invoice"][ "shipping"] = shipping elif self.contract["vendor_offer"]["listing"]["metadata"][ "category"] == "digital good": content_source = {"url": url, "password": password} conf_json["vendor_order_confirmation"]["invoice"][ "content_source"] = content_source if comments: conf_json["vendor_order_confirmation"]["invoice"][ "comments"] = comments confirmation = json.dumps( conf_json["vendor_order_confirmation"]["invoice"], indent=4) conf_json["vendor_order_confirmation"]["signature"] = \ self.keychain.signing_key.sign(confirmation, encoder=nacl.encoding.HexEncoder)[:128] order_id = digest(json.dumps(self.contract, indent=4)).encode("hex") # apply signatures outpoints = pickle.loads(self.db.Sales().get_outpoint(order_id)) redeem_script = self.contract["buyer_order"]["order"]["payment"][ "redeem_script"] value = 0 for output in outpoints: value += output["value"] del output["value"] value -= TRANSACTION_FEE outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) signatures = [] chaincode = self.contract["buyer_order"]["order"]["payment"][ "chaincode"] masterkey_v = bitcoin.bip32_extract_key( self.keychain.bitcoin_master_privkey) vendor_priv = derive_childkey(masterkey_v, chaincode, bitcoin.MAINNET_PRIVATE) for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, vendor_priv) signatures.append({"input_index": index, "signature": sig}) conf_json["vendor_order_confirmation"]["invoice"]["payout"] = {} conf_json["vendor_order_confirmation"]["invoice"]["payout"][ "address"] = payout_address conf_json["vendor_order_confirmation"]["invoice"]["payout"][ "value"] = value conf_json["vendor_order_confirmation"]["invoice"]["payout"][ "signature(s)"] = signatures self.contract["vendor_order_confirmation"] = conf_json[ "vendor_order_confirmation"] self.db.Sales().update_status(order_id, 2) file_path = DATA_FOLDER + "store/listings/in progress/" + order_id + ".json" with open(file_path, 'w') as outfile: outfile.write(json.dumps(self.contract, indent=4))
def add_purchase_info(self, quantity, ship_to=None, shipping_address=None, city=None, state=None, postal_code=None, country=None, moderator=None, options=None): """ Update the contract with the buyer's purchase information. """ profile = Profile(self.db).get() order_json = { "buyer_order": { "order": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"), "quantity": quantity, "id": { "guid": self.keychain.guid.encode("hex"), "pubkeys": { "guid": self.keychain.guid_signed_pubkey[64:].encode( "hex"), "bitcoin": bitcoin.bip32_extract_key( self.keychain.bitcoin_master_pubkey), "encryption": self.keychain.encryption_pubkey.encode("hex") } }, "payment": {} } } } if profile.HasField("handle"): order_json["buyer_order"]["order"]["id"][ "blockchain_id"] = profile.handle if self.contract["vendor_offer"]["listing"]["metadata"][ "category"] == "physical good": order_json["buyer_order"]["order"]["shipping"] = {} order_json["buyer_order"]["order"]["shipping"]["ship_to"] = ship_to order_json["buyer_order"]["order"]["shipping"][ "address"] = shipping_address order_json["buyer_order"]["order"]["shipping"]["city"] = city order_json["buyer_order"]["order"]["shipping"]["state"] = state order_json["buyer_order"]["order"]["shipping"][ "postal_code"] = postal_code order_json["buyer_order"]["order"]["shipping"]["country"] = country if options is not None: order_json["buyer_order"]["order"]["options"] = options if moderator: # TODO: Handle direct payments chaincode = sha256(str( random.getrandbits(256))).digest().encode("hex") order_json["buyer_order"]["order"]["payment"][ "chaincode"] = chaincode valid_mod = False for mod in self.contract["vendor_offer"]["listing"]["moderators"]: if mod["guid"] == moderator: order_json["buyer_order"]["order"]["moderator"] = moderator masterkey_m = mod["pubkeys"]["bitcoin"]["key"] valid_mod = True if not valid_mod: return False masterkey_b = bitcoin.bip32_extract_key( self.keychain.bitcoin_master_pubkey) masterkey_v = self.contract["vendor_offer"]["listing"]["id"][ "pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) vendor_key = derive_childkey(masterkey_v, chaincode) moderator_key = derive_childkey(masterkey_m, chaincode) redeem_script = bitcoin.mk_multisig_script( [buyer_key, vendor_key, moderator_key], 2) order_json["buyer_order"]["order"]["payment"][ "redeem_script"] = redeem_script if self.testnet: payment_address = bitcoin.p2sh_scriptaddr(redeem_script, 196) else: payment_address = bitcoin.p2sh_scriptaddr(redeem_script) order_json["buyer_order"]["order"]["payment"][ "address"] = payment_address price_json = self.contract["vendor_offer"]["listing"]["item"][ "price_per_unit"] if "bitcoin" in price_json: order_json["buyer_order"]["order"]["payment"][ "amount"] = price_json["bitcoin"] else: currency_code = price_json["fiat"]["currency_code"] fiat_price = price_json["fiat"]["price"] try: request = Request('https://api.bitcoinaverage.com/ticker/' + currency_code.upper() + '/last') response = urlopen(request) conversion_rate = response.read() except URLError: return False order_json["buyer_order"]["order"]["payment"]["amount"] = float( "{0:.8f}".format(float(fiat_price) / float(conversion_rate))) self.contract["buyer_order"] = order_json["buyer_order"] order = json.dumps(self.contract["buyer_order"]["order"], indent=4) # TODO: This should also be signed with the bitcoin key. It's the only way a moderator # will have to link this contract to a bitcoin transaction. self.contract["buyer_order"]["signature"] = \ self.keychain.signing_key.sign(order, encoder=nacl.encoding.HexEncoder)[:128] return (self.contract["buyer_order"]["order"]["payment"]["address"], order_json["buyer_order"]["order"]["payment"]["amount"])
def accept_receipt(self, ws, blockchain, receipt_json=None): """ Process the final receipt sent over by the buyer. If valid, broadcast the transaction to the bitcoin network. """ self.ws = ws self.blockchain = blockchain try: if receipt_json: self.contract["buyer_receipt"] = json.loads(receipt_json, object_pairs_hook=OrderedDict) contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict) del contract_dict["buyer_receipt"] contract_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex") ref_hash = self.contract["buyer_receipt"]["receipt"]["ref_hash"] if ref_hash != contract_hash: raise Exception("Order number doesn't match") # The buyer may have sent over this whole contract, make sure the data we added wasn't manipulated. verify_key = self.keychain.signing_key.verify_key verify_key.verify(json.dumps(self.contract["vendor_order_confirmation"]["invoice"], indent=4), unhexlify(self.contract["vendor_order_confirmation"]["signature"])) order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"] outpoints = pickle.loads(self.db.Sales().get_outpoint(order_id)) payout_address = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["address"] redeem_script = str(self.contract["buyer_order"]["order"]["payment"]["redeem_script"]) for output in outpoints: del output["value"] value = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["value"] outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = self.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"] buyer_key = derive_childkey(masterkey_b, chaincode) vendor_sigs = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"] buyer_sigs = self.contract["buyer_receipt"]["receipt"]["payout"]["signature(s)"] for index in range(0, len(outpoints)): for s in vendor_sigs: if s["input_index"] == index: sig1 = str(s["signature"]) for s in buyer_sigs: if s["input_index"] == index: sig2 = str(s["signature"]) if bitcoin.verify_tx_input(tx, index, redeem_script, sig2, buyer_key): tx = bitcoin.apply_multisignatures(tx, index, str(redeem_script), sig1, sig2) else: raise Exception("Buyer sent invalid signature") d = defer.Deferred() def on_broadcast_complete(success): if success: d.callback(order_id) else: d.callback(False) def on_validate(success): def on_fetch(ec, result): if ec: # if it's not in the blockchain, let's try broadcasting it. self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx, cb=on_broadcast_complete) else: d.callback(order_id) if success: # broadcast anyway but don't wait for callback self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx) d.callback(order_id) else: # check to see if the tx is already in the blockchain self.blockchain.fetch_transaction(unhexlify(bitcoin.txhash(tx)), on_fetch) if "txid" in self.contract["buyer_receipt"]["receipt"]["payout"] \ and bitcoin.txhash(tx) == self.contract["buyer_receipt"]["receipt"]["payout"]["txid"]: # check mempool and blockchain for tx self.blockchain.validate(tx, cb=on_validate) else: # try broadcasting self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx, cb=on_broadcast_complete) # TODO: update db and file system if successful # TODO: broadcast over websocket return d except Exception: return defer.succeed(False)
def add_receipt(self, received, libbitcoin_client, feedback=None, quality=None, description=None, delivery_time=None, customer_service=None, review="", dispute=False, claim=None, payout=True): """ Add the final piece of the contract that appends the review and payout transaction. """ self.blockchain = libbitcoin_client receipt_json = { "buyer_receipt": { "receipt": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"), "listing": { "received": received, "listing_hash": self.contract["buyer_order"]["order"]["ref_hash"] }, "dispute": { "dispute": dispute } } } } if None not in (feedback, quality, description, delivery_time, customer_service): receipt_json["buyer_receipt"]["receipt"]["rating"] = {} receipt_json["buyer_receipt"]["receipt"]["rating"]["feedback"] = feedback receipt_json["buyer_receipt"]["receipt"]["rating"]["quality"] = quality receipt_json["buyer_receipt"]["receipt"]["rating"]["description"] = description receipt_json["buyer_receipt"]["receipt"]["rating"]["delivery_time"] = delivery_time receipt_json["buyer_receipt"]["receipt"]["rating"]["customer_service"] = customer_service receipt_json["buyer_receipt"]["receipt"]["rating"]["review"] = review if payout: order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"] outpoints = pickle.loads(self.db.Purchases().get_outpoint(order_id)) payout_address = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["address"] redeem_script = str(self.contract["buyer_order"]["order"]["payment"]["redeem_script"]) for output in outpoints: del output["value"] value = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["value"] outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) signatures = [] chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_b = bitcoin.bip32_extract_key(self.keychain.bitcoin_master_privkey) buyer_priv = derive_childkey(masterkey_b, chaincode, bitcoin.MAINNET_PRIVATE) masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"] vendor_key = derive_childkey(masterkey_v, chaincode) valid_inputs = 0 for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, buyer_priv) signatures.append({"input_index": index, "signature": sig}) for s in self.contract["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"]: if s["input_index"] == index: if bitcoin.verify_tx_input(tx, index, redeem_script, s["signature"], vendor_key): tx = bitcoin.apply_multisignatures(tx, index, str(redeem_script), sig, str(s["signature"])) valid_inputs += 1 receipt_json["buyer_receipt"]["receipt"]["payout"] = {} if valid_inputs == len(outpoints): self.log.info("Broadcasting payout tx %s to network" % bitcoin.txhash(tx)) self.blockchain.broadcast(tx) receipt_json["buyer_receipt"]["receipt"]["payout"]["txid"] = bitcoin.txhash(tx) receipt_json["buyer_receipt"]["receipt"]["payout"]["signature(s)"] = signatures receipt_json["buyer_receipt"]["receipt"]["payout"]["value"] = value if claim: receipt_json["buyer_receipt"]["receipt"]["dispute"]["claim"] = claim receipt = json.dumps(receipt_json["buyer_receipt"]["receipt"], indent=4) receipt_json["buyer_receipt"]["signature"] = \ self.keychain.signing_key.sign(receipt, encoder=nacl.encoding.HexEncoder)[:128] self.contract["buyer_receipt"] = receipt_json["buyer_receipt"]
def add_order_confirmation(self, payout_address, comments=None, shipper=None, tracking_number=None, est_delivery=None, url=None, password=None): """ Add the vendor's order confirmation to the contract. """ if not self.testnet and not (payout_address[:1] == "1" or payout_address[:1] == "3"): raise Exception("Bitcoin address is not a mainnet address") elif self.testnet and not \ (payout_address[:1] == "n" or payout_address[:1] == "m" or payout_address[:1] == "2"): raise Exception("Bitcoin address is not a testnet address") try: bitcoin.b58check_to_hex(payout_address) except AssertionError: raise Exception("Invalid Bitcoin address") conf_json = { "vendor_order_confirmation": { "invoice": { "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex") } } } if self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "physical good": shipping = {"shipper": shipper, "tracking_number": tracking_number, "est_delivery": est_delivery} conf_json["vendor_order_confirmation"]["invoice"]["shipping"] = shipping elif self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "digital good": content_source = {"url": url, "password": password} conf_json["vendor_order_confirmation"]["invoice"]["content_source"] = content_source if comments: conf_json["vendor_order_confirmation"]["invoice"]["comments"] = comments confirmation = json.dumps(conf_json["vendor_order_confirmation"]["invoice"], indent=4) conf_json["vendor_order_confirmation"]["signature"] = \ self.keychain.signing_key.sign(confirmation, encoder=nacl.encoding.HexEncoder)[:128] order_id = digest(json.dumps(self.contract, indent=4)).encode("hex") # apply signatures outpoints = pickle.loads(self.db.Sales().get_outpoint(order_id)) redeem_script = self.contract["buyer_order"]["order"]["payment"]["redeem_script"] value = 0 for output in outpoints: value += output["value"] del output["value"] value -= TRANSACTION_FEE outs = [{'value': value, 'address': payout_address}] tx = bitcoin.mktx(outpoints, outs) signatures = [] chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"] masterkey_v = bitcoin.bip32_extract_key(self.keychain.bitcoin_master_privkey) vendor_priv = derive_childkey(masterkey_v, chaincode, bitcoin.MAINNET_PRIVATE) for index in range(0, len(outpoints)): sig = bitcoin.multisign(tx, index, redeem_script, vendor_priv) signatures.append({"input_index": index, "signature": sig}) conf_json["vendor_order_confirmation"]["invoice"]["payout"] = {} conf_json["vendor_order_confirmation"]["invoice"]["payout"]["address"] = payout_address conf_json["vendor_order_confirmation"]["invoice"]["payout"]["value"] = value conf_json["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"] = signatures self.contract["vendor_order_confirmation"] = conf_json["vendor_order_confirmation"] self.db.Sales().update_status(order_id, 2) file_path = DATA_FOLDER + "store/listings/in progress/" + order_id + ".json" with open(file_path, 'w') as outfile: outfile.write(json.dumps(self.contract, indent=4))