static void Main(string[] _) { var payloadBytes = new byte[2 * 1024 * 1024]; var nullDataTemplate = new TxNullDataTemplate(4194304); var txOut = new TxOut() { Value = Money.Zero, ScriptPubKey = nullDataTemplate.GenerateScriptPubKey(payloadBytes) }; var tx = Transaction.Create(Network.TestNet); tx.Outputs.Add(txOut); tx.ToHex(); // this line fails }
/// <summary> /// Creates cold staking setup <see cref="Transaction"/>. /// </summary> /// <remarks> /// The <paramref name="coldWalletAddress"/> and <paramref name="hotWalletAddress"/> would be expected to be /// from different wallets and typically also different physical machines under normal circumstances. The following /// rules are enforced by this method and would lead to a <see cref="WalletException"/> otherwise: /// <list type="bullet"> /// <item><description>The cold and hot wallet addresses are expected to belong to different wallets.</description></item> /// <item><description>Either the cold or hot wallet address must belong to a cold staking account in the wallet identified /// by <paramref name="walletName"/></description></item> /// <item><description>The account specified in <paramref name="walletAccount"/> can't be a cold staking account.</description></item> /// </list> /// </remarks> /// <param name="walletTransactionHandler">The wallet transaction handler. Contains the <see cref="WalletTransactionHandler.BuildTransaction"/> method.</param> /// <param name="coldWalletAddress">The cold wallet address generated by <see cref="GetColdStakingAddress"/>.</param> /// <param name="hotWalletAddress">The hot wallet address generated by <see cref="GetColdStakingAddress"/>.</param> /// <param name="walletName">The name of the wallet.</param> /// <param name="walletAccount">The wallet account.</param> /// <param name="walletPassword">The wallet password.</param> /// <param name="amount">The amount to cold stake.</param> /// <param name="feeAmount">The fee to pay for the cold staking setup transaction.</param> /// <param name="useSegwitChangeAddress">Use a segwit style change address.</param> /// <param name="payToScript">Indicate script staking (P2SH or P2WSH outputs).</param> /// <returns>The <see cref="Transaction"/> for setting up cold staking.</returns> /// <exception cref="WalletException">Thrown if any of the rules listed in the remarks section of this method are broken.</exception> internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler walletTransactionHandler, string coldWalletAddress, string hotWalletAddress, string walletName, string walletAccount, string walletPassword, Money amount, Money feeAmount, bool useSegwitChangeAddress = false, bool payToScript = false) { Guard.NotNull(walletTransactionHandler, nameof(walletTransactionHandler)); Guard.NotEmpty(coldWalletAddress, nameof(coldWalletAddress)); Guard.NotEmpty(hotWalletAddress, nameof(hotWalletAddress)); Guard.NotEmpty(walletName, nameof(walletName)); Guard.NotEmpty(walletAccount, nameof(walletAccount)); Guard.NotNull(amount, nameof(amount)); Guard.NotNull(feeAmount, nameof(feeAmount)); Wallet.Types.Wallet wallet = this.GetWalletByName(walletName); // Get/create the cold staking accounts. HdAccount coldAccount = this.GetOrCreateColdStakingAccount(walletName, true, walletPassword); HdAccount hotAccount = this.GetOrCreateColdStakingAccount(walletName, false, walletPassword); HdAddress coldAddress = coldAccount?.ExternalAddresses.FirstOrDefault(s => s.Address == coldWalletAddress || s.Bech32Address == coldWalletAddress); HdAddress hotAddress = hotAccount?.ExternalAddresses.FirstOrDefault(s => s.Address == hotWalletAddress || s.Bech32Address == hotWalletAddress); bool thisIsColdWallet = coldAddress != null; bool thisIsHotWallet = hotAddress != null; this.logger.LogDebug("Local wallet '{0}' does{1} contain cold wallet address '{2}' and does{3} contain hot wallet address '{4}'.", walletName, thisIsColdWallet ? "" : " NOT", coldWalletAddress, thisIsHotWallet ? "" : " NOT", hotWalletAddress); if (thisIsColdWallet && thisIsHotWallet) { this.logger.LogTrace("(-)[COLDSTAKE_BOTH_HOT_AND_COLD]"); throw new WalletException("You can't use this wallet as both hot wallet and cold wallet."); } if (!thisIsColdWallet && !thisIsHotWallet) { this.logger.LogTrace("(-)[COLDSTAKE_ADDRESSES_NOT_IN_ACCOUNTS]"); throw new WalletException("The hot and cold wallet addresses could not be found in the corresponding accounts."); } Script destination = null; KeyId hotPubKeyHash = null; KeyId coldPubKeyHash = null; // Check if this is a segwit address if (coldAddress?.Bech32Address == coldWalletAddress || hotAddress?.Bech32Address == hotWalletAddress) { hotPubKeyHash = new BitcoinWitPubKeyAddress(hotWalletAddress, wallet.Network).Hash.AsKeyId(); coldPubKeyHash = new BitcoinWitPubKeyAddress(coldWalletAddress, wallet.Network).Hash.AsKeyId(); destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash); if (payToScript) { HdAddress address = coldAddress ?? hotAddress; address.RedeemScript = destination; destination = destination.WitHash.ScriptPubKey; } } else { hotPubKeyHash = new BitcoinPubKeyAddress(hotWalletAddress, wallet.Network).Hash; coldPubKeyHash = new BitcoinPubKeyAddress(coldWalletAddress, wallet.Network).Hash; destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash); if (payToScript) { HdAddress address = coldAddress ?? hotAddress; address.RedeemScript = destination; destination = destination.Hash.ScriptPubKey; } } // Only normal accounts should be allowed. if (this.GetAccounts(walletName).All(a => a.Name != walletAccount)) { this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_NOT_FOUND]"); throw new WalletException($"Can't find wallet account '{walletAccount}'."); } var context = new TransactionBuildContext(wallet.Network) { AccountReference = new WalletAccountReference(walletName, walletAccount), TransactionFee = feeAmount, MinConfirmations = 0, Shuffle = false, UseSegwitChangeAddress = useSegwitChangeAddress, WalletPassword = walletPassword, Recipients = new List <Recipient>() { new Recipient { Amount = amount, ScriptPubKey = destination } } }; if (payToScript) { // In the case of P2SH and P2WSH, to avoid the possibility of lose track of funds // we add an opreturn with the hot and cold key hashes to the setup transaction // this will allow a user to recreate the redeem script of the output in case they lose // access to one of the keys. // The special marker will help a wallet that is tracking cold staking accounts to monitor // the hot and cold keys, if a special marker is found then the keys are in the opreturn are checked // against the current wallet, if found and validated the wallet will track that ScriptPubKey var opreturnKeys = new List <byte>(); opreturnKeys.AddRange(hotPubKeyHash.ToBytes()); opreturnKeys.AddRange(coldPubKeyHash.ToBytes()); context.OpReturnRawData = opreturnKeys.ToArray(); TxNullDataTemplate template = this.network.StandardScriptsRegistry.GetScriptTemplates.OfType <TxNullDataTemplate>().First(); if (template.MinRequiredSatoshiFee > 0) { context.OpReturnAmount = Money.Satoshis(template.MinRequiredSatoshiFee); // mandatory fee must be paid. } // The P2SH and P2WSH hide the cold stake keys in the script hash so the wallet cannot track // the ouputs based on the derived keys when the trx is subbmited to the network. // So we add the output script manually. } // Register the cold staking builder extension with the transaction builder. context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false)); // Build the transaction. Transaction transaction = walletTransactionHandler.BuildTransaction(context); this.logger.LogTrace("(-)"); return(transaction); }