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
        }
Exemplo n.º 2
0
        /// <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);
        }