def sendDepositTimeout(self, txid, defaulter=None): ''' This is sent when one or both parties have failed to deliver their deposit to the CNE after contract signing is complete. If defaulter is None, it means both have defaulted, meaning only a message is sent to mark the transaction as defunct. Otherwise defaulter marks the role of the defaulter and the other party's deposit is returned. ''' tx = self.getTxByID(txid) if defaulter and defaulter not in [tx.buyer, tx.seller]: shared.debug(0, ["Critical error: wrong argument"]) defaultedList = ','.join([tx.buyer, tx.seller ]) if not defaulter else defaulter for r in [tx.buyer, tx.seller]: self.sendMessage('CNE_DEPOSIT_TIMEOUT:' + defaultedList, recipientID=r, txID=txid) for a in [[tx.buyer, tx.buyerInitialDeposits], [tx.seller, tx.sellerInitialDeposits]]: if a[1]: #only need to spend back if there are any deposits #we want to spend these utxos back to the depositor, minus txfee shared.debug( 0, ["For agent:", a[0], "were refunding this:", a[1]]) multisig.spendUtxosDirect(self.uniqID(), self.uniqID(), a[0], a[1]) #rewind actions completed as necessary; make the transaction defunct self.transactionUpdate(tx=tx, new_state=212)
def getSingleMessage(recipientID, timeout=1, chanIndex=0, external=False): if external: global chanExternal, connExternal else: global chan chanTmp = chan[chanIndex] if not external else chanExternal chanTmp.queue_declare(queue=recipientID) for i in range(1, timeout + 1): #if timeout>1: time.sleep(1) method_frame,header_frame,body = chanTmp.basic_get(queue=recipientID,\ no_ack=True) if not method_frame: continue else: if '|' not in body: shared.debug( 0, ["Format error in message:", body, ";message ignored"]) return None else: msg = body.split('|') #bugfix 8 Oct 2013: there can be a | in the message!! #print "message wrapper is receiving:",'|'.join(msg[1:]) shared.debug( 5, ["in message layer got:", msg[0], '|'.join(msg[1:])]) return {msg[0]: '|'.join(msg[1:])}
def editcap_inner(args, frames, outfile): start_window = 0 filenames = [] while (start_window + MAX_FRAME_FILTER < len(frames)): tmpargs = [] tmpargs.extend(args) shared.debug(1,["starting an editcap run with start_window: " \ ,str(start_window)]) filename = outfile + ".tmp." + str(start_window) filenames.append(filename) tmpargs.append(filename) tmpargs.extend(frames[start_window:start_window + MAX_FRAME_FILTER]) #shared.debug(1,["Here is the call to editcap: ", tmpargs]) shared.debug(2, ["Calling editcap with these arguments: ", tmpargs]) shared.debug(4, subprocess.check_output(tmpargs)) start_window += MAX_FRAME_FILTER args.append(outfile + ".tmp." + str(start_window)) args.extend(frames[start_window:]) shared.debug(2, ["Calling editcap with these arguments: ", args]) shared.debug(4, subprocess.check_output(args)) #Lastly, need to concatenate and delete all the temporary files args = [g("Exepaths", "mergecap_exepath"), '-w', outfile] args.extend(filenames) subprocess.check_output(args) for filename in filenames: os.remove(filename)
def getCounterparty(self,requester): buyer,seller = [self.text['Buyer BTC Address'],self.text['Seller BTC Address']] if requester not in [buyer,seller]: shared.debug(1,["Error, this contract does not contain",requester]) return None ca = buyer if seller==requester else seller return ca
def receiveSSLKeysAndSendHtml(self, msg): #get the transaction first tx = self.getTxByID(msg[0].split('.')[0]) shared.debug(0,["Received these keys:",msg]) #grab the keys from the message and turn them into a single #keyfile for use by tshark sslkeylines = msg[1].split(':')[1].split(',')[1:] keydir = os.path.join(g("Directories","escrow_base_dir"),\ '_'.join(['escrow',tx.uniqID(),'banksession'])) kf = os.path.join(keydir,'buyer_sent.keys') with open(kf,'w') as f: for kl in sslkeylines: f.write(kl) f.close() #keys have been committed to disk: self.transactionUpdate(tx=tx,new_state=801) #now we can use user_sent.keys as our input to tshark stcpdir=os.path.join(keydir,'stcplog') merged_trace = os.path.join(stcpdir,'merged.pcap') sharkutils.mergecap(merged_trace,stcpdir,dir=True) htmlarray = sharkutils.get_all_html_from_key_file(capfile=merged_trace,\ keyfile=kf) #for user security, delete keys immediately ? TODO #send html to super escrow for adjudication TODO shared.debug(0,["Sending html to super escrow for this transaction"]) m_k = tx.uniqID()+'.'+self.escrowID for a in htmlarray: self.sendMessage('DISPUTE_HTML_EVIDENCE:'+\ str(a),recipientID='ADJ'+self.uniqID(),txID=tx.uniqID()) #arbiter has been notified; final action requires human intervention self.transactionUpdate(tx=tx,new_state=802)
def mergecap(outfile, infiles, dir=False): outfile = shared.verify_file_creation(outfile, \ "mergecap output already exists!",\ overwrite=True,prompt=False,remove_in_advance=True) args = [g("Exepaths", "mergecap_exepath")] if (dir): args.extend(['-w', outfile, os.path.join(infiles, '*')]) else: args.extend(['-w', outfile]) args.extend(infiles) shared.debug(0, ["mergecap call is:", args]) try: #bug discovered 29 Sep 2013: wildcards don't get read in Unix without #passing through the shell interpreter - does this muck up Windows? if shared.OS == 'Windows': return subprocess.check_output(args) elif shared.OS == 'Linux': return subprocess.check_output(' '.join(args), shell=True) else: print "Unrecognised OS" exit(1) except: shared.debug(0, ["Error in mergecap execution, quitting!"]) exit(1)
def get_all_ssl_hashes_from_capfile(capfile, handshake= False, port= -1, \ stcp_flag=False,stream='',in_options=[]): hashes = [] for options in build_option_list(in_options=in_options): if stcp_flag: #here "capfile" is not actually a file, it's the directory #containing all the per-stream captures. #(stcppipe logs multiple capfiles, one per stream) #6 Sep 2013: The following is to merge the stcppipe log files into one pcap #unfortunately, stcppipe currently does not mark separate stream numbers #(all tcp streams have stream index 0) and so the merged file CANNOT #be used currently. #Update 8 Sep 2013 updated stcppipe called stcppipe_port solves #problem by using client port so that merged file now has #separate streams. Can still use 'basic' stcppipe, but it will be slower #edited 17th Sept - we have abandoned 'non-port' stcppipe. shared.debug(1,[\ "stcppipe_port found; merging all streams from stcppipe for processing.."]) merged_stcp_file = os.path.join(capfile,"merged.pcap") #TODO: magic string? shared.debug(1,["merged stcppipe filename is:",merged_stcp_file]) mergecap(merged_stcp_file,capfile,dir=True) return get_ssl_hashes_from_capfile(capfile=merged_stcp_file,\ port=port,options=options) else: hashes.extend(get_ssl_hashes_from_capfile(capfile=capfile,port=port,\ stream=stream,options=options)) return hashes
def releaseFunds(self, transaction, toBuyer, sig): '''Provide signature for multisig release to buyer if 'toBuyer' is true then send tx to network Also return deposit to buyer and seller TODO: if toBuyer is false, release to seller ''' #construct all pubkeys: pubBuyer = transaction.getCtrprtyPubkey(True) pubSeller = transaction.getCtrprtyPubkey(False) pubEscrow = g("Escrow", "escrow_pubkey") #this config is fixed on the escrow receiver = transaction.buyer if toBuyer else transaction.seller sig2 = multisig.createSigForRedemptionRaw(pubEscrow, pubBuyer, pubSeller,\ transaction.sellerFundingTransactionHash,\ receiver) pubs = [pubBuyer, pubSeller, pubEscrow] #for now, the sig array has ONE element, corresponding to the #seller funding transaction hash #TODO: add another 1/2 transactions for deposit redemption sigArray = [[[sig, sig2], [pubSeller, pubEscrow]]] multisig.broadcastToNetworkRaw(sigArray,pubs,transaction.sellerFundingTransactionHash,\ receiver) shared.debug(0, [ "Sent the bitcoins to the buyer; transaction completed successfully!" ]) self.transactionUpdate(tx=transaction, new_state=700)
def mergecap(outfile,infiles,dir=False): outfile = shared.verify_file_creation(outfile, \ "mergecap output already exists!",\ overwrite=True,prompt=False,remove_in_advance=True) args = [g("Exepaths","mergecap_exepath")] if (dir): args.extend(['-w',outfile, os.path.join(infiles,'*')]) else: args.extend(['-w',outfile]) args.extend(infiles) shared.debug(0,["mergecap call is:",args]) try: #bug discovered 29 Sep 2013: wildcards don't get read in Unix without #passing through the shell interpreter - does this muck up Windows? if shared.OS=='Windows': return subprocess.check_output(args) elif shared.OS=='Linux': return subprocess.check_output(' '.join(args),shell=True) else: print "Unrecognised OS" exit(1) except: shared.debug(0,["Error in mergecap execution, quitting!"]) exit(1)
def editcap_inner(args,frames,outfile): start_window = 0 filenames = [] while (start_window+MAX_FRAME_FILTER < len(frames)): tmpargs = [] tmpargs.extend(args) shared.debug(1,["starting an editcap run with start_window: " \ ,str(start_window)]) filename = outfile+".tmp."+str(start_window) filenames.append(filename) tmpargs.append(filename) tmpargs.extend(frames[start_window:start_window+MAX_FRAME_FILTER]) #shared.debug(1,["Here is the call to editcap: ", tmpargs]) shared.debug(2,["Calling editcap with these arguments: ",tmpargs]) shared.debug(4,subprocess.check_output(tmpargs)) start_window += MAX_FRAME_FILTER args.append(outfile+".tmp."+str(start_window)) args.extend(frames[start_window:]) shared.debug(2,["Calling editcap with these arguments: ",args]) shared.debug(4,subprocess.check_output(args)) #Lastly, need to concatenate and delete all the temporary files args = [g("Exepaths","mergecap_exepath"),'-w',outfile] args.extend(filenames) subprocess.check_output(args) for filename in filenames: os.remove(filename)
def editcap(infile, outfile, reverse_flag = 0, filter='', frames=[]): editcap_exepath = g("Exepaths","editcap_exepath") frame_list=[] if reverse_flag: args = [editcap_exepath, '-r', infile] else: args = [editcap_exepath,infile] if (frames): frame_list = frames else: tshark_out = tshark(infile,field = 'frame.number',filter = filter) frame_list = shared.pisp(tshark_out) shared.debug(3,["Frames are: ", frame_list]) #TODO: This won't work if -r flag not used; #may need some reconsideration if (len(frame_list)>MAX_FRAME_FILTER): editcap_inner(args,frame_list,outfile) else: args.append(outfile) args.extend(frame_list) shared.debug(2,["Calling editcap with these arguments: ",args]) subprocess.check_output(args)
def build_option_list(in_options=[], base='http'): if not in_options: return [[]] option_boolean_lists = [] #build a list of possible options flags to try if (in_options): for option in in_options: if 'port' in option: #very ugly hack just to try option_boolean_lists.append([option, '']) else: option_boolean_lists.append( [option + ':True', option + ':False']) option_all_lists = list(itertools.product(*option_boolean_lists)) shared.debug(3, option_all_lists) else: option_all_lists = [] options_permutations_list = [] for option_list in option_all_lists: option_list_tmp = filter(None, list(option_list)) options_permutations_list.append(option_list_tmp) return options_permutations_list
def __init__(self,basedir,btcaddress): super(EscrowAgent,self).__init__(basedir=basedir, btcadd=btcaddress) shared.debug(1,[\ "instantiating an escrow agent, listening for messages"]) #messaging server should always be local for escrow self.host='127.0.0.1' #log in to message queue server Msg.instantiateConnection(un=g("Agent","agent_rabbitmq_user"),pw=g("Agent","agent_rabbitmq_pass")) #hardcoded for testing TODO self.escrowID=btcaddress #self.superID = g("Escrow","super_id") #get the public list of escrows for propagation to RE self.escrowList = self.getEscrowList() #this needs to be persisted as it contains #state information - in order to accept requests involving two parties, #the escrow needs to keep a record of earlier requests downloaded #from the MQ. The format is a list of lists, each inner list having #a key,message pair [k,m] self.requestStore=[] d = os.path.join(g("Directories","escrow_base_dir"),"multisig_store") p = g("Escrow","escrow_pubkey") #initialise multisig multisig.initialise(p,d)
def get_all_ssl_hashes_from_capfile(capfile, handshake= False, port= -1, \ stcp_flag=False,stream='',in_options=[]): hashes = [] for options in build_option_list(in_options=in_options): if stcp_flag: #here "capfile" is not actually a file, it's the directory #containing all the per-stream captures. #(stcppipe logs multiple capfiles, one per stream) #6 Sep 2013: The following is to merge the stcppipe log files into one pcap #unfortunately, stcppipe currently does not mark separate stream numbers #(all tcp streams have stream index 0) and so the merged file CANNOT #be used currently. #Update 8 Sep 2013 updated stcppipe called stcppipe_port solves #problem by using client port so that merged file now has #separate streams. Can still use 'basic' stcppipe, but it will be slower #edited 17th Sept - we have abandoned 'non-port' stcppipe. shared.debug(1,[\ "stcppipe_port found; merging all streams from stcppipe for processing.."]) merged_stcp_file = os.path.join( capfile, "merged.pcap") #TODO: magic string? shared.debug(1, ["merged stcppipe filename is:", merged_stcp_file]) mergecap(merged_stcp_file, capfile, dir=True) return get_ssl_hashes_from_capfile(capfile=merged_stcp_file,\ port=port,options=options) else: hashes.extend(get_ssl_hashes_from_capfile(capfile=capfile,port=port,\ stream=stream,options=options)) return hashes
def sendDepositTimeout(self,txid,defaulter=None): ''' This is sent when one or both parties have failed to deliver their deposit to the CNE after contract signing is complete. If defaulter is None, it means both have defaulted, meaning only a message is sent to mark the transaction as defunct. Otherwise defaulter marks the role of the defaulter and the other party's deposit is returned. ''' tx = self.getTxByID(txid) if defaulter and defaulter not in [tx.buyer,tx.seller]: shared.debug(0,["Critical error: wrong argument"]) defaultedList = ','.join([tx.buyer,tx.seller]) if not defaulter else defaulter for r in [tx.buyer,tx.seller]: self.sendMessage('CNE_DEPOSIT_TIMEOUT:'+defaultedList,recipientID=r,txID=txid) for a in [[tx.buyer,tx.buyerInitialDeposits],[tx.seller,tx.sellerInitialDeposits]]: if a[1]: #only need to spend back if there are any deposits #we want to spend these utxos back to the depositor, minus txfee shared.debug(0,["For agent:",a[0],"were refunding this:",a[1]]) multisig.spendUtxosDirect(self.uniqID(),self.uniqID(),a[0],a[1]) #rewind actions completed as necessary; make the transaction defunct self.transactionUpdate(tx=tx,new_state=212)
def editcap(infile, outfile, reverse_flag=0, filter='', frames=[]): editcap_exepath = g("Exepaths", "editcap_exepath") frame_list = [] if reverse_flag: args = [editcap_exepath, '-r', infile] else: args = [editcap_exepath, infile] if (frames): frame_list = frames else: tshark_out = tshark(infile, field='frame.number', filter=filter) frame_list = shared.pisp(tshark_out) shared.debug(3, ["Frames are: ", frame_list]) #TODO: This won't work if -r flag not used; #may need some reconsideration if (len(frame_list) > MAX_FRAME_FILTER): editcap_inner(args, frame_list, outfile) else: args.append(outfile) args.extend(frame_list) shared.debug(2, ["Calling editcap with these arguments: ", args]) subprocess.check_output(args)
def getSingleMessage(recipientID,timeout=1,chanIndex=0,external=False): if external: global chanExternal,connExternal else: global chan chanTmp = chan[chanIndex] if not external else chanExternal chanTmp.queue_declare(queue=recipientID) for i in range(1,timeout+1): #if timeout>1: time.sleep(1) method_frame,header_frame,body = chanTmp.basic_get(queue=recipientID,\ no_ack=True) if not method_frame: continue else: if '|' not in body: shared.debug(0,["Format error in message:",body,";message ignored"]) return None else: msg = body.split('|') #bugfix 8 Oct 2013: there can be a | in the message!! #print "message wrapper is receiving:",'|'.join(msg[1:]) shared.debug(5,["in message layer got:",msg[0],'|'.join(msg[1:])]) return {msg[0]:'|'.join(msg[1:])}
def receiveSSLKeysAndSendHtml(self, msg): #get the transaction first tx = self.getTxByID(msg[0].split('.')[0]) shared.debug(0, ["Received these keys:", msg]) #grab the keys from the message and turn them into a single #keyfile for use by tshark sslkeylines = msg[1].split(':')[1].split(',')[1:] keydir = os.path.join(g("Directories","escrow_base_dir"),\ '_'.join(['escrow',tx.uniqID(),'banksession'])) kf = os.path.join(keydir, 'buyer_sent.keys') with open(kf, 'w') as f: for kl in sslkeylines: f.write(kl) f.close() #keys have been committed to disk: self.transactionUpdate(tx=tx, new_state=801) #now we can use user_sent.keys as our input to tshark stcpdir = os.path.join(keydir, 'stcplog') merged_trace = os.path.join(stcpdir, 'merged.pcap') sharkutils.mergecap(merged_trace, stcpdir, dir=True) htmlarray = sharkutils.get_all_html_from_key_file(capfile=merged_trace,\ keyfile=kf) #for user security, delete keys immediately ? TODO #send html to super escrow for adjudication TODO shared.debug(0, ["Sending html to super escrow for this transaction"]) m_k = tx.uniqID() + '.' + self.escrowID for a in htmlarray: self.sendMessage('DISPUTE_HTML_EVIDENCE:'+\ str(a),recipientID='ADJ'+self.uniqID(),txID=tx.uniqID()) #arbiter has been notified; final action requires human intervention self.transactionUpdate(tx=tx, new_state=802)
def sendAdjudicatorCollateralSigningRequest(myself,identityInfo,collateralAmount): msigaddr,mscript = myself.createCollateralMultisig() if not msigaddr or not mscript: shared.debug(0,["Critical error: cannot create a multisignature address."]) return False mypub,mypriv = multisig.getKeysFromUniqueID(myself.uniqID()) shared.debug(0,["You have created this multisig address:",msigaddr]) shared.debug(0,["Please wait while your request is sent out to the pool."]) #broadcast #TODO this is slow but ..? for i,e in enumerate(getEscrowList()): shared.config.set("Escrow","escrow_id",value=e[0]) shared.config.set("Escrow","escrow_pubkey",value=e[2]) shared.config.set("Escrow","escrow_host",value=e[1]) shared.debug(2,["Set the escrow to host:",g("Escrow","escrow_host"),"id:",g("Escrow","escrow_id"),"pubkey:",g("Escrow","escrow_pubkey")]) #Msg.closeConnection() time.sleep(1) Msg.instantiateConnection() myself.sendMessage('ADJUDICATOR_APPLICATION:'+'|'.join([msigaddr,mypub,identityInfo])) shared.debug(0,["Your application has been sent to all pool members. Please wait for their response."]) return True
def allAdjudicatorsAcceptedApplication(self): with open(g("Escrow","adjudicator_store")) as f: content = f.readlines() filtered = [x for x in content if 'APPLICATION FOR ADJUDICATION ACCEPTED BY' in x] identities = [x.split(',')[0].split(':')[1] for x in filtered] adjudicatorIdentities = [x[0] for x in [e.split('|') for e in g("Escrow","escrow_list").split(',')]] if set(adjudicatorIdentities) != set(identities): shared.debug(5,["Not all adjudicators have yet accepted the application"]) return #we are ready to make the collateral payment rspns = shared.get_binary_user_input('Your application for adjudicator role has been accepted. Are you ready to make the payment of '+g("Escrow","escrow_collateral_size")+" satoshis?",'y','y','n','n') if rspns != 'y': return #create the transaction and present it to the user for verification #we must be VERY careful here as the amount is large msigaddr, mscript = self.createCollateralMultisig() if not msigaddr or not mscript: raise Exception("This should not be possible.") collSize = g("Escrow","escrow_collateral_size") multisig.spendUtxos(self.uniqID(),self.uniqID(), msigaddr, None, amt=int(collSize),prepare=True) rspns = shared.get_binary_user_input('Do you still want to pay?','y','y','n','n') if rspns != 'y': return #we have confirmation after checking; broadcast multisig.spendUtxos(self.uniqID(),self.uniqID(),msigaddr,None,amt=int(collSize)) shared.debug(0,["Congratulations, you are now a member of the pool. Please contact the other pool members on instructions for setting up the server."])
def __init__(self, basedir, btcaddress): super(EscrowAgent, self).__init__(basedir=basedir, btcadd=btcaddress) shared.debug(1,[\ "instantiating an escrow agent, listening for messages"]) #messaging server should always be local for escrow self.host = '127.0.0.1' #log in to message queue server Msg.instantiateConnection(un=g("Agent", "agent_rabbitmq_user"), pw=g("Agent", "agent_rabbitmq_pass")) #hardcoded for testing TODO self.escrowID = btcaddress #self.superID = g("Escrow","super_id") #get the public list of escrows for propagation to RE self.escrowList = self.getEscrowList() #this needs to be persisted as it contains #state information - in order to accept requests involving two parties, #the escrow needs to keep a record of earlier requests downloaded #from the MQ. The format is a list of lists, each inner list having #a key,message pair [k,m] self.requestStore = [] d = os.path.join(g("Directories", "escrow_base_dir"), "multisig_store") p = g("Escrow", "escrow_pubkey") #initialise multisig multisig.initialise(p, d)
def getCtrprtyPubkey(self, c): addr = self.buyer if c else self.seller msg = self.contract.getContractTextOrdered() for sig in self.contract.signatures.values(): pub = multisig.ecdsa_recover(msg, sig) if addr == multisig.pubtoaddr(pub): shared.debug(2, ["Got address", addr, "for pubkey,", pub]) return pub
def __init__(self, contractDetailsDict): shared.debug(5, ["\n instantiating a contract \n"]) self.text = contractDetailsDict #hash the contents self.setHash() #no signature on creation self.isSigned = False #allow multiple signatures "appended" self.signatures = {}
def sendConfirmationBankingSessionEnded(self,tx,rspns): #sanity check if tx.getRole(self.uniqID()) != 'buyer': shared.debug(0,["Error: user agent:",self.agent.uniqID(),\ "is not the buyer for this transaction and so can't confirm the end",\ "of the banking session!"]) #construct a message to the escrow shared.debug(0,["Sending bank session end confirm to seller and escrow"]) self.sendMessage('RE_BANK_SESSION_ENDED:'+rspns,recipientID='RE',txID=tx.uniqID())
def __init__(self,contractDetailsDict): shared.debug(5,["\n instantiating a contract \n"]) self.text = contractDetailsDict #hash the contents self.setHash() #no signature on creation self.isSigned=False #allow multiple signatures "appended" self.signatures={}
def receiveContractCNE(self, msg): '''When receiving a contract, first check it's signed and throw it out if not. Otherwise, store it in the list of contracts that have been proposed by possible counterparties We can choose to accept at anytime, within the process/session. However we will not persist contract 'suggestions' across sessions. ''' allContractDetails = ':'.join(msg.split(':')[1:]).split('|') contractDetails = allContractDetails[0] #the contract is in json; need to change it to a Contract object contractDetailsDict = json.loads(contractDetails) tmpContract = Contract.Contract(contractDetailsDict) ca = tmpContract.getCounterparty(self.uniqID()) if not ca: return 'Contract invalid: does not contain this identity' for s in allContractDetails[1:]: ad = multisig.pubtoaddr( multisig.ecdsa_recover(tmpContract.getContractTextOrdered(), s)) shared.debug(2, ["\n recovery produced this address: ", ad, "\n"]) tmpContract.signatures[ad] = s #now the temporary contract object is fully populated; #we can check the signatures match the IDs in the contract for k, v in tmpContract.signatures.iteritems(): if k not in [ tmpContract.text['Buyer BTC Address'], tmpContract.text['Seller BTC Address'] ]: shared.debug(1, ['Error: signature', v, 'from', k, 'was invalid']) return 'Invalid contract signature' self.contractLock.acquire() try: #note that this represents an override for #repeated sending of contracts; one cp can only #be suggesting one contract at a time self.pendingContracts[ca] = tmpContract finally: self.contractLock.release() #if the contract is already signed by me AND ctrprty, send it to escrow if len(tmpContract.signatures.keys()) > 1: self.contractLock.acquire() try: self.workingContract = tmpContract #wipe the pending contract list; we are only #interested in the live contract now self.pendingContracts = {} finally: self.contractLock.release() return 'Signed contract successfully received from counterparty: ' + ca
def sendTransactionSynchronization(self,msg): requester = msg[0].split('.')[1] shared.debug(0,["Requester:",requester]) smsg_key = '0.'+self.uniqID() for tx in self.transactions: if requester == tx.buyer or requester == tx.seller: self.sendMessage('RE_TRANSACTION_SYNC_RESPONSE:'\ +pickle.dumps(tx),recipientID=requester,txID=tx.uniqID()) #send a final message to mark end of list self.sendMessage('RE_TRANSACTION_SYNC_COMPLETE:',recipientID=requester,txID=tx.uniqID())
def getEscrowList(self): eL = g("Escrow","escrow_list").split(',') y=[] for i,x in enumerate(eL): d = x.split('|') y.append({'host':d[1]}) y[i]['pubkey']=d[2] y[i]['id']=d[0] shared.debug(4,["Generated this escrow list:",y]) return y
def getEscrowList(self): eL = g("Escrow", "escrow_list").split(',') y = [] for i, x in enumerate(eL): d = x.split('|') y.append({'host': d[1]}) y[i]['pubkey'] = d[2] y[i]['id'] = d[0] shared.debug(4, ["Generated this escrow list:", y]) return y
def getCounterparty(self, requester): buyer, seller = [ self.text['Buyer BTC Address'], self.text['Seller BTC Address'] ] if requester not in [buyer, seller]: shared.debug(1, ["Error, this contract does not contain", requester]) return None ca = buyer if seller == requester else seller return ca
def startRandomEscrowChoice(self, txhash): #first upgrade transaction. then notify counterparties. #then take timestamp for 1 or 2 minutes after deposits confirmed. #read from NIST beacon. convert to small rand in range. #message to counterparties and RE of choice. #mark the waypoint time. This time must be *after* the #arrival of both deposits; this is causally guaranteed by the protocol #note that this is not yet perfect TODO - we either change it #or somehow guarantee synchronisation between client and server waypoint = int(time.time()) tx = self.getTxByID(txhash) self.transactionUpdate(full=False, txID='', tx=tx, new_state=206) m = 'CNE_RE_CHOICE_STARTED:' + str(waypoint) for recipient in [tx.buyer, tx.seller]: self.sendMessage(m, recipientID=recipient, txID=txhash) #get public random number publicRandomMax = 1000000 timestamp, publicRandom = shared.get_public_random(publicRandomMax) numEscrows = len(self.escrowList) chosenEscrow = int( math.floor((numEscrows / publicRandomMax) * publicRandom)) shared.debug(0, ["We chose escrow:", chosenEscrow, "at time:", timestamp]) #TODO hack for testing, remove this chosenEscrow = 1 #generate multisig address on remote escrow epk = self.escrowList[chosenEscrow]['pubkey'] REMultisigAddr, mscript = self.getMultisigAddress(tx, epk) shared.debug(0, ["Generated multisig was: ", REMultisigAddr]) #We include the escrow pubkey in the message so as to confirm #the RE identity in case of some error syncing the escrow table m = 'CNE_RE_CHOSEN:'+'|'.join([self.escrowList[chosenEscrow]['id'],tx.uniqID(),\ tx.contract.getContractText(),str(epk),REMultisigAddr]) for recipient in [tx.buyer, tx.seller]: self.sendMessage(m, recipientID=recipient, txID=tx.uniqID()) paidAmount, btcTxhash = self.sendCoinsToRE(txhash, REMultisigAddr) tx.depositHash = btcTxhash tx.chosenRE = chosenEscrow #update the transaction state self.transactionUpdate(full=False, txID=tx.uniqID(), tx=None, new_state=300) self.transferTxToRE(tx, chosenEscrow)
def sendTransactionSynchronization(self, msg): requester = msg[0].split('.')[1] shared.debug(0, ["Requester:", requester]) smsg_key = '0.' + self.uniqID() for tx in self.transactions: if requester == tx.buyer or requester == tx.seller: self.sendMessage('RE_TRANSACTION_SYNC_RESPONSE:'\ +pickle.dumps(tx),recipientID=requester,txID=tx.uniqID()) #send a final message to mark end of list self.sendMessage('RE_TRANSACTION_SYNC_COMPLETE:', recipientID=requester, txID=tx.uniqID())
def sendConfirmationBankingSessionEnded(self, tx, rspns): #sanity check if tx.getRole(self.uniqID()) != 'buyer': shared.debug(0,["Error: user agent:",self.agent.uniqID(),\ "is not the buyer for this transaction and so can't confirm the end",\ "of the banking session!"]) #construct a message to the escrow shared.debug(0, ["Sending bank session end confirm to seller and escrow"]) self.sendMessage('RE_BANK_SESSION_ENDED:' + rspns, recipientID='RE', txID=tx.uniqID())
def getUtxos(payee,payer,arr=False): filteredUtxos=[] if arr: total = [] else: total = 0 h = history(payee) #for each transaction in the history, check if the input meets #the conditions of the filter unspent = [x for x in h if 'spend' not in x.keys()] #shared.debug(2,["unspent:",str(len(unspent)),unspent]) x = ea.get_from_electrum([payee],t='a')[0]['result'] shared.debug(8,["Got this from electrum:",x]) #build list of heights - to get the raw transaction and deserialize, #we need to query the electrum server with a transaction_get, which needs #BOTH the height and the transaction hash heightsDict={} for d in x: heightsDict[d['tx_hash']]=d['height'] for i in unspent: if payer: txh = i['output'].split(':')[0] if txh not in heightsDict.keys(): continue rawtx = ea.get_from_electrum([[txh,heightsDict[txh]]],t='t')[0]['result'] cookedtx = deserialize(rawtx) #slightly hack-y but necessary: #payers are allowed to use more than one input #(so they can sweep up utxos) #but a transaction with two DIFFERENT input addresses is not allowed if len(cookedtx['ins'])>1: for j in cookedtx['ins']: p,s,a = ea.get_address_from_input_script(cookedtx['ins'][0]['script'].decode('hex')) p2,s2,a2 = ea.get_address_from_input_script(j['script'].decode('hex')) if not a == a2: raise Exception("Found an input transaction with more than one payer!") pubkeys,signatures, addr = \ ea.get_address_from_input_script(cookedtx['ins'][0]['script'].decode('hex')) if addr != payer: continue filteredUtxos.append(i) if arr: total.append(i['value']) else: total += i['value'] return (filteredUtxos,total)
def receiveContractCNE(self, msg): '''acting as CNE, the escrow can receive a doubly-signed contract at any time from any party. After verifying that the signatures are valid, and that the deposits are specified correctly in the contract, the address for deposits is reported and the contract signed for the third time by the escrow. Messages sent to both parties giving them a deadline for deposit. ''' sender = msg[0].split('.')[1] #this special message is delimited by | allContractDetails = ':'.join(msg[1].split(':')[1:]).split('|') #the contract is in json format contractDetails = allContractDetails[0] tmpContract = Contract.Contract(json.loads(contractDetails)) pubs = {} for s in allContractDetails[1:]: tmpPub = multisig.ecdsa_recover( tmpContract.getContractTextOrdered(), s) ad = multisig.pubtoaddr(tmpPub) shared.debug(2, ["\n recovery produced this address: ", ad, "\n"]) tmpContract.signatures[ad] = s #store the pubkeys for later use pubs[ad] = tmpPub #immediately check for 2 signatures; otherwise dump immediately if len(allContractDetails) != 3: return (False, 'Not a valid and fully signed contract, ignoring', tmpContract) #now the temporary contract object is fully populated; #we can check the signatures match the IDs in the contract for k, v in tmpContract.signatures.iteritems(): if k not in [ tmpContract.text['Buyer BTC Address'], tmpContract.text['Seller BTC Address'] ]: shared.debug(1, ['Error: signature', v, 'from', k, 'was invalid']) return (False, 'Invalid contract signature', tmpContract) #need to check that the proposed deposits follow the business rules verdict, reason = self.checkBusinessRulesCNE(tmpContract) if not verdict: return (verdict, reason, tmpContract) #removed for now. #now we're happy that the contract is valid we build the dep multisig #multisig.initialise(g("Escrow","escrow_pubkey"),g("Directories","escrow_base_dir")) #for a,p in pubs.iteritems(): #multisig.store_share(p,a) #msigaddr, mscript = multisig.create_multisig_address(*pubs.keys()) return (True, multisig.pubtoaddr(g("Escrow", "escrow_pubkey")), tmpContract)
def signContractCNE(self): if not self.workingContract: shared.debug(0, ["Error: contract not defined."]) return False dummy,sig = multisig.signText(self.uniqID(),\ self.workingContract.getContractTextOrdered()) shared.debug(3, ["Here is the signature we made:", sig]) self.contractLock.acquire() try: self.workingContract.sign(self.uniqID(), sig) finally: self.contractLock.release() return True
def signContractCNE(self): if not self.workingContract: shared.debug(0,["Error: contract not defined."]) return False dummy,sig = multisig.signText(self.uniqID(),\ self.workingContract.getContractTextOrdered()) shared.debug(3,["Here is the signature we made:",sig]) self.contractLock.acquire() try: self.workingContract.sign(self.uniqID(),sig) finally: self.contractLock.release() return True
def get_magic_hashes(stcpdir,keyfile,port): #this data structure will contain ALL GET requests performed under SSL #for this banking session GETs=[] #pass the keyfile as a -o flag to tshark options=['ssl.keylog_file:'+keyfile] #these magic hashes will be sent to escrow; when escrow finds them #in his hash list, he will dump all following hashes in that stream. #(Detailed explanation of reason deferred to later TODO) magic_hashes = [] for x in os.listdir(stcpdir): if x == 'merged.pcap': continue capfile = os.path.join(stcpdir,x) GET_dict = get_GET_http_requests(capfile,options) shared.debug(0,["Here is the GET dictionary for the file",capfile,":",GET_dict]) if not GET_dict: continue if not any(GET_dict): #this happens if the stream doesn't contain SSL; just ignore it continue GETs.append(GET_dict) #this element of the list 'GETs' corresponds to one file, which means #one stream out of stcp. It is a dict which maps #frame numbers as keys to GET requests as strings. #We could conceivably act differently based on some string matching in #the GET, in particular related to the content type that's being requested #However the simplest action to take is to use tshark to check if #any HTTP content was returned AFTER the LAST GET in the stream. If not, #we mark the hash of the GET request frame as magic, and then pass these #magic hashes to escrow, who knows to ignore all hashes that occur after #it in that stream. #get the highest frame number in the dict highest_frame = max(GET_dict,key=int) shared.debug(0,["In file:",capfile," the highest get frame is:",\ highest_frame]) #check for no http-content-type OR no http-last-modified (cache hit) # after: this is the signal that #the connection was dropped, and we cannot assume the other parties #in proxying did NOT get the response. #TODO: other possible filters are http.server and tcp.srcport #I think in some way it should all work fs = 'ssl and (http.content_type or http.last_modified) and (frame.number gt '+highest_frame+')' if not tshark(capfile,filter=fs,field='frame.number',options=options): #get the ssl hashes of that frame ssl_hashes = get_ssl_hashes_from_capfile(capfile,\ port=port,frames=[highest_frame],options=options) #append it to magic_hashes shared.debug(0,["Appending these value to magic_hashes:",ssl_hashes]) magic_hashes.extend(ssl_hashes) shared.debug(1,["Here is the full printout of the GET requests:",GETs]) return magic_hashes
def allAdjudicatorsAcceptedApplication(self): with open(g("Escrow", "adjudicator_store")) as f: content = f.readlines() filtered = [ x for x in content if 'APPLICATION FOR ADJUDICATION ACCEPTED BY' in x ] identities = [x.split(',')[0].split(':')[1] for x in filtered] adjudicatorIdentities = [ x[0] for x in [e.split('|') for e in g("Escrow", "escrow_list").split(',')] ] if set(adjudicatorIdentities) != set(identities): shared.debug( 5, ["Not all adjudicators have yet accepted the application"]) return #we are ready to make the collateral payment rspns = shared.get_binary_user_input( 'Your application for adjudicator role has been accepted. Are you ready to make the payment of ' + g("Escrow", "escrow_collateral_size") + " satoshis?", 'y', 'y', 'n', 'n') if rspns != 'y': return #create the transaction and present it to the user for verification #we must be VERY careful here as the amount is large msigaddr, mscript = self.createCollateralMultisig() if not msigaddr or not mscript: raise Exception("This should not be possible.") collSize = g("Escrow", "escrow_collateral_size") multisig.spendUtxos(self.uniqID(), self.uniqID(), msigaddr, None, amt=int(collSize), prepare=True) rspns = shared.get_binary_user_input('Do you still want to pay?', 'y', 'y', 'n', 'n') if rspns != 'y': return #we have confirmation after checking; broadcast multisig.spendUtxos(self.uniqID(), self.uniqID(), msigaddr, None, amt=int(collSize)) shared.debug(0, [ "Congratulations, you are now a member of the pool. Please contact the other pool members on instructions for setting up the server." ])
def receiveContractCNE(self,msg): '''When receiving a contract, first check it's signed and throw it out if not. Otherwise, store it in the list of contracts that have been proposed by possible counterparties We can choose to accept at anytime, within the process/session. However we will not persist contract 'suggestions' across sessions. ''' allContractDetails = ':'.join(msg.split(':')[1:]).split('|') contractDetails = allContractDetails[0] #the contract is in json; need to change it to a Contract object contractDetailsDict = json.loads(contractDetails) tmpContract = Contract.Contract(contractDetailsDict) ca = tmpContract.getCounterparty(self.uniqID()) if not ca: return 'Contract invalid: does not contain this identity' for s in allContractDetails[1:]: ad = multisig.pubtoaddr(multisig.ecdsa_recover(tmpContract.getContractTextOrdered(),s)) shared.debug(2,["\n recovery produced this address: ",ad,"\n"]) tmpContract.signatures[ad]=s #now the temporary contract object is fully populated; #we can check the signatures match the IDs in the contract for k,v in tmpContract.signatures.iteritems(): if k not in [tmpContract.text['Buyer BTC Address'],tmpContract.text['Seller BTC Address']]: shared.debug(1,['Error: signature',v,'from',k,'was invalid']) return 'Invalid contract signature' self.contractLock.acquire() try: #note that this represents an override for #repeated sending of contracts; one cp can only #be suggesting one contract at a time self.pendingContracts[ca] = tmpContract finally: self.contractLock.release() #if the contract is already signed by me AND ctrprty, send it to escrow if len(tmpContract.signatures.keys())>1: self.contractLock.acquire() try: self.workingContract = tmpContract #wipe the pending contract list; we are only #interested in the live contract now self.pendingContracts = {} finally: self.contractLock.release() return 'Signed contract successfully received from counterparty: '+ca
def startRandomEscrowChoice(self,txhash): #first upgrade transaction. then notify counterparties. #then take timestamp for 1 or 2 minutes after deposits confirmed. #read from NIST beacon. convert to small rand in range. #message to counterparties and RE of choice. #mark the waypoint time. This time must be *after* the #arrival of both deposits; this is causally guaranteed by the protocol #note that this is not yet perfect TODO - we either change it #or somehow guarantee synchronisation between client and server waypoint = int(time.time()) tx = self.getTxByID(txhash) self.transactionUpdate(full=False,txID='',tx=tx,new_state=206) m = 'CNE_RE_CHOICE_STARTED:'+str(waypoint) for recipient in [tx.buyer,tx.seller]: self.sendMessage(m,recipientID=recipient,txID=txhash) #get public random number publicRandomMax=1000000 timestamp,publicRandom = shared.get_public_random(publicRandomMax) numEscrows = len(self.escrowList) chosenEscrow = int(math.floor((numEscrows/publicRandomMax)*publicRandom)) shared.debug(0,["We chose escrow:",chosenEscrow,"at time:",timestamp]) #TODO hack for testing, remove this chosenEscrow=1 #generate multisig address on remote escrow epk = self.escrowList[chosenEscrow]['pubkey'] REMultisigAddr,mscript = self.getMultisigAddress(tx,epk) shared.debug(0,["Generated multisig was: ", REMultisigAddr]) #We include the escrow pubkey in the message so as to confirm #the RE identity in case of some error syncing the escrow table m = 'CNE_RE_CHOSEN:'+'|'.join([self.escrowList[chosenEscrow]['id'],tx.uniqID(),\ tx.contract.getContractText(),str(epk),REMultisigAddr]) for recipient in [tx.buyer,tx.seller]: self.sendMessage(m,recipientID=recipient,txID=tx.uniqID()) paidAmount,btcTxhash = self.sendCoinsToRE(txhash,REMultisigAddr) tx.depositHash = btcTxhash tx.chosenRE=chosenEscrow #update the transaction state self.transactionUpdate(full=False,txID=tx.uniqID(),tx=None,new_state=300) self.transferTxToRE(tx,chosenEscrow)
def spendUtxosDirect(addrOwner,addrOwnerID,payee,utxoList): """ spend a set of utxos as returned from a call to getUtxos() to recipient payee from owner addrOwner """ u,total= utxoList outs = [{'value':total-shared.defaultBtcTxFee,'address':payee}] shared.debug(5,["About to make a transaction with these ins:",u,"and these outs:",outs]) tmptx = mktx(u,outs) pub,priv = getKeysFromUniqueID(addrOwnerID) for i,x in enumerate(u): tmptx = sign(tmptx,i,priv) rspns = ea.send_tx(tmptx) shared.debug(2,["Electrum server sent back:",rspns]) #in this case we return amount spent for convenience return (total,tx_hash(tmptx).encode('hex'))
def get_ssl_hashes_from_ssl_app_data_list(ssl_app_data_list): ssl_hashes = [] for s in ssl_app_data_list: #get rid of commas and colons #(ssl.app_data comma-delimits multiple SSL segments within the same frame) s = s.rstrip() s = s.replace(',',' ') s = s.replace(':',' ') if s == '': shared.debug(2,["Warning: empty ssl app data string passed for hashing!"]) else: ssl_hashes.append(hashlib.md5(bytearray.fromhex(s)).hexdigest()) return list(set(ssl_hashes))
def transactionUpdate(self, full=False, txID='',tx=None,new_state=''): '''To ensure correct persistence, transaction states can only be updated via this method. If full is set, we just persist the current list with no changes. Otherwise, the transaction can be set either with a tx object or an ID. The new state should be defined (see Transaction.Transaction) if it's not, the transaction will be deleted from the store ''' if not self.txStore: return #a little error checking: if new_state: try: new_state = int(new_state) except: shared.debug(0,["Critical error: we tried to update a transaction",\ "to a non-integer state! Quitting."]) exit(1) tdbLock.acquire() try: if not full: if not tx: if not txID: raise Exception("you called transactionUpdate without"+\ "specifying a transaction! Doh!") tx = self.getTxByID(txID) index = next((i for i in range(0,len(self.transactions)) \ if self.transactions[i].uniqID()==tx.uniqID()),None) shared.debug(0,["Set index to:",index,"for transaction:",tx.uniqID()]) #bugfix 6 Oct; zero counts as false!! if index is None: #means this is a new transaction; add it self.transactions.append(tx) if not new_state: raise Exception("You cannot add a transaction with no state!") self.transactions[len(self.transactions)-1].state=new_state else: if not new_state: #get rid of it del self.transactions[index] else: #update the state shared.debug(0,["Updating transaction state to",new_state]) self.transactions[index].state = new_state #persist to file - note that persistence is definitely necessary, #but of course this primitive file-as-database would not really #be acceptable except for the fact that performance is not an issue. #TODO need to review whether system crash makes this unacceptable; #transfer to simple mysql database or something like that with open(self.txFile,'w') as f: pickle.dump(self.transactions,f) shared.debug(0,["Dumped contents of transaction to file"]) finally: tdbLock.release()
def transferTxToRE(self, tx, chosenEscrow): #send a message to the chosen escrow #this message contains the full transaction object transaction_string = pickle.dumps(tx) buyerSig = tx.contract.getSignature('buyer') sellerSig = tx.contract.getSignature('seller') txt, escrowSig = multisig.signText( self.uniqID(), tx.contract.getContractTextOrdered()) m = 'CNE_RE_TRANSFER:'+'|'.join([transaction_string,buyerSig,\ sellerSig,escrowSig,tx.depositHash]) #Hack for testing: set chosen escrow to the "other" chosenEscrow = 1 self.sendMessage(m, recipientID='RE' + self.escrowList[chosenEscrow]['id'], txID=tx.uniqID()) msg = self.getSingleMessage(20, prefix='CNE') if not msg: return False a, b = msg.keys()[0].split('.') if b != self.escrowList[chosenEscrow]['id'] or tx.uniqID() != a: #we have received an inappropriate message return False else: if 'RE_CNE_TX_CONFIRM_RECEIPT' in msg.values()[0]: #once transaction is confirmed received, we update its state so as not to send it again #this is the final state of the transaction on the contract negotiation side self.transactionUpdate(full=False, txID=tx.uniqID(), new_state=301) return True elif 'RE_CNE_TX_REJECT_RECEIPT' in msg.values()[0]: shared.debug(0, [ "Warning, transaction", tx.uniqID(), "was not accepted by chosen Escrow", self.escrowList[chosenEscrow]['id'] ]) #do nothing here; we need to send again #TODO what kind of rejection? parse "reason" field? return False else: raise Exception("Random escrow sent unrecognised message type")
def get_ssl_hashes_from_capfile(capfile, port=-1, stream='', options=[], frames=[]): frames_str = '' ssl_frames = [] #Run tshark to get a list of frames with ssl app data in them #EDITED to test escrow filterstr = build_filterstr(stream=stream, port=port) if (frames): ssl_frames = frames else: try: frames_str = tshark(capfile,field='frame.number', \ filter= filterstr,options=options) except: #this could be caused by a corrupt file from stcppipe #or by a malformed query string,etc. - but in the former #case we should NOT exit, hence this approach shared.debug(0, ["tshark failed - see stderr for message"]) shared.debug(0, ["return code from tshark: ", frames_str]) return None ssl_frames = shared.pisp(frames_str) #gracefully handle null result (i.e. blank tshark output): ssl_frames = filter(None, ssl_frames) if not ssl_frames: return None #Now we definitely have ssl frames in this capture file shared.debug(1, ['need to process this many frames:', len(ssl_frames)]) ssl_app_data = tshark(capfile,field='ssl.app_data',frames=ssl_frames,\ options=options) #ssl.app_data will return all encrypted segments separated by commas #but also, lists of segments from different frames will be separated by #platform dependent newlines ssl_app_data_list = shared.pisp(ssl_app_data.replace(',', shared.PINL)) #remove any blank OR duplicate entries in the ssl app data list ssl_app_data_list = filter(None, list(set(ssl_app_data_list))) shared.debug(4, ["Full dump of ssl application data:\n", ssl_app_data_list]) shared.debug(1,["Length of list of ssl segments for file ",capfile," was: " \ ,len(ssl_app_data_list)]) return get_ssl_hashes_from_ssl_app_data_list(ssl_app_data_list)
def get_ssl_hashes_from_ssl_app_data_list(ssl_app_data_list): ssl_hashes = [] for s in ssl_app_data_list: #get rid of commas and colons #(ssl.app_data comma-delimits multiple SSL segments within the same frame) s = s.rstrip() s = s.replace(',', ' ') s = s.replace(':', ' ') if s == '': shared.debug( 2, ["Warning: empty ssl app data string passed for hashing!"]) else: ssl_hashes.append(hashlib.md5(bytearray.fromhex(s)).hexdigest()) return list(set(ssl_hashes))
def receiveContractCNE(self,msg): '''acting as CNE, the escrow can receive a doubly-signed contract at any time from any party. After verifying that the signatures are valid, and that the deposits are specified correctly in the contract, the address for deposits is reported and the contract signed for the third time by the escrow. Messages sent to both parties giving them a deadline for deposit. ''' sender = msg[0].split('.')[1] #this special message is delimited by | allContractDetails = ':'.join(msg[1].split(':')[1:]).split('|') #the contract is in json format contractDetails = allContractDetails[0] tmpContract = Contract.Contract(json.loads(contractDetails)) pubs = {} for s in allContractDetails[1:]: tmpPub = multisig.ecdsa_recover(tmpContract.getContractTextOrdered(),s) ad = multisig.pubtoaddr(tmpPub) shared.debug(2,["\n recovery produced this address: ",ad,"\n"]) tmpContract.signatures[ad]=s #store the pubkeys for later use pubs[ad]= tmpPub #immediately check for 2 signatures; otherwise dump immediately if len(allContractDetails) != 3: return (False,'Not a valid and fully signed contract, ignoring',tmpContract) #now the temporary contract object is fully populated; #we can check the signatures match the IDs in the contract for k,v in tmpContract.signatures.iteritems(): if k not in [tmpContract.text['Buyer BTC Address'],tmpContract.text['Seller BTC Address']]: shared.debug(1,['Error: signature',v,'from',k,'was invalid']) return (False,'Invalid contract signature',tmpContract) #need to check that the proposed deposits follow the business rules verdict,reason = self.checkBusinessRulesCNE(tmpContract) if not verdict: return (verdict, reason,tmpContract) #removed for now. #now we're happy that the contract is valid we build the dep multisig #multisig.initialise(g("Escrow","escrow_pubkey"),g("Directories","escrow_base_dir")) #for a,p in pubs.iteritems(): #multisig.store_share(p,a) #msigaddr, mscript = multisig.create_multisig_address(*pubs.keys()) return (True,multisig.pubtoaddr(g("Escrow","escrow_pubkey")),tmpContract)
def createCollateralMultisig(self): #request to be adjudicator requires identity info #construct list of pubkeys escrowList = self.getEscrowList() pubkeyList=[x[2] for x in escrowList] mypub,mypriv = multisig.getKeysFromUniqueID(self.uniqID()) pubkeyList.append(mypub) pubkeyList.sort() shared.debug(0,["Here are the pubkeys:",pubkeyList]) #majority M of N total: N = len(pubkeyList) if N>15: #TODO magic number shared.debug(0,["Critical error: the total number of keys in this multisig address exceeds the maximum of 17 - aborting"]) return None,None M = int(math.floor(N/2)+1) msigaddr,mscript = multisig.createMultisigRaw(M, N, pubkeyList) return (msigaddr,mscript)
def run(self, escrowRole='cne'): #the main loop to be called for the daemon meaning we're #listening for messages/requests/instructions from useragents. if escrowRole == 're': self.runRE() return elif escrowRole != 'cne': shared.debug(0, ["Error! Role must be cne or re"]) exit(1) while True: #deal with transactions self.takeAppropriateActions() msg = self.getSingleMessage(5, prefix='CNE') if not msg: shared.debug(0, ["Got nothing, waiting.."]) continue k, m = msg.items()[0] txID, requester = k.split('.') if 'ADJUDICATOR_APPLICATION:' in m: self.writeAdjudicatorApplication(':'.join(m.split(':')[1:]), requester) continue if 'CNE_SIGNED_CONTRACT:' in m: verdict, data, contract = self.receiveContractCNE([k, m]) self.sendContractVerdictCNE(verdict, data, contract) continue #TODO: this code is identical on RE and CNE; #how to avoid replicating? if 'QUERY_STATUS:' in m: queryee = m.split(':')[1] self.sendMessage('QUERY_STATUS:' + requester, recipientID=queryee) if 'QUERY_STATUS_RESPONSE:' in m: rspns, ctrprty = m.split(':')[1].split(',') self.sendMessage('QUERY_STATUS_RESPONSE:' + rspns, recipientID=ctrprty)
def startFirefox(self): #if not os.path.isdir(os.path.join(datadir, 'firefox')): # os.mkdir(os.path.join(datadir, 'firefox')) ffdir = shared.makedir([self.baseDir, 'firefox']) #touch files for fn in ['stdout', 'stderr']: open(os.path.join(ffdir, 'firefox.' + fn), 'w').close() if not os.path.isfile( os.path.join(ffdir, 'FF-profile', 'extensions.ini')): #FF rewrites extensions.ini on first run, so we allow FF to create it, #then we kill FF, rewrite the file and start FF again try: self.ff_proc = subprocess.Popen([g("Exepaths","firefox_exepath"),\ '-no-remote','-profile', os.path.join(ffdir, 'FF-profile')], \ stdout=open(os.path.join(ffdir,"firefox.stdout"),'w'), \ stderr=open(os.path.join(ffdir, "firefox.stderr"),'w')) except Exception, e: shared.debug(0, ["Error starting Firefox"]) return ["Error starting Firefox"] while 1: time.sleep(0.5) if os.path.isfile( os.path.join(ffdir, 'FF-profile', 'extensions.ini')): self.ff_proc.kill() break try: #enable extension with codecs.open (os.path.join(ffdir, 'FF-profile', \ 'extensions.ini'), "w") as f1: f1.write("[ExtensionDirs]\nExtension0=" + \ os.path.join(ffdir, 'FF-profile',\ "extensions", "lspnr@lspnr") + "\n") #show addon bar with codecs.open(os.path.join(ffdir, \ 'FF-profile', 'localstore.rdf'), 'w') as f2: f2.write( '<?xml version="1.0"?><RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#" xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><RDF:Description RDF:about="chrome://browser/content/browser.xul"><NC:persist RDF:resource="chrome://browser/content/browser.xul#addon-bar" collapsed="false"/></RDF:Description></RDF:RDF>' ) except Exception, e: shared.debug(0, ['File open error']) return False
def sendSignedContractToCtrprtyCNE(self): '''send a json dump of the contract contents also send to the chosen escrow if both parties signed should already be initialised and signed''' if not self.workingContract or not self.workingContract.isSigned: return False msg_details = [self.workingContract.getContractText()] msg_details.extend([v for k,v in self.workingContract.signatures.iteritems()]) msg = 'CNE_SIGNED_CONTRACT:'+'|'.join(msg_details) shared.debug(0,["sending message:",msg]) if len(self.workingContract.signatures.keys())>1: shared.debug(0,["\n **Sending a complete contract to the escrow**\n"]) self.persistContract(self.workingContract) self.sendMessage(msg,recipientID='CNE',\ txID=self.workingContract.textHash) self.sendMessage(msg,\ recipientID=self.workingContract.getCounterparty(self.uniqID()),\ txID=self.workingContract.textHash) return True
def createCollateralMultisig(self): #request to be adjudicator requires identity info #construct list of pubkeys escrowList = self.getEscrowList() pubkeyList = [x[2] for x in escrowList] mypub, mypriv = multisig.getKeysFromUniqueID(self.uniqID()) pubkeyList.append(mypub) pubkeyList.sort() shared.debug(0, ["Here are the pubkeys:", pubkeyList]) #majority M of N total: N = len(pubkeyList) if N > 15: #TODO magic number shared.debug(0, [ "Critical error: the total number of keys in this multisig address exceeds the maximum of 17 - aborting" ]) return None, None M = int(math.floor(N / 2) + 1) msigaddr, mscript = multisig.createMultisigRaw(M, N, pubkeyList) return (msigaddr, mscript)
def payInitialFees(self): #Working in mbtc here. txf = shared.defaultBtcTxFee if not self.workingContract: raise Exception("Tried to pay fees but there's no active contract") btc = int(self.workingContract.text['mBTC Amount']) bd = int(self.workingContract.text['Buyer Deposit Fee']) sd = int(self.workingContract.text['Seller Deposit Fee']) bf = int(self.workingContract.text['Buyer Escrow Fee']) sf = int(self.workingContract.text['Seller Escrow Fee']) #get our role role = 'buyer' if self.workingContract.text[ 'Buyer BTC Address'] == self.uniqID() else 'seller' if role == 'buyer': feeToPay = bd + bf else: #TODO is it either feasible or desirable for the seller to pay 'btc' here? Probably not. feeToPay = sd + sf #check that we hold sufficient funds; values are returned in btc c, u = multisig.get_balance_lspnr(self.uniqID()) #the extra 0.5 fee to be paid is to allow for cost of transfer from cne to re if feeToPay + math.ceil(txf * 1.5) >= c: shared.debug(0,["Cannot pay - insufficient funds. Confirmed funds:",\ str(c),",unconfirmed:",str(u),"while fee is: ",str(feeToPay+math.ceil(txf*1.5))]) return #we use any utxos, so "payers" is None feeSpendingTxHash = multisig.spendUtxos(self.uniqID(),self.uniqID(),\ multisig.pubtoaddr(g("Escrow","escrow_pubkey")),None,amt=feeToPay+txf) if not feeSpendingTxHash: shared.debug(0, [ "Error, cannot pay the fee because of insufficient funds at address:", myBtcAddress ]) return False else: return feeSpendingTxHash
def run(self,escrowRole='cne'): #the main loop to be called for the daemon meaning we're #listening for messages/requests/instructions from useragents. if escrowRole=='re': self.runRE() return elif escrowRole != 'cne': shared.debug(0,["Error! Role must be cne or re"]) exit(1) while True: #deal with transactions self.takeAppropriateActions() msg = self.getSingleMessage(5,prefix='CNE') if not msg: shared.debug(0,["Got nothing, waiting.."]) continue k,m = msg.items()[0] txID, requester = k.split('.') if 'ADJUDICATOR_APPLICATION:' in m: self.writeAdjudicatorApplication(':'.join(m.split(':')[1:]),requester) continue if 'CNE_SIGNED_CONTRACT:' in m: verdict,data,contract = self.receiveContractCNE([k,m]) self.sendContractVerdictCNE(verdict,data,contract) continue #TODO: this code is identical on RE and CNE; #how to avoid replicating? if 'QUERY_STATUS:' in m: queryee = m.split(':')[1] self.sendMessage('QUERY_STATUS:'+requester, recipientID=queryee) if 'QUERY_STATUS_RESPONSE:' in m: rspns,ctrprty = m.split(':')[1].split(',') self.sendMessage('QUERY_STATUS_RESPONSE:'+rspns, recipientID=ctrprty)
def get_ssl_hashes_from_capfile(capfile,port=-1,stream='',options=[],frames=[]): frames_str='' ssl_frames=[] #Run tshark to get a list of frames with ssl app data in them #EDITED to test escrow filterstr = build_filterstr(stream=stream,port=port) if (frames): ssl_frames=frames else: try: frames_str = tshark(capfile,field='frame.number', \ filter= filterstr,options=options) except: #this could be caused by a corrupt file from stcppipe #or by a malformed query string,etc. - but in the former #case we should NOT exit, hence this approach shared.debug(0,["tshark failed - see stderr for message"]) shared.debug(0,["return code from tshark: ",frames_str]) return None ssl_frames = shared.pisp(frames_str) #gracefully handle null result (i.e. blank tshark output): ssl_frames = filter(None,ssl_frames) if not ssl_frames: return None #Now we definitely have ssl frames in this capture file shared.debug(1,['need to process this many frames:', len(ssl_frames)]) ssl_app_data = tshark(capfile,field='ssl.app_data',frames=ssl_frames,\ options=options) #ssl.app_data will return all encrypted segments separated by commas #but also, lists of segments from different frames will be separated by #platform dependent newlines ssl_app_data_list = shared.pisp(ssl_app_data.replace(',',shared.PINL)) #remove any blank OR duplicate entries in the ssl app data list ssl_app_data_list = filter(None,list(set(ssl_app_data_list))) shared.debug(4,["Full dump of ssl application data:\n",ssl_app_data_list]) shared.debug(1,["Length of list of ssl segments for file ",capfile," was: " \ ,len(ssl_app_data_list)]) return get_ssl_hashes_from_ssl_app_data_list(ssl_app_data_list)
def getSingleMessage(self,timeout=1,chanIndex=0,prefix=None,external=False): qname = prefix+self.uniqID() if prefix else self.uniqID() msg = Msg.getSingleMessage(qname,timeout,chanIndex,external) if not msg: shared.debug(5,["Message layer returned none"]) return None #all messages must be verified shared.logToFile(g("Directories","agent_base_dir"),"Signed message received:"+msg.keys()[0]+':'+msg.values()[0]+ " at time:"+str(time.time())) sendingID = msg.keys()[0].split('.')[1] #retrieve pubkey msgInner = ';'.join(':'.join(msg.values()[0].split(':')[1:]).split(';')[:-1]) sig = ':'.join(msg.values()[0].split(':')[1:]).split(';')[-1] addr = multisig.pubtoaddr(multisig.ecdsa_recover(msgInner,sig)) if not addr == sendingID: #don't add anything but failure to message to prevent leaks shared.debug(0,["Verification failure",sendingID,chanIndex]) self.sendMessage({msg.keys()[0]:'VERIFICATION_FAILED:'},sendingID,chanIndex) return None else: #having checked the message signature, dispose of it v = ';'.join(msg.values()[0].split(';')[:-1]) msg = {msg.keys()[0]:v} shared.debug(4,["Returning this message:",msg]) return msg
def startFirefox(self): #if not os.path.isdir(os.path.join(datadir, 'firefox')): # os.mkdir(os.path.join(datadir, 'firefox')) ffdir = shared.makedir([self.baseDir,'firefox']) #touch files for fn in ['stdout','stderr']: open(os.path.join(ffdir,'firefox.'+fn), 'w').close() if not os.path.isfile(os.path.join(ffdir,'FF-profile', 'extensions.ini')): #FF rewrites extensions.ini on first run, so we allow FF to create it, #then we kill FF, rewrite the file and start FF again try: self.ff_proc = subprocess.Popen([g("Exepaths","firefox_exepath"),\ '-no-remote','-profile', os.path.join(ffdir, 'FF-profile')], \ stdout=open(os.path.join(ffdir,"firefox.stdout"),'w'), \ stderr=open(os.path.join(ffdir, "firefox.stderr"),'w')) except Exception,e: shared.debug(0,["Error starting Firefox"]) return ["Error starting Firefox"] while 1: time.sleep(0.5) if os.path.isfile(os.path.join(ffdir,'FF-profile', 'extensions.ini')): self.ff_proc.kill() break try: #enable extension with codecs.open (os.path.join(ffdir, 'FF-profile', \ 'extensions.ini'), "w") as f1: f1.write("[ExtensionDirs]\nExtension0=" + \ os.path.join(ffdir, 'FF-profile',\ "extensions", "lspnr@lspnr") + "\n") #show addon bar with codecs.open(os.path.join(ffdir, \ 'FF-profile', 'localstore.rdf'), 'w') as f2: f2.write('<?xml version="1.0"?><RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#" xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><RDF:Description RDF:about="chrome://browser/content/browser.xul"><NC:persist RDF:resource="chrome://browser/content/browser.xul#addon-bar" collapsed="false"/></RDF:Description></RDF:RDF>') except Exception,e: shared.debug(0,['File open error']) return False