├── .gitignore ├── README.md ├── LICENSE ├── src ├── bitcoind_server_interface.py ├── client_interface.py └── bitmesh.py └── PROTOCOL /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *** This was an early proof of concept. We are not using this repository for the product. There may be further developments for a python micropayments implementation in the future. *** 2 | 3 | BitMesh is an organization that is developing around the latest innovations in consumer electronics, networking technology and machine to machine payment mechanisms to build a platform that will revolutionize the way you share the internet across the globe. Visit www.bitmesh.network for more information, and sign up for e-mail updates to be the first to learn about early alpha/beta releases. 4 | 5 | Currently there is a proof of concept implemented in Python (not publically released). This demonstrates a client requesting a webpage from a server, negotiating a price and ultimately receiving the requested webpage. The next iteration will include porting this code to C/C++ or an equivalent compiled language for easy migration to new platforms and extending the functionality. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Snake Charmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/bitcoind_server_interface.py: -------------------------------------------------------------------------------- 1 | from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException 2 | import logging 3 | import sys 4 | 5 | access = None 6 | connected = False 7 | 8 | def connect_to_bitcoin_server(server_ip, port,user, password): 9 | global access 10 | access = AuthServiceProxy("http://%s:%s@%s:%s"%(user,password,server_ip,port)) 11 | pass 12 | 13 | 14 | def get_addresses_and_balances(): 15 | if access == None: 16 | raise Exception('Not Connected to server, cannot get addresses.') 17 | pass 18 | 19 | json_object = access.listaccounts() 20 | 21 | address_list = [] 22 | privKey_list = [] 23 | values_list = [] 24 | 25 | for account_name in json_object.keys(): 26 | addresses = access.getaddressesbyaccount(account_name) 27 | for address in addresses: 28 | address_list.append(address) 29 | privKey_list.append(access.dumpprivkey(address)) 30 | values_list.append(access.getreceivedbyaddress(address)) 31 | 32 | return (address_list, privKey_list,values_list) 33 | 34 | def import_private_key(private_key): 35 | logging.basicConfig() 36 | logging.getLogger("BitcoinRPC").setLevel(logging.DEBUG) 37 | if access == None: 38 | raise Exception('Not Connected to server, cannot import private key.') 39 | pass 40 | 41 | print(private_key) 42 | access.importprivkey(private_key,"rescan=false") 43 | 44 | pass -------------------------------------------------------------------------------- /src/client_interface.py: -------------------------------------------------------------------------------- 1 | from Tkinter import * 2 | from ttk import Style 3 | import bitmesh 4 | import traceback 5 | 6 | class ClientWindow(Frame): 7 | 8 | # width of the window 9 | w = int(500) 10 | # height of the window 11 | h = int(500) 12 | 13 | def __init__(self, parent): 14 | # Frame for the whole window 15 | Frame.__init__(self, parent, background="white") 16 | self.parent = parent 17 | self.centerWindow() 18 | self.initUI() 19 | 20 | def initUI(self): 21 | self.parent.title("Bitmesh Client") 22 | self.style = Style() 23 | self.style.theme_use("default") 24 | self.pack(fill=BOTH, expand=1) 25 | 26 | # set up the grid on the frame. Feels a lot like 27 | # "GridBagLayout" from java.swing 28 | self.columnconfigure(1, weight=1) 29 | self.columnconfigure(3, pad=1) 30 | self.rowconfigure(3, weight=1) 31 | self.rowconfigure(5, pad=1) 32 | 33 | # establish text area with border of 1 that is "Sunken" 34 | self.log_text_area = Text(self, bd=1, relief=SUNKEN) 35 | self.log_text_area.insert(END,"BEGIN LOG\n---------") 36 | # Texts have to be in state normal to be modified. I am doing this 37 | # so the user doesn't edit the log 38 | self.log_text_area.config(state=DISABLED) 39 | self.log_text_area.grid(row=1, column=0, columnspan=2, rowspan=4, sticky=E+W+S+N) 40 | 41 | self.connect_button = Button(self, text="Connect to Server") 42 | # TODO: attempting to strech the button across the grid 43 | self.connect_button.grid(row=1, column=2, sticky=E+W+N) 44 | self.connect_button.bind("", self.connect_callback) 45 | 46 | self.request_text_area = Text(self, height=1, bd=1, relief=SUNKEN) 47 | self.request_text_area.grid(row=0 ,column=1, sticky=E+W) 48 | 49 | self.request_website_button = Button(self, text="Request Site") 50 | self.request_website_button.grid(row=0, column=0, columnspan=1, sticky=W+N) 51 | self.request_website_button.bind("", self.request_callback) 52 | 53 | # on connect_button left clicked 54 | def connect_callback(self,event): 55 | self.log_text_area.config(state=NORMAL) 56 | self.log_text_area.insert(END,"\nConnecting.\n") 57 | self.log_text_area.config(state=DISABLED) 58 | print 'BUTTON CLICKED YO' 59 | try: 60 | bitmesh.buy_data("192.168.43.73", self) 61 | except: 62 | print "There was an issue." 63 | traceback.print_exc() 64 | self.parent.quit() 65 | 66 | def request_callback(self,event): 67 | self.log_text_area.config(state=NORMAL) 68 | self.log_text_area.insert(END,"\nFetching website: " + self.request_text_area.get(1.0,END)) 69 | self.log_text_area.config(state=DISABLED) 70 | 71 | # centers the window in the client's screen 72 | def centerWindow(self): 73 | sw = self.parent.winfo_screenwidth() 74 | sh = self.parent.winfo_screenheight() 75 | x = (sw - self.w)/2 76 | y = (sh - self.h)/2 77 | self.parent.geometry('%dx%d+%d+%d' % (self.w, self.h, x, y)) 78 | 79 | def get_site(): 80 | return self.myText_Box.get("1.0",END) 81 | 82 | def update_log(self,log_entry): 83 | self.log_text_area.config(state=NORMAL) 84 | self.log_text_area.insert(END,"\n\n"+log_entry) 85 | self.log_text_area.config(state=DISABLED) 86 | pass 87 | 88 | # start up the client GUI 89 | def start_client(): 90 | root = Tk() 91 | app = ClientWindow(root) 92 | root.mainloop() -------------------------------------------------------------------------------- /PROTOCOL: -------------------------------------------------------------------------------- 1 | STATE MACHINE: 2 | 3 | SERVICE_REQUESTER ALICE 4 | SERVICE_PROVIDER BOB 5 | 6 | BOB: 7 | 8 | LOAD_CONFIGURATION ==> CONNECT_TO_NETWORK ==> WAIT_FOR_REQUEST ==> RECEIVE_SERVICE_REQUEST ==> NEGOTIATE_CHANNEL_CONFIGURATION ==> NEGOTIATE_PRICE ==> PROVIDE_SERVICE ==> WAIT_FOR_REQUEST ... 9 | 10 | ALICE: 11 | 12 | LOAD_CONFIGURATION ==> CONNECT_TO_NETWORK ==> MAKE_REQUEST ==> NEGOTIATE_CHANNEL_CONFIGURATION ==> NEGOTIATE_PRICE ==> RECEIVE_SERVICE 13 | 14 | 15 | NOTES: 16 | LOAD_CONFIGURATION: 17 | Here Alice and Bob may load their preferences and constraints for the micropayment channel and the price. Such preferences and constraints would constitute a negotiation algorithm. We intentionally abstract this out so that any algorithm could be used if it conforms to this protocol. 18 | 19 | CONNECT_TO_NETWORK: 20 | Here Alice and Bob connect to the network via whatever protocol they choose to use. We intentionally abstract this out so that a variety of network protocols may be used. We imagine the communication happening via a meshing protocol, or standard wifi or tor or bluetooth or whatever works. This state (may) include broadcast and discovery of services. 21 | 22 | NEGOTIATE_*: 23 | Negotiation is modelled as a loop that terminates on any of the following conditions: 24 | - Either party times out, indicating loss of interest in service 25 | - Either party repeats themselves more than twice, indicating unwillingness to change the terms further 26 | - Either party repeats the other's message, indicating agreement. 27 | 28 | Communication occurs via JSON messages that are passed back and forth over whatever communication protocol is being used. JSON messages take the form: ? 29 | 30 | *_SERVICE: 31 | Services and payment are provided incrementally to minimize loss due to fraud. This is currently done via micropayment channels, but we are abstracting it to avoid committing the future to too much. 32 | 33 | 34 | 35 | 36 | 37 | EXAMPLE: 38 | 39 | 40 | OPEN MICROPAYMENT CHANNEL: 41 | while ALICE AND BOB HAVE NOT AGREED TO TERMS: 42 | ALICE - ANNOUNCE PURCHASE PROPOSAL 43 | { 44 | "intent" : "buy", 45 | "type" : "data", 46 | "channel_width" : 1024 (bytes), 47 | "channel_fund" : 100000 (satoshi), 48 | "refund_delay" : 12847 (epoch time in seconds or block_height), 49 | "public_key" : 1ALICEpbBsflekjfeugiup8u39743e, 50 | "signature" : eoaifnlsdzkufhozisernziowrlawmeljfpzsirugh 51 | } 52 | 53 | BOB - HEAR PROPOSAL, BEGIN NEGOTIATION 54 | { 55 | "intent" : "sell", 56 | "type" : "data", 57 | "channel_width" : 512 (bytes), 58 | "channel_fund" : 300000 (satoshi), 59 | "refund_delay" : 11147 (epoch time in seconds or block_height), 60 | "buyer_public_key" : 1ALICEpbBsflekjfeugiup8u39743e, 61 | "seller_public_key" : 1BOBasAbaet3i24ibasuozucnlaskj, 62 | "signature" : faskrhaweituabwoefuihadpewiurhaiuyg 63 | } 64 | ...[ALICE AND BOB GO BACK AND FORTH LIKE THIS UNTIL BOB PROPOSES SOMETHING ACCEPTABLE TO ALICE] 65 | 66 | ALICE - ACCEPT TERMS, BEGIN MICROPAYMENT PROTOCOL WITH AGREED UPON PARAMETERS 67 | 68 | NEGOTIATE CONNECTION: 69 | ALICE - REQUEST CONNECTION TO HOST 70 | { 71 | "intent" : "connect", 72 | "host" : "encrypted.google.com", 73 | "type" : "domain", 74 | "pub_key" : 1ALICEpbBsflekjfeugiup8u39743e, 75 | "signature" : "asdlfnkalwtuhraisraewiroakj95" 76 | } 77 | 78 | BOB - DETERMINE PRICE FOR CONNECTION 79 | while BOB DOES NOT HAVE ACCESS TO HOST: 80 | BOB - (TRANSITIVE) REQUEST CONNECTION TO HOST 81 | { 82 | "intent" : "connect", 83 | "host" : "encrypted.google.com", 84 | "type" : "domain", 85 | "pub_key" : 1BOBasAbaet3i24ibasuozucnlaskj, 86 | "signature" : "faaweugoiaueriauyi4uh4oiuy763" 87 | } 88 | 89 | ...[BOB PLAYS ALICE FOR THE REMAINDER OF THIS LOOP] 90 | 91 | [ BOB NOW HAS ACCESS TO THE HOST FOR SOME PRICE ] 92 | 93 | while ALICE DOES NOT ACCEPT TERMS: 94 | BOB - OFFER 95 | { 96 | "intent" : "connect", 97 | "host" : "encrypted.google.com", 98 | "type" : "domain", 99 | "price_per_channel_width" : 150 (satoshi) 100 | "pub_key" : 1BOBasAbaet3i24ibasuozucnlaskj, 101 | "signature" : "iugfahsfihbziuygefkjhzfieygyu" 102 | } 103 | 104 | ALICE - COUNTEROFFER 105 | { 106 | "intent" : "connect", 107 | "host" : "encrypted.google.com", 108 | "type" : "domain", 109 | "price_per_channel_width" : 100 (satoshi) 110 | "pub_key" : 1ALICEpbBsflekjfeugiup8u39743e, 111 | "signature" : "4587468361dfufueufaacuehba" 112 | } 113 | 114 | [ ALICE AND BOB HAVE NOW AGREED ON PRICE FOR CONNECTION ] 115 | 116 | while ALICE WANTS MORE DATA: 117 | 118 | BOB - PROVIDE [channel_width] OF DATA 119 | ALICE - UPDATE MICROPAYMENT TRANSACTION (PAY BOB) 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/bitmesh.py: -------------------------------------------------------------------------------- 1 | # to start buyer, call buy_data(ip_address) 2 | # to start seller, call listen_for_buyers() 3 | 4 | # seller side flow: 5 | # listen_for_buyers() 6 | # parse_message 7 | # send_pub_key_to_peer() 8 | # seller_handle_refund_tx() 9 | # seller_handle_escrow_tx() 10 | # seller_handle_domain_request() 11 | # seller_handle_updated_transaction() 12 | 13 | # buyer side flow: 14 | # buy_data() 15 | # buyer_open_micropayment_channel_with_peer() 16 | # create_escrow_transaction() 17 | # create_refund_transaction() 18 | # send_refund_for_signature() 19 | # buyer_sign_refund() 20 | # send_escrow_tx_to_seller() 21 | # create_tab_transaction() 22 | 23 | from bitcoin.main import * 24 | from bitcoin.transaction import * 25 | import zmq 26 | import requests 27 | import webbrowser 28 | import os 29 | 30 | # channel_width determines how many bytes pass thru before Alice pays 31 | # should be a multiple of MTU? change units to packets? 32 | channel_width = 1024 33 | 34 | # how long in seconds to delay refund tx for 35 | refund_delay = 100 36 | 37 | # amount of satoshi to lock up in escrow 38 | channel_fund = 100000 39 | 40 | # tx fee for each tx 41 | tx_fee = 5000 42 | 43 | # port to communicate on 44 | port = '5556' 45 | 46 | # zmq context 47 | context = zmq.Context() 48 | 49 | # socket to communicate thru 50 | socket = context.socket(zmq.PAIR) 51 | 52 | # private key seller uses to sign multisig output 53 | seller_multisig_priv_key = '' 54 | 55 | # private key buyer uses to sign multisig output 56 | buyer_multisig_priv_key = '' 57 | 58 | # private key buyer uses to spend pre-existing funds 59 | # hardcoded for now 60 | buyer_unspent_priv_key = '976895009f5c494a7d7969845ade3c570562fbe79b8e91ce20dc5ef502fd59eb' 61 | 62 | 63 | #buyer_unspent_testnet_addr = bitcoin.main.privkeytoaddr(buyer_unspent_priv_key, 111) 64 | buyer_unspent_testnet_addr = 'mj8psFsuu2PjfHFaiRUsuhnqJHoRsCPfwX' 65 | 66 | # complete refund tx signed and ready to broadcast 67 | complete_refund_tx = {} 68 | 69 | def get_new_pub_priv_key(): 70 | priv_key = random_key() 71 | pub_key = privkey_to_pubkey(priv_key) 72 | #TODO: handle failure 73 | #TODO: save to disk? 74 | return pub_key, priv_key 75 | 76 | def data_merchant_loop(): 77 | while tab_not_exhausted() and service_is_available(): 78 | request = receive_request 79 | get_prices_from_peers(bitmesh_peers, request) 80 | 81 | 82 | ################################## 83 | # MICROPAYMENT CHANNEL FUNCTIONS # 84 | ################################## 85 | 86 | def get_unspent_outputs(min): 87 | # return outputs, total_in 88 | # TODO hardwire it? 89 | #bitcoin.blockr_unspent(buyer_unspent_testnet_addr, 'testnet') 90 | return [{'output': u'eff37fa275802249315ffdf6dd71f2219bf130a9914403b4431d74491ce302b1:1', 'value': 100000}], 100000 91 | 92 | 93 | def buyer_open_micropayment_channel_with_peer(peer): 94 | global buyer_multisig_priv_key 95 | global complete_refund_tx 96 | buyer_multisig_pub_key, buyer_multisig_priv_key = get_new_pub_priv_key() 97 | seller_multisig_pub_key = request_pub_key_from_peer(peer) 98 | 99 | # print "Got multisig pub key from peer. ", peer 100 | 101 | # create the escrow tx with multisig output 102 | escrow_tx = create_escrow_transaction(buyer_multisig_pub_key, \ 103 | buyer_unspent_priv_key, \ 104 | seller_multisig_pub_key) 105 | 106 | # print escrow_tx 107 | # create the refund tx in case seller disappears 108 | refund_tx = create_refund_transaction(escrow_tx) 109 | 110 | # print "HELLO REFUND" , refund_tx 111 | # send the refund, get it signed, check the signature 112 | seller_refund_signature = send_refund_for_signature(peer, refund_tx) 113 | 114 | # apply signatures to refund tx 115 | complete_refund_tx = buyer_sign_refund(refund_tx, seller_refund_signature) 116 | 117 | # print "I am a complete refund", complete_refund_tx 118 | # send the escrow to seller, proving we have money on the table 119 | send_escrow_tx_to_seller(escrow_tx) 120 | 121 | # create a tab tx that sends seller bitcoin for services 122 | tab_tx = create_tab_transaction(escrow_tx, seller_multisig_pub_key) 123 | 124 | print 'Micropayment channel successfully created' 125 | return deserialize(tab_tx) 126 | 127 | 128 | # updates tab transaction by delta and re-signs the transaction 129 | # returns the signature and the updated tab_tx 130 | def buyer_update_tab_transaction(tab_tx, delta): 131 | # print "MY NAME IS TABBED TRANSITORY ACTIONS", tab_tx 132 | tab_tx = dict(tab_tx) 133 | 134 | refund_output = tab_tx['outs'][0] 135 | purchase_output = tab_tx['outs'][1] 136 | 137 | if refund_output['value'] < delta: 138 | print 'not enough funds available' 139 | return 140 | 141 | refund_output['value'] = refund_output['value'] - delta 142 | purchase_output['value'] = purchase_output['value'] + delta 143 | 144 | serialized_tab_tx = serialize(tab_tx) 145 | buyer_signature = multisign(serialized_tab_tx, 0, tab_tx['ins'][0]['script'], \ 146 | buyer_multisig_priv_key) 147 | print 'updating tx', json.dumps(tab_tx, indent=4, separators=(',',': ')) 148 | return buyer_signature, tab_tx 149 | 150 | # create escrow transaction 151 | # multisigs are applied in the following order: [0] buyer [1] seller 152 | def create_escrow_transaction(buyer_pub_key, buyer_priv_key, seller_pub_key): 153 | inputs, total_in = get_unspent_outputs(channel_fund + tx_fee) 154 | 155 | # make a multisig output 156 | script = mk_multisig_script(buyer_pub_key, seller_pub_key, 2, 2) 157 | multisig_output = {} 158 | multisig_output['script'] = script 159 | multisig_output['value'] = channel_fund 160 | 161 | # make a change address for the difference between inputs and outputs 162 | change_pub_key, change_priv_key = get_new_pub_priv_key() 163 | # print 'change_pub_key:', change_pub_key 164 | # print 'change_priv_key:', change_priv_key 165 | change_addr = pubkey_to_address(change_pub_key) 166 | change_output = {} 167 | change_output['address'] = change_addr 168 | change_output['value'] = total_in - channel_fund - tx_fee 169 | 170 | # make escrow_tx 171 | escrow_tx = mktx(inputs, multisig_output, change_output) 172 | 173 | # sign inputs 174 | # assumes there is one input and it's private key is buyer_priv_key 175 | return dict(deserialize(sign(escrow_tx, 0, buyer_priv_key))) 176 | 177 | # create a refund transaction with the escrow's multisig output as input 178 | def create_refund_transaction(escrow_tx): 179 | buyer_refund_pub_key, _ = get_new_pub_priv_key() 180 | buyer_refund_addr = pubkey_to_address(buyer_refund_pub_key) 181 | 182 | # just send all the money back to the buyer 183 | refund_output = {} 184 | refund_output['address'] = buyer_refund_addr 185 | refund_output['value'] = channel_fund - tx_fee 186 | 187 | 188 | multisig_input = escrow_tx['outs'][0] 189 | multisig_input['outpoint'] = {} 190 | multisig_input['outpoint']['index'] = 0 191 | multisig_input['outpoint']['hash'] = txhash(serialize(escrow_tx)) 192 | multisig_input['sequence'] = 0 193 | 194 | # print "I AM A GOOSE", multisig_input, "\nI AM ANOTHER GOOSE", refund_output 195 | 196 | return build_refund_transaction([multisig_input], [refund_output]) 197 | 198 | # create refund transaction 199 | # it must have non-zero lock time 200 | # lock time is in absolute time with UNIX timestamp format 201 | # at least one of the input's sequence numbers must have a non-maxed-out sequence number 202 | # using 0 for all sequence numbers 203 | def build_refund_transaction(*args): 204 | # [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ... 205 | ins, outs = [], [] 206 | for arg in args: 207 | if isinstance(arg, list): 208 | for a in arg: (ins if is_inp(a) else outs).append(a) 209 | else: 210 | (ins if is_inp(arg) else outs).append(arg) 211 | 212 | txobj = {"locktime": refund_delay, "version": 1, "ins": [], "outs": []} 213 | for i in ins: 214 | if isinstance(i, dict) and "outpoint" in i: 215 | txobj["ins"].append(i) 216 | else: 217 | if isinstance(i, dict) and "output" in i: 218 | i = i["output"] 219 | txobj["ins"].append({ 220 | "outpoint": {"hash": i[:64], "index": int(i[65:])}, 221 | "script": "", 222 | "sequence": 0 223 | }) 224 | for o in outs: 225 | if isinstance(o, (str, unicode)): 226 | addr = o[:o.find(':')] 227 | val = int(o[o.find(':')+1:]) 228 | o = {} 229 | if re.match('^[0-9a-fA-F]*$', addr): 230 | o["script"] = addr 231 | else: 232 | o["address"] = addr 233 | o["value"] = val 234 | 235 | outobj = {} 236 | if "address" in o: 237 | outobj["script"] = address_to_script(o["address"]) 238 | elif "script" in o: 239 | outobj["script"] = o["script"] 240 | else: 241 | raise Exception("Could not find 'address' or 'script' in output.") 242 | outobj["value"] = o["value"] 243 | txobj["outs"].append(outobj) 244 | 245 | return txobj 246 | 247 | def create_tab_transaction(escrow_tx, seller_pub_key): 248 | 249 | # make a refund output with the entire input 250 | refund_pub_key, _ = get_new_pub_priv_key() 251 | refund_addr = pubkey_to_address(refund_pub_key) 252 | 253 | refund_output = {} 254 | refund_output['address'] = refund_addr 255 | refund_output['value'] = channel_fund - tx_fee 256 | 257 | # make a purchase output that goes to the seller 258 | # this gets incrementally increased as more data is delivered 259 | purchase_output = {} 260 | purchase_output['address'] = pubtoaddr(seller_pub_key) 261 | purchase_output['value'] = 0 262 | 263 | # hook the multisig output up as an input 264 | multisig_input = escrow_tx['outs'][0] 265 | multisig_input['outpoint'] = {} 266 | multisig_input['outpoint']['index'] = 0 267 | multisig_input['outpoint']['hash'] = txhash(serialize(escrow_tx)) 268 | multisig_input['sequence'] = 0 269 | 270 | # make the tab tx 271 | return mktx(multisig_input, refund_output, purchase_output) 272 | 273 | 274 | # seller signs refund, returns signature 275 | def seller_sign_refund(refund_tx): 276 | # because serialization 277 | serialized_refund_tx = serialize(refund_tx) 278 | signature = multisign(serialized_refund_tx, 0, refund_tx['ins'][0]['script'], \ 279 | seller_multisig_priv_key) 280 | return signature 281 | 282 | def buyer_sign_refund(refund_tx, seller_signature): 283 | # get buyer's signature on refund 284 | serialized_refund_tx = serialize(refund_tx) 285 | buyer_signature = multisign(serialized_refund_tx, 0, refund_tx['ins'][0]['script'], \ 286 | buyer_multisig_priv_key) 287 | 288 | 289 | # apply signatures and return completed tx 290 | return apply_multisignatures(serialized_refund_tx, 0, refund_tx['ins'][0]['script'], \ 291 | buyer_signature, seller_signature) 292 | 293 | def seller_validate_escrow_tx(escrow_tx): 294 | #TODO: actually do this 295 | return True 296 | 297 | ################################ 298 | # PEER COMMUNICATION FUNCTIONS # 299 | ################################ 300 | 301 | def send_refund_for_signature(peer, refund_tx): 302 | socket.send_json(refund_tx) 303 | # print 'sent refund_tx for signature' 304 | 305 | # seller sends from seller_handle_refund_transaction 306 | signature = socket.recv_string() 307 | # print 'received signature' , signature 308 | 309 | #TODO: check signature 310 | 311 | return signature 312 | 313 | def send_escrow_tx_to_seller(escrow_tx): 314 | socket.send_json(escrow_tx) 315 | 316 | def get_bitmesh_peers(): 317 | # TODO 318 | pass 319 | 320 | def get_prices_from_peers(peers, request): 321 | # TODO 322 | pass 323 | 324 | def request_pub_key_from_peer(peer): 325 | socket.connect('tcp://%s:%s' % (peer, port)) 326 | message = {} 327 | message['intent'] = 'buy' 328 | socket.send_json(message) 329 | # print 'Sent my good lord a message' , str(message) 330 | msg = socket.recv() 331 | # print 'received pub key', msg 332 | return msg 333 | #socket.connect("tcp://localhost:%s" % port) 334 | 335 | def seller_handle_refund_tx(): 336 | 337 | # wait for the refund_tx from the buyer 338 | # buyer sends from send_refund_for_signature 339 | refund_tx = dict(socket.recv_json()) 340 | # print 'received refund tx', refund_tx 341 | 342 | # validate refund tx (check that input is multisig with our key) 343 | # TODO 344 | 345 | # sign it and send signature back, ensuring the ability of the buyer 346 | # to get a refund if seller disappears 347 | signature = seller_sign_refund(refund_tx) 348 | socket.send_string(signature) 349 | # print 'returned signature for refund', signature 350 | 351 | def seller_handle_escrow_tx(): 352 | 353 | # wait for the escrow_tx from the buyer to prove money is on the table 354 | escrow_tx = dict(socket.recv_json()) 355 | 356 | # validate escrow tx (check input signatures, multisig output) 357 | valid = seller_validate_escrow_tx(escrow_tx) 358 | 359 | if valid: 360 | # broadcast it 361 | broadcast_tx(escrow_tx) 362 | else: 363 | print 'escrow_tx was not valid', escrow_tx 364 | # TODO 365 | 366 | #socket.send('escrow tx was %s' % valid) 367 | 368 | def seller_handle_domain_request(): 369 | 370 | # get the requested domain from the buyer 371 | domain = socket.recv() 372 | 373 | # TODO: validate the domain is a good one 374 | html = requests.get(domain).text 375 | print html 376 | socket.send_string(html) 377 | 378 | def seller_handle_updated_transaction(): 379 | 380 | updated_sig = socket.recv() 381 | updated_tx = socket.recv() 382 | 383 | # TODO: verify sig and tx 384 | # print 'updated_sig', updated_sig 385 | # print 'updated_tx', updated_tx 386 | 387 | return updated_tx 388 | 389 | 390 | def buyer_send_domain_request(domain): 391 | socket.send_string(domain) 392 | return socket.recv_string() 393 | 394 | def buy_data(peer): 395 | # print 'buy_data(%s)' % str(peer) 396 | tab_tx = buyer_open_micropayment_channel_with_peer(peer) 397 | print 'Micropayment channel successfully created' 398 | while True: 399 | domain = raw_input('Request URL:') 400 | site = buyer_send_domain_request(domain) 401 | print site 402 | 403 | with open("site.html","wb") as f: 404 | f.write(site.encode('utf-8')) 405 | webbrowser.open('file://%s' % os.path.abspath('site.html')) 406 | 407 | 408 | updated_sig, tab_tx = buyer_update_tab_transaction(tab_tx, 100) 409 | socket.send_string(updated_sig) 410 | socket.send_json(tab_tx) 411 | 412 | 413 | def listen_for_buyers(): 414 | socket.bind('tcp://*:%s' % port) 415 | while True: 416 | try: 417 | 418 | # listen for messages 419 | message = socket.recv_json() 420 | # print 'received message', message 421 | parse_message(message) 422 | except Exception as e: 423 | pass 424 | 425 | 426 | def parse_message(peer_message): 427 | # handle requests 428 | # probably should be on another thread 429 | # and another port 430 | peer_message = dict(peer_message) 431 | print peer_message['intent'] 432 | if peer_message['intent'] == 'buy': 433 | # print 'intent is to buy' 434 | send_pub_key_to_peer() 435 | seller_handle_refund_tx() 436 | seller_handle_escrow_tx() 437 | while not socket.closed: 438 | seller_handle_domain_request() 439 | tab_tx = seller_handle_updated_transaction() 440 | 441 | broadcast_tx(tab_tx) 442 | 443 | def send_pub_key_to_peer(): 444 | 445 | global seller_multisig_priv_key 446 | pub, seller_multisig_priv_key = get_new_pub_priv_key() 447 | socket.send(pub) 448 | # print 'sent', pub, 'waiting for refund tx' 449 | 450 | 451 | 452 | ############################# 453 | # BITCOIN NETWORK FUNCTIONS # 454 | ############################# 455 | 456 | def broadcast_tx(tx): 457 | #blockr_pushtx(tx, network='testnet') 458 | print "broadcast_tx, pass so we don't spend our good money on nonsense" 459 | pass 460 | 461 | --------------------------------------------------------------------------------