<commit_before>__author__ = 'chris'

import base64
import bitcointools
import json
import nacl.encoding
import nacl.signing
import os
import random
import re
import time
from binascii import unhexlify
from bitcoin import SelectParams
from bitcoin.core.script import CScript, OP_2, OP_3, OP_CHECKMULTISIG
from bitcoin.wallet import P2SHBitcoinAddress, P2PKHBitcoinAddress, CBitcoinAddress
from collections import OrderedDict
from config import DATA_FOLDER, TRANSACTION_FEE
from copy import deepcopy
from datetime import datetime
from dht.utils import digest
from hashlib import sha256
from keys.bip32utils import derive_childkey
from keys.keychain import KeyChain
from log import Logger
from market.profile import Profile
from market.btcprice import BtcPrice
from market.transactions import BitcoinTransaction
from protos.countries import CountryCode
from protos.objects import Listings


class Contract(object):
    """
    A class for creating and interacting with OpenBazaar Ricardian contracts.
    """

    def __init__(self, database, contract=None, hash_value=None, testnet=False):
        """
        This class can be instantiated with either an `OrderedDict` or a hash
        of a contract. If a hash is used, we will load the contract from either
        the file system or cache.

        Alternatively, pass in no parameters if the intent is to create a new
        contract.

        Args:
            contract: an `OrderedDict` containing a filled out json contract
            hash_value: a hash160 of a contract
            testnet: is this contract on the testnet
        """
        self.db = database
        self.keychain = KeyChain(self.db)
        if contract is not None:
            self.contract = contract
        elif hash_value is not None:
            try:
                file_path = self.db.filemap.get_file(hash_value.encode("hex"))
                if file_path is None:
                    file_path = DATA_FOLDER + "cache/" + hash_value.encode("hex")
                with open(file_path, 'r') as filename:
                    self.contract = json.load(filename, object_pairs_hook=OrderedDict)
            except Exception:
                if os.path.exists(DATA_FOLDER + "purchases/unfunded/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/unfunded/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "purchases/in progress/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/in progress/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "purchases/trade receipts/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/trade receipts/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "store/contracts/unfunded/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/unfunded/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER +
                                    "store/contracts/in progress/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/in progress/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER +
                                    "store/contracts/trade receipts/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/trade receipts/" + hash_value.encode("hex") + ".json"

                try:
                    with open(file_path, 'r') as filename:
                        self.contract = json.load(filename, object_pairs_hook=OrderedDict)
                except Exception:
                    self.contract = {}
        else:
            self.contract = {}
        self.log = Logger(system=self)

        # used when purchasing this contract
        self.testnet = testnet
        self.notification_listener = None
        self.blockchain = None
        self.amount_funded = 0
        self.received_txs = []
        self.is_purchase = False
        self.outpoints = []

    def create(self,
               expiration_date,
               metadata_category,
               title,
               description,
               currency_code,
               price,
               process_time,
               nsfw,
               shipping_origin=None,
               shipping_regions=None,
               est_delivery_domestic=None,
               est_delivery_international=None,
               terms_conditions=None,
               returns=None,
               keywords=None,
               category=None,
               condition=None,
               sku=None,
               images=None,
               free_shipping=None,
               shipping_currency_code=None,
               shipping_domestic=None,
               shipping_international=None,
               options=None,
               moderators=None,
               contract_id=None):
        """
        All parameters are strings except:

        :param expiration_date: `string` (must be formatted UTC datetime)
        :param keywords: `list`
        :param nsfw: `boolean`
        :param images: a `list` of image files
        :param free_shipping: `boolean`
        :param shipping_origin: a 'string' formatted `CountryCode`
        :param shipping_regions: a 'list' of 'string' formatted `CountryCode`s
        :param options: a 'dict' containing options as keys and 'list' as option values.
        :param moderators: a 'list' of 'string' guids (hex encoded).
        """

        profile = Profile(self.db).get()
        if contract_id is not None and contract_id != "":
            self.previous_title = self.contract["vendor_offer"]["listing"]["item"]["title"]
        else:
            self.previous_title = None
            contract_id = digest(random.getrandbits(255)).encode("hex")

        self.contract = OrderedDict(
            {
                "vendor_offer": {
                    "listing": {
                        "contract_id": contract_id,
                        "metadata": {
                            "version": "1",
                            "category": metadata_category.lower(),
                            "category_sub": "fixed price"
                        },
                        "id": {
                            "guid": self.keychain.guid.encode("hex"),
                            "pubkeys": {
                                "guid": self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder),
                                "bitcoin": bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey)
                            }
                        },
                        "item": {
                            "title": title,
                            "description": description,
                            "process_time": process_time,
                            "price_per_unit": {},
                            "nsfw": nsfw
                        }
                    }
                }
            }
        )
        if expiration_date == "":
            self.contract["vendor_offer"]["listing"]["metadata"]["expiry"] = "never"
        else:
            self.contract["vendor_offer"]["listing"]["metadata"]["expiry"] = expiration_date + " UTC"
        if metadata_category == "physical good" and condition is not None:
            self.contract["vendor_offer"]["listing"]["item"]["condition"] = condition
        if currency_code.upper() == "BTC":
            item = self.contract["vendor_offer"]["listing"]["item"]
            item["price_per_unit"]["bitcoin"] = round(float(price), 8)
        else:
            item = self.contract["vendor_offer"]["listing"]["item"]
            item["price_per_unit"]["fiat"] = {}
            item["price_per_unit"]["fiat"]["price"] = price
            item["price_per_unit"]["fiat"]["currency_code"] = currency_code
        if keywords is not None:
            self.contract["vendor_offer"]["listing"]["item"]["keywords"] = []
            self.contract["vendor_offer"]["listing"]["item"]["keywords"].extend(keywords)
        if category is not None:
            self.contract["vendor_offer"]["listing"]["item"]["category"] = category
        if sku is not None:
            self.contract["vendor_offer"]["listing"]["item"]["sku"] = sku
        if options is not None:
            self.contract["vendor_offer"]["listing"]["item"]["options"] = options
        if metadata_category == "physical good":
            self.contract["vendor_offer"]["listing"]["shipping"] = {}
            shipping = self.contract["vendor_offer"]["listing"]["shipping"]
            shipping["shipping_origin"] = shipping_origin
            if free_shipping is False:
                self.contract["vendor_offer"]["listing"]["shipping"]["free"] = False
                self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"] = {}
                if shipping_currency_code == "BTC":
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"] = {}
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"][
                        "domestic"] = shipping_domestic
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"][
                        "international"] = shipping_international
                else:
                    shipping = self.contract["vendor_offer"]["listing"]["shipping"]
                    shipping["flat_fee"]["fiat"] = {}
                    shipping["flat_fee"]["fiat"]["price"] = {}
                    shipping["flat_fee"]["fiat"]["price"][
                        "domestic"] = shipping_domestic
                    shipping["flat_fee"]["fiat"]["price"][
                        "international"] = shipping_international
                    shipping["flat_fee"]["fiat"][
                        "currency_code"] = shipping_currency_code
            else:
                self.contract["vendor_offer"]["listing"]["shipping"]["free"] = True
            self.contract["vendor_offer"]["listing"]["shipping"]["shipping_regions"] = []
            for region in shipping_regions:
                shipping = self.contract["vendor_offer"]["listing"]["shipping"]
                shipping["shipping_regions"].append(region)
            listing = self.contract["vendor_offer"]["listing"]
            listing["shipping"]["est_delivery"] = {}
            listing["shipping"]["est_delivery"]["domestic"] = est_delivery_domestic
            listing["shipping"]["est_delivery"][
                "international"] = est_delivery_international
        if profile.handle != "":
            self.contract["vendor_offer"]["listing"]["id"]["blockchain_id"] = profile.handle
        if images is not None:
            self.contract["vendor_offer"]["listing"]["item"]["image_hashes"] = []
            for image_hash in images:
                if len(image_hash) != 40:
                    raise Exception("Invalid image hash")
                self.contract["vendor_offer"]["listing"]["item"]["image_hashes"].append(image_hash)
        if terms_conditions is not None or returns is not None:
            self.contract["vendor_offer"]["listing"]["policy"] = {}
            if terms_conditions is not None:
                self.contract["vendor_offer"]["listing"]["policy"]["terms_conditions"] = terms_conditions
            if returns is not None:
                self.contract["vendor_offer"]["listing"]["policy"]["returns"] = returns
        if moderators is not None:
            self.contract["vendor_offer"]["listing"]["moderators"] = []
            for mod in moderators:
                mod_info = self.db.moderators.get_moderator(mod)
                if mod_info is not None:
                    moderator = {
                        "guid": mod,
                        "name": mod_info[5],
                        "avatar": mod_info[7].encode("hex"),
                        "short_description": mod_info[6],
                        "fee": str(mod_info[8]) + "%",
                        "blockchain_id": mod_info[4],
                        "pubkeys": {
                            "guid": mod_info[1].encode("hex"),
                            "bitcoin": {
                                "key": mod_info[2].encode("hex"),
                                "signature": base64.b64encode(mod_info[3])
                            }
                        }
                    }
                    self.contract["vendor_offer"]["listing"]["moderators"].append(moderator)

        listing = json.dumps(self.contract["vendor_offer"]["listing"], indent=4)
        self.contract["vendor_offer"]["signatures"] = {}
        self.contract["vendor_offer"]["signatures"]["guid"] = \
            base64.b64encode(self.keychain.signing_key.sign(listing)[:64])
        self.contract["vendor_offer"]["signatures"]["bitcoin"] = \
            bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(
                listing, bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)))
        self.save()

    def add_purchase_info(self,
                          quantity,
                          refund_address,
                          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.
        """

        if not self.testnet and not (refund_address[:1] == "1" or refund_address[:1] == "3"):
            raise Exception("Bitcoin address is not a mainnet address")
        elif self.testnet and not \
                (refund_address[:1] == "n" or refund_address[:1] == "m" or refund_address[:1] == "2"):
            raise Exception("Bitcoin address is not a testnet address")
        try:
            bitcointools.b58check_to_hex(refund_address)
        except AssertionError:
            raise Exception("Invalid Bitcoin address")

        profile = Profile(self.db).get()
        order_json = {
            "buyer_order": {
                "order": {
                    "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"),
                    "date": str(datetime.utcnow()) + " UTC",
                    "quantity": quantity,
                    "id": {
                        "guid": self.keychain.guid.encode("hex"),
                        "pubkeys": {
                            "guid": self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder),
                            "bitcoin": bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey),
                        }
                    },
                    "payment": {},
                    "refund_address": refund_address
                }
            }
        }
        SelectParams("testnet" if self.testnet else "mainnet")
        if profile.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:
            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 = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey)
            masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"]
            buyer_key = unhexlify(derive_childkey(masterkey_b, chaincode))
            vendor_key = unhexlify(derive_childkey(masterkey_v, chaincode))
            moderator_key = unhexlify(derive_childkey(masterkey_m, chaincode))

            redeem_script = CScript([OP_2, buyer_key, vendor_key, moderator_key, OP_3, OP_CHECKMULTISIG])
            order_json["buyer_order"]["order"]["payment"]["redeem_script"] = redeem_script.encode("hex")
            payment_address = str(P2SHBitcoinAddress.from_redeemScript(redeem_script))
            order_json["buyer_order"]["order"]["payment"]["address"] = payment_address
            order_json["buyer_order"]["order"]["payment"]["refund_tx_fee"] = TRANSACTION_FEE
        else:
            chaincode = sha256(str(random.getrandbits(256))).digest().encode("hex")
            order_json["buyer_order"]["order"]["payment"]["chaincode"] = chaincode

            masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"]
            vendor_key = unhexlify(derive_childkey(masterkey_v, chaincode))

            payment_address = str(P2PKHBitcoinAddress.from_pubkey(vendor_key))
            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:
            amount_to_pay = float(price_json["bitcoin"]) * quantity
        else:
            currency_code = price_json["fiat"]["currency_code"]
            fiat_price = price_json["fiat"]["price"]
            conversion_rate = BtcPrice.instance().get(currency_code.upper())
            amount_to_pay = float("{0:.8f}".format(float(fiat_price) / float(conversion_rate))) * quantity
        if "shipping" in self.contract["vendor_offer"]["listing"]:
            if not self.contract["vendor_offer"]["listing"]["shipping"]["free"]:
                shipping_origin = str(self.contract["vendor_offer"]["listing"]["shipping"][
                    "shipping_origin"].upper())
                if shipping_origin == country.upper():
                    if "bitcoin" in self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]:
                        shipping_amount = float(self.contract["vendor_offer"]["listing"][
                            "shipping"]["flat_fee"]["bitcoin"]["domestic"]) * quantity
                    else:
                        price = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["fiat"][
                            "price"]["domestic"]
                        currency = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"][
                            "fiat"]["currency_code"]
                        conversion_rate = BtcPrice.instance().get(currency.upper())
                        shipping_amount = float("{0:.8f}".format(float(price) / float(conversion_rate))) * quantity
                else:
                    if "bitcoin" in self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]:
                        shipping_amount = float(self.contract["vendor_offer"]["listing"]["shipping"][
                            "flat_fee"]["bitcoin"]["international"]) * quantity
                    else:
                        price = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["fiat"][
                            "price"]["international"]
                        currency = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"][
                            "fiat"]["currency_code"]

                        conversion_rate = BtcPrice.instance().get(currency.upper())
                        shipping_amount = float("{0:.8f}".format(float(price) / float(conversion_rate))) * quantity
                amount_to_pay += shipping_amount

        if round(amount_to_pay, 8) < round(TRANSACTION_FEE / float(100000000), 8):
            raise Exception("Contract price is below transaction fee.")

        order_json["buyer_order"]["order"]["payment"]["amount"] = round(amount_to_pay, 8)
        self.contract["buyer_order"] = order_json["buyer_order"]

        order = json.dumps(self.contract["buyer_order"]["order"], indent=4)
        self.contract["buyer_order"]["signatures"] = {}
        self.contract["buyer_order"]["signatures"]["guid"] = \
            base64.b64encode(self.keychain.signing_key.sign(order)[:64])
        self.contract["buyer_order"]["signatures"]["bitcoin"] = \
            bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(
                order, bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)))

        return (self.contract["buyer_order"]["order"]["payment"]["address"],
                order_json["buyer_order"]["order"]["payment"]["amount"])

    def add_order_confirmation(self,
                               libbitcoin_client,
                               payout_address,
                               comments=None,
                               shipper=None,
                               tracking_number=None,
                               est_delivery=None,
                               url=None,
                               password=<PASSWORD>):
        """
        Add the vendor's order confirmation to the contract.
        """
        self.blockchain = libbitcoin_client
        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:
            bitcointools.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
        order_id = digest(json.dumps(self.contract, indent=4)).encode("hex")
        # apply signatures
        outpoints = json.loads(self.db.sales.get_outpoint(order_id))
        if "moderator" in self.contract["buyer_order"]["order"]:
            redeem_script = self.contract["buyer_order"]["order"]["payment"]["redeem_script"]
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address, testnet=self.testnet)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_v = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            vendor_priv = derive_childkey(masterkey_v, chaincode, bitcointools.MAINNET_PRIVATE)
            sigs = tx.create_signature(vendor_priv, redeem_script)
            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"] = tx.get_out_value()
            conf_json["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"] = sigs
        else:
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address, testnet=self.testnet)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_v = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            vendor_priv = derive_childkey(masterkey_v, chaincode, bitcointools.MAINNET_PRIVATE)
            tx.sign(vendor_priv)
            tx.broadcast(self.blockchain)
            self.db.transactions.add_transaction(tx.to_raw_tx())
            self.log.info("broadcasting payout tx %s to network" % tx.get_hash())
            self.db.sales.update_payment_tx(order_id, tx.get_hash())

        confirmation = json.dumps(conf_json["vendor_order_confirmation"]["invoice"], indent=4)
        conf_json["vendor_order_confirmation"]["signature"] = \
            base64.b64encode(self.keychain.signing_key.sign(confirmation)[:64])

        self.contract["vendor_order_confirmation"] = conf_json["vendor_order_confirmation"]
        self.db.sales.update_status(order_id, 2)
        file_path = DATA_FOLDER + "store/contracts/in progress/" + order_id + ".json"
        with open(file_path, 'w') as outfile:
            outfile.write(json.dumps(self.contract, indent=4))

    def accept_order_confirmation(self, notification_listener, confirmation_json=None):
        """
        Validate the order confirmation sent over from the vendor and update our node accordingly.
        """
        self.notification_listener = notification_listener
        try:
            if confirmation_json:
                self.contract["vendor_order_confirmation"] = json.loads(confirmation_json,
                                                                        object_pairs_hook=OrderedDict)

            contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict)
            del contract_dict["vendor_order_confirmation"]
            contract_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex")
            ref_hash = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
            if ref_hash != contract_hash:
                raise Exception("Order number doesn't match")
            if self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "physical good":
                shipping = self.contract["vendor_order_confirmation"]["invoice"]["shipping"]
                if "tracking_number" not in shipping or "shipper" not in shipping:
                    raise Exception("No shipping information")

            # TODO: verify signature
            # TODO: verify payout object
            status = self.db.purchases.get_status(contract_hash)
            if status == 2 or status == 3:
                raise Exception("Order confirmation already processed for this contract")

            # update the order status in the db
            self.db.purchases.update_status(contract_hash, 2)
            file_path = DATA_FOLDER + "purchases/in progress/" + contract_hash + ".json"

            # update the contract in the file system
            with open(file_path, 'w') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))
            title = self.contract["vendor_offer"]["listing"]["item"]["title"]
            if "image_hashes" in self.contract["vendor_offer"]["listing"]["item"]:
                image_hash = unhexlify(self.contract["vendor_offer"]["listing"]["item"]["image_hashes"][0])
            else:
                image_hash = ""
            if "blockchain_id" in self.contract["vendor_offer"]["listing"]["id"]:
                handle = self.contract["vendor_offer"]["listing"]["id"]["blockchain_id"]
            else:
                handle = ""
            vendor_guid = self.contract["vendor_offer"]["listing"]["id"]["guid"]
            self.notification_listener.notify(vendor_guid, handle, "order confirmation", contract_hash, title,
                                              image_hash)
            return contract_hash
        except Exception:
            return 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,
                    anonymous=True):

        """
        Add the final piece of the contract that appends the review and payout transaction.
        """
        self.blockchain = libbitcoin_client
        contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict)
        if "dispute" in contract_dict:
            del contract_dict["dispute"]
        if "dispute_resolution" in contract_dict:
            del contract_dict["dispute_resolution"]
        reference_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex")
        receipt_json = {
            "buyer_receipt": {
                "receipt": {
                    "ref_hash": reference_hash,
                    "listing": {
                        "received": received,
                        "listing_hash": self.contract["buyer_order"]["order"]["ref_hash"]
                    },
                    "dispute": {
                        "dispute": dispute
                    }
                }
            }
        }
        if "vendor_order_confirmation" in self.contract:
            order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
        else:
            order_id = self.get_order_id()
        if None not in (feedback, quality, description, delivery_time, customer_service):
            address = self.contract["buyer_order"]["order"]["payment"]["address"]
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_b = self.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"]
            buyer_pub = derive_childkey(masterkey_b, chaincode)
            buyer_priv = derive_childkey(bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey),
                                         chaincode, bitcointools.MAINNET_PRIVATE)
            amount = self.contract["buyer_order"]["order"]["payment"]["amount"]
            listing_hash = self.contract["vendor_offer"]["listing"]["contract_id"]

            receipt_json["buyer_receipt"]["receipt"]["rating"] = OrderedDict()
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"] = OrderedDict()
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["feedback"] = feedback
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["quality"] = quality
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["description"] = description
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["delivery_time"] = delivery_time
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["customer_service"] = customer_service
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["review"] = review
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["address"] = address
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_key"] = buyer_pub
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["amount"] = amount
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["listing"] = listing_hash
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["proof_of_tx"] = \
                base64.b64encode(self.db.purchases.get_proof_sig(order_id))
            if not anonymous:
                receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_guid"] = \
                    self.keychain.guid.encode("hex")
                receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_guid_key"] = \
                    self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder)

        status = self.db.purchases.get_status(order_id)
        if status < 3 and "moderator" in self.contract["buyer_order"]["order"]:
            outpoints = json.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"])
            value = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["value"]
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address,
                                                  testnet=self.testnet, out_value=value)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_b = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            buyer_priv = derive_childkey(masterkey_b, chaincode, bitcointools.MAINNET_PRIVATE)

            buyer_signatures = tx.create_signature(buyer_priv, redeem_script)
            signatures = []
            for i in range(len(outpoints)):
                for vendor_sig in self.contract["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"]:
                    if vendor_sig["index"] == i:
                        v_signature = vendor_sig["signature"]
                for buyer_sig in buyer_signatures:
                    if buyer_sig["index"] == i:
                        b_signature = buyer_sig["signature"]
                signature_obj = {"index": i, "signatures": [b_signature, v_signature]}
                signatures.append(signature_obj)

            receipt_json["buyer_receipt"]["receipt"]["payout"] = {}
            tx.multisign(signatures, redeem_script)
            tx.broadcast(self.blockchain)
            self.db.transactions.add_transaction(tx.to_raw_tx())

            self.log.info("broadcasting payout tx %s to network" % tx.get_hash())
            receipt_json["buyer_receipt"]["receipt"]["payout"]["txid"] = tx.get_hash()

            receipt_json["buyer_receipt"]["receipt"]["payout"]["signature(s)"] = buyer_signatures
            receipt_json["buyer_receipt"]["receipt"]["payout"]["value"] = tx.get_out_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"] = \
            base64.b64encode(self.keychain.signing_key.sign(receipt)[:64])
        self.contract["buyer_receipt"] = receipt_json["buyer_receipt"]

        if "rating" in self.contract["buyer_receipt"]["receipt"]:
            self.contract["buyer_receipt"]["receipt"]["rating"]["signature"] = \
                bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(json.dumps(
                    self.contract["buyer_receipt"]["receipt"]["rating"]["tx_summary"], indent=4), buyer_priv))
            if not anonymous:
                self.contract["buyer_receipt"]["receipt"]["rating"]["guid_signature"] = \
                    base64.b64encode(self.keychain.signing_key.sign(json.dumps(
                        self.contract["buyer_receipt"]["receipt"]["rating"]["tx_summary"], indent=4))[:64])

        if status < 3:
            self.db.purchases.update_status(order_id, 3)
            file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json"
            with open(file_path, 'w') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))
            file_path = DATA_FOLDER + "purchases/in progress/" + order_id + ".json"
            if os.path.exists(file_path):
                os.remove(file_path)
        else:
            file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json"
            with open(file_path, 'wb') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))

    def accept_receipt(self, notification_listener, blockchain, receipt_json=None):
        """
        Process the final receipt sent over by the buyer. If valid, broadcast the transaction
        to the bitcoin network.
        """
        self.notification_listener = notification_listener
        self.blockchain = blockchain
        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"]
        if "dispute" in contract_dict:
            del contract_dict["dispute"]
        if "dispute_resolution" in contract_dict:
            del contract_dict["dispute_resolution"]
        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")

        # TODO: verify buyer signature
        if "vendor_order_confirmation" in self.contract:
            order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
        else:<commit_msg>Prohibit multiple receipts on same order<commit_after>__author__ = 'chris'

import base64
import bitcointools
import json
import nacl.encoding
import nacl.signing
import os
import random
import re
import time
from binascii import unhexlify
from bitcoin import SelectParams
from bitcoin.core.script import CScript, OP_2, OP_3, OP_CHECKMULTISIG
from bitcoin.wallet import P2SHBitcoinAddress, P2PKHBitcoinAddress, CBitcoinAddress
from collections import OrderedDict
from config import DATA_FOLDER, TRANSACTION_FEE
from copy import deepcopy
from datetime import datetime
from dht.utils import digest
from hashlib import sha256
from keys.bip32utils import derive_childkey
from keys.keychain import KeyChain
from log import Logger
from market.profile import Profile
from market.btcprice import BtcPrice
from market.transactions import BitcoinTransaction
from protos.countries import CountryCode
from protos.objects import Listings


class Contract(object):
    """
    A class for creating and interacting with OpenBazaar Ricardian contracts.
    """

    def __init__(self, database, contract=None, hash_value=None, testnet=False):
        """
        This class can be instantiated with either an `OrderedDict` or a hash
        of a contract. If a hash is used, we will load the contract from either
        the file system or cache.

        Alternatively, pass in no parameters if the intent is to create a new
        contract.

        Args:
            contract: an `OrderedDict` containing a filled out json contract
            hash_value: a hash160 of a contract
            testnet: is this contract on the testnet
        """
        self.db = database
        self.keychain = KeyChain(self.db)
        if contract is not None:
            self.contract = contract
        elif hash_value is not None:
            try:
                file_path = self.db.filemap.get_file(hash_value.encode("hex"))
                if file_path is None:
                    file_path = DATA_FOLDER + "cache/" + hash_value.encode("hex")
                with open(file_path, 'r') as filename:
                    self.contract = json.load(filename, object_pairs_hook=OrderedDict)
            except Exception:
                if os.path.exists(DATA_FOLDER + "purchases/unfunded/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/unfunded/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "purchases/in progress/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/in progress/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "purchases/trade receipts/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "purchases/trade receipts/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER + "store/contracts/unfunded/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/unfunded/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER +
                                    "store/contracts/in progress/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/in progress/" + hash_value.encode("hex") + ".json"
                elif os.path.exists(DATA_FOLDER +
                                    "store/contracts/trade receipts/" + hash_value.encode("hex") + ".json"):
                    file_path = DATA_FOLDER + "store/contracts/trade receipts/" + hash_value.encode("hex") + ".json"

                try:
                    with open(file_path, 'r') as filename:
                        self.contract = json.load(filename, object_pairs_hook=OrderedDict)
                except Exception:
                    self.contract = {}
        else:
            self.contract = {}
        self.log = Logger(system=self)

        # used when purchasing this contract
        self.testnet = testnet
        self.notification_listener = None
        self.blockchain = None
        self.amount_funded = 0
        self.received_txs = []
        self.is_purchase = False
        self.outpoints = []

    def create(self,
               expiration_date,
               metadata_category,
               title,
               description,
               currency_code,
               price,
               process_time,
               nsfw,
               shipping_origin=None,
               shipping_regions=None,
               est_delivery_domestic=None,
               est_delivery_international=None,
               terms_conditions=None,
               returns=None,
               keywords=None,
               category=None,
               condition=None,
               sku=None,
               images=None,
               free_shipping=None,
               shipping_currency_code=None,
               shipping_domestic=None,
               shipping_international=None,
               options=None,
               moderators=None,
               contract_id=None):
        """
        All parameters are strings except:

        :param expiration_date: `string` (must be formatted UTC datetime)
        :param keywords: `list`
        :param nsfw: `boolean`
        :param images: a `list` of image files
        :param free_shipping: `boolean`
        :param shipping_origin: a 'string' formatted `CountryCode`
        :param shipping_regions: a 'list' of 'string' formatted `CountryCode`s
        :param options: a 'dict' containing options as keys and 'list' as option values.
        :param moderators: a 'list' of 'string' guids (hex encoded).
        """

        profile = Profile(self.db).get()
        if contract_id is not None and contract_id != "":
            self.previous_title = self.contract["vendor_offer"]["listing"]["item"]["title"]
        else:
            self.previous_title = None
            contract_id = digest(random.getrandbits(255)).encode("hex")

        self.contract = OrderedDict(
            {
                "vendor_offer": {
                    "listing": {
                        "contract_id": contract_id,
                        "metadata": {
                            "version": "1",
                            "category": metadata_category.lower(),
                            "category_sub": "fixed price"
                        },
                        "id": {
                            "guid": self.keychain.guid.encode("hex"),
                            "pubkeys": {
                                "guid": self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder),
                                "bitcoin": bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey)
                            }
                        },
                        "item": {
                            "title": title,
                            "description": description,
                            "process_time": process_time,
                            "price_per_unit": {},
                            "nsfw": nsfw
                        }
                    }
                }
            }
        )
        if expiration_date == "":
            self.contract["vendor_offer"]["listing"]["metadata"]["expiry"] = "never"
        else:
            self.contract["vendor_offer"]["listing"]["metadata"]["expiry"] = expiration_date + " UTC"
        if metadata_category == "physical good" and condition is not None:
            self.contract["vendor_offer"]["listing"]["item"]["condition"] = condition
        if currency_code.upper() == "BTC":
            item = self.contract["vendor_offer"]["listing"]["item"]
            item["price_per_unit"]["bitcoin"] = round(float(price), 8)
        else:
            item = self.contract["vendor_offer"]["listing"]["item"]
            item["price_per_unit"]["fiat"] = {}
            item["price_per_unit"]["fiat"]["price"] = price
            item["price_per_unit"]["fiat"]["currency_code"] = currency_code
        if keywords is not None:
            self.contract["vendor_offer"]["listing"]["item"]["keywords"] = []
            self.contract["vendor_offer"]["listing"]["item"]["keywords"].extend(keywords)
        if category is not None:
            self.contract["vendor_offer"]["listing"]["item"]["category"] = category
        if sku is not None:
            self.contract["vendor_offer"]["listing"]["item"]["sku"] = sku
        if options is not None:
            self.contract["vendor_offer"]["listing"]["item"]["options"] = options
        if metadata_category == "physical good":
            self.contract["vendor_offer"]["listing"]["shipping"] = {}
            shipping = self.contract["vendor_offer"]["listing"]["shipping"]
            shipping["shipping_origin"] = shipping_origin
            if free_shipping is False:
                self.contract["vendor_offer"]["listing"]["shipping"]["free"] = False
                self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"] = {}
                if shipping_currency_code == "BTC":
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"] = {}
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"][
                        "domestic"] = shipping_domestic
                    self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["bitcoin"][
                        "international"] = shipping_international
                else:
                    shipping = self.contract["vendor_offer"]["listing"]["shipping"]
                    shipping["flat_fee"]["fiat"] = {}
                    shipping["flat_fee"]["fiat"]["price"] = {}
                    shipping["flat_fee"]["fiat"]["price"][
                        "domestic"] = shipping_domestic
                    shipping["flat_fee"]["fiat"]["price"][
                        "international"] = shipping_international
                    shipping["flat_fee"]["fiat"][
                        "currency_code"] = shipping_currency_code
            else:
                self.contract["vendor_offer"]["listing"]["shipping"]["free"] = True
            self.contract["vendor_offer"]["listing"]["shipping"]["shipping_regions"] = []
            for region in shipping_regions:
                shipping = self.contract["vendor_offer"]["listing"]["shipping"]
                shipping["shipping_regions"].append(region)
            listing = self.contract["vendor_offer"]["listing"]
            listing["shipping"]["est_delivery"] = {}
            listing["shipping"]["est_delivery"]["domestic"] = est_delivery_domestic
            listing["shipping"]["est_delivery"][
                "international"] = est_delivery_international
        if profile.handle != "":
            self.contract["vendor_offer"]["listing"]["id"]["blockchain_id"] = profile.handle
        if images is not None:
            self.contract["vendor_offer"]["listing"]["item"]["image_hashes"] = []
            for image_hash in images:
                if len(image_hash) != 40:
                    raise Exception("Invalid image hash")
                self.contract["vendor_offer"]["listing"]["item"]["image_hashes"].append(image_hash)
        if terms_conditions is not None or returns is not None:
            self.contract["vendor_offer"]["listing"]["policy"] = {}
            if terms_conditions is not None:
                self.contract["vendor_offer"]["listing"]["policy"]["terms_conditions"] = terms_conditions
            if returns is not None:
                self.contract["vendor_offer"]["listing"]["policy"]["returns"] = returns
        if moderators is not None:
            self.contract["vendor_offer"]["listing"]["moderators"] = []
            for mod in moderators:
                mod_info = self.db.moderators.get_moderator(mod)
                if mod_info is not None:
                    moderator = {
                        "guid": mod,
                        "name": mod_info[5],
                        "avatar": mod_info[7].encode("hex"),
                        "short_description": mod_info[6],
                        "fee": str(mod_info[8]) + "%",
                        "blockchain_id": mod_info[4],
                        "pubkeys": {
                            "guid": mod_info[1].encode("hex"),
                            "bitcoin": {
                                "key": mod_info[2].encode("hex"),
                                "signature": base64.b64encode(mod_info[3])
                            }
                        }
                    }
                    self.contract["vendor_offer"]["listing"]["moderators"].append(moderator)

        listing = json.dumps(self.contract["vendor_offer"]["listing"], indent=4)
        self.contract["vendor_offer"]["signatures"] = {}
        self.contract["vendor_offer"]["signatures"]["guid"] = \
            base64.b64encode(self.keychain.signing_key.sign(listing)[:64])
        self.contract["vendor_offer"]["signatures"]["bitcoin"] = \
            bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(
                listing, bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)))
        self.save()

    def add_purchase_info(self,
                          quantity,
                          refund_address,
                          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.
        """

        if not self.testnet and not (refund_address[:1] == "1" or refund_address[:1] == "3"):
            raise Exception("Bitcoin address is not a mainnet address")
        elif self.testnet and not \
                (refund_address[:1] == "n" or refund_address[:1] == "m" or refund_address[:1] == "2"):
            raise Exception("Bitcoin address is not a testnet address")
        try:
            bitcointools.b58check_to_hex(refund_address)
        except AssertionError:
            raise Exception("Invalid Bitcoin address")

        profile = Profile(self.db).get()
        order_json = {
            "buyer_order": {
                "order": {
                    "ref_hash": digest(json.dumps(self.contract, indent=4)).encode("hex"),
                    "date": str(datetime.utcnow()) + " UTC",
                    "quantity": quantity,
                    "id": {
                        "guid": self.keychain.guid.encode("hex"),
                        "pubkeys": {
                            "guid": self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder),
                            "bitcoin": bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey),
                        }
                    },
                    "payment": {},
                    "refund_address": refund_address
                }
            }
        }
        SelectParams("testnet" if self.testnet else "mainnet")
        if profile.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:
            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 = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_pubkey)
            masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"]
            buyer_key = unhexlify(derive_childkey(masterkey_b, chaincode))
            vendor_key = unhexlify(derive_childkey(masterkey_v, chaincode))
            moderator_key = unhexlify(derive_childkey(masterkey_m, chaincode))

            redeem_script = CScript([OP_2, buyer_key, vendor_key, moderator_key, OP_3, OP_CHECKMULTISIG])
            order_json["buyer_order"]["order"]["payment"]["redeem_script"] = redeem_script.encode("hex")
            payment_address = str(P2SHBitcoinAddress.from_redeemScript(redeem_script))
            order_json["buyer_order"]["order"]["payment"]["address"] = payment_address
            order_json["buyer_order"]["order"]["payment"]["refund_tx_fee"] = TRANSACTION_FEE
        else:
            chaincode = sha256(str(random.getrandbits(256))).digest().encode("hex")
            order_json["buyer_order"]["order"]["payment"]["chaincode"] = chaincode

            masterkey_v = self.contract["vendor_offer"]["listing"]["id"]["pubkeys"]["bitcoin"]
            vendor_key = unhexlify(derive_childkey(masterkey_v, chaincode))

            payment_address = str(P2PKHBitcoinAddress.from_pubkey(vendor_key))
            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:
            amount_to_pay = float(price_json["bitcoin"]) * quantity
        else:
            currency_code = price_json["fiat"]["currency_code"]
            fiat_price = price_json["fiat"]["price"]
            conversion_rate = BtcPrice.instance().get(currency_code.upper())
            amount_to_pay = float("{0:.8f}".format(float(fiat_price) / float(conversion_rate))) * quantity
        if "shipping" in self.contract["vendor_offer"]["listing"]:
            if not self.contract["vendor_offer"]["listing"]["shipping"]["free"]:
                shipping_origin = str(self.contract["vendor_offer"]["listing"]["shipping"][
                    "shipping_origin"].upper())
                if shipping_origin == country.upper():
                    if "bitcoin" in self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]:
                        shipping_amount = float(self.contract["vendor_offer"]["listing"][
                            "shipping"]["flat_fee"]["bitcoin"]["domestic"]) * quantity
                    else:
                        price = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["fiat"][
                            "price"]["domestic"]
                        currency = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"][
                            "fiat"]["currency_code"]
                        conversion_rate = BtcPrice.instance().get(currency.upper())
                        shipping_amount = float("{0:.8f}".format(float(price) / float(conversion_rate))) * quantity
                else:
                    if "bitcoin" in self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]:
                        shipping_amount = float(self.contract["vendor_offer"]["listing"]["shipping"][
                            "flat_fee"]["bitcoin"]["international"]) * quantity
                    else:
                        price = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"]["fiat"][
                            "price"]["international"]
                        currency = self.contract["vendor_offer"]["listing"]["shipping"]["flat_fee"][
                            "fiat"]["currency_code"]

                        conversion_rate = BtcPrice.instance().get(currency.upper())
                        shipping_amount = float("{0:.8f}".format(float(price) / float(conversion_rate))) * quantity
                amount_to_pay += shipping_amount

        if round(amount_to_pay, 8) < round(TRANSACTION_FEE / float(100000000), 8):
            raise Exception("Contract price is below transaction fee.")

        order_json["buyer_order"]["order"]["payment"]["amount"] = round(amount_to_pay, 8)
        self.contract["buyer_order"] = order_json["buyer_order"]

        order = json.dumps(self.contract["buyer_order"]["order"], indent=4)
        self.contract["buyer_order"]["signatures"] = {}
        self.contract["buyer_order"]["signatures"]["guid"] = \
            base64.b64encode(self.keychain.signing_key.sign(order)[:64])
        self.contract["buyer_order"]["signatures"]["bitcoin"] = \
            bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(
                order, bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)))

        return (self.contract["buyer_order"]["order"]["payment"]["address"],
                order_json["buyer_order"]["order"]["payment"]["amount"])

    def add_order_confirmation(self,
                               libbitcoin_client,
                               payout_address,
                               comments=None,
                               shipper=None,
                               tracking_number=None,
                               est_delivery=None,
                               url=None,
                               password=<PASSWORD>):
        """
        Add the vendor's order confirmation to the contract.
        """
        self.blockchain = libbitcoin_client
        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:
            bitcointools.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
        order_id = digest(json.dumps(self.contract, indent=4)).encode("hex")
        # apply signatures
        outpoints = json.loads(self.db.sales.get_outpoint(order_id))
        if "moderator" in self.contract["buyer_order"]["order"]:
            redeem_script = self.contract["buyer_order"]["order"]["payment"]["redeem_script"]
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address, testnet=self.testnet)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_v = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            vendor_priv = derive_childkey(masterkey_v, chaincode, bitcointools.MAINNET_PRIVATE)
            sigs = tx.create_signature(vendor_priv, redeem_script)
            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"] = tx.get_out_value()
            conf_json["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"] = sigs
        else:
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address, testnet=self.testnet)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_v = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            vendor_priv = derive_childkey(masterkey_v, chaincode, bitcointools.MAINNET_PRIVATE)
            tx.sign(vendor_priv)
            tx.broadcast(self.blockchain)
            self.db.transactions.add_transaction(tx.to_raw_tx())
            self.log.info("broadcasting payout tx %s to network" % tx.get_hash())
            self.db.sales.update_payment_tx(order_id, tx.get_hash())

        confirmation = json.dumps(conf_json["vendor_order_confirmation"]["invoice"], indent=4)
        conf_json["vendor_order_confirmation"]["signature"] = \
            base64.b64encode(self.keychain.signing_key.sign(confirmation)[:64])

        self.contract["vendor_order_confirmation"] = conf_json["vendor_order_confirmation"]
        self.db.sales.update_status(order_id, 2)
        file_path = DATA_FOLDER + "store/contracts/in progress/" + order_id + ".json"
        with open(file_path, 'w') as outfile:
            outfile.write(json.dumps(self.contract, indent=4))

    def accept_order_confirmation(self, notification_listener, confirmation_json=None):
        """
        Validate the order confirmation sent over from the vendor and update our node accordingly.
        """
        self.notification_listener = notification_listener
        try:
            if confirmation_json:
                self.contract["vendor_order_confirmation"] = json.loads(confirmation_json,
                                                                        object_pairs_hook=OrderedDict)

            contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict)
            del contract_dict["vendor_order_confirmation"]
            contract_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex")
            ref_hash = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
            if ref_hash != contract_hash:
                raise Exception("Order number doesn't match")
            if self.contract["vendor_offer"]["listing"]["metadata"]["category"] == "physical good":
                shipping = self.contract["vendor_order_confirmation"]["invoice"]["shipping"]
                if "tracking_number" not in shipping or "shipper" not in shipping:
                    raise Exception("No shipping information")

            # TODO: verify signature
            # TODO: verify payout object
            status = self.db.purchases.get_status(contract_hash)
            if status == 2 or status == 3:
                raise Exception("Order confirmation already processed for this contract")

            # update the order status in the db
            self.db.purchases.update_status(contract_hash, 2)
            file_path = DATA_FOLDER + "purchases/in progress/" + contract_hash + ".json"

            # update the contract in the file system
            with open(file_path, 'w') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))
            title = self.contract["vendor_offer"]["listing"]["item"]["title"]
            if "image_hashes" in self.contract["vendor_offer"]["listing"]["item"]:
                image_hash = unhexlify(self.contract["vendor_offer"]["listing"]["item"]["image_hashes"][0])
            else:
                image_hash = ""
            if "blockchain_id" in self.contract["vendor_offer"]["listing"]["id"]:
                handle = self.contract["vendor_offer"]["listing"]["id"]["blockchain_id"]
            else:
                handle = ""
            vendor_guid = self.contract["vendor_offer"]["listing"]["id"]["guid"]
            self.notification_listener.notify(vendor_guid, handle, "order confirmation", contract_hash, title,
                                              image_hash)
            return contract_hash
        except Exception:
            return 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,
                    anonymous=True):

        """
        Add the final piece of the contract that appends the review and payout transaction.
        """
        self.blockchain = libbitcoin_client
        contract_dict = json.loads(json.dumps(self.contract, indent=4), object_pairs_hook=OrderedDict)
        if "dispute" in contract_dict:
            del contract_dict["dispute"]
        if "dispute_resolution" in contract_dict:
            del contract_dict["dispute_resolution"]
        reference_hash = digest(json.dumps(contract_dict, indent=4)).encode("hex")
        receipt_json = {
            "buyer_receipt": {
                "receipt": {
                    "ref_hash": reference_hash,
                    "listing": {
                        "received": received,
                        "listing_hash": self.contract["buyer_order"]["order"]["ref_hash"]
                    },
                    "dispute": {
                        "dispute": dispute
                    }
                }
            }
        }
        if "vendor_order_confirmation" in self.contract:
            order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
        else:
            order_id = self.get_order_id()
        if None not in (feedback, quality, description, delivery_time, customer_service):
            address = self.contract["buyer_order"]["order"]["payment"]["address"]
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_b = self.contract["buyer_order"]["order"]["id"]["pubkeys"]["bitcoin"]
            buyer_pub = derive_childkey(masterkey_b, chaincode)
            buyer_priv = derive_childkey(bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey),
                                         chaincode, bitcointools.MAINNET_PRIVATE)
            amount = self.contract["buyer_order"]["order"]["payment"]["amount"]
            listing_hash = self.contract["vendor_offer"]["listing"]["contract_id"]

            receipt_json["buyer_receipt"]["receipt"]["rating"] = OrderedDict()
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"] = OrderedDict()
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["feedback"] = feedback
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["quality"] = quality
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["description"] = description
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["delivery_time"] = delivery_time
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["customer_service"] = customer_service
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["review"] = review
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["address"] = address
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_key"] = buyer_pub
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["amount"] = amount
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["listing"] = listing_hash
            receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["proof_of_tx"] = \
                base64.b64encode(self.db.purchases.get_proof_sig(order_id))
            if not anonymous:
                receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_guid"] = \
                    self.keychain.guid.encode("hex")
                receipt_json["buyer_receipt"]["receipt"]["rating"]["tx_summary"]["buyer_guid_key"] = \
                    self.keychain.verify_key.encode(encoder=nacl.encoding.HexEncoder)

        status = self.db.purchases.get_status(order_id)
        if status < 3 and "moderator" in self.contract["buyer_order"]["order"]:
            outpoints = json.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"])
            value = self.contract["vendor_order_confirmation"]["invoice"]["payout"]["value"]
            tx = BitcoinTransaction.make_unsigned(outpoints, payout_address,
                                                  testnet=self.testnet, out_value=value)
            chaincode = self.contract["buyer_order"]["order"]["payment"]["chaincode"]
            masterkey_b = bitcointools.bip32_extract_key(self.keychain.bitcoin_master_privkey)
            buyer_priv = derive_childkey(masterkey_b, chaincode, bitcointools.MAINNET_PRIVATE)

            buyer_signatures = tx.create_signature(buyer_priv, redeem_script)
            signatures = []
            for i in range(len(outpoints)):
                for vendor_sig in self.contract["vendor_order_confirmation"]["invoice"]["payout"]["signature(s)"]:
                    if vendor_sig["index"] == i:
                        v_signature = vendor_sig["signature"]
                for buyer_sig in buyer_signatures:
                    if buyer_sig["index"] == i:
                        b_signature = buyer_sig["signature"]
                signature_obj = {"index": i, "signatures": [b_signature, v_signature]}
                signatures.append(signature_obj)

            receipt_json["buyer_receipt"]["receipt"]["payout"] = {}
            tx.multisign(signatures, redeem_script)
            tx.broadcast(self.blockchain)
            self.db.transactions.add_transaction(tx.to_raw_tx())

            self.log.info("broadcasting payout tx %s to network" % tx.get_hash())
            receipt_json["buyer_receipt"]["receipt"]["payout"]["txid"] = tx.get_hash()

            receipt_json["buyer_receipt"]["receipt"]["payout"]["signature(s)"] = buyer_signatures
            receipt_json["buyer_receipt"]["receipt"]["payout"]["value"] = tx.get_out_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"] = \
            base64.b64encode(self.keychain.signing_key.sign(receipt)[:64])
        self.contract["buyer_receipt"] = receipt_json["buyer_receipt"]

        if "rating" in self.contract["buyer_receipt"]["receipt"]:
            self.contract["buyer_receipt"]["receipt"]["rating"]["signature"] = \
                bitcointools.encode_sig(*bitcointools.ecdsa_raw_sign(json.dumps(
                    self.contract["buyer_receipt"]["receipt"]["rating"]["tx_summary"], indent=4), buyer_priv))
            if not anonymous:
                self.contract["buyer_receipt"]["receipt"]["rating"]["guid_signature"] = \
                    base64.b64encode(self.keychain.signing_key.sign(json.dumps(
                        self.contract["buyer_receipt"]["receipt"]["rating"]["tx_summary"], indent=4))[:64])

        if status < 3:
            self.db.purchases.update_status(order_id, 3)
            file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json"
            with open(file_path, 'w') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))
            file_path = DATA_FOLDER + "purchases/in progress/" + order_id + ".json"
            if os.path.exists(file_path):
                os.remove(file_path)
        else:
            file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json"
            with open(file_path, 'wb') as outfile:
                outfile.write(json.dumps(self.contract, indent=4))

    def accept_receipt(self, notification_listener, blockchain, receipt_json=None):
        """
        Process the final receipt sent over by the buyer. If valid, broadcast the transaction
        to the bitcoin network.
        """
        self.notification_listener = notification_listener
        self.blockchain = blockchain
        if "buyer_receipt" in self.contract:
            raise Exception("A receipt has already been processed for this order")
        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"]
        if "dispute" in contract_dict:
            del contract_dict["dispute"]
        if "dispute_resolution" in contract_dict:
            del contract_dict["dispute_resolution"]
        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")

        # TODO: verify buyer signature
        if "vendor_order_confirmation" in self.contract:
            order_id = self.contract["vendor_order_confirmation"]["invoice"]["ref_hash"]
        else:
{'fraction_non_alphanumeric': 0.12991775368012992, 'fraction_numerical': 0.004374246948504374, 'mean_word_length': 2.2412344002037523, 'pattern_counts': {'":': 140, '<': 11, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1734430', 'n_tokens_mistral': 21010, 'n_tokens_neox': 21416, 'n_words': 3352}