private byte[] CalculateHash(byte[] data, int offset, int count) { byte[] hashData = data.SafeSubarray(offset, count); uint256 hash; if (this.nVersion > 6) { hash = Hashes.DoubleSHA256(hashData); } else { var x13 = new X13(); hash = new uint256(this.x13.ComputeBytes(hashData)); } return(hash.ToBytes()); }
public override bool TryGetKey(string password, [MaybeNullWhen(false)] out Key key) { var derived = SCrypt.BitcoinComputeDerivedKey(password, AddressHash); var bitcoinprivkey = DecryptKey(Encrypted, derived); key = new Key(bitcoinprivkey, fCompressedIn: IsCompressed); var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network).ToString()); var salt = Hashes.DoubleSHA256(addressBytes).ToBytes().SafeSubarray(0, 4); if (!Utils.ArrayEqual(salt, AddressHash)) { key = null; } return(key is Key); }
public void CanHandleDuplicatedValuesTest() { var byteArray0 = new byte[] { 1, 2, 3, 4 }; var byteArray1 = new byte[] { 1, 2, 3, 4 }; var byteArray2 = new byte[] { 1, 2, 3, 4 }; var filter = new GolombRiceFilterBuilder() .SetKey(Hashes.DoubleSHA256(new byte[] { 99, 99, 99, 99 })) .AddEntries(new[] { byteArray0, byteArray1, byteArray2 }) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray0)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray1)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray2)) .Build(); Assert.Equal(1, filter.N); }
public void Signatures_use_low_R() { var rnd = new Random(); for (var i = 0; i < 100; i++) { var key = new Key(); var msgLen = rnd.Next(10, 1000); var msg = new byte[msgLen]; rnd.NextBytes(msg); var sig = key.Sign(Hashes.DoubleSHA256(msg)); Assert.True(sig.IsLowR); Assert.True(sig.ToDER().Length <= 70); } }
public override Key GetKey(string password) { var derived = SCrypt.BitcoinComputeDerivedKey(password, AddressHash); var bitcoinprivkey = DecryptKey(Encrypted, derived); var key = new Key(bitcoinprivkey, fCompressedIn: IsCompressed); var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network).ToString()); var salt = Hashes.DoubleSHA256(addressBytes).ToBytes().SafeSubarray(0, 4); if (!Utils.ArrayEqual(salt, AddressHash)) { throw new SecurityException("Invalid password (or invalid Network)"); } return(key); }
public static void SignBlock(this SlimBlock slimBlock, Key blockSignatureKey) { if (slimBlock.IsProofOfStake) { var headerBytes = slimBlock.SlimBlockHeader.SerializeTo80Bytes(); slimBlock.SlimBlockHeader.Data = headerBytes; // the Data field is used in serialization var headerHash = Hashes.DoubleSHA256(headerBytes); ECDSASignature signature = blockSignatureKey.Sign(headerHash); var blockSignature = new NBitcoin.Altcoins.X1Crypto.X1BlockSignature { Signature = signature.ToDER() }; slimBlock.SignatureBytes = blockSignature.ToBytes(); } else { slimBlock.SignatureBytes = new[] { (byte)0 }; // VarString with length of zero, zero encoded as as VarInt } }
public void SignatureTest() { // Get a compressed private key. string base58 = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; BitcoinSecret bitcoinSecret = Network.Main.CreateBitcoinSecret(base58); Key privateKey = bitcoinSecret.PrivateKey; Assert.True(privateKey.IsCompressed); uint256 hashMsg = Hashes.DoubleSHA256(Encoding.ASCII.GetBytes("compact hashing test")); CompactSignature compactSignature = privateKey.SignCompact(hashMsg); Assert.NotNull(compactSignature); byte[] sigBytes = CompactSignatureJsonConverter.ToBytes(compactSignature); string hex = ByteHelpers.ToHex(sigBytes); Assert.Equal("1F71932FFF735FA6A57787191A296717F71270B2B7E1D90008B7147117F250DBDE012359EED51D28682B1AAB686A8FD8A411A8D07F1EB4D7CDAC5B7EBE73F260A0", hex); }
public override Key GetKey(string password) { var encrypted = PartialEncrypted.ToArray(); //Derive passfactor using scrypt with ownerentropy and the user's passphrase and use it to recompute passpoint byte[] passfactor = CalculatePassFactor(password, LotSequence, OwnerEntropy); var passpoint = CalculatePassPoint(passfactor); var derived = SCrypt.BitcoinComputeDerivedKey2(passpoint, this.AddressHash.Concat(this.OwnerEntropy).ToArray()); //Decrypt encryptedpart1 to yield the remainder of seedb. var seedb = DecryptSeed(encrypted, derived); var factorb = Hashes.DoubleSHA256(seedb).ToBytes(); #if HAS_SPAN var eckey = NBitcoinContext.Instance.CreateECPrivKey(passfactor).TweakMul(factorb); var key = new Key(eckey, IsCompressed); #else var curve = ECKey.Secp256k1; //Multiply passfactor by factorb mod N to yield the private key associated with generatedaddress. var keyNum = new BigInteger(1, passfactor).Multiply(new BigInteger(1, factorb)).Mod(curve.N); var keyBytes = keyNum.ToByteArrayUnsigned(); if (keyBytes.Length < 32) { keyBytes = new byte[32 - keyBytes.Length].Concat(keyBytes).ToArray(); } var key = new Key(keyBytes, fCompressedIn: IsCompressed); #endif var generatedaddress = key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network); var addresshash = HashAddress(generatedaddress); if (!Utils.ArrayEqual(addresshash, AddressHash)) { throw new SecurityException("Invalid password (or invalid Network)"); } return(key); }
private static bool Fill(StealthMetadata output, Script metadata, byte[] data) { if (data == null || data.Length != 1 + 4 + 33) { return(false); } MemoryStream ms = new MemoryStream(data); output.Version = ms.ReadByte(); if (output.Version != 6) { return(false); } output.Nonce = ms.ReadBytes(4); output.EphemKey = new PubKey(ms.ReadBytes(33)); output.Script = metadata; output.Hash = Hashes.DoubleSHA256(data); var msprefix = new MemoryStream(output.Hash.ToBytes(false)); output.BitField = Utils.ToUInt32(msprefix.ReadBytes(4), true); return(true); }
public void BuildFilterAndMatchValues() { var names = from name in new[] { "New York", "Amsterdam", "Paris", "Buenos Aires", "La Habana" } select Encoding.ASCII.GetBytes(name); var key = Hashes.DoubleSHA256(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); var filter = new GolombRiceFilterBuilder() .SetKey(key) .AddEntries(names) .SetP(0x10) .Build(); var testKey = key.ToBytes().SafeSubarray(0, 16); // The filter should match all the values that were added. foreach (var name in names) { Assert.True(filter.Match(name, testKey)); } // The filter should NOT match any extra value. Assert.False(filter.Match(Encoding.ASCII.GetBytes("Porto Alegre"), testKey)); Assert.False(filter.Match(Encoding.ASCII.GetBytes("Madrid"), testKey)); // The filter should match because it has one element indexed: Buenos Aires. var otherCities = new[] { "La Paz", "Barcelona", "El Cairo", "Buenos Aires", "Asunción" }; var otherNames = from name in otherCities select Encoding.ASCII.GetBytes(name); Assert.True(filter.MatchAny(otherNames, testKey)); // The filter should NOT match because it doesn't have any element indexed. var otherCities2 = new[] { "La Paz", "Barcelona", "El Cairo", "Córdoba", "Asunción" }; var otherNames2 = from name in otherCities2 select Encoding.ASCII.GetBytes(name); Assert.False(filter.MatchAny(otherNames2, testKey)); }
public static async Task <AddressManager> LoadAddressManagerFromPeerFileAsync(string filePath, Network?expectedNetwork = null) { byte[] data, hash; using (var fs = File.Open(filePath, FileMode.Open, FileAccess.Read)) { data = new byte[fs.Length - 32]; await fs.ReadAsync(data.AsMemory(0, data.Length)).ConfigureAwait(false); hash = new byte[32]; await fs.ReadAsync(hash.AsMemory(0, 32)).ConfigureAwait(false); } var actual = Hashes.DoubleSHA256(data); var expected = new uint256(hash); if (expected != actual) { throw new FormatException("Invalid address manager file"); } BitcoinStream stream = new(data) { Type = SerializationType.Disk }; uint magic = 0; stream.ReadWrite(ref magic); if (expectedNetwork is { } && expectedNetwork.Magic != magic)
public void CanHandleCustomPandMValuesTest() { var byteArray0 = new byte[] { 1, 2, 3, 4 }; var byteArray1 = new byte[] { 2, 3, 4 }; var byteArray2 = new byte[] { 3, 4 }; var key = Hashes.DoubleSHA256(new byte[] { 99, 99, 99, 99 }); var testKey = key.ToBytes().SafeSubarray(0, 16); var filter = new GolombRiceFilterBuilder() .SetKey(key) .SetP(10) .SetM(1U << 10) .AddEntries(new[] { byteArray0, byteArray1, byteArray2 }) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray0)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray1)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray2)) .Build(); var filterSize10_10 = filter.ToBytes().Length; Assert.Equal(3, filter.N); Assert.Equal(10, filter.P); Assert.Equal(1U << 10, filter.M); Assert.True(filter.Match(byteArray0, testKey)); Assert.True(filter.Match(byteArray1, testKey)); Assert.True(filter.Match(byteArray2, testKey)); Assert.False(filter.Match(new byte[] { 6, 7, 8 }, testKey)); filter = new GolombRiceFilterBuilder() .SetKey(key) .SetP(10) .SetM(1U << 4) .AddEntries(new[] { byteArray0, byteArray1, byteArray2 }) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray0)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray1)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray2)) .Build(); var filterSize10_4 = filter.ToBytes().Length; Assert.Equal(3, filter.N); Assert.Equal(10, filter.P); Assert.Equal(1U << 4, filter.M); Assert.True(filter.Match(byteArray0, testKey)); Assert.True(filter.Match(byteArray1, testKey)); Assert.True(filter.Match(byteArray2, testKey)); Assert.False(filter.Match(new byte[] { 6, 7, 8 }, testKey)); Assert.Equal(filterSize10_4, filterSize10_10); filter = new GolombRiceFilterBuilder() .SetKey(key) .SetP(8) .SetM(1U << 4) .AddEntries(new[] { byteArray0, byteArray1, byteArray2 }) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray0)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray1)) .AddScriptPubkey(Script.FromBytesUnsafe(byteArray2)) .Build(); var filterSize8_4 = filter.ToBytes().Length; Assert.Equal(3, filter.N); Assert.Equal(8, filter.P); Assert.Equal(1U << 4, filter.M); Assert.True(filter.Match(byteArray0, testKey)); Assert.True(filter.Match(byteArray1, testKey)); Assert.True(filter.Match(byteArray2, testKey)); Assert.False(filter.Match(new byte[] { 6, 7, 8 }, testKey)); Assert.True(filterSize8_4 < filterSize10_10); // filter size depends only on P parameter }
public void FalsePositivesTest() { // Given this library can be used for building and query filters for each block of // the bitcoin's blockchain, we must be sure it performs well, specially in the queries. // Considering a 4MB block (overestimated) with an average transaction size of 250 bytes (underestimated) // gives us 16000 transactions (this is about 27 tx/sec). Assuming 2.5 txouts per tx we have 83885 txouts // per block. const byte P = 20; const int blockCount = 100; const int maxBlockSize = 4_000_000; const int avgTxSize = 250; // Currently the average is around 1kb. const int txoutCountPerBlock = maxBlockSize / avgTxSize; const int avgTxoutPushDataSize = 20; // P2PKH scripts has 20 bytes. const int walletAddressCount = 1_000; // We estimate that our user will have 1000 addresses. var key = Hashes.DoubleSHA256(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); var testKey = key.ToBytes().SafeSubarray(0, 16); // Generation of data to be added into the filter var random = new Random(); var blocks = new List <BlockFilter>(blockCount); for (var i = 0; i < blockCount; i++) { var builder = new GolombRiceFilterBuilder() .SetKey(key) .SetP(P); var txouts = new List <byte[]>(txoutCountPerBlock); for (var j = 0; j < txoutCountPerBlock; j++) { var pushDataBuffer = new byte[avgTxoutPushDataSize]; random.NextBytes(pushDataBuffer); txouts.Add(pushDataBuffer); } builder.AddEntries(txouts); var filter = builder.Build(); blocks.Add(new BlockFilter(filter, txouts)); } var walletAddresses = new List <byte[]>(walletAddressCount); var falsePositiveCount = 0; for (var i = 0; i < walletAddressCount; i++) { var walletAddress = new byte[avgTxoutPushDataSize]; random.NextBytes(walletAddress); walletAddresses.Add(walletAddress); } // Check that the filter can match every single txout in every block. foreach (var block in blocks) { if (block.Filter.MatchAny(walletAddresses, testKey)) { falsePositiveCount++; } } Assert.True(falsePositiveCount <= 2); // Filter has to mat existing values var falseNegativeCount = 0; // Check that the filter can match every single txout in every block. foreach (var block in blocks) { if (!block.Filter.MatchAny(block.Data, testKey)) { falseNegativeCount++; } } // False negatives have to be always zero. Assert.Equal(0, falseNegativeCount); }
public async Task <SubmitTransactionsResponse> SubmitTransactionsAsync(IEnumerable <SubmitTransaction> requestEnum, UserAndIssuer user) { var request = requestEnum.ToArray(); logger.LogInformation($"Processing {request.Length} incoming transactions"); // Take snapshot of current metadata and use use it for all transactions var info = blockChainInfo.GetInfo(); var currentMinerId = await minerId.GetCurrentMinerIdAsync(); var consolidationParameters = info.ConsolidationTxParameters; // Use the same quotes for all transactions in single request var quotes = feeQuoteRepository.GetValidFeeQuotesByIdentity(user).ToArray(); if (quotes == null || !quotes.Any()) { throw new Exception("No fee quotes available"); } var responses = new List <SubmitTransactionOneResponse>(); var transactionsToSubmit = new List <(string transactionId, SubmitTransaction transaction, bool allowhighfees, bool dontCheckFees)>(); int failureCount = 0; IDictionary <uint256, byte[]> allTxs = new Dictionary <uint256, byte[]>(); foreach (var oneTx in request) { if ((oneTx.RawTx == null || oneTx.RawTx.Length == 0) && string.IsNullOrEmpty(oneTx.RawTxString)) { AddFailureResponse(null, $"{nameof(SubmitTransaction.RawTx)} is required", ref responses); failureCount++; continue; } if (oneTx.RawTx == null) { try { oneTx.RawTx = HelperTools.HexStringToByteArray(oneTx.RawTxString); } catch (Exception ex) { AddFailureResponse(null, ex.Message, ref responses); failureCount++; continue; } } uint256 txId = Hashes.DoubleSHA256(oneTx.RawTx); string txIdString = txId.ToString(); if (allTxs.ContainsKey(txId)) { AddFailureResponse(txIdString, "Transaction with this id occurs more than once within request", ref responses); failureCount++; continue; } var vc = new ValidationContext(oneTx); var errors = oneTx.Validate(vc); if (errors.Count() > 0) { AddFailureResponse(txIdString, string.Join(",", errors.Select(x => x.ErrorMessage)), ref responses); failureCount++; continue; } allTxs.Add(txId, oneTx.RawTx); bool okToMine = false; bool okToRelay = false; if (await txRepository.TransactionExistsAsync(txId.ToBytes())) { AddFailureResponse(txIdString, "Transaction already known", ref responses); failureCount++; continue; } Transaction transaction = null; CollidedWith[] colidedWith = {}; Exception exception = null; string[] prevOutsErrors = { }; try { transaction = HelperTools.ParseBytesToTransaction(oneTx.RawTx); if (transaction.IsCoinBase) { throw new ExceptionWithSafeErrorMessage("Invalid transaction - coinbase transactions are not accepted"); } var(sumPrevOuputs, prevOuts) = await CollectPreviousOuputs(transaction, new ReadOnlyDictionary <uint256, byte[]>(allTxs), rpcMultiClient); prevOutsErrors = prevOuts.Where(x => !string.IsNullOrEmpty(x.Error)).Select(x => x.Error).ToArray(); colidedWith = prevOuts.Where(x => x.CollidedWith != null).Select(x => x.CollidedWith).ToArray(); if (IsConsolidationTxn(transaction, consolidationParameters, prevOuts)) { (okToMine, okToRelay) = (true, true); } else { foreach (var feeQuote in quotes) { var(okToMineTmp, okToRelayTmp) = CheckFees(transaction, oneTx.RawTx.LongLength, sumPrevOuputs, feeQuote); if (GetCheckFeesValue(okToMineTmp, okToRelayTmp) > GetCheckFeesValue(okToMine, okToRelay)) { // save best combination (okToMine, okToRelay) = (okToMineTmp, okToRelayTmp); } } } } catch (Exception ex) { exception = ex; } if (exception != null || colidedWith.Any() || transaction == null || prevOutsErrors.Any()) { var oneResponse = new SubmitTransactionOneResponse { Txid = txIdString, ReturnResult = ResultCodes.Failure, // Include non null ConflictedWith only if a collision has been detected ConflictedWith = !colidedWith.Any() ? null : colidedWith.Select( x => new SubmitTransactionConflictedTxResponse { Txid = x.TxId, Size = x.Size, Hex = x.Hex, }).ToArray() }; if (transaction is null) { oneResponse.ResultDescription = "Can not parse transaction"; } else if (exception is ExceptionWithSafeErrorMessage) { oneResponse.ResultDescription = exception.Message; } else if (exception != null) { oneResponse.ResultDescription = "Error fetching inputs"; } else // colidedWith !=null and there is no exception or prevOutsErrors is not empty { // return "Missing inputs" regardless of error returned from gettxouts (which is usually "missing") oneResponse.ResultDescription = "Missing inputs"; } logger.LogError($"Can not calculate fee for {txIdString}. Error: {oneResponse.ResultDescription} Exception: {exception?.ToString() ?? ""}"); responses.Add(oneResponse); failureCount++; continue; } // Transactions was successfully analyzed if (!okToMine && !okToRelay) { AddFailureResponse(txIdString, "Not enough fees", ref responses); failureCount++; } else { bool allowHighFees = false; bool dontcheckfee = okToMine; oneTx.TransactionInputs = transaction.Inputs.AsIndexedInputs().Select(x => new TxInput { N = x.Index, PrevN = x.PrevOut.N, PrevTxId = x.PrevOut.Hash.ToBytes() }).ToList(); transactionsToSubmit.Add((txIdString, oneTx, allowHighFees, dontcheckfee)); } } RpcSendTransactions rpcResponse; Exception submitException = null; if (transactionsToSubmit.Any()) { // Submit all collected transactions in one call try { rpcResponse = await rpcMultiClient.SendRawTransactionsAsync( transactionsToSubmit.Select(x => (x.transaction.RawTx, x.allowhighfees, x.dontCheckFees)) .ToArray()); } catch (Exception ex) { submitException = ex; rpcResponse = null; } } else { // Simulate empty response rpcResponse = new RpcSendTransactions(); } // Initialize common fields var result = new SubmitTransactionsResponse { Timestamp = clock.UtcNow(), MinerId = currentMinerId, CurrentHighestBlockHash = info.BestBlockHash, CurrentHighestBlockHeight = info.BestBlockHeight, // TxSecondMempoolExpiry // Remaining of the fields are initialized bellow }; if (submitException != null) { var unableToSubmit = transactionsToSubmit.Select(x => new SubmitTransactionOneResponse { Txid = x.transactionId, ReturnResult = ResultCodes.Failure, ResultDescription = "Error while submitting transactions to the node" // do not expose detailed error message. It might contain internal IPS etc }); logger.LogError($"Error while submitting transactions to the node {submitException}"); responses.AddRange(unableToSubmit); result.Txs = responses.ToArray(); result.FailureCount = result.Txs.Length; // all of the transactions have failed return(result); } else // submitted without error { var(submitFailureCount, transformed) = TransformRpcResponse(rpcResponse, transactionsToSubmit.Select(x => x.transactionId).ToArray()); responses.AddRange(transformed); result.Txs = responses.ToArray(); result.FailureCount = failureCount + submitFailureCount; var successfullTxs = transactionsToSubmit.Where(x => transformed.Any(y => y.ReturnResult == ResultCodes.Success && y.Txid == x.transactionId)); await txRepository.InsertTxsAsync(successfullTxs.Select(x => new Tx { CallbackToken = x.transaction.CallbackToken, CallbackUrl = x.transaction.CallbackUrl, CallbackEncryption = x.transaction.CallbackEncryption, DSCheck = x.transaction.DsCheck, MerkleProof = x.transaction.MerkleProof, TxExternalId = new uint256(x.transactionId), TxPayload = x.transaction.RawTx, ReceivedAt = clock.UtcNow(), TxIn = x.transaction.TransactionInputs }).ToList()); return(result); } }
public async Task <ActionResult> SubmitDSAsync([FromQuery] string txId, [FromQuery] int?n, [FromQuery] string cTxId, [FromQuery] int?cn) { logger.LogInformation($"SubmitDSAsync call received for txid:'{txId}', n:'{n}', cTxId:'{cTxId}', cn:'{cn}'"); // Set response header here that we are interested in DS submit again in case of any error this.Response.Headers.Add(DSHeader, "1"); if (string.IsNullOrEmpty(txId)) { return(AddBanScoreAndReturnResult("'txid' must not be null or empty.", "")); } if (string.IsNullOrEmpty(cTxId)) { return(AddBanScoreAndReturnResult("'ctxid' must not be null or empty.", txId)); } if (!uint256.TryParse(txId, out uint256 uTxId)) { return(AddBanScoreAndReturnResult("Invalid 'txid' format.", txId)); } if (!uint256.TryParse(cTxId, out uint256 ucTxId)) { return(AddBanScoreAndReturnResult("Invalid 'ctxid' format.", txId)); } if (txId == cTxId) { return(AddBanScoreAndReturnResult("'ctxid' parameter must not be the same as 'txid'.", txId, HostBanList.WarningScore)); } if (n == null || n < 0) { return(AddBanScoreAndReturnResult("'n' must be equal or greater than 0.", txId, HostBanList.WarningScore)); } if (cn == null || cn < 0) { return(AddBanScoreAndReturnResult("'cn' must be equal or greater than 0.", txId, HostBanList.WarningScore)); } if (!transactionRequestsCheck.WasTransactionIdQueried(Request.Host.Host, uTxId)) { return(AddBanScoreAndReturnResult("Submitted transactionId was not queried before making a call to submit, or it was already submitted.", txId)); } var tx = (await txRepository.GetTxsForDSCheckAsync(new byte[][] { uTxId.ToBytes() }, true)).SingleOrDefault(); if (tx == null) { return(AddBanScoreAndReturnResult($"There is no transaction waiting for double-spend notification with given transaction id '{txId}'.", txId, HostBanList.WarningScore)); } if (n > tx.OrderderInputs.Length) { return(AddBanScoreAndReturnResult($"'n' parameter must not be greater than total number of inputs.", txId, HostBanList.WarningScore)); } transactionRequestsCheck.LogKnownTransactionId(Request.Host.Host, uTxId); byte[] dsTxBytes; using (var ms = new MemoryStream()) { await Request.Body.CopyToAsync(ms); dsTxBytes = ms.ToArray(); } if (dsTxBytes.Length == 0) { return(AddBanScoreAndReturnResult("Proof must not be empty.", txId)); } if (Hashes.DoubleSHA256(dsTxBytes) != ucTxId) { return(AddBanScoreAndReturnResult("Double-spend transaction does not match the 'ctxid' parameter.", txId)); } Transaction dsTx; try { dsTx = HelperTools.ParseBytesToTransaction(dsTxBytes); } catch (Exception) { return(AddBanScoreAndReturnResult("'dsProof' is invalid.", txId)); } if (cn > dsTx.Inputs.Count) { return(AddBanScoreAndReturnResult($"'cn' parameter must not be greater than total number of inputs.", txId)); } var dsTxIn = dsTx.Inputs[cn.Value]; var txIn = tx.OrderderInputs[n.Value]; if (!(new uint256(txIn.PrevTxId) == dsTxIn.PrevOut.Hash && txIn.PrevN == dsTxIn.PrevOut.N)) { return(AddBanScoreAndReturnResult("Transaction marked as double-spend does not spend same inputs as original transaction.", txId)); } logger.LogInformation($"Double spend checks completed successfully for '{txId}' and '{cTxId}'. Verifying script."); var scripts = new List <(string Tx, int N)>() { (dsTx.ToHex(), cn.Value) }.AsEnumerable(); // We take single result, because we will be sending only 1 tx at a time var verified = (await rpcMultiClient.VerifyScriptAsync(true, appSettings.DSScriptValidationTimeoutSec, scripts)).Single(); if (verified.Result != "ok") { return(AddBanScoreAndReturnResult($"Invalid proof script. Reason: '{verified.Description}'.", cTxId)); } logger.LogInformation($"Successfully verified script for transaction '{cTxId}'. Inserting notification data into database"); transactionRequestsCheck.RemoveQueriedTransactionId(Request.Host.Host, uTxId); var inserted = await txRepository.InsertMempoolDoubleSpendAsync( tx.TxInternalId, dsTx.GetHash(Const.NBitcoinMaxArraySize).ToBytes(), dsTxBytes); if (inserted > 0) { var notificationData = new NotificationData { TxExternalId = uTxId.ToBytes(), DoubleSpendTxId = ucTxId.ToBytes(), CallbackEncryption = tx.CallbackEncryption, CallbackToken = tx.CallbackToken, CallbackUrl = tx.CallbackUrl, TxInternalId = tx.TxInternalId, BlockHeight = -1, BlockInternalId = -1, BlockHash = null }; eventBus.Publish(new Domain.Models.Events.NewNotificationEvent { CreationDate = clock.UtcNow(), NotificationData = notificationData, NotificationType = CallbackReason.DoubleSpendAttempt, TransactionId = uTxId.ToBytes() }); logger.LogInformation($"Inserted notification push data into database for '{txId}'."); } // Submit was successfull we set the x-bsv-dsnt to 0, to signal the node we are not interested in this DS anymore this.Response.Headers[DSHeader] = "0"; return(Ok()); }
public void key_test1() { BitcoinSecret bsecret1 = Network.Main.CreateBitcoinSecret(strSecret1); BitcoinSecret bsecret2 = Network.Main.CreateBitcoinSecret(strSecret2); BitcoinSecret bsecret1C = Network.Main.CreateBitcoinSecret(strSecret1C); BitcoinSecret bsecret2C = Network.Main.CreateBitcoinSecret(strSecret2C); Assert.Throws <FormatException>(() => Network.Main.CreateBitcoinSecret(strAddressBad)); Key key1 = bsecret1.PrivateKey; Assert.True(key1.IsCompressed == false); Assert.True(bsecret1.Copy(true).PrivateKey.IsCompressed == true); Assert.True(bsecret1.Copy(true).Copy(false).IsCompressed == false); Assert.True(bsecret1.Copy(true).Copy(false).ToString() == bsecret1.ToString()); Key key2 = bsecret2.PrivateKey; Assert.True(key2.IsCompressed == false); Key key1C = bsecret1C.PrivateKey; Assert.True(key1C.IsCompressed == true); Key key2C = bsecret2C.PrivateKey; Assert.True(key1C.IsCompressed == true); PubKey pubkey1 = key1.PubKey; PubKey pubkey2 = key2.PubKey; PubKey pubkey1C = key1C.PubKey; PubKey pubkey2C = key2C.PubKey; Assert.True(addr1.Hash == pubkey1.Hash); Assert.True(addr2.Hash == pubkey2.Hash); Assert.True(addr1C.Hash == pubkey1C.Hash); Assert.True(addr2C.Hash == pubkey2C.Hash); for (int n = 0; n < 16; n++) { string strMsg = String.Format("Very secret message {0}: 11", n); if (n == 10) { //Test one long message strMsg = String.Join(",", Enumerable.Range(0, 2000).Select(i => i.ToString()).ToArray()); } uint256 hashMsg = Hashes.DoubleSHA256(TestUtils.ToBytes(strMsg)); // normal signatures ECDSASignature sign1 = null, sign2 = null, sign1C = null, sign2C = null; List <Task> tasks = new List <Task>(); sign1 = key1.Sign(hashMsg); sign2 = key2.Sign(hashMsg); sign1C = key1C.Sign(hashMsg); sign2C = key2C.Sign(hashMsg); for (int i = 0; i < 30; i++) { Assert.True(pubkey1.Verify(hashMsg, sign1)); Assert.True(pubkey2.Verify(hashMsg, sign2)); Assert.True(pubkey1C.Verify(hashMsg, sign1C)); Assert.True(pubkey2C.Verify(hashMsg, sign2C)); Assert.True(pubkey1.Verify(hashMsg, sign1)); Assert.True(!pubkey1.Verify(hashMsg, sign2)); Assert.True(pubkey1.Verify(hashMsg, sign1C)); Assert.True(!pubkey1.Verify(hashMsg, sign2C)); Assert.True(!pubkey2.Verify(hashMsg, sign1)); Assert.True(pubkey2.Verify(hashMsg, sign2)); Assert.True(!pubkey2.Verify(hashMsg, sign1C)); Assert.True(pubkey2.Verify(hashMsg, sign2C)); Assert.True(pubkey1C.Verify(hashMsg, sign1)); Assert.True(!pubkey1C.Verify(hashMsg, sign2)); Assert.True(pubkey1C.Verify(hashMsg, sign1C)); Assert.True(!pubkey1C.Verify(hashMsg, sign2C)); Assert.True(!pubkey2C.Verify(hashMsg, sign1)); Assert.True(pubkey2C.Verify(hashMsg, sign2)); Assert.True(!pubkey2C.Verify(hashMsg, sign1C)); Assert.True(pubkey2C.Verify(hashMsg, sign2C)); } // compact signatures (with key recovery) byte[] csign1 = null, csign2 = null, csign1C = null, csign2C = null; csign1 = key1.SignCompact(hashMsg); csign2 = key2.SignCompact(hashMsg); csign1C = key1C.SignCompact(hashMsg); csign2C = key2C.SignCompact(hashMsg); PubKey rkey1 = null, rkey2 = null, rkey1C = null, rkey2C = null; rkey1 = PubKey.RecoverCompact(hashMsg, csign1); rkey2 = PubKey.RecoverCompact(hashMsg, csign2); rkey1C = PubKey.RecoverCompact(hashMsg, csign1C); rkey2C = PubKey.RecoverCompact(hashMsg, csign2C); Assert.True(rkey1.ToHex() == pubkey1.ToHex()); Assert.True(rkey2.ToHex() == pubkey2.ToHex()); Assert.True(rkey1C.ToHex() == pubkey1C.ToHex()); Assert.True(rkey2C.ToHex() == pubkey2C.ToHex()); Assert.True(sign1.IsLowR && sign1.ToDER().Length <= 70); Assert.True(sign2.IsLowR && sign2.ToDER().Length <= 70); Assert.True(sign1C.IsLowR && sign1C.ToDER().Length <= 70); Assert.True(sign2C.IsLowR && sign2C.ToDER().Length <= 70); } }
/// <summary> /// </summary> /// <param name="blockHeader"></param> /// <returns></returns> public async Task <VerifyResult> VerifyBlockHeader(BlockHeaderProto blockHeader) { Guard.Argument(blockHeader, nameof(blockHeader)).NotNull(); var verifySignature = _signing.VerifySignature(blockHeader.Signature.HexToByte(), blockHeader.PublicKey.HexToByte(), blockHeader.ToFinalStream()); if (verifySignature == false) { _logger.Here().Fatal("Unable to verify the block signature"); return(VerifyResult.UnableToVerify); } var verifySloth = VerifySloth(blockHeader.Bits, blockHeader.VrfSignature.HexToByte(), blockHeader.Nonce.ToBytes(), blockHeader.Sec.ToBytes()); if (verifySloth == VerifyResult.UnableToVerify) { _logger.Here().Fatal("Unable to verify the Verified Delay Function"); return(verifySloth); } var runningDistribution = await CurrentRunningDistribution(blockHeader.Solution); var verifyCoinbase = VerifyCoinbaseTransaction(blockHeader.Transactions.First().Vout.First(), blockHeader.Solution, runningDistribution); if (verifyCoinbase == VerifyResult.UnableToVerify) { _logger.Here().Fatal("Unable to verify the coinbase transaction"); return(verifyCoinbase); } uint256 hash; using (var ts = new TangramStream()) { blockHeader.Transactions.Skip(1).ForEach(x => ts.Append(x.Stream())); hash = Hashes.DoubleSHA256(ts.ToArray()); } var verifySolution = VerifySolution(blockHeader.VrfSignature.HexToByte(), hash.ToBytes(false), blockHeader.Solution); if (verifySolution == VerifyResult.UnableToVerify) { _logger.Here().Fatal("Unable to verify the solution"); return(verifySolution); } var bits = Difficulty(blockHeader.Solution, blockHeader.Transactions.First().Vout.First().A.DivWithNanoTan()); if (blockHeader.Bits != bits) { _logger.Here().Fatal("Unable to verify the bits"); return(VerifyResult.UnableToVerify); } Trie.Put(blockHeader.ToHash(), blockHeader.ToHash()); if (!blockHeader.MerkelRoot.Equals(Trie.GetRootHash().ByteToHex())) { _logger.Here().Fatal("Unable to verify the merkel"); var key = Trie.Get(blockHeader.ToHash()); if (key != null) { Trie.Delete(blockHeader.ToHash()); } return(VerifyResult.UnableToVerify); } var verifyLockTime = VerifyLockTime(new LockTime(Utils.UnixTimeToDateTime(blockHeader.Locktime)), blockHeader.LocktimeScript); if (verifyLockTime == VerifyResult.UnableToVerify) { _logger.Here().Fatal("Unable to verify the block lock time"); return(verifyLockTime); } if (blockHeader.MerkelRoot.HexToByte().Xor(BlockZeroMerkel.HexToByte()) && blockHeader.PrevMerkelRoot.HexToByte().Xor(BlockZeroPreMerkel.HexToByte())) { return(VerifyResult.Succeed); } if (blockHeader.Height == 0) { if (!blockHeader.MerkelRoot.HexToByte().Xor(BlockZeroMerkel.HexToByte()) && !blockHeader.PrevMerkelRoot.HexToByte().Xor(BlockZeroPreMerkel.HexToByte())) { return(VerifyResult.UnableToVerify); } } var prevBlock = await _unitOfWork.HashChainRepository.GetAsync(x => new ValueTask <bool>(x.MerkelRoot.Equals(blockHeader.PrevMerkelRoot))); if (prevBlock == null) { _logger.Here().Fatal("Unable to find the previous block"); return(VerifyResult.UnableToVerify); } var verifyTransactions = await VerifyTransactions(blockHeader.Transactions); if (verifyTransactions == VerifyResult.Succeed) { return(VerifyResult.Succeed); } _logger.Here().Fatal("Unable to verify the block transactions"); return(VerifyResult.UnableToVerify); }
public EncryptedKeyResult GenerateEncryptedSecret(bool isCompressed = true, byte[] seedb = null) { //Set flagbyte. byte flagByte = 0; //Turn on bit 0x20 if the Bitcoin address will be formed by hashing the compressed public key flagByte |= isCompressed ? (byte)0x20 : (byte)0x00; flagByte |= LotSequence != null ? (byte)0x04 : (byte)0x00; //Generate 24 random bytes, call this seedb. Take SHA256(SHA256(seedb)) to yield 32 bytes, call this factorb. seedb = seedb ?? RandomUtils.GetBytes(24); var factorb = Hashes.DoubleSHA256(seedb).ToBytes(); //ECMultiply passpoint by factorb. #if HAS_SPAN if (!NBitcoinContext.Instance.TryCreatePubKey(Passpoint, out var eckey) || eckey is null) { throw new InvalidOperationException("Invalid Passpoint"); } var pubKey = new PubKey(eckey.MultTweak(factorb), isCompressed); #else var curve = ECKey.Secp256k1; var passpoint = curve.Curve.DecodePoint(Passpoint); var pubPoint = passpoint.Multiply(new BigInteger(1, factorb)); //Use the resulting EC point as a public key var pubKey = new PubKey(pubPoint.GetEncoded()); //and hash it into a Bitcoin address using either compressed or uncompressed public key //This is the generated Bitcoin address, call it generatedaddress. pubKey = isCompressed ? pubKey.Compress() : pubKey.Decompress(); #endif //call it generatedaddress. var generatedaddress = pubKey.GetAddress(ScriptPubKeyType.Legacy, Network); //Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash. var addresshash = BitcoinEncryptedSecretEC.HashAddress(generatedaddress); //Derive a second key from passpoint using scrypt //salt is addresshash + ownerentropy var derived = BitcoinEncryptedSecretEC.CalculateDecryptionKey(Passpoint, addresshash, OwnerEntropy); //Now we will encrypt seedb. var encrypted = BitcoinEncryptedSecret.EncryptSeed (seedb, derived); //0x01 0x43 + flagbyte + addresshash + ownerentropy + encryptedpart1[0...7] + encryptedpart2 which totals 39 bytes var bytes = new[] { flagByte } .Concat(addresshash) .Concat(this.OwnerEntropy) .Concat(encrypted.Take(8).ToArray()) .Concat(encrypted.Skip(16).ToArray()) .ToArray(); var encryptedSecret = new BitcoinEncryptedSecretEC(bytes, Network); return(new EncryptedKeyResult(encryptedSecret, generatedaddress, seedb, () => { //ECMultiply factorb by G, call the result pointb. The result is 33 bytes. var pointb = new Key(factorb).PubKey.ToBytes(); //The first byte is 0x02 or 0x03. XOR it by (derivedhalf2[31] & 0x01), call the resulting byte pointbprefix. var pointbprefix = (byte)(pointb[0] ^ (byte)(derived[63] & 0x01)); var pointbx = BitcoinEncryptedSecret.EncryptKey(pointb.Skip(1).ToArray(), derived); var encryptedpointb = new byte[] { pointbprefix }.Concat(pointbx).ToArray(); var confirmBytes = Network.GetVersionBytes(Base58Type.CONFIRMATION_CODE, true) .Concat(new[] { flagByte }) .Concat(addresshash) .Concat(OwnerEntropy) .Concat(encryptedpointb) .ToArray(); return new BitcoinConfirmationCode(Network.NetworkStringParser.GetBase58CheckEncoder().EncodeData(confirmBytes), Network); })); }
/// <summary> /// Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash. /// </summary> /// <param name="address"></param> /// <returns></returns> internal static byte[] HashAddress(BitcoinAddress address) { return(Hashes.DoubleSHA256(Encoders.ASCII.DecodeData(address.ToString())).ToBytes().Take(4).ToArray()); }
/// <summary> /// Computes the Merkle-root. /// </summary> /// <remarks>This implements a constant-space merkle root/path calculator, limited to 2^32 leaves.</remarks> /// <param name="leaves">Merkle tree leaves.</param> /// <param name="mutated"><c>true</c> if at least one leaf of the merkle tree has the same hash as any subtree. Otherwise: <c>false</c>.</param> public static uint256 ComputeMerkleRoot(List <uint256> leaves, out bool mutated) { mutated = false; if (leaves.Count == 0) { return(uint256.Zero); } var branch = new List <uint256>(); // subTreeHashes is an array of eagerly computed subtree hashes, indexed by tree // level (0 being the leaves). // For example, when count is 25 (11001 in binary), subTreeHashes[4] is the hash of // the first 16 leaves, subTreeHashes[3] of the next 8 leaves, and subTreeHashes[0] equal to // the last leaf. The other subTreeHashes entries are undefined. var subTreeHashes = new uint256[32]; for (int i = 0; i < subTreeHashes.Length; i++) { subTreeHashes[i] = uint256.Zero; } // Which position in inner is a hash that depends on the matching leaf. int matchLevel = -1; uint processedLeavesCount = 0; var hash = new byte[64]; // First process all leaves into subTreeHashes values. while (processedLeavesCount < leaves.Count) { uint256 currentLeaveHash = leaves[(int)processedLeavesCount]; bool match = false; processedLeavesCount++; int level; // For each of the lower bits in processedLeavesCount that are 0, do 1 step. Each // corresponds to an subTreeHash value that existed before processing the // current leaf, and each needs a hash to combine it. for (level = 0; (processedLeavesCount & (((uint)1) << level)) == 0; level++) { if (match) { branch.Add(subTreeHashes[level]); } else if (matchLevel == level) { branch.Add(currentLeaveHash); match = true; } if (!mutated) { mutated = subTreeHashes[level] == currentLeaveHash; } Buffer.BlockCopy(subTreeHashes[level].ToBytes(), 0, hash, 0, 32); Buffer.BlockCopy(currentLeaveHash.ToBytes(), 0, hash, 32, 32); currentLeaveHash = Hashes.DoubleSHA256(hash); } // Store the resulting hash at subTreeHashes position level. subTreeHashes[level] = currentLeaveHash; if (match) { matchLevel = level; } } uint256 root; { // Do a final 'sweep' over the rightmost branch of the tree to process // odd levels, and reduce everything to a single top value. // Level is the level (counted from the bottom) up to which we've sweeped. int level = 0; // As long as bit number level in processedLeavesCount is zero, skip it. It means there // is nothing left at this level. while ((processedLeavesCount & (((uint)1) << level)) == 0) { level++; } root = subTreeHashes[level]; bool match = matchLevel == level; var hashh = new byte[64]; while (processedLeavesCount != (((uint)1) << level)) { // If we reach this point, hash is a subTreeHashes value that is not the top. // We combine it with itself (Bitcoin's special rule for odd levels in // the tree) to produce a higher level one. if (match) { branch.Add(root); } // Line was added to allocate once and not twice var rootBytes = root.ToBytes(); Buffer.BlockCopy(rootBytes, 0, hash, 0, 32); Buffer.BlockCopy(rootBytes, 0, hash, 32, 32); root = Hashes.DoubleSHA256(hash); // Increment processedLeavesCount to the value it would have if two entries at this // level had existed. processedLeavesCount += (((uint)1) << level); level++; // And propagate the result upwards accordingly. while ((processedLeavesCount & (((uint)1) << level)) == 0) { if (match) { branch.Add(subTreeHashes[level]); } else if (matchLevel == level) { branch.Add(root); match = true; } Buffer.BlockCopy(subTreeHashes[level].ToBytes(), 0, hashh, 0, 32); Buffer.BlockCopy(root.ToBytes(), 0, hashh, 32, 32); root = Hashes.DoubleSHA256(hashh); level++; } } } return(root); }
public static uint256 GetDoubleSHA256(this SlimBlockHeader slimBlockHeader) { return(Hashes.DoubleSHA256(slimBlockHeader.Data)); }
public bool TxIdMustMatchHexSha256(Transaction tx) { return(tx.GetHash() == Hashes.DoubleSHA256(tx.ToBytes())); }
public async Task <SubmitTransactionsResponse> SubmitTransactionsAsync(IEnumerable <SubmitTransaction> requestEnum, UserAndIssuer user) { var request = requestEnum.ToArray(); logger.LogInformation($"Processing {request.Length} incoming transactions"); // Take snapshot of current metadata and use use it for all transactions var info = await blockChainInfo.GetInfoAsync(); var currentMinerId = await minerId.GetCurrentMinerIdAsync(); var consolidationParameters = info.ConsolidationTxParameters; // Use the same quotes for all transactions in single request var quotes = feeQuoteRepository.GetValidFeeQuotesByIdentity(user).ToArray(); if (quotes == null || !quotes.Any()) { throw new Exception("No fee quotes available"); } var responses = new List <SubmitTransactionOneResponse>(); var transactionsToSubmit = new List <(string transactionId, SubmitTransaction transaction, bool allowhighfees, bool dontCheckFees, bool listUnconfirmedAncestors, Dictionary <string, object> config)>(); int failureCount = 0; IDictionary <uint256, byte[]> allTxs = new Dictionary <uint256, byte[]>(); foreach (var oneTx in request) { if (!string.IsNullOrEmpty(oneTx.MerkleFormat) && !MerkleFormat.ValidFormats.Any(x => x == oneTx.MerkleFormat)) { AddFailureResponse(null, $"Invalid merkle format {oneTx.MerkleFormat}. Supported formats: {String.Join(",", MerkleFormat.ValidFormats)}.", ref responses); failureCount++; continue; } if ((oneTx.RawTx == null || oneTx.RawTx.Length == 0) && string.IsNullOrEmpty(oneTx.RawTxString)) { AddFailureResponse(null, $"{nameof(SubmitTransaction.RawTx)} is required", ref responses); failureCount++; continue; } if (oneTx.RawTx == null) { try { oneTx.RawTx = HelperTools.HexStringToByteArray(oneTx.RawTxString); } catch (Exception ex) { AddFailureResponse(null, ex.Message, ref responses); failureCount++; continue; } } uint256 txId = Hashes.DoubleSHA256(oneTx.RawTx); string txIdString = txId.ToString(); logger.LogInformation($"Processing transaction: { txIdString }"); if (oneTx.MerkleProof && (appSettings.DontParseBlocks.Value || appSettings.DontInsertTransactions.Value)) { AddFailureResponse(txIdString, $"Transaction requires merkle proof notification but this instance of mAPI does not support callbacks", ref responses); failureCount++; continue; } if (oneTx.DsCheck && (appSettings.DontParseBlocks.Value || appSettings.DontInsertTransactions.Value)) { AddFailureResponse(txIdString, $"Transaction requires double spend notification but this instance of mAPI does not support callbacks", ref responses); failureCount++; continue; } if (allTxs.ContainsKey(txId)) { AddFailureResponse(txIdString, "Transaction with this id occurs more than once within request", ref responses); failureCount++; continue; } var vc = new ValidationContext(oneTx); var errors = oneTx.Validate(vc); if (errors.Any()) { AddFailureResponse(txIdString, string.Join(",", errors.Select(x => x.ErrorMessage)), ref responses); failureCount++; continue; } allTxs.Add(txId, oneTx.RawTx); bool okToMine = false; bool okToRelay = false; Dictionary <string, object> policies = null; if (await txRepository.TransactionExistsAsync(txId.ToBytes())) { AddFailureResponse(txIdString, "Transaction already known", ref responses); failureCount++; continue; } Transaction transaction = null; CollidedWith[] colidedWith = Array.Empty <CollidedWith>(); Exception exception = null; string[] prevOutsErrors = Array.Empty <string>(); try { transaction = HelperTools.ParseBytesToTransaction(oneTx.RawTx); if (transaction.IsCoinBase) { throw new ExceptionWithSafeErrorMessage("Invalid transaction - coinbase transactions are not accepted"); } var(sumPrevOuputs, prevOuts) = await CollectPreviousOuputs(transaction, new ReadOnlyDictionary <uint256, byte[]>(allTxs), rpcMultiClient); prevOutsErrors = prevOuts.Where(x => !string.IsNullOrEmpty(x.Error)).Select(x => x.Error).ToArray(); colidedWith = prevOuts.Where(x => x.CollidedWith != null).Select(x => x.CollidedWith).ToArray(); logger.LogInformation($"CollectPreviousOuputs for {txIdString} returned { prevOuts.Length } prevOuts ({prevOutsErrors.Length } prevOutsErrors, {colidedWith.Length} colidedWith)."); if (appSettings.CheckFeeDisabled.Value || IsConsolidationTxn(transaction, consolidationParameters, prevOuts)) { logger.LogInformation($"{txIdString}: appSettings.CheckFeeDisabled { appSettings.CheckFeeDisabled }"); (okToMine, okToRelay) = (true, true); } else { logger.LogInformation($"Starting with CheckFees calculation for {txIdString} and { quotes.Length} quotes."); foreach (var feeQuote in quotes) { var(okToMineTmp, okToRelayTmp) = CheckFees(transaction, oneTx.RawTx.LongLength, sumPrevOuputs, feeQuote); if (GetCheckFeesValue(okToMineTmp, okToRelayTmp) > GetCheckFeesValue(okToMine, okToRelay)) { // save best combination (okToMine, okToRelay, policies) = (okToMineTmp, okToRelayTmp, feeQuote.PoliciesDict); } } logger.LogInformation($"Finished with CheckFees calculation for {txIdString} and { quotes.Length} quotes: { (okToMine, okToRelay, policies == null ? "" : string.Join(";", policies.Select(x => x.Key + "=" + x.Value)) )}."); } } catch (Exception ex) { exception = ex; } if (exception != null || colidedWith.Any() || transaction == null || prevOutsErrors.Any()) { var oneResponse = new SubmitTransactionOneResponse { Txid = txIdString, ReturnResult = ResultCodes.Failure, // Include non null ConflictedWith only if a collision has been detected ConflictedWith = !colidedWith.Any() ? null : colidedWith.Select( x => new SubmitTransactionConflictedTxResponse { Txid = x.TxId, Size = x.Size, Hex = x.Hex, }).ToArray() }; if (transaction is null) { oneResponse.ResultDescription = "Can not parse transaction"; } else if (exception is ExceptionWithSafeErrorMessage) { oneResponse.ResultDescription = exception.Message; } else if (exception != null) { oneResponse.ResultDescription = "Error fetching inputs"; } else if (oneResponse.ConflictedWith != null && oneResponse.ConflictedWith.Any(c => c.Txid == oneResponse.Txid)) { oneResponse.ResultDescription = "Transaction already in the mempool"; oneResponse.ConflictedWith = null; } else { // return "Missing inputs" regardless of error returned from gettxouts (which is usually "missing") oneResponse.ResultDescription = "Missing inputs"; } logger.LogError($"Can not calculate fee for {txIdString}. Error: {oneResponse.ResultDescription} Exception: {exception?.ToString() ?? ""}"); responses.Add(oneResponse); failureCount++; continue; } // Transactions was successfully analyzed if (!okToMine && !okToRelay) { AddFailureResponse(txIdString, "Not enough fees", ref responses); failureCount++; } else { bool allowHighFees = false; bool dontcheckfee = okToMine; bool listUnconfirmedAncestors = false; oneTx.TransactionInputs = transaction.Inputs.AsIndexedInputs().Select(x => new TxInput { N = x.Index, PrevN = x.PrevOut.N, PrevTxId = x.PrevOut.Hash.ToBytes() }).ToList(); if (oneTx.DsCheck) { foreach (TxInput txInput in oneTx.TransactionInputs) { var prevOut = await txRepository.GetPrevOutAsync(txInput.PrevTxId, txInput.PrevN); if (prevOut == null) { listUnconfirmedAncestors = true; break; } } } transactionsToSubmit.Add((txIdString, oneTx, allowHighFees, dontcheckfee, listUnconfirmedAncestors, policies)); } } logger.LogInformation($"TransactionsToSubmit: { transactionsToSubmit.Count }: { string.Join("; ", transactionsToSubmit.Select(x => x.transactionId))} "); RpcSendTransactions rpcResponse; Exception submitException = null; if (transactionsToSubmit.Any()) { // Submit all collected transactions in one call try { rpcResponse = await rpcMultiClient.SendRawTransactionsAsync( transactionsToSubmit.Select(x => (x.transaction.RawTx, x.allowhighfees, x.dontCheckFees, x.listUnconfirmedAncestors, x.config)) .ToArray()); } catch (Exception ex) { submitException = ex; rpcResponse = null; } } else { // Simulate empty response rpcResponse = new RpcSendTransactions(); } // Initialize common fields var result = new SubmitTransactionsResponse { Timestamp = clock.UtcNow(), MinerId = currentMinerId, CurrentHighestBlockHash = info.BestBlockHash, CurrentHighestBlockHeight = info.BestBlockHeight, // TxSecondMempoolExpiry // Remaining of the fields are initialized bellow }; if (submitException != null) { var unableToSubmit = transactionsToSubmit.Select(x => new SubmitTransactionOneResponse { Txid = x.transactionId, ReturnResult = ResultCodes.Failure, ResultDescription = "Error while submitting transactions to the node" // do not expose detailed error message. It might contain internal IPS etc }); logger.LogError($"Error while submitting transactions to the node {submitException}"); responses.AddRange(unableToSubmit); result.Txs = responses.ToArray(); result.FailureCount = result.Txs.Length; // all of the transactions have failed return(result); } else // submitted without error { var(submitFailureCount, transformed) = TransformRpcResponse(rpcResponse, transactionsToSubmit.Select(x => x.transactionId).ToArray()); responses.AddRange(transformed); result.Txs = responses.ToArray(); result.FailureCount = failureCount + submitFailureCount; if (!appSettings.DontInsertTransactions.Value) { var successfullTxs = transactionsToSubmit.Where(x => transformed.Any(y => y.ReturnResult == ResultCodes.Success && y.Txid == x.transactionId)); logger.LogInformation($"Starting with InsertTxsAsync: { successfullTxs.Count() }: { string.Join("; ", successfullTxs.Select(x => x.transactionId))} (TransactionsToSubmit: { transactionsToSubmit.Count })"); var watch = System.Diagnostics.Stopwatch.StartNew(); await txRepository.InsertTxsAsync(successfullTxs.Select(x => new Tx { CallbackToken = x.transaction.CallbackToken, CallbackUrl = x.transaction.CallbackUrl, CallbackEncryption = x.transaction.CallbackEncryption, DSCheck = x.transaction.DsCheck, MerkleProof = x.transaction.MerkleProof, MerkleFormat = x.transaction.MerkleFormat, TxExternalId = new uint256(x.transactionId), TxPayload = x.transaction.RawTx, ReceivedAt = clock.UtcNow(), TxIn = x.transaction.TransactionInputs }).ToList(), false); long unconfirmedAncestorsCount = 0; if (rpcResponse.Unconfirmed != null) { List <Tx> unconfirmedAncestors = new(); foreach (var unconfirmed in rpcResponse.Unconfirmed) { unconfirmedAncestors.AddRange(unconfirmed.Ancestors.Select(u => new Tx { TxExternalId = new uint256(u.Txid), ReceivedAt = clock.UtcNow(), TxIn = u.Vin.Select(i => new TxInput() { PrevTxId = (new uint256(i.Txid)).ToBytes(), PrevN = i.Vout }).ToList() }) ); } await txRepository.InsertTxsAsync(unconfirmedAncestors, true); unconfirmedAncestorsCount = unconfirmedAncestors.Count; } watch.Stop(); logger.LogInformation($"Finished with InsertTxsAsync: { successfullTxs.Count() } found unconfirmedAncestors { unconfirmedAncestorsCount } took {watch.ElapsedMilliseconds} ms."); } return(result); } }