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 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, 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 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 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