rust-bitcoin / rust-miniscript

Support for Miniscript and Output Descriptors for rust-bitcoin
Creative Commons Zero v1.0 Universal
352 stars 139 forks source link

Using KeyMap and DescriptorSecretKey for signing transactions #495

Open evd0kim opened 1 year ago

evd0kim commented 1 year ago

From an outsider point of view KeyMap looks like interesting way to handle keys after importing descriptor.

Let's say for example we have such descriptor where we supply tprv which will be imported into KeyMap

"wsh(t:or_c(pk(02a7d30ac6b0cd55b6868f5a65aff1dbdaa18f4315fe04de809fffb2899340ae0f),v:multi(1,[255f0d9b]tprv8ZgxMBicQKsPerMTN7nhwCpqaAEPkaBCDeE8c9ekAY1xhPmF3HM9r3oz33V8JTwsJYp9FYLDQumLzNssLo6UNMNesVwPP1KcgA5atrSqU2w/84'/1'/1/0/0,02f17cce1778c101e3b0ac5c76997af3752b0a221b1ebffc23af679328f3f5d6fc,02350b5a9a12df96579d150f3f751de416e58a67f2ba9bc99f6964b7a48a79042b)))"

We could use KeyMap later in similar loop

        for (public, private) in &keys {
                  // ... doing something with keys
        }

The problem though is that public and private are DescriptorPublicKey and DescriptorSecretKey objects do not really contain convenient API even for extracting keys and using them in signing. I have a code example and honestly I am not entirely sure I didn't something wrong. Besides It doesn't actually work and I think the problem is that there is just wrong key in &prv.xkey.private_key.

        for (pk, prv) in &self.keys {
            match prv {
                DescriptorSecretKey::Single(prv) => {
                    let sig = self.secp.sign_ecdsa(&msg, &prv.key.inner);
                    let public = &prv.key.inner.public_key(&self.secp);
                    assert!(self.secp.verify_ecdsa(&msg, &sig, public).is_ok());
                    psbt.inputs[0].partial_sigs.insert(
                        public.to_public_key(),
                        bitcoin::EcdsaSig {
                            sig,
                            hash_ty: hash_ty,
                        },
                    );
                },
                DescriptorSecretKey::XPrv(prv) => {
                    let sig = self.secp.sign_ecdsa(&msg, &prv.xkey.private_key);
                    let public = &prv.xkey.private_key.public_key(&self.secp);
                    println!("{}", &public);
                    assert!(self.secp.verify_ecdsa(&msg, &sig, public).is_ok());
                    psbt.inputs[0].partial_sigs.insert(
                        public.to_public_key(),
                        bitcoin::EcdsaSig {
                            sig,
                            hash_ty: hash_ty,
                        },
                    );
                },
            };
        }
apoelstra commented 1 year ago

This is an interesting API question. I think the most common signing workflow we (the rust-minisrcipt devs) encounter involves iterating over public keys, then requesting a HWW produce a signature with those keys.

To do this directly with private keys we would need an API that could produce derived secret keys .. presumably a variant of the Satisfier API that could look up signatures based on secret keys rather than on public ones? We deliberately designed the API to discourage people "just using DescriptorSecretKey as the Pk type and then using Satisfier" ... that might work, but we tried not to make it easy :P.

There is also an open question about how much sighashing support or PSBT support belongs in this libarry.

LLFourn commented 1 year ago

I'd be +1 on making a KeyMap a Satisifer. You'd need to couple it with the sighash cache of the transaction and the txout it's spending I think. Another problem mention in #481 is that we'd need the merkle root passed into the satisfier for tapkey spends.

apoelstra commented 1 year ago

You'd need to couple it with the sighash cache of the transaction and the txout

Maybe we should have a structure which paired these, and added a with_ method to KeyMap, so you could do something like descriptor.satisfy(keymap.with(&sighash_cache)). This would be more discoverable than implementing Satisfier on a tuple or something, though still not as discoverable as I'd like ... I wish that we could customize the Rust error messages so that attempts to satisfy with a bare keymap would advise this.

Another problem mention in #481 is that we'd need the merkle root passed into the satisfier for tapkey spends

Ah right! We should make a separate issue about this because I keep forgetting it (and even when I'm reminded, my first impulse is to say "but we provide the leafhash!" ... but you're talking about keyspends, not scriptspends).

evd0kim commented 1 year ago

Thanks for comments. I am struggle to keep up with you. I have no idea about Satisfier yet. So I will try to learn more about it.

Now I understand that KeyMap doesn't really fit my purpose however, I should say that it is not created when there is no private keys provided in the descriptor. So I can't understand what you mean when saying about iterating over public keys and asking for HWW to sign something externally. On the other hand I could probably cast somehow DescriptorSecretKey into ExtendedPrivKey and work with it similarly to my PSBT code without KeyMap.

apoelstra commented 1 year ago

Now I understand that KeyMap doesn't really fit my purpose

No, but maybe it should :)

So I can't understand what you mean when saying about iterating over public keys and asking for HWW to sign something externally.

What I'm saying is that I never use KeyMap at all because I never have secret keys in software. I identify keys by their public key, and when I want to sign I just give the public key to a hardware wallet and let it figure out what to do with it.

On the other hand I could probably cast somehow DescriptorSecretKey into ExtendedPrivKey

Yes, the DescriptorSecretKey structure is completely public so you can destructure it and extract the key out directly. But we ought to provide a convenience method for this at least.

evd0kim commented 1 year ago

Okay. Thank you.

I was able to use KeyMap after thinking clearly about the structures and not just poking into them nearly randomly.

                DescriptorSecretKey::XPrv(prv) => {
                    let extended = prv.xkey;
                    let private = extended.derive_priv(&self.secp, &prv.derivation_path).unwrap();
                    let public = private.private_key.public_key(&self.secp);
                    let sig = self.secp.sign_ecdsa(&msg, &private.private_key);
                    assert!(self.secp.verify_ecdsa(&msg, &sig, &public).is_ok());
                    psbt.inputs[0].partial_sigs.insert(
                        public.to_public_key(),
                        bitcoin::EcdsaSig {
                            sig,
                            hash_ty: hash_ty,
                        },
                    );
                },

I never have secret keys in software.

I understood it. This is my next step to try to export extended keys from various of hardware wallets and apps and sign transactions using descriptors with public keys. It was quite handy to me to design in-software signing because I am going to prototype something not just for signing transactions exclusively.