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);
        }
Exemplo n.º 3
0
        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);
            }
        }
Exemplo n.º 5
0
        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);
        }
Exemplo n.º 6
0
        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
            }
        }
Exemplo n.º 7
0
    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);
    }
Exemplo n.º 8
0
        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);
        }
Exemplo n.º 10
0
        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));
        }
Exemplo n.º 11
0
        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)
Exemplo n.º 12
0
        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
        }
Exemplo n.º 13
0
        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);
        }
Exemplo n.º 14
0
        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);
            }
        }
Exemplo n.º 15
0
        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());
        }
Exemplo n.º 16
0
        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);
            }
        }
Exemplo n.º 17
0
        /// <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);
            }));
        }
Exemplo n.º 19
0
 /// <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());
 }
Exemplo n.º 20
0
        /// <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);
        }
Exemplo n.º 21
0
 public static uint256 GetDoubleSHA256(this SlimBlockHeader slimBlockHeader)
 {
     return(Hashes.DoubleSHA256(slimBlockHeader.Data));
 }
 public bool TxIdMustMatchHexSha256(Transaction tx)
 {
     return(tx.GetHash() == Hashes.DoubleSHA256(tx.ToBytes()));
 }
Exemplo n.º 23
0
        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);
            }
        }