From ea8f5883224a789917b16f88cfb4b3ebf78bcf86 Mon Sep 17 00:00:00 2001 From: etotheipi Date: Sun, 26 Feb 2012 15:47:29 -0500 Subject: [PATCH] Challenge-response dialog started... --- ArmoryQt.py | 2 + img/arrow_down32.png | Bin 0 -> 1263 bytes img/arrow_left32.png | Bin 0 -> 1299 bytes img/arrow_right32.png | Bin 0 -> 1280 bytes img/arrow_up32.png | Bin 0 -> 1268 bytes qtdialogs.py | 256 ++++++++++++++++++++++++++++++++++++++---- 6 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 img/arrow_down32.png create mode 100644 img/arrow_left32.png create mode 100644 img/arrow_right32.png create mode 100644 img/arrow_up32.png diff --git a/ArmoryQt.py b/ArmoryQt.py index cd9471366..eeb2df7d2 100644 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -379,7 +379,9 @@ def chngDev(b): actSetModeDev.setChecked(True) actOpenTools = self.createAction('&Calculator', lambda: DlgECDSACalc(self,self).exec_()) + actOwnership = self.createAction('&Prove Ownership', lambda: DlgOwnership(self,self).exec_()) self.menusList[MENUS.Tools].addAction(actOpenTools) + self.menusList[MENUS.Tools].addAction(actOwnership) reactor.callLater(0.1, self.execIntroDialog) diff --git a/img/arrow_down32.png b/img/arrow_down32.png new file mode 100644 index 0000000000000000000000000000000000000000..0a798a72b7c254ac5882b67b886c1d7e44aa0f05 GIT binary patch literal 1263 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L07=pS07=pTs6~qS00007bV*G`2iyW0 z1T_k&p%EJZ00eJIL_t(o!|j&;YgSbp$6udw?(`E*)UlX$l;?XMBZ87Kc9Jp7zY zB2Az0N%6E!;Bz1g`gA zq50C!gf5F8X2^ya{4j$b4iOALy9_1M9z`|(tupy{6^s~+SVTUB z0H7#3>di<)aiI4LKoyNGQj2QI!3te~h}<85k(I|FIrZ?S2hN}L?>Z?DICZGH5r7Xf$1U$qA`Wg-Y^V*Wd{5Hl^OqKSHUT22f>WO` z3~(Bv;=m|^!x%wur2KA7LJ>6>Bg~ysrkg*G_C*0BaOvs=!Z29eP#d(42~btzp{fgq zvV&LO+3=yZb~Z{BTt0txoj`09bVe0tl=M(H>7WPe34#Hq^kmyHQ$<;^PRpIn+jP_BtXm67{M$KpkkP zxKNp5r?aeN9pBdN`ugNfw%kd}-sxq>aXP}X%YPLYm2Q|_I*0yjH)T z(_HC4jT#^82{s{EYcQT*tso{AH=r8w!h$z--~K&+mzK;~H1nAh4YhavL+p6R>51*` zP*?G>6>BRtF1na%Vj#Q`tvnI0J|TE9L?kXa0;=b|ravC6qrbmzL4Ue0J(l={$8lAS zM?-Bu^#=uK-`M!}lSi+9DaMoATrUPh!6^hmz~bW7dZwXGPM`a2#fcp>-}QZY*m z>NF4N+Pv}ghmUsbmP8_fO+*03Mi&DYI7$niAaijR2M^b8J@qMH-IGk%P#Z8Xm<|R8 z)8z;1zL%=Go79OP?YWI&!SjTNrp#jIz!H8sT)!h<@o{4yx3o1n+8SMI`jWo-Y|6d$ zba{n#4W1WkEh2)IBu^z@<;!nAJ92U-nf67d{nyc5EX5tzwQR*(Zxbo~@Zx(k1=cxmO>i#FJC#X zH_=Bxx#V{Iv7cW72lzlRf%y{w$fy50eDi?h2;~Z!_}j2)4uH$4b78`8XP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L07=pS07=pTs6~qS00007bV*G`2iyW0 z1T`RX8raPM00fgsL_t(o!|j)Ah*eb-$A4>|bMK6eI;Ey$8kv!)L1~(ggdpvKWQq2{ zMhe6-LP=9Bi2C5i48)5j_xTiU>tYQF5{z-;v{J%=j8-9G$uMoU_;J!#y)| zvl$(o31uqqo`44~ z?C(VChYT7v@oN2CH33r_-Mg%B{fhaY3@9(_$Nx%T+^fvE^Y(kH-c#iTxc%Q$tu7B$6dOnI+cId;6(4j-!)Fbo5;lv8>Q9IV;%_0BcS zx*`ITmxW}4Y?-%Y|MH5}LmT6IF60|AQ@kc99w#AvN^e2c<1H!Nw}eDvR1;#2xO>nT zR;*k8)o<&VeKE$Rr-w1Dq_lMJvN`XT{(0tC5{)nnHN)_zd17s)rTHvgV-jBg_H9;U zBA?^7n}>G^(ItWLi+N=5z&p0Q^vs)9UAI*P!7xMsF%%D?n0O42na7;REWs0_BzQcE z$JBSP3EP@v>>?ICFk-@%7oJ*Tza0BcVxK3{Je~+o9xr*+C8�i7^*p&V%!qD2U=M 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 e5x;K#fBOfzXazj+pm3J}00000 else 1 for a in self.keyTxtList] allEmpty = not sum(totalEmpty)!=0 - self.btnSignMsg.setEnabled(not allEmpty) + #self.btnSignMsg.setEnabled(not allEmpty) return allEmpty ############################################################################# @@ -6564,6 +6569,21 @@ def keyWaterfall(self): return elif len(addrB58)>0: try: + raw25byte = base58_to_binary(addrB58) + if len(raw25byte)!=25: + QMessageBox.critical(self, 'Invalid Address', \ + 'The Bitcoin address supplied is invalid.', QMessageBox.Ok) + return + data,chk = raw25byte[:21], raw25byte[21:] + fixedData = verifyChecksum(data,chk) + if fixedData!=data: + if len(fixedData)==0: + QMessageBox.critical(self, 'Invalid Address', \ + 'The Bitcoin address has an error in it. Please double-check ' + 'that it was entered properly.', QMessageBox.Ok) + return + self.txtAddr.setText(hash160_to_addrStr(fixedData[1:])) + a160Bin = addrStr_to_hash160(addrB58) self.txtHash.setText(binary_to_hex(a160Bin)) except: @@ -6571,8 +6591,8 @@ def keyWaterfall(self): 'Address data is not recognized!', QMessageBox.Ok) return - self.btnSignMsg.setEnabled( len(privBin)>0 ) - self.btnVerify.setEnabled( len(pubfBin)>0 ) + #self.btnSignMsg.setEnabled( len(privBin)>0 ) + #self.btnVerify.setEnabled( len(pubfBin)>0 ) for txt in self.keyTxtList: txt.setCursorPosition(0) @@ -6581,6 +6601,7 @@ def keyWaterfall(self): + ############################################################################# def checkIfAddrIsOurs(self, addr160): wltID = self.main.getWalletForAddr160(addr160) if wltID=='': @@ -6592,6 +6613,7 @@ def checkIfAddrIsOurs(self, addr160): return wltID + ############################################################################# def getOtherData(self): ''' Look in your wallets for the address, fill in pub/priv keys ''' @@ -6636,9 +6658,6 @@ def clearFormData(self): wdgt.setText('') self.lblPrivType.setText('') self.lblTopMid.setText('') - self.btnWltData.setEnabled(False) - self.btnSignMsg.setEnabled(False) - self.btnVerify.setEnabled(False) ############################################################################# def privSwitch(self): @@ -6649,7 +6668,7 @@ def privSwitch(self): ############################################################################# def makeChallenge(self): rnd = SecureBinaryData().GenerateRandom(16) - msgToBeSigned = 'SignThisMessage_%s' % rnd.toHexStr() + msgToBeSigned = 'SignThisMessage%s' % rnd.toHexStr() self.txtMsg.setText(msgToBeSigned) @@ -6726,6 +6745,201 @@ def verifyMsg(self): +################################################################################ +class DlgOwnership(QDialog): + def __init__(self, parent=None, main=None): + super(DlgOwnership, self).__init__(parent) + + + self.radioCreateChallenge = QRadioButton('I want someone to prove they own an address') + self.radioCreateResponse = QRadioButton('I need to prove that I own an address') + self.radioVerifyResponse = QRadioButton('Some gave me a proof-of-ownership I need to verify') + btngrp = QButtonGroup(self) + btngrp.addButton(self.radioCreateChallenge) + btngrp.addButton(self.radioCreateResponse) + btngrp.addButton(self.radioVerifyResponse) + btngrp.setExclusive(True) + self.radioCreateChallenge.setChecked(True) + + self.connect(self.radioCreateChallenge, SIGNAL('clicked(bool)'), self.changeStack) + self.connect(self.radioCreateResponse, SIGNAL('clicked(bool)'), self.changeStack) + self.connect(self.radioVerifyResponse, SIGNAL('clicked(bool)'), self.changeStack) + + + ## Create the stacked widget + self.challengeStack = QStackedWidget() + dispFont = GETFONT('Var', 8) + w,h = relaxedSizeNChar(dispFont, 40) + wmin = 250 + + ## Create-Challenge options + ccWidget = QWidget() + ccLayout = QGridLayout() + + self.txtCCDescr = QRichLabel( \ + 'Since all transactions are public but identities are not tied to ' + 'the addresses, you may need to "challenge" someone to prove that ' + 'they own a particular Bitcoin address (perhaps you received money ' + 'from that address, and the other person claims it was them who ' + 'sent it to you). Enter the address to be verified, and then click ' + 'the button to create a "challenge." The challenge can be emailed ' + 'to the other party') + self.txtCCAddress = QLineEdit() + self.txtCCAddress.setMinimumWidth(wmin) + self.txtCCAddress.sizeHint = lambda: QSize(w,h) + self.txtCCAddress.setFont(dispFont) + + # Only advanced users should be able to specify a custom string + self.txtCCCustomStr = QLineEdit() + self.txtCCCustomStr.setMinimumWidth(wmin) + self.txtCCCustomStr.sizeHint = lambda: QSize(w,h) + self.txtCCCustomStr.setFont(dispFont) + self.txtCCCustomStr.setEnabled(False) + + self.txtCCChallenge = QTextEdit() + self.txtCCChallenge.setMinimumWidth(wmin) + self.txtCCChallenge.sizeHint = lambda: QSize(w,int(3.1*h)) + self.txtCCChallenge.setFont(dispFont) + + self.lblCCcopied = QRichLabel('') + self.btnCCCreate = QPushButton('Create Challenge') + + + c1 = self.makeChallenge('1'+'abc'*11, \ + 'Feb 28, 12:38.1839083290 UTC ' + 'BitcoinXYZ'*12, \ + '\xab\xcd\x12\x34'*8, + '\xab\xcd\x12\x34'*8) + c2 = self.makeChallenge('1'+'abc'*11, \ + 'Feb 28, 12:38.1839083290 UTC ' + 'BitcoinXYZ'*12) + + print c1, '\n\n', c2, '\n\n' + + print self.readChallenge(c1), '\n\n' + print self.readChallenge(c2), '\n\n' + + + + + + def changeStack(self, dummy): + """ + Dummy argument is only to take the bool from 'clicked(bool)', but we + don't actually use it + """ + if self.radioCreateResponse.isChecked(): + self.challengeStack.setCurrentIndex(1) + elif self.radioVerifyResponse.isChecked(): + self.challengeStack.setCurrentIndex(2) + else: + # We put this last to be default in case somehow nothing is checked + self.challengeStack.setCurrentIndex(0) + + + def readChallenge(self, fullPacket): + addrB58, challengeStr, pubkey, sig = '','','','' + lines = fullPacket.split('\n') + readingChallenge, readingPub, readingSig = False, False, False + for i in range(len(lines)): + s = lines[i].strip() + + # ADDRESS + if s.startswith('Addr'): + addrB58 = s.split(':')[-1].strip() + + # CHALLENGE STRING + if s.startswith('Chal') or readingChallenge: + readingChallenge = True + if s.startswith('Pub') or s.startswith('Sig') or ('END-CHAL' in s): + readingChallenge = False + else: + # Challenge string needs to be exact, grab what's between the + # double quotes, no newlines + iq1 = s.index('"') + 1 + iq2 = s.index('"', iq1) + challengeStr += s[iq1:iq2] + + # PUBLIC KEY + if s.startswith('Pub') or readingPub: + readingPub = True + if s.startswith('Sig') or ('END-CHAL' in s): + readingPub = False + else: + pubkey += s.split(':')[-1].strip().replace(' ','') + + # SIGNATURE + if s.startswith('Sig') or readingSig: + readingSig = True + if 'END-CHAL' in s: + readingSig = False + else: + sig += s.split(':')[-1].strip().replace(' ','') + + + if len(pubkey)>0: + try: + print pubkey, len(pubkey) + pubkey = hex_to_binary(pubkey) + if len(pubkey) not in (32, 33, 64, 65): raise + except: + QMessageBox.critical(self, 'Bad Public Key', \ + 'Public key data was not recognized', QMessageBox.Ok) + pubkey = '' + + if len(sig)>0: + try: + sig = hex_to_binary(sig) + except: + QMessageBox.critical(self, 'Bad Signature', \ + 'Signature data is malformed!', QMessageBox.Ok) + sig = '' + + return addrB58, challengeStr, pubkey, sig + + + def makeChallenge(self, addrB58, challengeStr, binPubkey='', binSig=''): + s = '-----BEGIN-CHALLENGE--------------------------------\n' + + ### Address ### + s += 'Address: %s\n' % addrB58 + + lineWid = 32 + ### Challenge ### + nChallengeLines = (len(challengeStr)-1)/lineWid + 1 + for i in range(nChallengeLines): + cLine = 'Challenge: "%s"\n' if i==0 else ' "%s"\n' + s += cLine % challengeStr[i*lineWid:(i+1)*lineWid] + + if len(binPubkey)>0: + hexPub = binary_to_hex(binPubkey) + prefix = ' ' + if len(binPubkey)%32==1: + prefix,hexPub = hexPub[:2], hexPub[2:] + + nPubLines = (len(hexPub)-1)/lineWid + 1 + for i in range(nPubLines): + idx0, idx1 = i*lineWid, (i+1)*lineWid + if i==0: + s += 'PublicKey: %s %s\n' % (prefix, hexPub[idx0:idx1]) + else: + s += ' %s\n' % ( hexPub[idx0:idx1]) + + if len(binSig)>0: + hexSig = binary_to_hex(binSig) + prefix = ' ' + if len(binSig)%32==1: + prefix,hexSig = hexSig[:2], hexSig[2:] + + nSigLines = (len(hexSig)-1)/lineWid + 1 + for i in range(nSigLines): + idx0, idx1 = i*lineWid, (i+1)*lineWid + if i==0: + s += 'Signature: %s %s\n' % (prefix, hexSig[idx0:idx1]) + else: + s += ' %s\n' % ( hexSig[idx0:idx1]) + + s += '-----END-CHALLENGE----------------------------------' + return s + ################################################################################ ################################################################################