def parse_header(header: str) -> Header: ''' Parses a header to a dict Args: header (str): hex formatted 80 byte header Returns: dict: hash (str): the header hash 0000-first version (int): the block version as an int prev_block (str): the previous block hash 0000-first merkle_root (str): the block transaction merkle tree root timestamp (int): the block header timestamp nbits (str): the difficulty bits nonce (str): the nonce difficulty (int): the difficulty as an int hex (str): the full header as hex height (int): the block height (always 0) ''' if len(header) != 160: raise ValueError('Invalid header received') as_bytes = bytes.fromhex(header) nbits = as_bytes[72:76] return { 'hash': rutils.hash256(bytes.fromhex(header))[::-1].hex(), 'version': rutils.le2i(as_bytes[0:4]), 'prev_block': as_bytes[4:36][::-1].hex(), 'merkle_root': as_bytes[36:68].hex(), 'timestamp': rutils.le2i(as_bytes[68:72]), 'nbits': nbits.hex(), 'nonce': as_bytes[76:80].hex(), 'difficulty': parse_difficulty(nbits), 'hex': header, 'height': 0, 'accumulated_work': 0 }
def get_value_and_lock_time(tx: Tx) -> Tuple[int, int]: '''Parses the time lock and first output's value from a txn Args: tx (riemann.tx.Tx): the transaction Returns: (int, int): the value and locktime ''' return (rutils.le2i(tx.tx_outs[0].value), rutils.le2i(tx.lock_time))
def calculate_fee(self, input_values): ''' Tx, list(int) -> int ''' total_in = sum(input_values) total_out = sum([utils.le2i(tx_out.value) for tx_out in self.tx_outs]) for js in self.tx_joinsplits: total_in += utils.le2i(js.vpub_new) total_out += utils.le2i(js.vpub_old) return total_in - total_out
def calculate_fee(self, input_values): ''' SaplingTx, list(int) -> int ''' total_in = sum(input_values) total_out = sum([utils.le2i(tx_out.value) for tx_out in self.tx_outs]) shileded_net = utils.le2i(self.value_balance, signed=True) for js in self.tx_joinsplits: total_in += utils.le2i(js.vpub_new) total_out += utils.le2i(js.vpub_old) return total_in - total_out + shileded_net
def __init__(self, vpub_old: bytes, vpub_new: bytes, anchor: bytes, nullifiers: bytes, commitments: bytes, ephemeral_key: bytes, random_seed: bytes, vmacs: bytes, zkproof: SproutZkproof, encoded_notes: bytes): super().__init__() if not isinstance(zkproof, SproutZkproof): raise ValueError( 'Invalid zkproof. ' 'Expected instance of SproutZkproof. Got {}' .format(type(zkproof).__name__)) if (utils.le2i(vpub_old) != 0 and utils.le2i(vpub_new) != 0): raise ValueError('vpub_old or vpub_new must be zero') self.validate_bytes(vpub_old, 8) self.validate_bytes(vpub_new, 8) self.validate_bytes(anchor, 32) self.validate_bytes(nullifiers, 64) self.validate_bytes(commitments, 64) self.validate_bytes(ephemeral_key, 32) self.validate_bytes(random_seed, 32) self.validate_bytes(vmacs, 64) self.validate_bytes(encoded_notes, 1202) self += vpub_old self += vpub_new self += anchor self += nullifiers self += commitments self += ephemeral_key self += random_seed self += vmacs self += zkproof self += encoded_notes self.vpub_old = vpub_old self.vpub_new = vpub_new self.anchor = anchor self.nullifiers = nullifiers self.commitments = commitments self.ephemeral_key = ephemeral_key self.random_seed = random_seed self.vmacs = vmacs self.zkproof = zkproof self.encoded_notes = encoded_notes self._make_immutable()
def from_bytes(VarInt, byte_string): ''' byte-like -> VarInt accepts arbitrary length input, gets a VarInt off the front ''' num = byte_string if num[0] <= 0xfc: num = num[0:1] non_compact = False elif num[0] == 0xfd: num = num[1:3] non_compact = (num[-1:] == b'\x00') elif num[0] == 0xfe: num = num[1:5] non_compact = (num[-2:] == b'\x00\x00') elif num[0] == 0xff: num = num[1:9] non_compact = (num[-4:] == b'\x00\x00\x00\x00') if len(num) not in [1, 2, 4, 8]: raise ValueError('Malformed VarInt. Got: {}'.format( byte_string.hex())) if (non_compact and ('overwinter' in riemann.get_current_network_name() or 'sapling' in riemann.get_current_network_name())): raise ValueError('VarInt must be compact. Got: {}'.format( byte_string.hex())) ret = VarInt(utils.le2i(num), length=len(num) + 1 if non_compact else 0) return ret
def calculate_fee(self, input_values): ''' Tx, list(int) -> int Inputs don't know their value without the whole chain. ''' return \ sum(input_values) \ - sum([utils.le2i(o.value) for o in self.tx_outs])
def make_target(nbits: bytes) -> int: ''' converts an nbits from a header into the target Args: nbits (bytes): the 4-byte nbits bytestring Returns: (int): the target threshold ''' exponent = rutils.be2i(nbits[-1:]) - 3 return math.floor(rutils.le2i(nbits[:-1]) * 0x100**(exponent))
def deserialize(serialized_script: bytes) -> str: '''deserialize a script from bytes to human-readable''' deserialized = [] i = 0 while i < len(serialized_script): current_byte = serialized_script[i] if current_byte == 0xab: raise NotImplementedError('OP_CODESEPARATOR is a bad idea.') if current_byte <= 75 and current_byte != 0: deserialized.append(serialized_script[i + 1:i + 1 + current_byte].hex()) i += 1 + current_byte if i > len(serialized_script): raise IndexError( 'Push {} caused out of bounds exception.'.format( current_byte)) elif current_byte == 76: # next hex blob length blob_len = serialized_script[i + 1] deserialized.append(serialized_script[i + 2:i + 2 + blob_len].hex()) i += 2 + blob_len elif current_byte == 77: # next hex blob length blob_len = utils.le2i(serialized_script[i + 1:i + 3]) deserialized.append(serialized_script[i + 3:i + 3 + blob_len].hex()) i += 3 + blob_len elif current_byte == 78: raise NotImplementedError('OP_PUSHDATA4 is a bad idea.') else: if current_byte in riemann.network.INT_TO_CODE_OVERWRITE: deserialized.append( riemann.network.INT_TO_CODE_OVERWRITE[current_byte]) elif current_byte in INT_TO_CODE: deserialized.append(INT_TO_CODE[current_byte]) else: raise ValueError('Unsupported opcode. ' 'Got 0x%x' % serialized_script[i]) i += 1 return ' '.join(deserialized)
def calculate_fee(self, input_values: Sequence[int]) -> int: ''' Calculate the fee associated with a transaction. Caller must provide a sequence representing the value (in satoshi) of each input. Args: input_values: The value of each input in order. Returns: The total fee paid to miners by this transaction. ''' return \ sum(input_values) \ - sum([utils.le2i(o.value) for o in self.tx_outs])
def from_bytes(SproutTx, byte_string): ''' byte-like -> SproutTx ''' version = byte_string[0:4] tx_ins = [] tx_ins_num = shared.VarInt.from_bytes(byte_string[4:]) current = 4 + len(tx_ins_num) for _ in range(tx_ins_num.number): tx_in = TxIn.from_bytes(byte_string[current:]) current += len(tx_in) tx_ins.append(tx_in) tx_outs = [] tx_outs_num = shared.VarInt.from_bytes(byte_string[current:]) current += len(tx_outs_num) for _ in range(tx_outs_num.number): tx_out = TxOut.from_bytes(byte_string[current:]) current += len(tx_out) tx_outs.append(tx_out) lock_time = byte_string[current:current + 4] current += 4 tx_joinsplits = None joinsplit_pubkey = None joinsplit_sig = None if utils.le2i(version) == 2: # If we expect joinsplits tx_joinsplits = [] tx_joinsplits_num = shared.VarInt.from_bytes(byte_string[current:]) current += len(tx_joinsplits_num) for _ in range(tx_joinsplits_num.number): joinsplit = z.SproutJoinsplit.from_bytes(byte_string[current:]) current += len(joinsplit) tx_joinsplits.append(joinsplit) joinsplit_pubkey = byte_string[current:current + 32] current += 32 joinsplit_sig = byte_string[current:current + 64] return SproutTx(version=version, tx_ins=tx_ins, tx_outs=tx_outs, lock_time=lock_time, tx_joinsplits=tx_joinsplits, joinsplit_pubkey=joinsplit_pubkey, joinsplit_sig=joinsplit_sig)
def check_final(t: tx.Tx, best_height: int = 0, best_timestamp: int = 0) -> bool: ''' Checks absolute locktime of a transaction. Pass in the best height and timestamp Args: t (tx.Tx): the transaction best_height (int): best known Bitcoin height best_timestamp (int): best known Bitcoin timestamp ''' lock = rutils.le2i(t.lock_time) if lock >= 500_000_000: # timelocked return lock <= best_timestamp else: # height-locked return lock <= best_height
def is_op_return(o: tx.TxOut) -> bool: ''' Checks whether a txout is standard TX_NULL_DATA op_return output Args: o (tx.TxOut): the output Returns: (bool): True if standard opreturn, otherwise false ''' script: str = ser.deserialize(o.output_script) split_script = script.split() # TX_NULL_DATA, up to 83 bytes (80 for safety) if (rutils.le2i(o.value) == 0 and split_script[0] == 'OP_RETURN' and len(script) < 80): return True return False
def check_is_standard(t: tx.Tx) -> bool: ''' Analog of Bitcoin's IsStandard Args: t (tx.Tx): the transaction to check Returns: (bool): True for standard, false for non-standard ''' for o in t.tx_outs: # 'scriptpubkey' if not is_standard_output_type(o): return False # 'dust' if (rutils.le2i(o.value) < 550 and o.output_script[:2] != b'\x00\x14'): return False # 'multi-op-return' if len([is_op_return(o) for o in t.tx_outs]) > 1: return False return True
def __init__(self, tx_ins, tx_outs, lock_time, expiry_height, value_balance, tx_shielded_spends, tx_shielded_outputs, tx_joinsplits, joinsplit_pubkey, joinsplit_sig, binding_sig): super().__init__() if 'sapling' not in riemann.get_current_network_name(): raise ValueError( 'SaplingTx not supported by network {}.' .format(riemann.get_current_network_name())) self.validate_bytes(lock_time, 4) self.validate_bytes(expiry_height, 4) self.validate_bytes(value_balance, 8) if utils.le2i(expiry_height) > 499999999: raise ValueError('Expiry time too high.' 'Expected <= 499999999. Got {}' .format(utils.le2i(expiry_height))) if (len(tx_shielded_spends) + len(tx_shielded_outputs) == 0 and value_balance != b'\x00' * 8): raise ValueError('If no shielded inputs or outputs, value balance ' 'must be 8 0-bytes. Got {}' .format(value_balance.hex())) elif binding_sig is not None: self.validate_bytes(binding_sig, 64) for tx_in in tx_ins: if not isinstance(tx_in, TxIn): raise ValueError( 'Invalid TxIn. ' 'Expected instance of TxOut. Got {}' .format(type(tx_in).__name__)) for tx_out in tx_outs: if not isinstance(tx_out, TxOut): raise ValueError( 'Invalid TxOut. ' 'Expected instance of TxOut. Got {}' .format(type(tx_out).__name__)) if len(tx_joinsplits) > 5: raise ValueError('Too many joinsplits. Stop that.') for shielded_spend in tx_shielded_spends: if not isinstance(shielded_spend, SaplingShieldedSpend): raise ValueError( 'Invalid shielded spend. ' 'Expected instance of SaplingShieldedSpend. Got {}' .format(type(shielded_spend).__name__)) for shielded_output in tx_shielded_outputs: if not isinstance(shielded_output, SaplingShieldedOutput): raise ValueError( 'Invalid shielded output. ' 'Expected instance of SaplingShieldedOutput. Got {}' .format(type(shielded_output).__name__)) for tx_joinsplit in tx_joinsplits: if not isinstance(tx_joinsplit, SaplingJoinsplit): raise ValueError( 'Invalid Joinsplit. ' 'Expected instance of SaplingJoinsplit. Got {}' .format(type(tx_joinsplit).__name__)) if len(tx_joinsplits) != 0: self.validate_bytes(joinsplit_pubkey, 32) self.validate_bytes(joinsplit_sig, 64) if len(tx_joinsplits) + len(tx_ins) + len(tx_shielded_spends) == 0: raise ValueError('Transaction must have some input value.') self += b'\x04\x00\x00\x00' # Sapling is always v4 self += b'\x85\x20\x2f\x89' # Sapling version group id self += shared.VarInt(len(tx_ins)) for tx_in in tx_ins: self += tx_in self += shared.VarInt(len(tx_outs)) for tx_out in tx_outs: self += tx_out self += lock_time self += expiry_height self += value_balance self += shared.VarInt(len(tx_shielded_spends)) if len(tx_shielded_spends) != 0: for shielded_spend in tx_shielded_spends: self += shielded_spend self += shared.VarInt(len(tx_shielded_outputs)) if len(tx_shielded_outputs) != 0: for shielded_output in tx_shielded_outputs: self += shielded_output self += shared.VarInt(len(tx_joinsplits)) if len(tx_joinsplits) != 0: for tx_joinsplit in tx_joinsplits: self += tx_joinsplit self += joinsplit_pubkey self += joinsplit_sig if len(tx_shielded_outputs) + len(tx_shielded_spends) != 0: self += binding_sig self.binding_sig = binding_sig self.header = b'\x04\x00\x00\x80' # Sapling is always v4 self.group_id = b'\x85\x20\x2f\x89' # Sapling version group id self.tx_ins = tuple(tx_in for tx_in in tx_ins) self.tx_outs = tuple(tx_out for tx_out in tx_outs) self.lock_time = lock_time self.expiry_height = expiry_height self.value_balance = value_balance if len(tx_shielded_spends) != 0: self.tx_shielded_spends = tuple(ss for ss in tx_shielded_spends) else: self.tx_shielded_spends = tuple() if len(tx_shielded_outputs) != 0: self.tx_shielded_outputs = tuple(so for so in tx_shielded_outputs) else: self.tx_shielded_outputs = tuple() if len(tx_joinsplits) != 0: self.tx_joinsplits = tuple(js for js in tx_joinsplits) self.joinsplit_pubkey = joinsplit_pubkey self.joinsplit_sig = joinsplit_sig # Zcash spec 5.4.1.4 Hsig hash function self.hsigs = (tuple(self._hsig(i) for i in range(len(self.tx_joinsplits)))) self.primary_inputs = (tuple(self._primary_input(i) for i in range(len(self.tx_joinsplits)))) else: self.tx_joinsplits = tuple() self.joinsplit_pubkey = None self.joinsplit_sig = None self.hsigs = tuple() self.primary_inputs = tuple() if len(tx_shielded_outputs) + len(tx_shielded_spends) != 0: self.binding_sig = binding_sig else: self.binding_sig = None self.tx_id_le = self.tx_id_le = utils.hash256(self.to_bytes()) self.tx_id = self.tx_id_le[::-1] self._make_immutable() if len(self) > 100000: raise ValueError( # pragma: no cover 'Tx is too large. ' 'Expect less than 100kB. Got: {} bytes'.format(len(self)))
import riemann from riemann import tx from riemann import utils riemann.select_network('zcash_sapling_main') tx_hex = 'YOUR_TX_HEX_HERE' # Parse the transaction blob into an object sapling_tx = tx.SaplingTx.from_hex(tx_hex) # Interact with it easily print(utils.le2i(sapling_tx.expiry_height)) print(sapling_tx.tx_shielded_outputs[0].enc_ciphertext.hex()) print(sapling_tx.tx_joinsplits[0].zkproof.pi_sub_c.hex()) # Change some part of it new_tx = sapling_tx.copy(expiry_height=77) print(new_tx.hex()) # Calculate the sighash new_tx.sighash_all( joinsplit=False, # True if you're signing a joinsplit script_code=b'\x19\x76\x00\x88\xac', # the script code to sign anyone_can_pay=False, # Anyone can pay can't be used with JSs prevout_value=b'\x00' * 8) # little-endian 8 byte value in zatoshi
txns: List[tx.Tx] = [] # Loop through the txn_bytes, parsing txns off the front # Riemann parsers all silently handle extra bytes. # Each time we call from_bytes it returns a tx from the front of the bytearray for i in range(num_txns.number): try: txns.append(tx.Tx.from_bytes(txn_bytes[offset:])) offset += len(txns[-1]) except ValueError: print() print(f'Errored after {len(txns)} txns') print('please file an issue') print(txn_bytes[offset:].hex()) break # list the total output value of each tx vals_out: List[int] = [ sum(rutils.le2i(tx_out.value) for tx_out in t.tx_outs) for t in txns ] num_outputs = sum(len(t.tx_outs) for t in txns) # Calculate and output some basic stats print() print(f'block produced {num_outputs} new TXOs') print(f'total output value of block is {as_btc(sum(vals_out))}') print(f'mean output value is {as_btc(sum(vals_out) / num_outputs)}') print(f'average tx output value is {as_btc(sum(vals_out) / len(vals_out))}') print()
def __init__(self, version, tx_ins, tx_outs, lock_time, tx_joinsplits, joinsplit_pubkey, joinsplit_sig): super().__init__() if 'sprout' not in riemann.get_current_network_name(): raise ValueError('SproutTx not supported by network {}.'.format( riemann.get_current_network_name())) self.validate_bytes(version, 4) self.validate_bytes(lock_time, 4) for tx_in in tx_ins: if not isinstance(tx_in, TxIn): raise ValueError('Invalid TxIn. ' 'Expected instance of TxOut. Got {}'.format( type(tx_in).__name__)) for tx_out in tx_outs: if not isinstance(tx_out, TxOut): raise ValueError('Invalid TxOut. ' 'Expected instance of TxOut. Got {}'.format( type(tx_out).__name__)) if utils.le2i(version) == 1: if tx_joinsplits is not None and len(tx_joinsplits) != 0: raise ValueError('Joinsplits not allowed in version 1 txns.') if tx_ins is None or len(tx_ins) == 0: raise ValueError('Version 1 txns must have at least 1 input.') if utils.le2i(version) == 2: if len(tx_joinsplits) > 5: raise ValueError('Too many joinsplits. Stop that.') for tx_joinsplit in tx_joinsplits: if not isinstance(tx_joinsplit, z.SproutJoinsplit): raise ValueError( 'Invalid Joinsplit. ' 'Expected instance of SproutJoinsplit. Got {}'.format( type(tx_joinsplit).__name__)) self.validate_bytes(joinsplit_pubkey, 32) if joinsplit_sig is not None and joinsplit_sig != b'': self.validate_bytes(joinsplit_sig, 64) if utils.le2i(version) not in [1, 2]: raise ValueError('Version must be 1 or 2. ' 'Got: {}'.format(utils.le2i(version))) self += version self += shared.VarInt(len(tx_ins)) for tx_in in tx_ins: self += tx_in self += shared.VarInt(len(tx_outs)) for tx_out in tx_outs: self += tx_out self += lock_time if version == utils.i2le_padded(2, 4): self += shared.VarInt(len(tx_joinsplits)) for tx_joinsplit in tx_joinsplits: self += tx_joinsplit self += joinsplit_pubkey self += joinsplit_sig self.version = version self.tx_ins = tuple(tx_in for tx_in in tx_ins) self.tx_outs = tuple(tx_out for tx_out in tx_outs) self.tx_joinsplits = tuple(js for js in tx_joinsplits) self.lock_time = lock_time if version == utils.i2le_padded(2, 4): self.joinsplit_pubkey = joinsplit_pubkey self.joinsplit_sig = joinsplit_sig # Zcash spec 5.4.1.4 Hsig hash function self.hsigs = (tuple( self._hsig(i) for i in range(len(self.tx_joinsplits)))) self.primary_inputs = (tuple( self._primary_input(i) for i in range(len(self.tx_joinsplits)))) else: self.joinsplit_pubkey = None self.joinsplit_sig = None self.hsigs = None self.primary_inputs = None self.tx_id_le = utils.hash256(self.to_bytes()).hex() self.tx_id = utils.hash256(self.to_bytes())[::-1].hex() self._make_immutable() if len(self) > 100000: raise ValueError( # pragma: no cover 'Tx is too large. ' 'Expect less than 100kB. Got: {} bytes'.format(len(self)))
def __init__(self, tx_ins, tx_outs, lock_time, expiry_height, tx_joinsplits, joinsplit_pubkey, joinsplit_sig): super().__init__() if 'overwinter' not in riemann.get_current_network_name(): raise ValueError( 'OverwinterTx not supported by network {}.' .format(riemann.get_current_network_name())) self.validate_bytes(lock_time, 4) self.validate_bytes(expiry_height, 4) if utils.le2i(expiry_height) > 499999999: raise ValueError('Expiry time too high.' 'Expected <= 499999999. Got {}' .format(utils.le2i(expiry_height))) for tx_in in tx_ins: if not isinstance(tx_in, TxIn): raise ValueError( 'Invalid TxIn. ' 'Expected instance of TxIn. Got {}' .format(type(tx_in).__name__)) for tx_out in tx_outs: if not isinstance(tx_out, TxOut): raise ValueError( 'Invalid TxOut. ' 'Expected instance of TxOut. Got {}' .format(type(tx_out).__name__)) if len(tx_joinsplits) > 5: raise ValueError('Too many joinsplits. Stop that.') for tx_joinsplit in tx_joinsplits: if not isinstance(tx_joinsplit, z.SproutJoinsplit): raise ValueError( 'Invalid Joinsplit. ' 'Expected instance of SproutJoinsplit. Got {}' .format(type(tx_joinsplit).__name__)) if len(tx_joinsplits) != 0: self.validate_bytes(joinsplit_pubkey, 32) self.validate_bytes(joinsplit_sig, 64) if len(tx_joinsplits) == 0 and len(tx_ins) == 0: raise ValueError('Transaction must have tx_ins or joinsplits.') self += b'\x03\x00\x00\x80' # Version 3 + fOverwintered self += b'\x70\x82\xc4\x03' # Overwinter Group ID self += shared.VarInt(len(tx_ins)) for tx_in in tx_ins: self += tx_in self += shared.VarInt(len(tx_outs)) for tx_out in tx_outs: self += tx_out self += lock_time self += expiry_height self += shared.VarInt(len(tx_joinsplits)) if len(tx_joinsplits) != 0: for tx_joinsplit in tx_joinsplits: self += tx_joinsplit self += joinsplit_pubkey self += joinsplit_sig self.header = b'\x03\x00\x00\x80' self.group_id = b'\x70\x82\xc4\x03' self.version = b'\x03\x00' self.tx_ins = tuple(tx_in for tx_in in tx_ins) self.tx_outs = tuple(tx_out for tx_out in tx_outs) self.lock_time = lock_time self.expiry_height = expiry_height if len(tx_joinsplits) != 0: self.tx_joinsplits = tuple(js for js in tx_joinsplits) self.joinsplit_pubkey = joinsplit_pubkey self.joinsplit_sig = joinsplit_sig # Zcash spec 5.4.1.4 Hsig hash function self.hsigs = (tuple(self._hsig(i) for i in range(len(self.tx_joinsplits)))) self.primary_inputs = (tuple(self._primary_input(i) for i in range(len(self.tx_joinsplits)))) else: self.tx_joinsplits = tuple() self.joinsplit_pubkey = None self.joinsplit_sig = None self.hsigs = tuple() self.primary_inputs = tuple() self.tx_id_le = self.tx_id_le = utils.hash256(self.to_bytes()) self.tx_id = self.tx_id_le[::-1] self._make_immutable() if len(self) > 100000: raise ValueError( # pragma: no cover 'Tx is too large. ' 'Expect less than 100kB. Got: {} bytes'.format(len(self)))
def parse_nbits(nbits_hex): nbits_bytes = bytes.fromhex(nbits_hex) exponent = rutils.be2i(nbits_bytes[-1:]) return rutils.le2i(nbits_bytes[:-1]) * 0xff**(exponent - 3)
def calculate_fee(self): return \ sum([utils.le2i(w.value) for w in self.tx_witnesses]) \ - sum([utils.le2i(o.value) for o in self.tx_outs])