Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ void PSBTOutput::Merge(const PSBTOutput& output)
hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end());
unknown.insert(output.unknown.begin(), output.unknown.end());
m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end());
m_committed_txs.insert(output.m_committed_txs.begin(), output.m_committed_txs.end());
m_tap_internal_keys.insert(output.m_tap_internal_keys.begin(), output.m_tap_internal_keys.end());
m_tap_trees.insert(output.m_tap_trees.begin(), output.m_tap_trees.end());

if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
Expand Down
145 changes: 110 additions & 35 deletions src/psbt.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
static constexpr uint8_t PSBT_OUT_COMMITTED_TXS = 0x0b;
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEYS = 0x0c;
static constexpr uint8_t PSBT_OUT_TAP_TREES = 0x0d;
static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC;

// The separator is 0x00. Reading this in means that the unserializer can interpret it
Expand Down Expand Up @@ -710,15 +713,66 @@ struct PSBTInput
}
};

using TapTreeList = std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>>;

template<typename Stream>
void SerTapTree(Stream& s, const TapTreeList& tap_tree)
{
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (const auto& [depth, leaf_ver, script] : tap_tree) {
s_value << depth;
s_value << leaf_ver;
s_value << script;
}
s << value;
}

template<typename Stream>
void UnserTapTree(Stream& s, TapTreeList& tap_tree)
{
std::vector<unsigned char> tree_v;
s >> tree_v;
SpanReader s_tree{tree_v};
if (s_tree.empty()) {
throw std::ios_base::failure("Output Taproot tree must not be empty");
}
TaprootBuilder builder;
while (!s_tree.empty()) {
uint8_t depth;
uint8_t leaf_ver;
std::vector<unsigned char> script;
s_tree >> depth;
s_tree >> leaf_ver;
s_tree >> script;
if (depth > TAPROOT_CONTROL_MAX_NODE_COUNT) {
throw std::ios_base::failure("Output Taproot tree has as leaf greater than Taproot maximum depth");
}
if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) {
throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version");
}
tap_tree.emplace_back(depth, leaf_ver, script);
builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
}
if (!builder.IsComplete()) {
throw std::ios_base::failure("Output Taproot tree is malformed");
}
}

/** A structure for PSBTs which contains per output information */
struct PSBTOutput
{
CScript redeem_script;
CScript witness_script;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
XOnlyPubKey m_tap_internal_key;
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
TapTreeList m_tap_tree;
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
std::map<uint256, CMutableTransaction> m_committed_txs;
//! Map from output key to internal key for output of transactions committed in this output.
std::map<XOnlyPubKey, XOnlyPubKey> m_tap_internal_keys;
//! Map from output key to Tap tree for output of transactions committed in this output.
std::map<XOnlyPubKey, TapTreeList> m_tap_trees;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary;

Expand Down Expand Up @@ -760,14 +814,7 @@ struct PSBTOutput
// Write taproot tree
if (!m_tap_tree.empty()) {
SerializeToVector(s, PSBT_OUT_TAP_TREE);
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
s_value << depth;
s_value << leaf_ver;
s_value << script;
}
s << value;
SerTapTree(s, m_tap_tree);
}

// Write taproot bip32 keypaths
Expand All @@ -781,6 +828,24 @@ struct PSBTOutput
s << value;
}

// Write the OP_TEMPLATEHASH-committed transactions
for (const auto& [hash, tx]: m_committed_txs) {
SerializeToVector(s, PSBT_OUT_COMMITTED_TXS, hash);
s << TX_WITH_WITNESS(tx);
}

// Write the additional Taproot internal keys
for (const auto& [output_key, internal_key]: m_tap_internal_keys) {
SerializeToVector(s, PSBT_OUT_TAP_INTERNAL_KEYS, output_key);
s << internal_key;
}

// Write the additional Taproot trees
for (const auto& [output_key, tree]: m_tap_trees) {
SerializeToVector(s, PSBT_OUT_TAP_TREES, output_key);
SerTapTree(s, tree);
}

// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
Expand Down Expand Up @@ -858,32 +923,7 @@ struct PSBTOutput
} else if (key.size() != 1) {
throw std::ios_base::failure("Output Taproot tree key is more than one byte type");
}
std::vector<unsigned char> tree_v;
s >> tree_v;
SpanReader s_tree{tree_v};
if (s_tree.empty()) {
throw std::ios_base::failure("Output Taproot tree must not be empty");
}
TaprootBuilder builder;
while (!s_tree.empty()) {
uint8_t depth;
uint8_t leaf_ver;
std::vector<unsigned char> script;
s_tree >> depth;
s_tree >> leaf_ver;
s_tree >> script;
if (depth > TAPROOT_CONTROL_MAX_NODE_COUNT) {
throw std::ios_base::failure("Output Taproot tree has as leaf greater than Taproot maximum depth");
}
if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) {
throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version");
}
m_tap_tree.emplace_back(depth, leaf_ver, script);
builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
}
if (!builder.IsComplete()) {
throw std::ios_base::failure("Output Taproot tree is malformed");
}
UnserTapTree(s, m_tap_tree);
break;
}
case PSBT_OUT_TAP_BIP32_DERIVATION:
Expand All @@ -907,6 +947,41 @@ struct PSBTOutput
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
break;
}
case PSBT_OUT_COMMITTED_TXS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output committed transaction already provided");
} else if (key.size() != 33) {
throw std::ios_base::failure("Output committed transaction key is not 33 bytes");
}
uint256 hash{std::span<uint8_t>{key.begin() + 1, key.end()}};
CMutableTransaction tx{deserialize, TX_WITH_WITNESS, s};
m_committed_txs.emplace(std::move(hash), std::move(tx));
break;
}
case PSBT_OUT_TAP_INTERNAL_KEYS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, additional output Taproot internal key already provided");
} else if (key.size() != 33) {
throw std::ios_base::failure("Additional output Taproot internal key key is not 33 bytes");
}
XOnlyPubKey output_key{std::span(key).last(32)}, internal_key;
s >> internal_key;
m_tap_internal_keys.emplace(std::move(output_key), std::move(internal_key));
break;
}
case PSBT_OUT_TAP_TREES:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, additional output Taproot internal key already provided");
} else if (key.size() != 33) {
throw std::ios_base::failure("Additional output Taproot internal key key is not 33 bytes");
}
XOnlyPubKey output_key{std::span(key).last(32)};
UnserTapTree(s, m_tap_trees[output_key]);
break;
}
case PSBT_OUT_PROPRIETARY:
{
PSBTProprietary this_prop;
Expand Down
57 changes: 57 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,29 @@ const RPCResult decodepsbt_outputs{
}},
}},
}},
{RPCResult::Type::OBJ_DYN, "committed_transactions", /*optional=*/true, "Map from template hash to corresponding transaction details",
{
{RPCResult::Type::OBJ, "xxxx", "Committed transaction details. Not all listed fields are committed to in the template hash, and may be different in a spending transaction.",
{
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
}},
}},
{RPCResult::Type::OBJ_DYN, "taproot_internal_keys", /*optional=*/true, "Map from output key to internal key for Taproot outputs of committed transactions",
{
{RPCResult::Type::STR_HEX, "xxxx", "Taproot internal key, keyed by Taproot output key"},
}},
{RPCResult::Type::OBJ_DYN, "taproot_trees", /*optional=*/true, "Map from output key to Taproot tree for outputs of committed transactions",
{
{RPCResult::Type::ARR, "xxxx", "List of tuples that make up the Taproot tree, in depth first search order, keyed by Taproot output key",
{
{RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree",
{
{RPCResult::Type::NUM, "depth", "The depth of this element in the tree"},
{RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"},
{RPCResult::Type::STR, "script", "The hex-encoded script itself"},
}},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
Expand Down Expand Up @@ -1403,6 +1426,40 @@ static RPCHelpMan decodepsbt()
out.pushKV("taproot_bip32_derivs", std::move(keypaths));
}

if (!output.m_committed_txs.empty()) {
UniValue tx_map{UniValue::VOBJ};
for (const auto& [template_hash, tx]: output.m_committed_txs) {
UniValue tx_details{UniValue::VOBJ};
TxToUniv(CTransaction{tx}, /*block_hash=*/uint256{}, /*entry=*/tx_details, /*include_hex=*/false);
tx_map.pushKV(HexStr(template_hash), std::move(tx_details));
}
out.pushKV("committed_transactions", tx_map);
}

if (!output.m_tap_internal_keys.empty()) {
UniValue keys_map{UniValue::VOBJ};
for (const auto& [output_key, internal_key]: output.m_tap_internal_keys) {
keys_map.pushKV(HexStr(output_key), HexStr(internal_key));
}
out.pushKV("taproot_internal_keys", std::move(keys_map));
}

if (!output.m_tap_trees.empty()) {
UniValue trees_map{UniValue::VOBJ};
for (const auto& [output_key, tap_tree]: output.m_tap_trees) {
UniValue tree(UniValue::VARR);
for (const auto& [depth, leaf_ver, script] : tap_tree) {
UniValue elem(UniValue::VOBJ);
elem.pushKV("depth", depth);
elem.pushKV("leaf_ver", leaf_ver);
elem.pushKV("script", HexStr(script));
tree.push_back(std::move(elem));
}
trees_map.pushKV(HexStr(output_key), std::move(tree));
}
out.pushKV("taproot_trees", std::move(trees_map));
}

// Proprietary
if (!output.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
Expand Down
Loading
Loading