Skip to content

Reference implementation of CT descriptors #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 23, 2023

Conversation

apoelstra
Copy link
Member

I'm aware that there are already a basic form of CT descriptors in this library, and this does not remove them, it just adds a new module which should act as a reference implementation for ElementsProject/ELIPs#1

WIP because it needs a new rust-elements release to get tagged hashes in bitcoin_hashes.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch from 6b678bb to 76ca5be Compare October 6, 2022 18:53
@apoelstra
Copy link
Member Author

Oops, missed some files. Added and force-pushed. I squashed everything because the individual commits weren't split out right; let me know if I should split them up again.

Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the Blinded descriptors now? Should this struct also be called Descriptor?

/// The first public key is included outside of the `Vec` to reduce allocations
/// and to enforce that the list of keys is not empty; but it is not special in
/// any way.
HashToSk(Pk, Vec<Pk>),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes creating the Key really annoying. Can we just have a Vec instead? To ensure that there is at least one element, we should wrap this in a struct and add constructors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, we should really make things convenient for users by adding some extra code and allocations here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, okay, agreed

let spk = self.descriptor.script_pubkey();
self.descriptor
.blinded_address(self.key.to_public_key(secp, &spk), params)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add other descriptor APIs later.

let mut addr = elements::Address::from_str("2dpWh6jbhAowNsQ5agtFzi7j6nKscj6UnEr").unwrap();
addr.blinding_pubkey = Some(mbk.blinding_key(&secp, &spk));
assert_eq!(addr.to_string(), "CTEkf75DFff5ReB7juTg2oehrj41aMj21kvvJaQdWsEAQohz1EDhu7Ayh6goxpz3GZRVKidTtaXaXYEJ");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know this code, but I tested this with elements 22.0 and it works as expected. Perhaps, you can also add this test?

    #[test]
    fn local_test_elements_22_0() {
        // Local test on elements 22.0
        let mbk = MasterBlindingKey::from_hex(
            "64269a8de756da06ebe35d26dccb4dd46bddcf858b54eeaae315490cfe6cacc0",
        )
        .unwrap();

        let addr = elements::Address::from_str(
            "el1qqg2pz79c0reryhr6hzxrzueju9m2asllwydrhexs6vj854cvwlen4tryh4thsdt2a26rte3fe87rf3my9t90wt78pcqrxv733",
        )
        .unwrap();

        let derived_blinding_key = mbk.blinding_private_key(&addr.script_pubkey());
        assert_eq!(
            derived_blinding_key,
            secp256k1_zkp::SecretKey::from_slice(&unhex(
                "791a1081ae2ad98a5ad603737c648247f19d3c26e2beb54617638172edb230e7"
            ))
            .unwrap()
        );
    }

@apoelstra
Copy link
Member Author

@sanket1729 regarding "should we remove the Blinded descriptors now", yes, and re "should this be called Descriptor, I think yes, the goal is to use it as confidential::Descirptor. We can also re-export it under a new name.

@sanket1729
Copy link
Member

@apoelstra, agreed on both things

@apoelstra
Copy link
Member Author

So, where I'm at with this is that I need

@sanket1729
Copy link
Member

sanket1729 commented Oct 18, 2022

Thanks for the enumerated list. Left some feedback on the rust-elements repo.

@sanket1729
Copy link
Member

@apoelstra, what is the status of this project/PR?

@apoelstra
Copy link
Member Author

@sanket1729 I intend to finish this and merge it. I think I've got everything I need now? Will try to do it over the weekend or early next week.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch from 7b35e15 to 47eee43 Compare January 6, 2023 15:36
@apoelstra
Copy link
Member Author

Rebase only.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch 3 times, most recently from 440aaa6 to c3b356c Compare January 31, 2023 21:26
@apoelstra apoelstra marked this pull request as ready for review January 31, 2023 21:26
@apoelstra
Copy link
Member Author

cc @sanket1729 I have finally finished this PR, with test vectors and everything.

No rush, but ready for review.

@LeoComandini
Copy link
Contributor

It would be nice to have elements_miniscript::confidential::Descriptor::parse_descriptor implemented so we can use descriptors strings with (extended) private keys. Now with from_str we can only parse descriptors with public keys. It would allow to create test vectors with private keys.

@apoelstra
Copy link
Member Author

On @LeoComandini out-of-band advice we may modify this scheme so that the "bare pubkey" variant p2c-commits to the scriptpubkey. Conveniently, even when the scriptpubkey is exposed alongside the resulting key, nobody can "undo" the p2c and learn the original public key. So we get very good on-chain privacy here, even when one participant in a multiparty setup is reusing keys.

Will update after some more thought. Writing here so I don't forget.

@sanket1729
Copy link
Member

Can now be rebased

apoelstra added 11 commits June 23, 2023 00:17
In a previous incarnation of CT descriptors we considered "musig" and
"slip77" expressions to constitute keys, allowing them to be parsed in
contexts where normally only keys would be allowed (and _not_ expanding
them as expression trees which would then need to be reconstructed).

This is still plausibly the direction we want to go in for MuSig keys,
but it never made a lot of sense for SLIP77, and indeed, we now want to
go a different direction, where SLIP77 expressions are allowed only as
the first argument of a CT descriptor.
@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch from c3b356c to a332208 Compare June 23, 2023 00:18
@apoelstra
Copy link
Member Author

Nice. I need to add some xpub-based test vectors but this is otherwise ready for review.

@apoelstra
Copy link
Member Author

cc @sanket1729 ready for review

Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 725f450.

Checked against the spec. Any reason why don't have wildcards in BlindedKey?

"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
);

// And hash of 100 bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 80 bytes

@apoelstra
Copy link
Member Author

Checked against the spec. Any reason why don't have wildcards in BlindedKey?

There isn't a type called BlindedKey, but I guess you mean, "why not have wildcards in the blinding key". The reason is a bit dumb -- it's because we have a generic keytype for the main descriptor, but for the blinding key we have either (a) a generic type, or (b) a DescriptorSecretKey.

Generic key types don't, in general, have wildcards. Normally, wildcards are handled by people taking a generic descriptor, using DescriptorPublicKey with it, and then using some convenience methods around Descriptor::translate_pk to map every pubkey to a DefiniteDescriptorKey at a particular index. The descriptor itself doesn't understand anything about indices.

So, when the descriptor has an extra "blinding key" which needs to be updated in tandem with the the rest of the keys, our generic-key model breaks down. I think the right way to handle this would be to link DescriptorPublicKey and DescriptorSecretKey somehow, so that we could say that CT view descriptors had a blinding key which was Pk::SecretKey or something rather than a concrete key type. Then for the specific case of DescriptorPublicKey, we could support deriving public and secret keys at the same time.

Does this make sense? Basically the issue is that we're mixing a generic keytype and a concrete one, and our API does not handle this gracefully.

@sanket1729
Copy link
Member

sanket1729 commented Jul 6, 2023

@apoelstra, I get the issues with DescriptorSecretKey. One of the main benefits of decsriptors is backing up and recovery.

I guess this is more a question of a spec. Perhaps, I am misunderstanding something here, if ct(xpub/*,wsh(ms)), would the backups not grow linearly instead of just a single copy?

@apoelstra
Copy link
Member Author

@sanket1729 I don't understand what you're saying. The spec allows wildcards. The reference implementation just doesn't support it because I didn't want to do the rearchitecture work required for it.

Copy link
Member

@delta1 delta1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 minor comment suggestions

impl MasterBlindingKey {
/// Compute a master blinding key from a seed
///
/// The recommended in (SLIP-39) source of this seed is to obtain the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The recommended in (SLIP-39) source of this seed is to obtain the
/// The recommended source of this seed (in SLIP-39) is to obtain the

test.check(&secp);
}
// Uncomment to regenerate test vectors; to see the output, run
// cargo test confidential::tests:;confidential_descriptor -- --nocapture
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// cargo test confidential::tests:;confidential_descriptor -- --nocapture
// cargo test confidential::tests::confidential_descriptor -- --nocapture

sanket1729
sanket1729 approved these changes Aug 23, 2023
@apoelstra
Copy link
Member Author

Going to merge this; can address comment nits in a followup PR.

@apoelstra apoelstra merged commit f887126 into ElementsProject:master Aug 23, 2023
@apoelstra apoelstra deleted the 2022-10--ct-descriptors branch August 23, 2023 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants