/// <summary>
        /// Adds a spendable cold staking transaction to a normal account, as oppose to dedicated special account.
        /// </summary>
        /// <param name="wallet">Wallet to add the transaction to.</param>
        /// <returns>The spendable transaction that was added to the wallet.</returns>
        private Transaction AddSpendableColdstakingTransactionToNormalWallet(Wallet.Wallet wallet, bool script = false)
        {
            // This will always be added to the secondary address.
            HdAddress address = wallet.GetAllAddresses().ToArray()[1];

            var transaction = this.Network.CreateTransaction();

            // Use the normal wallet address here.
            TxDestination hotPubKey  = BitcoinAddress.Create(address.Address, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);
            TxDestination coldPubKey = BitcoinAddress.Create(coldWalletAddress2, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);

            var scriptPubKey = new Script(OpcodeType.OP_DUP, OpcodeType.OP_HASH160, OpcodeType.OP_ROT, OpcodeType.OP_IF,
                                          OpcodeType.OP_CHECKCOLDSTAKEVERIFY, Op.GetPushOp(hotPubKey.ToBytes()), OpcodeType.OP_ELSE, Op.GetPushOp(coldPubKey.ToBytes()),
                                          OpcodeType.OP_ENDIF, OpcodeType.OP_EQUALVERIFY, OpcodeType.OP_CHECKSIG);

            transaction.Outputs.Add(new TxOut(Money.Coins(202), script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey));

            address.Transactions.Add(new TransactionData()
            {
                Hex             = transaction.ToHex(this.Network),
                Amount          = transaction.Outputs[0].Value,
                Id              = transaction.GetHash(),
                BlockHeight     = 0,
                Index           = 0,
                IsCoinBase      = false,
                IsCoinStake     = false,
                IsColdCoinStake = true,
                IsPropagated    = true,
                BlockHash       = this.Network.GenesisHash,
                ScriptPubKey    = script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey,
            });

            return(transaction);
        }
        /// <summary>
        /// Adds a spendable cold staking transaction to a wallet.
        /// </summary>
        /// <param name="wallet">Wallet to add the transaction to.</param>
        /// <returns>The spendable transaction that was added to the wallet.</returns>
        private Transaction AddSpendableColdstakingTransactionToWallet(Wallet.Wallet wallet)
        {
            // Get first unused cold staking address.
            this.coldStakingManager.GetOrCreateColdStakingAccount(wallet.Name, true, walletPassword);
            HdAddress address = this.coldStakingManager.GetFirstUnusedColdStakingAddress(wallet.Name, true);

            TxDestination hotPubKey  = BitcoinAddress.Create(hotWalletAddress1, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);
            TxDestination coldPubKey = BitcoinAddress.Create(coldWalletAddress2, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);

            var scriptPubKey = new Script(OpcodeType.OP_DUP, OpcodeType.OP_HASH160, OpcodeType.OP_ROT, OpcodeType.OP_IF,
                                          OpcodeType.OP_CHECKCOLDSTAKEVERIFY, Op.GetPushOp(hotPubKey.ToBytes()), OpcodeType.OP_ELSE, Op.GetPushOp(coldPubKey.ToBytes()),
                                          OpcodeType.OP_ENDIF, OpcodeType.OP_EQUALVERIFY, OpcodeType.OP_CHECKSIG);

            var transaction = this.Network.CreateTransaction();

            transaction.Outputs.Add(new TxOut(Money.Coins(101), scriptPubKey));

            address.Transactions.Add(new TransactionData()
            {
                Hex          = transaction.ToHex(this.Network),
                Amount       = transaction.Outputs[0].Value,
                Id           = transaction.GetHash(),
                BlockHeight  = 0,
                Index        = 0,
                IsCoinBase   = false,
                IsCoinStake  = false,
                IsPropagated = true,
                BlockHash    = this.Network.GenesisHash,
                ScriptPubKey = scriptPubKey
            });

            return(transaction);
        }
Exemple #3
0
        internal IEnumerable <Script> GetDestinations(Script redeemScript)
        {
            ScriptTemplate scriptTemplate = this.network.StandardScriptsRegistry.GetTemplateFromScriptPubKey(redeemScript);

            if (scriptTemplate != null)
            {
                // We need scripts suitable for matching to HDAddress.ScriptPubKey.
                switch (scriptTemplate.Type)
                {
                case TxOutType.TX_PUBKEYHASH:
                    yield return(redeemScript);

                    break;

                case TxOutType.TX_PUBKEY:
                    yield return(PayToPubkeyTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript).Hash.ScriptPubKey);

                    break;

                case TxOutType.TX_SCRIPTHASH:
                    yield return(PayToScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript).ScriptPubKey);

                    break;

                case TxOutType.TX_SEGWIT:
                    TxDestination txDestination = PayToWitTemplate.Instance.ExtractScriptPubKeyParameters(this.network, redeemScript);
                    if (txDestination != null)
                    {
                        yield return(new KeyId(txDestination.ToBytes()).ScriptPubKey);
                    }
                    break;

                default:
                    if (this.scriptAddressReader is ScriptDestinationReader scriptDestinationReader)
                    {
                        foreach (TxDestination destination in scriptDestinationReader.GetDestinationFromScriptPubKey(this.network, redeemScript))
                        {
                            yield return(destination.ScriptPubKey);
                        }
                    }
                    else
                    {
                        string        address     = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, redeemScript);
                        TxDestination destination = ScriptDestinationReader.GetDestinationForAddress(address, this.network);
                        if (destination != null)
                        {
                            yield return(destination.ScriptPubKey);
                        }
                    }

                    break;
                }
            }
        }
        public virtual IEnumerable <TxDestination> GetDestinationFromScriptPubKey(Network network, Script redeemScript)
        {
            string base58 = this.scriptAddressReader.GetAddressFromScriptPubKey(network, redeemScript);

            if (base58 != null)
            {
                TxDestination destination = ScriptDestinationReader.GetDestinationForAddress(base58, network);

                if (destination != null)
                {
                    yield return(destination);
                }
            }
        }
Exemple #5
0
        /// <inheritdoc/>
        /// <remarks>The public key hashes in the cold staking script are individually mapped to addresses.</remarks>
        public override HdAddress this[Script script]
        {
            get
            {
                // Use our special TryGetValue that also works with cold staking scripts to get the value.
                if (this.TryGetValue(script, out HdAddress address))
                {
                    return(address);
                }

                // This method should throw an exception if the value is not found.
                throw new Exception($"Could not resolve address from script '{script}' using '{nameof(ScriptToAddressLookup)}' collection.");
            }

            set
            {
                // First identify which of the public key hashes matches the address being recorded
                // and only record the address against that key.
                if (ColdStakingScriptTemplate.Instance.ExtractScriptPubKeyParameters(script, out KeyId hotPubKey, out KeyId coldPubKey))
                {
                    TxDestination destination = value.ScriptPubKey.GetDestination(this.network);

                    if (destination == hotPubKey)
                    {
                        this.keysLookup[hotPubKey.ScriptPubKey] = value;
                    }
                    else if (destination == coldPubKey)
                    {
                        this.keysLookup[coldPubKey.ScriptPubKey] = value;
                    }
                    else
                    {
                        // This method should throw an exception if there is no relationship between the script and the address.
                        throw new Exception("The address can't be matched to the script.");
                    }
                }
                else
                {
                    base[script] = value;
Exemple #6
0
        public void base58_keys_valid_gen()
        {
            var     tests   = TestCase.read_json("data/base58_keys_valid.json");
            Network network = null;

            foreach (var test in tests)
            {
                string strTest = test.ToString();
                if (test.Count < 3)                // Allow for extra stuff (useful for comments)
                {
                    Assert.False(true, "Bad test: " + strTest);
                    continue;
                }
                string  exp_base58string = (string)test[0];
                byte[]  exp_payload      = TestUtils.ParseHex((string)test[1]);
                dynamic metadata         = test.GetDynamic(2);
                bool    isPrivkey        = (bool)metadata.isPrivkey;
                bool    isTestnet        = (bool)metadata.isTestnet;

                if (isTestnet)
                {
                    network = Network.TestNet;
                }
                else
                {
                    network = Network.Main;
                }
                if (isPrivkey)
                {
                    bool          isCompressed = metadata.isCompressed;
                    Key           key          = new Key(exp_payload, fCompressedIn: isCompressed);
                    BitcoinSecret secret       = network.CreateBitcoinSecret(key);
                    Assert.True(secret.ToString() == exp_base58string, "result mismatch: " + strTest);
                }
                else
                {
                    string        exp_addrType = (string)metadata.addrType;
                    TxDestination dest;
                    if (exp_addrType == "pubkey")
                    {
                        dest = new KeyId(new uint160(exp_payload));
                    }
                    else if (exp_addrType == "script")
                    {
                        dest = new ScriptId(new uint160(exp_payload));
                    }
                    else if (exp_addrType == "none")
                    {
                        dest = new TxDestination(0);
                    }
                    else
                    {
                        Assert.True(false, "Bad addrtype: " + strTest);
                        continue;
                    }
                    try
                    {
                        BitcoinAddress addrOut = network.CreateBitcoinAddress(dest);
                        Assert.True(addrOut.ToString() == exp_base58string, "mismatch: " + strTest);
                    }
                    catch (ArgumentException)
                    {
                        Assert.True(dest.GetType() == typeof(TxDestination));
                    }
                }
            }

            // Visiting a CNoDestination must fail
            TxDestination nodest = new TxDestination();

            Assert.Throws <ArgumentException>(() => network.CreateBitcoinAddress(nodest));
        }
        public void ShouldThrowNetworkArgumentException()
        {
            TxDestination tx = new TxDestination();

            Assert.Throws <ArgumentNullException>(() => BitcoinAddress.Create(tx, null));
        }
        public void ShouldThrowIdArgumentException()
        {
            TxDestination tx = null;

            Assert.Throws <ArgumentNullException>(() => BitcoinAddress.Create(tx, Network.Main));
        }
Exemple #9
0
        public static IEnumerable <OrderedBalanceChange> ExtractScriptBalances(uint256 txId, Transaction transaction, uint256 blockId, BlockHeader blockHeader, int height)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }
            if (txId == null)
            {
                txId = transaction.GetHash();
            }

            if (blockId == null && blockHeader != null)
            {
                blockId = blockHeader.GetHash();
            }

            Dictionary <Script, OrderedBalanceChange> changeByScriptPubKey = new Dictionary <Script, OrderedBalanceChange>();
            uint i = 0;

            foreach (var input in transaction.Inputs)
            {
                if (transaction.IsCoinBase)
                {
                    i++;
                    break;
                }
                TxDestination signer = null;
                if (input.ScriptSig.Length != 0)
                {
                    signer = input.ScriptSig.GetSigner();
                }
                else
                {
                    signer = GetSigner(input.WitScript);
                }
                if (signer != null)
                {
                    OrderedBalanceChange entry = null;
                    if (!changeByScriptPubKey.TryGetValue(signer.ScriptPubKey, out entry))
                    {
                        entry = new OrderedBalanceChange(txId, signer.ScriptPubKey, blockId, blockHeader, height);
                        changeByScriptPubKey.Add(signer.ScriptPubKey, entry);
                    }
                    entry.SpentOutpoints.Add(input.PrevOut);
                    entry.SpentIndices.Add(i);
                }
                i++;
            }

            i = 0;
            bool hasOpReturn = false;

            foreach (var output in transaction.Outputs)
            {
                var bytes = output.ScriptPubKey.ToBytes(true);
                if (bytes.Length != 0 && bytes[0] == (byte)OpcodeType.OP_RETURN)
                {
                    hasOpReturn = true;
                    i++;
                    continue;
                }

                OrderedBalanceChange entry = null;
                if (!changeByScriptPubKey.TryGetValue(output.ScriptPubKey, out entry))
                {
                    entry = new OrderedBalanceChange(txId, output.ScriptPubKey, blockId, blockHeader, height);
                    changeByScriptPubKey.Add(output.ScriptPubKey, entry);
                }
                entry.ReceivedCoins.Add(new Coin()
                {
                    Outpoint = new OutPoint(txId, i),
                    TxOut    = output
                });
                i++;
            }

            foreach (var entity in changeByScriptPubKey)
            {
                entity.Value.HasOpReturn = hasOpReturn;
                entity.Value.IsCoinbase  = transaction.IsCoinBase;
            }

            return(changeByScriptPubKey.Values);
        }
 public void ShouldThrowNetworkArgumentException()
 {
     TxDestination tx = new TxDestination();
     Assert.Throws<ArgumentNullException>(() => BitcoinAddress.Create(tx, null));
 }
        /// <summary>
        /// Creates a transaction to transfers funds from an old federation to a new federation.
        /// </summary>
        /// <param name="isSideChain">Indicates whether the <paramref name="network"/> is the sidechain.</param>
        /// <param name="network">The network that we are creating the recovery transaction for.</param>
        /// <param name="counterChainNetwork">The counterchain network.</param>
        /// <param name="dataDirPath">The root folder containing the old federation.</param>
        /// <param name="multisigParams">Parameters related to the new redeem script.</param>
        /// <param name="password">The password required to generate transactions using the federation wallet.</param>
        /// <param name="txTime">Any deposits beyond this UTC date will be ignored when selecting coin inputs.</param>
        /// <param name="newFormat">Set to <c>true</c> to send the funds to a P2SH that is based on the new redeem script format.</param>
        /// <param name="burn">Set to <c>true</c> to sent the funds to an OP_RETURN containing the target multisig address.</param>
        /// <returns>A funds recovery transaction that moves funds to the new redeem script.</returns>
        public FundsRecoveryTransactionModel CreateFundsRecoveryTransaction(bool isSideChain, Network network, Network counterChainNetwork, string dataDirPath,
                                                                            PayToMultiSigTemplateParameters multisigParams, string password, DateTime txTime, bool newFormat = false, bool burn = false)
        {
            Script redeemScript = NewRedeemScript(multisigParams, newFormat);

            var model = new FundsRecoveryTransactionModel()
            {
                Network = network, IsSideChain = isSideChain, RedeemScript = redeemScript
            };

            string theChain     = isSideChain ? "sidechain" : "mainchain";
            var    nodeSettings = new NodeSettings(network, args: new string[] { $"datadir={dataDirPath}", $"-{theChain}" });

            // Get the old redeem script from the wallet file.
            var walletFileStorage            = new FileStorage <FederationWallet>(nodeSettings.DataFolder.WalletPath);
            FederationWallet wallet          = walletFileStorage.LoadByFileName("multisig_wallet.json");
            Script           oldRedeemScript = wallet.MultiSigAddress.RedeemScript;

            PayToMultiSigTemplateParameters oldMultisigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(oldRedeemScript);

            model.oldMultisigAddress = oldRedeemScript.Hash.GetAddress(network);
            model.newMultisigAddress = redeemScript.Hash.GetAddress(network);

            // Create dummy inputs to avoid errors when constructing FederatedPegSettings.
            var extraArgs = new Dictionary <string, string>();

            extraArgs[FederatedPegSettings.FederationIpsParam] = oldMultisigParams.PubKeys.Select(p => "0.0.0.0".ToIPEndPoint(nodeSettings.Network.DefaultPort)).Join(",");
            var privateKey = Key.Parse(wallet.EncryptedSeed, password, network);

            extraArgs[FederatedPegSettings.PublicKeyParam]    = privateKey.PubKey.ToHex(network);
            extraArgs[FederatedPegSettings.RedeemScriptParam] = oldRedeemScript.ToString();
            (new TextFileConfiguration(extraArgs.Select(i => $"{i.Key}={i.Value}").ToArray())).MergeInto(nodeSettings.ConfigReader);

            model.PubKey = privateKey.PubKey;

            var dBreezeSerializer = new DBreezeSerializer(network.Consensus.ConsensusFactory);
            var blockStore        = new BlockRepository(network, nodeSettings.DataFolder, nodeSettings.LoggerFactory, dBreezeSerializer);

            blockStore.Initialize();

            var           chain        = new ChainRepository(nodeSettings.DataFolder, nodeSettings.LoggerFactory, dBreezeSerializer);
            Block         genesisBlock = network.GetGenesis();
            ChainedHeader tip          = chain.LoadAsync(new ChainedHeader(genesisBlock.Header, genesisBlock.GetHash(), 0)).GetAwaiter().GetResult();
            var           chainIndexer = new ChainIndexer(network, tip);

            var nodeLifetime = new NodeLifetime();
            IDateTimeProvider dateTimeProvider = DateTimeProvider.Default;
            var federatedPegSettings           = new FederatedPegSettings(nodeSettings);
            var opReturnDataReader             = new OpReturnDataReader(nodeSettings.LoggerFactory, new CounterChainNetworkWrapper(counterChainNetwork));
            var walletFeePolicy = new WalletFeePolicy(nodeSettings);

            var walletManager = new FederationWalletManager(nodeSettings.LoggerFactory, network, chainIndexer, nodeSettings.DataFolder, walletFeePolicy,
                                                            new AsyncProvider(nodeSettings.LoggerFactory, new Signals(nodeSettings.LoggerFactory, new DefaultSubscriptionErrorHandler(nodeSettings.LoggerFactory)), nodeLifetime), nodeLifetime,
                                                            dateTimeProvider, federatedPegSettings, new WithdrawalExtractor(nodeSettings.LoggerFactory, federatedPegSettings, opReturnDataReader, network), blockStore);

            walletManager.Start();
            walletManager.EnableFederationWallet(password);

            if (!walletManager.IsFederationWalletActive())
            {
                throw new ArgumentException($"Could not activate the federation wallet on {network}.");
            }

            // Retrieves the unspent outputs in deterministic order.
            List <Stratis.Features.FederatedPeg.Wallet.UnspentOutputReference> coinRefs = walletManager.GetSpendableTransactionsInWallet().ToList();

            // Exclude coins (deposits) beyond the transaction (switch-over) time!
            coinRefs = coinRefs.Where(r => r.Transaction.CreationTime < txTime).ToList();
            if (!coinRefs.Any())
            {
                Console.WriteLine($"There are no coins to recover from the federation wallet on {network}.");
            }
            else
            {
                Money fee = federatedPegSettings.GetWithdrawalTransactionFee(coinRefs.Count());

                var builder = new TransactionBuilder(network);
                builder.AddKeys(privateKey);
                builder.AddCoins(coinRefs.Select(c => ScriptCoin.Create(network, c.Transaction.Id, (uint)c.Transaction.Index, c.Transaction.Amount, c.Transaction.ScriptPubKey, oldRedeemScript)));

                Money amount = coinRefs.Sum(r => r.Transaction.Amount) - fee;

                if (!burn)
                {
                    // Split the coins into multiple outputs.
                    const int numberOfSplits = 10;
                    Money     splitAmount    = new Money((long)amount / numberOfSplits);
                    Script    recipient      = redeemScript.PaymentScript;

                    for (int i = 0; i < numberOfSplits; i++)
                    {
                        Money sendAmount = (i != (numberOfSplits - 1)) ? splitAmount : amount - splitAmount * (numberOfSplits - 1);

                        builder.Send(recipient, sendAmount);
                    }
                }
                else
                {
                    // We don't have the STRAX network classes but we need a STRAX address.
                    // These Base58 prefixes will aid in synthesizing the required format.
                    const byte straxMainScriptAddressPrefix    = 140;
                    const byte straxTestScriptAddressPrefix    = 127;
                    const byte straxRegTestScriptAddressPrefix = 127;

                    // We only have the prefixes for the Stratis -> Strax path.
                    Guard.Assert(network.Name.StartsWith("Stratis"));

                    // Determine the required prefix determining on the type of network.
                    byte addressPrefix = straxMainScriptAddressPrefix;
                    if (network.IsRegTest())
                    {
                        addressPrefix = straxRegTestScriptAddressPrefix;
                    }
                    else if (network.IsTest())
                    {
                        addressPrefix = straxTestScriptAddressPrefix;
                    }

                    // Synthesize the multisig address with a STRAX prefix.
                    byte[] temp = network.Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS];
                    network.Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS] = new byte[] { addressPrefix };
                    TxDestination txDestination = redeemScript.PaymentScript.GetDestination(network);
                    model.newMultisigAddress = txDestination.GetAddress(network);
                    network.Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS] = temp;

                    // Add it to the OP_RETURN.
                    byte[] bytes          = Encoding.UTF8.GetBytes(model.newMultisigAddress.ToString());
                    Script opReturnScript = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes);
                    builder.Send(opReturnScript, amount);
                }

                builder.SetTimeStamp((uint)(new DateTimeOffset(txTime)).ToUnixTimeSeconds());
                builder.CoinSelector = new DeterministicCoinSelector();
                builder.SendFees(fee);

                model.tx = builder.BuildTransaction(true);

                File.WriteAllText(Path.Combine(dataDirPath, $"{network.Name}_{model.PubKey.ToHex(network).Substring(0, 8)}.hex"), model.tx.ToHex(network));

                // Merge our transaction with other transactions which have been placed in the data folder.
                Transaction oldTransaction = model.tx;
                string      namePattern    = $"{network.Name}_*.hex";
                int         sigCount       = 1;
                foreach (string fileName in Directory.EnumerateFiles(dataDirPath, namePattern))
                {
                    Transaction incomingPartialTransaction = network.CreateTransaction(File.ReadAllText(fileName));

                    // Don't merge with self.
                    if (incomingPartialTransaction.GetHash() == oldTransaction.GetHash())
                    {
                        continue;
                    }

                    // Transaction times must match.
                    if (incomingPartialTransaction is PosTransaction && incomingPartialTransaction.Time != model.tx.Time)
                    {
                        Console.WriteLine($"The locally generated transaction is time-stamped differently from the transaction contained in '{fileName}'. The imported signature can't be used.");
                        continue;
                    }

                    // Combine signatures.
                    Transaction newTransaction = SigningUtils.CheckTemplateAndCombineSignatures(builder, model.tx, new[] { incomingPartialTransaction });

                    if (oldTransaction.GetHash() == newTransaction.GetHash())
                    {
                        Console.WriteLine($"The locally generated transaction is not similar to '{fileName}'. The imported signature can't be used.");
                        continue;
                    }

                    model.tx = newTransaction;
                    sigCount++;
                }

                Console.WriteLine($"{sigCount} of {oldMultisigParams.SignatureCount} signatures collected for {network.Name}.");

                if (sigCount >= oldMultisigParams.SignatureCount)
                {
                    if (builder.Verify(model.tx))
                    {
                        // Write the transaction to file.
                        File.WriteAllText(Path.Combine(dataDirPath, $"{(txTime > DateTime.Now ? "Preliminary " : "")}{network.Name}Recovery.txt"), model.tx.ToHex(network));
                    }
                    else
                    {
                        Console.WriteLine("Could not verify the transaction.");
                    }
                }
            }

            // Stop the wallet manager to release the database folder.
            nodeLifetime.StopApplication();
            walletManager.Stop();

            return(model);
        }
Exemple #12
0
		public void base58_keys_valid_gen()
		{
			var tests = TestCase.read_json("data/base58_keys_valid.json");
			Network network = null;

			foreach(var test in tests)
			{
				string strTest = test.ToString();
				if(test.Count < 3) // Allow for extra stuff (useful for comments)
				{
					Assert.False(true, "Bad test: " + strTest);
					continue;
				}
				string exp_base58string = (string)test[0];
				byte[] exp_payload = TestUtils.ParseHex((string)test[1]);
				dynamic metadata = test.GetDynamic(2);
				bool isPrivkey = (bool)metadata.isPrivkey;
				bool isTestnet = (bool)metadata.isTestnet;

				if(isTestnet)
					network = Network.TestNet;
				else
					network = Network.Main;
				if(isPrivkey)
				{
					bool isCompressed = metadata.isCompressed;
					Key key = new Key(exp_payload, fCompressedIn: isCompressed);
					BitcoinSecret secret = network.CreateBitcoinSecret(key);
					Assert.True(secret.ToString() == exp_base58string, "result mismatch: " + strTest);
				}
				else
				{
					string exp_addrType = (string)metadata.addrType;
					TxDestination dest;
					if(exp_addrType == "pubkey")
					{
						dest = new KeyId(new uint160(exp_payload));
					}
					else if(exp_addrType == "script")
					{
						dest = new ScriptId(new uint160(exp_payload));
					}
					else if(exp_addrType == "none")
					{
						dest = new TxDestination(0);
					}
					else
					{
						Assert.True(false, "Bad addrtype: " + strTest);
						continue;
					}
					try
					{
						BitcoinAddress addrOut = network.CreateBitcoinAddress(dest);
						Assert.True(addrOut.ToString() == exp_base58string, "mismatch: " + strTest);
					}
					catch(ArgumentException)
					{
						Assert.True(dest.GetType() == typeof(TxDestination));
					}
				}
			}

			// Visiting a CNoDestination must fail
			TxDestination nodest = new TxDestination();
			Assert.Throws<ArgumentException>(() => network.CreateBitcoinAddress(nodest));
		}