3131#include < confidential_validation.h>
3232#include < validationinterface.h>
3333#include < blind.h>
34+ #include < issuance.h>
3435#include < rpc/util.h>
3536#ifdef ENABLE_WALLET
3637#include < wallet/rpcwallet.h>
@@ -1937,6 +1938,329 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
19371938 return EncodeHexTx (tx);
19381939}
19391940
1941+ struct RawIssuanceDetails
1942+ {
1943+ int input_index;
1944+ uint256 entropy;
1945+ CAsset asset;
1946+ CAsset token;
1947+ };
1948+
1949+ // Appends a single issuance to the first input that doesn't have one, and includes
1950+ // a single output per asset type in shuffled positions.
1951+ void issueasset_base (CMutableTransaction& mtx, RawIssuanceDetails& issuance_details, const CAmount asset_amount, const CAmount token_amount, const std::string& asset_address_str, const std::string& token_address_str, const bool blind_issuance, const uint256& contract_hash)
1952+ {
1953+
1954+ CTxDestination asset_address (DecodeDestination (asset_address_str));
1955+ CTxDestination token_address (DecodeDestination (token_address_str));
1956+ CScript asset_destination = GetScriptForDestination (asset_address);
1957+ CScript token_destination = GetScriptForDestination (token_address);
1958+
1959+ // Find an input with no issuance field
1960+ size_t issuance_input_index = 0 ;
1961+ for (; issuance_input_index < mtx.vin .size (); issuance_input_index++) {
1962+ if (mtx.vin [issuance_input_index].assetIssuance .IsNull ()) {
1963+ break ;
1964+ }
1965+ }
1966+ // Can't add another one, exit
1967+ if (issuance_input_index == mtx.vin .size ()) {
1968+ issuance_details.input_index = -1 ;
1969+ return ;
1970+ }
1971+
1972+ uint256 entropy;
1973+ CAsset asset;
1974+ CAsset token;
1975+ GenerateAssetEntropy (entropy, mtx.vin [issuance_input_index].prevout , contract_hash);
1976+ CalculateAsset (asset, entropy);
1977+ CalculateReissuanceToken (token, entropy, blind_issuance);
1978+
1979+ issuance_details.input_index = issuance_input_index;
1980+ issuance_details.entropy = entropy;
1981+ issuance_details.asset = asset;
1982+ issuance_details.token = token;
1983+
1984+ mtx.vin [issuance_input_index].assetIssuance .assetEntropy = contract_hash;
1985+
1986+ // Place assets into randomly placed output slots, just insert in place
1987+ // -1 due to fee output being at the end no matter what.
1988+ int asset_place = GetRandInt (mtx.vout .size ()-1 );
1989+ int token_place = GetRandInt (mtx.vout .size ()); // Don't bias insertion
1990+
1991+ CTxOut asset_out (asset, asset_amount, asset_destination);
1992+ // If blinded address, insert the pubkey into the nonce field for later substitution by blinding
1993+ if (IsBlindDestination (asset_address)) {
1994+ CPubKey asset_blind = GetDestinationBlindingKey (asset_address);
1995+ asset_out.nNonce .vchCommitment = std::vector<unsigned char >(asset_blind.begin (), asset_blind.end ());
1996+ }
1997+ // Explicit 0 is represented by a null value, don't set to non-null in that case
1998+ if (blind_issuance || asset_amount != 0 ) {
1999+ mtx.vin [issuance_input_index].assetIssuance .nAmount = asset_amount;
2000+ }
2001+ // Don't make zero value output(impossible by consensus)
2002+ if (asset_amount > 0 ) {
2003+ mtx.vout .insert (mtx.vout .begin ()+asset_place, asset_out);
2004+ }
2005+
2006+ CTxOut token_out (token, token_amount, token_destination);
2007+ // If blinded address, insert the pubkey into the nonce field for later substitution by blinding
2008+ if (IsBlindDestination (token_address)) {
2009+ CPubKey token_blind = GetDestinationBlindingKey (token_address);
2010+ token_out.nNonce .vchCommitment = std::vector<unsigned char >(token_blind.begin (), token_blind.end ());
2011+ }
2012+ // Explicit 0 is represented by a null value, don't set to non-null in that case
2013+ if (blind_issuance || token_amount != 0 ) {
2014+ mtx.vin [issuance_input_index].assetIssuance .nInflationKeys = token_amount;
2015+ }
2016+ // Don't make zero value output(impossible by consensus)
2017+ if (token_amount > 0 ) {
2018+ mtx.vout .insert (mtx.vout .begin ()+token_place, token_out);
2019+ }
2020+ }
2021+
2022+ // Appends a single reissuance to the specified input if none exists,
2023+ // and the corresponding output in a shuffled position. Errors otherwise.
2024+ void reissueasset_base (CMutableTransaction& mtx, int & issuance_input_index, const CAmount asset_amount, const std::string& asset_address_str, const uint256& asset_blinder, const uint256& entropy)
2025+ {
2026+
2027+ CTxDestination asset_address (DecodeDestination (asset_address_str));
2028+ CScript asset_destination = GetScriptForDestination (asset_address);
2029+
2030+ // Check if issuance already exists, error if already exists
2031+ if ((size_t )issuance_input_index >= mtx.vin .size () || !mtx.vin [issuance_input_index].assetIssuance .IsNull ()) {
2032+ issuance_input_index = -1 ;
2033+ return ;
2034+ }
2035+
2036+ CAsset asset;
2037+ CalculateAsset (asset, entropy);
2038+
2039+ mtx.vin [issuance_input_index].assetIssuance .assetEntropy = entropy;
2040+ mtx.vin [issuance_input_index].assetIssuance .assetBlindingNonce = asset_blinder;
2041+ mtx.vin [issuance_input_index].assetIssuance .nAmount = asset_amount;
2042+
2043+ // Place assets into randomly placed output slots, before change output, inserted in place
2044+ assert (mtx.vout .size () >= 1 );
2045+ int asset_place = GetRandInt (mtx.vout .size ()-1 );
2046+
2047+ CTxOut asset_out (asset, asset_amount, asset_destination);
2048+ // If blinded address, insert the pubkey into the nonce field for later substitution by blinding
2049+ if (IsBlindDestination (asset_address)) {
2050+ CPubKey asset_blind = GetDestinationBlindingKey (asset_address);
2051+ asset_out.nNonce .vchCommitment = std::vector<unsigned char >(asset_blind.begin (), asset_blind.end ());
2052+ }
2053+ assert (asset_amount > 0 );
2054+ mtx.vout .insert (mtx.vout .begin ()+asset_place, asset_out);
2055+ mtx.vin [issuance_input_index].assetIssuance .nAmount = asset_amount;
2056+ }
2057+
2058+ UniValue rawissueasset (const JSONRPCRequest& request)
2059+ {
2060+ if (request.fHelp || request.params .size () != 2 )
2061+ throw std::runtime_error (
2062+ " rawissueasset transaction [{\" asset_amount\" :x.xxx, \" asset_address\" :\" address\" , \" token_amount\" :x.xxx, \" token_address\" :\" address\" , \" blind\" :bool, ( \" contract_hash\" :\" hash\" )}, ...]\n "
2063+ " \n Create an asset by attaching issuances to transaction inputs. Returns the transaction hex. There must be as many inputs as issuances requested. The final transaction hex is the final version of the transaction appended to the last object in the array.\n "
2064+ " \n Arguments:\n "
2065+ " 1. \" transaction\" (string, required) Transaction in hex in which to include an issuance input.\n "
2066+ " 2. \" issuances\" (list, required) List of issuances to create. Each issuance must have one non-zero amount. \n "
2067+ " [\n "
2068+ " {\n "
2069+ " \" asset_amount\" :x.xxx (numeric or string, optional) Amount of asset to generate, if any.\n "
2070+ " \" asset_address\" :addr (string, optional) Destination address of generated asset. Required if `asset_amount` given.\n "
2071+ " \" token_amount\" :x.xxx (numeric or string, optional) Amount of reissuance token to generate, if any.\n "
2072+ " \" token_address\" :addr (string, optional) Destination address of generated reissuance tokens. Required if `token_amount` given.\n "
2073+ " \" blind\" :bool (bool, optional, default=true) Whether to mark the issuance input for blinding or not. Only affects issuances with re-issuance tokens."
2074+ " \" contract_hash\" :str (string, optional, default=00..00) Contract hash that is put into issuance definition. Must be 32 bytes worth in hex string form. This will affect the asset id."
2075+ " }\n "
2076+ " ...\n "
2077+ " ]\n "
2078+ " \n Result:\n "
2079+ " [ (json array) Results of issuances, in the order of `issuances` argument\n "
2080+ " { (json object)\n "
2081+ " \" hex\" :<hex>, (string) The transaction with issuances appended. Only appended to final index in returned array.\n "
2082+ " \" vin\" :\" n\" , (numeric) The input position of the issuance in the transaction.\n "
2083+ " \" entropy\" :\" <entropy>\" (string) Entropy of the asset type.\n "
2084+ " \" asset\" :\" <asset>\" , (string) Asset type for issuance if known.\n "
2085+ " \" token\" :\" <token>\" , (string) Token type for issuance.\n "
2086+ " },\n "
2087+ " ...\n "
2088+ " ]"
2089+ );
2090+
2091+ CMutableTransaction mtx;
2092+
2093+ if (!DecodeHexTx (mtx, request.params [0 ].get_str ()))
2094+ throw JSONRPCError (RPC_DESERIALIZATION_ERROR, " TX decode failed" );
2095+
2096+ UniValue issuances = request.params [1 ].get_array ();
2097+
2098+ std::string asset_address_str = " " ;
2099+ std::string token_address_str = " " ;
2100+
2101+ UniValue ret (UniValue::VARR);
2102+
2103+ // Count issuances, only append hex to final one
2104+ unsigned int issuances_til_now = 0 ;
2105+
2106+ for (unsigned int idx = 0 ; idx < issuances.size (); idx++) {
2107+ const UniValue& issuance = issuances[idx];
2108+ const UniValue& issuance_o = issuance.get_obj ();
2109+
2110+ CAmount asset_amount = 0 ;
2111+ const UniValue& asset_amount_uni = issuance_o[" asset_amount" ];
2112+ if (asset_amount_uni.isNum ()) {
2113+ asset_amount = AmountFromValue (asset_amount_uni);
2114+ if (asset_amount <= 0 ) {
2115+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, asset_amount must be positive" );
2116+ }
2117+ const UniValue& asset_address_uni = issuance_o[" asset_address" ];
2118+ if (!asset_address_uni.isStr ()) {
2119+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, missing corresponding asset_address" );
2120+ }
2121+ asset_address_str = asset_address_uni.get_str ();
2122+ }
2123+
2124+ CAmount token_amount = 0 ;
2125+ const UniValue& token_amount_uni = issuance_o[" token_amount" ];
2126+ if (token_amount_uni.isNum ()) {
2127+ token_amount = AmountFromValue (token_amount_uni);
2128+ if (token_amount <= 0 ) {
2129+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, token_amount must be positive" );
2130+ }
2131+ const UniValue& token_address_uni = issuance_o[" token_address" ];
2132+ if (!token_address_uni.isStr ()) {
2133+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, missing corresponding token_address" );
2134+ }
2135+ token_address_str = token_address_uni.get_str ();
2136+ }
2137+ if (asset_amount == 0 && token_amount == 0 ) {
2138+ throw JSONRPCError (RPC_TYPE_ERROR, " Issuance must have one non-zero component" );
2139+ }
2140+
2141+ // If we have issuances, check if reissuance tokens will be generated via blinding path
2142+ const UniValue blind_uni = issuance_o[" blind" ];
2143+ const bool blind_issuance = !blind_uni.isBool () || blind_uni.get_bool ();
2144+
2145+ // Check for optional contract to hash into definition
2146+ uint256 contract_hash;
2147+ if (!issuance_o[" contract_hash" ].isNull ()) {
2148+ contract_hash = ParseHashV (issuance_o[" contract_hash" ], " contract_hash" );
2149+ }
2150+
2151+ RawIssuanceDetails details;
2152+
2153+ issueasset_base (mtx, details, asset_amount, token_amount, asset_address_str, token_address_str, blind_issuance, contract_hash);
2154+ if (details.input_index == -1 ) {
2155+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Failed to find enough blank inputs for listed issuances." );
2156+ }
2157+
2158+ issuances_til_now++;
2159+
2160+ UniValue obj (UniValue::VOBJ);
2161+ if (issuances_til_now == issuances.size ()) {
2162+ obj.pushKV (" hex" , EncodeHexTx (mtx, RPCSerializationFlags ()));
2163+ }
2164+ obj.pushKV (" vin" , details.input_index );
2165+ obj.pushKV (" entropy" , details.entropy .GetHex ());
2166+ obj.pushKV (" asset" , details.asset .GetHex ());
2167+ obj.pushKV (" token" , details.token .GetHex ());
2168+
2169+ ret.push_back (obj);
2170+ }
2171+
2172+ return ret;
2173+ }
2174+
2175+ UniValue rawreissueasset (const JSONRPCRequest& request)
2176+ {
2177+ if (request.fHelp || request.params .size () != 2 )
2178+ throw std::runtime_error (
2179+ " rawreissueasset transaction {\" vin\" :\" n\" , \" asset_amount\" :x.xxx, \" asset_address\" :\" address\" , \" asset_blinder\" :<hex>, \" entropy\" :<hex>, ( \" contract_hash\" :<hex> )}\n "
2180+ " \n Re-issue an asset by attaching pseudo-inputs to transaction inputs, revealing the underlying reissuance token of the input. Returns the transaction hex.\n "
2181+ " \n Arguments:\n "
2182+ " 1. \" transaction\" (string, required) Transaction in hex in which to include an issuance input.\n "
2183+ " 2. \" reissuances\" (list, required) List of re-issuances to create. Each issuance must have one non-zero amount.\n "
2184+ " [\n "
2185+ " {\n "
2186+ " \" input_index\" :\" n\" , (numeric, required) The input position of the reissuance in the transaction.\n "
2187+ " \" asset_amount\" :x.xxx, (numeric or string, required) Amount of asset to generate, if any.\n "
2188+ " \" asset_address\" :addr, (string, required) Destination address of generated asset. Required if `asset_amount` given.\n "
2189+ " \" asset_blinder\" :<hex>, (string, required) The blinding factor of the reissuance token output being spent.\n "
2190+ " \" entropy\" :<hex>, (string, required) The `entropy` returned during initial issuance for the asset being reissued."
2191+ " }\n "
2192+ " \n Result:\n "
2193+ " { (json object)\n "
2194+ " \" hex\" :<hex>, (string) The transaction with reissuances appended.\n "
2195+ " }\n "
2196+ );
2197+
2198+ CMutableTransaction mtx;
2199+
2200+ if (!DecodeHexTx (mtx, request.params [0 ].get_str ()))
2201+ throw JSONRPCError (RPC_DESERIALIZATION_ERROR, " TX decode failed" );
2202+
2203+ if (mtx.vout .empty ()) {
2204+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Transaction must have at least one output." );
2205+ }
2206+
2207+ UniValue issuances = request.params [1 ].get_array ();
2208+
2209+ unsigned int num_issuances = 0 ;
2210+
2211+ for (unsigned int idx = 0 ; idx < issuances.size (); idx++) {
2212+ const UniValue& issuance = issuances[idx];
2213+ const UniValue& issuance_o = issuance.get_obj ();
2214+
2215+ CAmount asset_amount = 0 ;
2216+ const UniValue& asset_amount_uni = issuance_o[" asset_amount" ];
2217+ if (asset_amount_uni.isNum ()) {
2218+ asset_amount = AmountFromValue (asset_amount_uni);
2219+ if (asset_amount <= 0 ) {
2220+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, asset_amount must be positive" );
2221+ }
2222+ } else {
2223+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Asset amount must be given for each reissuance." );
2224+ }
2225+
2226+ const UniValue& asset_address_uni = issuance_o[" asset_address" ];
2227+ if (!asset_address_uni.isStr ()) {
2228+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Reissuance missing asset_address" );
2229+ }
2230+ std::string asset_address_str = asset_address_uni.get_str ();
2231+
2232+ int input_index = -1 ;
2233+ const UniValue& input_index_o = issuance_o[" input_index" ];
2234+ if (input_index_o.isNum ()) {
2235+ input_index = input_index_o.get_int ();
2236+ if (input_index < 0 ) {
2237+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Input index must be non-negative." );
2238+ }
2239+ } else {
2240+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Input indexes for all reissuances are required." );
2241+ }
2242+
2243+ uint256 asset_blinder = ParseHashV (issuance_o[" asset_blinder" ], " asset_blinder" );
2244+
2245+ uint256 entropy = ParseHashV (issuance_o[" entropy" ], " entropy" );
2246+
2247+ reissueasset_base (mtx, input_index, asset_amount, asset_address_str, asset_blinder, entropy);
2248+ if (input_index == -1 ) {
2249+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Selected transaction input already has issuance data." );
2250+ }
2251+
2252+ num_issuances++;
2253+ }
2254+
2255+ if (num_issuances != issuances.size ()) {
2256+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Failed to find enough blank inputs for listed issuances." );
2257+ }
2258+
2259+ UniValue ret (UniValue::VOBJ);
2260+ ret.pushKV (" hex" , EncodeHexTx (mtx, RPCSerializationFlags ()));
2261+ return ret;
2262+ }
2263+
19402264
19412265// clang-format off
19422266static const CRPCCommand commands[] =
@@ -1960,6 +2284,8 @@ static const CRPCCommand commands[] =
19602284
19612285 { " blockchain" , " gettxoutproof" , &gettxoutproof, {" txids" , " blockhash" } },
19622286 { " blockchain" , " verifytxoutproof" , &verifytxoutproof, {" proof" } },
2287+ { " rawtransactions" , " rawissueasset" , &rawissueasset, {" transaction" , " issuances" }},
2288+ { " rawtransactions" , " rawreissueasset" , &rawreissueasset, {" transaction" , " reissuances" }},
19632289};
19642290// clang-format on
19652291
0 commit comments