signingprovider.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto 2 // Copyright (c) 2009-2022 The Bitcoin Core developers 3 // Distributed under the MIT software license, see the accompanying 4 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 #include <script/keyorigin.h> 7 #include <script/interpreter.h> 8 #include <script/signingprovider.h> 9 10 #include <logging.h> 11 12 const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); 13 14 template<typename M, typename K, typename V> 15 bool LookupHelper(const M& map, const K& key, V& value) 16 { 17 auto it = map.find(key); 18 if (it != map.end()) { 19 value = it->second; 20 return true; 21 } 22 return false; 23 } 24 25 bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const 26 { 27 return m_provider->GetCScript(scriptid, script); 28 } 29 30 bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const 31 { 32 return m_provider->GetPubKey(keyid, pubkey); 33 } 34 35 bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const 36 { 37 if (m_hide_secret) return false; 38 return m_provider->GetKey(keyid, key); 39 } 40 41 bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const 42 { 43 if (m_hide_origin) return false; 44 return m_provider->GetKeyOrigin(keyid, info); 45 } 46 47 bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const 48 { 49 return m_provider->GetTaprootSpendData(output_key, spenddata); 50 } 51 bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const 52 { 53 return m_provider->GetTaprootBuilder(output_key, builder); 54 } 55 56 bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } 57 bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } 58 bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const 59 { 60 std::pair<CPubKey, KeyOriginInfo> out; 61 bool ret = LookupHelper(origins, keyid, out); 62 if (ret) info = std::move(out.second); 63 return ret; 64 } 65 bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } 66 bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const 67 { 68 TaprootBuilder builder; 69 if (LookupHelper(tr_trees, output_key, builder)) { 70 spenddata = builder.GetSpendData(); 71 return true; 72 } 73 return false; 74 } 75 bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const 76 { 77 return LookupHelper(tr_trees, output_key, builder); 78 } 79 80 FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b) 81 { 82 scripts.merge(b.scripts); 83 pubkeys.merge(b.pubkeys); 84 keys.merge(b.keys); 85 origins.merge(b.origins); 86 tr_trees.merge(b.tr_trees); 87 return *this; 88 } 89 90 void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) 91 { 92 AssertLockHeld(cs_KeyStore); 93 CKeyID key_id = pubkey.GetID(); 94 // This adds the redeemscripts necessary to detect P2WPKH and P2SH-P2WPKH 95 // outputs. Technically P2WPKH outputs don't have a redeemscript to be 96 // spent. However, our current IsMine logic requires the corresponding 97 // P2SH-P2WPKH redeemscript to be present in the wallet in order to accept 98 // payment even to P2WPKH outputs. 99 // Also note that having superfluous scripts in the keystore never hurts. 100 // They're only used to guide recursion in signing and IsMine logic - if 101 // a script is present but we can't do anything with it, it has no effect. 102 // "Implicitly" refers to fact that scripts are derived automatically from 103 // existing keys, and are present in memory, even without being explicitly 104 // loaded (e.g. from a file). 105 if (pubkey.IsCompressed()) { 106 CScript script = GetScriptForDestination(WitnessV0KeyHash(key_id)); 107 // This does not use AddCScript, as it may be overridden. 108 CScriptID id(script); 109 mapScripts[id] = std::move(script); 110 } 111 } 112 113 bool FillableSigningProvider::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const 114 { 115 CKey key; 116 if (!GetKey(address, key)) { 117 return false; 118 } 119 vchPubKeyOut = key.GetPubKey(); 120 return true; 121 } 122 123 bool FillableSigningProvider::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) 124 { 125 LOCK(cs_KeyStore); 126 mapKeys[pubkey.GetID()] = key; 127 ImplicitlyLearnRelatedKeyScripts(pubkey); 128 return true; 129 } 130 131 bool FillableSigningProvider::HaveKey(const CKeyID &address) const 132 { 133 LOCK(cs_KeyStore); 134 return mapKeys.count(address) > 0; 135 } 136 137 std::set<CKeyID> FillableSigningProvider::GetKeys() const 138 { 139 LOCK(cs_KeyStore); 140 std::set<CKeyID> set_address; 141 for (const auto& mi : mapKeys) { 142 set_address.insert(mi.first); 143 } 144 return set_address; 145 } 146 147 bool FillableSigningProvider::GetKey(const CKeyID &address, CKey &keyOut) const 148 { 149 LOCK(cs_KeyStore); 150 KeyMap::const_iterator mi = mapKeys.find(address); 151 if (mi != mapKeys.end()) { 152 keyOut = mi->second; 153 return true; 154 } 155 return false; 156 } 157 158 bool FillableSigningProvider::AddCScript(const CScript& redeemScript) 159 { 160 if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { 161 LogError("FillableSigningProvider::AddCScript(): redeemScripts > %i bytes are invalid\n", MAX_SCRIPT_ELEMENT_SIZE); 162 return false; 163 } 164 165 LOCK(cs_KeyStore); 166 mapScripts[CScriptID(redeemScript)] = redeemScript; 167 return true; 168 } 169 170 bool FillableSigningProvider::HaveCScript(const CScriptID& hash) const 171 { 172 LOCK(cs_KeyStore); 173 return mapScripts.count(hash) > 0; 174 } 175 176 std::set<CScriptID> FillableSigningProvider::GetCScripts() const 177 { 178 LOCK(cs_KeyStore); 179 std::set<CScriptID> set_script; 180 for (const auto& mi : mapScripts) { 181 set_script.insert(mi.first); 182 } 183 return set_script; 184 } 185 186 bool FillableSigningProvider::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const 187 { 188 LOCK(cs_KeyStore); 189 ScriptMap::const_iterator mi = mapScripts.find(hash); 190 if (mi != mapScripts.end()) 191 { 192 redeemScriptOut = (*mi).second; 193 return true; 194 } 195 return false; 196 } 197 198 CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest) 199 { 200 // Only supports destinations which map to single public keys: 201 // P2PKH, P2WPKH, P2SH-P2WPKH, P2TR 202 if (auto id = std::get_if<PKHash>(&dest)) { 203 return ToKeyID(*id); 204 } 205 if (auto witness_id = std::get_if<WitnessV0KeyHash>(&dest)) { 206 return ToKeyID(*witness_id); 207 } 208 if (auto script_hash = std::get_if<ScriptHash>(&dest)) { 209 CScript script; 210 CScriptID script_id = ToScriptID(*script_hash); 211 CTxDestination inner_dest; 212 if (store.GetCScript(script_id, script) && ExtractDestination(script, inner_dest)) { 213 if (auto inner_witness_id = std::get_if<WitnessV0KeyHash>(&inner_dest)) { 214 return ToKeyID(*inner_witness_id); 215 } 216 } 217 } 218 if (auto output_key = std::get_if<WitnessV1Taproot>(&dest)) { 219 TaprootSpendData spenddata; 220 CPubKey pub; 221 if (store.GetTaprootSpendData(*output_key, spenddata) 222 && !spenddata.internal_key.IsNull() 223 && spenddata.merkle_root.IsNull() 224 && store.GetPubKeyByXOnly(spenddata.internal_key, pub)) { 225 return pub.GetID(); 226 } 227 } 228 return CKeyID(); 229 } 230 231 void MultiSigningProvider::AddProvider(std::unique_ptr<SigningProvider> provider) 232 { 233 m_providers.push_back(std::move(provider)); 234 } 235 236 bool MultiSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const 237 { 238 for (const auto& provider: m_providers) { 239 if (provider->GetCScript(scriptid, script)) return true; 240 } 241 return false; 242 } 243 244 bool MultiSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const 245 { 246 for (const auto& provider: m_providers) { 247 if (provider->GetPubKey(keyid, pubkey)) return true; 248 } 249 return false; 250 } 251 252 253 bool MultiSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const 254 { 255 for (const auto& provider: m_providers) { 256 if (provider->GetKeyOrigin(keyid, info)) return true; 257 } 258 return false; 259 } 260 261 bool MultiSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const 262 { 263 for (const auto& provider: m_providers) { 264 if (provider->GetKey(keyid, key)) return true; 265 } 266 return false; 267 } 268 269 bool MultiSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const 270 { 271 for (const auto& provider: m_providers) { 272 if (provider->GetTaprootSpendData(output_key, spenddata)) return true; 273 } 274 return false; 275 } 276 277 bool MultiSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const 278 { 279 for (const auto& provider: m_providers) { 280 if (provider->GetTaprootBuilder(output_key, builder)) return true; 281 } 282 return false; 283 } 284 285 /*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b) 286 { 287 NodeInfo ret; 288 /* Iterate over all tracked leaves in a, add b's hash to their Merkle branch, and move them to ret. */ 289 for (auto& leaf : a.leaves) { 290 leaf.merkle_branch.push_back(b.hash); 291 ret.leaves.emplace_back(std::move(leaf)); 292 } 293 /* Iterate over all tracked leaves in b, add a's hash to their Merkle branch, and move them to ret. */ 294 for (auto& leaf : b.leaves) { 295 leaf.merkle_branch.push_back(a.hash); 296 ret.leaves.emplace_back(std::move(leaf)); 297 } 298 ret.hash = ComputeTapbranchHash(a.hash, b.hash); 299 return ret; 300 } 301 302 void TaprootSpendData::Merge(TaprootSpendData other) 303 { 304 // TODO: figure out how to better deal with conflicting information 305 // being merged. 306 if (internal_key.IsNull() && !other.internal_key.IsNull()) { 307 internal_key = other.internal_key; 308 } 309 if (merkle_root.IsNull() && !other.merkle_root.IsNull()) { 310 merkle_root = other.merkle_root; 311 } 312 for (auto& [key, control_blocks] : other.scripts) { 313 scripts[key].merge(std::move(control_blocks)); 314 } 315 } 316 317 void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) 318 { 319 assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT); 320 /* We cannot insert a leaf at a lower depth while a deeper branch is unfinished. Doing 321 * so would mean the Add() invocations do not correspond to a DFS traversal of a 322 * binary tree. */ 323 if ((size_t)depth + 1 < m_branch.size()) { 324 m_valid = false; 325 return; 326 } 327 /* As long as an entry in the branch exists at the specified depth, combine it and propagate up. 328 * The 'node' variable is overwritten here with the newly combined node. */ 329 while (m_valid && m_branch.size() > (size_t)depth && m_branch[depth].has_value()) { 330 node = Combine(std::move(node), std::move(*m_branch[depth])); 331 m_branch.pop_back(); 332 if (depth == 0) m_valid = false; /* Can't propagate further up than the root */ 333 --depth; 334 } 335 if (m_valid) { 336 /* Make sure the branch is big enough to place the new node. */ 337 if (m_branch.size() <= (size_t)depth) m_branch.resize((size_t)depth + 1); 338 assert(!m_branch[depth].has_value()); 339 m_branch[depth] = std::move(node); 340 } 341 } 342 343 /*static*/ bool TaprootBuilder::ValidDepths(const std::vector<int>& depths) 344 { 345 std::vector<bool> branch; 346 for (int depth : depths) { 347 // This inner loop corresponds to effectively the same logic on branch 348 // as what Insert() performs on the m_branch variable. Instead of 349 // storing a NodeInfo object, just remember whether or not there is one 350 // at that depth. 351 if (depth < 0 || (size_t)depth > TAPROOT_CONTROL_MAX_NODE_COUNT) return false; 352 if ((size_t)depth + 1 < branch.size()) return false; 353 while (branch.size() > (size_t)depth && branch[depth]) { 354 branch.pop_back(); 355 if (depth == 0) return false; 356 --depth; 357 } 358 if (branch.size() <= (size_t)depth) branch.resize((size_t)depth + 1); 359 assert(!branch[depth]); 360 branch[depth] = true; 361 } 362 // And this check corresponds to the IsComplete() check on m_branch. 363 return branch.size() == 0 || (branch.size() == 1 && branch[0]); 364 } 365 366 TaprootBuilder& TaprootBuilder::Add(int depth, Span<const unsigned char> script, int leaf_version, bool track) 367 { 368 assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0); 369 if (!IsValid()) return *this; 370 /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ 371 NodeInfo node; 372 node.hash = ComputeTapleafHash(leaf_version, script); 373 if (track) node.leaves.emplace_back(LeafInfo{std::vector<unsigned char>(script.begin(), script.end()), leaf_version, {}}); 374 /* Insert into the branch. */ 375 Insert(std::move(node), depth); 376 return *this; 377 } 378 379 TaprootBuilder& TaprootBuilder::AddOmitted(int depth, const uint256& hash) 380 { 381 if (!IsValid()) return *this; 382 /* Construct NodeInfo object with the hash directly, and insert it into the branch. */ 383 NodeInfo node; 384 node.hash = hash; 385 Insert(std::move(node), depth); 386 return *this; 387 } 388 389 TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key) 390 { 391 /* Can only call this function when IsComplete() is true. */ 392 assert(IsComplete()); 393 m_internal_key = internal_key; 394 auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash); 395 assert(ret.has_value()); 396 std::tie(m_output_key, m_parity) = *ret; 397 return *this; 398 } 399 400 WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; } 401 402 TaprootSpendData TaprootBuilder::GetSpendData() const 403 { 404 assert(IsComplete()); 405 assert(m_output_key.IsFullyValid()); 406 TaprootSpendData spd; 407 spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; 408 spd.internal_key = m_internal_key; 409 if (m_branch.size()) { 410 // If any script paths exist, they have been combined into the root m_branch[0] 411 // by now. Compute the control block for each of its tracked leaves, and put them in 412 // spd.scripts. 413 for (const auto& leaf : m_branch[0]->leaves) { 414 std::vector<unsigned char> control_block; 415 control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size()); 416 control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0); 417 std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1); 418 if (leaf.merkle_branch.size()) { 419 std::copy(leaf.merkle_branch[0].begin(), 420 leaf.merkle_branch[0].begin() + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size(), 421 control_block.begin() + TAPROOT_CONTROL_BASE_SIZE); 422 } 423 spd.scripts[{leaf.script, leaf.leaf_version}].insert(std::move(control_block)); 424 } 425 } 426 return spd; 427 } 428 429 std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output) 430 { 431 // Verify that the output matches the assumed Merkle root and internal key. 432 auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root); 433 if (!tweak || tweak->first != output) return std::nullopt; 434 // If the Merkle root is 0, the tree is empty, and we're done. 435 std::vector<std::tuple<int, std::vector<unsigned char>, int>> ret; 436 if (spenddata.merkle_root.IsNull()) return ret; 437 438 /** Data structure to represent the nodes of the tree we're going to build. */ 439 struct TreeNode { 440 /** Hash of this node, if known; 0 otherwise. */ 441 uint256 hash; 442 /** The left and right subtrees (note that their order is irrelevant). */ 443 std::unique_ptr<TreeNode> sub[2]; 444 /** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair. 445 * nullptr otherwise. */ 446 const std::pair<std::vector<unsigned char>, int>* leaf = nullptr; 447 /** Whether or not this node has been explored (is known to be a leaf, or known to have children). */ 448 bool explored = false; 449 /** Whether or not this node is an inner node (unknown until explored = true). */ 450 bool inner; 451 /** Whether or not we have produced output for this subtree. */ 452 bool done = false; 453 }; 454 455 // Build tree from the provided branches. 456 TreeNode root; 457 root.hash = spenddata.merkle_root; 458 for (const auto& [key, control_blocks] : spenddata.scripts) { 459 const auto& [script, leaf_ver] = key; 460 for (const auto& control : control_blocks) { 461 // Skip script records with nonsensical leaf version. 462 if (leaf_ver < 0 || leaf_ver >= 0x100 || leaf_ver & 1) continue; 463 // Skip script records with invalid control block sizes. 464 if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || 465 ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) continue; 466 // Skip script records that don't match the control block. 467 if ((control[0] & TAPROOT_LEAF_MASK) != leaf_ver) continue; 468 // Skip script records that don't match the provided Merkle root. 469 const uint256 leaf_hash = ComputeTapleafHash(leaf_ver, script); 470 const uint256 merkle_root = ComputeTaprootMerkleRoot(control, leaf_hash); 471 if (merkle_root != spenddata.merkle_root) continue; 472 473 TreeNode* node = &root; 474 size_t levels = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; 475 for (size_t depth = 0; depth < levels; ++depth) { 476 // Can't descend into a node which we already know is a leaf. 477 if (node->explored && !node->inner) return std::nullopt; 478 479 // Extract partner hash from Merkle branch in control block. 480 uint256 hash; 481 std::copy(control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - 1 - depth) * TAPROOT_CONTROL_NODE_SIZE, 482 control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - depth) * TAPROOT_CONTROL_NODE_SIZE, 483 hash.begin()); 484 485 if (node->sub[0]) { 486 // Descend into the existing left or right branch. 487 bool desc = false; 488 for (int i = 0; i < 2; ++i) { 489 if (node->sub[i]->hash == hash || (node->sub[i]->hash.IsNull() && node->sub[1-i]->hash != hash)) { 490 node->sub[i]->hash = hash; 491 node = &*node->sub[1-i]; 492 desc = true; 493 break; 494 } 495 } 496 if (!desc) return std::nullopt; // This probably requires a hash collision to hit. 497 } else { 498 // We're in an unexplored node. Create subtrees and descend. 499 node->explored = true; 500 node->inner = true; 501 node->sub[0] = std::make_unique<TreeNode>(); 502 node->sub[1] = std::make_unique<TreeNode>(); 503 node->sub[1]->hash = hash; 504 node = &*node->sub[0]; 505 } 506 } 507 // Cannot turn a known inner node into a leaf. 508 if (node->sub[0]) return std::nullopt; 509 node->explored = true; 510 node->inner = false; 511 node->leaf = &key; 512 node->hash = leaf_hash; 513 } 514 } 515 516 // Recursive processing to turn the tree into flattened output. Use an explicit stack here to avoid 517 // overflowing the call stack (the tree may be 128 levels deep). 518 std::vector<TreeNode*> stack{&root}; 519 while (!stack.empty()) { 520 TreeNode& node = *stack.back(); 521 if (!node.explored) { 522 // Unexplored node, which means the tree is incomplete. 523 return std::nullopt; 524 } else if (!node.inner) { 525 // Leaf node; produce output. 526 ret.emplace_back(stack.size() - 1, node.leaf->first, node.leaf->second); 527 node.done = true; 528 stack.pop_back(); 529 } else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() && 530 ComputeTapbranchHash(node.sub[1]->hash, node.sub[1]->hash) == node.hash) { 531 // Whenever there are nodes with two identical subtrees under it, we run into a problem: 532 // the control blocks for the leaves underneath those will be identical as well, and thus 533 // they will all be matched to the same path in the tree. The result is that at the location 534 // where the duplicate occurred, the left child will contain a normal tree that can be explored 535 // and processed, but the right one will remain unexplored. 536 // 537 // This situation can be detected, by encountering an inner node with unexplored right subtree 538 // with known hash, and H_TapBranch(hash, hash) is equal to the parent node (this node)'s hash. 539 // 540 // To deal with this, simply process the left tree a second time (set its done flag to false; 541 // noting that the done flag of its children have already been set to false after processing 542 // those). To avoid ending up in an infinite loop, set the done flag of the right (unexplored) 543 // subtree to true. 544 node.sub[0]->done = false; 545 node.sub[1]->done = true; 546 } else if (node.sub[0]->done && node.sub[1]->done) { 547 // An internal node which we're finished with. 548 node.sub[0]->done = false; 549 node.sub[1]->done = false; 550 node.done = true; 551 stack.pop_back(); 552 } else if (!node.sub[0]->done) { 553 // An internal node whose left branch hasn't been processed yet. Do so first. 554 stack.push_back(&*node.sub[0]); 555 } else if (!node.sub[1]->done) { 556 // An internal node whose right branch hasn't been processed yet. Do so first. 557 stack.push_back(&*node.sub[1]); 558 } 559 } 560 561 return ret; 562 } 563 564 std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> TaprootBuilder::GetTreeTuples() const 565 { 566 assert(IsComplete()); 567 std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> tuples; 568 if (m_branch.size()) { 569 const auto& leaves = m_branch[0]->leaves; 570 for (const auto& leaf : leaves) { 571 assert(leaf.merkle_branch.size() <= TAPROOT_CONTROL_MAX_NODE_COUNT); 572 uint8_t depth = (uint8_t)leaf.merkle_branch.size(); 573 uint8_t leaf_ver = (uint8_t)leaf.leaf_version; 574 tuples.emplace_back(depth, leaf_ver, leaf.script); 575 } 576 } 577 return tuples; 578 }