public WalletCreation() { SignatureRequired = 1; RootKeys = new ExtPubKey[0]; Network = Network.Main; DerivationPath = new KeyPath(); }
public WalletCreation() { SignatureRequired = 1; RootKeys = new ExtPubKey[0]; Network = Network.Main; DerivationPath = new KeyPath(); Name = Guid.NewGuid().ToString(); }
public WalletCreation() { SignatureRequired = 1; RootKeys = new ExtPubKey[0]; Network = Network.Main; DerivationPath = new KeyPath(); Name = Guid.NewGuid().ToString(); PurgeConnectionOnFilterChange = true; }
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()); }
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(); }
/// <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(); }
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); }
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()); }
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)); }
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); } }
public abstract DerivationStrategyBase GetLineFor(KeyPath keyPath);
private void IncrementCurrentIndex(KeyPath keyPath) { if(!_PathStates.ContainsKey(keyPath)) _PathStates.Add(keyPath, new PathState()); _PathStates[keyPath].Next++; }
public abstract Derivation Derive(KeyPath keyPath);
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; }
public KeyPath GetNonHardenedKeyPath() { return(_nonHardenedKeyPath ?? (_nonHardenedKeyPath = new KeyPath(FullKeyPath[3], FullKeyPath[4]))); }
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); }
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()); }
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))); }
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(); }
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); }
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); }
private void AddKnown(KeyPath keyPath) { AddKnown(keyPath, Tracker, IsInternal(keyPath)); }
/// <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)); }
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()); }
private bool IsInternal(KeyPath keyPath) { return _Parameters.DerivationPath.Derive(1, false) == keyPath; }
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); } }
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)); }
public void ResolveKeyPath(KeyPath keyPath, int depth, List <KeyPath> accumulator, KeyPath currentPartialKeyPath) { MiscUtils.ResolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this); }
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; }
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) });
private int GetNextIndex(KeyPath keyPath) { if(!_PathStates.ContainsKey(keyPath)) return 0; return _PathStates[keyPath].Next; }
public BitcoinAddress AddressOf(DerivationStrategyBase scheme, string path) { return(scheme.Derive(KeyPath.Parse(path)).ScriptPubKey.GetDestinationAddress(Network)); }
private int GetLastLoaded(KeyPath keyPath) { if(!_PathStates.ContainsKey(keyPath)) return 0; return _PathStates[keyPath].Loaded; }
private bool IsInternal(KeyPath keyPath) { return(_Parameters.DerivationPath.Derive(1, false) == keyPath); }
private Script CreateMultiSig(KeyPath keyPath) { return PayToMultiSigTemplate.Instance.GenerateScriptPubKey(_Parameters.SignatureRequired, _Parameters.RootKeys.Select((r, i) => Derivate(i, keyPath).PubKey).ToArray()); }
private void AddKnown(KeyPath keyPath) { AddKnown(keyPath, Tracker, IsInternal(keyPath)); }
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); }
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); } }