public static PSBT Parse(string hexOrBase64, Network network)
        {
            if (network == null)
            {
                throw new ArgumentNullException(nameof(network));
            }
            if (hexOrBase64 == null)
            {
                throw new ArgumentNullException(nameof(hexOrBase64));
            }
            if (network == null)
            {
                throw new ArgumentNullException(nameof(network));
            }
            byte[] raw;
            if (HexEncoder.IsWellFormed(hexOrBase64))
            {
                raw = Encoders.Hex.DecodeData(hexOrBase64);
            }
            else
            {
                raw = Encoders.Base64.DecodeData(hexOrBase64);
            }

            return(Load(raw, network));
        }
Example #2
0
        public static Script ParseScript(string s)
        {
            MemoryStream result = new MemoryStream();

            if (mapOpNames.Count == 0)
            {
                mapOpNames = new Dictionary <string, OpcodeType>(Op._OpcodeByName);
                foreach (var kv in mapOpNames.ToArray())
                {
                    if (kv.Key.StartsWith("OP_", StringComparison.Ordinal))
                    {
                        var name = kv.Key.Substring(3, kv.Key.Length - 3);
                        mapOpNames.AddOrReplace(name, kv.Value);
                    }
                }
            }

            var words = s.Split(' ', '\t', '\n');

            foreach (string w in words)
            {
                if (w == "")
                {
                    continue;
                }
                if (w.All(l => l.IsDigit()) ||
                    (w.StartsWith("-") && w.Substring(1).All(l => l.IsDigit())))
                {
                    // Number
                    long n = long.Parse(w);
                    Op.GetPushOp(new BigInteger(n)).WriteTo(result);
                }
                else if (w.StartsWith("0x") && HexEncoder.IsWellFormed(w.Substring(2)))
                {
                    // Raw hex data, inserted NOT pushed onto stack:
                    var raw = Encoders.Hex.DecodeData(w.Substring(2));
                    result.Write(raw, 0, raw.Length);
                }
                else if (w.Length >= 2 && w.StartsWith("'") && w.EndsWith("'"))
                {
                    // Single-quoted string, pushed as data. NOTE: this is poor-man's
                    // parsing, spaces/tabs/newlines in single-quoted strings won't work.
                    var b = TestUtils.ToBytes(w.Substring(1, w.Length - 2));
                    Op.GetPushOp(b).WriteTo(result);
                }
                else if (mapOpNames.ContainsKey(w))
                {
                    // opcode, e.g. OP_ADD or ADD:
                    result.WriteByte((byte)mapOpNames[w]);
                }
                else
                {
                    Assert.True(false, "Invalid test");
                    return(null);
                }
            }

            return(new Script(result.ToArray()));
        }
Example #3
0
 public static Slip21Node FromSeed(string seed)
 {
     if (HexEncoder.IsWellFormed(seed))
     {
         return(FromSeed(Encoders.Hex.DecodeData(seed)));
     }
     return(FromSeed(Encoding.ASCII.GetBytes(seed)));
 }
Example #4
0
        public static Offer ParseFromTLV(string hexOrBase64, Network network)
        {
            var bytes  = HexEncoder.IsWellFormed(hexOrBase64) ? Encoders.Hex.DecodeData(hexOrBase64) : Encoders.Base64.DecodeData(hexOrBase64);
            var reader = new TLVReader(new MemoryStream(bytes));
            var offer  = new Offer();

            offer.ReadTLV(reader, network);
            return(offer);
        }
Example #5
0
        public static Accept ParseFromTLV(string hexOrBase64, Network network)
        {
            var bytes  = HexEncoder.IsWellFormed(hexOrBase64) ? Encoders.Hex.DecodeData(hexOrBase64) : Encoders.Base64.DecodeData(hexOrBase64);
            var reader = new TLVReader(new MemoryStream(bytes));
            var accept = new Accept();

            accept.ReadTLV(reader, network);
            return(accept);
        }
Example #6
0
        public static Sign ParseFromTLV(string hexOrBase64, Network network)
        {
            var bytes  = HexEncoder.IsWellFormed(hexOrBase64) ? Encoders.Hex.DecodeData(hexOrBase64) : Encoders.Base64.DecodeData(hexOrBase64);
            var reader = new TLVReader(new MemoryStream(bytes));
            var sign   = new Sign();

            sign.ReadTLV(reader, network);
            return(sign);
        }
Example #7
0
 public Slip21Node DeriveChild(string label)
 {
     if (string.IsNullOrEmpty(label))
     {
         throw new ArgumentException("label must not be null or empty", nameof(label));
     }
     if (HexEncoder.IsWellFormed(label))
     {
         return(DeriveChild(Encoders.Hex.DecodeData(label)));
     }
     return(DeriveChild(Encoding.ASCII.GetBytes(label)));
 }
        public static bool TryParse(string str, out SSHFingerprint fingerPrint)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }
            fingerPrint = null;
            str         = str.Trim();
            try
            {
                var shortFingerprint = str.Replace(":", "", StringComparison.OrdinalIgnoreCase);
                if (HexEncoder.IsWellFormed(shortFingerprint))
                {
                    var hash = Encoders.Hex.DecodeData(shortFingerprint);
                    if (hash.Length == 16)
                    {
                        fingerPrint = new SSHFingerprint(hash);
                        return(true);
                    }
                    return(false);
                }
            }
            catch
            {
            }

            if (str.StartsWith("SHA256:", StringComparison.OrdinalIgnoreCase))
            {
                str = str.Substring("SHA256:".Length).Trim();
            }
            if (str.Contains(':', StringComparison.OrdinalIgnoreCase))
            {
                return(false);
            }
            if (!str.EndsWith('='))
            {
                str = str + "=";
            }
            try
            {
                var hash = Encoders.Base64.DecodeData(str);
                if (hash.Length == 32)
                {
                    fingerPrint = new SSHFingerprint(hash);
                    return(true);
                }
            }
            catch
            {
            }
            return(false);
        }
Example #9
0
 private ushort GetTLVType(string hexOrBase64)
 {
     try
     {
         var data = HexEncoder.IsWellFormed(hexOrBase64) ? Encoders.Hex.DecodeData(hexOrBase64) : Encoders.Base64.DecodeData(hexOrBase64);
         var r    = new TLVReader(new MemoryStream(data));
         return(r.ReadU16());
     }
     catch
     {
     }
     return(0);
 }
Example #10
0
        public void util_IsHex()
        {
            Assert.True(HexEncoder.IsWellFormed("00"));
            Assert.True(HexEncoder.IsWellFormed("00112233445566778899aabbccddeeffAABBCCDDEEFF"));
            Assert.True(HexEncoder.IsWellFormed("ff"));
            Assert.True(HexEncoder.IsWellFormed("FF"));

            Assert.True(HexEncoder.IsWellFormed(""));
            Assert.True(!HexEncoder.IsWellFormed("0"));
            Assert.True(!HexEncoder.IsWellFormed("a"));
            Assert.True(!HexEncoder.IsWellFormed("eleven"));
            Assert.True(!HexEncoder.IsWellFormed("00xx00"));
            Assert.True(!HexEncoder.IsWellFormed("0x0000"));
        }
Example #11
0
 public static bool TryParse(string str, out HDFingerprint result)
 {
     if (str == null)
     {
         throw new ArgumentNullException(nameof(str));
     }
     result = default;
     if (!HexEncoder.IsWellFormed(str) || str.Length != 4 * 2)
     {
         return(false);
     }
     result = new HDFingerprint(Encoders.Hex.DecodeData(str));
     return(true);
 }
Example #12
0
        public static bool TryParse(string str, [MaybeNullWhen(false)] out OracleId id)
        {
            id = null;
            if (!HexEncoder.IsWellFormed(str))
            {
                return(false);
            }
            var bytes = Encoders.Hex.DecodeData(str);

            if (!ECXOnlyPubKey.TryCreate(bytes, Context.Instance, out var k) || k is null)
            {
                return(false);
            }
            id = new OracleId(k);
            return(true);
        }
        public static PSBT Parse(string hexOrBase64, ConsensusFactory consensusFactory)
        {
            if (hexOrBase64 == null)
            {
                throw new ArgumentNullException(nameof(hexOrBase64));
            }
            if (consensusFactory == null)
            {
                throw new ArgumentNullException(nameof(consensusFactory));
            }
            byte[] raw;
            if (HexEncoder.IsWellFormed(hexOrBase64))
            {
                raw = Encoders.Hex.DecodeData(hexOrBase64);
            }
            else
            {
                raw = Encoders.Base64.DecodeData(hexOrBase64);
            }

            return(Load(raw, consensusFactory));
        }
Example #14
0
        private static Command Connect()
        {
            var c   = new Command("connect", "connect to other lightning node");
            var op1 = new Option(new [] { "--nodeid", "--pubkey" }, "hex-encoded public key of the node we want to connect to")
            {
                Argument = new Argument <string>
                {
                    Arity = ArgumentArity.ExactlyOne
                }
            };

            op1.AddValidator(r =>
            {
                var msg = "Must specify valid pubkey in hex";
                var v   = r.GetValueOrDefault <string>();

                if (String.IsNullOrEmpty(v))
                {
                    return(msg);
                }

                if (!HexEncoder.IsWellFormed(v))
                {
                    return($"Invalid hex for pubkey: {v}");
                }

                var hexEncoder = new HexEncoder();
                var b          = hexEncoder.DecodeData(v);

                if (!PubKey.Check(b, true))
                {
                    return($"Invalid pubkey {v}");
                }
                return(null);
            });
            c.AddOption(op1);

            var op2 = new Option("--host", "ip:port pair of the node")
            {
                Argument = new Argument <string>
                {
                    Arity = ArgumentArity.ExactlyOne
                }
            };

            op2.AddValidator(r =>
            {
                var v = r.GetValueOrDefault <string>();

                if (String.IsNullOrEmpty(v))
                {
                    return("Invalid host");
                }

                if (!NBitcoin.Utils.TryParseEndpoint(v, 80, out var _))
                {
                    return("Invalid host");
                }

                return(null);
            });
            c.AddOption(op2);
            return(c);
        }
Example #15
0
        public async Task <IActionResult> Submit(string cryptoCode)
        {
            var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode);

            if (network == null)
            {
                return(BadRequest(CreatePayjoinError(400, "invalid-network", "Incorrect network")));
            }

            var explorer = _explorerClientProvider.GetExplorerClient(network);

            if (Request.ContentLength is long length)
            {
                if (length > 1_000_000)
                {
                    return(this.StatusCode(413,
                                           CreatePayjoinError(413, "payload-too-large", "The transaction is too big to be processed")));
                }
            }
            else
            {
                return(StatusCode(411,
                                  CreatePayjoinError(411, "missing-content-length",
                                                     "The http header Content-Length should be filled")));
            }

            string rawBody;

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

            Transaction originalTx      = null;
            FeeRate     originalFeeRate = null;
            bool        psbtFormat      = true;

            if (!PSBT.TryParse(rawBody, network.NBitcoinNetwork, out var psbt))
            {
                psbtFormat = false;
                if (!Transaction.TryParse(rawBody, network.NBitcoinNetwork, out var tx))
                {
                    return(BadRequest(CreatePayjoinError(400, "invalid-format", "invalid transaction or psbt")));
                }
                originalTx = tx;
                psbt       = PSBT.FromTransaction(tx, network.NBitcoinNetwork);
                psbt       = (await explorer.UpdatePSBTAsync(new UpdatePSBTRequest()
                {
                    PSBT = psbt
                })).PSBT;
                for (int i = 0; i < tx.Inputs.Count; i++)
                {
                    psbt.Inputs[i].FinalScriptSig     = tx.Inputs[i].ScriptSig;
                    psbt.Inputs[i].FinalScriptWitness = tx.Inputs[i].WitScript;
                }
            }
            else
            {
                if (!psbt.IsAllFinalized())
                {
                    return(BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT should be finalized")));
                }
                originalTx = psbt.ExtractTransaction();
            }

            async Task BroadcastNow()
            {
                await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx);
            }

            var sendersInputType = psbt.GetInputsScriptPubKeyType();

            if (sendersInputType is null)
            {
                return(BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support segwit inputs (of the same type)")));
            }
            if (psbt.CheckSanity() is var errors && errors.Count != 0)
            {
                return(BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})")));
            }
            if (!psbt.TryGetEstimatedFeeRate(out originalFeeRate))
            {
                return(BadRequest(CreatePayjoinError(400, "need-utxo-information",
                                                     "You need to provide Witness UTXO information to the PSBT.")));
            }

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

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

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

            var mempool = await explorer.BroadcastAsync(originalTx, true);

            if (!mempool.Success)
            {
                return(BadRequest(CreatePayjoinError(400, "invalid-transaction",
                                                     $"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}")));
            }

            var   paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
            bool  paidSomething   = false;
            Money due             = null;
            Dictionary <OutPoint, UTXO> selectedUTXOs = new Dictionary <OutPoint, UTXO>();

            async Task UnlockUTXOs()
            {
                await _payJoinRepository.TryUnlock(selectedUTXOs.Select(o => o.Key).ToArray());
            }

            PSBTOutput               originalPaymentOutput = null;
            BitcoinAddress           paymentAddress        = null;
            InvoiceEntity            invoice = null;
            DerivationSchemeSettings derivationSchemeSettings = null;

            foreach (var output in psbt.Outputs)
            {
                var key = output.ScriptPubKey.Hash + "#" + network.CryptoCode.ToUpperInvariant();
                invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { key })).FirstOrDefault();
                if (invoice is null)
                {
                    continue;
                }
                derivationSchemeSettings = invoice.GetSupportedPaymentMethod <DerivationSchemeSettings>(paymentMethodId)
                                           .SingleOrDefault();
                if (derivationSchemeSettings is null)
                {
                    continue;
                }

                var receiverInputsType = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType();
                if (!PayjoinClient.SupportedFormats.Contains(receiverInputsType))
                {
                    //this should never happen, unless the store owner changed the wallet mid way through an invoice
                    return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now")));
                }
                if (sendersInputType != receiverInputsType)
                {
                    return(StatusCode(503,
                                      CreatePayjoinError(503, "out-of-utxos",
                                                         "We do not have any UTXO available for making a payjoin with the sender's inputs type")));
                }
                var paymentMethod  = invoice.GetPaymentMethod(paymentMethodId);
                var paymentDetails =
                    paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
                if (paymentDetails is null || !paymentDetails.PayjoinEnabled)
                {
                    continue;
                }
                if (invoice.GetAllBitcoinPaymentData().Any())
                {
                    return(UnprocessableEntity(CreatePayjoinError(422, "already-paid",
                                                                  $"The invoice this PSBT is paying has already been partially or completely paid")));
                }

                paidSomething = true;
                due           = paymentMethod.Calculate().TotalDue - output.Value;
                if (due > Money.Zero)
                {
                    break;
                }

                if (!await _payJoinRepository.TryLockInputs(originalTx.Inputs.Select(i => i.PrevOut).ToArray()))
                {
                    return(BadRequest(CreatePayjoinError(400, "inputs-already-used",
                                                         "Some of those inputs have already been used to make payjoin transaction")));
                }

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

                originalPaymentOutput = output;
                paymentAddress        = paymentDetails.GetDepositAddress(network.NBitcoinNetwork);
                break;
            }

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

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

            if (selectedUTXOs.Count == 0)
            {
                await BroadcastNow();

                return(StatusCode(503,
                                  CreatePayjoinError(503, "out-of-utxos",
                                                     "We do not have any UTXO available for making a payjoin for now")));
            }

            var originalPaymentValue = originalPaymentOutput.Value;
            await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1.0), originalTx, network);

            //check if wallet of store is configured to be hot wallet
            var extKeyStr = await explorer.GetMetadataAsync <string>(
                derivationSchemeSettings.AccountDerivation,
                WellknownMetadataKeys.AccountHDKey);

            if (extKeyStr == null)
            {
                // This should not happen, as we check the existance of private key before creating invoice with payjoin
                await UnlockUTXOs();
                await BroadcastNow();

                return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now")));
            }

            Money           contributedAmount = Money.Zero;
            var             newTx             = originalTx.Clone();
            var             ourNewOutput      = newTx.Outputs[originalPaymentOutput.Index];
            HashSet <TxOut> isOurOutput       = new HashSet <TxOut>();

            isOurOutput.Add(ourNewOutput);
            foreach (var selectedUTXO in selectedUTXOs.Select(o => o.Value))
            {
                contributedAmount += (Money)selectedUTXO.Value;
                newTx.Inputs.Add(selectedUTXO.Outpoint);
            }
            ourNewOutput.Value += contributedAmount;
            var minRelayTxFee = this._dashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ??
                                new FeeRate(1.0m);

            // Probably receiving some spare change, let's add an output to make
            // it looks more like a normal transaction
            if (newTx.Outputs.Count == 1)
            {
                var change = await explorer.GetUnusedAsync(derivationSchemeSettings.AccountDerivation, DerivationFeature.Change);

                var randomChangeAmount = RandomUtils.GetUInt64() % (ulong)contributedAmount.Satoshi;

                // Randomly round the amount to make the payment output look like a change output
                var roundMultiple = (ulong)Math.Pow(10, (ulong)Math.Log10(randomChangeAmount));
                while (roundMultiple > 1_000UL)
                {
                    if (RandomUtils.GetUInt32() % 2 == 0)
                    {
                        roundMultiple = roundMultiple / 10;
                    }
                    else
                    {
                        randomChangeAmount = (randomChangeAmount / roundMultiple) * roundMultiple;
                        break;
                    }
                }

                var fakeChange = newTx.Outputs.CreateNewTxOut(randomChangeAmount, change.ScriptPubKey);
                if (fakeChange.IsDust(minRelayTxFee))
                {
                    randomChangeAmount = fakeChange.GetDustThreshold(minRelayTxFee);
                    fakeChange.Value   = randomChangeAmount;
                }
                if (randomChangeAmount < contributedAmount)
                {
                    ourNewOutput.Value -= fakeChange.Value;
                    newTx.Outputs.Add(fakeChange);
                    isOurOutput.Add(fakeChange);
                }
            }

            var rand = new Random();

            Utils.Shuffle(newTx.Inputs, rand);
            Utils.Shuffle(newTx.Outputs, rand);

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

            Money ourFeeContribution = Money.Zero;
            // We need to adjust the fee to keep a constant fee rate
            var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder();

            txBuilder.AddCoins(psbt.Inputs.Select(i => i.GetSignableCoin()));
            txBuilder.AddCoins(selectedUTXOs.Select(o => o.Value.AsCoin(derivationSchemeSettings.AccountDerivation)));
            Money expectedFee   = txBuilder.EstimateFees(newTx, originalFeeRate);
            Money actualFee     = newTx.GetFee(txBuilder.FindSpentCoins(newTx));
            Money additionalFee = expectedFee - actualFee;

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

                // The rest, we take from user's change
                for (int i = 0; i < newTx.Outputs.Count && additionalFee > Money.Zero; i++)
                {
                    if (!isOurOutput.Contains(newTx.Outputs[i]))
                    {
                        var outputContribution = Money.Min(additionalFee, newTx.Outputs[i].Value);
                        outputContribution = Money.Min(outputContribution,
                                                       newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee));
                        newTx.Outputs[i].Value -= outputContribution;
                        additionalFee          -= outputContribution;
                    }
                }

                if (additionalFee > Money.Zero)
                {
                    // We could not pay fully the additional fee, however, as long as
                    // we are not under the relay fee, it should be OK.
                    var newVSize   = txBuilder.EstimateSize(newTx, true);
                    var newFeePaid = newTx.GetFee(txBuilder.FindSpentCoins(newTx));
                    if (new FeeRate(newFeePaid, newVSize) < minRelayTxFee)
                    {
                        await UnlockUTXOs();
                        await BroadcastNow();

                        return(UnprocessableEntity(CreatePayjoinError(422, "not-enough-money",
                                                                      "Not enough money is sent to pay for the additional payjoin inputs")));
                    }
                }
            }

            var accountKey = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
            var newPsbt    = PSBT.FromTransaction(newTx, network.NBitcoinNetwork);

            foreach (var selectedUtxo in selectedUTXOs.Select(o => o.Value))
            {
                var signedInput = newPsbt.Inputs.FindIndexedInput(selectedUtxo.Outpoint);
                var coin        = selectedUtxo.AsCoin(derivationSchemeSettings.AccountDerivation);
                signedInput.UpdateFromCoin(coin);
                var privateKey = accountKey.Derive(selectedUtxo.KeyPath).PrivateKey;
                signedInput.Sign(privateKey);
                signedInput.FinalizeInput();
                newTx.Inputs[signedInput.Index].WitScript = newPsbt.Inputs[(int)signedInput.Index].FinalScriptWitness;
            }

            // Add the transaction to the payments with a confirmation of -1.
            // This will make the invoice paid even if the user do not
            // broadcast the payjoin.
            var originalPaymentData = new BitcoinLikePaymentData(paymentAddress,
                                                                 originalPaymentOutput.Value,
                                                                 new OutPoint(originalTx.GetHash(), originalPaymentOutput.Index),
                                                                 originalTx.RBF);

            originalPaymentData.ConfirmationCount  = -1;
            originalPaymentData.PayjoinInformation = new PayjoinInformation()
            {
                CoinjoinTransactionHash = newPsbt.GetGlobalTransaction().GetHash(),
                CoinjoinValue           = originalPaymentValue - ourFeeContribution,
                ContributedOutPoints    = selectedUTXOs.Select(o => o.Key).ToArray()
            };
            var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true);

            if (payment is null)
            {
                await UnlockUTXOs();
                await BroadcastNow();

                return(UnprocessableEntity(CreatePayjoinError(422, "already-paid",
                                                              $"The original transaction has already been accounted")));
            }
            await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(originalTx);

            _eventAggregator.Publish(new InvoiceEvent(invoice, 1002, InvoiceEvent.ReceivedPayment)
            {
                Payment = payment
            });

            if (psbtFormat && HexEncoder.IsWellFormed(rawBody))
            {
                return(Ok(newPsbt.ToHex()));
            }
            else if (psbtFormat)
            {
                return(Ok(newPsbt.ToBase64()));
            }
            else
            {
                return(Ok(newTx.ToHex()));
            }
        }
Example #16
0
        public static bool TryParseFromWalletFile(string fileContents, BTCPayNetwork network, out DerivationSchemeSettings settings)
        {
            settings = null;
            if (fileContents == null)
            {
                throw new ArgumentNullException(nameof(fileContents));
            }
            if (network == null)
            {
                throw new ArgumentNullException(nameof(network));
            }
            var     result = new DerivationSchemeSettings();
            var     derivationSchemeParser = new DerivationSchemeParser(network);
            JObject jobj = null;

            try
            {
                if (HexEncoder.IsWellFormed(fileContents))
                {
                    fileContents = Encoding.UTF8.GetString(Encoders.Hex.DecodeData(fileContents));
                }
                jobj = JObject.Parse(fileContents);
            }
            catch
            {
                result.Source = "GenericFile";
                if (TryParseXpub(fileContents, derivationSchemeParser, ref result) ||
                    TryParseXpub(fileContents, derivationSchemeParser, ref result, false))
                {
                    settings         = result;
                    settings.Network = network;
                    return(true);
                }

                return(false);
            }

            // Electrum
            if (jobj.ContainsKey("keystore"))
            {
                result.Source = "ElectrumFile";
                jobj          = (JObject)jobj["keystore"];

                if (!jobj.ContainsKey("xpub") ||
                    !TryParseXpub(jobj["xpub"].Value <string>(), derivationSchemeParser, ref result))
                {
                    return(false);
                }

                if (jobj.ContainsKey("label"))
                {
                    try
                    {
                        result.Label = jobj["label"].Value <string>();
                    }
                    catch { return(false); }
                }

                if (jobj.ContainsKey("ckcc_xfp"))
                {
                    try
                    {
                        result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value <uint>());
                    }
                    catch { return(false); }
                }

                if (jobj.ContainsKey("derivation"))
                {
                    try
                    {
                        result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value <string>());
                    }
                    catch { return(false); }
                }
            }
            // Specter
            else if (jobj.ContainsKey("descriptor") && jobj.ContainsKey("blockheight"))
            {
                result.Source = "SpecterFile";

                if (!TryParseXpub(jobj["descriptor"].Value <string>(), derivationSchemeParser, ref result, false))
                {
                    return(false);
                }

                if (jobj.ContainsKey("label"))
                {
                    try
                    {
                        result.Label = jobj["label"].Value <string>();
                    }
                    catch { return(false); }
                }
            }
            // Wasabi
            else
            {
                result.Source = "WasabiFile";
                if (!jobj.ContainsKey("ExtPubKey") ||
                    !TryParseXpub(jobj["ExtPubKey"].Value <string>(), derivationSchemeParser, ref result, false))
                {
                    return(false);
                }
                if (jobj.ContainsKey("MasterFingerprint"))
                {
                    try
                    {
                        var mfpString = jobj["MasterFingerprint"].ToString().Trim();
                        // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066

                        if (uint.TryParse(mfpString, out var fingerprint))
                        {
                            result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(fingerprint);
                        }
                        else
                        {
                            var shouldReverseMfp = jobj.ContainsKey("ColdCardFirmwareVersion") &&
                                                   jobj["ColdCardFirmwareVersion"].ToString() == "2.1.0";
                            var bytes = Encoders.Hex.DecodeData(mfpString);
                            result.AccountKeySettings[0].RootFingerprint = shouldReverseMfp ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);
                        }
                    }

                    catch { return(false); }
                }
                if (jobj.ContainsKey("AccountKeyPath"))
                {
                    try
                    {
                        result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["AccountKeyPath"].Value <string>());
                    }
                    catch { return(false); }
                }
                if (jobj.ContainsKey("DerivationPath"))
                {
                    try
                    {
                        result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["DerivationPath"].Value <string>().ToLowerInvariant());
                    }
                    catch { return(false); }
                }

                if (jobj.ContainsKey("ColdCardFirmwareVersion"))
                {
                    result.Source = "ColdCard";
                }

                if (jobj.ContainsKey("CoboVaultFirmwareVersion"))
                {
                    result.Source = "CoboVault";
                }
            }
            settings         = result;
            settings.Network = network;
            return(true);
        }
Example #17
0
        public static Script ParseScript(string s)
        {
            MemoryStream result = new MemoryStream();

            if (mapOpNames.Count == 0)
            {
                for (int op = 0; op <= (byte)OpcodeType.OP_NOP10; op++)
                {
                    // Allow OP_RESERVED to get into mapOpNames
                    if (op < (byte)OpcodeType.OP_NOP && op != (byte)OpcodeType.OP_RESERVED)
                    {
                        continue;
                    }

                    var name = Op.GetOpName((OpcodeType)op);
                    if (name == "OP_UNKNOWN")
                    {
                        continue;
                    }
                    string strName = name;
                    mapOpNames[strName] = (OpcodeType)op;
                    // Convenience: OP_ADD and just ADD are both recognized:
                    strName             = strName.Replace("OP_", "");
                    mapOpNames[strName] = (OpcodeType)op;
                }
            }

            var words = s.Split(' ', '\t', '\n');

            foreach (string w in words)
            {
                if (w == "")
                {
                    continue;
                }
                if (w.All(l => Money.isdigit(l)) ||
                    (w.StartsWith("-") && w.Substring(1).All(l => Money.isdigit(l))))
                {
                    // Number
                    long n = long.Parse(w);
                    Op.GetPushOp(new BigInteger(n)).WriteTo(result);
                }
                else if (w.StartsWith("0x") && HexEncoder.IsWellFormed(w.Substring(2)))
                {
                    // Raw hex data, inserted NOT pushed onto stack:
                    var raw = Encoders.Hex.DecodeData(w.Substring(2));
                    result.Write(raw, 0, raw.Length);
                }
                else if (w.Length >= 2 && w.StartsWith("'") && w.EndsWith("'"))
                {
                    // Single-quoted string, pushed as data. NOTE: this is poor-man's
                    // parsing, spaces/tabs/newlines in single-quoted strings won't work.
                    var b = TestUtils.ToBytes(w.Substring(1, w.Length - 2));
                    Op.GetPushOp(b).WriteTo(result);
                }
                else if (mapOpNames.ContainsKey(w))
                {
                    // opcode, e.g. OP_ADD or ADD:
                    result.WriteByte((byte)mapOpNames[w]);
                }
                else
                {
                    Assert.True(false, "Invalid test");
                    return(null);
                }
            }

            return(new Script(result.ToArray()));
        }
Example #18
0
        public async Task <bool> WaitOfferTakenAsync(OfferData offerData, CancellationToken cancellation = default(CancellationToken))
        {
            var decodedTxById = new Dictionary <uint256, JObject>();
            var initiator     = GetChain(offerData.Initiator.Asset.Chain);

            var redeemHash = offerData.CreateRedeemScript().Hash;

            int offset    = 0;
            int takeCount = 10;

            while (true)
            {
                cancellation.ThrowIfCancellationRequested();
                var transactions = await initiator.RPCClient.SendCommandAsync(RPCOperations.listtransactions, "*", takeCount, offset, true).ConfigureAwait(false);

                offset += takeCount;

                //If we reach end of the list, start over
                if (transactions.Result == null || ((JArray)transactions.Result).Count() == 0)
                {
                    offset = 0;
                    await Task.Delay(1000, cancellation).ConfigureAwait(false);

                    continue;
                }

                bool startOver = false;
                //Check if the preimage is present
                foreach (var tx in ((JArray)transactions.Result))
                {
                    int confirmation = 0;
                    if (tx["confirmations"] != null)
                    {
                        confirmation = tx["confirmations"].Value <int>();
                    }
                    //Old transaction, skip
                    if (confirmation > 144)
                    {
                        startOver = true;
                        continue;
                    }
                    //Coinbase we can't have the preimage
                    var category = tx["category"]?.Value <string>();
                    if (category == "immature" || category == "generate")
                    {
                        continue;
                    }

                    var     txId  = new uint256(tx["txid"].Value <string>());
                    JObject txObj = null;
                    if (!decodedTxById.TryGetValue(txId, out txObj))
                    {
                        var getTransaction = await initiator.RPCClient.SendCommandAsync("gettransaction", txId.ToString(), true);

                        var decodeRawTransaction = await initiator.RPCClient.SendCommandAsync("decoderawtransaction", getTransaction.Result["hex"]);

                        txObj = (JObject)decodeRawTransaction.Result;
                        decodedTxById.Add(txId, txObj);
                    }
                    foreach (var input in txObj["vin"])
                    {
                        var scriptSig = input["scriptSig"]["asm"].Value <string>();
                        var sigParts  = scriptSig.Split(' ').ToList();
                        // Add the hash in txin if segwit
                        if (input["txinwitness"] is JArray scriptWitness)
                        {
                            sigParts.AddRange(scriptWitness.Select(c => c.Value <string>()));
                        }

                        foreach (var sigPart in sigParts)
                        {
                            if (!HexEncoder.IsWellFormed(sigPart))
                            {
                                continue;
                            }
                            var preimage = new Preimage(Encoders.Hex.DecodeData(sigPart));
                            if (preimage.GetHash() == offerData.Hash)
                            {
                                _Repository.SavePreimage(preimage);
                                return(true);
                            }
                        }
                    }
                }

                //If we reach end of the list, start over
                if (startOver)
                {
                    offset = 0;
                    await Task.Delay(1000, cancellation).ConfigureAwait(false);

                    continue;
                }
            }
        }