Ejemplo n.º 1
0
        public async Task <IActionResult> InputsAsync([FromBody] InputsRequest request)
        {
            var roundId = Global.StateMachine.RoundId;

            TumblerPhase phase = TumblerPhase.InputRegistration;

            try
            {
                if (Global.StateMachine.Phase != TumblerPhase.InputRegistration || !Global.StateMachine.AcceptRequest)
                {
                    return(new ObjectResult(new FailureResponse {
                        Message = "Wrong phase"
                    }));
                }

                // Check not nulls
                string blindedOutputString = request.BlindedOutput.Trim();
                if (string.IsNullOrWhiteSpace(blindedOutputString))
                {
                    return(new BadRequestResult());
                }
                if (string.IsNullOrWhiteSpace(request.ChangeOutput))
                {
                    return(new BadRequestResult());
                }
                if (request.Inputs == null || request.Inputs.Count() == 0)
                {
                    return(new BadRequestResult());
                }

                // Check format (parse everyting))
                if (Global.StateMachine.BlindedOutputs.Contains(blindedOutputString))
                {
                    throw new ArgumentException("Blinded output has already been registered");
                }
                byte[]  blindedOutput = HexHelpers.GetBytes(blindedOutputString);
                Network network       = Global.Config.Network;
                var     changeOutput  = new BitcoinWitPubKeyAddress(request.ChangeOutput, expectedNetwork: network);
                if (request.Inputs.Count() > Global.Config.MaximumInputsPerAlices)
                {
                    throw new NotSupportedException("Too many inputs provided");
                }
                var inputs = new HashSet <(TxOut Output, OutPoint OutPoint)>();

                var alicesToRemove = new HashSet <Guid>();
                using (await InputRegistrationLock.LockAsync())
                {
                    foreach (InputProofModel input in request.Inputs)
                    {
                        var op = new OutPoint();
                        op.FromHex(input.Input);
                        if (inputs.Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N))
                        {
                            throw new ArgumentException("Attempting to register an input twice is not permitted");
                        }
                        foreach (var a in Global.StateMachine.Alices)
                        {
                            if (a.Inputs.Any(x => x.OutPoint.Hash == op.Hash && x.OutPoint.N == op.N))
                            {
                                alicesToRemove.Add(a.UniqueId);                                 // input is already registered by this alice, remove it if all the checks are completed fine
                            }
                        }

                        BannedUtxo banned = Global.UtxoReferee.Utxos.FirstOrDefault(x => x.Utxo.Hash == op.Hash && x.Utxo.N == op.N);
                        if (banned != default(BannedUtxo))
                        {
                            var maxBan  = (int)TimeSpan.FromDays(30).TotalMinutes;
                            int banLeft = maxBan - (int)((DateTimeOffset.UtcNow - banned.TimeOfBan).TotalMinutes);
                            throw new ArgumentException($"Input is banned for {banLeft} minutes");
                        }

                        var getTxOutResponse = await Global.RpcClient.GetTxOutAsync(op.Hash, (int)op.N, true);

                        // Check if inputs are unspent
                        if (getTxOutResponse == null)
                        {
                            throw new ArgumentException("Provided input is not unspent");
                        }
                        // Check if inputs are unconfirmed, if so check if they are part of previous CoinJoin
                        if (getTxOutResponse.Confirmations <= 0)
                        {
                            if (!Global.CoinJoinStore.Transactions
                                .Any(x => x.State >= CoinJoinTransactionState.Succeeded && x.Transaction.GetHash() == op.Hash))
                            {
                                throw new ArgumentException("Provided input is not confirmed, nor spends a previous CJ transaction");
                            }
                            else
                            {
                                // after 24 unconfirmed cj in the mempool dont't let unconfirmed coinjoin to be registered
                                var unconfirmedCoinJoins = Global.CoinJoinStore.Transactions.Where(x => x.State == CoinJoinTransactionState.Succeeded);
                                if (unconfirmedCoinJoins.Count() >= 24)
                                {
                                    var toFailed    = new HashSet <uint256>();
                                    var toConfirmed = new HashSet <uint256>();
                                    foreach (var tx in unconfirmedCoinJoins)
                                    {
                                        RPCResponse getRawTransactionResponse = (await Global.RpcClient.SendCommandAsync("getrawtransaction", tx.Transaction.GetHash().ToString(), true));
                                        if (string.IsNullOrWhiteSpace(getRawTransactionResponse?.ResultString))
                                        {
                                            toFailed.Add(tx.Transaction.GetHash());
                                        }
                                        if (getRawTransactionResponse.Result.Value <int>("confirmations") > 0)
                                        {
                                            toConfirmed.Add(tx.Transaction.GetHash());
                                        }
                                    }
                                    foreach (var tx in toFailed)
                                    {
                                        Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Failed);
                                    }
                                    foreach (var tx in toConfirmed)
                                    {
                                        Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Confirmed);
                                    }
                                    if (toFailed.Count + toConfirmed.Count > 0)
                                    {
                                        await Global.CoinJoinStore.ToFileAsync(Global.CoinJoinStorePath);
                                    }
                                    // if couldn't remove any unconfirmed tx then refuse registration
                                    if (Global.CoinJoinStore.Transactions.Count(x => x.State == CoinJoinTransactionState.Succeeded) >= 24)
                                    {
                                        throw new ArgumentException("Registering unconfirmed CJ transaction output is currently not allowed due to too long mempool chain");
                                    }
                                }
                            }
                        }
                        // Check coinbase > 100
                        if (getTxOutResponse.Confirmations < 100)
                        {
                            if (getTxOutResponse.IsCoinBase)
                            {
                                throw new ArgumentException("Provided input is unspendable");
                            }
                        }
                        // Check if inputs are native segwit
                        if (getTxOutResponse.ScriptPubKeyType != "witness_v0_keyhash")
                        {
                            throw new ArgumentException("Provided input is not witness_v0_keyhash");
                        }

                        var txout = getTxOutResponse.TxOut;

                        var address = (BitcoinWitPubKeyAddress)txout.ScriptPubKey.GetDestinationAddress(network);
                        // Check if proofs are valid
                        var validProof = address.VerifyMessage(blindedOutputString, input.Proof);
                        if (!validProof)
                        {
                            throw new ArgumentException("Provided proof is invalid");
                        }

                        inputs.Add((txout, op));
                    }

                    // Check if inputs have enough coins
                    Money amount = Money.Zero;
                    foreach (Money val in inputs.Select(x => x.Output.Value))
                    {
                        amount += val;
                    }
                    Money feeToPay     = (inputs.Count() * Global.StateMachine.FeePerInputs + 2 * Global.StateMachine.FeePerOutputs);
                    Money changeAmount = amount - (Global.StateMachine.Denomination + feeToPay);
                    if (changeAmount < Money.Zero + new Money(548))                     // 546 is dust
                    {
                        throw new ArgumentException("Total provided inputs must be > denomination + fee + dust");
                    }

                    byte[] signature = Global.RsaKey.SignBlindedData(blindedOutput);
                    Global.StateMachine.BlindedOutputs.Add(blindedOutputString);

                    Guid uniqueId = Guid.NewGuid();

                    var alice = new Alice
                    {
                        UniqueId     = uniqueId,
                        ChangeOutput = changeOutput,
                        ChangeAmount = changeAmount,
                        State        = AliceState.InputsRegistered
                    };
                    alice.Inputs = new ConcurrentHashSet <(TxOut Output, OutPoint OutPoint)>();
                    foreach (var input in inputs)
                    {
                        alice.Inputs.Add(input);
                    }

                    AssertPhase(roundId, phase);
                    foreach (var aliceToRemove in alicesToRemove)
                    {
                        if (Global.StateMachine.TryRemoveAlice(aliceToRemove))
                        {
                            await Global.StateMachine.BroadcastPeerRegisteredAsync();
                        }
                    }
                    Global.StateMachine.Alices.Add(alice);

                    await Global.StateMachine.BroadcastPeerRegisteredAsync();

                    if (Global.StateMachine.Alices.Count >= Global.StateMachine.AnonymitySet)
                    {
                        Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation);
                    }
                    TumblerStateMachine.EstimateInputAndOutputSizes(out int inputSizeInBytes, out int outputSizeInBytes);
                    int estimatedTxSize = Global.StateMachine.Alices.SelectMany(x => x.Inputs).Count() * inputSizeInBytes + 2 * outputSizeInBytes;
                    if (estimatedTxSize >= 90000)                    // standard transaction is < 100KB
                    {
                        Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation);
                    }

                    var ret = new ObjectResult(new InputsResponse()
                    {
                        UniqueId            = uniqueId.ToString(),
                        SignedBlindedOutput = HexHelpers.ToString(signature)
                    });
                    return(ret);
                }
            }
            catch (Exception ex)
            {
                return(new ObjectResult(new FailureResponse {
                    Message = ex.Message
                }));
            }
        }
Ejemplo n.º 2
0
        public async Task TrezorTKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect an already initialized device and unlock it.
            // Run this test.
            // displayaddress request: refuse
            // displayaddress request: confirm
            // displayaddress request: confirm
            // signtx request: confirm
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.True(entry.Fingerprint.HasValue);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER SHOULD REFUSE ACTION
            await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token));

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);

            // USER: CONFIRM
            PSBT psbt       = BuildPsbt(network, fingerprint, xpub1, keyPath1);
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);
        }
Ejemplo n.º 3
0
        public async Task LedgerNanoXKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect and initialize your Nano X with the following seed phrase:
            // more maid moon upgrade layer alter marine screen benefit way cover alcohol
            // Run this test.
            // displayaddress request(derivation path): approve
            // displayaddress request: reject
            // displayaddress request(derivation path): approve
            // displayaddress request: approve
            // displayaddress request(derivation path): approve
            // displayaddress request: approve
            // signtx request: reject
            // signtx request: accept
            // confirm transaction: accept and send
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            HwiEnumerateEntry entry = Assert.Single(enumerate);

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model);
            Assert.NotNull(entry.Fingerprint);
            Assert.Null(entry.Code);
            Assert.Null(entry.Error);
            Assert.Null(entry.SerialNumber);
            Assert.False(entry.NeedsPassphraseSent);
            Assert.False(entry.NeedsPinSent);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            KeyPath   keyPath1 = KeyManager.GetAccountKeyPath(network);
            KeyPath   keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER SHOULD REFUSE ACTION
            await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token));

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);

            // USER: REFUSE
            var ex = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));

            Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode);

            // USER: CONFIRM
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);
        }
Ejemplo n.º 4
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));

            var wallet = GetWalletByName(walletName);

            // Get/create the cold staking accounts.
            var coldAccount = GetOrCreateColdStakingAccount(walletName, true, walletPassword);
            var hotAccount  = GetOrCreateColdStakingAccount(walletName, false, walletPassword);

            var coldAddress = coldAccount?.ExternalAddresses.FirstOrDefault(s =>
                                                                            s.Address == coldWalletAddress || s.Bech32Address == coldWalletAddress);
            var hotAddress = hotAccount?.ExternalAddresses.FirstOrDefault(s =>
                                                                          s.Address == hotWalletAddress || s.Bech32Address == hotWalletAddress);

            var thisIsColdWallet = coldAddress != null;
            var 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)
                {
                    var 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)
                {
                    var address = coldAddress ?? hotAddress;
                    address.RedeemScript = destination;
                    destination          = destination.Hash.ScriptPubKey;
                }
            }

            // Only normal accounts should be allowed.
            if (!GetAccounts(walletName).Any(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 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();
                //context.OpReturnAmount = Money.Satoshis(1); // 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.
            var transaction = walletTransactionHandler.BuildTransaction(context);

            this.logger.LogTrace("(-)");
            return(transaction);
        }
Ejemplo n.º 5
0
        private TransactionBuildContext GetSetupTransactionBuildContext(IWalletTransactionHandler walletTransactionHandler,
                                                                        string coldWalletAddress, string hotWalletAddress, string walletName, string walletAccount,
                                                                        string walletPassword, Money amount, Money feeAmount, bool subtractFeeFromAmount, bool offline, bool useSegwitChangeAddress, int splitCount, ExtPubKey extPubKey = null)
        {
            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));

            Wallet.Wallet wallet = this.GetWallet(walletName);

            KeyId hotPubKeyHash  = null;
            KeyId coldPubKeyHash = null;

            if (!offline)
            {
                // Get/create the cold staking accounts.
                HdAccount coldAccount = this.GetOrCreateColdStakingAccount(walletName, true, walletPassword, extPubKey);
                HdAccount hotAccount  = this.GetOrCreateColdStakingAccount(walletName, false, walletPassword, extPubKey);

                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 the 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.");
                }

                // 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();
                }
                else
                {
                    hotPubKeyHash  = new BitcoinPubKeyAddress(hotWalletAddress, wallet.Network).Hash;
                    coldPubKeyHash = new BitcoinPubKeyAddress(coldWalletAddress, wallet.Network).Hash;
                }
            }
            else
            {
                // In offline mode we relax all the restrictions to enable simpler setup. The user should ensure they are using separate wallets, or the cold private key could be inadvertently loaded on the online node.
                IDestination hot  = BitcoinAddress.Create(hotWalletAddress, this.network);
                IDestination cold = BitcoinAddress.Create(coldWalletAddress, this.network);

                if (hot is BitcoinPubKeyAddress && cold is BitcoinPubKeyAddress)
                {
                    hotPubKeyHash  = new BitcoinPubKeyAddress(hotWalletAddress, wallet.Network).Hash;
                    coldPubKeyHash = new BitcoinPubKeyAddress(coldWalletAddress, wallet.Network).Hash;
                }

                if (hot is BitcoinWitPubKeyAddress && cold is BitcoinWitPubKeyAddress)
                {
                    hotPubKeyHash  = new BitcoinWitPubKeyAddress(hotWalletAddress, wallet.Network).Hash.AsKeyId();
                    coldPubKeyHash = new BitcoinWitPubKeyAddress(coldWalletAddress, wallet.Network).Hash.AsKeyId();
                }
            }

            if (hotPubKeyHash == null || coldPubKeyHash == null)
            {
                this.logger.LogTrace("(-)[PUBKEYHASH_NOT_AVAILABLE]");
                throw new WalletException($"Unable to compute the needed hashes from the given addresses.");
            }

            Script destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash);

            // Only normal accounts should be allowed.
            if (!this.GetAccounts(walletName).Any(a => a.Name == walletAccount))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_NOT_FOUND]");
                throw new WalletException($"Can't find wallet account '{walletAccount}'.");
            }

            List <Recipient> recipients = GetRecipients(destination, amount, subtractFeeFromAmount, splitCount);

            var context = new TransactionBuildContext(wallet.Network)
            {
                AccountReference       = new WalletAccountReference(walletName, walletAccount),
                TransactionFee         = feeAmount,
                MinConfirmations       = 0,
                Shuffle                = false,
                UseSegwitChangeAddress = useSegwitChangeAddress,
                WalletPassword         = walletPassword,
                Recipients             = recipients
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            return(context);
        }
Ejemplo n.º 6
0
        public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase)
        {
            using (await RoundSyncronizerLock.LockAsync())
            {
                try
                {
                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase change requested: {expectedPhase.ToString()}.");

                    if (Status == CcjRoundStatus.NotStarted)                     // So start the input registration phase
                    {
                        if (expectedPhase != CcjRoundPhase.InputRegistration)
                        {
                            return;
                        }

                        // Calculate fees
                        var inputSizeInBytes  = (int)Math.Ceiling(((3 * Constants.P2wpkhInputSizeInBytes) + Constants.P2pkhInputSizeInBytes) / 4m);
                        var outputSizeInBytes = Constants.OutputSizeInBytes;
                        try
                        {
                            var estimateSmartFeeResponse = await RpcClient.EstimateSmartFeeAsync(ConfirmationTarget, EstimateSmartFeeMode.Conservative, simulateIfRegTest : true);

                            if (estimateSmartFeeResponse == null)
                            {
                                throw new InvalidOperationException("FeeRate is not yet initialized");
                            }
                            var   feeRate     = estimateSmartFeeResponse.FeeRate;
                            Money feePerBytes = (feeRate.FeePerK / 1000);

                            // Make sure min relay fee (1000 sat) is hit.
                            FeePerInputs  = Math.Max(feePerBytes * inputSizeInBytes, new Money(500));
                            FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250));
                        }
                        catch (Exception ex)
                        {
                            // If fee hasn't been initialized once, fall back.
                            if (FeePerInputs == null || FeePerOutputs == null)
                            {
                                var feePerBytes = new Money(100);                                 // 100 satoshi per byte

                                // Make sure min relay fee (1000 sat) is hit.
                                FeePerInputs  = Math.Max(feePerBytes * inputSizeInBytes, new Money(500));
                                FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250));
                            }

                            Logger.LogError <CcjRound>(ex);
                        }

                        Status = CcjRoundStatus.Running;
                    }
                    else if (Status != CcjRoundStatus.Running)                     // Failed or succeeded, swallow
                    {
                        return;
                    }
                    else if (Phase == CcjRoundPhase.InputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.ConnectionConfirmation)
                        {
                            return;
                        }

                        RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.OutPoint));

                        Phase = CcjRoundPhase.ConnectionConfirmation;
                    }
                    else if (Phase == CcjRoundPhase.ConnectionConfirmation)
                    {
                        if (expectedPhase != CcjRoundPhase.OutputRegistration)
                        {
                            return;
                        }

                        Phase = CcjRoundPhase.OutputRegistration;
                    }
                    else if (Phase == CcjRoundPhase.OutputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.Signing)
                        {
                            return;
                        }

                        // Build CoinJoin

                        // 1. Set new denomination: minor optimization.
                        Money newDenomination = Alices.Min(x => x.OutputSumWithoutCoordinatorFeeAndDenomination);
                        var   transaction     = RpcClient.Network.Consensus.ConsensusFactory.CreateTransaction();

                        // 2. Add Bob outputs.
                        foreach (Bob bob in Bobs)
                        {
                            transaction.AddOutput(newDenomination, bob.ActiveOutputAddress.ScriptPubKey);
                        }

                        BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(RpcClient.Network);
                        // 3. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign.
                        for (int i = 0; i < Alices.Count - Bobs.Count; i++)
                        {
                            transaction.AddOutput(newDenomination, coordinatorAddress);
                        }

                        // 4. Start building Coordinator fee.
                        Money coordinatorFeePerAlice = newDenomination.Percentange(CoordinatorFeePercent);
                        Money coordinatorFee         = Alices.Count * coordinatorFeePerAlice;

                        // 5. Add the inputs and the changes of Alices.
                        foreach (Alice alice in Alices)
                        {
                            foreach (var input in alice.Inputs)
                            {
                                transaction.AddInput(new TxIn(input.OutPoint));
                            }
                            Money changeAmount = alice.GetChangeAmount(newDenomination, coordinatorFeePerAlice);
                            if (changeAmount > Money.Zero)                                        // If the coordinator fee would make change amount to be negative or zero then no need to pay it.
                            {
                                Money minimumOutputAmount      = Money.Coins(0.0001m);            // If the change would be less than about $1 then add it to the coordinator.
                                Money onePercentOfDenomination = newDenomination.Percentange(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee.
                                Money minimumChangeAmount      = Math.Max(minimumOutputAmount, onePercentOfDenomination);
                                if (changeAmount < minimumChangeAmount)
                                {
                                    coordinatorFee += changeAmount;
                                }
                                else
                                {
                                    transaction.AddOutput(changeAmount, alice.ChangeOutputAddress.ScriptPubKey);
                                }
                            }
                            else
                            {
                                coordinatorFee -= coordinatorFeePerAlice;
                            }
                        }

                        // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee.
                        if (coordinatorFee > Money.Coins(0.0003m))
                        {
                            transaction.AddOutput(coordinatorFee, coordinatorAddress);
                        }

                        // 7. Create the unsigned transaction.
                        var builder = new TransactionBuilder();
                        UnsignedCoinJoin = builder
                                           .ContinueToBuild(transaction)
                                           .Shuffle()
                                           .BuildTransaction(false);

                        SignedCoinJoin = RpcClient.Network.Consensus.ConsensusFactory.CreateTransaction();
                        SignedCoinJoin.FromHex(UnsignedCoinJoin.ToHex());

                        Phase = CcjRoundPhase.Signing;
                    }
                    else
                    {
                        return;
                    }

                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase initialized: {expectedPhase.ToString()}.");
                }
                catch (Exception ex)
                {
                    Logger.LogError <CcjRound>(ex);
                    Status = CcjRoundStatus.Failed;
                    throw;
                }
            }

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            Task.Run(async() =>
            {
                TimeSpan timeout;
                switch (expectedPhase)
                {
                case CcjRoundPhase.InputRegistration:
                    timeout = InputRegistrationTimeout;
                    break;

                case CcjRoundPhase.ConnectionConfirmation:
                    timeout = ConnectionConfirmationTimeout;
                    break;

                case CcjRoundPhase.OutputRegistration:
                    timeout = OutputRegistrationTimeout;
                    break;

                case CcjRoundPhase.Signing:
                    timeout = SigningTimeout;
                    break;

                default: throw new InvalidOperationException("This is impossible to happen.");
                }

                // Delay asyncronously to the requested timeout.
                await Task.Delay(timeout);

                var executeRunFailure = false;
                using (await RoundSyncronizerLock.LockAsync())
                {
                    executeRunFailure = Status == CcjRoundStatus.Running && Phase == expectedPhase;
                }
                if (executeRunFailure)
                {
                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timed out after {timeout.TotalSeconds} seconds. Failure mode is executing.");

                    // This will happen outside the lock.
                    Task.Run(async() =>
                    {
                        try
                        {
                            switch (expectedPhase)
                            {
                            case CcjRoundPhase.InputRegistration:
                                {
                                    // Only fail if less two one Alice is registered.
                                    // Don't ban anyone, it's ok if they lost connection.
                                    await RemoveAlicesIfInputsSpentAsync();
                                    int aliceCountAfterInputRegistrationTimeout = CountAlices();
                                    if (aliceCountAfterInputRegistrationTimeout < 2)
                                    {
                                        Fail();
                                    }
                                    else
                                    {
                                        UpdateAnonymitySet(aliceCountAfterInputRegistrationTimeout);
                                        // Progress to the next phase, which will be ConnectionConfirmation
                                        await ExecuteNextPhaseAsync(CcjRoundPhase.ConnectionConfirmation);
                                    }
                                }
                                break;

                            case CcjRoundPhase.ConnectionConfirmation:
                                {
                                    // Only fail if less than two one alices are registered.
                                    // What if an attacker registers all the time many alices, then drops out. He'll achieve only 2 alices to participate?
                                    // If he registers many alices at InputRegistration
                                    // AND never confirms in connection confirmation
                                    // THEN connection confirmation will go with 2 alices in every round
                                    // Therefore Alices those didn't confirm, nor requested dsconnection should be banned:
                                    IEnumerable <Alice> alicesToBan1 = GetAlicesBy(AliceState.InputsRegistered);
                                    IEnumerable <Alice> alicesToBan2 = await RemoveAlicesIfInputsSpentAsync();                                            // So ban only those who confirmed participation, yet spent their inputs.

                                    IEnumerable <OutPoint> inputsToBan = alicesToBan1.SelectMany(x => x.Inputs).Select(y => y.OutPoint).Concat(alicesToBan2.SelectMany(x => x.Inputs).Select(y => y.OutPoint)).Distinct();

                                    if (inputsToBan.Any())
                                    {
                                        await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, inputsToBan.ToArray());
                                    }

                                    RemoveAlicesBy(alicesToBan1.Select(x => x.UniqueId).Concat(alicesToBan2.Select(y => y.UniqueId)).Distinct().ToArray());

                                    int aliceCountAfterConnectionConfirmationTimeout = CountAlices();
                                    if (aliceCountAfterConnectionConfirmationTimeout < 2)
                                    {
                                        Fail();
                                    }
                                    else
                                    {
                                        UpdateAnonymitySet(aliceCountAfterConnectionConfirmationTimeout);
                                        // Progress to the next phase, which will be OutputRegistration
                                        await ExecuteNextPhaseAsync(CcjRoundPhase.OutputRegistration);
                                    }
                                }
                                break;

                            case CcjRoundPhase.OutputRegistration:
                                {
                                    // Output registration never fails.
                                    // We don't know which Alice to ban.
                                    // Therefore proceed to signing, and whichever Alice doesn't sign ban.
                                    await ExecuteNextPhaseAsync(CcjRoundPhase.Signing);
                                }
                                break;

                            case CcjRoundPhase.Signing:
                                {
                                    var outpointsToBan = new List <OutPoint>();
                                    using (await RoundSyncronizerLock.LockAsync())
                                    {
                                        foreach (Alice alice in Alices)
                                        {
                                            if (alice.State != AliceState.SignedCoinJoin)
                                            {
                                                outpointsToBan.AddRange(alice.Inputs.Select(x => x.OutPoint));
                                            }
                                        }
                                    }
                                    if (outpointsToBan.Any())
                                    {
                                        await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, outpointsToBan.ToArray());
                                    }
                                    Fail();
                                }
                                break;

                            default: throw new InvalidOperationException("This is impossible to happen.");
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogWarning <CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timeout failed with exception: {ex}");
                        }
                    });
                }
            });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }
Ejemplo n.º 7
0
        public static T Parse <T>(string str, Network expectedNetwork) where T : IBitcoinString
        {
            if (str == null)
            {
                throw new ArgumentNullException("str");
            }

            IEnumerable <Network> networks = expectedNetwork == null?NetworkRegistration.GetNetworks() : new[] { expectedNetwork };
            bool maybeb58 = true;

            for (int i = 0; i < str.Length; i++)
            {
                if (!Base58Encoder.pszBase58Chars.Contains(str[i]))
                {
                    maybeb58 = false;
                    break;
                }
            }

            if (maybeb58)
            {
                try
                {
                    Encoders.Base58Check.DecodeData(str);
                }
                catch (FormatException)
                {
                    maybeb58 = false;
                }
                if (maybeb58)
                {
                    foreach (IBase58Data candidate in GetCandidates(networks, str))
                    {
                        bool rightNetwork = expectedNetwork == null || (candidate.Network == expectedNetwork);
                        bool rightType    = candidate is T;
                        if (rightNetwork && rightType)
                        {
                            return((T)(object)candidate);
                        }
                    }
                    throw new FormatException("Invalid base58 string");
                }
            }

            foreach (Network network in networks)
            {
                int i = -1;
                foreach (Bech32Encoder encoder in network.Bech32Encoders)
                {
                    i++;
                    if (encoder == null)
                    {
                        continue;
                    }
                    var type = (Bech32Type)i;
                    try
                    {
                        byte   witVersion;
                        byte[] bytes     = encoder.Decode(str, out witVersion);
                        object candidate = null;

                        if (witVersion == 0 && bytes.Length == 20 && type == Bech32Type.WITNESS_PUBKEY_ADDRESS)
                        {
                            candidate = new BitcoinWitPubKeyAddress(str, network);
                        }
                        if (witVersion == 0 && bytes.Length == 32 && type == Bech32Type.WITNESS_SCRIPT_ADDRESS)
                        {
                            candidate = new BitcoinWitScriptAddress(str, network);
                        }

                        if (candidate is T)
                        {
                            return((T)candidate);
                        }
                    }
                    catch (Bech32FormatException)
                    {
                        throw;
                    }
                    catch (FormatException)
                    {
                        continue;
                    }
                }
            }

            throw new FormatException("Invalid string");
        }
Ejemplo n.º 8
0
        public async Task LedgerNanoXTestsAsync(Network network)
        {
            var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Ledger_Nano_X));

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model);
            Assert.Equal(@"\\?\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", entry.Path);
            Assert.False(entry.NeedsPassphraseSent);
            Assert.False(entry.NeedsPinSent);
            Assert.Null(entry.Error);
            Assert.Null(entry.Code);
            Assert.True(entry.IsInitialized());
            Assert.Equal("4054d6f6", entry.Fingerprint.ToString());

            var deviceType = entry.Model;
            var devicePath = entry.Path;

            var wipe = await Assert.ThrowsAsync <HwiException>(async() => await client.WipeAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Ledger Nano X does not support wiping via software", wipe.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, wipe.ErrorCode);

            var setup = await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Ledger Nano X does not support software setup", setup.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, setup.ErrorCode);

            var restore = await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Ledger Nano X does not support restoring via software", restore.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, restore.ErrorCode);

            var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", promptpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, promptpin.ErrorCode);

            var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", sendpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, sendpin.ErrorCode);

            KeyPath   keyPath1 = KeyManager.GetAccountKeyPath(network);
            KeyPath   keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            ExtPubKey expectedXpub1;
            ExtPubKey expectedXpub2;

            if (network == Network.TestNet)
            {
                expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6CaGC5LjEw1YWw8br7AURnB6ioJY2bEVApXh8NMsPQ9mdDbzN51iwVrnmGSof3MfjjRrntnE8mbYeTW5ywgvCXdjqF8meQEwnhPDQV2TW7c");
                expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6E7pup6CRRS5jM1r3HVYQhHwQHpddJALjRDbsVDtsnQJozHrfE8Pua2X5JhtkWCxdcmGhPXWxV7DoJtSgZSUvUy6cvDchVQt2RGEd4mD4FA");
            }
            else
            {
                expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
                expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");
            }
            Assert.Equal(expectedXpub1, xpub1);
            Assert.Equal(expectedXpub2, xpub2);

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

            BitcoinAddress expectedAddress1;
            BitcoinAddress expectedAddress2;

            if (network == Network.Main)
            {
                expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
                expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
            }
            else if (network == Network.TestNet)
            {
                expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
                expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
            }
            else if (network == Network.RegTest)
            {
                expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
                expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
        private async void SendDGB()
        {
            InvokeHelp.SetControlPropertyThreadSafe(progressBar1, "Value", 1);
            Network       network = NBitcoin.Altcoins.Digibyte.Instance.Mainnet;
            BitcoinSecret sender  = saveKey.SecretKey;

            var myTransaction = Transaction.Create(network);

            var fee     = new Money(Fee);
            var allUtxo = await GetReceivedCoinFromDigibyte(sender.GetAddress().ToString());

            var utxo = allUtxo.OrderByDescending(u => u.amount).First().AsCoin();
            //var utxo = allUtxo[0].ToCoin();

            var txBuilder = network.CreateTransactionBuilder();

            for (int i = 0; i < allUtxo.Count(); i++)
            {
                txBuilder = txBuilder.AddCoins(allUtxo[i].AsCoin());
            }

            txBuilder.AddKeys(sender);

            foreach (var a in LowMoneyWallet)
            {
                try
                {
                    BitcoinAddress receiver;
                    if (a.account_id.publickey.StartsWith("dgb1"))
                    {
                        receiver = new BitcoinWitPubKeyAddress(a.account_id.publickey, network);
                    }
                    else
                    {
                        receiver = new BitcoinPubKeyAddress(a.account_id.publickey, network);
                    }

                    txBuilder
                    .Send(receiver, new Money(MoneyToSend));
                }
                catch
                {
                    continue;
                }
            }
            tx = txBuilder
                 .SetChange(sender.GetAddress())
                 .SendFees(fee)
                 .BuildTransaction(true);

            var verified = txBuilder.Verify(tx);

            InvokeHelp.SetControlPropertyThreadSafe(progressBar1, "Value", 2);

            var txid = await BroadcastTransaction(tx.ToHex());

            int issuedRepeat = 20;

            InvokeHelp.SetControlPropertyThreadSafe(progressBar1, "Value", 3);
start:
            var txContract = await api.GetTransactionInfo(txid.txid);

            if (txContract.time == 0 && txContract.blocktime == 0)
            {
                if (issuedRepeat > 0)
                {
                    issuedRepeat--;
                    Thread.Sleep(5000);
                    goto start;
                }
                else
                {
                    InvokeHelp.SetControlPropertyThreadSafe(progressBar1, "Visible", false);
                    MessageBox.Show("การออกเงินให้เจ้าของเงินไม่สมบูรณ์ กรุณาตรวจสอบรหัสธุรกรรมการออกเงิน :" + txid.txid);
                    triggerFinish = true;
                    Action action1 = new Action(FinishCreating);
                    this.BeginInvoke(action1);
                    return;
                }
            }
            InvokeHelp.SetControlPropertyThreadSafe(progressBar1, "Value", 4);
            MessageBox.Show("ส่งเงินสำเร็จ");

            triggerFinish = true;
            Action action = new Action(FinishCreating);

            this.BeginInvoke(action);
        }
Ejemplo n.º 10
0
        public async Task MempoolNotifiesAsync()
        {
            using HostedServices services = new();
            CoreNode coreNode = await TestNodeBuilder.CreateAsync(services);

            await services.StartAllAsync();

            using var node = await coreNode.CreateNewP2pNodeAsync();

            try
            {
                string dir     = Common.GetWorkDir();
                var    network = coreNode.Network;
                var    rpc     = coreNode.RpcClient;
                await using IndexStore indexStore = new(Path.Combine(dir, "indexStore"), network, new SmartHeaderChain());
                await using AllTransactionStore transactionStore = new(Path.Combine(dir, "transactionStore"), network);
                MempoolService            mempoolService = new();
                FileSystemBlockRepository blocks         = new(Path.Combine(dir, "blocks"), network);

                // Construct BitcoinStore.
                await using BitcoinStore bitcoinStore = new(indexStore, transactionStore, mempoolService, blocks);
                await bitcoinStore.InitializeAsync();

                await rpc.GenerateAsync(blockCount : 101);

                node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());
                node.VersionHandshake();

                using Key k = new Key();
                BitcoinWitPubKeyAddress address = k.PubKey.GetSegwitAddress(network);

                // Number of transactions to send.
                const int TransactionsCount = 10;

                EventsAwaiter <SmartTransaction> eventAwaiter = new(
                    subscribe : h => mempoolService.TransactionReceived += h,
                    unsubscribe : h => mempoolService.TransactionReceived -= h,
                    count : TransactionsCount);

                List <Task <uint256> > txHashesList = new();
                IRPCClient             rpcBatch     = rpc.PrepareBatch();

                // Add to the batch 10 RPC commands: Send 1 coin to the same address.
                for (int i = 0; i < TransactionsCount; i++)
                {
                    txHashesList.Add(rpcBatch.SendToAddressAsync(address, Money.Coins(1)));
                }

                // Publish the RPC batch.
                Task rpcBatchTask = rpcBatch.SendBatchAsync();

                // Wait until the mempool service receives all the sent transactions.
                IEnumerable <SmartTransaction> mempoolSmartTxs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(30));

                await rpcBatchTask;

                // Collect all the transaction hashes of the sent transactions.
                uint256[] hashes = await Task.WhenAll(txHashesList);

                // Check that all the received transaction hashes are in the set of sent transaction hashes.
                foreach (SmartTransaction tx in mempoolSmartTxs)
                {
                    Assert.Contains(tx.GetHash(), hashes);
                }
            }
            finally
            {
                await services.StopAllAsync();

                node.Disconnect();
                await coreNode.TryStopAsync();
            }
        }
Ejemplo n.º 11
0
        private async void SendDigibyte()
        {
            InvokeHelp.SetControlPropertyThreadSafe(Sendinglabel, "Text", "กำลังส่งเงินอัตโนมัติ");
            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Visible", true);
            InvokeHelp.SetControlPropertyThreadSafe(refreshbutton, "Enabled", false);
            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Value", 1);
            Network       network = NBitcoin.Altcoins.Digibyte.Instance.Mainnet;
            BitcoinSecret sender  = saveKey.SecretKey;

            var myTransaction = Transaction.Create(network);

            var fee     = new Money(Fee);
            var allUtxo = await GetReceivedCoinFromDigibyte(sender.GetAddress().ToString());

            var utxo = allUtxo.OrderByDescending(u => u.amount).First().AsCoin();
            //var utxo = allUtxo[0].ToCoin();

            var txBuilder = network.CreateTransactionBuilder();

            for (int i = 0; i < allUtxo.Count(); i++)
            {
                txBuilder = txBuilder.AddCoins(allUtxo[i].AsCoin());
            }

            txBuilder.AddKeys(sender);

            foreach (var a in LowMoneyWallet)
            {
                try
                {
                    BitcoinAddress receiver;
                    if (a.account_id.publickey.StartsWith("dgb1"))
                    {
                        receiver = new BitcoinWitPubKeyAddress(a.account_id.publickey, network);
                    }
                    else
                    {
                        receiver = new BitcoinPubKeyAddress(a.account_id.publickey, network);
                    }

                    txBuilder
                    .Send(receiver, new Money(MoneyToSend));
                }
                catch
                {
                    continue;
                }
            }
            tx = txBuilder
                 .SetChange(sender.GetAddress())
                 .SendFees(fee)
                 .BuildTransaction(true);

            var verified = txBuilder.Verify(tx);

            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Value", 2);

            var txid = await BroadcastTransaction(tx.ToHex());

            int issuedRepeat = 30;

            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Value", 3);
start:
            var txContract = await api.GetTransactionInfo(txid.txid);

            if (txContract.time == 0 && txContract.blocktime == 0)
            {
                if (issuedRepeat > 0)
                {
                    issuedRepeat--;
                    Thread.Sleep(5000);
                    goto start;
                }
                else
                {
                    InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Visible", false);
                    InvokeHelp.SetControlPropertyThreadSafe(Sendinglabel, "Text", "ส่งเงินไม่สำเร็จ");
                    timer.Start();
                    return;
                }
            }


            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Value", 4);
            InvokeHelp.SetControlPropertyThreadSafe(progressBar2, "Visible", false);

            InvokeHelp.SetControlPropertyThreadSafe(Sendinglabel, "ForeColor", Color.Green);
            InvokeHelp.SetControlPropertyThreadSafe(Sendinglabel, "Text", "ส่งเงินอัตโนมัติสำเร็จ");

            InvokeHelp.SetControlPropertyThreadSafe(timeSendinglabel, "Text", DateTime.Now.ToString("HH:mm dd/MM/yyyy"));
            InvokeHelp.SetControlPropertyThreadSafe(NumOfWalletSendlabel, "Text", LowMoneyWallet.Count().ToString());

            InvokeHelp.SetControlPropertyThreadSafe(totalSendlabel, "ForeColor", Color.BlueViolet);

            decimal totaltSent = LowMoneyWallet.Count() * (MoneyToSend / 100000000m);

            totaltSent = totaltSent + (Fee / 100000000m);
            InvokeHelp.SetControlPropertyThreadSafe(totalSendlabel, "Text", totaltSent.ToString());

            InvokeHelp.SetControlPropertyThreadSafe(refreshbutton, "Enabled", true);

            LoadData();
            t.Abort();
        }
Ejemplo n.º 12
0
        internal static void UpdateNodeServices(string clientId, DirectoryInfo dataDirRoot, string passphrase, string rpcHost, int rpcPort, string rpcUsr, string rpcPassword, bool mine, bool stake, BitcoinWitPubKeyAddress minetoaddress, List <int> disabledIndices)
        {
            var nodeServices = (AppConfiguration)App.ServiceProvider.GetService <IAppConfiguration>();

            nodeServices.ClientId              = clientId;
            nodeServices.DataDirRoot           = dataDirRoot;
            nodeServices.Passphrase            = passphrase;
            nodeServices.RPCHost               = rpcHost;
            nodeServices.RPCPort               = rpcPort;
            nodeServices.RPCUser               = rpcUsr;
            nodeServices.RPCPassword           = rpcPassword;
            nodeServices.Stake                 = stake;
            nodeServices.Mine                  = mine;
            nodeServices.MineToAddress         = minetoaddress;
            nodeServices.DisabledDeviceIndices = disabledIndices ?? new List <int>();
        }
Ejemplo n.º 13
0
        static void LoadConfig(DirectoryInfo dataDirRoot)
        {
            var configFilePath = Path.Combine(dataDirRoot.ToString(), "x1-producer.config");

            if (!File.Exists(configFilePath))
            {
                CreateConfigFileAndExit(configFilePath);
            }

            try
            {
                var parser = new FileIniDataParser();
                parser.Parser.Configuration.CommentString   = "#";
                parser.Parser.Configuration.AssigmentSpacer = "";

                IniData data     = parser.ReadFile(configFilePath);
                string  clientId = Environment.GetEnvironmentVariable("COMPUTERNAME") ??
                                   Environment.GetEnvironmentVariable("HOSTNAME");

                string targetIp = Read(data, "targetip", true, true);
                var    ip       = IPAddress.Parse(targetIp);
                var    host     = $"http://{ip}";


                string targetPort  = Read(data, "targetport", true, true);
                bool   mine        = Read(data, "mine", true, true) == "0" ? false : Read(data, "mine", true, true) == "1" ? true : throw new InvalidOperationException("mine = 1 or mine = 0 is expected.");
                bool   stake       = Read(data, "stake", true, true) == "0" ? false : Read(data, "stake", true, true) == "1" ? true : throw new InvalidOperationException("stake = 1 or stake = 0 is expected.");
                string rpcuser     = Read(data, "rpcuser", true, true);
                string rpcpassword = Read(data, "rpcpassword", true, true);

                BitcoinWitPubKeyAddress minetoaddress = null;
                List <int> disabledIndices            = new List <int>();

                if (mine)
                {
                    minetoaddress = ReadMineToAddress(data);
                    // read disabled mining devices if any
                    string disable = Read(data, "disable", false, false);
                    if (!string.IsNullOrWhiteSpace(disable))
                    {
                        string[] maybeIds = disable.Split(",");
                        foreach (var s in maybeIds)
                        {
                            if (int.TryParse(s, out int number))
                            {
                                disabledIndices.Add(number);
                            }
                        }
                    }
                }


                App.UpdateNodeServices(clientId, dataDirRoot, null, host, int.Parse(targetPort), rpcuser, rpcpassword, mine, stake, minetoaddress, disabledIndices);
            }
            catch (Exception e)
            {
                App.Logger.LogCritical($"Error processing config: {e.Message}. Config file: {configFilePath}. Please fix the errors or delete the config file to create a new one. Press any key to exit.");
                Console.ReadKey(true);
                Environment.Exit(1);
            }
            Logger.LogInformation($"Configuration loaded from {configFilePath}.");
        }
Ejemplo n.º 14
0
        public async Task NotingTestsAsync()
        {
            (_, IRPCClient rpc, Network network, Coordinator coordinator, _, _, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            Money   denomination                  = Money.Coins(1m);
            decimal coordinatorFeePercent         = 0.1m;
            int     anonymitySet                  = 2;
            int     connectionConfirmationTimeout = 1;
            bool    doesNoteBeforeBan             = true;
            CoordinatorRoundConfig roundConfig    = RegTestFixture.CreateRoundConfig(denomination, 140, 0.7, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1, 24, doesNoteBeforeBan, 11);

            coordinator.RoundConfig.UpdateOrDefault(roundConfig, toFile: true);
            coordinator.AbortAllRoundsInInputRegistration("");

            Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);

            var              registerRequests  = new List <(BitcoinWitPubKeyAddress changeOutputAddress, BlindedOutputWithNonceIndex blindedData, InputProofModel[] inputsProofs)>();
            AliceClient4     aliceClientBackup = null;
            CoordinatorRound round             = coordinator.GetCurrentInputRegisterableRoundOrDefault();

            for (int i = 0; i < roundConfig.AnonymitySet; i++)
            {
                BitcoinWitPubKeyAddress activeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
                BitcoinWitPubKeyAddress changeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
                Key inputKey = new Key();
                BitcoinWitPubKeyAddress inputAddress = inputKey.PubKey.GetSegwitAddress(network);

                var requester = new Requester();
                var nonce     = round.NonceProvider.GetNextNonce();

                var     blinded = new BlindedOutputWithNonceIndex(nonce.N, requester.BlindScript(round.MixingLevels.GetBaseLevel().SignerKey.PubKey, nonce.R, activeOutputAddress.ScriptPubKey));
                uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.BlindedOutput.ToBytes()));

                uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Coins(2));

                await rpc.GenerateAsync(1);

                Transaction transaction = await rpc.GetRawTransactionAsync(txHash);

                Coin     coin  = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
                OutPoint input = coin.Outpoint;

                InputProofModel inputProof = new InputProofModel {
                    Input = input, Proof = inputKey.SignCompact(blindedOutputScriptsHash)
                };
                InputProofModel[] inputsProofs = new InputProofModel[] { inputProof };
                registerRequests.Add((changeOutputAddress, blinded, inputsProofs));
                aliceClientBackup = await AliceClientBase.CreateNewAsync(round.RoundId, new[] { activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { requester }, network, changeOutputAddress, new[] { blinded }, inputsProofs, BackendHttpClient);
            }

            await WaitForTimeoutAsync();

            int bannedCount = coordinator.UtxoReferee.CountBanned(false);

            Assert.Equal(0, bannedCount);
            int notedCount = coordinator.UtxoReferee.CountBanned(true);

            Assert.Equal(anonymitySet, notedCount);

            round = coordinator.GetCurrentInputRegisterableRoundOrDefault();

            foreach (var registerRequest in registerRequests)
            {
                await AliceClientBase.CreateNewAsync(round.RoundId, aliceClientBackup.RegisteredAddresses, round.MixingLevels.GetAllLevels().Select(x => x.SignerKey.PubKey), aliceClientBackup.Requesters, network, registerRequest.changeOutputAddress, new[] { registerRequest.blindedData }, registerRequest.inputsProofs, BackendHttpClient);
            }

            await WaitForTimeoutAsync();

            bannedCount = coordinator.UtxoReferee.CountBanned(false);
            Assert.Equal(anonymitySet, bannedCount);
            notedCount = coordinator.UtxoReferee.CountBanned(true);
            Assert.Equal(anonymitySet, notedCount);
        }
Ejemplo n.º 15
0
        public async Task ColdCardMk1MockTestsAsync(Network network)
        {
            var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Coldcard));

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.Equal(HardwareWalletModels.Coldcard, entry.Model);
            string rawPath        = "\\\\\\\\?\\\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
            string normalizedPath = HwiParser.NormalizeRawDevicePath(rawPath);

            Assert.Equal(normalizedPath, entry.Path);
            Assert.Null(entry.NeedsPassphraseSent);
            Assert.Null(entry.NeedsPinSent);
            Assert.Null(entry.Error);
            Assert.Null(entry.Code);
            Assert.Equal("a3d0d797", entry.Fingerprint.ToString());
            Assert.True(entry.IsInitialized());

            var           deviceType  = entry.Model;
            var           devicePath  = entry.Path;
            HDFingerprint fingerprint = entry.Fingerprint.Value;

            var wipe = await Assert.ThrowsAsync <HwiException>(async() => await client.WipeAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Coldcard does not support wiping via software", wipe.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, wipe.ErrorCode);

            // ColdCard doesn't support it.
            var setup = await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Coldcard does not support software setup", setup.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, setup.ErrorCode);

            // ColdCard doesn't support it.
            var restore = await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Coldcard does not support restoring via software", restore.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, restore.ErrorCode);

            // ColdCard doesn't support it.
            var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Coldcard does not need a PIN sent from the host", promptpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, promptpin.ErrorCode);

            // ColdCard doesn't support it.
            var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            Assert.Equal("The Coldcard does not need a PIN sent from the host", sendpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, sendpin.ErrorCode);

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            var expecteXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
            var expecteXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");

            Assert.Equal(expecteXpub1, xpub1);
            Assert.Equal(expecteXpub2, xpub2);

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

            BitcoinAddress expectedAddress1;
            BitcoinAddress expectedAddress2;

            if (network == Network.Main)
            {
                expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
                expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
            }
            else if (network == Network.TestNet)
            {
                expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
                expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
            }
            else if (network == Network.RegTest)
            {
                expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
                expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
Ejemplo n.º 16
0
        public async Task BanningTestsAsync()
        {
            (_, IRPCClient rpc, Network network, Coordinator coordinator, _, _, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            Money   denomination                  = Money.Coins(0.1m);
            decimal coordinatorFeePercent         = 0.1m;
            int     anonymitySet                  = 3;
            int     connectionConfirmationTimeout = 120;
            var     roundConfig = RegTestFixture.CreateRoundConfig(denomination, 140, 0.7, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1, 24, true, 11);

            coordinator.RoundConfig.UpdateOrDefault(roundConfig, toFile: true);
            coordinator.AbortAllRoundsInInputRegistration("");

            await rpc.GenerateAsync(3);             // So to make sure we have enough money.

            Uri baseUri                = new Uri(RegTestFixture.BackendEndPoint);
            var fundingTxCount         = 0;
            var inputRegistrationUsers = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData)>();
            CoordinatorRound round     = null;

            for (int i = 0; i < roundConfig.AnonymitySet; i++)
            {
                var userInputData       = new List <(Key key, BitcoinWitPubKeyAddress inputAddress, uint256 txHash, Transaction tx, OutPoint input)>();
                var activeOutputAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
                var changeOutputAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
                round = coordinator.GetCurrentInputRegisterableRoundOrDefault();
                Requester requester = new Requester();
                var       nonce     = round.NonceProvider.GetNextNonce();

                var     blinded = new BlindedOutputWithNonceIndex(nonce.N, requester.BlindScript(round.MixingLevels.GetBaseLevel().SignerKey.PubKey, nonce.R, activeOutputAddress.ScriptPubKey));
                uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.BlindedOutput.ToBytes()));

                var inputProofModels  = new List <InputProofModel>();
                int numberOfInputs    = CryptoHelpers.RandomInt(1, 6);
                var receiveSatoshiSum = 0;
                for (int j = 0; j < numberOfInputs; j++)
                {
                    var key            = new Key();
                    var receiveSatoshi = CryptoHelpers.RandomInt(1000, 100000000);
                    receiveSatoshiSum += receiveSatoshi;
                    if (j == numberOfInputs - 1)
                    {
                        receiveSatoshi = 100000000;
                    }
                    BitcoinWitPubKeyAddress inputAddress = key.PubKey.GetSegwitAddress(network);
                    uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Satoshis(receiveSatoshi));

                    fundingTxCount++;
                    Assert.NotNull(txHash);
                    Transaction transaction = await rpc.GetRawTransactionAsync(txHash);

                    var coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();

                    OutPoint input      = coin.Outpoint;
                    var      inputProof = new InputProofModel {
                        Input = input, Proof = key.SignCompact(blindedOutputScriptsHash)
                    };
                    inputProofModels.Add(inputProof);

                    GetTxOutResponse getTxOutResponse = await rpc.GetTxOutAsync(input.Hash, (int)input.N, includeMempool : true);

                    // Check if inputs are unspent.
                    Assert.NotNull(getTxOutResponse);

                    userInputData.Add((key, inputAddress, txHash, transaction, input));
                }

                inputRegistrationUsers.Add((requester, blinded, activeOutputAddress, changeOutputAddress, inputProofModels, userInputData));
            }

            var mempool = await rpc.GetRawMempoolAsync();

            Assert.Equal(inputRegistrationUsers.SelectMany(x => x.userInputData).Count(), mempool.Length);

            while ((await rpc.GetRawMempoolAsync()).Length != 0)
            {
                await rpc.GenerateAsync(1);
            }

            var aliceClients = new List <Task <AliceClient4> >();

            foreach (var user in inputRegistrationUsers)
            {
                aliceClients.Add(AliceClientBase.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, BackendHttpClient));
            }

            long roundId = 0;
            var  users   = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient4 aliceClient, UnblindedSignature unblindedSignature)>();

            for (int i = 0; i < inputRegistrationUsers.Count; i++)
            {
                var user    = inputRegistrationUsers[i];
                var request = aliceClients[i];

                var aliceClient = await request;

                if (roundId == 0)
                {
                    roundId = aliceClient.RoundId;
                }
                else
                {
                    Assert.Equal(roundId, aliceClient.RoundId);
                }

                // Because it's valuetuple.
                users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
            }

            Assert.Equal(users.Count, roundConfig.AnonymitySet);

            var confirmationRequests = new List <Task <(RoundPhase currentPhase, IEnumerable <ActiveOutput>)> >();

            foreach (var user in users)
            {
                confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
            }

            RoundPhase roundPhase = RoundPhase.InputRegistration;
            int        k          = 0;

            foreach (var request in confirmationRequests)
            {
                var resp = await request;
                if (roundPhase == RoundPhase.InputRegistration)
                {
                    roundPhase = resp.currentPhase;
                }
                else
                {
                    Assert.Equal(roundPhase, resp.currentPhase);
                }

                var user = users.ElementAt(k);
                user.unblindedSignature = resp.Item2.First().Signature;
            }

            {
                var times = 0;
                while (!(await SatoshiClient.GetAllRoundStatesAsync()).All(x => x.Phase == RoundPhase.InputRegistration))
                {
                    await Task.Delay(100);

                    if (times > 50)                     // 5 sec, 3 should be enough
                    {
                        throw new TimeoutException("Not all rounds were in InputRegistration.");
                    }
                    times++;
                }
            }

            int bannedCount = coordinator.UtxoReferee.CountBanned(false);

            Assert.Equal(0, bannedCount);

            aliceClients.Clear();
            round = coordinator.GetCurrentInputRegisterableRoundOrDefault();
            foreach (var user in inputRegistrationUsers)
            {
                aliceClients.Add(AliceClientBase.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, BackendHttpClient));
            }

            roundId = 0;
            users   = new List <(Requester requester, BlindedOutputWithNonceIndex blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable <InputProofModel> inputProofModels, List <(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient4 aliceClient, UnblindedSignature unblindedSignature)>();
            for (int i = 0; i < inputRegistrationUsers.Count; i++)
            {
                var user    = inputRegistrationUsers[i];
                var request = aliceClients[i];

                var aliceClient = await request;
                if (roundId == 0)
                {
                    roundId = aliceClient.RoundId;
                }
                else
                {
                    Assert.Equal(roundId, aliceClient.RoundId);
                }

                // Because it's valuetuple.
                users.Add((user.requester, user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, null));
            }

            Assert.Equal(users.Count, roundConfig.AnonymitySet);

            confirmationRequests = new List <Task <(RoundPhase currentPhase, IEnumerable <ActiveOutput>)> >();

            foreach (var user in users)
            {
                confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
            }

            {
                var times = 0;
                while (!(await SatoshiClient.GetAllRoundStatesAsync()).All(x => x.Phase == RoundPhase.InputRegistration))
                {
                    await Task.Delay(100);

                    if (times > 50)                     // 5 sec, 3 should be enough
                    {
                        throw new TimeoutException("Not all rounds were in InputRegistration.");
                    }
                    times++;
                }
            }

            bannedCount = coordinator.UtxoReferee.CountBanned(false);
            Assert.True(bannedCount >= roundConfig.AnonymitySet);

            foreach (var aliceClient in aliceClients)
            {
                aliceClient?.Dispose();
            }
        }
Ejemplo n.º 17
0
        public async Task TrezorTMockTestsAsync(Network network)
        {
            var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Trezor_T));

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.Equal("webusb: 001:4", entry.Path);
            Assert.False(entry.NeedsPassphraseSent);
            Assert.False(entry.NeedsPinSent);
            Assert.NotNull(entry.Error);
            Assert.NotEmpty(entry.Error);
            Assert.Equal(HwiErrorCode.DeviceNotInitialized, entry.Code);
            Assert.False(entry.IsInitialized());
            Assert.Null(entry.Fingerprint);

            var deviceType = entry.Model;
            var devicePath = entry.Path;

            await client.WipeAsync(deviceType, devicePath, cts.Token);

            await client.SetupAsync(deviceType, devicePath, false, cts.Token);

            await client.RestoreAsync(deviceType, devicePath, false, cts.Token);

            // Trezor T doesn't support it.
            var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The PIN has already been sent to this device", promptpin.Message);
            Assert.Equal(HwiErrorCode.DeviceAlreadyUnlocked, promptpin.ErrorCode);

            var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            Assert.Equal("The PIN has already been sent to this device", sendpin.Message);
            Assert.Equal(HwiErrorCode.DeviceAlreadyUnlocked, sendpin.ErrorCode);

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            var expecteXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
            var expecteXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");

            Assert.Equal(expecteXpub1, xpub1);
            Assert.Equal(expecteXpub2, xpub2);

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

            BitcoinAddress expectedAddress1;
            BitcoinAddress expectedAddress2;

            if (network == Network.Main)
            {
                expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
                expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
            }
            else if (network == Network.TestNet)
            {
                expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
                expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
            }
            else if (network == Network.RegTest)
            {
                expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
                expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
Ejemplo n.º 18
0
        public async Task <IActionResult> TumbleAsync([FromBody] TumbleRequest request)
        {
            List <uint256> txIds = new List <uint256>();

            IsMixOngoing = true;
            try
            {
                if (request == null || request.From == null || request.To == null || request.RoundCount == 0)
                {
                    return(new ObjectResult(new FailureResponse {
                        Message = "Bad request", Details = ""
                    }));
                }

                var getFrom = Global.WalletWrapper.GetAccount(request.From, out SafeAccount fromAccount);
                if (getFrom != null)
                {
                    return(new ObjectResult(getFrom));
                }

                var getTo = Global.WalletWrapper.GetAccount(request.To, out SafeAccount toAccount);
                if (getTo != null)
                {
                    return(new ObjectResult(getTo));
                }

                CancelMixSource = new CancellationTokenSource();

                for (int i = 0; i < request.RoundCount; i++)
                {
                    IEnumerable <Script> unusedOutputs = await Global.WalletWrapper.WalletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, toAccount, HdPathType.NonHardened);

                    BitcoinAddress          activeOutput = unusedOutputs.RandomElement().GetDestinationAddress(Global.WalletWrapper.WalletJob.CurrentNetwork);            // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
                    BitcoinWitPubKeyAddress bech32       = new BitcoinWitPubKeyAddress(activeOutput.ToString(), Global.WalletWrapper.WalletJob.CurrentNetwork);

                    uint256 txid = null;
                    try
                    {
                        txid = await Global.WalletWrapper.WalletJob.CoinJoinService.TumbleAsync(fromAccount, bech32, CancelMixSource.Token);
                    }
                    catch (InvalidOperationException e) when(e.Message == "Round aborted")
                    {
                        // register for the next round
                        i--;
                        continue;
                    }

                    if (txid == null)
                    {
                        return(new ObjectResult(new FailureResponse {
                            Message = "Either the coordinator failed to propagate the latest transaction or it did not arrive to our mempool", Details = "Successful mixes:" + Environment.NewLine + string.Join(Environment.NewLine, txIds.Select(a => a.ToString()))
                        }));
                    }
                    txIds.Add(txid);
                    if (CancelMixSource.Token.IsCancellationRequested)
                    {
                        string details = "";
                        if (txIds.Count > 0)
                        {
                            details = "Successful mixes:" + Environment.NewLine + string.Join(Environment.NewLine, txIds.Select(a => a.ToString()));
                        }
                        return(new ObjectResult(new FailureResponse {
                            Message = "Mixing was cancelled", Details = details
                        }));
                    }
                }

                return(new ObjectResult(new TumbleResponse()
                {
                    Transactions = string.Join(Environment.NewLine, txIds.Select(a => a.ToString()))
                }));
            }
            catch (OperationCanceledException)
            {
                string details = "";
                if (txIds.Count > 0)
                {
                    details = "Successful mixes:" + Environment.NewLine + string.Join(Environment.NewLine, txIds.Select(a => a.ToString()));
                }
                return(new ObjectResult(new FailureResponse {
                    Message = "Mixing was cancelled", Details = details
                }));
            }
            catch (Exception ex)
            {
                string details = "";
                if (txIds.Count > 0)
                {
                    details = "Successful mixes:" + Environment.NewLine + string.Join(Environment.NewLine, txIds.Select(a => a.ToString()));
                }
                return(new ObjectResult(new FailureResponse {
                    Message = ex.Message, Details = details
                }));
            }
            finally
            {
                CancelMixSource?.Dispose();
                IsMixOngoing = false;
            }
        }
Ejemplo n.º 19
0
        public static Transaction SetupValidSegwitTransaction(Features.Wallet.Wallet wallet, string password, HdAddress spendingAddress, HdAddress destinationAddress, HdAddress changeAddress, Money amount, Money fee)
        {
            Script scriptPubKey = BitcoinWitPubKeyAddress.Create(destinationAddress.Bech32Address, wallet.Network).ScriptPubKey;

            return(SetupValidTransaction(wallet, password, spendingAddress, scriptPubKey, changeAddress, amount, fee));
        }
Ejemplo n.º 20
0
        private (TransactionBuildContext, HdAccount, Script) GetWithdrawalTransactionBuildContext(string receivingAddress, string walletName, Money amount, Money feeAmount, bool subtractFeeFromAmount)
        {
            Guard.NotEmpty(receivingAddress, nameof(receivingAddress));
            Guard.NotEmpty(walletName, nameof(walletName));
            Guard.NotNull(amount, nameof(amount));

            Wallet.Wallet wallet = this.GetWallet(walletName);

            // Get the cold staking account.
            HdAccount coldAccount = this.GetColdStakingAccount(wallet, true);

            if (coldAccount == null)
            {
                this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_DOES_NOT_EXIST]");
                throw new WalletException("The cold wallet account does not exist.");
            }

            // Prevent reusing cold stake addresses as regular withdrawal addresses.
            if (coldAccount.ExternalAddresses.Concat(coldAccount.InternalAddresses).Any(s => s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_COLD_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking cold wallet account.");
            }

            HdAccount hotAccount = this.GetColdStakingAccount(wallet, false);

            if (hotAccount != null && hotAccount.ExternalAddresses.Concat(hotAccount.InternalAddresses).Any(s => s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_HOT_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking hot wallet account.");
            }

            Script destination = null;

            if (BitcoinWitPubKeyAddress.IsValid(receivingAddress, this.network, out Exception _))
            {
                destination = new BitcoinWitPubKeyAddress(receivingAddress, wallet.Network).ScriptPubKey;
            }
            else
            {
                // Send the money to the receiving address.
                destination = BitcoinAddress.Create(receivingAddress, wallet.Network).ScriptPubKey;
            }

            // Create the transaction build context (used in BuildTransaction).
            var accountReference = new WalletAccountReference(walletName, coldAccount.Name);
            var context          = new TransactionBuildContext(wallet.Network)
            {
                AccountReference = accountReference,
                // Specify a dummy change address to prevent a change (internal) address from being created.
                // Will be changed after the transaction is built and before it is signed.
                ChangeAddress    = coldAccount.ExternalAddresses.First(),
                TransactionFee   = feeAmount,
                MinConfirmations = 0,
                Shuffle          = false,
                Sign             = false,
                Recipients       = new[] { new Recipient {
                                               Amount = amount, ScriptPubKey = destination, SubtractFeeFromAmount = subtractFeeFromAmount
                                           } }.ToList()
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            // Avoid script errors due to missing scriptSig.
            context.TransactionBuilder.StandardTransactionPolicy.ScriptVerify = null;

            return(context, coldAccount, destination);
        }
Ejemplo n.º 21
0
        public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase, Money feePerInputs = null, Money feePerOutputs = null)
        {
            using (await RoundSynchronizerLock.LockAsync())
            {
                try
                {
                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase change requested: {expectedPhase.ToString()}.");

                    if (Status == CcjRoundStatus.NotStarted)                     // So start the input registration phase
                    {
                        if (expectedPhase != CcjRoundPhase.InputRegistration)
                        {
                            return;
                        }

                        // Calculate fees.
                        if (feePerInputs is null || feePerOutputs is null)
                        {
                            (Money feePerInputs, Money feePerOutputs)fees = await CalculateFeesAsync(RpcClient, ConfirmationTarget);

                            FeePerInputs  = feePerInputs ?? fees.feePerInputs;
                            FeePerOutputs = feePerOutputs ?? fees.feePerOutputs;
                        }
                        else
                        {
                            FeePerInputs  = feePerInputs;
                            FeePerOutputs = feePerOutputs;
                        }

                        Status = CcjRoundStatus.Running;
                    }
                    else if (Status != CcjRoundStatus.Running)                     // Aborted or succeeded, swallow
                    {
                        return;
                    }
                    else if (Phase == CcjRoundPhase.InputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.ConnectionConfirmation)
                        {
                            return;
                        }

                        Phase = CcjRoundPhase.ConnectionConfirmation;
                    }
                    else if (Phase == CcjRoundPhase.ConnectionConfirmation)
                    {
                        if (expectedPhase != CcjRoundPhase.OutputRegistration)
                        {
                            return;
                        }

                        Phase = CcjRoundPhase.OutputRegistration;
                    }
                    else if (Phase == CcjRoundPhase.OutputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.Signing)
                        {
                            return;
                        }
                        Money newDenomination = CalculateNewDenomination();
                        var   transaction     = Network.Consensus.ConsensusFactory.CreateTransaction();

                        // 2. Add Bob outputs.
                        foreach (Bob bob in Bobs.Where(x => x.Level == MixingLevels.GetBaseLevel()))
                        {
                            transaction.Outputs.Add(newDenomination, bob.ActiveOutputAddress.ScriptPubKey);
                        }

                        // 2.1 newDenomination may differs from the Denomination at registration, so we may not be able to tinker with
                        // additional outputs.
                        bool tinkerWithAdditionalMixingLevels = CanUseAdditionalOutputs(newDenomination);

                        if (tinkerWithAdditionalMixingLevels)
                        {
                            foreach (MixingLevel level in MixingLevels.GetLevelsExceptBase())
                            {
                                IEnumerable <Bob> bobsOnThisLevel = Bobs.Where(x => x.Level == level);
                                if (bobsOnThisLevel.Count() <= 1)
                                {
                                    break;
                                }

                                foreach (Bob bob in bobsOnThisLevel)
                                {
                                    transaction.Outputs.Add(level.Denomination, bob.ActiveOutputAddress.ScriptPubKey);
                                }
                            }
                        }

                        BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(Network);
                        // 3. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign.
                        for (int i = 0; i < MixingLevels.Count(); i++)
                        {
                            var aliceCountInLevel = Alices.Count(x => i < x.BlindedOutputScripts.Length);
                            var missingBobCount   = aliceCountInLevel - Bobs.Count(x => x.Level == MixingLevels.GetLevel(i));
                            for (int j = 0; j < missingBobCount; j++)
                            {
                                var denomination = MixingLevels.GetLevel(i).Denomination;
                                transaction.Outputs.Add(denomination, coordinatorAddress);
                            }
                        }

                        // 4. Start building Coordinator fee.
                        Money coordinatorFeePerAlice = newDenomination.Percentage(CoordinatorFeePercent * Alices.Count);
                        Money coordinatorFee         = Alices.Count * coordinatorFeePerAlice;

                        if (tinkerWithAdditionalMixingLevels)
                        {
                            foreach (MixingLevel level in MixingLevels.GetLevelsExceptBase())
                            {
                                int bobsOnThisLevel = Bobs.Count(x => x.Level == level);
                                if (bobsOnThisLevel <= 1)
                                {
                                    break;
                                }

                                coordinatorFee += level.Denomination.Percentage(CoordinatorFeePercent * bobsOnThisLevel * bobsOnThisLevel);
                            }
                        }

                        // 5. Add the inputs and the changes of Alices.
                        var spentCoins = new List <Coin>();
                        foreach (Alice alice in Alices)
                        {
                            foreach (var input in alice.Inputs)
                            {
                                transaction.Inputs.Add(new TxIn(input.Outpoint));
                                spentCoins.Add(input);
                            }

                            Money changeAmount = alice.InputSum - alice.NetworkFeeToPay - newDenomination - coordinatorFeePerAlice;

                            if (tinkerWithAdditionalMixingLevels)
                            {
                                for (int i = 1; i < alice.BlindedOutputScripts.Length; i++)
                                {
                                    MixingLevel level           = MixingLevels.GetLevel(i);
                                    int         bobsOnThisLevel = Bobs.Count(x => x.Level == level);
                                    if (bobsOnThisLevel <= 1)
                                    {
                                        break;
                                    }

                                    changeAmount -= (level.Denomination + FeePerOutputs + (level.Denomination.Percentage(CoordinatorFeePercent * bobsOnThisLevel)));
                                }
                            }

                            if (changeAmount > Money.Zero)                                       // If the coordinator fee would make change amount to be negative or zero then no need to pay it.
                            {
                                Money minimumOutputAmount      = Money.Coins(0.0001m);           // If the change would be less than about $1 then add it to the coordinator.
                                Money onePercentOfDenomination = newDenomination.Percentage(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee.
                                Money minimumChangeAmount      = Math.Max(minimumOutputAmount, onePercentOfDenomination);
                                if (changeAmount < minimumChangeAmount)
                                {
                                    coordinatorFee += changeAmount;
                                }
                                else
                                {
                                    transaction.Outputs.Add(changeAmount, alice.ChangeOutputAddress.ScriptPubKey);
                                }
                            }
                            else
                            {
                                // Alice has no money enough to pay the coordinator fee then allow her to pay what she can.
                                coordinatorFee += changeAmount;
                            }
                        }

                        // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee.
                        if (coordinatorFee > Money.Coins(0.0003m))
                        {
                            transaction.Outputs.Add(coordinatorFee, coordinatorAddress);
                        }

                        // 7. Create the unsigned transaction.
                        var builder = Network.CreateTransactionBuilder();
                        UnsignedCoinJoin = builder
                                           .ContinueToBuild(transaction)
                                           .AddCoins(spentCoins)              // It makes sure the UnsignedCoinJoin goes through TransactionBuilder optimizations.
                                           .BuildTransaction(false);

                        // 8. Try optimize fees.
                        await OptimizeFeesAsync(spentCoins);

                        SignedCoinJoin = Transaction.Parse(UnsignedCoinJoin.ToHex(), Network);

                        Phase = CcjRoundPhase.Signing;
                    }
                    else
                    {
                        return;
                    }

                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase initialized: {expectedPhase.ToString()}.");
                }
Ejemplo n.º 22
0
        public async Task ColdCardKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect and initialize your Coldcard with the following seed phrase:
            // more maid moon upgrade layer alter marine screen benefit way cover alcohol
            // Run this test.
            // signtx request: refuse
            // signtx request: confirm
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Coldcard, entry.Model);
            Assert.NotNull(entry.Fingerprint);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            // ColdCard doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.WipeAsync(deviceType, devicePath, cts.Token));

            // ColdCard doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            // ColdCard doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            // ColdCard doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            // ColdCard doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            KeyPath   keyPath1 = KeyManager.GetAccountKeyPath(network);
            KeyPath   keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER: REFUSE
            var ex = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));

            Assert.Equal(HwiErrorCode.ActionCanceled, ex.ErrorCode);

            // USER: CONFIRM
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);

            // ColdCard just display the address. There is no confirm/refuse action.

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
Ejemplo n.º 23
0
        private TransactionBuildContext GetSetupTransactionBuildContext(IWalletTransactionHandler walletTransactionHandler,
                                                                        string coldWalletAddress, string hotWalletAddress, string walletName, string walletAccount,
                                                                        string walletPassword, Money amount, Money feeAmount, bool useSegwitChangeAddress = 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));

            Wallet.Wallet wallet = this.GetWallet(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 the 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;

            // Check if this is a segwit address
            if (coldAddress?.Bech32Address == coldWalletAddress || hotAddress?.Bech32Address == hotWalletAddress)
            {
                KeyId hotPubKeyHash  = new BitcoinWitPubKeyAddress(hotWalletAddress, wallet.Network).Hash.AsKeyId();
                KeyId coldPubKeyHash = new BitcoinWitPubKeyAddress(coldWalletAddress, wallet.Network).Hash.AsKeyId();
                destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash);
            }
            else
            {
                KeyId hotPubKeyHash  = new BitcoinPubKeyAddress(hotWalletAddress, wallet.Network).Hash;
                KeyId coldPubKeyHash = new BitcoinPubKeyAddress(coldWalletAddress, wallet.Network).Hash;
                destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash);
            }

            // Only normal accounts should be allowed.
            if (!this.GetAccounts(walletName).Any(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
                    }
                }
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            return(context);
        }
Ejemplo n.º 24
0
        public async Task TrezorTKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect and initialize your Trezor T with the following seed phrase:
            // more maid moon upgrade layer alter marine screen benefit way cover alcohol
            // Run this test.
            // displayaddress request: confirm 1 time
            // displayaddress request: confirm 1 time
            // signtx request: refuse 1 time
            // signtx request: Hold to confirm
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.NotNull(entry.Fingerprint);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            // Because of the Trezor T 2.3.5 firmware update,
            // we cannot use any longer the KeyManager.DefaultAccountKeyPath.
            KeyPath   keyPath1 = new("m/84h/0h/0h/0/0");
            KeyPath   keyPath2 = new("m/84h/0h/0h/0/1");
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);

            // USER SHOULD REFUSE ACTION
            var result = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));

            Assert.Equal(HwiErrorCode.ActionCanceled, result.ErrorCode);

            // USER: Hold to confirm
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);
        }
Ejemplo n.º 25
0
        public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase)
        {
            using (await RoundSynchronizerLock.LockAsync())
            {
                try
                {
                    Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase change requested: {expectedPhase.ToString()}.");

                    if (Status == CcjRoundStatus.NotStarted)                     // So start the input registration phase
                    {
                        if (expectedPhase != CcjRoundPhase.InputRegistration)
                        {
                            return;
                        }

                        // Calculate fees
                        var inputSizeInBytes  = (int)Math.Ceiling(((3 * Constants.P2wpkhInputSizeInBytes) + Constants.P2pkhInputSizeInBytes) / 4m);
                        var outputSizeInBytes = Constants.OutputSizeInBytes;
                        try
                        {
                            var estimateSmartFeeResponse = await RpcClient.EstimateSmartFeeAsync(ConfirmationTarget, EstimateSmartFeeMode.Conservative, simulateIfRegTest : true, tryOtherFeeRates : true);

                            if (estimateSmartFeeResponse is null)
                            {
                                throw new InvalidOperationException("FeeRate is not yet initialized");
                            }
                            var   feeRate     = estimateSmartFeeResponse.FeeRate;
                            Money feePerBytes = (feeRate.FeePerK / 1000);

                            // Make sure min relay fee (1000 sat) is hit.
                            FeePerInputs  = Math.Max(feePerBytes * inputSizeInBytes, new Money(500));
                            FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250));
                        }
                        catch (Exception ex)
                        {
                            // If fee hasn't been initialized once, fall back.
                            if (FeePerInputs is null || FeePerOutputs is null)
                            {
                                var feePerBytes = new Money(100);                                 // 100 satoshi per byte

                                // Make sure min relay fee (1000 sat) is hit.
                                FeePerInputs  = Math.Max(feePerBytes * inputSizeInBytes, new Money(500));
                                FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250));
                            }

                            Logger.LogError <CcjRound>(ex);
                        }

                        Status = CcjRoundStatus.Running;
                    }
                    else if (Status != CcjRoundStatus.Running)                     // Aborted or succeeded, swallow
                    {
                        return;
                    }
                    else if (Phase == CcjRoundPhase.InputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.ConnectionConfirmation)
                        {
                            return;
                        }

                        RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.Outpoint));

                        Phase = CcjRoundPhase.ConnectionConfirmation;
                    }
                    else if (Phase == CcjRoundPhase.ConnectionConfirmation)
                    {
                        if (expectedPhase != CcjRoundPhase.OutputRegistration)
                        {
                            return;
                        }

                        Phase = CcjRoundPhase.OutputRegistration;
                    }
                    else if (Phase == CcjRoundPhase.OutputRegistration)
                    {
                        if (expectedPhase != CcjRoundPhase.Signing)
                        {
                            return;
                        }

                        // Build CoinJoin

                        // 1. Set new denomination: minor optimization.
                        Money newDenomination = Alices.Min(x => x.OutputSumWithoutCoordinatorFeeAndDenomination);
                        var   transaction     = Network.Consensus.ConsensusFactory.CreateTransaction();

                        // 2. Add Bob outputs.
                        foreach (Bob bob in Bobs)
                        {
                            transaction.Outputs.Add(newDenomination, bob.ActiveOutputAddress.ScriptPubKey);
                        }

                        BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(Network);
                        // 3. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign.
                        for (int i = 0; i < Alices.Count - Bobs.Count; i++)
                        {
                            transaction.Outputs.Add(newDenomination, coordinatorAddress);
                        }

                        // 4. Start building Coordinator fee.
                        Money coordinatorFeePerAlice = newDenomination.Percentange(CoordinatorFeePercent) * Alices.Count;
                        Money coordinatorFee         = Alices.Count * coordinatorFeePerAlice;

                        // 5. Add the inputs and the changes of Alices.
                        var spentCoins = new List <Coin>();
                        foreach (Alice alice in Alices)
                        {
                            foreach (var input in alice.Inputs)
                            {
                                transaction.Inputs.Add(new TxIn(input.Outpoint));
                                spentCoins.Add(input);
                            }
                            Money changeAmount = alice.GetChangeAmount(newDenomination, coordinatorFeePerAlice);
                            if (changeAmount > Money.Zero)                                        // If the coordinator fee would make change amount to be negative or zero then no need to pay it.
                            {
                                Money minimumOutputAmount      = Money.Coins(0.0001m);            // If the change would be less than about $1 then add it to the coordinator.
                                Money onePercentOfDenomination = newDenomination.Percentange(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee.
                                Money minimumChangeAmount      = Math.Max(minimumOutputAmount, onePercentOfDenomination);
                                if (changeAmount < minimumChangeAmount)
                                {
                                    coordinatorFee += changeAmount;
                                }
                                else
                                {
                                    transaction.Outputs.Add(changeAmount, alice.ChangeOutputAddress.ScriptPubKey);
                                }
                            }
                            else
                            {
                                coordinatorFee -= coordinatorFeePerAlice;
                            }
                        }

                        // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee.
                        if (coordinatorFee > Money.Coins(0.0003m))
                        {
                            transaction.Outputs.Add(coordinatorFee, coordinatorAddress);
                        }

                        // 7. Create the unsigned transaction.
                        var builder = Network.CreateTransactionBuilder();
                        UnsignedCoinJoin = builder
                                           .ContinueToBuild(transaction)
                                           .AddCoins(spentCoins)              // It makes sure the UnsignedCoinJoin goes through TransactionBuilder optimizations.
                                           .BuildTransaction(false);

                        // 8. Try optimize fees.
                        try
                        {
                            // 8.1. Estimate the current FeeRate. Note, there are no signatures yet!
                            int   estimatedSigSizeBytes = UnsignedCoinJoin.Inputs.Count * Constants.P2wpkhInputSizeInBytes;
                            int   estimatedFinalTxSize  = UnsignedCoinJoin.GetSerializedSize() + estimatedSigSizeBytes;
                            Money fee = UnsignedCoinJoin.GetFee(spentCoins.ToArray());
                            // There is a currentFeeRate null check later.
                            FeeRate currentFeeRate = fee is null ? null : new FeeRate(fee, estimatedFinalTxSize);

                            // 8.2. Get the most optimal FeeRate.
                            EstimateSmartFeeResponse estimateSmartFeeResponse = await RpcClient.EstimateSmartFeeAsync(ConfirmationTarget, EstimateSmartFeeMode.Conservative, simulateIfRegTest : true, tryOtherFeeRates : true);

                            if (estimateSmartFeeResponse is null)
                            {
                                throw new InvalidOperationException("FeeRate is not yet initialized");
                            }
                            FeeRate optimalFeeRate = estimateSmartFeeResponse.FeeRate;

                            if (!(optimalFeeRate is null) && optimalFeeRate != FeeRate.Zero && !(currentFeeRate is null) && currentFeeRate != FeeRate.Zero) // This would be really strange if it'd happen.
                            {
                                var sanityFeeRate = new FeeRate(2m);                                                                                        // 2 s/b
                                optimalFeeRate = optimalFeeRate < sanityFeeRate ? sanityFeeRate : optimalFeeRate;
                                if (optimalFeeRate < currentFeeRate)
                                {
                                    // 8.2 If the fee can be lowered, lower it.
                                    // 8.2.1. How much fee can we save?
                                    Money feeShouldBePaid = new Money(estimatedFinalTxSize * (int)optimalFeeRate.SatoshiPerByte);
                                    Money toSave          = fee - feeShouldBePaid;

                                    // 8.2.2. Get the outputs to divide  the savings between.
                                    int   maxMixCount   = UnsignedCoinJoin.GetIndistinguishableOutputs().Max(x => x.count);
                                    Money bestMixAmount = UnsignedCoinJoin.GetIndistinguishableOutputs().Where(x => x.count == maxMixCount).Max(x => x.value);
                                    int   bestMixCount  = UnsignedCoinJoin.GetIndistinguishableOutputs().First(x => x.value == bestMixAmount).count;

                                    // 8.2.3. Get the savings per best mix outputs.
                                    long toSavePerBestMixOutputs = toSave.Satoshi / bestMixCount;

                                    // 8.2.4. Modify the best mix outputs in the transaction.
                                    if (toSavePerBestMixOutputs > 0)
                                    {
                                        foreach (TxOut output in UnsignedCoinJoin.Outputs.Where(x => x.Value == bestMixAmount))
                                        {
                                            output.Value += toSavePerBestMixOutputs;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                Logger.LogError <CcjRound>($"This is impossible. {nameof(optimalFeeRate)}: {optimalFeeRate}, {nameof(currentFeeRate)}: {currentFeeRate}.");
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogWarning <CcjRound>("Couldn't optimize fees. Fallback to normal fees.");
                            Logger.LogWarning <CcjRound>(ex);
                        }

                        SignedCoinJoin = Transaction.Parse(UnsignedCoinJoin.ToHex(), Network);

                        Phase = CcjRoundPhase.Signing;
                    }
Ejemplo n.º 26
0
        /// <summary>
        ///     Creates a cold staking withdrawal <see cref="Transaction" />.
        /// </summary>
        /// <remarks>
        ///     Cold staking withdrawal is performed on the wallet that is in the role of the cold staking cold wallet.
        /// </remarks>
        /// <param name="walletTransactionHandler">The wallet transaction handler used to build the transaction.</param>
        /// <param name="receivingAddress">The address that will receive the withdrawal.</param>
        /// <param name="walletName">The name of the wallet in the role of cold wallet.</param>
        /// <param name="walletPassword">The wallet password.</param>
        /// <param name="amount">The amount to remove from cold staking.</param>
        /// <param name="feeAmount">The fee to pay for cold staking transaction withdrawal.</param>
        /// <returns>The <see cref="Transaction" /> for cold staking withdrawal.</returns>
        /// <exception cref="WalletException">Thrown if the receiving address is in a cold staking account in this wallet.</exception>
        /// <exception cref="ArgumentNullException">Thrown if the receiving address is invalid.</exception>
        internal Transaction GetColdStakingWithdrawalTransaction(IWalletTransactionHandler walletTransactionHandler,
                                                                 string receivingAddress,
                                                                 string walletName, string walletPassword, Money amount, Money feeAmount)
        {
            Guard.NotEmpty(receivingAddress, nameof(receivingAddress));
            Guard.NotEmpty(walletName, nameof(walletName));
            Guard.NotNull(amount, nameof(amount));
            Guard.NotNull(feeAmount, nameof(feeAmount));

            var wallet = GetWalletByName(walletName);

            // Get the cold staking account.
            var coldAccount = GetColdStakingAccount(wallet, true);

            if (coldAccount == null)
            {
                this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_DOES_NOT_EXIST]");
                throw new WalletException("The cold wallet account does not exist.");
            }

            // Prevent reusing cold stake addresses as regular withdrawal addresses.
            if (coldAccount.ExternalAddresses.Concat(coldAccount.InternalAddresses).Any(s =>
                                                                                        s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_COLD_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking cold wallet account.");
            }

            var hotAccount = GetColdStakingAccount(wallet, false);

            if (hotAccount != null && hotAccount.ExternalAddresses.Concat(hotAccount.InternalAddresses)
                .Any(s => s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_HOT_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking hot wallet account.");
            }

            Script destination = null;

            if (BitcoinWitPubKeyAddress.IsValid(receivingAddress, this.network, out _))
            {
                destination = new BitcoinWitPubKeyAddress(receivingAddress, wallet.Network).ScriptPubKey;
            }
            else
            {
                // Send the money to the receiving address.
                destination = BitcoinAddress.Create(receivingAddress, wallet.Network).ScriptPubKey;
            }

            // Create the transaction build context (used in BuildTransaction).
            var accountReference = new WalletAccountReference(walletName, coldAccount.Name);
            var context          = new TransactionBuildContext(wallet.Network)
            {
                AccountReference = accountReference,
                // Specify a dummy change address to prevent a change (internal) address from being created.
                // Will be changed after the transacton is built and before it is signed.
                ChangeAddress    = coldAccount.ExternalAddresses.First(),
                TransactionFee   = feeAmount,
                MinConfirmations = 0,
                Shuffle          = false,
                Sign             = false,
                Recipients       = new[] { new Recipient {
                                               Amount = amount, ScriptPubKey = destination
                                           } }.ToList()
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            // Avoid script errors due to missing scriptSig.
            context.TransactionBuilder.StandardTransactionPolicy.ScriptVerify = null;

            // Build the transaction according to the settings recorded in the context.
            var transaction = walletTransactionHandler.BuildTransaction(context);

            // Map OutPoint to UnspentOutputReference.
            var mapOutPointToUnspentOutput = GetSpendableTransactionsInAccount(accountReference)
                                             .ToDictionary(unspent => unspent.ToOutPoint(), unspent => unspent);

            // Set the cold staking scriptPubKey on the change output.
            var changeOutput =
                transaction.Outputs.SingleOrDefault(output => output.ScriptPubKey != destination && output.Value != 0);

            if (changeOutput != null)
            {
                // Find the largest input.
                var largestInput = transaction.Inputs
                                   .OrderByDescending(input => mapOutPointToUnspentOutput[input.PrevOut].Transaction.Amount).Take(1)
                                   .Single();

                // Set the scriptPubKey of the change output to the scriptPubKey of the largest input.
                changeOutput.ScriptPubKey = mapOutPointToUnspentOutput[largestInput.PrevOut].Transaction.ScriptPubKey;
            }

            // Add keys for signing inputs. This takes time so only add keys for distinct addresses.
            foreach (var item in transaction.Inputs.Select(i => mapOutPointToUnspentOutput[i.PrevOut]).Distinct())
            {
                var prevscript = item.Transaction.ScriptPubKey;

                if (prevscript.IsScriptType(ScriptType.P2SH) || prevscript.IsScriptType(ScriptType.P2WSH))
                {
                    if (item.Address.RedeemScript == null)
                    {
                        throw new WalletException("Missing redeem script");
                    }

                    // Provide the redeem script to the builder
                    var scriptCoin = ScriptCoin.Create(this.network, item.ToOutPoint(),
                                                       new TxOut(item.Transaction.Amount, prevscript), item.Address.RedeemScript);
                    context.TransactionBuilder.AddCoins(scriptCoin);
                }

                context.TransactionBuilder.AddKeys(
                    wallet.GetExtendedPrivateKeyForAddress(walletPassword, item.Address));
            }

            // Sign the transaction.
            context.TransactionBuilder.SignTransactionInPlace(transaction);

            this.logger.LogTrace("(-):'{0}'", transaction.GetHash());
            return(transaction);
        }
Ejemplo n.º 27
0
        public IActionResult Output([FromBody] OutputRequest request)
        {
            var          roundId = Global.StateMachine.RoundId;
            TumblerPhase phase   = TumblerPhase.OutputRegistration;

            try
            {
                if (Global.StateMachine.Phase != TumblerPhase.OutputRegistration || !Global.StateMachine.AcceptRequest)
                {
                    return(new ObjectResult(new FailureResponse {
                        Message = "Wrong phase"
                    }));
                }

                if (string.IsNullOrWhiteSpace(request.Output))
                {
                    return(new BadRequestResult());
                }
                if (string.IsNullOrWhiteSpace(request.Signature))
                {
                    return(new BadRequestResult());
                }
                if (string.IsNullOrWhiteSpace(request.RoundHash))
                {
                    return(new BadRequestResult());
                }

                if (request.RoundHash != Global.StateMachine.RoundHash)
                {
                    throw new ArgumentException("Wrong round hash provided");
                }

                var output = new BitcoinWitPubKeyAddress(request.Output, expectedNetwork: Global.Config.Network);
                // if not already registered
                if (Global.StateMachine.Bobs.Any(x => x.Output == output))
                {
                    return(new ObjectResult(new SuccessResponse()));
                }

                if (Global.RsaKey.PubKey.Verify(HexHelpers.GetBytes(request.Signature), Encoding.UTF8.GetBytes(request.Output)))
                {
                    try
                    {
                        AssertPhase(roundId, phase);

                        Global.StateMachine.Bobs.Add(new Bob {
                            Output = output
                        });

                        return(new ObjectResult(new SuccessResponse()));
                    }
                    finally
                    {
                        if (Global.StateMachine.Alices.Count == Global.StateMachine.Bobs.Count)
                        {
                            Global.StateMachine.UpdatePhase(TumblerPhase.Signing);
                        }
                    }
                }
                else
                {
                    throw new ArgumentException("Bad output");
                }
            }
            catch (Exception ex)
            {
                return(new ObjectResult(new FailureResponse {
                    Message = ex.Message
                }));
            }
        }