From 5b04f829dd141d39aef5dda20739bcaafee3a23a Mon Sep 17 00:00:00 2001 From: etotheipi Date: Tue, 22 Nov 2011 18:16:24 -0500 Subject: [PATCH] Deterministic addresses underway (but broke AES) --- README | 8 +- btcarmoryengine.py | 273 ++++++++++++++---- cppForSwig/EncryptionUtils.h | 7 +- cryptoTimings.txt | 76 +++-- .../scriptEvalStackState.txt | 0 .../testnetNonStdScript.txt | 0 swigPbeEngine.py | 18 -- unittest.py | 31 +- 8 files changed, 299 insertions(+), 114 deletions(-) rename scriptEvalStackState.txt => extras/scriptEvalStackState.txt (100%) rename testnetNonStdScript.txt => extras/testnetNonStdScript.txt (100%) delete mode 100644 swigPbeEngine.py diff --git a/README b/README index d54ddc220..c00d82fd7 100755 --- a/README +++ b/README @@ -14,11 +14,11 @@ * Descr: This file serves as an engine for python-based Bitcoin software. * I forked this from my own project -- PyBtcEngine -- because I * I needed to start including/rewriting code to use CppBlockUtils -* but did not want to break the pure-python-ness of PyBtcEngine. +* but did not want to break the pure-python methods in PyBtcEngine. * If you are interested in in a pure-python set of bitcoin utils * please go checkout the PyBtcEngine github project. * -* Of course, the biggest advatage here is that you have access to +* The biggest advatage of using SWIG is that you have access to * the blockchain through BlockObj/BlockObjRef/BlockUtils, as found * in the CppForSWIG directory. This is available in PyBtcEngine, * but I had to split out the modules, and I didn't have a good way @@ -27,6 +27,10 @@ * * ***NOTE*** This is still the Full-RAM implementation, which * requires holding the *entire* blockchain in memory. +* This is not a problem for newer computers, which +* usually have 4GB to 16GB of RAM, and this only uses +* 1.1 GB to hold the blockchain. +* * In the future, I plan to try to make this more * lightweight, but I am going to forego that yet * in favor of making a utility that works for some diff --git a/btcarmoryengine.py b/btcarmoryengine.py index c9787f876..23c41b027 100755 --- a/btcarmoryengine.py +++ b/btcarmoryengine.py @@ -166,6 +166,11 @@ def coin2str(nSatoshi, ndec=8, rJust=False): MIN_TX_FEE = 50000 MIN_RELAY_TX_FEE = 10000 +UINT8_MAX = 2**8-1 +UINT16_MAX = 2**16-1 +UINT32_MAX = 2**32-1 +UINT64_MAX = 2**64-1 + # Define all the hashing functions we're going to need. We don't actually # use any of the first three directly (sha1, sha256, ripemd160), we only # use hash256 and hash160 which use the first three to create the ONLY hash @@ -699,18 +704,46 @@ class PyBtcAddress(object): """ PyBtcAddress -- - Encapsulate an address, regardless of whether it includes the - private key or just an address we've seen on the network. - - Having the privateKey is the most data. Public key is next - Finally, you frequently just have someone's address, without - even having their public key. + This class encapsulated EVERY kind of address object: + -- Full, plaintext private-key-bearing addresses + -- Encrypted private key addresses, with AES locking and unlocking + -- Watching-only public-key addresses + -- Address-string-only storage, representing someone else's key + -- Deterministic address generation from previous addresses + + For deterministic wallets, new addresses will be created from a chaincode + and the previous address. What is implemented here is a special kind of + deterministic calculation that actually allows the user to securely + generate new addresses even if they don't have the private key. This + method uses Diffie-Hellman shared-secret calculations to produce the new + keys, and has the same level of security as all other ECDSA operations. + There's a lot of fantastic benefits to doing this: + + (1) If all addresses in wallet are chained, then you only need to backup + your wallet ONCE -- when you first create it. Print it out, put it + in a safety-deposit box, or tattoo the generator key to the inside + of your eyelid: it will never change. - The "createFrom" methods actually calculate the data below it - The serialize/unserialize methods do no extra calculation, or - consistency checks, because the lisecdsa library is slow, and - we don't want to spend the time verifying thousands of keypairs - if we've precomputed them before. + (2) You can keep your private keys on an offline machine, and keep a + watching-only wallet online. You will be able to generate new + keys/addresses, and verify incoming transactions, without ever + requiring your private key to touch the internet. + + (3) If your friend has the chaincode and your first public key, they + too can generate new addresses for you -- allowing them to send + you money multiple times, with different addresses, without ever + needing to specifically request the addresses. + (the downside to this is if the chaincode is compromised, all + chained addresses become de-anonymized -- but is only a loss of + privacy, not security) + + However, we do require some fairly complicated logic, due to the fact + that a user with a full, private-key-bearing wallet, may try to generate + a new key/address without supplying a passphrase. If this happens, the + wallet logic gets very complicated -- we don't want to reject the request + to generate a new address, but we can't compute the private key until the + next time the user unlocks their wallet. Thus, we have to save off the + data they will need to create the key, to be applied on next unlock. """ ############################################################################# def __init__(self): @@ -734,14 +767,40 @@ def __init__(self): self.walletByteLoc = -1 self.chainIndex = 0 + # Information to be used by C++ to know where to search for transactions + # in the blockchain + # [unixTime, blkNum] + self.timeRange = [UINT32_MAX, 0] + self.blkRange = [UINT32_MAX, 0] + + # This feels like a hack, but it's the only way I can think to handle + # the case of generating new, chained addresses, even without the + # private key currently in memory. i.e. - If we can't unlock the priv + # key when creating a new chained priv key, we will simply extend the + # public key, and store the last-known chain info, so that it can be + # generated the next time the address is unlocked + self.createPrivKeyNextUnlock = False + self.createPrivKeyNextUnlock_IVandKey = (None, None) # (IV,Key) + self.createPrivKeyNextUnlock_ChainDepth = -1 + self.createPrivKeyNextUnlock_Chaincode = -1 + ############################################################################# def isInitialized(self): + """ Keep track of whether this address has been initialized """ return self.isInitialized ############################################################################# def hasPrivKey(self): + """ + We have a private key if either the plaintext, or ciphertext private-key + fields are non-empty. We also consider ourselves to "have" the private + key if this address was chained from a key that has the private key, even + if we haven't computed it yet (due to not having unlocked the private key + before creating the new address). + """ return (self.binPrivKey32_Encr.getSize() != 0 or \ - self.binPrivKey32_Plain.getSize() != 0 ) + self.binPrivKey32_Plain.getSize() != 0 or \ + self.createPrivKeyNextUnlock) ############################################################################# def hasPubKey(self): @@ -756,6 +815,39 @@ def getAddrStr(self, netbyte=ADDRBYTE): def getAddr160(self): return self.addStr20 + ############################################################################# + def touch(self, unixTime=None, blkNum=None): + """ + Just like "touching" a file, this makes sure that the firstSeen and + lastSeen fields for this address are updated to include "now" + """ + if unixTime==None: + unixTime = time.time() + + self.timeRange[0] = min(self.timeRange[0], unixTime) + self.timeRange[1] = max(self.timeRange[1], unixTime) + + if not blkNum==None: + self.blkRange[0] = min(self.blkRange[0], blkNum) + self.blkRange[1] = max(self.blkRange[1], blkNum) + + + ############################################################################# + def isAddressUsed(self): + isUntouched = self.timeRange[0]==UINT32_MAX and \ + self.timeRange[1]==UINT32_MAX and \ + self.blkRange[0]==UINT32_MAX and \ + self.blkRange[1]==UINT32_MAX + return not isUntouched + + ############################################################################# + def getTimeRange(self): + return (firstSeen[0], lastSeen[0]) + + ############################################################################# + def getBlockNumRange(self): + return (firstSeen[1], lastSeen[1]) + ############################################################################# def SerializePublicKey(self): """Converts the SecureBinaryData public key to a 65-byte python string""" @@ -782,6 +874,9 @@ def SerializePlainPrivateKey(self): ############################################################################# def verifyEncryptionKey(self, secureKdfKey): + """ + Determine if this data is the decryption key for this encrypted address + """ if not self.useEncryption or not self.hasPrivKey(): return False @@ -804,10 +899,25 @@ def verifyEncryptionKey(self, secureKdfKey): return verified + ############################################################################# def setWalletLocByte(self, byteOffset): self.walletByteLoc = byteOffset + ############################################################################# + def setInitializationVector(self, IV16=None, random=False): + """ + Either set the IV through input arg, or explicitly call random=True + Returns the IV -- which is especially important if it is randomly gen + """ + if not IV16==None: + self.binInitVect16 = SecureBinaryData(IV16) + elif random==True: + self.binInitVect16 = SecureBinaryData().GenerateRandom(16) + else: + raise KeyDataError, 'setInitializationVector: set IV, or random=True' + return self.binInitVect16 + ############################################################################# def createFromEncryptedKeyData(self, addr20, encrPrivKey32, IV16, \ @@ -818,7 +928,7 @@ def createFromEncryptedKeyData(self, addr20, encrPrivKey32, IV16, \ self.__init__() self.addrStr20 = addr20 self.binPrivKey32_Encr = SecureBinaryData(encrPrivKey32) - self.binInitVect16 = SecureBinaryData(IV16) + self.setInitializationVector(IV16) self.isLocked = True self.useEncryption = True self.isInitialized = True @@ -830,7 +940,7 @@ def createFromEncryptedKeyData(self, addr20, encrPrivKey32, IV16, \ raise KeyDataError, "Public key does not match supplied address" ############################################################################# - def createFromPlainKeyData(self, addr20, plainPrivKey, IV16, \ + def createFromPlainKeyData(self, addr20, plainPrivKey, IV16=None, \ chkSum=None, pubKey=None, \ skipCheck=False, skipPubCompute=False): assert(len(plainPrivKey)==32) @@ -839,7 +949,7 @@ def createFromPlainKeyData(self, addr20, plainPrivKey, IV16, \ self.isInitialized = True self.binPrivKey32_Plain = SecureBinaryData(plainPrivKey) - if not len(IV16)==16: + if IV16==None or not len(IV16)==16: # No IV means no encryption if not len(IV16)==0: # It looks like we tried to specify an IV, but an invalid one @@ -865,13 +975,16 @@ def createFromPlainKeyData(self, addr20, plainPrivKey, IV16, \ elif not skipPubCompute: # No public key supplied, but we do want to calculate it self.binPublicKey65 = CryptoECDSA().ComputePublicKey(plainPrivKey) + + return self ############################################################################# - def lock(self, secureKdfKey=None): + def lock(self, secureKdfKey=None, generateIVIfNecessary=False): # We don't want to destroy the private key if it's not supposed to be # encrypted. Similarly, if we haven't actually saved the encrypted # version, let's not lock it + newIV = False if not self.useEncryption or not self.hasPrivKey(): # This isn't supposed to be encrypted, or there's no privkey to encrypt return @@ -884,9 +997,11 @@ def lock(self, secureKdfKey=None): # Addr should be encrypted, but haven't yet encrypted priv key if secureKdfKey!=None: # We have an encryption key, use it - if self.binInitVect16.getSize() < 16: - # Generate a new IV if we need one + if self.binInitVect16.getSize() < 16 and not generateIVIfNecessary: + raise KeyDataError, 'No Initialization Vector available' + else: self.binInitVect16 = SecureBinaryData().GenerateRandom(16) + newIV = True # Finally execute the encryption self.binPrivKey32_Encr = CryptoAES().Encrypt( \ @@ -902,6 +1017,9 @@ def lock(self, secureKdfKey=None): "encrypted key data is available, and no " "encryption key provided to encrypt it.") + # In case we changed the IV, we should let the caller know this + return self.binInitVect16 if newIV else SecureBinaryData() + ############################################################################# def unlock(self, secureKdfOutput, skipCheck=False): @@ -911,17 +1029,42 @@ def unlock(self, secureKdfOutput, skipCheck=False): best if that AES key is actually derived from "heavy" key-derivation function. """ - if not self.useEncryption or not self.isLocked or not self.hasPrivKey(): + if not self.useEncryption or not self.isLocked: + # Bail out if the wallet is unencrypted, or already unlocked self.isLocked = False return - if not self.binPrivKey32_Encr.getSize()==32: - raise WalletLockError, 'No encrypted private key to decrypt!' + + if self.createPrivKeyNextUnlock: + # This is SPECIFICALLY for the case that we didn't have the encr key + # available when we tried to extend our deterministic wallet, and + # generated a new address anyway + self.binPrivKey32_Plain = CryptoAES().Decrypt( \ + self.createPrivKeyNextUnlock_IVandKey[1], \ + SecureBinaryData(secureKdfResult), \ + self.createPrivKeyNextUnlock_IVandKey[0]) + + for i in range(self.createPrivKeyNextUnlock_ChainDepth): + self.binPrivKey32_Plain = CryptoECDSA().ComputeChainedPrivateKey( \ + self.binPrivKey32_Plain, \ + self.createPrivKeyNextUnlock_Chaincode) + + + # IV should have already been randomly generated, before + self.isLocked = False + self.createPrivKeyNextUnlock = False + self.createPrivKeyNextUnlock_IVandKey = [] + self.createPrivKeyNextUnlock_ChainDepth = 0 + self.createPrivKeyNextUnlock_Chaincode = '' + else: - if not self.binInitVect16.getSize()==16: - raise WalletLockError, 'Initialization Vect (IV) is missing!' + if not self.binPrivKey32_Encr.getSize()==32: + raise WalletLockError, 'No encrypted private key to decrypt!' - self.binPrivKey32_Plain = CryptoAES().Decrypt( \ + if not self.binInitVect16.getSize()==16: + raise WalletLockError, 'Initialization Vect (IV) is missing!' + + self.binPrivKey32_Plain = CryptoAES().Decrypt( \ self.binPrivKey32_Encr, \ SecureBinaryData(secureKdfResult), \ self.binInitVect16) @@ -930,12 +1073,13 @@ def unlock(self, secureKdfOutput, skipCheck=False): if not skipCheck: if not self.hasPubKey(): - self.binPublicKey65 = CryptoECDSA().ComputePublicKey(self.binPrivKey32_Plain) + self.binPublicKey65 = CryptoECDSA().ComputePublicKey(\ + self.binPrivKey32_Plain) else: - # We should usually check to make sure keys match, but may choose to skip - # if we have a lot of keys + # We should usually check that keys match, but may choose to skip + # if we have a lot of keys to load if not checkPubPrivKeyPairMatch(self.binPrivKey32_Plain, \ - self.binPublicKey65): + self.binPublicKey65): raise KeyDataError, "Stored public key does not match priv key!" @@ -956,6 +1100,9 @@ def changeEncryptionKey(self, secureOldKey, secureNewKey): if self.isLocked: self.unlock(secureOldKey, skipCheck=False) + # Keep the old IV if we are changing the key. IV reuse is perfectly + # fine for a new key, and might save us from disaster if we otherwise + # generated a new one and then forgot to take note of it. if secureNewKey==None: # If we chose not to re-encrypt, make sure we clear the encryption self.binInitVect16 = SecureBinaryData() @@ -965,7 +1112,6 @@ def changeEncryptionKey(self, secureOldKey, secureNewKey): else: # Re-encrypt with new key (using new, random IV) self.useEncryption = True - self.binInitVect16 = SecureBinaryData().GenerateRandom(16) self.lock(secureNewKey) self.isLocked = True @@ -1000,7 +1146,6 @@ def generateDERSignature(self, binMsg, secureKdfKey=None): self.unlock(secureKdfKey, skipCheck=False) try: - # With temporary unlock, we include the finally clause secureMsg = SecureBinaryData(binMsg) sig = CryptoECDSA().SignData(secureMsg, self.binPrivKey32_Plain) sigstr = sig.toBinStr() @@ -1019,7 +1164,8 @@ def generateDERSignature(self, binMsg, secureKdfKey=None): except: print 'Failed signature generation' finally: - # Always re-lock the address, but if locking throws an error, ignore + # Always re-lock/cleanup after unlocking, even after an exception. + # If locking triggers an error too, we will just skip it. try: if secureKdfKey!=None: self.lock(secureKdfKey) @@ -1060,7 +1206,7 @@ def verifyDERSignature(self, binMsgVerify, derSig): ############################################################################# - def createNewRandomAddress(self, secureKdfKey=None): + def createNewRandomAddress(self, secureKdfKey=None, IV16=None): """ This generates a new private key directly into a secure binary container and then encrypts it immediately if encryption is enabled and the AES key @@ -1077,7 +1223,9 @@ def createNewRandomAddress(self, secureKdfKey=None): self.isInitialized = True if secureKdfKey!=None: - self.binInitVect16 = SecureBinaryData().GenerateRandom(16) + self.binInitVect16 = IV16 + if IV16==None or IV16.getSize()!=16: + self.binInitVect16 = SecureBinaryData().GenerateRandom(16) self.lock(secureKdfKey) self.isLocked = True self.useEncryption = True @@ -1089,18 +1237,26 @@ def createNewRandomAddress(self, secureKdfKey=None): ############################################################################# def extendAddressChain(self, chaincode, secureKdfKey=None): + """ + We require some fairly complicated logic here, due to the fact that a + user with a full, private-key-bearing wallet, may try to generate a new + key/address without supplying a passphrase. If this happens, the wallet + logic gets mucked up -- we don't want to reject the request to + generate a new address, but we can't compute the private key until the + next time the user unlocks their wallet. Thus, we have to save off the + data they will need to create the key, to be applied on next unlock. + """ newAddr = PyBtcAddress() - - # TODO TODO TODO: technically we only need the public to extend the - # the chain and produce a new address, but the - # book-keeping gets out-of-whack if I try to create - # a priv-key-addr without a private key... need to - # figure out how to do this (otherwise, a watching-only - # wallet actually has more flexibility than an encrypted - # wallet for which we can't find the private key...) privKeyAvailButNotDecryptable = (self.hasPrivKey() and \ self.isLocked and \ secureKdfKey==None ) + + if self.chainIndex==-1: + # TODO: should we allow ANY address to be chained? Or only + # addresses that have their chainIndex explicitly set + # to zero? For now, we'll chain anything + pass + if self.hasPrivKey() and not privKeyAvailButNotDecryptable: # We are extending a chain of private-key-bearing addresses if self.useEncryption: @@ -1122,19 +1278,38 @@ def extendAddressChain(self, chaincode, secureKdfKey=None): self.lock(secureKdfKey) return newAddr else: - # We are extending a chain of public-key-only addresses - # TODO: this will get run if we couldn't unlock the private key + # We are extending the address based solely on its public key if not self.hasPubKey(): raise KeyDataError, 'No public key available to extend chain' - newPub = CryptoECDSA().ComputeChainedPublicKey( \ + newAddr.binPublicKey65 = CryptoECDSA().ComputeChainedPublicKey( \ self.binPublicKey65, chaincode) - newAddr = newAddrPub.getHash160() + newAddr.addrStr20 = newAddr.getHash160() + newAddr.useEncryption = self.useEncryption + newAddr.isInitialized = True newAddr.chainIndex = self.chainIndex+1 + + if privKeyAvailButNotDecryptable: - # TODO: again, figure out what to do here (or rather, how to - # handle other methods that might find it weird to have - # (useEncryption and not hasPrivKey()) - pass + # *** store what is needed to recover key on next addr unlock *** + newAddr.isLocked = True + newAddr.useEncryption = True + newAddr.binInitVect16 = SecureBinaryData().GenerateRandom(16) + newAddr.createPrivKeyNextUnlock = True + newAddr.createPrivKeyNextUnlock_Chaincode = chaincode + newAddr.createPrivKeyNextUnlock_IVandKey = [None,None] + if self.createPrivKeyNextUnlock: + # We are chaining from address also requiring gen on next unlock + newAddr.createPrivKeyNextUnlock_IVandKey[0] = \ + self.createPrivKeyNextUnlock_IVandKey[0].copy() + newAddr.createPrivKeyNextUnlock_IVandKey[1] = \ + self.createPrivKeyNextUnlock_IVandKey[1].copy() + newAddr.createPrivKeyNextUnlock_ChainDepth = \ + self.createPrivKeyNextUnlock_ChainDepth+1 + else: + # The address from which we are extending has already been generated + newAddr.createPrivKeyNextUnlock_IVandKey[0] = self.binInitVect16.copy() + newAddr.createPrivKeyNextUnlock_IVandKey[1] = self.binPrivKey32_Encr.copy() + newAddr.createPrivKeyNextUnlock_ChainDepth = 1 return newAddr diff --git a/cppForSwig/EncryptionUtils.h b/cppForSwig/EncryptionUtils.h index 199cfcf18..6a2969db0 100644 --- a/cppForSwig/EncryptionUtils.h +++ b/cppForSwig/EncryptionUtils.h @@ -146,9 +146,10 @@ class SecureBinaryData : public BinaryData // These methods are definitely inherited, but SWIG needs them here if they // are to be used from python - uint8_t const * getPtr(void) const { return BinaryData::getPtr(); } - uint8_t * getPtr(void) { return BinaryData::getPtr(); } - size_t getSize(void) const { return BinaryData::getSize(); } + uint8_t const * getPtr(void) const { return BinaryData::getPtr(); } + uint8_t * getPtr(void) { return BinaryData::getPtr(); } + size_t getSize(void) const { return BinaryData::getSize(); } + SecureBinaryData copy(void) const { return SecureBinaryData(getPtr(), getSize());} string toHexStr(bool BE=false) const { return BinaryData::toHexStr(BE);} string toBinStr(void) const { return BinaryData::toBinStr(); } diff --git a/cryptoTimings.txt b/cryptoTimings.txt index d3e5ea502..01685e21d 100644 --- a/cryptoTimings.txt +++ b/cryptoTimings.txt @@ -1,31 +1,6 @@ -################################################################################ -Testing Crypto++::AES timings -################################################################################ - AES Encryption with IV generation: 13299.5/sec - AES Encryption with supplied IV : 11507.8/sec - AES Decryption with supplied IV : 12124.1/sec - AES roundtrip, compare results: - Secret : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - Cipher : 5c24d6bdb56621b3dc42f29d01ba0c327e410367e43cc2e4d0d713d8a83c454e - Decrypt: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - Result : *** PASSED *** - - - -################################################################################ -Testing Crypto++::ECDSA timings -################################################################################ - PrivateKey --> PublicKey : 753.4/sec - PrivateKey --> Signature : 755.1/sec - PublicKey --> SigVerified : 130.8/sec - PrivateKey --> NextInChain : 14974.3/sec - PublicKey --> NextInChain : 109.0/sec - - - -################################################################################ +********************************************************************* Testing key-derivation function - timings and memory usage: -################################################################################ +********************************************************************* ***KDF 1: Default params*** ***KDF 2: 0.5s-1.0s timing, default mem*** ***KDF 3: 0.25s-0.5s timing, 256kB max*** @@ -33,25 +8,46 @@ Testing key-derivation function - timings and memory usage: Hash Function: sha512 Mem Required : 8.0 MB Num Iteration: 2 - Hex Salt Used: 8f953b5020995b464d7d5cbfabb3e... - Pass: "This is my password " --> Key: 1446c42ec06ffecf21b5199fe14df82f (0.183717 sec) - Pass: "This is my password." --> Key: 176484a5bbfe25522151e426d3853394 (0.174887 sec) - Pass: "This is my password " --> Key: 1446c42ec06ffecf21b5199fe14df82f (0.174840 sec) + Hex Salt Used: adad20a735fa53be53a4180001ec9... + Pass: "This is my password " --> Key: 2e623cf21e74a229b97a48ab05848567 (0.173542 sec) + Pass: "This is my password." --> Key: 20570bc64cec8e34ae220108589be512 (0.172990 sec) + Pass: "This is my password " --> Key: 2e623cf21e74a229b97a48ab05848567 (0.173402 sec) Testing KDF(2) Hash Function: sha512 Mem Required : 32.0 MB Num Iteration: 2 - Hex Salt Used: 64a37c25c00c9bd95fc8eefd77b7f... - Pass: "This is my password " --> Key: d041597c0e0a1fddf0d69cf57ab8ef50 (0.695917 sec) - Pass: "This is my password." --> Key: c3ab637b0e72f69ecce54d9fd9c009f8 (0.702719 sec) - Pass: "This is my password " --> Key: d041597c0e0a1fddf0d69cf57ab8ef50 (0.695516 sec) + Hex Salt Used: ceca667f029ad0a10d1ef3ab0944c... + Pass: "This is my password " --> Key: 859031ea1b754907db0de9a6f63f1891 (0.747265 sec) + Pass: "This is my password." --> Key: 07f9d8a93707ca5503469c05ef8fe9d9 (0.694215 sec) + Pass: "This is my password " --> Key: 859031ea1b754907db0de9a6f63f1891 (0.688132 sec) Testing KDF(3) Hash Function: sha512 Mem Required : 256.0 kB - Num Iteration: 47 - Hex Salt Used: c3843c1175bddfd1935d02a36244c... - Pass: "This is my password " --> Key: 7a525bbd64fdbc52497df31c8f5ec669 (0.165906 sec) - Pass: "This is my password." --> Key: 1af480216027b77b0a8eb6e4e2bf88ef (0.161794 sec) - Pass: "This is my password " --> Key: 7a525bbd64fdbc52497df31c8f5ec669 (0.165756 sec) + Num Iteration: 90 + Hex Salt Used: 0a888c7586609df3daf8f7dd1e2d5... + Pass: "This is my password " --> Key: 2125ce61ddae1656f3eb7c65a2628f11 (0.299631 sec) + Pass: "This is my password." --> Key: 54538eb4bba54abdc1af93eb97001a13 (0.326200 sec) + Pass: "This is my password " --> Key: 2125ce61ddae1656f3eb7c65a2628f11 (0.347052 sec) + +********************************************************************* +Testing Crypto++::AES timings +********************************************************************* + AES Encryption with IV generation: 15317.2/sec + AES Encryption with supplied IV : 16316.4/sec + AES Decryption with supplied IV : 9497.4/sec + AES roundtrip, compare results: + Secret : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + Cipher : 5c24d6bdb56621b3dc42f29d01ba0c327e410367e43cc2e4d0d713d8a83c454e + Decrypt: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + Result : *** PASSED *** +********************************************************************* +Testing Crypto++::ECDSA timings +********************************************************************* + PrivateKey --> PublicKey : 595.3/sec + PubPrivPair--> CheckMatch : 205.6/sec + PrivateKey --> Signature : 870.7/sec + PublicKey --> SigVerified : 129.9/sec + PrivateKey --> NextInChain : 16479.3/sec + PublicKey --> NextInChain : 110.8/sec diff --git a/scriptEvalStackState.txt b/extras/scriptEvalStackState.txt similarity index 100% rename from scriptEvalStackState.txt rename to extras/scriptEvalStackState.txt diff --git a/testnetNonStdScript.txt b/extras/testnetNonStdScript.txt similarity index 100% rename from testnetNonStdScript.txt rename to extras/testnetNonStdScript.txt diff --git a/swigPbeEngine.py b/swigPbeEngine.py deleted file mode 100644 index 594db7575..000000000 --- a/swigPbeEngine.py +++ /dev/null @@ -1,18 +0,0 @@ -################################################################################ -# -# Copyright (C) 2011, Alan C. Reiner -# Distributed under the GNU Affero General Public License (AGPL v3) -# See LICENSE or http://www.gnu.org/licenses/agpl.html -# -################################################################################ - - - - - - - - - - - diff --git a/unittest.py b/unittest.py index 939d1faad..a011efa37 100755 --- a/unittest.py +++ b/unittest.py @@ -9,9 +9,11 @@ Test_BasicUtils = True Test_PyBlockUtils = True Test_CppBlockUtils = True -Test_AddressSimple = True +Test_SimpleAddress = True +Test_EncryptedAddress = False Test_MultiSigTx = True Test_TxSimpleCreate = True +Test_SelectCoins = True Test_CryptoTiming = True @@ -159,7 +161,7 @@ def printpassorfail(abool): ################################################################################ ################################################################################ -if Test_AddressSimple: +if Test_SimpleAddress: # Execute the tests with Satoshi's public key from the Bitcoin specification page satoshiPubKeyHex = '04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284' @@ -216,6 +218,23 @@ def printpassorfail(abool): txoutA.binScript = '\x76\xa9\x14' + AddrA.getAddr160() + '\x88\xac' +################################################################################ +################################################################################ +if Test_EncryptedAddress: + # Create an address to user for all subsequent tests + privKey = SecureBinaryData(hex_to_binary('aa'*32)) + pubKey = CryptoECDSA().ComputePublicKey(privKey) + addr20 = pubKey.getHash160() + + # We pretend that we plugged some passphrases through a KDF + fakeKdfOutput1 = SecureBinaryData( hex_to_binary('11'*32) ) + fakeKdfOutput2 = SecureBinaryData( hex_to_binary('22'*32) ) + + testAddr = PyBtcAddress().createFromPlainKeyData(addr20, privKey) + testAddr = PyBtcAddress().createFromPlainKeyData(addr20, privKey, pubKey) + testAddr = PyBtcAddress().createFromPlainKeyData(addr20, privKey, pubKey, skipCheck=True) + testAddr = PyBtcAddress().createFromPlainKeyData(addr20, privKey, skipPubCompute=True) + ################################################################################ ################################################################################ @@ -427,6 +446,14 @@ def printpassorfail(abool): print ' PrivateKey --> PublicKey'.ljust(36), print ': %0.1f/sec' % (nTest/(end-start)) + # Check keypair match + start = time.time() + for i in range(nTest): + match = CryptoECDSA().CheckPubPrivKeyMatch(privKey, pubKey) + end = time.time() + print ' PubPrivPair--> CheckMatch'.ljust(36), + print ': %0.1f/sec' % (nTest/(end-start)) + # Test signing speed msg = SecureBinaryData( hex_to_binary('ff'*32) ) sig = SecureBinaryData()