def _copy_kv(self, fout, psbtv, key): # find offset of the key if it exists off = psbtv.seek_to_value(key, from_current=True) if off is None: return # we found it - copy over ser_string(fout, key) l = compact.read_from(psbtv.stream) fout.write(compact.to_bytes(l)) read_write(psbtv.stream, fout, l) return off
def fill_pset_scope(self, scope, desc, stream=None, rangeproof_offset=None, surj_proof_offset=None): # if we don't have a rangeproof offset - nothing we can really do if rangeproof_offset is None: return True # pointer and length of preallocated memory for rangeproof rewind memptr, memlen = get_preallocated_ram() # for inputs we check if rangeproof is there # check if we actually need to rewind if None not in [ scope.asset, scope.value, scope.asset_blinding_factor, scope.value_blinding_factor ]: # verify that asset and value blinding factors lead to value and asset commitments return True stream.seek(rangeproof_offset) l = compact.read_from(stream) vout = scope.utxo if isinstance(scope, LInputScope) else scope.blinded_vout blinding_key = desc.blinding_key.get_blinding_key( vout.script_pubkey).secret # get the nonce for unblinding pub = secp256k1.ec_pubkey_parse(vout.ecdh_pubkey) secp256k1.ec_pubkey_tweak_mul(pub, blinding_key) sec = secp256k1.ec_pubkey_serialize(pub) nonce = hashlib.sha256(hashlib.sha256(sec).digest()).digest() commit = secp256k1.pedersen_commitment_parse(vout.value) gen = secp256k1.generator_parse(vout.asset) try: value, vbf, msg, _, _ = secp256k1.rangeproof_rewind_from( stream, l, memptr, memlen, nonce, commit, vout.script_pubkey.data, gen) except ValueError as e: raise RewindError(str(e)) asset = msg[:32] abf = msg[32:64] scope.value = value scope.value_blinding_factor = vbf scope.asset = asset scope.asset_blinding_factor = abf return True
def aead_decrypt(ciphertext: bytes, key: bytes) -> tuple: """ Verifies MAC and decrypts ciphertext with associated data. Inverse to aead_encrypt Returns a tuple adata, plaintext """ mac = ciphertext[-32:] ct = ciphertext[:-32] aes_key = tagged_hash("aes", key) hmac_key = tagged_hash("hmac", key) assert mac == hmac.new(hmac_key, msg=ct, digestmod="sha256").digest() b = BytesIO(ct) l = compact.read_from(b) adata = b.read(l) assert len(adata) == l ct = b.read() if len(ct) == 0: return adata, b"" return adata, decrypt(ct, aes_key)
def aead_decrypt(ciphertext: bytes, key: bytes) -> tuple: """ Verifies MAC and decrypts ciphertext with associated data. Inverse to aead_encrypt Returns a tuple adata, plaintext """ mac = ciphertext[-32:] ct = ciphertext[:-32] aes_key = tagged_hash("aes", key) hmac_key = tagged_hash("hmac", key) if mac != hmac.new(hmac_key, ct, digestmod="sha256").digest(): raise Exception("Invalid HMAC") b = BytesIO(ct) l = compact.read_from(b) adata = b.read(l) if len(adata) != l: raise Exception("Invalid length") ct = b.read() if len(ct) == 0: return adata, b"" return adata, decrypt(ct, aes_key)
def preprocess_psbt(self, stream, fout): """ Processes incoming PSBT, fills missing information and writes to fout. Returns: - wallets in inputs: dict {wallet: amount} - metadata for tx display including warnings that require user confirmation - default sighash to use for signing """ self.show_loader(title="Parsing transaction...") # compress = True flag will make sure large fields won't be loaded to RAM psbtv = self.PSBTViewClass.view(stream, compress=True) # Write global scope first psbtv.stream.seek(psbtv.offset) res = read_write(psbtv.stream, fout, psbtv.first_scope-psbtv.offset) # string representation of the Bitcoin for wallet processing fee = 0 # here we will store all wallets that we detect in inputs # {wallet: {"amount": amount, "gaps": [gaps]}} wallets = {} meta = { "inputs": [{} for i in range(psbtv.num_inputs)], "outputs": [{} for i in range(psbtv.num_outputs)], "default_asset": "BTC" if self.network == "main" else "tBTC", } fingerprint = self.keystore.fingerprint # We need to detect wallets owning inputs and outputs, # in case of liquid - unblind them. # Fill all necessary information: # For Bitcoin: bip32 derivations, witness script, redeem script # For Liquid: same + values, assets, commitments, proofs etc. # At the end we should have the most complete PSBT / PSET possible for i in range(psbtv.num_inputs): self.show_loader(title="Parsing input %d..." % i) # load input to memory, verify it (check prevtx hash) inp = psbtv.input(i) metainp = meta["inputs"][i] # verify, do not require non_witness_utxo if witness_utxo is set inp.verify(ignore_missing=True) # check sighash in the input if inp.sighash_type is not None and inp.sighash_type != self.DEFAULT_SIGHASH: metainp["sighash"] = self.get_sighash_info(inp.sighash_type)["name"] self.fill_zero_fingerprint(inp) # Find wallets owning the inputs and fill scope data: # first we check already detected wallet owns the input # as in most common case all inputs are owned by the same wallet. wallet = None gaps = None for w in wallets: # pass rangeproof offset if it's in the scope if w and w.fill_scope(inp, fingerprint): wallet = w break if wallet is None: # find wallet and append it to wallets for w in self.wallets: # pass rangeproof offset if it's in the scope if w.fill_scope(inp, fingerprint): wallet = w break if wallet: gaps = [g for g in wallet.gaps] # copy res = wallet.get_derivation(inp.bip32_derivations) if res: idx, branch_idx = res gaps[branch_idx] = max(gaps[branch_idx], idx+wallet.GAP_LIMIT+1) # add wallet to tx wallets dict if wallet not in wallets: wallets[wallet] = {"amount": 0, "gaps": gaps} else: if wallets[wallet]["gaps"] is not None and gaps is not None: wallets[wallet]["gaps"] = [max(g1,g2) for g1,g2 in zip(gaps, wallets[wallet]["gaps"])] value = inp.utxo.value fee += value wallets[wallet]["amount"] = wallets.get(wallet, {}).get("amount") + value metainp.update({ "label": wallet.name if wallet else "Unknown wallet", "value": value, }) if wallet and wallet.is_watchonly: metainp["label"] += " (watch-only)" # write non_witness_utxo separately if it exists (as we use compressed psbtview) non_witness_utxo_off = None off = psbtv.seek_to_scope(i) non_witness_utxo_off = psbtv.seek_to_value(b'\x00', from_current=True) if non_witness_utxo_off: non_witness_utxo_off += off l = compact.read_from(psbtv.stream) fout.write(b"\x01\x00") fout.write(compact.to_bytes(l)) read_write(psbtv.stream, fout, l) inp.write_to(fout, version=psbtv.version) # parse all outputs for i in range(psbtv.num_outputs): self.show_loader(title="Parsing output %d..." % i) out = psbtv.output(i) metaout = meta["outputs"][i] self.fill_zero_fingerprint(out) wallet = None for w in wallets: # pass rangeproof offset if it's in the scope if w and w.fill_scope(out, fingerprint): wallet = w break if wallet is None: # find wallet and append it to wallets for w in self.wallets: if w.fill_scope(out, fingerprint): wallet = w break # Get values and store in metadata and wallets dict value = out.value fee -= value metaout.update({ "change": (wallet is not None and len(wallets) == 1 and wallet in wallets), "value": value, "address": self.get_address(out), }) if wallet: metaout["label"] = wallet.name res = wallet.get_derivation(out.bip32_derivations) if res: idx, branch_idx = res branch_txt = "" if branch_idx == 1: "change " elif branch_idx > 1: "branch %d " % branch_idx metaout["label"] = "%s %s#%d" % (wallet.name, branch_txt, idx) if wallet in wallets: allowed_idx = wallets[wallet]["gaps"][branch_idx] else: allowed_idx = wallet.gaps[branch_idx] if allowed_idx <= idx: metaout["warning"] = "Derivation index is by %d larger than last known used index %d!" % (idx-allowed_idx+wallet.GAP_LIMIT, allowed_idx-wallet.GAP_LIMIT) if wallet.is_watchonly: metaout["warning"] = "Watch-only wallet!" out.write_to(fout, version=psbtv.version) meta["fee"] = fee return wallets, meta