def getNextBlock(f): global blkCounter f.seek(MAGIC_NUMBER_LENGTH, 1) blkSize = binary_to_int(f.read(BLOCK_SIZE_LENGTH), LITTLEENDIAN) result = None if blkSize > 0: binunpack = BinaryUnpacker(f.read(blkSize)) blkHdrBinary = binunpack.get(BINARY_CHUNK, HEADER_LENGTH) blkHdr = parseBlockHeader(blkHdrBinary) blkCounter += 1 txCount = binunpack.get(VAR_INT) txBinary = binunpack.get(BINARY_CHUNK, binunpack.getRemainingSize()) txOffsetList = getTxOffsetList(txBinary, txCount) txList = [] for i in range(len(txOffsetList)): tx = txBinary[txOffsetList[i]:txOffsetList[i+1] if i < len(txOffsetList) - 1 else len(txBinary)] txList.append(getTx(tx, blkCounter)) result = Block(blkCounter, blkSize, blkHdr, txCount, txBinary, txOffsetList,txList) else: f.seek(0,2) return result
def parseBlockHeader(blkHdrBinary): binunpack = BinaryUnpacker(blkHdrBinary) return BlockHeader(binunpack.get(UINT32), binunpack.get(BINARY_CHUNK, 32), binunpack.get(BINARY_CHUNK, 32), binunpack.get(UINT32), binunpack.get(UINT32), binunpack.get(UINT32))
def restoreTableView(qtbl, hexBytes): try: binunpack = BinaryUnpacker(hex_to_binary(hexBytes)) hexByte = binunpack.get(UINT8) binLen = binunpack.get(UINT8) toRestore = [] for i in range(binLen): sz = binunpack.get(UINT16) if sz > 0: toRestore.append([i, sz]) for i, c in toRestore[:-1]: qtbl.setColumnWidth(i, c) except Exception, e: print 'ERROR!' pass
def restoreTableView(qtbl, hexBytes): try: binunpack = BinaryUnpacker(hex_to_binary(hexBytes)) hexByte = binunpack.get(UINT8) binLen = binunpack.get(UINT8) toRestore = [] for i in range(binLen): sz = binunpack.get(UINT16) if sz>0: toRestore.append([i,sz]) for i,c in toRestore[:-1]: qtbl.setColumnWidth(i, c) except Exception, e: print 'ERROR!' pass
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 getBlkHdrValues(header): '''Get the block header values & hash. Will read the data itself.''' # Get the block hash (endian-flipped result of 2xSHA256 block header # hash), then get the individual block pieces and return everything. blkHdrData = header.read(80) blkHdrHash = hash256(blkHdrData) # BE blkHdrUnpack = BinaryUnpacker(blkHdrData) blkVer = blkHdrUnpack.get(UINT32) # LE prevBlkHash = blkHdrUnpack.get(BINARY_CHUNK, 32) # BE blkMerkleRoot = blkHdrUnpack.get(BINARY_CHUNK, 32) # BE blkTimestamp = blkHdrUnpack.get(UINT32) # LE blkBits = blkHdrUnpack.get(UINT32) # LE blkNonce = blkHdrUnpack.get(UINT32) # LE return (blkVer, prevBlkHash, blkMerkleRoot, blkTimestamp, blkBits, \ blkNonce, blkHdrHash)
def testBinaryUnpacker(self): ts = '\xff\xff\xff' bu = BinaryUnpacker(ts) self.assertEqual(bu.getSize(), len(ts)) bu.advance(1) self.assertEqual(bu.getRemainingSize(), len(ts)-1) self.assertEqual(bu.getBinaryString(), ts) self.assertEqual(bu.getRemainingString(), ts[1:]) bu.rewind(1) self.assertEqual(bu.getRemainingSize(), len(ts)) bu.resetPosition(2) self.assertEqual(bu.getRemainingSize(), len(ts) - 2) self.assertEqual(bu.getPosition(), 2) bu.resetPosition() self.assertEqual(bu.getRemainingSize(), len(ts)) self.assertEqual(bu.getPosition(), 0) bu.append(ts) self.assertEqual(bu.getBinaryString(), ts + ts)
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
# Iterate through each block by going through each file. Note that the code # assumes blocks are in order. In the future, this may not be case. # while(os.path.isfile(fileName) is True): for fileNum in range(0, 1): # SPECIAL DEBUG: Only the first few files are parsed print("DEBUG: File blk%05d.dat is being processed." % curBlkFile) # While reading the files, read data only as needed, and not all at # once. More I/O but it keeps memory usage down. with open(fileName, "rb") as rawData: try: # Read the magic bytes (4 bytes) & block size (4 bytes). Proceed # only if there's data to read. readData = rawData.read(8) while(readData != ""): # If the magic bytes are legit, proceed. readUnpack = BinaryUnpacker(readData) read_magic = readUnpack.get(BINARY_CHUNK, 4) if(read_magic == MAGIC_BYTES): # Get the block header data. blockLen = readUnpack.get(UINT32) blockVer, prevBlkHash, merkRoot, timestamp, bits, \ nonce, blkHdrHash = getBlkHdrValues(rawData) # Get the transaction data and process it. rawTxData = rawData.read(blockLen - 80) txUnpack = BinaryUnpacker(rawTxData) txVarInt = txUnpack.get(VAR_INT) txIdx = 0 # Process all Tx objects. while(txVarInt > 0):
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 testBinaryUnpacker(self): ts = '\xff\xff\xff' bu = BinaryUnpacker(ts) self.assertEqual(bu.getSize(), len(ts)) bu.advance(1) self.assertEqual(bu.getRemainingSize(), len(ts) - 1) self.assertEqual(bu.getBinaryString(), ts) self.assertEqual(bu.getRemainingString(), ts[1:]) bu.rewind(1) self.assertEqual(bu.getRemainingSize(), len(ts)) bu.resetPosition(2) self.assertEqual(bu.getRemainingSize(), len(ts) - 2) self.assertEqual(bu.getPosition(), 2) bu.resetPosition() self.assertEqual(bu.getRemainingSize(), len(ts)) self.assertEqual(bu.getPosition(), 0) bu.append(ts) self.assertEqual(bu.getBinaryString(), ts + ts)
def processTxInScr(txInScr, blkHash, blkPos, txIdx, txInIdx, txInFile): '''Function processing a TxIn script.''' # Proceed only if there's data to read. txInScrUnpack = BinaryUnpacker(txInScr) retVal = TxType.unknownTx txInScrSize = txInScrUnpack.getRemainingSize() if(txInScrSize > 0): # Read the initial byte and determine what TxOut type it is. initByte = txInScrUnpack.get(BINARY_CHUNK, 1) # Except for multisig and possibly OP_RETURN, all should start with a # sig. if(initByte >= '\x43' and initByte <= '\x4b'): # Make sure it's a valid pub key before declaring it valid. # CATCH: We'll rewind because the first byte of the sig isn't # repeated, meaning the stack uses the first byte of the sig to push # the rest of the sig onto the stack. The rewind isn't necessary but # I'd like to keep the sig verification whole. txInScrUnpack.rewind(1) sigLen = isSigShell(txInScrUnpack, False) if(sigLen != 0): txInScrUnpack.advance(sigLen) if(txInScrUnpack.getRemainingSize() == 0): retVal = TxType.p2pKey else: readByte = txInScrUnpack.get(BINARY_CHUNK, 1) if(readByte == '\x21' or readByte == '\x41'): pkLen = isPubKey(txInScrUnpack) if(pkLen != 0): retVal = TxType.p2pHash # OP_0 = P2SH or MultiSig elif(initByte == OP_0): numBytesAdv = isSigShell(txInScrUnpack, True) # Proceed only if there was at least 1 valid sig. if(numBytesAdv != 0): txInScrUnpack.advance(numBytesAdv) numBytesRem = txInScrUnpack.getRemainingSize() if(numBytesRem != 0): # Confirm that the remaining bytes are a standard script # before marking this as a P2SH script. (There are P2SH # scripts that aren't standard, so we'll mark the entire # script as unknown and save it.) In a fully robust system, # we'd Hash160 and compare against the Hash160 in the # ref'd TxOut to confirm that this is valid. # NB: In the real world, it looks like all scripts don't # match the normal TxOut types! Just mark this as P2SH and # write it out anyway. # p2shScript = txInScrUnpack.get(BINARY_CHUNK, numBytesRem) # if(processTxOutScr(p2shScript, blkHash, blkPos, txIdx, \ # txOutIdx, txOutFile) != TxType.unknownTx): # retVal = TxType.p2sh # print("HEY, WE GOT A GOOD SCRIPT! {0}".format(binary_to_hex(p2shScript))) # else: # print("OH NO, WE HAVE A BAD SCRIPT! {0}".format(binary_to_hex(p2shScript))) retVal = TxType.p2sh else: # We have multi-sig. retVal = TxType.multiSig # We have an unknown script type. We'll report it. There's a chance it # refers to an OP_RETURN TxOut script but we'll ignore that possibility # for now in order to keep things simple. else: print("DEBUG: Block {0}: 1st BYTE (TxIn) IS TOTALLY UNKNOWN!!! " \ "BYTE={1}".format(blkPos, binary_to_hex(initByte))) # If a script is unknown or is P2SH, write it out here. # NB: After running this code several times, it appears that the multisig # code uses keys with invalid first bytes. I'm not sure what's going on. The # scripts seem valid otherwise. # if(retVal == TxType.unknownTx or retVal == TxType.p2sh): # if(retVal == TxType.p2sh): # print("P2SH script") # else: # print("Unknown TxIn script") if retVal == TxType.unknownTx: print("TxIn: {0}".format(binary_to_hex(txInScr)), \ file=txInFile) print("Block Number: {0}".format(blkPos), file=txInFile) print("Block Hash: {0}".format(binary_to_hex(blkHash)), \ file=txInFile) print("Tx Hash: {0}", binary_to_hex(curTxHash, endOut=BIGENDIAN), \ file=txInFile) print("Tx Index: {0}", txIdx, file=txInFile) print("TxIn Index: {0}", txInIdx, file=txInFile) print("---------------------------------------", file=txInFile) return retVal
def processTxOutScr(txOutScr, blkHash, blkPos, txIdx, txOutIdx, txOutFile): '''Function processing a TxOut script.''' # Proceed only if there's data to read. txOutScrUnpack = BinaryUnpacker(txOutScr) retVal = 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): retVal = 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)): txOutScrUnpack.advance(binary_to_int(hashSize)) eqVerByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(eqVerByte == OP_EQUALVERIFY): checkSigByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(checkSigByte == OP_CHECKSIG): retVal = 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)): txOutScrUnpack.advance(binary_to_int(hashSize)) eqByte = txOutScrUnpack.get(BINARY_CHUNK, 1) if(eqByte == OP_EQUAL): retVal = 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): txOutScrUnpack.advance(pkLen) 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): retVal = 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. retVal = TxType.opReturn # Everything else is weird and should be reported. else: print("DEBUG: Block {0} - Tx Hash {1}: 1st BYTE (TxOut) IS " \ "TOTALLY UNKNOWN!!! BYTE={2}".format(blkPos, \ binary_to_hex(curTxHash, endOut=BIGENDIAN), \ binary_to_hex(initByte))) # Write the script to the file if necessary. if(retVal == TxType.unknownTx): print("TxOut: {0}".format(binary_to_hex(txOutScr)), \ file=txOutFile) print("Block Number: {0}".format(blkPos), file=txOutFile) print("Block Hash: {0}".format(binary_to_hex(blkHash)), \ file=txOutFile) print("Tx Hash: {0}", binary_to_hex(curTxHash, endOut=BIGENDIAN), \ file=txOutFile) print("Tx Index: {0}", txIdx, file=txOutFile) print("TxOut Index: {0}", txOutIdx, file=txOutFile) print("---------------------------------------", file=txOutFile) return retVal
def getTx(tx, blkNum): binunpacker = BinaryUnpacker(tx) txData = Tx(hash256(tx), binunpacker.get(UINT32),[], []) txInCount = binunpacker.get(VAR_INT) for i in range(txInCount): outPoint = OutPoint(binunpacker.get(BINARY_CHUNK, TX_OUT_HASH_LENGTH),binunpacker.get(UINT32), blkNum) txInLength = binunpacker.get(VAR_INT) script = binunpacker.get(BINARY_CHUNK, txInLength) sequence = binunpacker.get(UINT32) txData.txInList.append(TxIn(outPoint, script, sequence)) txOutCount = binunpacker.get(VAR_INT) for j in range(txOutCount): value = binunpacker.get(UINT64) scriptLength = binunpacker.get(VAR_INT) if scriptLength > 0: script = binunpacker.get(BINARY_CHUNK,scriptLength) opcode = binary_to_int(script[:1]) if opcode < 75 and len(script)==2+opcode and binary_to_int(script[1+opcode:], BIGENDIAN) == OP_CHECKSIG: txOutType = PAY_TO_PUBLIC_KEY elif opcode == OP_DUP and binary_to_int(script[-2]) == OP_EQUALVERIFY and binary_to_int(script[-1]) == OP_CHECKSIG: txOutType = PAY_TO_PUBKEY_HASH elif opcode == OP_HASH160 and binary_to_int(script[1]) == 20 and binary_to_int(script[-1]) == OP_EQUAL: txOutType = PAY_TO_SCRIPT_HASH elif opcode == P2POOL_LAST_TX_OUT_OP_CODE and len(script) == 1 + opcode: txOutType = PAY_TO_POOL_LAST_TX_OUT elif opcode in [OP_1,OP_2,OP_3] and binary_to_int(script[-1]) == OP_CHECKMULTISIG: txOutType = MULTISIGNATURE else: txOutType = UNKNOWN else: script = None txOutType = None j = txOutCount txData.txOutList.append(TxOut(j, value, script, txOutType)) return txData
def getTxOffsetList(txListBinary, txCount): binunpack = BinaryUnpacker(txListBinary) txOffsetList = [] for i in range(txCount): txOffsetList.append(binunpack.getPosition()) binunpack.advance(VERSION_LENGTH) txInCount = binunpack.get(VAR_INT) for j in range(txInCount): binunpack.advance(TX_OUT_HASH_LENGTH + TX_OUT_INDEX_LENGTH) sigScriptLength = binunpack.get(VAR_INT) binunpack.advance(sigScriptLength + SEQUENCE_LENGTH) txOutCount = binunpack.get(VAR_INT) for k in range(txOutCount): binunpack.advance(SATOSHI_LENGTH) scriptLength = binunpack.get(VAR_INT) binunpack.advance(scriptLength) binunpack.advance(LOCKTIME_LENGTH) return txOffsetList
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)