Skip to content

Add child key pair generation api #154

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

Conversation

dhruv-1001
Copy link
Contributor

@dhruv-1001 dhruv-1001 commented May 15, 2022

Completes #87

@dhruv-1001 dhruv-1001 marked this pull request as ready for review May 15, 2022 22:03
@dhruv-1001 dhruv-1001 marked this pull request as draft May 15, 2022 22:04
@dhruv-1001
Copy link
Contributor Author

@notmandatory @artfuldev please review this draft.

src/bdk.udl Outdated
@@ -3,6 +3,8 @@ namespace bdk {
ExtendedKeyInfo generate_extended_key(Network network, WordCount word_count, string? password);
[Throws=BdkError]
ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password);
[Throws=BdkError]
ChildKeyPair derive_child_key_pair(string str_xprv, string str_path);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we not need to expose ExtendedPrivKey and DerivationPath? @notmandatory

Copy link
Contributor

Choose a reason for hiding this comment

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

@notmandatory if this is the only way to create a child key pair, can it be a constructor?

Copy link
Contributor Author

@dhruv-1001 dhruv-1001 May 16, 2022

Choose a reason for hiding this comment

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

I modified the bdk-ff commit that the bdk-kotlin uses, and made same changes. After that, I added test for the deriveChildKeyPair() function

@Test
fun validChildKeys() {
    val str_xprv = "tprv8ZgxMBicQKsPdMTRB73kkTfC665bK3JL33eAFZ3MUiGZj8jniKQroG2yuamoQcRb24GG8dKRmVgaisjcZrDc9L3fyMf2NW19oajw8iWSwMV"
    val str_path = "m/84'/1'/0'/0"
    val childKeyPair = deriveChildKeyPair(str_xprv, str_path)
    assertEquals(
        "[59a3697d/84'/1'/0'/0]tprv8hZWYaWfKprzVnSVaaxwYun2yVp9TgpvcosjrGYwgL9hKMU1Jsrp3xKsNCpxSNBSEgUPp7c8A1MYLQcMcq7Ab9eafzNZUHQaBtGBvfCQiMg/*",
        childKeyPair.xprv
    )
    assertEquals(
        "[59a3697d/84'/1'/0'/0]tpubDEFYgzYuUCYfPFUHUEdXxKS9YXL5d21qC7UX8nbF6bx69qimwGgQESwjYN52awKwDd5PWGKXWAw27EBUk6QzSK3eEwsK8odY8FxVn4poRDp/*",
        childKeyPair.xpub
    )
}

And It worked

(I used the CLI to derive this result first, and used the same key for testing)

Copy link
Contributor

Choose a reason for hiding this comment

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

Not discounting that it works, just checking if the types are okay or if we need something more elaborate. and if child key pair could have a constructor instead of this function deriveChildKeyPair. also wondering if ChildKeyPair is what we want to expose - it could be any key pair, not necessarily only a "child" key pair. just thinking out loud.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to keep the types and design for this simple to start and not add additional types for ExtendedPrivKey and DerivationPath. They can always be added later if we find a need, I think most apps will 1. generate_extended_key and get the user to write down the mnemonic, 2) from the ExtendedKeyInfo.xprv or possibly from importing a xprv from somewhere else, derive a descriptor key, and 3) create a descriptor for the wallet, currently needs to be done putting strings together but in future we can make it simpler with some standard/common templates. That all said, I have two naming change suggestions:

  1. rename ChildKeyPair to DescriptorKeyInfo which is more in line with what bdk calls it
  2. rename arguments str_xprv and str_path to simply xprv and path since type is already enforced thus redundant in the variable name.

As for creating the ChildKeyPair or DescriptorKeyInfo with a constructor, if we do that we need to change it from dictionary (pass by value) to an interface (pass by reference), so I'd prefer to keep it a dictionary and not use a constructor.

Copy link
Member

@notmandatory notmandatory May 17, 2022

Choose a reason for hiding this comment

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

One other suggestion, in the case someone has a watch-only wallet then they won't have the xprv for the DescriptorKeyInfo and will only have the xpub. So I think you should make xprv optional, ie. string? xprv and xprv: Option<String>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted. Will force push the changes soon.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks @notmandatory

Copy link
Contributor

@artfuldev artfuldev left a comment

Choose a reason for hiding this comment

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

Just have some questions, mostly for @notmandatory

src/lib.rs Outdated
if let Secret(desc_seckey, _, _) = derived_xprv_desc_key {
let desc_pubkey = desc_seckey
.as_public(&secp)
.map_err(|e| Error::Generic(e.to_string()))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

The bdk-cli code here uses .unwrap() - @notmandatory can you add some context?

Copy link
Member

@notmandatory notmandatory May 17, 2022

Choose a reason for hiding this comment

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

The bdk-cli code here uses .unwrap() - @notmandatory can you add some context?

I think maybe you're looking at an older version of bdk-cli 🙂 this is how the cli code works also without an .unwrap().

src/bdk.udl Outdated
@@ -3,6 +3,8 @@ namespace bdk {
ExtendedKeyInfo generate_extended_key(Network network, WordCount word_count, string? password);
[Throws=BdkError]
ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password);
[Throws=BdkError]
ChildKeyPair derive_child_key_pair(string str_xprv, string str_path);
Copy link
Contributor

Choose a reason for hiding this comment

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

@notmandatory if this is the only way to create a child key pair, can it be a constructor?

@dhruv-1001
Copy link
Contributor Author

@artfuldev @notmandatory any suggestions here?

src/lib.rs Outdated
network: Network,
word_count: WordCount,
password: Option<String>,
) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we retain Result<T, E> semantics here? Did we remove it intentionally? I'd prefer not having .unwrap() and throwing exceptions in Kotlin etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, will add back the Result<T, E> and force push the changes.

@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch 2 times, most recently from 8069624 to b2b3a8c Compare June 23, 2022 17:27
src/bdk.udl Outdated
};

interface ExtendedPubKey {
string to_string();
Copy link

@kirillzh kirillzh Jun 28, 2022

Choose a reason for hiding this comment

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

Looks like this won't compile in Kotlin since toString() is an open, member function of Any that needs to be overriden and uniffi doesn't seem to have a way to declare an overriding API, as far as I can tell. A simple workaround here would be to rename to_string() to something else (maybe ExtendedPubKey#value or #raw read-only property?
Screen Shot 2022-06-27 at 6 37 39 PM
)

Choose a reason for hiding this comment

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

Filed an issue with uniffi to see if it's feasible to allow adding override modifier in Kotlin: mozilla/uniffi-rs#1286.

@notmandatory
Copy link
Member

Overall looks good, I commented on a few places you can remove the .unwrap() and use map_error and map to generate your Result. There are a few others I didn't comment on, but in general try to avoid .unwrap() if you can, except in tests where it's OK.

@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch from b2b3a8c to e538b72 Compare June 30, 2022 17:43
@dhruv-1001 dhruv-1001 marked this pull request as ready for review June 30, 2022 17:43
@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch from e538b72 to 2a2ffde Compare June 30, 2022 17:49
@notmandatory
Copy link
Member

notmandatory commented Jul 15, 2022

I had a little chat with @thunderbiscuit and @dhruv-1001 and it looks like the logic in this PR probably does belong in bdk. I'm going to take a stab at adding DescriptorKey::derive() and DescriptorKey::extend() functions in bdk. Then we can review and discuss there and expose what ever API we come up with to bdk-ffi. How does that sound @quad ?

@notmandatory
Copy link
Member

@quad I've created a draftbdk PR to explore what I had in mind for an API. The main diff I think with what you have is that I'm doing everything at the DescriptorKey level. Let me know what you and your team think and/or we can jump on a call to discuss.

bitcoindevkit/bdk#670

Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

Overall looks good! I'd just like the match statements cleaned up to avoid returning None and then unwrap(), which I don't think is as clear.

@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch from 2ab9e09 to e77e48c Compare August 11, 2022 09:02
@dhruv-1001 dhruv-1001 marked this pull request as ready for review August 16, 2022 12:03
@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch from e77e48c to 5bc3811 Compare August 17, 2022 13:23
Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

ACK 5bc3811

Good work! this one was a challenging API to simplify for FFI and I think this way strikes the right balance.

@notmandatory notmandatory added this to the Release 0.9.0 milestone Aug 17, 2022
@notmandatory notmandatory added enhancement New feature or request summer-of-bitcoin Summer of Bitcoin Project labels Aug 17, 2022
@dhruv-1001 dhruv-1001 force-pushed the add-child-key-generation-api branch from 5bc3811 to 5944756 Compare August 17, 2022 22:54
@notmandatory notmandatory merged commit 7da2865 into bitcoindevkit:master Aug 18, 2022
@notmandatory
Copy link
Member

Note to anyone following this issue, as discussed in bitcoindevkit/bdk#670 we decided not to put this logic in the bdk lib, but once this PR is in I'll add some code example in the bdk repo, it should be only a couple lines to generate, derive or extend a DescriptorPublicKey or DescriptorSecretKey.

@ConorOkus ConorOkus added the ldk-compatibility Compatibility with the Lightning Dev Kit label Aug 31, 2022
@dhruv-1001 dhruv-1001 mentioned this pull request Oct 28, 2022
5 tasks
notmandatory added a commit that referenced this pull request Nov 7, 2022
9866649 Added Mnemonic Interface (dhruvbaliyan)

Pull request description:

  ### Description
  This PR adds `interface Mnemonic` which will make the API to generate new DescriptorSecretKey have type-safe arguments.

  <!-- Describe the purpose of this PR, what's being adding and/or fixed -->

  ### Notes to the reviewers

  This PR doesn't have any issue linked to it, as it was discusses on a call during the implementation of `DescriptorSecretKey` (PR #154). It was discussed to make `Mnemonic` an interface and use that instead of string `Mnemonic` so that the API to generate `DescriptorSecretKey` doesn't have any potential failure (like in case it's provided with incorrect Mnemonic words).

  APIs added
  ```
  // generates and returns Mnemonic with random entropy
  Mnemonic::new(word_count: WordCount) -> Self { ... }
  // converts string Mnemonic to Mnemonic type with error (in case of incorrect string Mnemonic)
  Mnemonic::from_str(mnemonic: String) -> Result<Self, BdkError> { ... }
  // generates and returns Mnemonic with given entropy
  Mnemonic::from_entropy(entropy: Vec<u8>) -> Result<Self, BdkError> {...}
  // view mnemonic as string
  Mnemonic::as_string(&self) -> String { ... }
  ```
  Along with some changes to `DescriptorSecretKey::new()` to fit these new APIs

  ### Changelog notice
  ```
  - Added Struct Mnemonic with following methods [#219]
    - new(word_count: WordCount) generates and returns Mnemonic with random entropy
    - from_str(mnemonic: String) converts string Mnemonic to Mnemonic type with error
    - from_entropy(entropy: Vec<u8>) generates and returns Mnemonic with given entropy
    - as_string() view Mnemonic as string
  - API removed [#219]
    - generate_mnemonic(word_count: WordCount)

  [#219](#219)
  ```
  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [x] This pull request breaks the existing API
      * Top level function `generate_mnemonic(...)` was removed

ACKs for top commit:
  thunderbiscuit:
    ACK 9866649.
  notmandatory:
    ACK 9866649

Tree-SHA512: 45f9158beb6fe7bfe2a901c3f17126db855fe0b4b479ecb2a16381e06a415eed295fe6be3c840bd1d1fc8cffaf58bd97dc27bdc1e82699367a827d700e8fd09b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request ldk-compatibility Compatibility with the Lightning Dev Kit summer-of-bitcoin Summer of Bitcoin Project
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

7 participants