def test_sign_verify(self): unsigned = PSBT.from_string(B64PSBT) signed = PSBT.from_string(B64SIGNED) tx = unsigned.tx values = [inp.utxo.value for inp in unsigned.inputs] scripts = [inp.utxo.script_pubkey for inp in unsigned.inputs] for i, inp in enumerate(signed.inputs): wit = inp.final_scriptwitness.items[0] sig = SchnorrSig.parse(wit[:64]) if len(wit) == 65: sighash = wit[64] else: sighash = SIGHASH.DEFAULT hsh = tx.sighash_taproot(i, script_pubkeys=scripts, values=values, sighash=sighash) pub = PublicKey.from_xonly(inp.utxo.script_pubkey.data[2:]) self.assertTrue(pub.schnorr_verify(sig, hsh)) # check signing and derivation prv = ROOT.derive([0, i]) tweaked = prv.taproot_tweak(b"") self.assertEqual(pub.xonly(), tweaked.xonly()) sig2 = tweaked.schnorr_sign(hsh) self.assertTrue(pub.schnorr_verify(sig2, hsh)) self.assertEqual(sig, sig2) # sign with individual pks sigcount = unsigned.sign_with(prv.key, SIGHASH.ALL) self.assertEqual(sigcount, 1) self.assertEqual(unsigned.inputs[i].final_scriptwitness, signed.inputs[i].final_scriptwitness) for i, inp in enumerate(unsigned.inputs): prv = ROOT.derive([0, i]) # remove final scriptwitness to test signing with root inp.final_scriptwitness = None # populate derivation inp.bip32_derivations[prv.key.get_public_key()] = DerivationPath( ROOT.my_fingerprint, [0, i]) # test signing with root key counter = unsigned.sign_with(ROOT, SIGHASH.ALL) self.assertEqual(counter, 2) for inp1, inp2 in zip(unsigned.inputs, signed.inputs): self.assertEqual(inp1.final_scriptwitness, inp2.final_scriptwitness) inp1.final_scriptwitness = None # test signing with psbtview, unsigned already has derivations stream = BytesIO(unsigned.serialize()) psbtv = PSBTView.view(stream, compress=False) sigs = BytesIO() sigcount = psbtv.sign_with(ROOT, sigs, SIGHASH.ALL) self.assertEqual(sigcount, len(unsigned.inputs)) v = sigs.getvalue() # check sigs are in the stream for inp in signed.inputs: self.assertTrue(inp.final_scriptwitness.items[0] in v)
def test_sign(self): """Test if we can sign psbtview and get the same as from signing psbt""" for compress in [True, False]: for b64 in PSBTS: psbt = PSBT.from_string(b64, compress=compress) stream = BytesIO(a2b_base64(b64)) psbtv = PSBTView.view(stream, compress=compress) # incomplete psbtview xpsbt = PSBT.from_string(b64, compress=compress) # remove derivations and other important data from original for sc in xpsbt.inputs: sc.bip32_derivations = {} sc.witness_script = None sc.redeem_script = None xstream = BytesIO(xpsbt.serialize()) xpsbtv = PSBTView.view(xstream, compress=compress) csigs1 = psbt.sign_with(ROOT) sigs_stream2 = BytesIO() csigs2 = psbtv.sign_with(ROOT, sigs_stream2) # signing incomplete psbtview sigs_stream3 = BytesIO() csigs3 = 0 for i in range(xpsbtv.num_inputs): csigs3 += xpsbtv.sign_input( i, ROOT, sigs_stream3, extra_scope_data=psbt.inputs[i]) # add separator sigs_stream3.write(b"\x00") self.assertEqual(csigs1, csigs2) self.assertEqual(csigs1, csigs3) for sigs_stream in [sigs_stream2, sigs_stream3]: sigs_stream.seek(0) # check all sigs signed_inputs = [ InputScope.read_from(sigs_stream) for i in range(len(psbt.inputs)) ] self.assertEqual(len(signed_inputs), len(psbt.inputs)) for i, inp in enumerate(signed_inputs): inp2 = psbt.inputs[i] self.assertEqual(inp.partial_sigs, inp2.partial_sigs) # check serialization with signatures sigs_stream.seek(0) ser = BytesIO() psbtv.write_to(ser, extra_input_streams=[sigs_stream2]) self.assertEqual(ser.getvalue(), psbt.serialize()) # check compress reduces the size of psbt sigs_stream2.seek(0) ser2 = BytesIO() psbtv.write_to(ser2, extra_input_streams=[sigs_stream2], compress=True) self.assertTrue(len(ser2.getvalue()) < len(ser.getvalue()))
def clean_psbt(b64psbt): try: psbt = PSBT.from_string(b64psbt) except: psbt = PSET.from_string(b64psbt) for inp in psbt.inputs: if inp.witness_utxo is not None and inp.non_witness_utxo is not None: inp.non_witness_utxo = None return psbt.to_string()
def sign_with_descriptor(self, d1, d2, root): rpc = daemon.rpc wname = random_wallet_name() # to derive addresses desc1 = Descriptor.from_string(d1) desc2 = Descriptor.from_string(d2) # recv addr 2 addr1 = desc1.derive(2).address(net) # change addr 3 addr2 = desc2.derive(3).address(net) # to add checksums d1 = add_checksum(str(d1)) d2 = add_checksum(str(d2)) rpc.createwallet(wname, True, True) w = daemon.wallet(wname) res = w.importmulti([{ "desc": d1, "internal": False, "timestamp": "now", "watchonly": True, "range": 10, }, { "desc": d2, "internal": True, "timestamp": "now", "watchonly": True, "range": 10, }], {"rescan": False}) self.assertTrue(all([k["success"] for k in res])) wdefault = daemon.wallet() wdefault.sendtoaddress(addr1, 0.1) daemon.mine() psbt = w.walletcreatefundedpsbt([], [{ wdefault.getnewaddress(): 0.002 }], 0, { "includeWatching": True, "changeAddress": addr2 }, True) unsigned = psbt["psbt"] psbt = PSBT.from_string(unsigned) psbt.sign_with(root) final = rpc.finalizepsbt(str(psbt)) self.assertTrue(final["complete"]) # test accept res = rpc.testmempoolaccept([final["hex"]]) self.assertTrue(res[0]["allowed"])
def create_psbts(self, base64_psbt, wallet): try: # remove rangeproofs and add sighash alls psbt = PSET.from_string(base64_psbt) for out in psbt.outputs: out.range_proof = None out.surjection_proof = None for inp in psbt.inputs: if not inp.sighash_type: inp.sighash_type = LSIGHASH.ALL base64_psbt = psbt.to_string() except: pass psbts = super().create_psbts(base64_psbt, wallet) # remove non-witness utxo if they are there to reduce QR code size updated_psbt = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=False) try: qr_psbt = PSBT.from_string(updated_psbt) except: qr_psbt = PSET.from_string(updated_psbt) # find my key fgp = None derivation = None for k in wallet.keys: if k in self.keys and k.fingerprint and k.derivation: fgp = bytes.fromhex(k.fingerprint) derivation = bip32.parse_path(k.derivation) break # remove unnecessary derivations from inputs and outputs for inp in qr_psbt.inputs + qr_psbt.outputs: # keep only my derivation for k in list(inp.bip32_derivations.keys()): if fgp and inp.bip32_derivations[k].fingerprint != fgp: inp.bip32_derivations.pop(k, None) # remove scripts from outputs (DIY should know about the wallet) for out in qr_psbt.outputs: out.witness_script = None out.redeem_script = None # remove partial sigs from inputs for inp in qr_psbt.inputs: inp.partial_sigs = {} psbts["qrcode"] = qr_psbt.to_string() # we can add xpubs to SD card, but non_witness can be too large for MCU psbts["sdcard"] = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True) return psbts
def convert_rawtransaction_to_psbt(wallet_rpc, rawtransaction) -> str: """ Converts a raw transaction in HEX format into a PSBT in b64 format """ tx = Transaction.from_string(rawtransaction) psbt = PSBT(tx) # this empties the signatures psbt = wallet_rpc.walletprocesspsbt(str(psbt), False).get("psbt", str(psbt)) psbt = PSBT.from_string(psbt) # we need the class object again # Recover signatures (witness or scriptsig) if available in raw tx for vin, psbtin in zip(tx.vin, psbt.inputs): if vin.witness: psbtin.final_scriptwitness = vin.witness if vin.script_sig: psbtin.final_scriptsig = vin.script_sig b64_psbt = str(psbt) return b64_psbt
def test_scopes(self): """Tests that PSBT and PSBTView result in the same scopes and other constants""" for compress in [True, False]: for b64 in PSBTS: psbt = PSBT.from_string(b64, compress=compress) stream = BytesIO(a2b_base64(b64)) psbtv = PSBTView.view(stream, compress=compress) self.assertEqual(len(psbt.inputs), psbtv.num_inputs) self.assertEqual(len(psbt.outputs), psbtv.num_outputs) self.assertEqual(psbt.version, psbtv.version) if psbt.version != 2: self.assertTrue(psbtv.tx_offset > 0) # check something left when seek to n-1 psbtv.seek_to_scope(psbtv.num_inputs + psbtv.num_outputs - 1) self.assertEqual(len(stream.read(1)), 1) # check nothing left in the stream when seeking to the end psbtv.seek_to_scope(psbtv.num_inputs + psbtv.num_outputs) self.assertEqual(stream.read(1), b"") # check that all scopes are the same in psbt and psbtv # check random input scope first idx = len(psbt.inputs) // 2 self.assertEqual(psbt.inputs[idx].serialize(), psbtv.input(idx).serialize()) # check input scopes sequentially for i, inp in enumerate(psbt.inputs): self.assertEqual(inp.serialize(), psbtv.input(i).serialize()) self.assertEqual(inp.vin.serialize(), psbtv.vin(i).serialize()) # check random output scope idx = len(psbt.outputs) // 2 self.assertEqual(psbt.outputs[idx].serialize(), psbtv.output(idx).serialize()) # check input scopes sequentially for i, out in enumerate(psbt.outputs): self.assertEqual(out.serialize(), psbtv.output(i).serialize()) self.assertEqual(out.vout.serialize(), psbtv.vout(i).serialize()) self.assertEqual(psbt.tx_version, psbtv.tx_version) self.assertEqual(psbt.locktime, psbtv.locktime)
def create_psbts(self, base64_psbt, wallet): # liquid transaction if base64_psbt.startswith("cHNl"): # remove rangeproofs and add sighash alls psbt = PSET.from_string(base64_psbt) # make sure we have tx blinding seed in the transaction if psbt.unknown.get(b"\xfc\x07specter\x00"): for out in psbt.outputs: out.range_proof = None # out.surjection_proof = None # we know assets - we can blind it if out.asset: out.asset_commitment = None out.asset_blinding_factor = None # we know value - we can blind it if out.value: out.value_commitment = None out.value_blinding_factor = None for inp in psbt.inputs: if inp.value and inp.asset: inp.range_proof = None else: psbt = PSBT.from_string(base64_psbt) fill_external_wallet_derivations(psbt, wallet) base64_psbt = psbt.to_string() psbts = super().create_psbts(base64_psbt, wallet) # remove non-witness utxo if they are there to reduce QR code size updated_psbt = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=False, taproot_derivations=True) try: qr_psbt = PSBT.from_string(updated_psbt) except: qr_psbt = PSET.from_string(updated_psbt) # find my key fgp = None derivation = None for k in wallet.keys: if k in self.keys and k.fingerprint and k.derivation: fgp = bytes.fromhex(k.fingerprint) derivation = bip32.parse_path(k.derivation) break # remove unnecessary derivations from inputs and outputs for inp in qr_psbt.inputs + qr_psbt.outputs: # keep only one derivation path (idealy ours) found = False pubkeys = list(inp.bip32_derivations.keys()) for i, pub in enumerate(pubkeys): if fgp and inp.bip32_derivations[pub].fingerprint != fgp: # only pop if we already saw our derivation # or if it's not the last one if found or i < len(pubkeys) - 1: inp.bip32_derivations.pop(k, None) else: found = True # remove scripts from outputs (DIY should know about the wallet) for out in qr_psbt.outputs: out.witness_script = None out.redeem_script = None # remove partial sigs from inputs for inp in qr_psbt.inputs: inp.partial_sigs = {} psbts["qrcode"] = qr_psbt.to_string() # we can add xpubs to SD card, but non_witness can be too large for MCU psbts["sdcard"] = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True, taproot_derivations=True) psbts["hwi"] = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True, taproot_derivations=True) return psbts