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)); }
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())); }
public static Slip21Node FromSeed(string seed) { if (HexEncoder.IsWellFormed(seed)) { return(FromSeed(Encoders.Hex.DecodeData(seed))); } return(FromSeed(Encoding.ASCII.GetBytes(seed))); }
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); }
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); }
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); }
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); }
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); }
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")); }
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); }
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)); }
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); }
public async Task <IActionResult> Submit(string cryptoCode) { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (network == null) { return(BadRequest(CreatePayjoinError(400, "invalid-network", "Incorrect network"))); } var explorer = _explorerClientProvider.GetExplorerClient(network); if (Request.ContentLength is long length) { if (length > 1_000_000) { return(this.StatusCode(413, CreatePayjoinError(413, "payload-too-large", "The transaction is too big to be processed"))); } } else { return(StatusCode(411, CreatePayjoinError(411, "missing-content-length", "The http header Content-Length should be filled"))); } string rawBody; using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { rawBody = (await reader.ReadToEndAsync()) ?? string.Empty; } Transaction originalTx = null; FeeRate originalFeeRate = null; bool psbtFormat = true; if (!PSBT.TryParse(rawBody, network.NBitcoinNetwork, out var psbt)) { psbtFormat = false; if (!Transaction.TryParse(rawBody, network.NBitcoinNetwork, out var tx)) { return(BadRequest(CreatePayjoinError(400, "invalid-format", "invalid transaction or psbt"))); } originalTx = tx; psbt = PSBT.FromTransaction(tx, network.NBitcoinNetwork); psbt = (await explorer.UpdatePSBTAsync(new UpdatePSBTRequest() { PSBT = psbt })).PSBT; for (int i = 0; i < tx.Inputs.Count; i++) { psbt.Inputs[i].FinalScriptSig = tx.Inputs[i].ScriptSig; psbt.Inputs[i].FinalScriptWitness = tx.Inputs[i].WitScript; } } else { if (!psbt.IsAllFinalized()) { return(BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT should be finalized"))); } originalTx = psbt.ExtractTransaction(); } async Task BroadcastNow() { await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx); } var sendersInputType = psbt.GetInputsScriptPubKeyType(); if (sendersInputType is null) { return(BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support segwit inputs (of the same type)"))); } if (psbt.CheckSanity() is var errors && errors.Count != 0) { return(BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})"))); } if (!psbt.TryGetEstimatedFeeRate(out originalFeeRate)) { return(BadRequest(CreatePayjoinError(400, "need-utxo-information", "You need to provide Witness UTXO information to the PSBT."))); } // This is actually not a mandatory check, but we don't want implementers // to leak global xpubs if (psbt.GlobalXPubs.Any()) { return(BadRequest(CreatePayjoinError(400, "leaking-data", "GlobalXPubs should not be included in the PSBT"))); } if (psbt.Outputs.Any(o => o.HDKeyPaths.Count != 0) || psbt.Inputs.Any(o => o.HDKeyPaths.Count != 0)) { return(BadRequest(CreatePayjoinError(400, "leaking-data", "Keypath information should not be included in the PSBT"))); } if (psbt.Inputs.Any(o => !o.IsFinalized())) { return(BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT Should be finalized"))); } //////////// var mempool = await explorer.BroadcastAsync(originalTx, true); if (!mempool.Success) { return(BadRequest(CreatePayjoinError(400, "invalid-transaction", $"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"))); } var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike); bool paidSomething = false; Money due = null; Dictionary <OutPoint, UTXO> selectedUTXOs = new Dictionary <OutPoint, UTXO>(); async Task UnlockUTXOs() { await _payJoinRepository.TryUnlock(selectedUTXOs.Select(o => o.Key).ToArray()); } PSBTOutput originalPaymentOutput = null; BitcoinAddress paymentAddress = null; InvoiceEntity invoice = null; DerivationSchemeSettings derivationSchemeSettings = null; foreach (var output in psbt.Outputs) { var key = output.ScriptPubKey.Hash + "#" + network.CryptoCode.ToUpperInvariant(); invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { key })).FirstOrDefault(); if (invoice is null) { continue; } derivationSchemeSettings = invoice.GetSupportedPaymentMethod <DerivationSchemeSettings>(paymentMethodId) .SingleOrDefault(); if (derivationSchemeSettings is null) { continue; } var receiverInputsType = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType(); if (!PayjoinClient.SupportedFormats.Contains(receiverInputsType)) { //this should never happen, unless the store owner changed the wallet mid way through an invoice return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"))); } if (sendersInputType != receiverInputsType) { return(StatusCode(503, CreatePayjoinError(503, "out-of-utxos", "We do not have any UTXO available for making a payjoin with the sender's inputs type"))); } var paymentMethod = invoice.GetPaymentMethod(paymentMethodId); var paymentDetails = paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; if (paymentDetails is null || !paymentDetails.PayjoinEnabled) { continue; } if (invoice.GetAllBitcoinPaymentData().Any()) { return(UnprocessableEntity(CreatePayjoinError(422, "already-paid", $"The invoice this PSBT is paying has already been partially or completely paid"))); } paidSomething = true; due = paymentMethod.Calculate().TotalDue - output.Value; if (due > Money.Zero) { break; } if (!await _payJoinRepository.TryLockInputs(originalTx.Inputs.Select(i => i.PrevOut).ToArray())) { return(BadRequest(CreatePayjoinError(400, "inputs-already-used", "Some of those inputs have already been used to make payjoin transaction"))); } var utxos = (await explorer.GetUTXOsAsync(derivationSchemeSettings.AccountDerivation)) .GetUnspentUTXOs(false); // In case we are paying ourselves, be need to make sure // we can't take spent outpoints. var prevOuts = originalTx.Inputs.Select(o => o.PrevOut).ToHashSet(); utxos = utxos.Where(u => !prevOuts.Contains(u.Outpoint)).ToArray(); Array.Sort(utxos, UTXODeterministicComparer.Instance); foreach (var utxo in await SelectUTXO(network, utxos, output.Value, psbt.Outputs.Where(o => o.Index != output.Index).Select(o => o.Value).ToArray())) { selectedUTXOs.Add(utxo.Outpoint, utxo); } originalPaymentOutput = output; paymentAddress = paymentDetails.GetDepositAddress(network.NBitcoinNetwork); break; } if (!paidSomething) { return(BadRequest(CreatePayjoinError(400, "invoice-not-found", "This transaction does not pay any invoice with payjoin"))); } if (due is null || due > Money.Zero) { return(BadRequest(CreatePayjoinError(400, "invoice-not-fully-paid", "The transaction must pay the whole invoice"))); } if (selectedUTXOs.Count == 0) { await BroadcastNow(); return(StatusCode(503, CreatePayjoinError(503, "out-of-utxos", "We do not have any UTXO available for making a payjoin for now"))); } var originalPaymentValue = originalPaymentOutput.Value; await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1.0), originalTx, network); //check if wallet of store is configured to be hot wallet var extKeyStr = await explorer.GetMetadataAsync <string>( derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.AccountHDKey); if (extKeyStr == null) { // This should not happen, as we check the existance of private key before creating invoice with payjoin await UnlockUTXOs(); await BroadcastNow(); return(StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"))); } Money contributedAmount = Money.Zero; var newTx = originalTx.Clone(); var ourNewOutput = newTx.Outputs[originalPaymentOutput.Index]; HashSet <TxOut> isOurOutput = new HashSet <TxOut>(); isOurOutput.Add(ourNewOutput); foreach (var selectedUTXO in selectedUTXOs.Select(o => o.Value)) { contributedAmount += (Money)selectedUTXO.Value; newTx.Inputs.Add(selectedUTXO.Outpoint); } ourNewOutput.Value += contributedAmount; var minRelayTxFee = this._dashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ?? new FeeRate(1.0m); // Probably receiving some spare change, let's add an output to make // it looks more like a normal transaction if (newTx.Outputs.Count == 1) { var change = await explorer.GetUnusedAsync(derivationSchemeSettings.AccountDerivation, DerivationFeature.Change); var randomChangeAmount = RandomUtils.GetUInt64() % (ulong)contributedAmount.Satoshi; // Randomly round the amount to make the payment output look like a change output var roundMultiple = (ulong)Math.Pow(10, (ulong)Math.Log10(randomChangeAmount)); while (roundMultiple > 1_000UL) { if (RandomUtils.GetUInt32() % 2 == 0) { roundMultiple = roundMultiple / 10; } else { randomChangeAmount = (randomChangeAmount / roundMultiple) * roundMultiple; break; } } var fakeChange = newTx.Outputs.CreateNewTxOut(randomChangeAmount, change.ScriptPubKey); if (fakeChange.IsDust(minRelayTxFee)) { randomChangeAmount = fakeChange.GetDustThreshold(minRelayTxFee); fakeChange.Value = randomChangeAmount; } if (randomChangeAmount < contributedAmount) { ourNewOutput.Value -= fakeChange.Value; newTx.Outputs.Add(fakeChange); isOurOutput.Add(fakeChange); } } var rand = new Random(); Utils.Shuffle(newTx.Inputs, rand); Utils.Shuffle(newTx.Outputs, rand); // Remove old signatures as they are not valid anymore foreach (var input in newTx.Inputs) { input.WitScript = WitScript.Empty; } Money ourFeeContribution = Money.Zero; // We need to adjust the fee to keep a constant fee rate var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder(); txBuilder.AddCoins(psbt.Inputs.Select(i => i.GetSignableCoin())); txBuilder.AddCoins(selectedUTXOs.Select(o => o.Value.AsCoin(derivationSchemeSettings.AccountDerivation))); Money expectedFee = txBuilder.EstimateFees(newTx, originalFeeRate); Money actualFee = newTx.GetFee(txBuilder.FindSpentCoins(newTx)); Money additionalFee = expectedFee - actualFee; if (additionalFee > Money.Zero) { // If the user overpaid, taking fee on our output (useful if sender dump a full UTXO for privacy) for (int i = 0; i < newTx.Outputs.Count && additionalFee > Money.Zero && due < Money.Zero; i++) { if (isOurOutput.Contains(newTx.Outputs[i])) { var outputContribution = Money.Min(additionalFee, -due); outputContribution = Money.Min(outputContribution, newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee)); newTx.Outputs[i].Value -= outputContribution; additionalFee -= outputContribution; due += outputContribution; ourFeeContribution += outputContribution; } } // The rest, we take from user's change for (int i = 0; i < newTx.Outputs.Count && additionalFee > Money.Zero; i++) { if (!isOurOutput.Contains(newTx.Outputs[i])) { var outputContribution = Money.Min(additionalFee, newTx.Outputs[i].Value); outputContribution = Money.Min(outputContribution, newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee)); newTx.Outputs[i].Value -= outputContribution; additionalFee -= outputContribution; } } if (additionalFee > Money.Zero) { // We could not pay fully the additional fee, however, as long as // we are not under the relay fee, it should be OK. var newVSize = txBuilder.EstimateSize(newTx, true); var newFeePaid = newTx.GetFee(txBuilder.FindSpentCoins(newTx)); if (new FeeRate(newFeePaid, newVSize) < minRelayTxFee) { await UnlockUTXOs(); await BroadcastNow(); return(UnprocessableEntity(CreatePayjoinError(422, "not-enough-money", "Not enough money is sent to pay for the additional payjoin inputs"))); } } } var accountKey = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork); var newPsbt = PSBT.FromTransaction(newTx, network.NBitcoinNetwork); foreach (var selectedUtxo in selectedUTXOs.Select(o => o.Value)) { var signedInput = newPsbt.Inputs.FindIndexedInput(selectedUtxo.Outpoint); var coin = selectedUtxo.AsCoin(derivationSchemeSettings.AccountDerivation); signedInput.UpdateFromCoin(coin); var privateKey = accountKey.Derive(selectedUtxo.KeyPath).PrivateKey; signedInput.Sign(privateKey); signedInput.FinalizeInput(); newTx.Inputs[signedInput.Index].WitScript = newPsbt.Inputs[(int)signedInput.Index].FinalScriptWitness; } // Add the transaction to the payments with a confirmation of -1. // This will make the invoice paid even if the user do not // broadcast the payjoin. var originalPaymentData = new BitcoinLikePaymentData(paymentAddress, originalPaymentOutput.Value, new OutPoint(originalTx.GetHash(), originalPaymentOutput.Index), originalTx.RBF); originalPaymentData.ConfirmationCount = -1; originalPaymentData.PayjoinInformation = new PayjoinInformation() { CoinjoinTransactionHash = newPsbt.GetGlobalTransaction().GetHash(), CoinjoinValue = originalPaymentValue - ourFeeContribution, ContributedOutPoints = selectedUTXOs.Select(o => o.Key).ToArray() }; var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true); if (payment is null) { await UnlockUTXOs(); await BroadcastNow(); return(UnprocessableEntity(CreatePayjoinError(422, "already-paid", $"The original transaction has already been accounted"))); } await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(originalTx); _eventAggregator.Publish(new InvoiceEvent(invoice, 1002, InvoiceEvent.ReceivedPayment) { Payment = payment }); if (psbtFormat && HexEncoder.IsWellFormed(rawBody)) { return(Ok(newPsbt.ToHex())); } else if (psbtFormat) { return(Ok(newPsbt.ToBase64())); } else { return(Ok(newTx.ToHex())); } }
public 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); }
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())); }
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; } } }