def parse(self, fd): self.fd = fd while 1: ks = deser_compact_size(fd) if ks is None: break if ks == 0: break key = fd.read(ks) vs = deser_compact_size(fd) assert vs != None, 'eof' kt = key[0] if kt in self.no_keys: assert len(key) == 1, "no expecto key" # storing offset and length only! Mostly. if kt in self.short_values: actual = fd.read(vs) self.store(kt, bytes(key), actual) else: # skip actual data for now proxy = (fd.tell(), vs) fd.seek(vs, 1) self.store(kt, bytes(key), proxy)
def get_utxo(self, idx): # Load up the TxOut for specific output of the input txn associated with this in PSBT # Aka. the "spendable" for this input #. # - preserve the file pointer # - nValue needed for total_value_in, but all fields needed for signing # fd = self.fd old_pos = fd.tell() if self.witness_utxo: # Going forward? Just what we will witness; no other junk # - prefer this format, altho does that imply segwit txn must be generated? # - I don't know why we wouldn't always use this # - once we use this partial utxo data, we must create witness data out self.is_segwit = True fd.seek(self.witness_utxo[0]) utxo = CTxOut() utxo.deserialize(fd) fd.seek(old_pos) return utxo assert self.utxo, 'no utxo' # skip over all the parts of the txn we don't care about, without # fully parsing it... pull out a single TXO fd.seek(self.utxo[0]) _, marker, flags = unpack("<iBB", fd.read(6)) wit_format = (marker == 0 and flags != 0x0) if not wit_format: # rewind back over marker+flags fd.seek(-2, 1) # How many ins? We accept zero here because utxo's inputs might have been # trimmed to save space, and we have test cases like that. num_in = deser_compact_size(fd) _skip_n_objs(fd, num_in, 'CTxIn') num_out = deser_compact_size(fd) assert idx < num_out, "not enuf outs" _skip_n_objs(fd, idx, 'CTxOut') utxo = CTxOut() utxo.deserialize(fd) # ... followed by more outs, and maybe witness data, but we don't care ... fd.seek(old_pos) return utxo
def parse_txn(self): # Need to semi-parse in unsigned transaction. # - learn number of ins/outs so rest of PSBT can be understood # - also captures lots of position details # - called right after globals section is read fd = self.fd old_pos = fd.tell() fd.seek(self.txn[0]) # see serializations.py:CTransaction.deserialize() # and BIP-144 ... we expect witness serialization, but # don't force that self.txn_version, marker, flags = unpack("<iBB", fd.read(6)) self.had_witness = (marker == 0 and flags != 0x0) assert self.txn_version in {1, 2}, "bad txn version" if not self.had_witness: # rewind back over marker+flags fd.seek(-2, 1) num_in = deser_compact_size(fd) assert num_in > 0, "no ins?" self.num_inputs = num_in # all the ins are in sequence starting at this position self.vin_start = _skip_n_objs(fd, num_in, 'CTxIn') # next is outputs self.num_outputs = deser_compact_size(fd) self.vout_start = _skip_n_objs(fd, self.num_outputs, 'CTxOut') end_pos = sum(self.txn) # remainder is the witness data, and then the lock time if self.had_witness: # we'll need to come back to this pos if we # want to read the witness data later. self.wit_start = _skip_n_objs(fd, num_in, 'CTxInWitness') # we are at end of outputs, and no witness data, so locktime is here self.lock_time = unpack("<I", fd.read(4))[0] assert fd.tell() == end_pos, 'txn read end wrong' fd.seek(old_pos)
def calc_txid(self, poslen): # Given the (pos,len) of a transaction, return the txid for that. # - doesn't validate data # - does detected witness txn vs. old style # - simple dsha256() if old style txn, other wise witness must be skipped # see if witness encoding in effect fd = self.fd fd.seek(poslen[0]) txn_version, marker, flags = unpack("<iBB", fd.read(6)) has_witness = (marker == 0 and flags != 0x0) if not has_witness: # txn does not have witness data, so txid==wtxix return self.get_hash256(poslen) rv = tcc.sha256() # de/reserialize much of the txn -- but not the witness data rv.update(pack("<i", txn_version)) body_start = fd.tell() # determine how long ins + outs are... num_in = deser_compact_size(fd) _skip_n_objs(fd, num_in, 'CTxIn') num_out = deser_compact_size(fd) _skip_n_objs(fd, num_out, 'CTxOut') body_len = fd.tell() - body_start # hash the bulk of txn self.get_hash256((body_start, body_len), hasher=rv) # assume last 4 bytes are the lock_time fd.seek(sum(poslen) - 4) rv.update(fd.read(4)) return tcc.sha256(rv.digest()).digest()
def _skip_n_objs(fd, n, cls): # skip N sized objects in the stream, for example a vectors of CTxIns # - returns starting position if cls == 'CTxIn': # output point(hash, n) + script sig + locktime pat = [32 + 4, None, 4] elif cls == 'CTxOut': # nValue + Script pat = [8, None] else: raise ValueError(cls) rv = fd.tell() for i in range(n): for p in pat: if p is None: # variable-length part sz = deser_compact_size(fd) fd.seek(sz, 1) else: fd.seek(p, 1) return rv