|
35 | 35 | #include <psbt.h> |
36 | 36 | #include <pubkey.h> |
37 | 37 | #include <random.h> |
| 38 | +#include <regex> |
38 | 39 | #include <script/descriptor.h> |
| 40 | +#include <script/parsing.h> |
39 | 41 | #include <script/interpreter.h> |
40 | 42 | #include <script/script.h> |
41 | 43 | #include <script/sign.h> |
@@ -2579,29 +2581,146 @@ util::Result<void> CWallet::DisplayAddress(const CTxDestination& dest) |
2579 | 2581 | return util::Error{_("There is no ScriptPubKeyManager for this address")}; |
2580 | 2582 | } |
2581 | 2583 |
|
2582 | | -util::Result<std::string> CWallet::RegisterPolicy(std::optional<std::string> name) |
| 2584 | +// TODO: |
| 2585 | +// - apply furth BIP388 restrictions |
| 2586 | +// - avoid string parsing, add properties to Descriptor instead |
| 2587 | +bool CWallet::IsCandidateForBIP388Policy(DescriptorScriptPubKeyMan& spkm) |
2583 | 2588 | { |
2584 | | - // TODO: use wallet name as default |
2585 | | - const std::string policy_name{name.has_value() ? *name : "Core"}; |
| 2589 | + std::string desc; |
| 2590 | + if (!Assume(spkm.GetDescriptorString(desc, /*priv=*/false))) return false; |
2586 | 2591 |
|
2587 | | - // TODO: derive policy from descriptor (fail if not possible) |
2588 | | - const std::string descriptor_template{"wsh(multi(@0,@1))"}; |
| 2592 | + std::span<const char> sp{desc}; |
2589 | 2593 |
|
2590 | | - // TODO: extract key information |
2591 | | - const std::vector<std::string> keys_info{ |
2592 | | - "[00000001/47h/1h/0h]tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H", |
2593 | | - "[00000001/47h/1h/0h]tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY" |
2594 | | - }; |
| 2594 | + if (!(script::Const("tr(", sp, /*skip=*/true) || script::Const("wsh(", sp, /*skip=*/true))) return false; |
| 2595 | + // Must be MuSig2, have a leaf script or segwit multisig |
| 2596 | + if (desc.find(',') == std::string::npos) return false; |
| 2597 | + |
| 2598 | + return true; |
| 2599 | +} |
| 2600 | + |
| 2601 | +util::Result<std::pair<std::string, std::vector<std::string>>> CWallet::DerivePolicy(std::optional<std::pair<DescriptorScriptPubKeyMan&, DescriptorScriptPubKeyMan&>> spk_pair) |
| 2602 | +{ |
| 2603 | + std::string receive_descriptor; |
| 2604 | + std::string change_descriptor; |
| 2605 | + |
| 2606 | + DescriptorScriptPubKeyMan* receive{nullptr}; |
| 2607 | + DescriptorScriptPubKeyMan* change{nullptr}; |
| 2608 | + |
| 2609 | + if (spk_pair) { |
| 2610 | + if (!IsCandidateForBIP388Policy(spk_pair->first) || !IsCandidateForBIP388Policy(spk_pair->second)) { |
| 2611 | + return util::Error{_("Provided descriptors are not compatible with BIP388")}; |
| 2612 | + } |
| 2613 | + receive = &spk_pair->first; |
| 2614 | + change = &spk_pair->second; |
| 2615 | + } else { |
| 2616 | + for (bool internal : {false, true}) { |
| 2617 | + // TODO: support P2SH. |
| 2618 | + for (const OutputType type : {OutputType::BECH32M, OutputType::BECH32}) { |
| 2619 | + // Only look for a single candidate |
| 2620 | + if (!internal && !receive_descriptor.empty()) continue; |
| 2621 | + if (internal && !change_descriptor.empty()) continue; |
| 2622 | + |
| 2623 | + auto spk_man = GetScriptPubKeyMan(type, internal); |
| 2624 | + if (!Assume(spk_man)) continue; |
| 2625 | + auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan *>(spk_man); |
| 2626 | + if (!Assume(desc_spk_man)) continue; |
| 2627 | + |
| 2628 | + if (!IsCandidateForBIP388Policy(*desc_spk_man)) continue; |
| 2629 | + |
| 2630 | + if (internal) { |
| 2631 | + change = desc_spk_man; |
| 2632 | + } else { |
| 2633 | + receive = desc_spk_man; |
| 2634 | + } |
| 2635 | + } |
| 2636 | + } |
| 2637 | + } |
| 2638 | + |
| 2639 | + if (!receive || !change) { |
| 2640 | + return util::Error{_("No suitable descriptors found or provided for BIP388 policy")}; |
| 2641 | + } |
| 2642 | + |
| 2643 | + bool res{receive->GetDescriptorString(receive_descriptor, /*priv=*/false)}; |
| 2644 | + res &= change->GetDescriptorString(change_descriptor, /*priv=*/false); |
| 2645 | + if (!Assume(res)) { |
| 2646 | + return util::Error{_("Failed to get descriptor strings")}; |
| 2647 | + } |
| 2648 | + |
| 2649 | + // TODO: render (candidate) policy directly from Descriptor class and extract |
| 2650 | + // keys there. |
| 2651 | + |
| 2652 | + // Lookup keys in the receive descriptor. |
| 2653 | + std::vector<std::string> keys_info; |
| 2654 | + const std::regex key_regex{R"(((?:\[[a-f\dh'/]{8,}\])?[\w]{10,})[,\)\/])"}; |
| 2655 | + auto search_begin = std::sregex_token_iterator(receive_descriptor.begin(), receive_descriptor.end(), key_regex, 1); |
| 2656 | + auto search_end = std::sregex_token_iterator(); |
| 2657 | + |
| 2658 | + for (std::regex_token_iterator i = search_begin; i != search_end; ++i) { |
| 2659 | + std::string key{i->str()}; |
| 2660 | + if (std::find(keys_info.begin(), keys_info.end(), key) != keys_info.end()) continue; |
| 2661 | + keys_info.push_back(key); |
| 2662 | + } |
| 2663 | + |
| 2664 | + // Replace keys in descriptor swith @1, etc |
| 2665 | + // - convert /0/* in the receive descriptor to /** |
| 2666 | + // - convert /1/* in the change descriptor to /** |
| 2667 | + // - TODO: support arbitrary /<NUM;NUM>/* |
| 2668 | + // - also drop checksum |
| 2669 | + std::string descriptor_template{receive_descriptor.substr(0, receive_descriptor.size() - 9)}; |
| 2670 | + std::string descriptor_template_check{change_descriptor.substr(0, change_descriptor.size() - 9)}; |
| 2671 | + uint32_t key_index = 0; |
| 2672 | + for (auto& key_info : keys_info) { |
| 2673 | + util::ReplaceAll(descriptor_template, key_info, strprintf("@%d", key_index), /*regex=*/false); |
| 2674 | + util::ReplaceAll(descriptor_template_check, key_info, strprintf("@%d", key_index), /*regex=*/false); |
| 2675 | + key_index++; |
| 2676 | + } |
| 2677 | + |
| 2678 | + // Replace /0/* with /@0/** |
| 2679 | + util::ReplaceAll(descriptor_template, "/0/*", "/**", /*regex=*/false); |
| 2680 | + |
| 2681 | + // Replace /1/* with /@0/** |
| 2682 | + util::ReplaceAll(descriptor_template_check, "/1/*", "/**", /*regex=*/false); |
| 2683 | + |
| 2684 | + // The receive and change descriptor should now be identical |
| 2685 | + if (descriptor_template != descriptor_template_check) { |
| 2686 | + return util::Error{Untranslated(strprintf( |
| 2687 | + "Receive and change descriptors incompatible for BIP388 policy registration:\n%s\n%s\nKey info:\n%s", |
| 2688 | + descriptor_template.c_str(), |
| 2689 | + descriptor_template_check.c_str(), |
| 2690 | + util::Join(keys_info, "\n") |
| 2691 | + ))}; |
| 2692 | + } |
| 2693 | + |
| 2694 | + // Replace h with ' in key info: |
| 2695 | + for (std::string& key : keys_info) { |
| 2696 | + util::ReplaceAll(key, "h/", "'/", /*regex=*/false); |
| 2697 | + util::ReplaceAll(key, "h]", "']", /*regex=*/false); |
| 2698 | + } |
| 2699 | + |
| 2700 | + return std::pair<std::string, std::vector<std::string>>{descriptor_template, keys_info}; |
| 2701 | +} |
| 2702 | + |
| 2703 | +util::Result<std::string> CWallet::RegisterPolicy(std::optional<std::string> name) |
| 2704 | +{ |
| 2705 | + const std::string policy_name{name.has_value() ? *name : m_name}; |
| 2706 | + |
| 2707 | + // A wallet with multiple descriptors could have multiple BIP388 policies, |
| 2708 | + // but this is currently unsupported. DerivePolicy just picks one. |
| 2709 | + const auto res{DerivePolicy(/*spk_pair=*/std::nullopt)}; |
| 2710 | + if (!res) return util::Error{util::ErrorString(res)}; |
| 2711 | + const std::string& descriptor_template{res->first}; |
| 2712 | + const std::vector<std::string>& keys_info{res->second}; |
2595 | 2713 |
|
2596 | 2714 | for (const auto& spk_man : GetActiveScriptPubKeyMans()) { |
2597 | 2715 | auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan *>(spk_man); |
2598 | 2716 | if (signer_spk_man == nullptr) { |
2599 | 2717 | continue; |
2600 | 2718 | } |
2601 | 2719 | auto signer{ExternalSignerScriptPubKeyMan::GetExternalSigner()}; |
2602 | | - if (!signer) return util::ErrorString(signer).original; |
| 2720 | + if (!signer) return util::Error{util::ErrorString(signer)}; |
2603 | 2721 |
|
2604 | 2722 | util::Result<std::string> res{signer_spk_man->RegisterPolicy(*signer, policy_name, descriptor_template, keys_info)}; |
| 2723 | + if (!res) return util::Error{util::ErrorString(res)}; |
2605 | 2724 |
|
2606 | 2725 | if (res) { |
2607 | 2726 | // Store hmac in wallet |
|
0 commit comments