0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-01 09:35:52 -05:00

Merge bitcoin/bitcoin#24043: Add (sorted)multi_a descriptor for k-of-n multisig inside tr

4828d53ecc Add (sorted)multi_a descriptors to doc/descriptors.md (Pieter Wuille)
b5f33ac1f8 Simplify wallet_taproot.py functional test (Pieter Wuille)
eb0667ea96 Add tests for (sorted)multi_a derivation/signing (Pieter Wuille)
c17c6aa08d Add signing support for (sorted)multi_a scripts (Pieter Wuille)
3eed6fca57 Add multi_a descriptor inference (Pieter Wuille)
79728c4a3d Add (sorted)multi_a descriptor and script derivation (Pieter Wuille)
25e95f9ff8 Merge/generalize IsValidMultisigKeyCount/GetMultisigKeyCount (Pieter Wuille)

Pull request description:

  This adds a new `multi_a(k,key_1,key_2,...,key_n)` (and corresponding `sortedmulti_a`) descriptor for k-of-n policies inside `tr()`. Semantically it is very similar to the existing `multi()` descriptor, but with the following changes:
  * The corresponding script is `<key1> OP_CHECKSIG <key2> OP_CHECKSIGADD <key3> OP_CHECKSIGADD ... <key_n> OP_CHECKSIGADD <k> OP_NUMEQUAL`, rather than the traditional `OP_CHECKMULTISIG`-based script, making it usable inside the `tr()` descriptor.
  * The keys can optionally be specified in x-only notation.
  * Both the number of keys and the threshold can be as high as 999; this is the limit due to the consensus stacksize=1000 limit

  I expect that this functionality will later be replaced with a miniscript-based implementation, but I don't think it's necessary to wait for that.

  Limitations:
  * The wallet code will for not estimate witness size incorrectly for script path spends, which may result in a (dramatic) fee underpayment with large multi_a scripts.
  * The multi_a script construction is (slightly) suboptimal for n-of-n (where a `<key1> OP_CHECKSIGVERIFY ... <key_n-1> OP_CHECKSIGVERIFY <key_n> OP_CHECKSIG` would be better). Such a construction is not included here.

ACKs for top commit:
  achow101:
    ACK 4828d53ecc
  gruve-p:
    ACK 4828d53ecc
  sanket1729:
    code review ACK 4828d53ecc
  darosior:
    Code review ACK 4828d53ecc

Tree-SHA512: 5dcd434b79585f0ff830f7d501d27df5e346f5749f47a3109ec309ebf2cbbad0e1da541eec654026d911ab67fd7cf7793fab0f765628d68d81b96ef2a4d234ce
This commit is contained in:
Andrew Chow 2022-03-04 07:12:16 -05:00
commit bada9636d7
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
8 changed files with 223 additions and 47 deletions

View file

@ -33,6 +33,7 @@ Output descriptors currently support:
- Pay-to-taproot outputs (P2TR), through the `tr` function.
- Multisig scripts, through the `multi` function.
- Multisig scripts where the public keys are sorted lexicographically, through the `sortedmulti` function.
- Multisig scripts inside taproot script trees, through the `multi_a` (and `sortedmulti_a`) function.
- Any type of supported address through the `addr` function.
- Raw hex scripts through the `raw` function.
- Public keys (compressed and uncompressed) in hex notation, or BIP32 extended pubkeys with derivation paths.
@ -56,6 +57,7 @@ Output descriptors currently support:
- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default).
- `wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where one multisig key is the *1/0/`i`* child of the first specified xpub and the other multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). The order of public keys in the resulting witnessScripts is determined by the lexicographic order of the public keys at that index.
- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})` describes a P2TR output with the `c6...` x-only pubkey as internal key, and two script paths.
- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,sortedmulti_a(2,2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc))` describes a P2TR output with the `c6...` x-only pubkey as internal key, and a single `multi_a` script that needs 2 signatures with 2 specified x-only keys, which will be sorted lexicographically.
## Reference
@ -68,8 +70,10 @@ Descriptors consist of several types of expressions. The top level expression is
- `pkh(KEY)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash).
- `wpkh(KEY)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey.
- `combo(KEY)` (top level only): an alias for the collection of `pk(KEY)` and `pkh(KEY)`. If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script.
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script using OP_CHECKMULTISIG.
- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script with keys sorted lexicographically in the resulting script.
- `multi_a(k,KEY_1,KEY_2,...,KEY_N)` (only inside `tr`): k-of-n multisig script using OP_CHECKSIG, OP_CHECKSIGADD, and OP_NUMEQUAL.
- `sortedmulti_a(k,KEY_1,KEY_2,...,KEY_N)` (only inside `tr`): similar to `multi_a`, but the (x-only) public keys in it will be sorted lexicographically.
- `tr(KEY)` or `tr(KEY,TREE)` (top level only): P2TR output with the specified key as internal key, and optionally a tree of script paths.
- `addr(ADDR)` (top level only): the script which ADDR expands to.
- `raw(HEX)` (top level only): the script whose hex encoding is HEX.

View file

@ -802,6 +802,30 @@ public:
bool IsSingleType() const final { return true; }
};
/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
class MultiADescriptor final : public DescriptorImpl
{
const int m_threshold;
const bool m_sorted;
protected:
std::string ToStringExtra() const override { return strprintf("%i", m_threshold); }
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override {
CScript ret;
std::vector<XOnlyPubKey> xkeys;
for (const auto& key : keys) xkeys.emplace_back(key);
if (m_sorted) std::sort(xkeys.begin(), xkeys.end());
ret << ToByteVector(xkeys[0]) << OP_CHECKSIG;
for (size_t i = 1; i < keys.size(); ++i) {
ret << ToByteVector(xkeys[i]) << OP_CHECKSIGADD;
}
ret << m_threshold << OP_NUMEQUAL;
return Vector(std::move(ret));
}
public:
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
};
/** A parsed sh(...) descriptor. */
class SHDescriptor final : public DescriptorImpl
{
@ -1040,7 +1064,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
using namespace spanparsing;
auto expr = Expr(sp);
bool sorted_multi = false;
if (Func("pk", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
@ -1065,7 +1088,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have combo() at top level";
return nullptr;
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
const bool multi = Func("multi", expr);
const bool sortedmulti = !multi && Func("sortedmulti", expr);
const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr);
const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr);
if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) ||
(ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) {
auto threshold = Expr(expr);
uint32_t thres;
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@ -1086,9 +1114,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
providers.emplace_back(std::move(pk));
key_exp_index++;
}
if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
if ((multi || sortedmulti) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG)) {
error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
return nullptr;
} else if ((multi_a || sortedmulti_a) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTI_A)) {
error = strprintf("Cannot have %u keys in multi_a; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTI_A);
return nullptr;
} else if (thres < 1) {
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
return nullptr;
@ -1109,10 +1140,17 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
return nullptr;
}
}
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
} else if (Func("sortedmulti", expr) || Func("multi", expr)) {
if (multi || sortedmulti) {
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sortedmulti);
} else {
return std::make_unique<MultiADescriptor>(thres, std::move(providers), sortedmulti_a);
}
} else if (multi || sortedmulti) {
error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
return nullptr;
} else if (multi_a || sortedmulti_a) {
error = "Can only have multi_a/sortedmulti_a inside tr()";
return nullptr;
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
@ -1257,6 +1295,21 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
return key_provider;
}
std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
{
auto match = MatchMultiA(script);
if (!match) return {};
std::vector<std::unique_ptr<PubkeyProvider>> keys;
keys.reserve(match->second.size());
for (const auto keyspan : match->second) {
if (keyspan.size() != 32) return {};
auto key = InferXOnlyPubkey(XOnlyPubKey{keyspan}, ctx, provider);
if (!key) return {};
keys.push_back(std::move(key));
}
return std::make_unique<MultiADescriptor>(match->first, std::move(keys));
}
std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
{
if (ctx == ParseScriptContext::P2TR && script.size() == 34 && script[0] == 32 && script[33] == OP_CHECKSIG) {
@ -1264,6 +1317,11 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider), true);
}
if (ctx == ParseScriptContext::P2TR) {
auto ret = InferMultiA(script, ctx, provider);
if (ret) return ret;
}
std::vector<std::vector<unsigned char>> data;
TxoutType txntype = Solver(script, data);

View file

@ -29,6 +29,9 @@ static const int MAX_OPS_PER_SCRIPT = 201;
// Maximum number of public keys per multisig
static const int MAX_PUBKEYS_PER_MULTISIG = 20;
/** The limit of keys in OP_CHECKSIGADD-based scripts. It is due to the stack limit in BIP342. */
static constexpr unsigned int MAX_PUBKEYS_PER_MULTI_A = 999;
// Maximum script length in bytes
static const int MAX_SCRIPT_SIZE = 10000;

View file

@ -174,6 +174,29 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu
result = Vector(std::move(sig));
return true;
}
return false;
}
// multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL)
if (auto match = MatchMultiA(script)) {
std::vector<std::vector<unsigned char>> sigs;
int good_sigs = 0;
for (size_t i = 0; i < match->second.size(); ++i) {
XOnlyPubKey pubkey{*(match->second.rbegin() + i)};
std::vector<unsigned char> sig;
bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion);
if (good_sig && good_sigs < match->first) {
++good_sigs;
sigs.push_back(std::move(sig));
} else {
sigs.emplace_back();
}
}
if (good_sigs == match->first) {
result = std::move(sigs);
return true;
}
return false;
}
return false;

View file

@ -96,51 +96,83 @@ static constexpr bool IsPushdataOp(opcodetype opcode)
return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
}
static constexpr bool IsValidMultisigKeyCount(int n_keys)
{
return n_keys > 0 && n_keys <= MAX_PUBKEYS_PER_MULTISIG;
}
static bool GetMultisigKeyCount(opcodetype opcode, valtype data, int& count)
/** Retrieve a minimally-encoded number in range [min,max] from an (opcode, data) pair,
* whether it's OP_n or through a push. */
static std::optional<int> GetScriptNumber(opcodetype opcode, valtype data, int min, int max)
{
int count;
if (IsSmallInteger(opcode)) {
count = CScript::DecodeOP_N(opcode);
return IsValidMultisigKeyCount(count);
}
if (IsPushdataOp(opcode)) {
if (!CheckMinimalPush(data, opcode)) return false;
} else if (IsPushdataOp(opcode)) {
if (!CheckMinimalPush(data, opcode)) return {};
try {
count = CScriptNum(data, /* fRequireMinimal = */ true).getint();
return IsValidMultisigKeyCount(count);
} catch (const scriptnum_error&) {
return false;
return {};
}
} else {
return {};
}
return false;
if (count < min || count > max) return {};
return count;
}
static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys)
{
opcodetype opcode;
valtype data;
int num_keys;
CScript::const_iterator it = script.begin();
if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false;
if (!script.GetOp(it, opcode, data) || !GetMultisigKeyCount(opcode, data, required_sigs)) return false;
if (!script.GetOp(it, opcode, data)) return false;
auto req_sigs = GetScriptNumber(opcode, data, 1, MAX_PUBKEYS_PER_MULTISIG);
if (!req_sigs) return false;
required_sigs = *req_sigs;
while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) {
pubkeys.emplace_back(std::move(data));
}
if (!GetMultisigKeyCount(opcode, data, num_keys)) return false;
if (pubkeys.size() != static_cast<unsigned long>(num_keys) || num_keys < required_sigs) return false;
auto num_keys = GetScriptNumber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG);
if (!num_keys) return false;
if (pubkeys.size() != static_cast<unsigned long>(*num_keys)) return false;
return (it + 1 == script.end());
}
std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script)
{
std::vector<Span<const unsigned char>> keyspans;
// Redundant, but very fast and selective test.
if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {};
// Parse keys
auto it = script.begin();
while (script.end() - it >= 34) {
if (*it != 32) return {};
++it;
keyspans.emplace_back(&*it, 32);
it += 32;
if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {};
++it;
}
if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {};
// Parse threshold.
opcodetype opcode;
std::vector<unsigned char> data;
if (!script.GetOp(it, opcode, data)) return {};
if (it == script.end()) return {};
if (*it != OP_NUMEQUAL) return {};
++it;
if (it != script.end()) return {};
auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size());
if (!threshold) return {};
// Construct result.
return std::pair{*threshold, std::move(keyspans)};
}
TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet)
{
vSolutionsRet.clear();

View file

@ -191,6 +191,10 @@ CScript GetScriptForDestination(const CTxDestination& dest);
/** Generate a P2PK script for the given pubkey. */
CScript GetScriptForRawPubKey(const CPubKey& pubkey);
/** Determine if script is a "multi_a" script. Returns (threshold, keyspans) if so, and nullopt otherwise.
* The keyspans refer to bytes in the passed script. */
std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND);
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);

View file

@ -27,6 +27,7 @@ from .messages import (
from .ripemd160 import ripemd160
MAX_SCRIPT_ELEMENT_SIZE = 520
MAX_PUBKEYS_PER_MULTI_A = 999
LOCKTIME_THRESHOLD = 500000000
ANNEX_TAG = 0x50

View file

@ -12,8 +12,11 @@ from test_framework.util import assert_equal
from test_framework.descriptors import descsum_create
from test_framework.script import (
CScript,
MAX_PUBKEYS_PER_MULTI_A,
OP_1,
OP_CHECKSIG,
OP_CHECKSIGADD,
OP_NUMEQUAL,
taproot_construct,
)
from test_framework.segwit_addr import encode_segwit_address
@ -167,6 +170,17 @@ def pk(hex_key):
"""Construct a script expression for taproot_construct for pk(hex_key)."""
return (None, CScript([bytes.fromhex(hex_key), OP_CHECKSIG]))
def multi_a(k, hex_keys, sort=False):
"""Construct a script expression for taproot_construct for a multi_a script."""
xkeys = [bytes.fromhex(hex_key) for hex_key in hex_keys]
if sort:
xkeys.sort()
ops = [xkeys[0], OP_CHECKSIG]
for i in range(1, len(hex_keys)):
ops += [xkeys[i], OP_CHECKSIGADD]
ops += [k, OP_NUMEQUAL]
return (None, CScript(ops))
def compute_taproot_address(pubkey, scripts):
"""Compute the address for a taproot output with given inner key and scripts."""
tap = taproot_construct(pubkey, scripts)
@ -275,7 +289,8 @@ class WalletTaprootTest(BitcoinTestFramework):
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
test_balance = int(self.rpc_online.getbalance() * 100000000)
ret_amnt = random.randrange(100000, test_balance)
res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True)
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
assert(self.rpc_online.gettransaction(res)["confirmations"] > 0)
@ -306,7 +321,8 @@ class WalletTaprootTest(BitcoinTestFramework):
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
test_balance = int(self.psbt_online.getbalance() * 100000000)
ret_amnt = random.randrange(100000, test_balance)
psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0]})['psbt']
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200})['psbt']
res = self.psbt_offline.walletprocesspsbt(psbt)
assert(res['complete'])
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
@ -314,7 +330,8 @@ class WalletTaprootTest(BitcoinTestFramework):
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0)
def do_test(self, comment, pattern, privmap, treefn, nkeys):
def do_test(self, comment, pattern, privmap, treefn):
nkeys = len(privmap)
keys = self.rand_keys(nkeys * 4)
self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys])
self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys])
@ -349,64 +366,98 @@ class WalletTaprootTest(BitcoinTestFramework):
"tr(XPRV)",
"tr($1/*)",
[True],
lambda k1: (key(k1), []),
1
lambda k1: (key(k1), [])
)
self.do_test(
"tr(H,XPRV)",
"tr($H,pk($1/*))",
[True],
lambda k1: (key(H_POINT), [pk(k1)]),
1
lambda k1: (key(H_POINT), [pk(k1)])
)
self.do_test(
"wpkh(XPRV)",
"wpkh($1/*)",
[True],
None,
1
None
)
self.do_test(
"tr(XPRV,{H,{H,XPUB}})",
"tr($1/*,{pk($H),{pk($H),pk($2/*)}})",
[True, False],
lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]),
2
lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]])
)
self.do_test(
"wsh(multi(1,XPRV,XPUB))",
"wsh(multi(1,$1/*,$2/*))",
[True, False],
None,
2
None
)
self.do_test(
"tr(XPRV,{XPUB,XPUB})",
"tr($1/*,{pk($2/*),pk($2/*)})",
[True, False],
lambda k1, k2: (key(k1), [pk(k2), pk(k2)]),
2
lambda k1, k2: (key(k1), [pk(k2), pk(k2)])
)
self.do_test(
"tr(XPRV,{{XPUB,H},{H,XPUB}})",
"tr($1/*,{{pk($2/*),pk($H)},{pk($H),pk($2/*)}})",
[True, False],
lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]]),
2
lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]])
)
self.do_test(
"tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})",
"tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})",
[False, False, True],
lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]),
3
lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]])
)
self.do_test(
"tr(XPRV,{XPUB,{{XPUB,{H,H}},{{H,H},XPUB}}})",
"tr($1/*,{pk($2/*),{{pk($2/*),{pk($H),pk($H)}},{{pk($H),pk($H)},pk($2/*)}}})",
[True, False],
lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]]),
2
lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]])
)
self.do_test(
"tr(H,multi_a(1,XPRV))",
"tr($H,multi_a(1,$1/*))",
[True],
lambda k1: (key(H_POINT), [multi_a(1, [k1])])
)
self.do_test(
"tr(H,sortedmulti_a(1,XPRV,XPUB))",
"tr($H,sortedmulti_a(1,$1/*,$2/*))",
[True, False],
lambda k1, k2: (key(H_POINT), [multi_a(1, [k1, k2], True)])
)
self.do_test(
"tr(H,multi_a(1,XPUB,XPRV))",
"tr($H,multi_a(1,$1/*,$2/*))",
[False, True],
lambda k1, k2: (key(H_POINT), [multi_a(1, [k1, k2])])
)
self.do_test(
"tr(H,sortedmulti_a(1,XPUB,XPRV,XPRV))",
"tr($H,sortedmulti_a(1,$1/*,$2/*,$3/*))",
[False, True, True],
lambda k1, k2, k3: (key(H_POINT), [multi_a(1, [k1, k2, k3], True)])
)
self.do_test(
"tr(H,multi_a(2,XPRV,XPUB,XPRV))",
"tr($H,multi_a(2,$1/*,$2/*,$3/*))",
[True, False, True],
lambda k1, k2, k3: (key(H_POINT), [multi_a(2, [k1, k2, k3])])
)
self.do_test(
"tr(XPUB,{{XPUB,{XPUB,sortedmulti_a(2,XPRV,XPUB,XPRV)}})",
"tr($2/*,{pk($2/*),{pk($2/*),sortedmulti_a(2,$1/*,$2/*,$3/*)}})",
[True, False, True],
lambda k1, k2, k3: (key(k2), [pk(k2), [pk(k2), multi_a(2, [k1, k2, k3], True)]])
)
rnd_pos = random.randrange(MAX_PUBKEYS_PER_MULTI_A)
self.do_test(
"tr(XPUB,multi_a(1,H...,XPRV,H...))",
"tr($2/*,multi_a(1" + (",$H" * rnd_pos) + ",$1/*" + (",$H" * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)) + "))",
[True, False],
lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))])
)
self.log.info("Sending everything back...")