Example #1
0
		public WalletCreation()
		{
			SignatureRequired = 1;
			RootKeys = new ExtPubKey[0];
			Network = Network.Main;
			DerivationPath = new KeyPath();
		}
Example #2
0
		public WalletCreation()
		{
			SignatureRequired = 1;
			RootKeys = new ExtPubKey[0];
			Network = Network.Main;
			DerivationPath = new KeyPath();
			Name = Guid.NewGuid().ToString();
		}
Example #3
0
		public WalletCreation()
		{
			SignatureRequired = 1;
			RootKeys = new ExtPubKey[0];
			Network = Network.Main;
			DerivationPath = new KeyPath();
			Name = Guid.NewGuid().ToString();
			PurgeConnectionOnFilterChange = true;
		}
Example #4
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);
        }
 public Transaction SignTransaction(KeyPath keyPath, ICoin[] signedCoins, Transaction[] parents, Transaction transaction, KeyPath changePath = null)
 {
     return(SignTransactionAsync(keyPath, signedCoins, parents, transaction, changePath).GetAwaiter().GetResult());
 }
Example #6
0
        public KeyManager(BitcoinEncryptedSecretNoEC encryptedSecret, byte[] chainCode, HDFingerprint?masterFingerprint, ExtPubKey extPubKey, bool?passwordVerified, int?minGapLimit, BlockchainState blockchainState, string filePath = null, KeyPath accountKeyPath = null)
        {
            HdPubKeys               = new List <HdPubKey>();
            HdPubKeyScriptBytes     = new List <byte[]>();
            ScriptHdPubKeyMap       = new Dictionary <Script, HdPubKey>();
            HdPubKeysLock           = new object();
            HdPubKeyScriptBytesLock = new object();
            ScriptHdPubKeyMapLock   = new object();
            BlockchainStateLock     = new object();

            EncryptedSecret   = encryptedSecret;
            ChainCode         = chainCode;
            MasterFingerprint = masterFingerprint;
            ExtPubKey         = Guard.NotNull(nameof(extPubKey), extPubKey);

            PasswordVerified = passwordVerified;
            SetMinGapLimit(minGapLimit);

            BlockchainState = blockchainState ?? new BlockchainState();
            AccountKeyPath  = accountKeyPath ?? DefaultAccountKeyPath;

            SetFilePath(filePath);
            ToFileLock = new object();
            ToFile();
        }
Example #7
0
        /// <inheritdoc />
        public async Task TumbleAsync(string originWalletName, string destinationWalletName, string originWalletPassword)
        {
            // Make sure it won't start new tumbling round if already started
            if (this.State == TumbleState.Tumbling)
            {
                this.logger.LogDebug("Tumbler is already running");
                throw new Exception("Tumbling is already running");
            }

            this.tumblingState.TumblerUri = new Uri(this.TumblerAddress);

            // Check if in initial block download
            if (!this.chain.IsDownloaded())
            {
                this.logger.LogDebug("Chain is still being downloaded: " + this.chain.Tip);
                throw new Exception("Chain is still being downloaded");
            }

            Wallet originWallet      = this.walletManager.GetWallet(originWalletName);
            Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName);

            // Check if password is valid before starting any cycles
            try
            {
                HdAddress tempAddress = originWallet.GetAccountsByCoinType(this.tumblingState.CoinType).First()
                                        .GetFirstUnusedReceivingAddress();
                originWallet.GetExtendedPrivateKeyForAddress(originWalletPassword, tempAddress);
            }
            catch (Exception)
            {
                this.logger.LogDebug("Origin wallet password appears to be invalid");
                throw new Exception("Origin wallet password appears to be invalid");
            }

            // Update the state and save
            this.tumblingState.DestinationWallet     = destinationWallet ?? throw new Exception($"Destination wallet not found. Have you created a wallet with name {destinationWalletName}?");
            this.tumblingState.DestinationWalletName = destinationWalletName;
            this.tumblingState.OriginWallet          = originWallet ?? throw new Exception($"Origin wallet not found. Have you created a wallet with name {originWalletName}?");
            this.tumblingState.OriginWalletName      = originWalletName;
            this.tumblingState.OriginWalletPassword  = originWalletPassword;

            var accounts = this.tumblingState.DestinationWallet.GetAccountsByCoinType(this.tumblingState.CoinType);
            // TODO: Possibly need to preserve destination account name in tumbling state. Default to first account for now
            string    accountName = accounts.First().Name;
            HdAccount destAccount = this.tumblingState.DestinationWallet.GetAccountByCoinType(accountName, this.tumblingState.CoinType);
            string    key         = destAccount.ExtendedPubKey;
            KeyPath   keyPath     = new KeyPath("0");

            // Stop and dispose onlymonitor
            if (this.broadcasterJob != null && this.broadcasterJob.Started)
            {
                await this.broadcasterJob.Stop().ConfigureAwait(false);
            }
            this.runtime?.Dispose();

            // Bypass Tor for integration tests
            FullNodeTumblerClientConfiguration config;

            if (this.TumblerAddress.Contains("127.0.0.1"))
            {
                config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false,
                                                                connectionTest: false, useProxy: false);
            }
            else
            {
                config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false,
                                                                connectionTest: false, useProxy: true);
            }

            this.runtime = await TumblerClientRuntime.FromConfigurationAsync(config).ConfigureAwait(false);

            // Check if origin wallet has a sufficient balance to begin tumbling at least 1 cycle
            if (!runtime.HasEnoughFundsForCycle(true))
            {
                this.logger.LogDebug("Insufficient funds in origin wallet");
                throw new Exception("Insufficient funds in origin wallet");
            }

            BitcoinExtPubKey extPubKey = new BitcoinExtPubKey(key, this.runtime.Network);

            if (key != null)
            {
                this.runtime.DestinationWallet =
                    new ClientDestinationWallet(extPubKey, keyPath, this.runtime.Repository, this.runtime.Network);
            }
            this.TumblerParameters = this.runtime.TumblerParameters;

            // Run onlymonitor mode
            this.broadcasterJob = this.runtime.CreateBroadcasterJob();
            this.broadcasterJob.Start();

            // Run tumbling mode
            this.stateMachine = new StateMachinesExecutor(this.runtime);
            this.stateMachine.Start();
        }
Example #8
0
		private ExtPubKey Derivate(int rootKeyIndex, KeyPath keyPath)
		{
			if(_ParentKeys == null)
			{
				_ParentKeys = _Parameters.RootKeys.Select(r => r.Derive(_Parameters.DerivationPath)).ToArray();
			}
			return _ParentKeys[rootKeyIndex].Derive(keyPath);
		}
Example #9
0
 private Script CreateMultiSig(KeyPath keyPath)
 {
     return(PayToMultiSigTemplate.Instance.GenerateScriptPubKey(_Parameters.SignatureRequired, _Parameters.RootKeys.Select((r, i) => Derivate(i, keyPath).PubKey).ToArray()));
 }
 public KeyPath GetWalletHDKeyPathForSegwitAddress(KeyPath rootKeyPath, string segwitAddress, Network network, int startAtIndex = 0, int maxAttempts = 100)
 {
     return(GetWalletHDKeyPathForSegwitAddressAsync(rootKeyPath, segwitAddress, network, startAtIndex, maxAttempts).GetAwaiter().GetResult());
 }
Example #11
0
		private void IncrementLastLoaded(KeyPath keyPath, int value)
		{
			if(!_PathStates.ContainsKey(keyPath))
				_PathStates.Add(keyPath, new PathState());
			_PathStates[keyPath].Loaded += value;
		}
 public GetWalletPubKeyResponse GetWalletPubKey(KeyPath keyPath)
 {
     return(GetWalletPubKeyAsync(keyPath).GetAwaiter().GetResult());
 }
 public GetWalletPubKeyResponse GetWalletPubKey(KeyPath keyPath, AddressType displayMode = AddressType.Legacy, bool display = false)
 {
     return(GetWalletPubKeyAsync(keyPath, displayMode, display).GetAwaiter().GetResult());
 }
        public async Task <Transaction> SignTransactionAsync(SignatureRequest[] signatureRequests, Transaction transaction, KeyPath changePath = null)
        {
            if (signatureRequests.Length == 0)
            {
                throw new ArgumentException("No signatureRequests is passed", "signatureRequests");
            }
            var segwitCoins = signatureRequests.Where(s => s.InputCoin.GetHashVersion() == HashVersion.Witness).Count();

            if (segwitCoins != signatureRequests.Count() && segwitCoins != 0)
            {
                throw new ArgumentException("Mixing segwit input with non segwit input is not supported", "signatureRequests");
            }

            var segwitMode = segwitCoins != 0;

            Dictionary <OutPoint, SignatureRequest> requests = signatureRequests
                                                               .ToDictionaryUnique(o => o.InputCoin.Outpoint);

            transaction = transaction.Clone();
            Dictionary <OutPoint, IndexedTxIn> inputsByOutpoint = transaction.Inputs.AsIndexedInputs().ToDictionary(i => i.PrevOut);
            Dictionary <OutPoint, ICoin>       coinsByOutpoint  = requests.ToDictionary(o => o.Key, o => o.Value.InputCoin);

            List <Task <TrustedInput> > trustedInputsAsync = new List <Task <TrustedInput> >();

            if (!segwitMode)
            {
                foreach (var sigRequest in signatureRequests)
                {
                    trustedInputsAsync.Add(GetTrustedInputAsync(sigRequest.InputTransaction, (int)sigRequest.InputCoin.Outpoint.N));
                }
            }

            var noPubKeyRequests = signatureRequests.Where(r => r.PubKey == null).ToArray();
            List <Task <GetWalletPubKeyResponse> > getPubKeys = new List <Task <GetWalletPubKeyResponse> >();

            foreach (var previousReq in noPubKeyRequests)
            {
                getPubKeys.Add(GetWalletPubKeyAsync(previousReq.KeyPath));
            }
            await Task.WhenAll(getPubKeys).ConfigureAwait(false);

            await Task.WhenAll(trustedInputsAsync).ConfigureAwait(false);

            for (int i = 0; i < noPubKeyRequests.Length; i++)
            {
                noPubKeyRequests[i].PubKey = getPubKeys[i].Result.UncompressedPublicKey.Compress();
            }

            var            trustedInputs  = trustedInputsAsync.Select(t => t.Result).ToDictionaryUnique(i => i.OutPoint);
            List <byte[]>  apdus          = new List <byte[]>();
            InputStartType inputStartType = segwitMode ? InputStartType.NewSegwit : InputStartType.New;


            bool segwitParsedOnce = false;

            for (int i = 0; i < signatureRequests.Length; i++)
            {
                var sigRequest = signatureRequests[i];
                var input      = inputsByOutpoint[sigRequest.InputCoin.Outpoint];
                apdus.AddRange(UntrustedHashTransactionInputStart(inputStartType, input, trustedInputs, coinsByOutpoint, segwitMode, segwitParsedOnce));
                inputStartType = InputStartType.Continue;
                if (!segwitMode || !segwitParsedOnce)
                {
                    apdus.AddRange(UntrustedHashTransactionInputFinalizeFull(changePath, transaction.Outputs));
                }
                changePath = null;                 //do not resubmit the changepath
                if (segwitMode && !segwitParsedOnce)
                {
                    segwitParsedOnce = true;
                    i--;                     //pass once more
                    continue;
                }
                apdus.Add(UntrustedHashSign(sigRequest.KeyPath, null, transaction.LockTime, SigHash.All));
            }
            var responses = await Exchange(apdus.ToArray()).ConfigureAwait(false);

            foreach (var response in responses)
            {
                if (response.Response.Length > 10)                //Probably a signature
                {
                    response.Response[0] = 0x30;
                }
            }
            var signatures = responses.Where(p => TransactionSignature.IsValid(p.Response)).Select(p => new TransactionSignature(p.Response)).ToArray();

            if (signatureRequests.Length != signatures.Length)
            {
                throw new LedgerWalletException("failed to sign some inputs");
            }
            int sigIndex = 0;

            TransactionBuilder builder = new TransactionBuilder();

            foreach (var sigRequest in signatureRequests)
            {
                var input = inputsByOutpoint[sigRequest.InputCoin.Outpoint];
                if (input == null)
                {
                    continue;
                }
                builder.AddCoins(sigRequest.InputCoin);
                builder.AddKnownSignature(sigRequest.PubKey, signatures[sigIndex]);
                sigIndex++;
            }
            builder.SignTransactionInPlace(transaction);

            sigIndex = 0;
            foreach (var sigRequest in signatureRequests)
            {
                var input = inputsByOutpoint[sigRequest.InputCoin.Outpoint];
                if (input == null)
                {
                    continue;
                }
                sigRequest.Signature = signatures[sigIndex];
                if (!sigRequest.PubKey.Verify(transaction.GetSignatureHash(sigRequest.InputCoin, sigRequest.Signature.SigHash), sigRequest.Signature.Signature))
                {
                    foreach (var sigRequest2 in signatureRequests)
                    {
                        sigRequest2.Signature = null;
                    }
                    return(null);
                }
                sigIndex++;
            }

            return(transaction);
        }
 public Transaction SignTransaction(SignatureRequest[] signatureRequests, Transaction transaction, KeyPath changePath = null)
 {
     return(SignTransactionAsync(signatureRequests, transaction, changePath).GetAwaiter().GetResult());
 }
        public Task <Transaction> SignTransactionAsync(KeyPath keyPath, ICoin[] signedCoins, Transaction[] parents, Transaction transaction, KeyPath changePath = null)
        {
            List <SignatureRequest> requests = new List <SignatureRequest>();

            foreach (var c in signedCoins)
            {
                var tx = parents.FirstOrDefault(t => t.GetHash() == c.Outpoint.Hash);
                if (tx != null)
                {
                    requests.Add(new SignatureRequest()
                    {
                        InputCoin        = c,
                        InputTransaction = tx,
                        KeyPath          = keyPath
                    });
                }
            }
            return(SignTransactionAsync(requests.ToArray(), transaction, changePath: changePath));
        }
Example #17
0
		private void AddKnown(KeyPath keyPath, Tracker tracker, bool isInternal)
		{
			if(_Parameters.UseP2SH)
			{
				var script = GetScriptPubKey(keyPath, true);
				_KnownScripts.Add(script.Hash.ScriptPubKey, keyPath);
				tracker.Add(script, true, isInternal, wallet: Name);
			}
			else
			{
				var script = GetScriptPubKey(keyPath, false);
				_KnownScripts.Add(script, keyPath);
				tracker.Add(script, false, isInternal, wallet: Name);
			}
		}
Example #18
0
 public abstract DerivationStrategyBase GetLineFor(KeyPath keyPath);
Example #19
0
		private void IncrementCurrentIndex(KeyPath keyPath)
		{
			if(!_PathStates.ContainsKey(keyPath))
				_PathStates.Add(keyPath, new PathState());
			_PathStates[keyPath].Next++;
		}
Example #20
0
 public abstract Derivation Derive(KeyPath keyPath);
Example #21
0
		public Script GetScriptPubKey(KeyPath keyPath, bool redeem)
		{
			if(!_Parameters.UseP2SH && redeem)
				throw new ArgumentException("The wallet is not P2SH so there is no redeem script", "redeem");

			Script scriptPubKey = null;
			if(_Parameters.RootKeys.Length == 1)
			{
				var pubkey = Derivate(0, keyPath).PubKey;
				scriptPubKey = _Parameters.UseP2SH ? pubkey.ScriptPubKey : pubkey.Hash.ScriptPubKey;
			}
			else
			{
				scriptPubKey = CreateMultiSig(keyPath);
			}
			return redeem || !_Parameters.UseP2SH ? scriptPubKey : scriptPubKey.Hash.ScriptPubKey;
		}
Example #22
0
 public KeyPath GetNonHardenedKeyPath()
 {
     return(_nonHardenedKeyPath ?? (_nonHardenedKeyPath = new KeyPath(FullKeyPath[3], FullKeyPath[4])));
 }
Example #23
0
        private static PSBT BuildPsbt(Network network, HDFingerprint fingerprint, ExtPubKey xpub, KeyPath xpubKeyPath)
        {
            var deriveSendFromKeyPath = new KeyPath("1/0");
            var deriveSendToKeyPath   = new KeyPath("0/0");

            KeyPath sendFromKeyPath = xpubKeyPath.Derive(deriveSendFromKeyPath);
            KeyPath sendToKeyPath   = xpubKeyPath.Derive(deriveSendToKeyPath);

            PubKey sendFromPubKey = xpub.Derive(deriveSendFromKeyPath).PubKey;
            PubKey sendToPubKey   = xpub.Derive(deriveSendToKeyPath).PubKey;

            BitcoinAddress sendFromAddress = sendFromPubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            BitcoinAddress sendToAddress   = sendToPubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            TransactionBuilder builder = network.CreateTransactionBuilder();

            builder = builder.AddCoins(new Coin(uint256.One, 0, Money.Coins(1), sendFromAddress.ScriptPubKey));
            builder.Send(sendToAddress.ScriptPubKey, Money.Coins(0.99999m));
            PSBT psbt = builder
                        .SendFees(Money.Coins(0.00001m))
                        .BuildPSBT(false);

            var rootKeyPath1 = new RootedKeyPath(fingerprint, sendFromKeyPath);
            var rootKeyPath2 = new RootedKeyPath(fingerprint, sendToKeyPath);

            psbt.AddKeyPath(sendFromPubKey, rootKeyPath1, sendFromAddress.ScriptPubKey);
            psbt.AddKeyPath(sendToPubKey, rootKeyPath2, sendToAddress.ScriptPubKey);
            return(psbt);
        }
Example #24
0
        public string GetRegFormat(HiveTypeEnum hiveType)
        {
            var sb = new StringBuilder();

            string keyBase;

            switch (hiveType)
            {
            case HiveTypeEnum.NtUser:
                keyBase = "HKEY_CURRENT_USER";
                break;

            case HiveTypeEnum.Sam:
                keyBase = "HKEY_CURRENT_USER\\SAM";
                break;

            case HiveTypeEnum.Security:
                keyBase = "HKEY_CURRENT_USER\\SECURITY";
                break;

            case HiveTypeEnum.Software:
                keyBase = "HKEY_CURRENT_USER\\SOFTWARE";
                break;

            case HiveTypeEnum.System:
                keyBase = "HKEY_CURRENT_USER\\SYSTEM";
                break;

            case HiveTypeEnum.UsrClass:
                keyBase = "HKEY_CLASSES_ROOT";
                break;

            case HiveTypeEnum.Components:
                keyBase = "HKEY_CURRENT_USER\\COMPONENTS";
                break;

            default:
                keyBase = "HKEY_CURRENT_USER\\UNKNOWN_BASEPATH";
                break;
            }

            var keyNames          = KeyPath.Split('\\');
            var normalizedKeyPath = string.Join("\\", keyNames.Skip(1));

            var keyName = normalizedKeyPath.Length > 0
                ? $"[{keyBase}\\{normalizedKeyPath}]"
                : $"[{keyBase}]";

            sb.AppendLine();
            sb.AppendLine(keyName);
            sb.AppendLine($";Last write timestamp {LastWriteTime.Value.UtcDateTime.ToString("o")}");
            //sb.AppendLine($";Last write timestamp {LastWriteTime.Value.UtcDateTime.ToString("o")}");

            foreach (var keyValue in Values)
            {
                var keyNameOut = keyValue.ValueName;
                if (keyNameOut.ToLowerInvariant() == "(default)")
                {
                    keyNameOut = "@";
                }
                else
                {
                    keyNameOut = keyNameOut.Replace("\\", "\\\\");
                    keyNameOut = $"\"{keyNameOut.Replace("\"", "\\\"")}\"";
                }

                var keyValueOut = "";

                switch (keyValue.VKRecord.DataType)
                {
                case VKCellRecord.DataTypeEnum.RegSz:
                    keyValueOut = $"\"{keyValue.ValueData.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
                    break;

                case VKCellRecord.DataTypeEnum.RegNone:
                case VKCellRecord.DataTypeEnum.RegDwordBigEndian:
                case VKCellRecord.DataTypeEnum.RegFullResourceDescription:
                case VKCellRecord.DataTypeEnum.RegMultiSz:
                case VKCellRecord.DataTypeEnum.RegQword:
                case VKCellRecord.DataTypeEnum.RegFileTime:
                case VKCellRecord.DataTypeEnum.RegLink:
                case VKCellRecord.DataTypeEnum.RegResourceRequirementsList:
                case VKCellRecord.DataTypeEnum.RegExpandSz:

                    var prefix = $"hex({(int) keyValue.VKRecord.DataType:x}):";

                    keyValueOut =
                        $"{prefix}{BitConverter.ToString(keyValue.ValueDataRaw).Replace("-", ",")}".ToLowerInvariant();

                    if (keyValueOut.Length + prefix.Length + keyNameOut.Length > 76)
                    {
                        keyValueOut =
                            $"{prefix}{FormatBinaryValueData(keyValue.ValueDataRaw, keyNameOut.Length, prefix.Length)}";
                    }

                    break;

                case VKCellRecord.DataTypeEnum.RegDword:
                    keyValueOut =
                        $"dword:{BitConverter.ToInt32(keyValue.ValueDataRaw, 0):X8}"
                        .ToLowerInvariant();
                    break;

                case VKCellRecord.DataTypeEnum.RegBinary:

                    keyValueOut =
                        $"hex:{BitConverter.ToString(keyValue.ValueDataRaw).Replace("-", ",")}"
                        .ToLowerInvariant();

                    if (keyValueOut.Length + 5 + keyNameOut.Length > 76)
                    {
                        keyValueOut = $"hex:{FormatBinaryValueData(keyValue.ValueDataRaw, keyNameOut.Length, 5)}";
                    }

                    break;
                }

                sb.AppendLine($"{keyNameOut}={keyValueOut}");
            }

            return(sb.ToString().TrimEnd());
        }
Example #25
0
        public static KeyManager Recover(Mnemonic mnemonic, string password, string filePath = null, KeyPath accountKeyPath = null, int minGapLimit = AbsoluteMinGapLimit)
        {
            Guard.NotNull(nameof(mnemonic), mnemonic);
            if (password is null)
            {
                password = "";
            }

            ExtKey extKey          = mnemonic.DeriveExtKey(password);
            var    encryptedSecret = extKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main);

            HDFingerprint masterFingerprint = extKey.Neuter().PubKey.GetHDFingerPrint();

            KeyPath   keyPath   = accountKeyPath ?? DefaultAccountKeyPath;
            ExtPubKey extPubKey = extKey.Derive(keyPath).Neuter();

            return(new KeyManager(encryptedSecret, extKey.ChainCode, masterFingerprint, extPubKey, true, minGapLimit, new BlockchainState(), filePath, keyPath));
        }
 public override DerivationStrategyBase GetLineFor(KeyPath keyPath)
 {
     return(new P2WSHDerivationStrategy(Inner.GetLineFor(keyPath)));
 }
Example #27
0
        public KeyManager(BitcoinEncryptedSecretNoEC encryptedSecret, byte[] chainCode, string password, int minGapLimit = AbsoluteMinGapLimit, string filePath = null, KeyPath accountKeyPath = null)
        {
            HdPubKeys               = new List <HdPubKey>();
            HdPubKeyScriptBytes     = new List <byte[]>();
            ScriptHdPubKeyMap       = new Dictionary <Script, HdPubKey>();
            HdPubKeysLock           = new object();
            HdPubKeyScriptBytesLock = new object();
            ScriptHdPubKeyMapLock   = new object();
            BlockchainState         = new BlockchainState();
            BlockchainStateLock     = new object();

            if (password is null)
            {
                password = "";
            }

            SetMinGapLimit(minGapLimit);

            EncryptedSecret = Guard.NotNull(nameof(encryptedSecret), encryptedSecret);
            ChainCode       = Guard.NotNull(nameof(chainCode), chainCode);
            var extKey = new ExtKey(encryptedSecret.GetKey(password), chainCode);

            MasterFingerprint = extKey.Neuter().PubKey.GetHDFingerPrint();
            AccountKeyPath    = accountKeyPath ?? DefaultAccountKeyPath;
            ExtPubKey         = extKey.Derive(AccountKeyPath).Neuter();

            SetFilePath(filePath);
            ToFileLock = new object();
            ToFile();
        }
Example #28
0
		private void LoadPool(KeyPath keyPath)
		{
			var lastLoaded = GetLastLoaded(keyPath);
			var isInternal = IsInternal(keyPath);
			var tracker = Tracker;
			for(int i = lastLoaded ; i < lastLoaded + _KeyPoolSize ; i++)
			{
				var childPath = keyPath.Derive(i, false);
				AddKnown(childPath, tracker, isInternal);
			}
			IncrementLastLoaded(keyPath, _KeyPoolSize);
		}
Example #29
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.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: 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);
        }
Example #30
0
		private void AddKnown(KeyPath keyPath)
		{
			AddKnown(keyPath, Tracker, IsInternal(keyPath));
		}
Example #31
0
        /// <summary>
        ///  Describe ext public key
        /// </summary>
        /// <param name="extPubKey"></param>
        /// <param name="network"></param>
        /// <param name="keyPath">The root of the keypath to follow</param>
        /// <returns></returns>
        public static ScanTxoutPubkey ExtPubKey(ExtPubKey extPubKey, Network network, KeyPath keyPath)
        {
            if (extPubKey == null)
            {
                throw new ArgumentNullException(nameof(extPubKey));
            }
            if (network == null)
            {
                throw new ArgumentNullException(nameof(network));
            }

            return(ExtPubKey(extPubKey.GetWif(network), keyPath));
        }
Example #32
0
        public void CanRecover()
        {
            string password    = "******";
            var    manager     = KeyManager.CreateNew(out Mnemonic mnemonic, password);
            var    sameManager = KeyManager.Recover(mnemonic, password);

            Assert.Equal(manager.ChainCode, sameManager.ChainCode);
            Assert.Equal(manager.EncryptedSecret, sameManager.EncryptedSecret);
            Assert.Equal(manager.ExtPubKey, sameManager.ExtPubKey);

            var differentManager = KeyManager.Recover(mnemonic, "differentPassword", null, KeyPath.Parse("m/999'/999'/999'"), 55);

            Assert.NotEqual(manager.ChainCode, differentManager.ChainCode);
            Assert.NotEqual(manager.EncryptedSecret, differentManager.EncryptedSecret);
            Assert.NotEqual(manager.ExtPubKey, differentManager.ExtPubKey);

            differentManager.AssertCleanKeysIndexed();
            var newKey = differentManager.GenerateNewKey("some-label", KeyState.Clean, true, false);

            Assert.Equal(newKey.Index, differentManager.MinGapLimit);
            Assert.Equal("999'/999'/999'/1/55", newKey.FullKeyPath.ToString());
        }
Example #33
0
		private bool IsInternal(KeyPath keyPath)
		{
			return _Parameters.DerivationPath.Derive(1, false) == keyPath;
		}
Example #34
0
 internal override void ResolveChildKeyPath(KeyPath keyPath, int depth, List <KeyPath> accumulator, KeyPath currentPartialKeyPath)
 {
     for (int i = 0; i < _layers.Count; i++)
     {
         _layers[i].ResolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath);
     }
 }
Example #35
0
        public async Task <IActionResult> UpdateWallet(WalletSetupViewModel vm)
        {
            var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);

            if (checkResult != null)
            {
                return(checkResult);
            }

            vm.Network     = network;
            vm.RootKeyPath = network.GetRootKeyPath();
            DerivationSchemeSettings strategy = null;

            var wallet = _WalletProvider.GetWallet(network);

            if (wallet == null)
            {
                return(NotFound());
            }

            if (!string.IsNullOrEmpty(vm.Config))
            {
                if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }

            if (vm.WalletFile != null)
            {
                if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.WalletFile), "Wallet file was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }
            else if (!string.IsNullOrEmpty(vm.WalletFileContent))
            {
                if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.WalletFileContent), "QR import was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }
            else if (!string.IsNullOrEmpty(vm.DerivationScheme))
            {
                try
                {
                    var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
                    if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
                    {
                        var accountKey = string.IsNullOrEmpty(vm.AccountKey)
                            ? null
                            : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
                        if (accountKey != null)
                        {
                            var accountSettings =
                                newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
                            if (accountSettings != null)
                            {
                                accountSettings.AccountKeyPath =
                                    vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
                                accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
                                    ? (HDFingerprint?)null
                                    : new HDFingerprint(
                                    NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
                            }
                        }

                        strategy            = newStrategy;
                        strategy.Source     = vm.Source;
                        vm.DerivationScheme = strategy.AccountDerivation.ToString();
                    }
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid wallet format");
                    return(View(vm.ViewName, vm));
                }
            }
            else
            {
                ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key");
                return(View(vm.ViewName, vm));
            }

            var oldConfig = vm.Config;

            vm.Config = strategy?.ToJson();
            var             configChanged   = oldConfig != vm.Config;
            PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
            var             storeBlob       = store.GetStoreBlob();
            var             willBeExcluded  = !vm.Enabled;

            var showAddress = // Show addresses if:
                              // - If the user is testing the hint address in confirmation screen
                              (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
                              // - The user is clicking on continue after changing the config
                              (!vm.Confirmation && configChanged);

            showAddress = showAddress && strategy != null;
            if (!showAddress)
            {
                try
                {
                    if (strategy != null)
                    {
                        await wallet.TrackAsync(strategy.AccountDerivation);
                    }
                    store.SetSupportedPaymentMethod(paymentMethodId, strategy);
                    storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
                    storeBlob.Hints.Wallet = false;
                    store.SetStoreBlob(storeBlob);
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme");
                    return(View(vm.ViewName, vm));
                }

                await _Repo.UpdateStore(store);

                _EventAggregator.Publish(new WalletChangedEvent {
                    WalletId = new WalletId(vm.StoreId, vm.CryptoCode)
                });

                TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} have been updated.";

                // This is success case when derivation scheme is added to the store
                return(RedirectToAction(nameof(UpdateStore), new { storeId = vm.StoreId }));
            }

            if (!string.IsNullOrEmpty(vm.HintAddress))
            {
                BitcoinAddress address;
                try
                {
                    address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork);
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address");
                    return(ConfirmAddresses(vm, strategy));
                }

                try
                {
                    var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network);
                    if (newStrategy.AccountDerivation != strategy.AccountDerivation)
                    {
                        strategy.AccountDerivation = newStrategy.AccountDerivation;
                        strategy.AccountOriginal   = null;
                    }
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address. Are you sure the wallet and address provided are correct and from the same source?");
                    return(ConfirmAddresses(vm, strategy));
                }

                vm.HintAddress = "";
                TempData[WellKnownTempData.SuccessMessage] =
                    "Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
                ModelState.Remove(nameof(vm.HintAddress));
                ModelState.Remove(nameof(vm.DerivationScheme));
            }

            return(ConfirmAddresses(vm, strategy));
        }
Example #36
0
 public void ResolveKeyPath(KeyPath keyPath, int depth, List <KeyPath> accumulator, KeyPath currentPartialKeyPath)
 {
     MiscUtils.ResolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this);
 }
Example #37
0
		public Script GetNextScriptPubKey(KeyPath keyPath)
		{
			AssertGroupAffected();
			Script result;
			lock(cs)
			{
				var currentIndex = GetNextIndex(keyPath);
				KeyPath childPath = keyPath.Derive(currentIndex, false);

				result = GetScriptPubKey(childPath, false);
				IncrementCurrentIndex(keyPath);

				if(_KeyPoolSize != 0)
				{
					var created = (double)(currentIndex + 1) / (double)GetLastLoaded(keyPath);
					if(created > 0.9)
					{
						LoadPool(keyPath);
						RefreshFilter();
					}
				}
				else
				{
					AddKnown(childPath);
					if(_Group != null)
					{
						foreach(var node in _Group.ConnectedNodes)
						{
							var tracker = node.Behaviors.Find<TrackerBehavior>();
							if(tracker == null)
								continue;
							foreach(var data in result.ToOps().Select(o => o.PushData).Where(o => o != null))
							{
								tracker.SendMessageAsync(new FilterAddPayload(data));
							}
						}
					}
				}
			}
			return result;
		}
Example #38
0
        public async Task<IActionResult> Submit(string cryptoCode,
            long? maxadditionalfeecontribution,
            int? additionalfeeoutputindex,
            decimal minfeerate = -1.0m,
            bool disableoutputsubstitution = false,
            int v = 1)
        {
            var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
            if (network == null)
                return NotFound();

            if (v != 1)
            {
                return BadRequest(new JObject
                {
                    new JProperty("errorCode", "version-unsupported"),
                    new JProperty("supported", new JArray(1)),
                    new JProperty("message", "This version of payjoin is not supported.")
                });
            }

            await using var ctx = new PayjoinReceiverContext(_invoiceRepository, _explorerClientProvider.GetExplorerClient(network), _payJoinRepository);
            ObjectResult CreatePayjoinErrorAndLog(int httpCode, PayjoinReceiverWellknownErrors err, string debug)
            {
                ctx.Logs.Write($"Payjoin error: {debug}", InvoiceEventData.EventSeverity.Error);
                return StatusCode(httpCode, CreatePayjoinError(err, debug));
            }
            var explorer = _explorerClientProvider.GetExplorerClient(network);
            if (Request.ContentLength is long length)
            {
                if (length > 1_000_000)
                    return this.StatusCode(413,
                        CreatePayjoinError("payload-too-large", "The transaction is too big to be processed"));
            }
            else
            {
                return StatusCode(411,
                    CreatePayjoinError("missing-content-length",
                        "The http header Content-Length should be filled"));
            }

            string rawBody;
            using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
            {
                rawBody = (await reader.ReadToEndAsync()) ?? string.Empty;
            }

            FeeRate originalFeeRate = null;
            bool psbtFormat = true;

            if (PSBT.TryParse(rawBody, network.NBitcoinNetwork, out var psbt))
            {
                if (!psbt.IsAllFinalized())
                    return BadRequest(CreatePayjoinError("original-psbt-rejected", "The PSBT should be finalized"));
                ctx.OriginalTransaction = psbt.ExtractTransaction();
            }
            // BTCPay Server implementation support a transaction instead of PSBT
            else
            {
                psbtFormat = false;
                if (!Transaction.TryParse(rawBody, network.NBitcoinNetwork, out var tx))
                    return BadRequest(CreatePayjoinError("original-psbt-rejected", "invalid transaction or psbt"));
                ctx.OriginalTransaction = tx;
                psbt = PSBT.FromTransaction(tx, network.NBitcoinNetwork);
                psbt = (await explorer.UpdatePSBTAsync(new UpdatePSBTRequest() { PSBT = psbt })).PSBT;
                for (int i = 0; i < tx.Inputs.Count; i++)
                {
                    psbt.Inputs[i].FinalScriptSig = tx.Inputs[i].ScriptSig;
                    psbt.Inputs[i].FinalScriptWitness = tx.Inputs[i].WitScript;
                }
            }

            FeeRate senderMinFeeRate = minfeerate >= 0.0m ? new FeeRate(minfeerate) : null;
            Money allowedSenderFeeContribution = Money.Satoshis(maxadditionalfeecontribution is long t && t >= 0 ? t : 0);

            var sendersInputType = psbt.GetInputsScriptPubKeyType();
            if (psbt.CheckSanity() is var errors && errors.Count != 0)
            {
                return BadRequest(CreatePayjoinError("original-psbt-rejected", $"This PSBT is insane ({errors[0]})"));
            }
            if (!psbt.TryGetEstimatedFeeRate(out originalFeeRate))
            {
                return BadRequest(CreatePayjoinError("original-psbt-rejected",
                    "You need to provide Witness UTXO information to the PSBT."));
            }

            // This is actually not a mandatory check, but we don't want implementers
            // to leak global xpubs
            if (psbt.GlobalXPubs.Any())
            {
                return BadRequest(CreatePayjoinError("original-psbt-rejected",
                    "GlobalXPubs should not be included in the PSBT"));
            }

            if (psbt.Outputs.Any(o => o.HDKeyPaths.Count != 0) || psbt.Inputs.Any(o => o.HDKeyPaths.Count != 0))
            {
                return BadRequest(CreatePayjoinError("original-psbt-rejected",
                    "Keypath information should not be included in the PSBT"));
            }

            if (psbt.Inputs.Any(o => !o.IsFinalized()))
            {
                return BadRequest(CreatePayjoinError("original-psbt-rejected", "The PSBT Should be finalized"));
            }
            ////////////

            var mempool = await explorer.BroadcastAsync(ctx.OriginalTransaction, true);
            if (!mempool.Success)
            {
                ctx.DoNotBroadcast();
                return BadRequest(CreatePayjoinError("original-psbt-rejected",
                    $"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"));
            }
            var enforcedLowR = ctx.OriginalTransaction.Inputs.All(IsLowR);
            var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
            bool paidSomething = false;
            Money due = null;
            Dictionary<OutPoint, UTXO> selectedUTXOs = new Dictionary<OutPoint, UTXO>();
            PSBTOutput originalPaymentOutput = null;
            BitcoinAddress paymentAddress = null;
            KeyPath paymentAddressIndex = null;
            InvoiceEntity invoice = null;
            DerivationSchemeSettings derivationSchemeSettings = null;
            WalletId walletId = null;
            foreach (var output in psbt.Outputs)
            {
                var walletReceiveMatch =
                    _walletReceiveService.GetByScriptPubKey(network.CryptoCode, output.ScriptPubKey);
                if (walletReceiveMatch is null)
                {

                    var key = output.ScriptPubKey.Hash + "#" + network.CryptoCode.ToUpperInvariant();
                    invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] {key})).FirstOrDefault();
                    if (invoice is null)
                        continue;
                    derivationSchemeSettings = invoice
                        .GetSupportedPaymentMethod<DerivationSchemeSettings>(paymentMethodId)
                        .SingleOrDefault();
                    walletId = new WalletId(invoice.StoreId, network.CryptoCode.ToUpperInvariant());
                }
                else
                {
                    var store = await _storeRepository.FindStore(walletReceiveMatch.Item1.StoreId);
                    derivationSchemeSettings = store.GetDerivationSchemeSettings(_btcPayNetworkProvider,
                        walletReceiveMatch.Item1.CryptoCode);
                    
                    walletId = walletReceiveMatch.Item1;
                }
                
                if (derivationSchemeSettings is null)
                    continue;
                var receiverInputsType = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType();
                if (receiverInputsType == ScriptPubKeyType.Legacy)
                {
                    //this should never happen, unless the store owner changed the wallet mid way through an invoice
                    return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "Our wallet does not support payjoin");
                }
                if (sendersInputType is ScriptPubKeyType t1 && t1 != receiverInputsType)
                {
                    return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "We do not have any UTXO available for making a payjoin with the sender's inputs type");
                }

                if (walletReceiveMatch is null)
                {
                    var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
                    var paymentDetails =
                        paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
                    if (paymentDetails is null || !paymentDetails.PayjoinEnabled)
                        continue;
                    paidSomething = true;
                    due = paymentMethod.Calculate().TotalDue - output.Value;
                    if (due > Money.Zero)
                    {
                        break;
                    }

                    paymentAddress = paymentDetails.GetDepositAddress(network.NBitcoinNetwork);
                    paymentAddressIndex = paymentDetails.KeyPath;

                    if (invoice.GetAllBitcoinPaymentData(false).Any())
                    {
                        ctx.DoNotBroadcast();
                        return UnprocessableEntity(CreatePayjoinError("already-paid",
                            $"The invoice this PSBT is paying has already been partially or completely paid"));
                    }
                }
                else
                {
                    paidSomething = true;
                    due = Money.Zero;
                    paymentAddress = walletReceiveMatch.Item2.Address;
                    paymentAddressIndex = walletReceiveMatch.Item2.KeyPath;
                }


                if (!await _payJoinRepository.TryLockInputs(ctx.OriginalTransaction.Inputs.Select(i => i.PrevOut).ToArray()))
                {
                    // We do not broadcast, since we might double spend a delayed transaction of a previous payjoin
                    ctx.DoNotBroadcast();
                    return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "Some of those inputs have already been used to make another payjoin transaction");
                }

                var utxos = (await explorer.GetUTXOsAsync(derivationSchemeSettings.AccountDerivation))
                    .GetUnspentUTXOs(false);
                // In case we are paying ourselves, be need to make sure
                // we can't take spent outpoints.
                var prevOuts = ctx.OriginalTransaction.Inputs.Select(o => o.PrevOut).ToHashSet();
                utxos = utxos.Where(u => !prevOuts.Contains(u.Outpoint)).ToArray();
                Array.Sort(utxos, UTXODeterministicComparer.Instance);
                foreach (var utxo in (await SelectUTXO(network, utxos, psbt.Inputs.Select(input => input.WitnessUtxo.Value.ToDecimal(MoneyUnit.BTC)), output.Value.ToDecimal(MoneyUnit.BTC),
                    psbt.Outputs.Where(psbtOutput => psbtOutput.Index != output.Index).Select(psbtOutput => psbtOutput.Value.ToDecimal(MoneyUnit.BTC)))).selectedUTXO)
                {
                    selectedUTXOs.Add(utxo.Outpoint, utxo);
                }
                ctx.LockedUTXOs = selectedUTXOs.Select(u => u.Key).ToArray();
                originalPaymentOutput = output;
                break;
            }

            if (!paidSomething)
            {
                return BadRequest(CreatePayjoinError("invoice-not-found",
                    "This transaction does not pay any invoice with payjoin"));
            }

            if (due is null || due > Money.Zero)
            {
                return BadRequest(CreatePayjoinError("invoice-not-fully-paid",
                    "The transaction must pay the whole invoice"));
            }

            if (selectedUTXOs.Count == 0)
            {
                return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "We do not have any UTXO available for contributing to a payjoin");
            }

            var originalPaymentValue = originalPaymentOutput.Value;
            await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), ctx.OriginalTransaction, network);

            //check if wallet of store is configured to be hot wallet
            var extKeyStr = await explorer.GetMetadataAsync<string>(
                derivationSchemeSettings.AccountDerivation,
                WellknownMetadataKeys.AccountHDKey);
            if (extKeyStr == null)
            {
                // This should not happen, as we check the existance of private key before creating invoice with payjoin
                return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "The HD Key of the store changed");
            }

            Money contributedAmount = Money.Zero;
            var newTx = ctx.OriginalTransaction.Clone();
            var ourNewOutput = newTx.Outputs[originalPaymentOutput.Index];
            HashSet<TxOut> isOurOutput = new HashSet<TxOut>();
            isOurOutput.Add(ourNewOutput);
            TxOut feeOutput =
                additionalfeeoutputindex is int feeOutputIndex &&
                maxadditionalfeecontribution is long v3 &&
                v3 >= 0 &&
                feeOutputIndex >= 0
                && feeOutputIndex < newTx.Outputs.Count
                && !isOurOutput.Contains(newTx.Outputs[feeOutputIndex])
                ? newTx.Outputs[feeOutputIndex] : null;
            int senderInputCount = newTx.Inputs.Count;
            foreach (var selectedUTXO in selectedUTXOs.Select(o => o.Value))
            {
                contributedAmount += (Money)selectedUTXO.Value;
                var newInput = newTx.Inputs.Add(selectedUTXO.Outpoint);
                newInput.Sequence = newTx.Inputs[(int)(RandomUtils.GetUInt32() % senderInputCount)].Sequence;
            }
            ourNewOutput.Value += contributedAmount;
            var minRelayTxFee = this._dashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ??
                                new FeeRate(1.0m);

            // Remove old signatures as they are not valid anymore
            foreach (var input in newTx.Inputs)
            {
                input.WitScript = WitScript.Empty;
            }

            Money ourFeeContribution = Money.Zero;
            // We need to adjust the fee to keep a constant fee rate
            var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder();
            var coins = psbt.Inputs.Select(i => i.GetSignableCoin())
                .Concat(selectedUTXOs.Select(o => o.Value.AsCoin(derivationSchemeSettings.AccountDerivation))).ToArray();

            txBuilder.AddCoins(coins);
            Money expectedFee = txBuilder.EstimateFees(newTx, originalFeeRate);
            Money actualFee = newTx.GetFee(txBuilder.FindSpentCoins(newTx));
            Money additionalFee = expectedFee - actualFee;
            if (additionalFee > Money.Zero)
            {
                // If the user overpaid, taking fee on our output (useful if sender dump a full UTXO for privacy)
                for (int i = 0; i < newTx.Outputs.Count && additionalFee > Money.Zero && due < Money.Zero; i++)
                {
                    if (disableoutputsubstitution)
                        break;
                    if (isOurOutput.Contains(newTx.Outputs[i]))
                    {
                        var outputContribution = Money.Min(additionalFee, -due);
                        outputContribution = Money.Min(outputContribution,
                            newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee));
                        newTx.Outputs[i].Value -= outputContribution;
                        additionalFee -= outputContribution;
                        due += outputContribution;
                        ourFeeContribution += outputContribution;
                    }
                }

                // The rest, we take from user's change
                if (feeOutput != null)
                {
                    var outputContribution = Money.Min(additionalFee, feeOutput.Value);
                    outputContribution = Money.Min(outputContribution,
                        feeOutput.Value - feeOutput.GetDustThreshold(minRelayTxFee));
                    outputContribution = Money.Min(outputContribution, allowedSenderFeeContribution);
                    feeOutput.Value -= outputContribution;
                    additionalFee -= outputContribution;
                    allowedSenderFeeContribution -= outputContribution;
                }

                if (additionalFee > Money.Zero)
                {
                    // We could not pay fully the additional fee, however, as long as
                    // we are not under the relay fee, it should be OK.
                    var newVSize = txBuilder.EstimateSize(newTx, true);
                    var newFeePaid = newTx.GetFee(txBuilder.FindSpentCoins(newTx));
                    if (new FeeRate(newFeePaid, newVSize) < (senderMinFeeRate ?? minRelayTxFee))
                    {
                        return CreatePayjoinErrorAndLog(422, PayjoinReceiverWellknownErrors.NotEnoughMoney, "Not enough money is sent to pay for the additional payjoin inputs");
                    }
                }
            }

            var accountKey = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
            var newPsbt = PSBT.FromTransaction(newTx, network.NBitcoinNetwork);
            foreach (var selectedUtxo in selectedUTXOs.Select(o => o.Value))
            {
                var signedInput = newPsbt.Inputs.FindIndexedInput(selectedUtxo.Outpoint);
                var coin = selectedUtxo.AsCoin(derivationSchemeSettings.AccountDerivation);
                signedInput.UpdateFromCoin(coin);
                var privateKey = accountKey.Derive(selectedUtxo.KeyPath).PrivateKey;
                signedInput.PSBT.Settings.SigningOptions = new SigningOptions()
                {
                    EnforceLowR = enforcedLowR
                };
                signedInput.Sign(privateKey);
                signedInput.FinalizeInput();
                newTx.Inputs[signedInput.Index].WitScript = newPsbt.Inputs[(int)signedInput.Index].FinalScriptWitness;
            }

            // Add the transaction to the payments with a confirmation of -1.
            // This will make the invoice paid even if the user do not
            // broadcast the payjoin.
            var originalPaymentData = new BitcoinLikePaymentData(paymentAddress,
                originalPaymentOutput.Value,
                new OutPoint(ctx.OriginalTransaction.GetHash(), originalPaymentOutput.Index),
                ctx.OriginalTransaction.RBF, paymentAddressIndex);
            originalPaymentData.ConfirmationCount = -1;
            originalPaymentData.PayjoinInformation = new PayjoinInformation()
            {
                CoinjoinTransactionHash = GetExpectedHash(newPsbt, coins),
                CoinjoinValue = originalPaymentValue - ourFeeContribution,
                ContributedOutPoints = selectedUTXOs.Select(o => o.Key).ToArray()
            };
            if (invoice != null)
            {
                var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true);
                if (payment is null)
                {
                    return UnprocessableEntity(CreatePayjoinError("already-paid",
                        $"The original transaction has already been accounted"));
                }
                _eventAggregator.Publish(new InvoiceEvent(invoice,InvoiceEvent.ReceivedPayment) { Payment = payment });
            }

           
            await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(ctx.OriginalTransaction);
            _eventAggregator.Publish(new UpdateTransactionLabel()
            {
                WalletId = walletId,
                TransactionLabels = selectedUTXOs.GroupBy(pair => pair.Key.Hash).Select(utxo =>
                       new KeyValuePair<uint256, List<(string color, Label label)>>(utxo.Key,
                           new List<(string color, Label label)>()
                           {
                                UpdateTransactionLabel.PayjoinExposedLabelTemplate(invoice?.Id)
                           }))
                    .ToDictionary(pair => pair.Key, pair => pair.Value)
            });
Example #39
0
		private int GetNextIndex(KeyPath keyPath)
		{
			if(!_PathStates.ContainsKey(keyPath))
				return 0;
			return _PathStates[keyPath].Next;
		}
Example #40
0
 public BitcoinAddress AddressOf(DerivationStrategyBase scheme, string path)
 {
     return(scheme.Derive(KeyPath.Parse(path)).ScriptPubKey.GetDestinationAddress(Network));
 }
Example #41
0
		private int GetLastLoaded(KeyPath keyPath)
		{
			if(!_PathStates.ContainsKey(keyPath))
				return 0;
			return _PathStates[keyPath].Loaded;
		}
Example #42
0
 private bool IsInternal(KeyPath keyPath)
 {
     return(_Parameters.DerivationPath.Derive(1, false) == keyPath);
 }
Example #43
0
		private Script CreateMultiSig(KeyPath keyPath)
		{
			return PayToMultiSigTemplate.Instance.GenerateScriptPubKey(_Parameters.SignatureRequired, _Parameters.RootKeys.Select((r, i) => Derivate(i, keyPath).PubKey).ToArray());
		}
Example #44
0
 private void AddKnown(KeyPath keyPath)
 {
     AddKnown(keyPath, Tracker, IsInternal(keyPath));
 }
Example #45
0
		public void CanUseKeyPath()
		{
			var keyPath = KeyPath.Parse("0/1/2/3");
			Assert.Equal(keyPath.ToString(), "0/1/2/3");
			var key = new ExtKey();
			Assert.Equal(key
							.Derive(0)
							.Derive(1)
							.Derive(2)
							.Derive(3)
							.ToString(Network.Main), key.Derive(keyPath).ToString(Network.Main));

			var neuter = key.Neuter();
			Assert.Equal(neuter
							.Derive(0)
							.Derive(1)
							.Derive(2)
							.Derive(3)
							.ToString(Network.Main), neuter.Derive(keyPath).ToString(Network.Main));

			Assert.Equal(neuter.Derive(keyPath).ToString(Network.Main), key.Derive(keyPath).Neuter().ToString(Network.Main));

			keyPath = new KeyPath(new uint[] { 0x8000002Cu, 1u });
			Assert.Equal(keyPath.ToString(), "44'/1");

			keyPath = KeyPath.Parse("44'/1");
			Assert.False(keyPath.IsHardened);
			Assert.True(KeyPath.Parse("44'/1'").IsHardened);
			Assert.Equal(keyPath[0], 0x8000002Cu);
			Assert.Equal(keyPath[1], 1u);

			key = new ExtKey();
			Assert.Equal(key.Derive(keyPath).ToString(Network.Main), key.Derive(44, true).Derive(1, false).ToString(Network.Main));

			keyPath = KeyPath.Parse("");
			keyPath = keyPath.Derive(44, true).Derive(1, false);
			Assert.Equal(keyPath.ToString(), "44'/1");
			Assert.Equal(keyPath.Increment().ToString(), "44'/2");
			Assert.Equal(keyPath.Derive(1,true).Increment().ToString(), "44'/1/2'");
			Assert.Equal(keyPath.Parent.ToString(), "44'");
			Assert.Equal(keyPath.Parent.Parent.ToString(), "");
			Assert.Equal(keyPath.Parent.Parent.Parent, null);
			Assert.Equal(keyPath.Parent.Parent.Increment(), null);
			Assert.Equal(key.Derive(keyPath).ToString(Network.Main), key.Derive(44, true).Derive(1, false).ToString(Network.Main));

			Assert.True(key.Derive(44, true).IsHardened);
			Assert.False(key.Derive(44, false).IsHardened);

			neuter = key.Derive(44, true).Neuter();
			Assert.True(neuter.IsHardened);
			neuter = key.Derive(44, false).Neuter();
			Assert.False(neuter.IsHardened);
		}
Example #46
0
        public HdPubKey GenerateNewKey(string label, KeyState keyState, bool isInternal, bool toFile = true)
        {
            // BIP44-ish derivation scheme
            // m / purpose' / coin_type' / account' / change / address_index
            var change = isInternal ? 1 : 0;

            lock (HdPubKeysLock)
            {
                IEnumerable <HdPubKey> relevantHdPubKeys;
                if (isInternal)
                {
                    relevantHdPubKeys = HdPubKeys.Where(x => x.IsInternal());
                }
                else
                {
                    relevantHdPubKeys = HdPubKeys.Where(x => !x.IsInternal());
                }

                KeyPath path;
                if (!relevantHdPubKeys.Any())
                {
                    path = new KeyPath($"{change}/0");
                }
                else
                {
                    int        largestIndex   = relevantHdPubKeys.Max(x => x.GetIndex());
                    List <int> missingIndexes = Enumerable.Range(0, largestIndex).Except(relevantHdPubKeys.Select(x => x.GetIndex())).ToList();
                    if (missingIndexes.Any())
                    {
                        int smallestMissingIndex = missingIndexes.Min();
                        path = relevantHdPubKeys.First(x => x.GetIndex() == (smallestMissingIndex - 1)).GetNonHardenedKeyPath().Increment();
                    }
                    else
                    {
                        path = relevantHdPubKeys.First(x => x.GetIndex() == largestIndex).GetNonHardenedKeyPath().Increment();
                    }
                }

                var fullPath = AccountKeyPath.Derive(path);
                var pubKey   = ExtPubKey.Derive(path).PubKey;

                var hdPubKey = new HdPubKey(pubKey, fullPath, label, keyState);
                HdPubKeys.Add(hdPubKey);
                lock (HdPubKeyScriptBytesLock)
                {
                    HdPubKeyScriptBytes.Add(hdPubKey.GetP2wpkhScript().ToCompressedBytes());
                }

                lock (ScriptHdPubkeyMapLock)
                {
                    ScriptHdPubkeyMap.Add(hdPubKey.GetP2wpkhScript(), hdPubKey);
                }

                if (toFile)
                {
                    ToFile();
                }

                return(hdPubKey);
            }
        }