def breakDownWallet(walletPath, fragSize): print 'breaking down wallet in packs of %d addresses' % fragSize print 'reading wallet' theWallet = PyBtcWallet().readWalletFile(walletPath) nAddresses = len(theWallet.addrMap) addrIndex = 0 wltIndex = 1 if walletPath[-7:] == '.wallet': newDir = walletPath[:-7] else: newDir = os.path.dirname(walletPath) if not os.path.exists(newDir): os.mkdir(newDir) rootAddr = theWallet.addrMap['ROOT'] withEncrypt = theWallet.useEncryption addrIter = theWallet.addrMap.iteritems() while nAddresses > 0: print 'breaking down wallet from address #%d to #%d' % ( addrIndex, addrIndex + fragSize - 1) nAddresses -= fragSize newWalletPath = os.path.join(newDir, 'armory_wlt_%05d_.wallet' % wltIndex) wltIndex += 1 walletFragment = PyBtcWallet() createNewWallet(walletFragment, rootAddr, newWalletPath, withEncrypt, theWallet.kdf) fileData = BinaryPacker() i = 0 try: while i < fragSize: addrItem = addrIter.next()[1] addrItem.chainIndex = -2 fileData.put( BINARY_CHUNK, '\x00' + addrItem.addrStr20 + addrItem.serialize()) #walletFragment.walletFileSafeUpdate([[WLT_UPDATE_ADD, \ #WLT_DATATYPE_KEYDATA, addrItem.addrStr20, addrItem]]) i += 1 except StopIteration: pass walletFile = open(newWalletPath, 'ab') walletFile.write(fileData.getBinaryString()) walletFile.close() addrIndex += i print 'Done'
def createNewWallet(wlt, rootEntry, newWalletFilePath, withEncrypt, kdfParam=None): """ This method will create a new wallet, using as much customizability as you want. You can enable encryption, and set the target params of the key-derivation function (compute-time and max memory usage). The KDF parameters will be experimentally determined to be as hard as possible for your computer within the specified time target (default, 0.25s). It will aim for maximizing memory usage and using only 1 or 2 iterations of it, but this can be changed by scaling down the kdfMaxMem parameter (default 32 MB). If you use encryption, don't forget to supply a 32-byte passphrase, created via SecureBinaryData(pythonStr). This method will apply the passphrase so that the wallet is "born" encrypted. The field plainRootKey could be used to recover a written backup of a wallet, since all addresses are deterministically computed from the root address. This obviously won't reocver any imported keys, but does mean that you can recover your ENTIRE WALLET from only those 32 plaintext bytes AND the 32-byte chaincode. We skip the atomic file operations since we don't even have a wallet file yet to safely update. DO NOT CALL THIS FROM BDM METHOD. IT MAY DEADLOCK. """ # Create the root address object rootAddr = rootEntry # Update wallet object with the new data # NEW IN WALLET VERSION 1.35: unique ID is now based on # the first chained address: this guarantees that the unique ID # is based not only on the private key, BUT ALSO THE CHAIN CODE wlt.useEncryption = withEncrypt wlt.addrMap['ROOT'] = rootAddr wlt.uniqueIDBin = (ADDRBYTE + str(random.getrandbits(48))[:5])[::-1] wlt.uniqueIDB58 = binary_to_base58(wlt.uniqueIDBin) wlt.labelName = '' wlt.labelDescr = '' wlt.lastComputedChainAddr160 = rootAddr wlt.lastComputedChainIndex = 0 wlt.highestUsedChainIndex = 0 wlt.wltCreateDate = long(RightNow()) wlt.kdf = kdfParam # We don't have to worry about atomic file operations when # creating the wallet: so we just do it naively here. wlt.walletPath = newWalletFilePath newfile = open(newWalletFilePath, 'wb') fileData = BinaryPacker() # packHeader method writes KDF params and root address headerBytes = wlt.packHeader(fileData) newfile.write(fileData.getBinaryString()) newfile.close()
def breakDownWallet(walletPath, fragSize): print 'breaking down wallet in packs of %d addresses' % fragSize print 'reading wallet' theWallet = PyBtcWallet().readWalletFile(walletPath) nAddresses = len(theWallet.addrMap) addrIndex = 0 wltIndex = 1 if walletPath[-7:] == '.wallet': newDir = walletPath[:-7] else: newDir = os.path.dirname(walletPath) if not os.path.exists(newDir): os.mkdir(newDir) rootAddr = theWallet.addrMap['ROOT'] withEncrypt = theWallet.useEncryption addrIter = theWallet.addrMap.iteritems() while nAddresses > 0 : print 'breaking down wallet from address #%d to #%d' % (addrIndex, addrIndex +fragSize-1) nAddresses -= fragSize newWalletPath = os.path.join(newDir, 'armory_wlt_%05d_.wallet' % wltIndex) wltIndex += 1 walletFragment = PyBtcWallet() createNewWallet(walletFragment, rootAddr, newWalletPath, withEncrypt, theWallet.kdf) fileData = BinaryPacker() i=0 try: while i < fragSize: addrItem = addrIter.next()[1] addrItem.chainIndex = -2 fileData.put(BINARY_CHUNK, '\x00' + addrItem.addrStr20 + addrItem.serialize()) #walletFragment.walletFileSafeUpdate([[WLT_UPDATE_ADD, \ #WLT_DATATYPE_KEYDATA, addrItem.addrStr20, addrItem]]) i += 1 except StopIteration: pass walletFile = open(newWalletPath, 'ab') walletFile.write(fileData.getBinaryString()) walletFile.close() addrIndex += i print 'Done'
def getTxOutStr(txOutVal, txOutScrLen, txOutScr): '''Function that creates a transaction output from the function inputs.''' txOutBin = BinaryPacker() txOutBin.put(UINT64, txOutVal) txOutBin.put(VAR_INT, txOutScrLen) txOutBin.put(BINARY_CHUNK, txOutScr) return txOutBin.getBinaryString()
def testBinaryPacker(self): UNKNOWN_TYPE = 100 TEST_FLOAT = 1.23456789 TEST_UINT = 0xff TEST_INT = -1 TEST_VARINT = 78 TEST_STR = 'abc' TEST_BINARY_PACKER_STR = hex_to_binary('ffff00ff000000ff00000000000000ffffffffffffffffffffffffffffff4e0361626352069e3fffffffffffff00') FS_FOR_3_BYTES = '\xff\xff\xff' bp = BinaryPacker() bp.put(UINT8, TEST_UINT) bp.put(UINT16, TEST_UINT) bp.put(UINT32, TEST_UINT) bp.put(UINT64, TEST_UINT) bp.put(INT8, TEST_INT) bp.put(INT16, TEST_INT) bp.put(INT32, TEST_INT) bp.put(INT64, TEST_INT) bp.put(VAR_INT, TEST_VARINT) bp.put(VAR_STR, TEST_STR) bp.put(FLOAT, TEST_FLOAT) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES, 4) self.assertRaises(PackerError, bp.put, UNKNOWN_TYPE, TEST_INT) self.assertRaises(PackerError, bp.put, BINARY_CHUNK, FS_FOR_3_BYTES, 2) self.assertEqual(bp.getSize(), len(TEST_BINARY_PACKER_STR)) ts = bp.getBinaryString() self.assertEqual(ts, TEST_BINARY_PACKER_STR) bu = BinaryUnpacker(ts) self.assertEqual(bu.get(UINT8), TEST_UINT) self.assertEqual(bu.get(UINT16), TEST_UINT) self.assertEqual(bu.get(UINT32), TEST_UINT) self.assertEqual(bu.get(UINT64), TEST_UINT) self.assertEqual(bu.get(INT8), TEST_INT) self.assertEqual(bu.get(INT16), TEST_INT) self.assertEqual(bu.get(INT32), TEST_INT) self.assertEqual(bu.get(INT64), TEST_INT) self.assertEqual(bu.get(VAR_INT), TEST_VARINT) self.assertEqual(bu.get(VAR_STR), TEST_STR) self.assertAlmostEqual(bu.get(FLOAT), TEST_FLOAT, 2) self.assertEqual(bu.get(BINARY_CHUNK, 3), FS_FOR_3_BYTES) self.assertEqual(bu.get(BINARY_CHUNK, 4), FS_FOR_3_BYTES+"\x00") self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1) self.assertRaises(UnpackerError, bu.get, UNKNOWN_TYPE) self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1)
def processTxOutScr(txOutScr, blkHash, blkPos, txIdx, txOutIdx): '''Function processing a TxOut script.''' # Proceed only if there's data to read. retVal = txOutScr txOutScrUnpack = BinaryUnpacker(txOutScr) txOutAddress = BinaryPacker() txType = TxType.unknownTx txOutScrSize = txOutScrUnpack.getRemainingSize() if(txOutScrSize > 0): # Read the initial byte and determine what TxOut type it is. initByte = txOutScrUnpack.get(BINARY_CHUNK, 1) # 0x21/0x41 = Pay2PubKey if(initByte == '\x21' or initByte == '\x41'): # Make sure it's a valid pub key before declaring it valid. pkLen = isPubKey(txOutScrUnpack) if(pkLen != 0): # Save the pub key. txOutKey = txOutScrUnpack.get(BINARY_CHUNK, pkLen) txOutAddress.put(BINARY_CHUNK, txOutKey) txType = TxType.p2pKey # OP_DUP = Pay2PubKeyHash elif(initByte == OP_DUP): # HACK ALERT: Some bright bulb has created OP_* TxOuts that have # nothing but the OP_* code. Check the remaining size upfront. # (Checking after every read is more robust, really. I'm just lazy # and don't want to retrofit this chunk of code. :) ) if(txOutScrUnpack.getRemainingSize() > 0): hashByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(hashByte == OP_HASH160): hashSize = txOutScrUnpack.get(BINARY_CHUNK, 1) hashRemSize = txOutScrUnpack.getRemainingSize() if(hashSize == '\x14' and \ hashRemSize >= binary_to_int(hashSize)): txOutHash = txOutScrUnpack.get(BINARY_CHUNK, \ binary_to_int(hashSize)) # Save the hash. txOutAddress.put(BINARY_CHUNK, txOutHash) eqVerByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(eqVerByte == OP_EQUALVERIFY): checkSigByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(checkSigByte == OP_CHECKSIG): txType = TxType.p2pHash # OP_HASH160 = Pay2ScriptHash elif(initByte == OP_HASH160): hashSize = txOutScrUnpack.get(BINARY_CHUNK, 1) hashRemSize = txOutScrUnpack.getRemainingSize() if(hashSize == '\x14' and hashRemSize >= binary_to_int(hashSize)): txOutHash = txOutScrUnpack.get(BINARY_CHUNK, \ binary_to_int(hashSize)) # Save the hash. txOutAddress.put(BINARY_CHUNK, txOutHash) eqByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(eqByte == OP_EQUAL): txType = TxType.p2sh # OP_1/2/3 = MultiSig elif(initByte == OP_1 or initByte == OP_2 or initByte == OP_3): validKeys = True readByte = 0 numKeys = 0 # HACK ALERT 1: Some scripts are weird and initially appear to be # multi-sig but really aren't. We should compensate. One particular # way is to require at least 36 bytes (assume 1-of-1 w/ compressed # key) beyond the initial byte. # # HACK ALERT 2: There are some multisig TxOuts that, for unknown # reasons have things like compressed keys that where the first byte # is 0x00, not 0x02 or 0x03. For now, we just mark them as unknown # Tx and move on. if(txOutScrUnpack.getRemainingSize() >= 36): readByte = txOutScrUnpack.get(BINARY_CHUNK, 1) while((readByte == '\x21' or readByte == '\x41') and numKeys < 3 and validKeys == True): pkLen = isPubKey(txOutScrUnpack) if(pkLen != 0): txOutKey = txOutScrUnpack.get(BINARY_CHUNK, pkLen) # Save the key. txOutAddress.put(BINARY_CHUNK, txOutKey) numKeys += 1 readByte = txOutScrUnpack.get(BINARY_CHUNK, 1) else: validKeys = False else: validKeys = False if(validKeys == True): if((readByte == OP_1 or readByte == OP_2 or readByte == OP_3) \ and binary_to_int(initByte) <= binary_to_int(readByte)): cmsByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(cmsByte == OP_CHECKMULTISIG): txType = TxType.multiSig # OP_RETURN = Arbitrary data attached to a Tx. # Official as of BC-Core 0.9. https://bitcoinfoundation.org/blog/?p=290 # and https://github.com/bitcoin/bitcoin/pull/2738 have the details of # the initial commit, with https://github.com/bitcoin/bitcoin/pull/3737 # having the revision down to 40 bytes. elif(initByte == OP_RETURN): # If the 1st byte is OP_RETURN, as of BC-Core v0.9, there can be # arbitrary data placed afterwards. This makes the TxOut immediately # prunable, meaning it can never be used as a TxIn. (It can still be # spent, mind you.) The final BC-Core 0.9 only accepts <=40 bytes, # but preview versions accepted <=80. In theory, any amount of data # is valid, but miners won't accept non-standard amounts by default. # # Anyway, since it's arbitrary, we don't care what's present and # just assume it's valid. Save all the data as the TxOut address. opRetData = txOutScrUnpack.get(BINARY_CHUNK, \ txOutScrUnpack.getRemainingSize()) # Save the data. txOutAddress.put(BINARY_CHUNK, opRetData) txType = TxType.opReturn # Everything else isn't standard. For now, we'll do nothing. # else: # print("DEBUG: 1st BYTE (TxOut) IS TOTALLY UNKNOWN!!! BYTE={0}".format(binary_to_hex(initByte))) # Could it be that there is no TxOut script? Is this legal? Let's take a # peek and write some debug code. else: print("DEBUG: At block {0}, we have an empty TxOut script!".format(blkPos)) # If we have a known TxOut type, we'll return an address instead of the # entire TxOut script. Note that it's unlikely but possible that an address # value for, say, OP_RETURN could match a pub key hash. In production code, # we'd want to add extra data to ensure that no collisions have occurred # (e.g., 2 different addresses are sharing a common balance), or possibly # use a different approach altogether. if(txType != TxType.unknownTx): retVal = txOutAddress.getBinaryString() if(txType == TxType.p2pKey): step1 = hash160(retVal) step2 = hash160_to_addrStr(step1) retVal = base58_to_binary(step2) elif(txType == TxType.p2pHash): step1 = hash160_to_addrStr(retVal) retVal = base58_to_binary(step1) return retVal
def testBinaryPacker(self): UNKNOWN_TYPE = 100 TEST_FLOAT = 1.23456789 TEST_UINT = 0xff TEST_INT = -1 TEST_VARINT = 78 TEST_STR = 'abc' TEST_BINARY_PACKER_STR = hex_to_binary( 'ffff00ff000000ff00000000000000ffffffffffffffffffffffffffffff4e0361626352069e3fffffffffffff00' ) FS_FOR_3_BYTES = '\xff\xff\xff' bp = BinaryPacker() bp.put(UINT8, TEST_UINT) bp.put(UINT16, TEST_UINT) bp.put(UINT32, TEST_UINT) bp.put(UINT64, TEST_UINT) bp.put(INT8, TEST_INT) bp.put(INT16, TEST_INT) bp.put(INT32, TEST_INT) bp.put(INT64, TEST_INT) bp.put(VAR_INT, TEST_VARINT) bp.put(VAR_STR, TEST_STR) bp.put(FLOAT, TEST_FLOAT) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES, 4) self.assertRaises(PackerError, bp.put, UNKNOWN_TYPE, TEST_INT) self.assertRaises(PackerError, bp.put, BINARY_CHUNK, FS_FOR_3_BYTES, 2) self.assertEqual(bp.getSize(), len(TEST_BINARY_PACKER_STR)) ts = bp.getBinaryString() self.assertEqual(ts, TEST_BINARY_PACKER_STR) bu = BinaryUnpacker(ts) self.assertEqual(bu.get(UINT8), TEST_UINT) self.assertEqual(bu.get(UINT16), TEST_UINT) self.assertEqual(bu.get(UINT32), TEST_UINT) self.assertEqual(bu.get(UINT64), TEST_UINT) self.assertEqual(bu.get(INT8), TEST_INT) self.assertEqual(bu.get(INT16), TEST_INT) self.assertEqual(bu.get(INT32), TEST_INT) self.assertEqual(bu.get(INT64), TEST_INT) self.assertEqual(bu.get(VAR_INT), TEST_VARINT) self.assertEqual(bu.get(VAR_STR), TEST_STR) self.assertAlmostEqual(bu.get(FLOAT), TEST_FLOAT, 2) self.assertEqual(bu.get(BINARY_CHUNK, 3), FS_FOR_3_BYTES) self.assertEqual(bu.get(BINARY_CHUNK, 4), FS_FOR_3_BYTES + "\x00") self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1) self.assertRaises(UnpackerError, bu.get, UNKNOWN_TYPE) self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1) # Running tests with "python <module name>" will NOT work for any Armory tests # You must run tests with "python -m unittest <module name>" or run all tests with "python -m unittest discover" # if __name__ == "__main__": # unittest.main()
def getTxHash(txVer, numTxIn, txInList, numTxOut, txOutList, txLockTime): '''Function that creates a 2xSHA256 hash of a transaction.''' txHashBin = BinaryPacker() txHashBin.put(UINT32, txVer) txHashBin.put(VAR_INT, numTxIn) for txIn in txInList: txHashBin.put(BINARY_CHUNK, txIn) txHashBin.put(VAR_INT, numTxOut) for txOut in txOutList: txHashBin.put(BINARY_CHUNK, txOut) txHashBin.put(UINT32, txLockTime) return hash256(txHashBin.getBinaryString())
def getTxInStr(txInPrevHash, txInPrevTxOutHashIdx, txInScrLen, txInScr, \ txInSeqNum): '''Function that creates a transaction input from the function inputs.''' txInBin = BinaryPacker() txInBin.put(BINARY_CHUNK, txInPrevHash) txInBin.put(UINT32, txInPrevTxOutHashIdx) txInBin.put(VAR_INT, txInScrLen) txInBin.put(BINARY_CHUNK, txInScr) txInBin.put(UINT32, txInSeqNum) return txInBin.getBinaryString()
def testBinaryPacker(self): UNKNOWN_TYPE = 100 TEST_FLOAT = 1.23456789 TEST_UINT = 0xff TEST_INT = -1 TEST_VARINT = 78 TEST_STR = 'abc' TEST_BINARY_PACKER_STR = hex_to_binary( 'ffff00ff000000ff00000000000000ffffffffffffffffffffffffffffff4e0361626352069e3fffffffffffff00' ) FS_FOR_3_BYTES = '\xff\xff\xff' bp = BinaryPacker() bp.put(UINT8, TEST_UINT) bp.put(UINT16, TEST_UINT) bp.put(UINT32, TEST_UINT) bp.put(UINT64, TEST_UINT) bp.put(INT8, TEST_INT) bp.put(INT16, TEST_INT) bp.put(INT32, TEST_INT) bp.put(INT64, TEST_INT) bp.put(VAR_INT, TEST_VARINT) bp.put(VAR_STR, TEST_STR) bp.put(FLOAT, TEST_FLOAT) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES) bp.put(BINARY_CHUNK, FS_FOR_3_BYTES, 4) self.assertRaises(PackerError, bp.put, UNKNOWN_TYPE, TEST_INT) self.assertRaises(PackerError, bp.put, BINARY_CHUNK, FS_FOR_3_BYTES, 2) self.assertEqual(bp.getSize(), len(TEST_BINARY_PACKER_STR)) ts = bp.getBinaryString() self.assertEqual(ts, TEST_BINARY_PACKER_STR) bu = BinaryUnpacker(ts) self.assertEqual(bu.get(UINT8), TEST_UINT) self.assertEqual(bu.get(UINT16), TEST_UINT) self.assertEqual(bu.get(UINT32), TEST_UINT) self.assertEqual(bu.get(UINT64), TEST_UINT) self.assertEqual(bu.get(INT8), TEST_INT) self.assertEqual(bu.get(INT16), TEST_INT) self.assertEqual(bu.get(INT32), TEST_INT) self.assertEqual(bu.get(INT64), TEST_INT) self.assertEqual(bu.get(VAR_INT), TEST_VARINT) self.assertEqual(bu.get(VAR_STR), TEST_STR) self.assertAlmostEqual(bu.get(FLOAT), TEST_FLOAT, 2) self.assertEqual(bu.get(BINARY_CHUNK, 3), FS_FOR_3_BYTES) self.assertEqual(bu.get(BINARY_CHUNK, 4), FS_FOR_3_BYTES + "\x00") self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1) self.assertRaises(UnpackerError, bu.get, UNKNOWN_TYPE) self.assertRaises(UnpackerError, bu.get, BINARY_CHUNK, 1)