bitcoin/src/outputtype.cpp
furszy 0c9fedfc45
fix incorrect multisig redeem script size limit for segwit
The multisig script generation process currently fails when
the user exceeds 15 keys, even when it shouldn't. The maximum
number of keys allowed for segwit redeem scripts (p2sh-segwit
and bech32) is 20 keys.
This is because the redeem script placed in the witness is not
restricted by the item size limit.

The reason behind this issue is the utilization of the legacy
p2sh redeem script restrictions on segwit ones. Redeem scripts
longer than 520 bytes are blocked from being inserted into the
keystore, which causes the signing process and the descriptor
inference process to fail.

This occurs because the multisig generation flow uses the same
keystore as the legacy spkm (FillableSigningProvider), which
contains the 520-byte limit.
2024-05-03 14:20:44 -03:00

125 lines
4.4 KiB
C++

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <outputtype.h>
#include <pubkey.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <util/vector.h>
#include <assert.h>
#include <optional>
#include <string>
static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m";
static const std::string OUTPUT_TYPE_STRING_UNKNOWN = "unknown";
std::optional<OutputType> ParseOutputType(const std::string& type)
{
if (type == OUTPUT_TYPE_STRING_LEGACY) {
return OutputType::LEGACY;
} else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) {
return OutputType::P2SH_SEGWIT;
} else if (type == OUTPUT_TYPE_STRING_BECH32) {
return OutputType::BECH32;
} else if (type == OUTPUT_TYPE_STRING_BECH32M) {
return OutputType::BECH32M;
}
return std::nullopt;
}
const std::string& FormatOutputType(OutputType type)
{
switch (type) {
case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY;
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M;
case OutputType::UNKNOWN: return OUTPUT_TYPE_STRING_UNKNOWN;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
{
switch (type) {
case OutputType::LEGACY: return PKHash(key);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
if (!key.IsCompressed()) return PKHash(key);
CTxDestination witdest = WitnessV0KeyHash(key);
CScript witprog = GetScriptForDestination(witdest);
if (type == OutputType::P2SH_SEGWIT) {
return ScriptHash(witprog);
} else {
return witdest;
}
}
case OutputType::BECH32M:
case OutputType::UNKNOWN: {} // This function should never be used with BECH32M or UNKNOWN, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
{
PKHash keyid(key);
CTxDestination p2pkh{keyid};
if (key.IsCompressed()) {
CTxDestination segwit = WitnessV0KeyHash(keyid);
CTxDestination p2sh = ScriptHash(GetScriptForDestination(segwit));
return Vector(std::move(p2pkh), std::move(p2sh), std::move(segwit));
} else {
return Vector(std::move(p2pkh));
}
}
CTxDestination AddAndGetDestinationForScript(FlatSigningProvider& keystore, const CScript& script, OutputType type)
{
// Add script to keystore
keystore.scripts.emplace(CScriptID(script), script);
switch (type) {
case OutputType::LEGACY:
return ScriptHash(script);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
CTxDestination witdest = WitnessV0ScriptHash(script);
CScript witprog = GetScriptForDestination(witdest);
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
keystore.scripts.emplace(CScriptID(witprog), witprog);
if (type == OutputType::BECH32) {
return witdest;
} else {
return ScriptHash(witprog);
}
}
case OutputType::BECH32M:
case OutputType::UNKNOWN: {} // This function should not be used for BECH32M or UNKNOWN, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
if (std::holds_alternative<PKHash>(dest) ||
std::holds_alternative<ScriptHash>(dest)) {
return OutputType::LEGACY;
}
if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
std::holds_alternative<WitnessV0ScriptHash>(dest)) {
return OutputType::BECH32;
}
if (std::holds_alternative<WitnessV1Taproot>(dest) ||
std::holds_alternative<WitnessUnknown>(dest)) {
return OutputType::BECH32M;
}
return std::nullopt;
}