feat(compile): compile taproot descriptor with randomized unspendable internal key#225
feat(compile): compile taproot descriptor with randomized unspendable internal key#225va-an wants to merge 3 commits intobitcoindevkit:masterfrom
Conversation
Pull Request Test Coverage Report for Build 21687075357Details
💛 - Coveralls |
110CodingP
left a comment
There was a problem hiding this comment.
Refactoring the tests to usestd::process::Command instead of calling the internal function handle_compile_subcommand make the tests vulnerable to the state of environment variables and makes debugging harder in my opinion. But I have no strong disagreement to the approach in the PR.
My main idea is to have integration tests, not just unit tests, because that's the closest to how users actually interact with it. As for the environment variable state concern — we could remove related env vars to isolate integration tests, for example for compile tests: let output = Command::new("cargo")
.args(args)
.env_remove("NETWORK")
.env_remove("DATADIR")
.env_remove("POLICY")
.env_remove("TYPE")
.output()
.unwrap();This way we can isolate tests from the environment. Maintaining such tests might be a bit less convenient, but I promise to keep an eye on it :) |
That definitely makes sense.
Thanks! I did not know that we could remove them. I agree this could be helpful. Do you think it should be done in a separate PR though? maybe one that also refactors |
I would prefer to keep this PR as is, since @tvpeter has already looked at these changes. |
|
@110CodingP I decided to add the code for removing env vars in tests, as we discussed, so we don't forget to do this in future PRs. I verified locally that this works as expected — the command process will not receive these env vars from the parent process. use std::{env, process::Command};
fn main() {
unsafe {
env::set_var("FOO", "1");
env::set_var("BAR", "2");
env::set_var("BAZ", "3");
}
let cmd = Command::new("printenv")
.env_remove("FOO")
.env_remove("BAR")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&cmd.stdout);
assert!(!stdout.contains("FOO"), "FOO should be removed");
assert!(!stdout.contains("BAR"), "BAR should be removed");
assert!(stdout.contains("BAZ"), "BAZ should still be present");
println!("it's all good man");
}Please take a look — afc76aa. |
| .table() | ||
| .display() | ||
| .map_err(|e| Error::Generic(e.to_string()))?; | ||
| let mut rows = vec![vec!["Descriptor".cell().bold(true), descriptor.cell()]]; |
There was a problem hiding this comment.
the descriptor value is quite long, can you apply the [shorten](https://github.com/bitcoindevkit/bdk-cli/blob/fb7f6c60ca4ac1ad80750391435bbafd7346c714/src/utils.rs#L381) fn so it displays better?
There was a problem hiding this comment.
Done! I also used shorten() for r.
I extracted shorten description into a variable to shorten only the internal key and leave the rest of the descriptor as is.
without pretty:
-> % cargo run --all-features -- compile "pk(ABC)" -t tr
{
"descriptor": "tr(1985c2a86e01bbcdb1b5806422531a73c77e568ecfdd6d2863c158a76628ea50,pk(ABC))#6qsjh5q3",
"r": "e38caa40d0db7cae7037336c982278a7fc6b770e7a80f8bbce0d79976a14d5e1"
}with pretty:
-> % cargo run --all-features -- compile "pk(ABC)" -t tr --pretty
+------------+----------------------------------+
| Descriptor | tr(ccb4...b1d0,pk(ABC))#qrk923py |
+------------+----------------------------------+
| r | a129...7a14 |
+------------+----------------------------------+| let output = Command::new("cargo") | ||
| .args(args) | ||
| .env_remove("NETWORK") | ||
| .env_remove("DATADIR") |
There was a problem hiding this comment.
nit: ig removing DATADIR env variable is not required? Since the compile command does not use it?
There was a problem hiding this comment.
You're right, DATADIR isn't used in compile command.
I'm planning to reuse this command execution code in other tests, so I preemptively cleared all env vars.
| .map_err(|e| Error::Generic(e.to_string()))?; | ||
| let descriptor = shorten_descriptor.unwrap_or(descriptor); | ||
|
|
||
| let mut rows = vec![vec!["Descriptor".cell().bold(true), descriptor.cell()]]; |
There was a problem hiding this comment.
| let mut rows = vec![vec!["Descriptor".cell().bold(true), descriptor.cell()]]; | |
| let mut rows = vec![vec!["Descriptor".cell().bold(true), shorten(descriptor, 32, 29).cell()]]; |
you can remove: 908, 931-934, 946
you can also revert 951; it should size most screens comfortably.
There was a problem hiding this comment.
Ok, I’ll add these changes when I do the squash.
There was a problem hiding this comment.
I made the changes, and only then remembered why I did it this way — if we apply shorten globally (not just for tr), we'll get an error for other descriptors because we're trying to shorten an already too short string.
Here's an example of how to reproduce this (if you apply your suggested changes locally):
-> % cargo run --all-features -- compile "pk(ABC)" -t tr --pretty
+------------+------------------------------------------------------------------+
| Descriptor | tr(cf07afc46391a3ce22c1024a682ca...fc9486809e6,pk(ABC))#3qlg4fwz |
+------------+------------------------------------------------------------------+
| r | ee1c3591351b7c879fd415c0fbe3871bf61c752d7d35ed2ed94f0a278de9073d |
+------------+------------------------------------------------------------------+
-> % cargo run --all-features -- compile "pk(ABC)" -t wsh --pretty
thread 'main' (1258654) panicked at src/utils.rs:383:39:
byte index 32 is out of bounds of `wsh(pk(ABC))#8gmtcnaw`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceAlso, I'm not sure we should successfully compile a script like "pk(ABC)" at all, but we can discuss this in a separate issue.
There was a problem hiding this comment.
Also, I'm not sure we should successfully compile a script like "pk(ABC)" at all, but we can discuss this in a separate issue.
Well, policy compilation is actually from the upstream source but I have tested policies that failed. I will try to see if I can know what made this one to pass.
There was a problem hiding this comment.
Also, I'm not sure we should successfully compile a script like "pk(ABC)" at all, but we can discuss this in a separate issue.
Well, policy compilation is actually from the upstream source but I have tested policies that failed. I will try to see if I can know what made this one to pass.
I just noticed that bdk-cli can create a descriptor that bitcoin-cli doesn't accept as valid:
-> % bdk-cli compile "pk(ABC)"
{
"descriptor": "wsh(pk(ABC))#8gmtcnaw"
}
-> % bitcoin-cli deriveaddresses "wsh(pk(ABC))#8gmtcnaw"
error code: -5
error message:
pk(): key 'ABC' is not validThis might confuse some users.
As a simple fix, we could explicitly state in the compile command documentation that we don't validate keys at compile time.
There was a problem hiding this comment.
787c701 to
b5dac76
Compare
Squashed into 2 commits, but without the shortening approach changes - I've replied about that in the discussion above. |
|
Hey! I noticed that #203 was merged. I merged master into my branch and verified that the shorten() bug no longer reproduces. -> % cargo run --all-features -- compile "pk(ABC)" -t tr --pretty
+------------+----------------------------------+
| Descriptor | tr(e753...c650,pk(ABC))#quuf6809 |
+------------+----------------------------------+
| r | d527...f9c9 |
+------------+----------------------------------+
-> % cargo run --all-features -- compile "pk(ABC)" -t wsh --pretty
+------------+-----------------------+
| Descriptor | wsh(pk(ABC))#8gmtcnaw |
+------------+-----------------------+Please take a look. |
Description
Resolves #218 (part 1/2).
Depends on #203 (
shorten()fix).Implements randomization of the unspendable internal key for taproot descriptors. This is the first part of #218, which consists of two parts:
Split into separate PRs for easier review and iteration, and to allow independent discussion of the verification command implementation, as one of the possible approaches could introduce breaking changes.
Notes to the reviewers
The
compilecommand now returns an additionalrfield for taproot descriptors (-t tr), containing the randomly generated internal key. Each compilation will produce a different internal key instead of using a fixed NUMS key.Example output for taproot (first execution):
Same descriptor compiled again produces different
rand internal key:Other descriptor types remain unchanged:
Tests for
compilecommand have been moved fromhandlers.rsto thetestsdirectory. Since taproot descriptors now generate a random internal key on each invocation, the test for thecompilecommand has been simplified. I plan to enhance this test in a follow-up PR once the verification command is implemented.Changelog notice
Checklists
All Submissions:
cargo fmtandcargo clippybefore committingNew Features:
CHANGELOG.md