private void RunTest(TestVector test) { var seed = TestUtils.ParseHex(test.strHexMaster); ExtKey key = new ExtKey(seed); ExtPubKey pubkey = key.Neuter(); foreach(TestDerivation derive in test.vDerive) { byte[] data = key.ToBytes(); Assert.Equal(74, data.Length); data = pubkey.ToBytes(); Assert.Equal(74, data.Length); // Test private key BitcoinExtKey b58key = Network.Main.CreateBitcoinExtKey(key); Assert.True(b58key.ToString() == derive.prv); // Test public key BitcoinExtPubKey b58pubkey = Network.Main.CreateBitcoinExtPubKey(pubkey); Assert.True(b58pubkey.ToString() == derive.pub); // Derive new keys ExtKey keyNew = key.Derive(derive.nChild); ExtPubKey pubkeyNew = keyNew.Neuter(); if(!((derive.nChild & 0x80000000) != 0)) { // Compare with public derivation ExtPubKey pubkeyNew2 = pubkey.Derive(derive.nChild); Assert.True(pubkeyNew == pubkeyNew2); } key = keyNew; pubkey = pubkeyNew; } }
public static WalletModel HashTable2Wallet(Hashtable hashT) { var walletFile = hashT["File"].ToString(); var walletFileRecover = hashT["FileRecover"].ToString(); var walletId = hashT["Id"].ToString(); var walletName = hashT["Name"].ToString(); var walletPassHash = hashT["PassHash"].ToString(); var walletRecPhraseHash = hashT["RecPhraseHash"].ToString(); var walletDescription = hashT["Description"].ToString(); var walletNetwork = Network.GetNetwork(hashT["Network"].ToString()); var walletMasterKey = ExtKey.Parse(hashT["MasterKey"].ToString(), walletNetwork); var walletCreated = (DateTimeOffset)hashT["Created"]; BitcoinExtPubKey rootKey = Generators.GenerateRootKey(walletMasterKey, walletNetwork); var wallet = new WalletModel(id: walletId, newName: walletName, rootKey: rootKey, newCreated: walletCreated, description: walletDescription); wallet.FileRecover = walletFileRecover; wallet.PassHash = walletPassHash; wallet.RecoveryPhraseHash = walletRecPhraseHash; wallet.SetMasterKey(walletMasterKey); return(wallet); }
public static void HdWallet1() { ExtKey d = new ExtKey(); string Path = "m/44'/60'/0'/0/x"; string words = ""; var mneumonic = new Mnemonic(words); var seed = mneumonic.DeriveSeed("123456@a").ToHex(); var masterKey = new ExtKey(seed); var d1 = new Account(masterKey.PrivateKey.ToBytes()).Address; for (int i = 0; i < 10; i++) { var keyPath = new NBitcoin.KeyPath(Path.Replace("x", i.ToString())); ExtKey extKey = masterKey.Derive(keyPath); byte[] privateKey = extKey.PrivateKey.ToBytes(); var account = new Account(privateKey); Console.WriteLine("Account index : " + i + " - Address : " + account.Address + " - Private key : " + account.PrivateKey); } }
public static Safe Load(string password, string walletFilePath) { if (!File.Exists(walletFilePath)) { throw new Exception("WalletFileDoesNotExists"); } var walletFileRawContent = WalletFileSerializer.Deserialize(walletFilePath); var encryptedBitcoinPrivateKeyString = walletFileRawContent.EncryptedSeed; var chainCodeString = walletFileRawContent.ChainCode; var chainCode = Convert.FromBase64String(chainCodeString); Network network; var networkString = walletFileRawContent.Network; if (networkString == Network.MainNet.ToString()) { network = Network.MainNet; } else if (networkString == Network.TestNet.ToString()) { network = Network.TestNet; } else { throw new Exception("NotRecognizedNetworkInWalletFile"); } var safe = new Safe(password, walletFilePath, network); var privateKey = Key.Parse(encryptedBitcoinPrivateKeyString, password, safe._network); var seedExtKey = new ExtKey(privateKey, chainCode); safe.SetSeed(seedExtKey); return(safe); }
public static (Bitcoin.Features.Wallet.Wallet wallet, ExtKey key) GenerateBlankWalletWithExtKey(string name, string password) { Mnemonic mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve); ExtKey extendedKey = mnemonic.DeriveExtKey(password); Bitcoin.Features.Wallet.Wallet walletFile = new Bitcoin.Features.Wallet.Wallet { Name = name, EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main).ToWif(), ChainCode = extendedKey.ChainCode, CreationTime = DateTimeOffset.Now, Network = Network.Main, AccountsRoot = new List <AccountRoot> { new AccountRoot() { Accounts = new List <HdAccount>(), CoinType = (CoinType)Network.Main.Consensus.CoinType } }, }; return(walletFile, extendedKey); }
/// <inheritdoc /> public Mnemonic CreateWallet(string password, string name, string passphrase = null, string mnemonicList = null) { Guard.NotEmpty(password, nameof(password)); Guard.NotEmpty(name, nameof(name)); // for now the passphrase is set to be the password by default. if (passphrase == null) { passphrase = password; } // generate the root seed used to generate keys from a mnemonic picked at random // and a passphrase optionally provided by the user Mnemonic mnemonic = string.IsNullOrEmpty(mnemonicList) ? new Mnemonic(Wordlist.English, WordCount.Twelve) : new Mnemonic(mnemonicList); ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, name, extendedKey); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletCreationAccountsCount; i++) { HdAccount account = wallet.AddNewAccount(password, this.coinType); account.CreateAddresses(this.network, UnusedAddressesBuffer); account.CreateAddresses(this.network, UnusedAddressesBuffer, true); } // update the height of the we start syncing from this.UpdateLastBlockSyncedHeight(wallet, this.chain.Tip); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(mnemonic); }
public void SubmitSeed() { if (submitSeedText.text != null && submitPWText.text.Length > 7) { try { string seedWord = submitSeedText.text; string password = submitPWText.text; Mnemonic mnemo = new Mnemonic(seedWord, Wordlist.English); ExtKey hdRoot = mnemo.DeriveExtKey(password); CancleForgotPanel(); Debug.Log("New password:" + password); } catch (FormatException) { missingText.enabled = true; } } }
public static (Wallet wallet, ExtKey key) GenerateBlankWalletWithExtKey(string name, string password) { Mnemonic mnemonic = new Mnemonic("grass industry beef stereo soap employ million leader frequent salmon crumble banana"); ExtKey extendedKey = mnemonic.DeriveExtKey(password); Wallet walletFile = new Wallet { Name = name, EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main).ToWif(), ChainCode = extendedKey.ChainCode, CreationTime = DateTimeOffset.Now, Network = Network.Main, AccountsRoot = new List <AccountRoot> { new AccountRoot() { Accounts = new List <HdAccount>(), CoinType = (CoinType)Network.Main.Consensus.CoinType } }, }; return(walletFile, extendedKey); }
static void CorrectOne(string basePath, Network net, string words, ScriptPubKeyType scriptPubKeyType, string password = null) { Console.WriteLine("-------------------------------------"); Console.WriteLine($" basePath: {basePath}"); Console.WriteLine($" words: {words}"); Console.WriteLine(); //https://stackoverflow.com/questions/46550818/nbitcoin-and-mnemonic-standards Mnemonic mnemo = new Mnemonic(words, Wordlist.English); ExtKey hdroot = mnemo.DeriveExtKey(password); for (int i = 0; i < 10; i++) { var privkey = hdroot.Derive(new NBitcoin.KeyPath(basePath + "/0/" + i.ToString())); var publicKey = privkey.Neuter().PubKey; var privateKey = privkey.Neuter().GetWif(net); var address = publicKey.GetAddress(scriptPubKeyType, net).ToString(); Console.WriteLine($"{ net.ToString() } , public key : { address} , privateKey {privateKey}"); Console.WriteLine(""); } ExtKey masterKey = hdroot.Derive(new NBitcoin.KeyPath(basePath)); string masterPublicKey = masterKey.Neuter().GetWif(net).ToString(); string masterPrivateKey = masterKey.GetWif(net).ToString(); Console.WriteLine($""); Console.WriteLine($"MasterPrivateKey : {net.ToString()} {masterPrivateKey} "); Console.WriteLine($"MasterPublicKey : {net.ToString()} {masterPublicKey} "); Console.WriteLine($""); Console.WriteLine("-------------------------------------"); /* Console.WriteLine("Master key 11111 : " + hdroot.ToString(net)); * ExtPubKey masterPubKey = hdroot.Neuter(); * Console.WriteLine("Master PubKey " + masterPubKey.ToString(net)); * Console.WriteLine(); * Console.WriteLine(); */ }
/// <inheritdoc /> public HdAccount CreateNewAccount(Wallet wallet, string password) { Guard.NotNull(wallet, nameof(wallet)); Guard.NotEmpty(password, nameof(password)); // get the accounts for this type of coin var accounts = wallet.AccountsRoot.Single(a => a.CoinType == this.coinType).Accounts.ToList(); int newAccountIndex = 0; if (accounts.Any()) { newAccountIndex = accounts.Max(a => a.Index) + 1; } // get the extended pub key used to generate addresses for this account var privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network); var seedExtKey = new ExtKey(privateKey, wallet.ChainCode); var accountHdPath = $"m/44'/{(int)this.coinType}'/{newAccountIndex}'"; KeyPath keyPath = new KeyPath(accountHdPath); ExtKey accountExtKey = seedExtKey.Derive(keyPath); ExtPubKey accountExtPubKey = accountExtKey.Neuter(); var newAccount = new HdAccount { Index = newAccountIndex, ExtendedPubKey = accountExtPubKey.ToString(wallet.Network), ExternalAddresses = new List <HdAddress>(), InternalAddresses = new List <HdAddress>(), Name = $"account {newAccountIndex}", HdPath = accountHdPath, CreationTime = DateTimeOffset.Now }; accounts.Add(newAccount); wallet.AccountsRoot.Single(a => a.CoinType == this.coinType).Accounts = accounts; return(newAccount); }
/// <summary> /// Interactive example that walks through creating, signing, and broadcasting a Dash transaction. /// Make sure you have Dash Core wallet running on TestNet for this example to work. /// </summary> public static void RunExample() { // Create a new HD extended private key ExtKey extKey = CreateExtKey(); // Derive a child address so we can send it some coins var extPubKey = extKey.Neuter().GetWif(_network); int customerId = 1234567; string keyDerivationPath = $"{customerId}/0"; BitcoinPubKeyAddress childAddress = DeriveChildAddress(extPubKey.ToString(), keyDerivationPath); // Watch this address in your core wallet so that you can monitor it for funds RPCClient rpc = GetRpcClient(); rpc.ImportAddress(childAddress, $"Customer {customerId}", false); // Send some coins manually to the address just created Console.WriteLine($"Now, go to another wallet and send some coins to address {childAddress}."); Console.WriteLine($"Wait a few minutes for it to confirm, then press ENTER to continue."); Console.ReadLine(); // Create and sign a transaction that spends the coins received by the address above. Console.WriteLine($"Enter an address of yours to which you want to receive coins:"); string toAddr = Console.ReadLine(); Console.WriteLine($"Enter the amount to spend:"); string spend = Console.ReadLine(); decimal minerFee = 0.00001m; decimal amountToSpend = Convert.ToDecimal(spend) - minerFee; Key privateKey = extKey.Derive(KeyPath.Parse(keyDerivationPath)).PrivateKey; var transaction = CreateAndSignTransaction(childAddress.ToString(), toAddr, amountToSpend, privateKey); // Finally, broadcast transaction to network rpc.SendRawTransaction(transaction); }
/// <summary> /// Loads all the private keys for each of the <see cref="HdAddress"/> in <see cref="TransactionBuildContext.UnspentOutputs"/> /// </summary> /// <param name="context">The context associated with the current transaction being built.</param> /// <param name="coinsSpent">The coins spent to generate the transaction.</param> protected void AddSecrets(TransactionBuildContext context, IEnumerable <ICoin> coinsSpent) { if (!context.Sign) { return; } Wallet wallet = this.walletManager.GetWallet(context.AccountReference.WalletName); ExtKey seedExtKey = this.walletManager.GetExtKey(context.AccountReference, context.WalletPassword); var signingKeys = new HashSet <ISecret>(); Dictionary <OutPoint, UnspentOutputReference> outpointLookup = context.UnspentOutputs.ToDictionary(o => o.ToOutPoint(), o => o); IEnumerable <string> uniqueHdPaths = coinsSpent.Select(s => s.Outpoint).Select(o => outpointLookup[o].Address.HdPath).Distinct(); foreach (string hdPath in uniqueHdPaths) { ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(hdPath)); BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(wallet.Network); signingKeys.Add(addressPrivateKey); } context.TransactionBuilder.AddKeys(signingKeys.ToArray()); }
/// <summary> /// Unlock a locked wallet. /// </summary> /// <param name="password"></param> /// <returns></returns> public bool Unlock(string password) { if (IsUnlocked || !IsCreated) { return(IsUnlocked && IsCreated); } try { var masterKey = Key.Parse(_walletFile.WifKey, password, _network); _extKey = new ExtKey(masterKey, _walletFile.ChainCode); } catch (SecurityException ex) { Log.Error(ex); return(false); } // finally load all keys LoadKeys(); return(true); }
internal BitcoinExtKey GetPrivateKey(int index, HdPathType hdPathType = HdPathType.Receive) { KeyPath keyPath; if (hdPathType == HdPathType.Receive) { keyPath = new KeyPath($"{ReceiveHdPath}/{index}'"); } else if (hdPathType == HdPathType.Change) { keyPath = new KeyPath($"{ChangeHdPath}/{index}'"); } else if (hdPathType == HdPathType.NonHardened) { keyPath = new KeyPath($"{NonHardenedHdPath}/{index}"); } else { throw new Exception("HdPathType not exists"); } return(ExtKey.Derive(keyPath).GetWif(Network)); }
public void CanGenerateAddress() { using (var server = TumblerServerTester.Create()) { var key = new ExtKey().GetWif(Network.RegTest); var w = new ClientDestinationWallet(key.Neuter(), new KeyPath("0/1"), server.ClientRuntime.Repository, server.ClientRuntime.Network); var k1 = w.GetNewDestination(); var k2 = w.GetNewDestination(); Assert.Equal(new KeyPath("0/1/0"), w.GetKeyPath(k1)); Assert.Equal(new KeyPath("0/1/1"), w.GetKeyPath(k2)); Assert.Null(w.GetKeyPath(new Key().ScriptPubKey)); var wrpc = new RPCDestinationWallet(server.AliceNode.CreateRPCClient()); k1 = wrpc.GetNewDestination(); k2 = wrpc.GetNewDestination(); Assert.Equal(new KeyPath("0'/0'/1'"), wrpc.GetKeyPath(k1)); Assert.Equal(new KeyPath("0'/0'/2'"), wrpc.GetKeyPath(k2)); Assert.Null(w.GetKeyPath(new Key().ScriptPubKey)); } }
public IActionResult About(string transactonHex = "dc7268d85689fe3a4dade2a5886794237221fb0161e59f12d8128c46ca7fab90") { ViewData["Message"] = "Your application description page."; var network = Network.TestNet; //mysLDbXCoie81EysydNHfSTva5sy4rRzKB var secret = new ExtKey().GetWif(network); //new BitcoinSecret("943b00f1-1488-4d44-91fa-f3bcc5789099"); var key = secret.PrivateKey; var client = new QBitNinjaClient(network); Transaction tx = new Transaction(); var input = new TxIn(); var transactionId = uint256.Parse("dc7268d85689fe3a4dade2a5886794237221fb0161e59f12d8128c46ca7fab90"); // var transactionResponse = client.GetTransaction(transactionId).Result; input.PrevOut = new OutPoint(new uint256(transactonHex), 1); //Transaction ID input.ScriptSig = key.GetBitcoinSecret(network).GetAddress().ScriptPubKey; tx.AddInput(input); TxOut output = new TxOut(); var desctination = BitcoinAddress.Create("mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf"); Money fee = Money.Satoshis(40000); output.Value = Money.Coins(0.1m) - fee; output.ScriptPubKey = desctination.ScriptPubKey; tx.AddOutput(output); tx.Sign(secret, false); BroadcastResponse broadcastResponse = client.Broadcast(tx).Result; return(View()); }
bool AddTransaction(ExtKey key, TxId txId) { bool outputAdded = false; var txResponse = Spend.Api.GetTransaction(txId.TransactionId).Result; if (txResponse.Block == null) { return(false); } var receivedCoins = txResponse.ReceivedCoins; OutPoint outPointToSpend = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == key.PrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; txId.MoneyOut += (Money)coin.Amount; coins.Add(coin); } } if (outPointToSpend != null) { Tx.Inputs.Add( new TxIn() { PrevOut = outPointToSpend, ScriptSig = key.PrivateKey.GetWif(Spend.Api.Network).GetAddress(ScriptPubKeyType.Legacy).ScriptPubKey }); outputAdded = true; } return(outputAdded); }
/// <inheritdoc /> public Wallet RecoverWallet(string password, string name, string mnemonic, DateTime creationTime, string passphrase = null) { Guard.NotEmpty(password, nameof(password)); Guard.NotEmpty(name, nameof(name)); Guard.NotEmpty(mnemonic, nameof(mnemonic)); // for now the passphrase is set to be the password by default. if (passphrase == null) { passphrase = password; } // generate the root seed used to generate keys ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, name, extendedKey, creationTime); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletRecoveryAccountsCount; i++) { HdAccount account = wallet.AddNewAccount(password, this.coinType); account.CreateAddresses(this.network, UnusedAddressesBuffer); account.CreateAddresses(this.network, UnusedAddressesBuffer, true); } int blockSyncStart = this.chain.GetHeightAtTime(creationTime); this.UpdateLastBlockSyncedHeight(wallet, this.chain.GetBlock(blockSyncStart)); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(wallet); }
private static void MasterPublicKeyAndPrivateKey(string path, Network net, string words, string password) { // Mnemonic mnemo = new Mnemonic(Wordlist.English, WordCount.Twelve); Mnemonic mnemo = new Mnemonic(words); ExtKey extKey = mnemo.DeriveExtKey(password); ExtKey masterKey = extKey.Derive(new NBitcoin.KeyPath(path)); string masterPublicKey = masterKey.Neuter().GetWif(net).ToString(); string masterPrivateKey = masterKey.GetWif(net).ToString(); Console.WriteLine($""); Console.WriteLine($"MasterPrivateKey : {net.ToString()} {masterPrivateKey} "); Console.WriteLine($"MasterPublicKey : {net.ToString()} {masterPublicKey} "); Console.WriteLine($""); var drivekey = masterKey.Derive(0); string publicKey = masterKey.Neuter().GetWif(net).ToString(); string privateKey = masterKey.GetWif(net).ToString(); Console.WriteLine($""); Console.WriteLine($"first publicKey : {net.ToString()} {publicKey} "); Console.WriteLine($"first privateKey : {net.ToString()} {privateKey} "); Console.WriteLine($""); }
private void Refresh() { try { ExtKey changeKey = tcBitcoin.GetExtendedKey(change.FullHDPath); textKeyNamespace.Text = change.KeyNamespace; textKeyPath.Text = change.FullHDPath; textChangeStatus.Text = change.ChangeStatus; textInvoiceNumber.Text = change.InvoiceNumber; textPrivateKey.Text = Properties.Settings.Default.HidePrivateKeys ? "..." : $"{changeKey.PrivateKey.GetWif(tcBitcoin.GetNetwork)}";; textAddress.Text = $"{changeKey.PrivateKey.GetWif(tcBitcoin.GetNetwork).GetAddress(ScriptPubKeyType.Legacy)}"; textBalance.Text = $"{change.Balance}"; textNote.Text = change.Note; dgTransactions.ItemsSource = tcBitcoin.NodeCash.fnChangeTx(textAddress.Text) .OrderByDescending(text => text.TransactedOn) .Select(text => text); } catch (Exception err) { MessageBox.Show($"{err.Message}", $"{err.Source}.{err.TargetSite.Name}", MessageBoxButton.OK, MessageBoxImage.Error); } }
private ExtKey GetMasterKey() { if (String.IsNullOrEmpty(WalletExtendedPublicKey)) { return(ExtKey.Parse(WalletPrivateKey)); } var xpub = ExtPubKey.Parse(WalletExtendedPublicKey); var password = default(SecureString); try { var encrypted = BitcoinEncryptedSecret.Create(WalletPrivateKey); password = GetPassword(); return(new ExtKey(xpub, encrypted.GetKey(new NetworkCredential(String.Empty, password).Password))); } catch (FormatException) { return(new ExtKey(xpub, Key.Parse(WalletPrivateKey))); } catch (SecurityException) { Console.WriteLine("Invalid password."); #if DEBUG Console.WriteLine("Press Any Key To Exit..."); Console.Read(); #endif Environment.Exit(1); return(null); } finally { password?.Dispose(); } }
/// <inheritdoc /> public ISecret GetKeyForAddress(string walletName, string password, HdAddress address) { Guard.NotEmpty(walletName, nameof(walletName)); Guard.NotEmpty(password, nameof(password)); Guard.NotNull(address, nameof(address)); var wallet = this.GetWalletByName(walletName); // check if the wallet contains the address. if (!wallet.AccountsRoot.Any(r => r.Accounts.Any( a => a.ExternalAddresses.Any(i => i.Address == address.Address) || a.InternalAddresses.Any(i => i.Address == address.Address)))) { throw new WalletException("Address not found on wallet."); } // get extended private key var privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network); var seedExtKey = new ExtKey(privateKey, wallet.ChainCode); ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(address.HdPath)); BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(wallet.Network); return(addressPrivateKey); }
/// <inheritdoc /> public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null) { // for now the passphrase is set to be the password by default. if (passphrase == null) { passphrase = password; } // generate the root seed used to generate keys from a mnemonic picked at random // and a passphrase optionally provided by the user Mnemonic mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve); ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase); Network coinNetwork = WalletHelpers.GetNetwork(network); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, folderPath, name, coinNetwork, extendedKey); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletCreationAccountsCount; i++) { HdAccount account = CreateNewAccount(wallet, this.coinType, password); this.CreateAddressesInAccount(account, coinNetwork, UnusedAddressesBuffer); this.CreateAddressesInAccount(account, coinNetwork, UnusedAddressesBuffer, true); } // update the height of the we start syncing from this.UpdateLastBlockSyncedHeight(wallet, this.chain.Tip.Height); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(mnemonic); }
public static ExtKey GetMasterExtKey(KeyManager keyManager, string password, out string compatiblityPassword) { password = Helpers.Guard.Correct(password); // Correct the password to ensure compatiblity. User will be notified about this through TogglePasswordBox. Guard(password); compatiblityPassword = null; Exception resultException = null; foreach (var pw in GetPossiblePasswords(password)) { try { ExtKey result = keyManager.GetMasterExtKey(pw); if (resultException != null) // Now the password is OK but if we had SecurityException before than we used a cmp password. { compatiblityPassword = pw; Logger.LogError(CompatibilityPasswordWarnMessage); } return(result); } catch (SecurityException ex) // Any other exception - let it go. { resultException = ex; } } if (resultException is null) // This mustn't be null. { throw new InvalidOperationException(); } throw resultException; // Throw the last exception - Invalid password. }
internal string SignSGAMessageWithCode(int[] SecretSalt, int[] SecretPW, string sgaMessage, string DisplayCode) { try { ExtKey masterKey = GetExtKey(SecretSalt, SecretPW); ExtKey masterKeyD = masterKey.Derive(GetCurrencyIndex("btc"), hardened: true); ExtKey key = masterKeyD.Derive((uint)0); byte[] keybytes = key.PrivateKey.ToBytes(); Key pkey = new Key(keybytes, -1, false); BitcoinSecret bitcoinSecret = new BitcoinSecret(pkey, Network.Main); var address = bitcoinSecret.PubKey.GetAddress(GetBLKNetworkAlt("btc")).ToString(); var signature = bitcoinSecret.PrivateKey.SignMessage(sgaMessage); Sclear.EraseBytes(keybytes); masterKeyD = null; key = null; pkey = null; NWS WS = new NWS(); return(WS.GetSGATokenForSignin(address, signature, DisplayCode)); } catch { return("4"); } }
public void MoreWalletTest() { Wallet wallet = new Wallet("赛 烂 肉 什 状 系 既 株 炼 硫 辞 州", "123456"); var pk = wallet.GetPrivateKey(0).ToHex(); //ef47fca84122c17bc312d44985ebf75cd09b4beb611204b43f9f448c86cdf5e3 var addrArray = wallet.GetAddresses(19); //获得20个地址,每一个都不一样,19 是下标 var masterKey = wallet.GetMasterKey().PrivateKey.ToHex(); //获得主钥 var publicKey = wallet.GetMasterKey().Neuter(); int index = 0; var keyPath = new NBitcoin.KeyPath(wallet.Path.Replace("x", index.ToString())); // masterKey.Derive(keyPath); var childKey = wallet.GetMasterKey().Derive((uint)index); var pubKey = wallet.GetMasterKey().Neuter(); ExtKey recovered = childKey.GetParentExtKey(pubKey); ExtKey recovered0 = wallet.GetPrivateExtKey(0).Derive(0); //得到序列为0的钱包私钥,计算它的子私钥 对应序列也是0 ExtPubKey recovered0PublicKey = wallet.GetPrivateExtKey(0).Neuter(); //得到序列为0的钱包私钥 的公钥 //var result = recovered.PrivateKey.ToHex(); var recovered0PrivateKey = recovered0.GetParentExtKey(recovered0PublicKey); Assert.Equal(wallet.GetPrivateExtKey(0).PrivateKey.ToHex(), recovered0PrivateKey.PrivateKey.ToHex()); }
public KeyManager(BitcoinEncryptedSecretNoEC encryptedSecret, byte[] chainCode, string password, string filePath = null) { HdPubKeys = new List <HdPubKey>(); HdPubKeyScriptBytes = new List <byte[]>(); ScriptHdPubkeyMap = new Dictionary <Script, HdPubKey>(); HdPubKeysLock = new object(); HdPubKeyScriptBytesLock = new object(); ScriptHdPubkeyMapLock = new object(); if (password is null) { password = ""; } EncryptedSecret = Guard.NotNull(nameof(encryptedSecret), encryptedSecret); ChainCode = Guard.NotNull(nameof(chainCode), chainCode); var extKey = new ExtKey(encryptedSecret.GetKey(password), chainCode); ExtPubKey = extKey.Derive(AccountKeyPath).Neuter(); SetFilePath(filePath); ToFileLock = new object(); ToFile(); }
public void CanRecoverExtKeyFromExtPubKeyAndOneChildExtKey2() { for(int i = 0 ; i < 255 ; i++) { ExtKey key = new ExtKey().Derive((uint)i); var childKey = key.Derive((uint)i); var pubKey = key.Neuter(); ExtKey recovered = childKey.GetParentExtKey(pubKey); Assert.Equal(recovered.ToString(Network.Main), key.ToString(Network.Main)); } }
public BitcoinExtKey(ExtKey key, Network network) : base(key, network) { }
public void CanSyncWallet2() { using(NodeServerTester servers = new NodeServerTester(Network.TestNet)) { var chainBuilder = new BlockchainBuilder(); SetupSPVBehavior(servers, chainBuilder); NodesGroup aliceConnection = CreateGroup(servers, 1); NodesGroup bobConnection = CreateGroup(servers, 1); var aliceKey = new ExtKey(); Wallet alice = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { aliceKey.Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); Wallet bob = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { new ExtKey().Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); alice.Configure(aliceConnection); alice.Connect(); bob.Configure(bobConnection); bob.Connect(); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //New address tracked var addressAlice = alice.GetNextScriptPubKey(); chainBuilder.GiveMoney(addressAlice, Money.Coins(1.0m)); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 0); //Purge TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //Reconnect ////// TestUtils.Eventually(() => alice.GetTransactions().Count == 1); //Alice send tx to bob var coins = alice.GetTransactions().GetSpendableCoins(); var keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); var builder = new TransactionBuilder(); var tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.4m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Alice get change TestUtils.Eventually(() => alice.GetTransactions().Count == 2); coins = alice.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.6m)); ////// //Bob get coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// MemoryStream bobWalletBackup = new MemoryStream(); bob.Save(bobWalletBackup); bobWalletBackup.Position = 0; MemoryStream bobTrakerBackup = new MemoryStream(); bob.Tracker.Save(bobTrakerBackup); bobTrakerBackup.Position = 0; bob.Disconnect(); //Restore bob bob = Wallet.Load(bobWalletBackup); bobConnection.NodeConnectionParameters.TemplateBehaviors.Remove<TrackerBehavior>(); bobConnection.NodeConnectionParameters.TemplateBehaviors.Add(new TrackerBehavior(Tracker.Load(bobTrakerBackup), chainBuilder.Chain)); ///// bob.Configure(bobConnection); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// bob.Connect(); TestUtils.Eventually(() => bobConnection.ConnectedNodes.Count == 1); //New block found ! chainBuilder.FindBlock(); //Alice send tx to bob coins = alice.GetTransactions().GetSpendableCoins(); keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); builder = new TransactionBuilder(); tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.1m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 2); //Bob has both, old and new tx coins = bob.GetTransactions().GetSpendableCoins(); ////// } }
public void CheckBIP32Constructors() { var key = new ExtKey(); Assert.Equal(key.GetWif(Network.Main), new ExtKey(key.PrivateKey, key.ChainCode).GetWif(Network.Main)); Assert.Equal(key.Neuter().GetWif(Network.Main), new ExtPubKey(key.PrivateKey.PubKey, key.ChainCode).GetWif(Network.Main)); key = key.Derive(1); Assert.Equal(key.GetWif(Network.Main), new ExtKey(key.PrivateKey, key.ChainCode, key.Depth, key.Fingerprint, key.Child).GetWif(Network.Main)); Assert.Equal(key.Neuter().GetWif(Network.Main), new ExtPubKey(key.PrivateKey.PubKey, key.ChainCode, key.Depth, key.Fingerprint, key.Child).GetWif(Network.Main)); }
public async Task <IActionResult> Submit(string cryptoCode) { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (network == null) { return(BadRequest(CreatePayjoinError(400, "invalid-network", "Incorrect network"))); } var explorer = _explorerClientProvider.GetExplorerClient(network); if (Request.ContentLength is long length) { if (length > 1_000_000) { return(this.StatusCode(413, CreatePayjoinError(413, "payload-too-large", "The transaction is too big to be processed"))); } } else { return(StatusCode(411, CreatePayjoinError(411, "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; } Transaction originalTx = null; FeeRate originalFeeRate = null; bool psbtFormat = true; if (!PSBT.TryParse(rawBody, network.NBitcoinNetwork, out var psbt)) { psbtFormat = false; if (!Transaction.TryParse(rawBody, network.NBitcoinNetwork, out var tx)) { return(BadRequest(CreatePayjoinError(400, "invalid-format", "invalid transaction or psbt"))); } originalTx = 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; } } else { if (!psbt.IsAllFinalized()) { return(BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT should be finalized"))); } originalTx = psbt.ExtractTransaction(); } async Task BroadcastNow() { await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx); } var sendersInputType = psbt.GetInputsScriptPubKeyType(); if (sendersInputType is null) { return(BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support segwit inputs (of the same type)"))); } if (psbt.CheckSanity() is var errors && errors.Count != 0) { return(BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})"))); } if (!psbt.TryGetEstimatedFeeRate(out originalFeeRate)) { return(BadRequest(CreatePayjoinError(400, "need-utxo-information", "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(400, "leaking-data", "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(400, "leaking-data", "Keypath information should not be included in the PSBT"))); } if (psbt.Inputs.Any(o => !o.IsFinalized())) { return(BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT Should be finalized"))); } //////////// var mempool = await explorer.BroadcastAsync(originalTx, true); if (!mempool.Success) { return(BadRequest(CreatePayjoinError(400, "invalid-transaction", $"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"))); } var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike); bool paidSomething = false; Money due = null; Dictionary <OutPoint, UTXO> selectedUTXOs = new Dictionary <OutPoint, UTXO>(); async Task UnlockUTXOs() { await _payJoinRepository.TryUnlock(selectedUTXOs.Select(o => o.Key).ToArray()); } PSBTOutput originalPaymentOutput = null; BitcoinAddress paymentAddress = null; InvoiceEntity invoice = null; DerivationSchemeSettings derivationSchemeSettings = null; foreach (var output in psbt.Outputs) { 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(); if (derivationSchemeSettings is null) { continue; } var receiverInputsType = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType(); if (!PayjoinClient.SupportedFormats.Contains(receiverInputsType)) { //this should never happen, unless the store owner changed the wallet mid way through an invoice return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"))); } if (sendersInputType != receiverInputsType) { return(StatusCode(503, CreatePayjoinError(503, "out-of-utxos", "We do not have any UTXO available for making a payjoin with the sender's inputs type"))); } var paymentMethod = invoice.GetPaymentMethod(paymentMethodId); var paymentDetails = paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; if (paymentDetails is null || !paymentDetails.PayjoinEnabled) { continue; } if (invoice.GetAllBitcoinPaymentData().Any()) { return(UnprocessableEntity(CreatePayjoinError(422, "already-paid", $"The invoice this PSBT is paying has already been partially or completely paid"))); } paidSomething = true; due = paymentMethod.Calculate().TotalDue - output.Value; if (due > Money.Zero) { break; } if (!await _payJoinRepository.TryLockInputs(originalTx.Inputs.Select(i => i.PrevOut).ToArray())) { return(BadRequest(CreatePayjoinError(400, "inputs-already-used", "Some of those inputs have already been used to make 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 = originalTx.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, output.Value, psbt.Outputs.Where(o => o.Index != output.Index).Select(o => o.Value).ToArray())) { selectedUTXOs.Add(utxo.Outpoint, utxo); } originalPaymentOutput = output; paymentAddress = paymentDetails.GetDepositAddress(network.NBitcoinNetwork); break; } if (!paidSomething) { return(BadRequest(CreatePayjoinError(400, "invoice-not-found", "This transaction does not pay any invoice with payjoin"))); } if (due is null || due > Money.Zero) { return(BadRequest(CreatePayjoinError(400, "invoice-not-fully-paid", "The transaction must pay the whole invoice"))); } if (selectedUTXOs.Count == 0) { await BroadcastNow(); return(StatusCode(503, CreatePayjoinError(503, "out-of-utxos", "We do not have any UTXO available for making a payjoin for now"))); } var originalPaymentValue = originalPaymentOutput.Value; await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1.0), originalTx, 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 await UnlockUTXOs(); await BroadcastNow(); return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"))); } Money contributedAmount = Money.Zero; var newTx = originalTx.Clone(); var ourNewOutput = newTx.Outputs[originalPaymentOutput.Index]; HashSet <TxOut> isOurOutput = new HashSet <TxOut>(); isOurOutput.Add(ourNewOutput); foreach (var selectedUTXO in selectedUTXOs.Select(o => o.Value)) { contributedAmount += (Money)selectedUTXO.Value; newTx.Inputs.Add(selectedUTXO.Outpoint); } ourNewOutput.Value += contributedAmount; var minRelayTxFee = this._dashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ?? new FeeRate(1.0m); // Probably receiving some spare change, let's add an output to make // it looks more like a normal transaction if (newTx.Outputs.Count == 1) { var change = await explorer.GetUnusedAsync(derivationSchemeSettings.AccountDerivation, DerivationFeature.Change); var randomChangeAmount = RandomUtils.GetUInt64() % (ulong)contributedAmount.Satoshi; // Randomly round the amount to make the payment output look like a change output var roundMultiple = (ulong)Math.Pow(10, (ulong)Math.Log10(randomChangeAmount)); while (roundMultiple > 1_000UL) { if (RandomUtils.GetUInt32() % 2 == 0) { roundMultiple = roundMultiple / 10; } else { randomChangeAmount = (randomChangeAmount / roundMultiple) * roundMultiple; break; } } var fakeChange = newTx.Outputs.CreateNewTxOut(randomChangeAmount, change.ScriptPubKey); if (fakeChange.IsDust(minRelayTxFee)) { randomChangeAmount = fakeChange.GetDustThreshold(minRelayTxFee); fakeChange.Value = randomChangeAmount; } if (randomChangeAmount < contributedAmount) { ourNewOutput.Value -= fakeChange.Value; newTx.Outputs.Add(fakeChange); isOurOutput.Add(fakeChange); } } var rand = new Random(); Utils.Shuffle(newTx.Inputs, rand); Utils.Shuffle(newTx.Outputs, rand); // 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(); txBuilder.AddCoins(psbt.Inputs.Select(i => i.GetSignableCoin())); txBuilder.AddCoins(selectedUTXOs.Select(o => o.Value.AsCoin(derivationSchemeSettings.AccountDerivation))); 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 (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 for (int i = 0; i < newTx.Outputs.Count && additionalFee > Money.Zero; i++) { if (!isOurOutput.Contains(newTx.Outputs[i])) { var outputContribution = Money.Min(additionalFee, newTx.Outputs[i].Value); outputContribution = Money.Min(outputContribution, newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee)); newTx.Outputs[i].Value -= outputContribution; additionalFee -= 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) < minRelayTxFee) { await UnlockUTXOs(); await BroadcastNow(); return(UnprocessableEntity(CreatePayjoinError(422, "not-enough-money", "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.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(originalTx.GetHash(), originalPaymentOutput.Index), originalTx.RBF); originalPaymentData.ConfirmationCount = -1; originalPaymentData.PayjoinInformation = new PayjoinInformation() { CoinjoinTransactionHash = newPsbt.GetGlobalTransaction().GetHash(), CoinjoinValue = originalPaymentValue - ourFeeContribution, ContributedOutPoints = selectedUTXOs.Select(o => o.Key).ToArray() }; var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true); if (payment is null) { await UnlockUTXOs(); await BroadcastNow(); return(UnprocessableEntity(CreatePayjoinError(422, "already-paid", $"The original transaction has already been accounted"))); } await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(originalTx); _eventAggregator.Publish(new InvoiceEvent(invoice, 1002, InvoiceEvent.ReceivedPayment) { Payment = payment }); if (psbtFormat && HexEncoder.IsWellFormed(rawBody)) { return(Ok(newPsbt.ToHex())); } else if (psbtFormat) { return(Ok(newPsbt.ToBase64())); } else { return(Ok(newTx.ToHex())); } }
public void CanRecoverExtKeyFromExtPubKeyAndSecret() { ExtKey key = new ExtKey().Derive(1); var underlying = key.PrivateKey.GetBitcoinSecret(Network.Main); var pubKey = key.Neuter().GetWif(Network.Main); ExtKey key2 = new ExtKey(pubKey, underlying); Assert.Equal(key.ToString(Network.Main), key2.ToString(Network.Main)); }
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 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}"); 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; InvoiceEntity invoice = null; DerivationSchemeSettings derivationSchemeSettings = null; foreach (var output in psbt.Outputs) { 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(); if (derivationSchemeSettings is null) { continue; } var receiverInputsType = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType(); if (!PayjoinClient.SupportedFormats.Contains(receiverInputsType)) { //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")); } var paymentMethod = invoice.GetPaymentMethod(paymentMethodId); var paymentDetails = paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; if (paymentDetails is null || !paymentDetails.PayjoinEnabled) { continue; } if (invoice.GetAllBitcoinPaymentData().Any()) { ctx.DoNotBroadcast(); return(UnprocessableEntity(CreatePayjoinError("already-paid", $"The invoice this PSBT is paying has already been partially or completely paid"))); } paidSomething = true; due = paymentMethod.Calculate().TotalDue - output.Value; if (due > Money.Zero) { break; } if (!await _payJoinRepository.TryLockInputs(ctx.OriginalTransaction.Inputs.Select(i => i.PrevOut).ToArray())) { 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; paymentAddress = paymentDetails.GetDepositAddress(network.NBitcoinNetwork); 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; var rand = new Random(); 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[rand.Next(0, 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.Sign(privateKey, new SigningOptions() { EnforceLowR = enforcedLowR }); 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); originalPaymentData.ConfirmationCount = -1; originalPaymentData.PayjoinInformation = new PayjoinInformation() { CoinjoinTransactionHash = GetExpectedHash(newPsbt, coins), CoinjoinValue = originalPaymentValue - ourFeeContribution, ContributedOutPoints = selectedUTXOs.Select(o => o.Key).ToArray() }; 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"))); } await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(ctx.OriginalTransaction); _eventAggregator.Publish(new InvoiceEvent(invoice, 1002, InvoiceEvent.ReceivedPayment) { Payment = payment }); _eventAggregator.Publish(new UpdateTransactionLabel() { WalletId = new WalletId(invoice.StoreId, network.CryptoCode), TransactionLabels = selectedUTXOs.GroupBy(pair => pair.Key.Hash).Select(utxo => new KeyValuePair <uint256, List <(string color, string label)> >(utxo.Key, new List <(string color, string label)>() { UpdateTransactionLabel.PayjoinExposedLabelTemplate(invoice.Id) })) .ToDictionary(pair => pair.Key, pair => pair.Value) });
public void CanRoundTripExtKeyBase58Data() { var key = new ExtKey(); var pubkey = key.Neuter(); Assert.True(ExtKey.Parse(key.ToString(Network.Main)).ToString(Network.Main) == key.ToString(Network.Main)); Assert.True(ExtPubKey.Parse(pubkey.ToString(Network.Main)).ToString(Network.Main) == pubkey.ToString(Network.Main)); }
private void ShowHistory() { outputTranstactionHistory.Rows.Clear(); MyFile myFile = new MyFile(); WalletDotDat walletDotDat = new WalletDotDat(); string text = myFile.ReadFile(path); string[] splitted = text.Split(' '); string seed; int i; if (splitted.Length != 12) { Aes aes = new Aes(); walletDotDat.FromString(aes.Decrypt(text, password)); seed = walletDotDat.mnemonics; i = walletDotDat.bitcoinSecrets.Count; } else { walletDotDat.FromString(myFile.ReadFile(path)); seed = walletDotDat.mnemonics; i = walletDotDat.bitcoinSecrets.Count; } ExtKey extKey = Wallet.generateMasterAdress(seed); int n = 0; string address = ""; for (int j = 0; j < i; j++) { bitcoinSecret = Wallet.generateDerivedAdress(extKey, j); address = bitcoinSecret.PubKey.GetAddress(Network.TestNet).ToString(); using (var client = new HttpClient()) { var result = client.GetAsync("http://tapi.qbit.ninja/balances/" + address).Result; if (result.IsSuccessStatusCode) { var content = result.Content; string transactions = content.ReadAsStringAsync().Result; JObject json = JObject.Parse(transactions); foreach (var transaction in json["operations"]) { outputTranstactionHistory.Rows.Add(); if (int.Parse(transaction["confirmations"].ToString()) > 6) { outputTranstactionHistory.Rows[n].Cells[0].Value = "6+"; } else { outputTranstactionHistory.Rows[n].Cells[0].Value = transaction["confirmations"]; } outputTranstactionHistory.Rows[n].Cells[1].Value = transaction["firstSeen"]; if (int.Parse(transaction["confirmations"].ToString()) < 6) { outputTranstactionHistory.Rows[n].Cells[2].Value = "Unconfirmed transaction"; } else { outputTranstactionHistory.Rows[n].Cells[2].Value = "Confirmed transaction"; } outputTranstactionHistory.Rows[n].Cells[3].Value = decimal.Parse(transaction["amount"].ToString()) * 0.00000001m; n++; } } } } if (outputTranstactionHistory.Rows.Count != 0) { outputTranstactionHistory.Sort(outputTranstactionHistory.Columns[1], ListSortDirection.Descending); outputTranstactionHistory.Rows[outputTranstactionHistory.Rows.Count - 1].Cells[4].Value = outputTranstactionHistory.Rows[outputTranstactionHistory.Rows.Count - 1].Cells[3].Value; for (int j = outputTranstactionHistory.Rows.Count - 2; j >= 0; j--) { outputTranstactionHistory.Rows[j].Cells[4].Value = decimal.Parse(outputTranstactionHistory.Rows[j + 1].Cells[4].Value.ToString()) + decimal.Parse(outputTranstactionHistory.Rows[j].Cells[3].Value.ToString()); } } }
public void CanCheckChildKey() { var parent = new ExtKey(); var child = parent.Derive(1); var notchild = new ExtKey(); Assert.True(child.IsChildOf(parent)); Assert.True(parent.IsParentOf(child)); Assert.False(notchild.IsChildOf(parent)); Assert.False(parent.IsParentOf(notchild)); Assert.True(child.Neuter().IsChildOf(parent.Neuter())); Assert.True(parent.Neuter().IsParentOf(child.Neuter())); Assert.False(notchild.Neuter().IsChildOf(parent.Neuter())); Assert.False(parent.Neuter().IsParentOf(notchild.Neuter())); var keyA = parent.Neuter(); var keyB = new ExtPubKey(keyA.ToBytes()); AssertEx.CollectionEquals(keyA.ToBytes(), keyB.ToBytes()); }
public void Execute(object parameter) { PrivateKey = new ExtKey().GetWif(App.Network); PubKey = PrivateKey.ExtKey.Neuter().GetWif(App.Network); }
public void CanCheckChildKey() { var parent = new ExtKey(); var child = parent.Derive(1); var notchild = new ExtKey(); Assert.True(child.IsChildOf(parent)); Assert.True(parent.IsParentOf(child)); Assert.False(notchild.IsChildOf(parent)); Assert.False(parent.IsParentOf(notchild)); Assert.True(child.Neuter().IsChildOf(parent.Neuter())); Assert.True(parent.Neuter().IsParentOf(child.Neuter())); Assert.False(notchild.Neuter().IsChildOf(parent.Neuter())); Assert.False(parent.Neuter().IsParentOf(notchild.Neuter())); }