2019-04-02 16:51:32 -04:00
// Copyright (c) 2010 Satoshi Nakamoto
2022-12-24 23:49:50 +00:00
// Copyright (c) 2009-2022 The Bitcoin Core developers
2019-04-02 16:51:32 -04:00
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <rpc/rawtransaction_util.h>
2020-11-09 21:20:40 +00:00
# include <block_proof.h>
2019-04-02 16:51:32 -04:00
# include <coins.h>
2021-09-11 10:29:00 +08:00
# include <consensus/amount.h>
2019-04-02 16:51:32 -04:00
# include <core_io.h>
# include <key_io.h>
2020-10-28 03:15:23 +00:00
# include <pegins.h>
2019-04-02 16:51:32 -04:00
# include <policy/policy.h>
# include <primitives/transaction.h>
2020-11-09 21:20:40 +00:00
# include <primitives/bitcoin/merkleblock.h>
# include <primitives/bitcoin/transaction.h>
2019-06-20 02:39:38 +09:00
# include <rpc/request.h>
2019-04-02 16:51:32 -04:00
# include <rpc/util.h>
2019-06-06 22:52:24 +02:00
# include <script/sign.h>
# include <script/signingprovider.h>
2019-04-02 16:51:32 -04:00
# include <tinyformat.h>
# include <univalue.h>
2019-04-02 17:03:37 -04:00
# include <util/rbf.h>
2019-04-02 16:51:32 -04:00
# include <util/strencodings.h>
2021-06-23 17:28:54 -04:00
# include <util/translation.h>
2020-11-09 21:20:21 +00:00
# include <validation.h>
2019-04-02 16:51:32 -04:00
2020-11-09 21:20:40 +00:00
template < typename T_tx >
unsigned int GetPeginTxnOutputIndex ( const T_tx & txn , const CScript & witnessProgram , const std : : vector < std : : pair < CScript , CScript > > & fedpegscripts )
{
for ( const auto & scripts : fedpegscripts ) {
2020-11-27 21:29:55 +00:00
CScript mainchain_script = GetScriptForDestination ( WitnessV0ScriptHash ( calculate_contract ( scripts . second , witnessProgram ) ) ) ;
2020-11-09 21:20:40 +00:00
if ( scripts . first . IsPayToScriptHash ( ) ) {
mainchain_script = GetScriptForDestination ( ScriptHash ( mainchain_script ) ) ;
}
for ( unsigned int nOut = 0 ; nOut < txn . vout . size ( ) ; nOut + + ) {
if ( txn . vout [ nOut ] . scriptPubKey = = mainchain_script ) {
return nOut ;
}
}
}
return txn . vout . size ( ) ;
}
// Modifies an existing transaction input in-place to be a valid peg-in input, and inserts the witness if deemed valid.
template < typename T_tx_ref , typename T_merkle_block >
2021-07-29 13:18:11 +00:00
static void CreatePegInInputInner ( CMutableTransaction & mtx , uint32_t input_idx , T_tx_ref & txBTCRef , T_merkle_block & merkleBlock , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData , const CBlockIndex * active_chain_tip )
2020-11-09 21:20:40 +00:00
{
if ( ( mtx . vin . size ( ) > input_idx & & ! mtx . vin [ input_idx ] . scriptSig . empty ( ) ) | | ( mtx . witness . vtxinwit . size ( ) > input_idx & & ! mtx . witness . vtxinwit [ input_idx ] . IsNull ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Attempting to add a peg-in to an input that already has a scriptSig or witness " ) ;
}
2025-11-06 13:08:59 +02:00
DataStream ssTx ( txData ) ;
2020-11-09 21:20:40 +00:00
try {
2025-11-06 13:08:59 +02:00
ssTx > > TX_WITH_WITNESS ( txBTCRef ) ;
2020-11-09 21:20:40 +00:00
}
catch ( . . . ) {
throw JSONRPCError ( RPC_TYPE_ERROR , " The included bitcoinTx is malformed. Are you sure that is the whole string? " ) ;
}
2025-11-06 13:08:59 +02:00
DataStream ssTxOutProof ( txOutProofData ) ;
2020-11-09 21:20:40 +00:00
try {
2025-11-06 13:08:59 +02:00
ssTxOutProof > > TX_NO_WITNESS ( merkleBlock ) ;
2020-11-09 21:20:40 +00:00
}
catch ( . . . ) {
throw JSONRPCError ( RPC_TYPE_ERROR , " The included txoutproof is malformed. Are you sure that is the whole string? " ) ;
}
if ( ! ssTxOutProof . empty ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
std : : vector < uint256 > txHashes ;
std : : vector < unsigned int > txIndices ;
if ( merkleBlock . txn . ExtractMatches ( txHashes , txIndices ) ! = merkleBlock . header . hashMerkleRoot )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
if ( txHashes . size ( ) ! = 1 | | txHashes [ 0 ] ! = txBTCRef - > GetHash ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " The txoutproof must contain bitcoinTx and only bitcoinTx " ) ;
CScript witness_script ;
unsigned int nOut = txBTCRef - > vout . size ( ) ;
2021-07-29 13:18:11 +00:00
const auto fedpegscripts = GetValidFedpegScripts ( active_chain_tip , Params ( ) . GetConsensus ( ) , true /* nextblock_validation */ ) ;
2020-11-09 21:20:40 +00:00
for ( const CScript & script : claim_scripts ) {
nOut = GetPeginTxnOutputIndex ( * txBTCRef , script , fedpegscripts ) ;
if ( nOut ! = txBTCRef - > vout . size ( ) ) {
witness_script = script ;
break ;
}
}
if ( nOut = = txBTCRef - > vout . size ( ) ) {
if ( claim_scripts . size ( ) = = 1 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given claim_script does not match the given Bitcoin transaction. " ) ;
} else {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress " ) ;
}
}
2020-12-04 15:17:46 +00:00
CHECK_NONFATAL ( witness_script ! = CScript ( ) ) ;
2020-11-09 21:20:40 +00:00
int version = - 1 ;
std : : vector < unsigned char > witness_program ;
if ( ! witness_script . IsWitnessProgram ( version , witness_program ) | | version ! = 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given or recovered script is not a v0 witness program. " ) ;
}
CAmount value = 0 ;
if ( ! GetAmountFromParentChainPegin ( value , * txBTCRef , nOut ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Amounts to pegin must be explicit and asset must be %s " , Params ( ) . GetConsensus ( ) . parent_pegged_asset . GetHex ( ) ) ) ;
}
// Add/replace input in mtx
if ( mtx . vin . size ( ) < = input_idx ) {
mtx . vin . resize ( input_idx + 1 ) ;
}
2025-11-10 15:40:17 +02:00
mtx . vin [ input_idx ] = CTxIn ( COutPoint ( Txid : : FromUint256 ( txHashes [ 0 ] ) , nOut ) , CScript ( ) , ~ ( uint32_t ) 0 ) ;
2020-11-09 21:20:40 +00:00
// Construct pegin proof
CScriptWitness pegin_witness = CreatePeginWitness ( value , Params ( ) . GetConsensus ( ) . pegged_asset , Params ( ) . ParentGenesisBlockHash ( ) , witness_script , txBTCRef , merkleBlock ) ;
// Peg-in witness isn't valid, even though the block header is(without depth check)
// We re-check depth before returning with more descriptive result
std : : string err ;
if ( ! IsValidPeginWitness ( pegin_witness , fedpegscripts , mtx . vin [ input_idx ] . prevout , err , false ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Constructed peg-in witness is invalid: %s " , err ) ) ;
}
// Put input witness in transaction
mtx . vin [ input_idx ] . m_is_pegin = true ;
CTxInWitness txinwit ;
txinwit . m_pegin_witness = pegin_witness ;
if ( mtx . witness . vtxinwit . size ( ) < = input_idx ) {
mtx . witness . vtxinwit . resize ( input_idx + 1 ) ;
}
mtx . witness . vtxinwit [ input_idx ] = txinwit ;
}
2021-07-29 13:18:11 +00:00
void CreatePegInInput ( CMutableTransaction & mtx , uint32_t input_idx , CTransactionRef & tx_btc , CMerkleBlock & merkle_block , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData , const CBlockIndex * active_chain_tip )
2020-11-09 21:20:40 +00:00
{
2021-07-29 13:18:11 +00:00
CreatePegInInputInner ( mtx , input_idx , tx_btc , merkle_block , claim_scripts , txData , txOutProofData , active_chain_tip ) ;
2020-11-09 21:20:40 +00:00
}
2021-07-29 13:18:11 +00:00
void CreatePegInInput ( CMutableTransaction & mtx , uint32_t input_idx , Sidechain : : Bitcoin : : CTransactionRef & tx_btc , Sidechain : : Bitcoin : : CMerkleBlock & merkle_block , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData , const CBlockIndex * active_chain_tip )
2020-11-09 21:20:40 +00:00
{
2021-07-29 13:18:11 +00:00
CreatePegInInputInner ( mtx , input_idx , tx_btc , merkle_block , claim_scripts , txData , txOutProofData , active_chain_tip ) ;
2020-11-09 21:20:40 +00:00
}
2025-04-06 21:07:49 +02:00
void AddInputs ( CMutableTransaction & rawTx , const UniValue & inputs_in , std : : optional < bool > rbf , const CBlockIndex * active_chain_tip , bool allow_peg_in , bool allow_issuance )
2019-04-02 16:51:32 -04:00
{
2019-07-11 18:50:45 +01:00
UniValue inputs ;
2020-09-17 15:29:32 +02:00
if ( inputs_in . isNull ( ) ) {
2019-07-11 18:50:45 +01:00
inputs = UniValue : : VARR ;
2020-09-17 15:29:32 +02:00
} else {
2019-07-11 18:50:45 +01:00
inputs = inputs_in . get_array ( ) ;
2020-09-17 15:29:32 +02:00
}
2019-04-02 16:51:32 -04:00
for ( unsigned int idx = 0 ; idx < inputs . size ( ) ; idx + + ) {
const UniValue & input = inputs [ idx ] ;
const UniValue & o = input . get_obj ( ) ;
2023-10-11 14:53:04 +01:00
Txid txid = Txid : : FromUint256 ( ParseHashO ( o , " txid " ) ) ;
2019-04-02 16:51:32 -04:00
2023-05-09 09:25:50 +02:00
const UniValue & vout_v = o . find_value ( " vout " ) ;
2019-04-02 16:51:32 -04:00
if ( ! vout_v . isNum ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, missing vout key " ) ;
2022-05-13 12:32:59 +02:00
int nOutput = vout_v . getInt < int > ( ) ;
2019-04-02 16:51:32 -04:00
if ( nOutput < 0 )
2020-09-30 20:43:05 +03:30
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, vout cannot be negative " ) ;
2019-04-02 16:51:32 -04:00
uint32_t nSequence ;
2022-07-13 16:29:27 -04:00
if ( rbf . value_or ( true ) ) {
2019-04-02 16:51:32 -04:00
nSequence = MAX_BIP125_RBF_SEQUENCE ; /* CTxIn::SEQUENCE_FINAL - 2 */
} else if ( rawTx . nLockTime ) {
2022-01-10 14:48:13 +01:00
nSequence = CTxIn : : MAX_SEQUENCE_NONFINAL ; /* CTxIn::SEQUENCE_FINAL - 1 */
2019-04-02 16:51:32 -04:00
} else {
nSequence = CTxIn : : SEQUENCE_FINAL ;
}
// set the sequence number if passed in the parameters object
2023-05-09 09:25:50 +02:00
const UniValue & sequenceObj = o . find_value ( " sequence " ) ;
2019-04-02 16:51:32 -04:00
if ( sequenceObj . isNum ( ) ) {
2022-05-13 12:32:59 +02:00
int64_t seqNr64 = sequenceObj . getInt < int64_t > ( ) ;
2019-04-02 16:51:32 -04:00
if ( seqNr64 < 0 | | seqNr64 > CTxIn : : SEQUENCE_FINAL ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, sequence number is out of range " ) ;
} else {
nSequence = ( uint32_t ) seqNr64 ;
}
}
CTxIn in ( COutPoint ( txid , nOutput ) , CScript ( ) , nSequence ) ;
2021-03-23 14:53:39 -04:00
// Get issuance stuff if it's there
2025-06-20 09:43:15 +02:00
const UniValue & blinding_nonce_v = o . find_value ( " asset_blinding_nonce " ) ;
const UniValue & entropy_v = o . find_value ( " asset_entropy " ) ;
const UniValue & amount_v = o . find_value ( " issuance_amount " ) ;
const UniValue & issuance_tokens_v = o . find_value ( " issuance_tokens " ) ;
const UniValue & blind_reissuance_v = o . find_value ( " blind_reissuance " ) ;
2021-03-23 14:53:39 -04:00
if ( ! amount_v . isNull ( ) & & allow_issuance ) {
if ( ! amount_v . isNum ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " issuance_amount is not a number " ) ;
}
CAmount amt = AmountFromValue ( amount_v ) ;
// issuance_tokens may be null for reissuance
if ( ! issuance_tokens_v . isNull ( ) ) {
if ( ! issuance_tokens_v . isNum ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " issuance_tokens is not a number " ) ;
}
CAmount num_inflation = AmountFromValue ( issuance_tokens_v ) ;
in . assetIssuance . nInflationKeys . SetToAmount ( num_inflation ) ;
}
uint256 blinding_nonce ;
if ( ! blinding_nonce_v . isNull ( ) ) {
blinding_nonce = ParseHashV ( blinding_nonce_v , " asset_blinding_nonce " ) ;
}
uint256 entropy ;
if ( ! entropy_v . isNull ( ) ) {
entropy = ParseHashV ( entropy_v , " asset_entropy " ) ;
}
in . assetIssuance . nAmount . SetToAmount ( amt ) ;
in . assetIssuance . assetBlindingNonce = blinding_nonce ;
in . assetIssuance . assetEntropy = entropy ;
} else if ( ! blinding_nonce_v . isNull ( ) | | ! entropy_v . isNull ( ) | | ! issuance_tokens_v . isNull ( ) | | ! blind_reissuance_v . isNull ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " auxiliary issuance arguments provided without issuance amount " ) ;
}
// Add to the tx
2019-04-02 16:51:32 -04:00
rawTx . vin . push_back ( in ) ;
2020-11-09 21:20:40 +00:00
// Get the pegin stuff if it's there
2025-06-20 09:43:15 +02:00
const UniValue & pegin_tx = o . find_value ( " pegin_bitcoin_tx " ) ;
const UniValue & pegin_tx_proof = o . find_value ( " pegin_txout_proof " ) ;
const UniValue & pegin_script = o . find_value ( " pegin_claim_script " ) ;
2020-11-09 21:20:40 +00:00
if ( ! pegin_tx . isNull ( ) & & ! pegin_tx_proof . isNull ( ) & & ! pegin_script . isNull ( ) & & allow_peg_in ) {
if ( ! IsHex ( pegin_script . get_str ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given claim_script is not hex. " ) ;
}
// If given manually, no need for it to be a witness script
std : : vector < unsigned char > claim_script_bytes ( ParseHex ( pegin_script . get_str ( ) ) ) ;
CScript claim_script ( claim_script_bytes . begin ( ) , claim_script_bytes . end ( ) ) ;
std : : set < CScript > claim_scripts ;
claim_scripts . insert ( std : : move ( claim_script ) ) ;
if ( Params ( ) . GetConsensus ( ) . ParentChainHasPow ( ) ) {
Sidechain : : Bitcoin : : CTransactionRef tx_btc ;
Sidechain : : Bitcoin : : CMerkleBlock merkle_block ;
2021-07-29 13:18:11 +00:00
CreatePegInInput ( rawTx , idx , tx_btc , merkle_block , claim_scripts , ParseHex ( pegin_tx . get_str ( ) ) , ParseHex ( pegin_tx_proof . get_str ( ) ) , active_chain_tip ) ;
2020-11-09 21:20:40 +00:00
if ( ! CheckParentProofOfWork ( merkle_block . header . GetHash ( ) , merkle_block . header . nBits , Params ( ) . GetConsensus ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
} else {
CTransactionRef tx_btc ;
CMerkleBlock merkle_block ;
2021-07-29 13:18:11 +00:00
CreatePegInInput ( rawTx , idx , tx_btc , merkle_block , claim_scripts , ParseHex ( pegin_tx . get_str ( ) ) , ParseHex ( pegin_tx_proof . get_str ( ) ) , active_chain_tip ) ;
2020-11-09 21:20:40 +00:00
if ( ! CheckProofSignedParent ( merkle_block . header , Params ( ) . GetConsensus ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
}
} else if ( ! pegin_tx . isNull ( ) | | ! pegin_tx_proof . isNull ( ) | | ! pegin_script . isNull ( ) ) {
if ( allow_peg_in ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Some but not all pegin_ arguments provided " ) ;
} else {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " pegin_ arguments provided but this command does not support peg-ins " ) ;
}
}
2019-04-02 16:51:32 -04:00
}
2022-06-11 21:54:52 +03:00
}
2023-12-22 11:10:39 +01:00
UniValue NormalizeOutputs ( const UniValue & outputs_in )
2022-06-11 21:54:52 +03:00
{
if ( outputs_in . isNull ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, output argument must be non-null " ) ;
}
const bool outputs_is_obj = outputs_in . isObject ( ) ;
UniValue outputs = outputs_is_obj ? outputs_in . get_obj ( ) : outputs_in . get_array ( ) ;
2019-04-02 16:51:32 -04:00
2025-11-20 09:05:52 +02:00
// ELEMENTS: we normalize to an array to support sending to the same address with multiple assets
if ( outputs_is_obj ) {
UniValue outputs_array = UniValue ( UniValue : : VARR ) ;
const auto & keys = outputs . getKeys ( ) ;
for ( size_t i = 0 ; i < keys . size ( ) ; + + i ) {
const auto & key = keys [ i ] ;
const auto & val = outputs [ key ] ;
UniValue output = UniValue ( UniValue : : VOBJ ) ;
output . pushKV ( key , val ) ;
outputs_array . push_back ( output ) ;
2019-04-02 16:51:32 -04:00
}
2025-11-20 09:05:52 +02:00
outputs = std : : move ( outputs_array ) ;
2019-04-02 16:51:32 -04:00
}
2023-12-22 11:10:39 +01:00
return outputs ;
}
2019-04-02 16:51:32 -04:00
2025-11-20 09:05:52 +02:00
std : : vector < std : : pair < CTxDestination , CTxOut > > ParseOutputs ( const UniValue & outputs , std : : map < CTxOut , PSBTOutput > * outputs_aux )
2023-12-22 11:10:39 +01:00
{
2019-04-02 16:51:32 -04:00
// Duplicate checking
2025-11-20 09:05:52 +02:00
std : : set < std : : pair < CTxDestination , CAsset > > destinations ;
std : : vector < std : : pair < CTxDestination , CTxOut > > parsed_outputs ;
2019-04-02 16:51:32 -04:00
bool has_data { false } ;
2025-11-20 09:05:52 +02:00
// Keep track of the fee output so we can add it in the very end of the transaction.
CTxOut fee_out ;
2019-04-02 16:51:32 -04:00
2021-03-26 14:47:42 -04:00
std : : vector < PSBTOutput > psbt_outs ;
2025-11-20 09:05:52 +02:00
CHECK_NONFATAL ( outputs . isArray ( ) ) ;
bool add_fee_output = false ;
2021-03-25 19:29:50 -04:00
for ( unsigned int i = 0 ; i < outputs . size ( ) ; + + i ) {
const UniValue & output = outputs [ i ] . get_obj ( ) ;
2021-03-26 14:47:42 -04:00
// New PSBTOutput with version 2
PSBTOutput psbt_out ( 2 ) ;
2021-03-25 19:29:50 -04:00
2020-10-28 03:15:23 +00:00
// ELEMENTS:
// Asset defaults to policyAsset
2021-03-25 19:29:50 -04:00
CTxOut out ( : : policyAsset , 0 , CScript ( ) ) ;
2020-10-28 03:15:23 +00:00
2021-03-25 19:29:50 -04:00
bool is_fee = false ;
2025-07-30 12:41:37 +01:00
CTxDestination destination ;
std : : string dest ;
2021-03-25 19:29:50 -04:00
for ( const std : : string & name_ : output . getKeys ( ) ) {
if ( name_ = = " data " ) {
if ( has_data ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, duplicate key: data " ) ;
}
has_data = true ;
std : : vector < unsigned char > data = ParseHexV ( output [ name_ ] . getValStr ( ) , " Data " ) ;
2020-10-28 03:15:23 +00:00
2021-03-25 19:29:50 -04:00
out . nValue = 0 ;
out . scriptPubKey = CScript ( ) < < OP_RETURN < < data ;
2025-11-20 09:05:52 +02:00
destination = CNoDestination ( out . scriptPubKey ) ;
2021-03-25 19:29:50 -04:00
} else if ( name_ = = " vdata " ) {
// ELEMENTS: support multi-push OP_RETURN
UniValue vdata = output [ name_ ] . get_array ( ) ;
CScript datascript = CScript ( ) < < OP_RETURN ;
for ( size_t i = 0 ; i < vdata . size ( ) ; i + + ) {
std : : vector < unsigned char > data = ParseHexV ( vdata [ i ] . get_str ( ) , " Data " ) ;
datascript < < data ;
}
2019-04-02 16:51:32 -04:00
2021-03-25 19:29:50 -04:00
out . nValue = 0 ;
out . scriptPubKey = datascript ;
2025-11-20 09:05:52 +02:00
destination = CNoDestination ( out . scriptPubKey ) ;
2021-03-25 19:29:50 -04:00
} else if ( name_ = = " fee " ) {
// ELEMENTS: explicit fee outputs
CAmount nAmount = AmountFromValue ( output [ name_ ] ) ;
out . nValue = nAmount ;
out . scriptPubKey = CScript ( ) ;
is_fee = true ;
2025-11-20 09:05:52 +02:00
add_fee_output = true ;
2021-03-25 19:29:50 -04:00
break ;
} else if ( name_ = = " burn " ) {
CScript datascript = CScript ( ) < < OP_RETURN ;
CAmount nAmount = AmountFromValue ( output [ name_ ] ) ;
out . nValue = nAmount ;
out . scriptPubKey = datascript ;
2025-11-30 15:09:38 +02:00
destination = CNoDestination ( out . scriptPubKey ) ;
2021-03-25 19:29:50 -04:00
} else if ( name_ = = " asset " ) {
// ELEMENTS: Assets are specified
out . nAsset = CAsset ( ParseHashO ( output , name_ ) ) ;
} else if ( name_ = = " blinder_index " ) {
2021-03-26 14:47:42 -04:00
// For PSET
2025-06-20 09:43:15 +02:00
psbt_out . m_blinder_index = output . find_value ( name_ ) . getInt < int > ( ) ;
2021-03-25 19:29:50 -04:00
} else {
2025-07-30 12:41:37 +01:00
destination = DecodeDestination ( name_ ) ;
2021-03-25 19:29:50 -04:00
if ( ! IsValidDestination ( destination ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , std : : string ( " Invalid Bitcoin address: " ) + name_ ) ;
}
2025-07-30 12:41:37 +01:00
dest = name_ ;
2019-04-02 16:51:32 -04:00
2021-03-25 19:29:50 -04:00
CScript scriptPubKey = GetScriptForDestination ( destination ) ;
2025-11-20 09:05:52 +02:00
CAmount nAmount = AmountFromValue ( output [ name_ ] , out . nAsset . GetAsset ( ) = = : : policyAsset ) ;
2021-03-25 19:29:50 -04:00
out . nValue = nAmount ;
out . scriptPubKey = scriptPubKey ;
CPubKey blind_pub ;
if ( IsBlindDestination ( destination ) ) {
blind_pub = GetDestinationBlindingKey ( destination ) ;
2021-03-26 14:47:42 -04:00
if ( ! outputs_aux ) {
2021-03-25 19:29:50 -04:00
// Only use the pubkey-in-nonce hack if the caller is not getting the pubkeys the nice way.
out . nNonce . vchCommitment = std : : vector < unsigned char > ( blind_pub . begin ( ) , blind_pub . end ( ) ) ;
}
2020-11-09 21:20:10 +00:00
}
2021-03-26 14:47:42 -04:00
psbt_out . m_blinding_pubkey = blind_pub ;
2020-10-28 03:15:23 +00:00
}
2021-03-25 19:29:50 -04:00
}
2025-07-30 12:41:37 +01:00
if ( ! destinations . emplace ( destination , out . nAsset . GetAsset ( ) ) . second ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , std : : string ( " Invalid parameter, duplicated address and asset: " + dest + " " + out . nAsset . GetHex ( ) ) ) ;
}
2021-03-25 19:29:50 -04:00
if ( is_fee ) {
fee_out = out ;
} else {
2025-11-20 09:05:52 +02:00
parsed_outputs . emplace_back ( destination , out ) ;
2021-03-26 14:47:42 -04:00
psbt_outs . push_back ( psbt_out ) ;
2019-04-02 16:51:32 -04:00
}
}
2020-10-28 03:15:23 +00:00
// Add fee output in the end.
2025-11-20 09:05:52 +02:00
if ( add_fee_output & & ! fee_out . nValue . IsNull ( ) & & fee_out . nValue . GetAmount ( ) > 0 ) {
parsed_outputs . emplace_back ( CNoDestination ( ) , fee_out ) ;
2021-03-26 14:47:42 -04:00
// New PSBTOutput with version 2
psbt_outs . emplace_back ( 2 ) ;
}
if ( outputs_aux ) {
2025-11-20 09:05:52 +02:00
for ( unsigned int i = 0 ; i < parsed_outputs . size ( ) ; + + i ) {
const auto & [ destination , out ] = parsed_outputs [ i ] ;
outputs_aux - > insert ( std : : make_pair ( out , psbt_outs [ i ] ) ) ;
2020-11-09 21:20:10 +00:00
}
2020-10-28 03:15:23 +00:00
}
2023-12-22 11:27:53 +01:00
return parsed_outputs ;
}
2019-04-02 16:51:32 -04:00
2025-11-20 09:05:52 +02:00
void AddOutputs ( CMutableTransaction & rawTx , const UniValue & outputs_in , std : : map < CTxOut , PSBTOutput > * outputs_aux )
2023-12-22 11:27:53 +01:00
{
UniValue outputs ( UniValue : : VOBJ ) ;
outputs = NormalizeOutputs ( outputs_in ) ;
2019-04-02 16:51:32 -04:00
2025-11-20 09:05:52 +02:00
std : : vector < std : : pair < CTxDestination , CTxOut > > parsed_outputs = ParseOutputs ( outputs , outputs_aux ) ;
for ( const auto & [ destination , out ] : parsed_outputs ) {
2023-12-22 11:27:53 +01:00
rawTx . vout . push_back ( out ) ;
2019-04-02 16:51:32 -04:00
}
2022-06-11 21:54:52 +03:00
}
2025-04-06 21:07:49 +02:00
CMutableTransaction ConstructTransaction ( const UniValue & inputs_in , const UniValue & outputs_in , const UniValue & locktime , std : : optional < bool > rbf , const CBlockIndex * active_chain_tip , std : : map < CTxOut , PSBTOutput > * outputs_aux , bool allow_peg_in , bool allow_issuance )
2022-06-11 21:54:52 +03:00
{
CMutableTransaction rawTx ;
if ( ! locktime . isNull ( ) ) {
int64_t nLockTime = locktime . getInt < int64_t > ( ) ;
if ( nLockTime < 0 | | nLockTime > LOCKTIME_MAX )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, locktime out of range " ) ;
rawTx . nLockTime = nLockTime ;
}
2025-04-06 21:07:49 +02:00
AddInputs ( rawTx , inputs_in , rbf , active_chain_tip , allow_peg_in , allow_issuance ) ;
AddOutputs ( rawTx , outputs_in , outputs_aux ) ;
2020-10-28 03:15:23 +00:00
2022-07-13 16:29:27 -04:00
if ( rbf . has_value ( ) & & rbf . value ( ) & & rawTx . vin . size ( ) > 0 & & ! SignalsOptInRBF ( CTransaction ( rawTx ) ) ) {
2019-04-02 16:51:32 -04:00
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter combination: Sequence number(s) contradict replaceable option " ) ;
}
return rawTx ;
}
2020-11-09 21:20:10 +00:00
2019-04-02 16:51:32 -04:00
/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
2020-10-28 03:15:23 +00:00
static void TxInErrorToJSON ( const CTxIn & txin , const CTxInWitness & txinwit , UniValue & vErrorsRet , const std : : string & strMessage )
2019-04-02 16:51:32 -04:00
{
UniValue entry ( UniValue : : VOBJ ) ;
entry . pushKV ( " txid " , txin . prevout . hash . ToString ( ) ) ;
entry . pushKV ( " vout " , ( uint64_t ) txin . prevout . n ) ;
UniValue witness ( UniValue : : VARR ) ;
2020-10-28 03:15:23 +00:00
for ( unsigned int i = 0 ; i < txinwit . scriptWitness . stack . size ( ) ; i + + ) {
2020-11-26 01:09:12 +00:00
witness . push_back ( HexStr ( txinwit . scriptWitness . stack [ i ] ) ) ;
2019-04-02 16:51:32 -04:00
}
2024-05-13 20:20:10 +00:00
entry . pushKV ( " witness " , std : : move ( witness ) ) ;
2020-06-24 15:48:26 +02:00
entry . pushKV ( " scriptSig " , HexStr ( txin . scriptSig ) ) ;
2019-04-02 16:51:32 -04:00
entry . pushKV ( " sequence " , ( uint64_t ) txin . nSequence ) ;
entry . pushKV ( " error " , strMessage ) ;
2024-05-13 20:20:10 +00:00
vErrorsRet . push_back ( std : : move ( entry ) ) ;
2019-04-02 16:51:32 -04:00
}
2023-08-21 17:50:09 -03:00
void ParsePrevouts ( const UniValue & prevTxsUnival , FlatSigningProvider * keystore , std : : map < COutPoint , Coin > & coins )
2019-04-02 16:51:32 -04:00
{
if ( ! prevTxsUnival . isNull ( ) ) {
2022-07-26 11:12:53 +02:00
const UniValue & prevTxs = prevTxsUnival . get_array ( ) ;
2019-04-02 16:51:32 -04:00
for ( unsigned int idx = 0 ; idx < prevTxs . size ( ) ; + + idx ) {
const UniValue & p = prevTxs [ idx ] ;
if ( ! p . isObject ( ) ) {
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , " expected object with { \" txid' \" , \" vout \" , \" scriptPubKey \" } " ) ;
}
2022-07-26 11:12:53 +02:00
const UniValue & prevOut = p . get_obj ( ) ;
2019-04-02 16:51:32 -04:00
RPCTypeCheckObj ( prevOut ,
{
{ " txid " , UniValueType ( UniValue : : VSTR ) } ,
{ " vout " , UniValueType ( UniValue : : VNUM ) } ,
{ " scriptPubKey " , UniValueType ( UniValue : : VSTR ) } ,
} ) ;
2023-10-11 14:53:04 +01:00
Txid txid = Txid : : FromUint256 ( ParseHashO ( prevOut , " txid " ) ) ;
2019-04-02 16:51:32 -04:00
2023-05-09 09:25:50 +02:00
int nOut = prevOut . find_value ( " vout " ) . getInt < int > ( ) ;
2019-04-02 16:51:32 -04:00
if ( nOut < 0 ) {
2020-09-30 20:43:05 +03:30
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , " vout cannot be negative " ) ;
2019-04-02 16:51:32 -04:00
}
COutPoint out ( txid , nOut ) ;
std : : vector < unsigned char > pkData ( ParseHexO ( prevOut , " scriptPubKey " ) ) ;
CScript scriptPubKey ( pkData . begin ( ) , pkData . end ( ) ) ;
{
auto coin = coins . find ( out ) ;
if ( coin ! = coins . end ( ) & & ! coin - > second . IsSpent ( ) & & coin - > second . out . scriptPubKey ! = scriptPubKey ) {
std : : string err ( " Previous output scriptPubKey mismatch: \n " ) ;
err = err + ScriptToAsmStr ( coin - > second . out . scriptPubKey ) + " \n vs: \n " +
ScriptToAsmStr ( scriptPubKey ) ;
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , err ) ;
}
Coin newcoin ;
newcoin . out . scriptPubKey = scriptPubKey ;
2020-10-28 03:15:23 +00:00
newcoin . out . nValue = CConfidentialValue ( MAX_MONEY ) ;
2019-04-02 16:51:32 -04:00
if ( prevOut . exists ( " amount " ) ) {
2025-06-20 09:43:15 +02:00
newcoin . out . nValue = CConfidentialValue ( AmountFromValue ( prevOut . find_value ( " amount " ) ) ) ;
2020-10-28 03:15:23 +00:00
} else if ( prevOut . exists ( " amountcommitment " ) ) {
// Segwit sigs require the amount commitment to be sighashed
2020-11-28 23:04:05 +00:00
newcoin . out . nValue . vchCommitment = ParseHexO ( prevOut , " amountcommitment " ) ;
2019-04-02 16:51:32 -04:00
}
newcoin . nHeight = 1 ;
coins [ out ] = std : : move ( newcoin ) ;
}
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
2019-06-20 18:01:23 +10:00
const bool is_p2sh = scriptPubKey . IsPayToScriptHash ( ) ;
const bool is_p2wsh = scriptPubKey . IsPayToWitnessScriptHash ( ) ;
if ( keystore & & ( is_p2sh | | is_p2wsh ) ) {
2019-04-02 16:51:32 -04:00
RPCTypeCheckObj ( prevOut ,
{
{ " redeemScript " , UniValueType ( UniValue : : VSTR ) } ,
{ " witnessScript " , UniValueType ( UniValue : : VSTR ) } ,
} , true ) ;
2023-05-09 11:26:58 +02:00
const UniValue & rs { prevOut . find_value ( " redeemScript " ) } ;
const UniValue & ws { prevOut . find_value ( " witnessScript " ) } ;
2019-06-20 14:02:06 +10:00
if ( rs . isNull ( ) & & ws . isNull ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Missing redeemScript/witnessScript " ) ;
}
2019-06-20 18:01:23 +10:00
// work from witnessScript when possible
std : : vector < unsigned char > scriptData ( ! ws . isNull ( ) ? ParseHexV ( ws , " witnessScript " ) : ParseHexV ( rs , " redeemScript " ) ) ;
CScript script ( scriptData . begin ( ) , scriptData . end ( ) ) ;
2023-08-21 17:50:09 -03:00
keystore - > scripts . emplace ( CScriptID ( script ) , script ) ;
2019-06-20 18:01:23 +10:00
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
// This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
2020-03-31 13:36:34 -04:00
CScript witness_output_script { GetScriptForDestination ( WitnessV0ScriptHash ( script ) ) } ;
2023-08-21 17:50:09 -03:00
keystore - > scripts . emplace ( CScriptID ( witness_output_script ) , witness_output_script ) ;
2019-06-20 18:01:23 +10:00
if ( ! ws . isNull ( ) & & ! rs . isNull ( ) ) {
// if both witnessScript and redeemScript are provided,
// they should either be the same (for backwards compat),
// or the redeemScript should be the encoded form of
// the witnessScript (ie, for p2sh-p2wsh)
if ( ws . get_str ( ) ! = rs . get_str ( ) ) {
std : : vector < unsigned char > redeemScriptData ( ParseHexV ( rs , " redeemScript " ) ) ;
CScript redeemScript ( redeemScriptData . begin ( ) , redeemScriptData . end ( ) ) ;
if ( redeemScript ! = witness_output_script ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript does not correspond to witnessScript " ) ;
}
}
}
if ( is_p2sh ) {
const CTxDestination p2sh { ScriptHash ( script ) } ;
const CTxDestination p2sh_p2wsh { ScriptHash ( witness_output_script ) } ;
if ( scriptPubKey = = GetScriptForDestination ( p2sh ) ) {
// traditional p2sh; arguably an error if
// we got here with rs.IsNull(), because
// that means the p2sh script was specified
// via witnessScript param, but for now
// we'll just quietly accept it
} else if ( scriptPubKey = = GetScriptForDestination ( p2sh_p2wsh ) ) {
// p2wsh encoded as p2sh; ideally the witness
// script was specified in the witnessScript
// param, but also support specifying it via
// redeemScript param for backwards compat
// (in which case ws.IsNull() == true)
} else {
// otherwise, can't generate scriptPubKey from
// either script, so we got unusable parameters
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript/witnessScript does not match scriptPubKey " ) ;
}
} else if ( is_p2wsh ) {
// plain p2wsh; could throw an error if script
// was specified by redeemScript rather than
// witnessScript (ie, ws.IsNull() == true), but
// accept it for backwards compat
const CTxDestination p2wsh { WitnessV0ScriptHash ( script ) } ;
if ( scriptPubKey ! = GetScriptForDestination ( p2wsh ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript/witnessScript does not match scriptPubKey " ) ;
}
}
2019-04-02 16:51:32 -04:00
}
}
}
2019-07-05 17:39:31 -04:00
}
2019-04-02 16:51:32 -04:00
2020-11-26 01:07:51 +00:00
// ELEMENTS: check whether pegin inputs make sense against the current fedpegscript
2023-04-07 11:14:41 +00:00
bool ValidateTransactionPeginInputs ( const CMutableTransaction & mtx , const CBlockIndex * active_chain_tip , std : : map < int , bilingual_str > & input_errors )
2019-07-05 17:39:31 -04:00
{
2021-07-29 13:18:11 +00:00
const auto & fedpegscripts = GetValidFedpegScripts ( active_chain_tip , Params ( ) . GetConsensus ( ) , true /* nextblock_validation */ ) ;
2020-10-28 03:15:23 +00:00
// Track an immature peg-in that's otherwise valid, give warning
bool immature_pegin = false ;
2019-04-02 16:51:32 -04:00
for ( unsigned int i = 0 ; i < mtx . vin . size ( ) ; i + + ) {
2020-11-26 01:07:51 +00:00
const CTxIn & txin = mtx . vin [ i ] ;
2020-10-28 03:15:23 +00:00
std : : string err ;
2020-11-26 01:07:51 +00:00
if ( txin . m_is_pegin & & ( mtx . witness . vtxinwit . size ( ) < = i | | ! IsValidPeginWitness ( mtx . witness . vtxinwit [ i ] . m_pegin_witness , fedpegscripts , txin . prevout , err , false ) ) ) {
2023-04-07 11:14:41 +00:00
input_errors [ i ] = _ ( " Peg-in input has invalid proof. " ) ;
2020-10-28 03:15:23 +00:00
continue ;
}
// Report warning about immature peg-in though
When validation is waiting for parent chain daemon, "stall".
Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.
Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.
We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
2021-09-07 17:01:33 +00:00
bool depth_failed = false ;
if ( txin . m_is_pegin & & ! IsValidPeginWitness ( mtx . witness . vtxinwit [ i ] . m_pegin_witness , fedpegscripts , txin . prevout , err , true , & depth_failed ) ) {
CHECK_NONFATAL ( depth_failed ) ;
2020-10-28 03:15:23 +00:00
immature_pegin = true ;
2019-04-02 16:51:32 -04:00
}
2020-11-26 01:07:51 +00:00
}
return immature_pegin ;
}
2020-10-28 03:15:23 +00:00
2021-07-29 13:18:11 +00:00
void SignTransaction ( CMutableTransaction & mtx , const SigningProvider * keystore , const std : : map < COutPoint , Coin > & coins , const UniValue & hashType , UniValue & result , const CBlockIndex * active_chain_tip )
2019-07-05 17:39:31 -04:00
{
2019-04-02 16:51:32 -04:00
int nHashType = ParseSighashString ( hashType ) ;
// Script verification errors
2021-06-23 17:28:54 -04:00
std : : map < int , bilingual_str > input_errors ;
2019-04-02 16:51:32 -04:00
2021-07-29 13:18:11 +00:00
bool immature_pegin = ValidateTransactionPeginInputs ( mtx , active_chain_tip , input_errors ) ;
2021-09-04 21:11:12 +00:00
bool complete = SignTransaction ( mtx , keystore , coins , nHashType , Params ( ) . HashGenesisBlock ( ) , input_errors ) ;
2020-11-26 01:07:51 +00:00
SignTransactionResultToJSON ( mtx , complete , coins , input_errors , immature_pegin , result ) ;
2020-02-10 19:19:59 -05:00
}
2019-04-02 16:51:32 -04:00
2023-04-07 11:14:41 +00:00
void SignTransactionResultToJSON ( CMutableTransaction & mtx , bool complete , const std : : map < COutPoint , Coin > & coins , const std : : map < int , bilingual_str > & input_errors , bool immature_pegin , UniValue & result )
2020-02-10 19:19:59 -05:00
{
// Make errors UniValue
UniValue vErrors ( UniValue : : VARR ) ;
for ( const auto & err_pair : input_errors ) {
2021-06-23 17:28:54 -04:00
if ( err_pair . second . original = = " Missing amount " ) {
2020-02-10 19:19:59 -05:00
// This particular error needs to be an exception for some reason
throw JSONRPCError ( RPC_TYPE_ERROR , strprintf ( " Missing amount for %s " , coins . at ( mtx . vin . at ( err_pair . first ) . prevout ) . out . ToString ( ) ) ) ;
2019-04-02 16:51:32 -04:00
}
2023-04-07 11:14:41 +00:00
TxInErrorToJSON ( mtx . vin . at ( err_pair . first ) , mtx . witness . vtxinwit . at ( err_pair . first ) , vErrors , err_pair . second . original ) ;
2019-04-02 16:51:32 -04:00
}
result . pushKV ( " hex " , EncodeHexTx ( CTransaction ( mtx ) ) ) ;
2020-02-10 19:19:59 -05:00
result . pushKV ( " complete " , complete ) ;
2019-04-02 16:51:32 -04:00
if ( ! vErrors . empty ( ) ) {
2019-11-18 14:56:52 -05:00
if ( result . exists ( " errors " ) ) {
vErrors . push_backV ( result [ " errors " ] . getValues ( ) ) ;
}
2024-05-13 20:20:10 +00:00
result . pushKV ( " errors " , std : : move ( vErrors ) ) ;
2019-04-02 16:51:32 -04:00
}
2020-10-28 03:15:23 +00:00
if ( immature_pegin ) {
result . pushKV ( " warning " , " Possibly immature peg-in input(s) detected, signed anyways. " ) ;
}
2019-04-02 16:51:32 -04:00
}