public void CanValidateCETSigs() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var accept = Parse <Messages.Accept>("Data/Accept2.json"); var sign = Parse <Messages.Sign>("Data/Sign2.json"); var funding = Transaction.Parse("02000000000102118df2c93e3be1f05af4fe16ba82eab135ba624bb96db5b218222ad7f99f1c400000000000ffffffff9ff82cc6f2509b2bc9f0d56fa37bea855d40b74cd2633e75ca343469508e892d0000000000ffffffff0372dc010000000000220020a9dbe7e77d7f966d4b8ab36d576e19f4c392caf039ea5e53a0078d771a3e92eefb2a042a01000000160014ec8d64ed4742016d12deac1106577f94747f09f41b5afa2f01000000160014c22a6d286c108ad458ba60e00576859a78ae8de002473044022071e5e088ba8e4434ab044350448479561ad8179bf2a88789a9459215bcf610c80220128535940b083dca52791f373b6474dd24f3a9096c039411943ffcff6fb293a801210333dea605a7d2c223de68c1189bd5e171cb94bd35712122eff08a9cdbed5dca8d02473044022059d6a3ae8cee036998065d2139459a029c4d925f952a860434910b12f9e9a3a002205871d75c28b8486caa90a6d44c3ae59eeb98bbbc90990f5b510466178eb80d9c01210310f8a8f195fe3167af0db8c6bb208e9a8ac075c99276a0150c1d374d0d13d26a00000000", Network.Main); var cet = Transaction.Parse("020000000001016b3ea350dfa327d9c4c030fc3e2e49c04aae28d6020f1624202c57bf620dddbc0000000000feffffff01a08601000000000016001404bea358f0a1c3bdabe6148fd2ea44838110ef830400483045022100c3d1901ae606851946edaaa007cc5ed3ba05a1a812e76d8e4f5a3b034fe11dd4022063d51c44bc8a78ee752e735e3c84be2329e0eb15022db552a1181eaf1edb7dff0147304402205f5ba5027909abef13c61bc604eb2deaf223549213c6c7c9456d972ae1badf800220348bf4918b01bea8ee69d104a60254ad23159654a8f8b31e43da1fc53603c2390147522102d63fec4b8a6705a34b32e5476a5a07c20774fe2abcf88eeea320c9714bc3010e21038b22637639db00f3dd5d8c46644b61954a01362bc92a87eb136bd6ba80c1f12852ae64000000", Network.Main); RemoveSigs(cet); RemoveSigs(funding); foreach (var isInitiator in new[] { true, false }) { var b = new DLCTransactionBuilder(isInitiator, offer, accept, sign, funding, Network.RegTest); var actualCet = b.BuildCET(offer.ContractInfo[1].Outcome); Assert.Equal(cet.ToString(), actualCet.ToString()); Assert.True(b.VerifyRemoteCetSigs(isInitiator ? accept.CetSigs.OutcomeSigs : sign.CetSigs.OutcomeSigs)); Assert.True(b.VerifyRemoteRefundSignature()); b = new DLCTransactionBuilder(isInitiator, offer, accept, sign, Network.RegTest); var actualFunding = b.GetFundingTransaction(); RemoveSigs(actualFunding); Assert.Equal(funding.ToString(), actualFunding.ToString()); } }
public void CanCreateFunding() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var accept = Parse <Messages.Accept>("Data/Accept2.json"); var b = new DLCTransactionBuilder(true, offer, accept, null, Network.RegTest); var expected = Transaction.Parse("0200000000010213a31be98d8e29a08cbb3b64de59727b0a6285e2f34338a3ca576ae5250692fc0100000000ffffffffff9208b14cf747f04a7b653debb4dfedc9b4a3291985b91c872dda54b420e7110200000000ffffffff037418f605000000002200205f4c70ba1400d2efc70a2ff32f07a2d79c26d9f6c49a65bde68efc53cf12f0d5dcfb0d700000000016001486039150dbcfd4a136243a7cb4ea2b3dad24e606bbbfd17400000000160014775277f3b5fc4c32bed192ac6b3fd90b4403f1c902483045022100a9518901afbe84053644c0f08e50d45eb373616f7aa729677ecf5718f2fac8b2022032949f92beaf8d4ee8b944fa9452f1feabbc863e59b943fd5acce1aedc89feef012102a3795336df054e4fe408e0d453ff48fc8a0ec4ba7ef9234391babfa4247aef0a024730440220320baf857ca4ff420613e89930dcaa6ee423251fdcfc2108c7a029263cab5c2302201237c4b978c65ed3ed00bb763ff1d1b6f61bdaff8a29e5ca537761e089ce5265012103af3fb0e5788dbfdc478dd5e58d27001177a9a3fb19b990c883dd3ea75aec424c00000000", Network.Main); foreach (var input in expected.Inputs) { input.WitScript = WitScript.Empty; } // We cheat for now... b.FundingOverride = expected; var actual = b.BuildFunding(); Assert.Equal(expected.ToString(), actual.ToString()); expected = Transaction.Parse("02000000000101ef43cda2f4e8fb1e254864a00b3b81f4915b9c8d060e41fe37523001ac03a08a0000000000feffffff0200879303000000001600145e8bf52af230ae61e9637ecb498a5bed8d09a59b005a62020000000016001411eb59e70fa4abe8c02699b54fb71ed3d76574fc04004830450221009942f177f872e786f6d1edd98abc78e4114bcbd886a72170190e39515841da85022001908f53671480bd2eb869dc7c34d188921a72243dce1e3108646f83948e516401483045022100e988416aba7518317c7a3e1acf051e0ef28ba7cb0473039115072a4073a2a650022028ee55f5d202f9facc230aa7f7c9fd18a75228fc80493bea9d1ae36085cec8830147522103fbae911c3acb17f06a9a544f068d925715900c69d0c58408543c09e75ba249332103f81ee1a50b2b01ce32982b2900c34f0c3a3745e98458e67ecf239086f4e8908852aec8000000", Network.Main); foreach (var input in expected.Inputs) { input.WitScript = WitScript.Empty; } actual = b.BuildRefund(); Assert.Equal(expected.ToString(), actual.ToString()); }
public void FullExchange2() { var initiatorInputKey = new Key(); var acceptorInputKey = new Key(); var offerKey = new Key(); var acceptKey = new Key(); var initiator = new DLCTransactionBuilder(true, null, null, null, Network.RegTest); var acceptor = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var oracleInfo = OracleInfo.Parse("e4d36e995ff4bba4da2b60ad907d61d36e120d6f7314a3c2a20c6e27a5cd850ff67f8f41718c86f05eb95fab308f5ed788a2a963124299154648f97124caa579"); var requiredCollateral = initiator.Offer(oracleInfo.PubKey, oracleInfo.RValue, new DiscretePayoffs() { new DiscretePayoff("Republicans", Money.Coins(0.4m)), new DiscretePayoff("Democrats", -Money.Coins(0.6m)), new DiscretePayoff("Smith", Money.Zero) }, new Timeouts() { ContractMaturity = 100, ContractTimeout = 200 }); var fund1 = GetFundingPSBT(acceptorInputKey, requiredCollateral); var offer = initiator.FundOffer(offerKey, fund1); var payoff = acceptor.Accept(offer); var fund2 = GetFundingPSBT(initiatorInputKey, payoff.CalculateMinimumCollateral()); var accept = acceptor.FundAccept(acceptKey, fund2); initiator.Sign1(accept); var fundPSBT = initiator.GetFundingPSBT(); fundPSBT.SignWithKeys(initiatorInputKey); var sign = initiator.Sign2(offerKey, fundPSBT); acceptor.Finalize1(sign); fundPSBT = acceptor.GetFundingPSBT(); fundPSBT.SignWithKeys(acceptorInputKey); var fullyVerified = acceptor.Finalize(fundPSBT); foreach (var i in fullyVerified.Inputs) { Assert.NotNull(i.WitScript); } var cet = initiator.BuildCET(offer.ContractInfo[0].Outcome); var keyBytes = Encoders.Hex.DecodeData("d1da46f96f0be50bce2bbabe6bc8633f448ec3f1d14715a0b086b68ed34e095d"); var oracleSecret = new Key(keyBytes); var sig = oracleInfo.RValue.CreateSchnorrSignature(oracleSecret.ToECPrivKey()); Assert.True(oracleInfo.PubKey.SigVerifyBIP340(sig, new DiscreteOutcome("Republicans").Hash)); initiator.BuildSignedCET(offerKey, oracleSecret); this.testOutputHelper.WriteLine("----Final state------"); testOutputHelper.WriteLine(JObject.Parse(initiator.ExportState()).ToString(Formatting.Indented)); this.testOutputHelper.WriteLine("---------------------"); }
protected override async Task InvokeAsyncBase(InvocationContext context) { var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } if (await this.TryGetDLC(name) != null) { throw new CommandException("name", "This DLC already exists"); } EventFullName evtName = context.GetEventName(); var oracle = await GetOracle("eventfullname", evtName.OracleName); if (oracle?.PubKey is null) { throw new CommandException("eventfullname", "The specified oracle does not exists"); } var evt = await GetEvent("eventfullname", evtName); var payoffsStr = context.ParseResult.CommandResult.GetArgumentValueOrDefault <List <string> >("payoff"); if (payoffsStr is null || payoffsStr.Count == 0) { throw new CommandOptionRequiredException("payoff"); } var payoffs = CreatePayoffs(payoffsStr); FixCasing(evt, payoffs); var builder = new DLCTransactionBuilder(true, null, null, null, Network); var timeout = new Timeouts() { ContractMaturity = 0, ContractTimeout = Constants.NeverLockTime }; if (context.ParseResult.HasOption("cetlocktime")) { timeout.ContractMaturity = new LockTime(context.ParseResult.ValueForOption <uint>("cetlocktime")); } if (context.ParseResult.HasOption("refundlocktime")) { timeout.ContractTimeout = new LockTime(context.ParseResult.ValueForOption <uint>("refundlocktime")); } var collateral = payoffs.CalculateMinimumCollateral(); builder.Offer(oracle.PubKey, evt.EventId !.RValue, payoffs, timeout); var dlc = await Repository.NewDLC(evt.EventId, builder); await NameRepository.AsDLCNameRepository().SetMapping(name, dlc.LocalId); context.Console.Out.Write($"Offer created, you now need to setup the DLC sending {collateral} BTC to yourself. For more information, run `dlc show \"{name}\"`."); }
public void CanCalculateEventId() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var accept = Parse <Messages.Accept>("Data/Accept2.json"); var builder = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var payoff = builder.Accept(offer); PSBT fundPSBT = GetFundingPSBT(new Key(), payoff.CalculateMinimumCollateral()); var accept2 = builder.FundAccept(new Key(), fundPSBT); Assert.Equal(accept.EventId, accept2.EventId); }
public void CanCreateAccept() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var builder = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var fundingInputKey = new Key(); var payoffs = builder.Accept(offer); PSBT fundPSBT = GetFundingPSBT(fundingInputKey, payoffs.CalculateMinimumCollateral()); var accept = builder.FundAccept(new Key(), fundPSBT); builder = new DLCTransactionBuilder(true, offer, null, null, Network.RegTest); builder.Sign1(accept); }
public async Task dlc_test() { await foreach (var vector in DLCTestVector.ReadVectors()) { Assert.Equal(vector.ExpectedOfferTLV, Encoders.Hex.EncodeData(vector.Offer.ToTLV())); var accept = new Accept(); accept.TemporaryContractId = vector.Offer.GetTemporaryContractId(); vector.FillFundingInformation(accept, "acceptParams"); accept.PubKeys.FundingKey = vector.AcceptPrivateKey.PubKey; var acceptor = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); acceptor.Accept(vector.Offer, accept.TotalCollateral); Assert.True(acceptor.State.TemporaryContractId); var builtAccept = acceptor.FundAccept(vector.AcceptPrivateKey, accept.CreateSetupPSBT(Network.RegTest)); accept.CetSigs = builtAccept.CetSigs; // this signature is non deterministic... accept.CetSigs.RefundSig = Accept.ParseFromTLV(vector.ExpectedAcceptTLV, Network.RegTest).CetSigs.RefundSig; Assert.Equal(vector.ExpectedAcceptTLV, Encoders.Hex.EncodeData(accept.ToTLV())); var actualFundingTransaction = acceptor.GetFundingTransaction(); var actualRefundTransaction = acceptor.BuildRefund(); var actualCETs = new Transaction[vector.Offer.ContractInfo.Length]; for (int i = 0; i < vector.Offer.ContractInfo.Length; i++) { actualCETs[i] = acceptor.BuildCET(vector.Offer.ContractInfo[i].Outcome); } var offerer = new DLCTransactionBuilder(true, vector.Offer, null, null, Network.RegTest); offerer.Sign1(accept); var fundingPSBT = offerer.GetFundingPSBT(); FillSignatures(fundingPSBT, vector.SignedTxs.FundingTx); var sign = offerer.Sign2(vector.OfferPrivateKey, fundingPSBT); // this signature is non deterministic... sign.CetSigs.RefundSig = Sign.ParseFromTLV(vector.ExpectedSignTLV, Network.RegTest).CetSigs.RefundSig; Assert.Equal(vector.ExpectedSignTLV, Encoders.Hex.EncodeData(sign.ToTLV())); Assert.Equal( RemoveForDebug(vector.UnsignedTxs.FundingTx.ToString()), RemoveForDebug(actualFundingTransaction.ToString())); Assert.Equal( RemoveForDebug(vector.UnsignedTxs.RefundTx.ToString()), RemoveForDebug(actualRefundTransaction.ToString())); for (int i = 0; i < actualCETs.Length; i++) { Assert.Equal( RemoveForDebug(vector.UnsignedTxs.Cets[i].ToString()), RemoveForDebug(actualCETs[i].ToString())); } Assert.NotNull(offerer.State.ContractId); Assert.Equal(offerer.State.ContractId, acceptor.State.ContractId); Assert.False(offerer.State.TemporaryContractId); } }
public void CanCreateAccept() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var builder = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var fundingInputKey = new Key(); PSBT fundPSBT = GetFundingPSBT(fundingInputKey, Money.Coins(0.4m)); Assert.True(PSBTFundingTemplate.TryParse(fundPSBT.ToBase64(), fundPSBT.Network, out var template)); var accept = builder.Accept(offer, template); builder = new DLCTransactionBuilder(true, offer, accept, null, Network.RegTest); Assert.True(builder.VerifyRemoteCetSigs()); Assert.True(builder.VerifyRemoteRefundSignature()); }
protected override async Task InvokeAsyncBase(InvocationContext context) { ECXOnlyPubKey oraclePubKey = GetOraclePubKey(context.ParseResult.ValueForOption <string>("oraclepubkey")); SchnorrNonce schnorrNonce = GetSchnorrNonce(context.ParseResult.ValueForOption <string>("nonce")); DLCTransactionBuilder builder = new DLCTransactionBuilder(true, null, null, null, Network); var timeouts = new Timeouts() { ContractMaturity = GetLockTime(context, "maturity") ?? new LockTime(0), ContractTimeout = GetLockTime(context, "expiration") ?? throw new CommandOptionRequiredException("expiration") }; var ci = GetOutcomes(context.ParseResult.ValueForOption <string[]>("outcome")); //var offer = builder.Offer(template, new OracleInfo(oraclePubKey, schnorrNonce), ci, timeouts); //WriteObject(context, offer); }
public DLCNextStep GetNextStep(Network network) { if (BuilderState is null) { throw new InvalidOperationException("BuilderState not set"); } var builder = new DLCTransactionBuilder(BuilderState.ToString(), network); DLCNextStep nextStep; if (builder.State.IsInitiator) { if (FundKeyPath is null) { nextStep = DLCNextStep.Setup; } else if (Accept is null) { nextStep = DLCNextStep.CheckSigs; } else if (Sign is null) { nextStep = DLCNextStep.Fund; } else { nextStep = DLCNextStep.Done; } } else { if (builder.State.Funding is null) { nextStep = DLCNextStep.Setup; } else if (Sign is null) { nextStep = DLCNextStep.CheckSigs; } else if (!builder.State.Funding.PSBT.CanExtractTransaction()) { nextStep = DLCNextStep.Fund; } else { nextStep = DLCNextStep.Done; } } return(nextStep); }
public void FullExchange2() { var initiatorInputKey = new Key(); var acceptorInputKey = new Key(); var fund1 = GetFundingPSBT(initiatorInputKey, Money.Coins(0.6m)); var fund2 = GetFundingPSBT(acceptorInputKey, Money.Coins(0.4m)); var initiator = new DLCTransactionBuilder(true, null, null, null, Network.RegTest); var acceptor = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var oracleInfo = OracleInfo.Parse("156c7d1c7922f0aa1168d9e21ac77ea88bbbe05e24e70a08bbe0519778f2e5daea3a68d8749b81682513b0479418d289d17e24d4820df2ce979f1a56a63ca525"); var offer = initiator.Offer(PSBTFundingTemplate.Parse(fund1), oracleInfo, ContractInfo.CreateContract( ("Republicans_win", Money.Coins(1.0m)), ("Democrats_win", Money.Coins(0m)), ("other", Money.Coins(0.6m))), new Timeouts() { ContractMaturity = 100, ContractTimeout = 200 }); var accept = acceptor.Accept(offer, PSBTFundingTemplate.Parse(fund2)); initiator.VerifySign(accept); var fundPSBT = initiator.BuildFundingPSBT(); fundPSBT.SignWithKeys(initiatorInputKey); var sign = initiator.EndSign(fundPSBT); acceptor.VerifySign(sign); fundPSBT = acceptor.BuildFundingPSBT(); fundPSBT.SignWithKeys(acceptorInputKey); var psbt = acceptor.CombineFunding(fundPSBT); psbt = psbt.Finalize(); var fullyVerified = psbt.ExtractTransaction(); foreach (var i in fullyVerified.Inputs) { Assert.NotNull(i.WitScript); } var cet = initiator.BuildCET(offer.ContractInfo[0].Outcome); var keyBytes = Encoders.Hex.DecodeData("39eabd151030f4f2d518fb8a8d00f679aa9e034c66263032a1245a04cfbc592b"); //Array.Reverse(keyBytes); var oracleSecret = new Key(keyBytes); initiator.BuildSignedCET(oracleSecret); }
protected override async Task InvokeAsyncBase(InvocationContext context) { var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } var dlc = await GetDLC("name", name); if (dlc?.BuilderState is null || dlc?.OracleInfo is null) { throw new CommandException("name", "This DLC does not exist"); } var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); var psbt = context.ParsePSBT("signedpsbt", Network); if (!builder.State.IsInitiator) { context.AssertState("name", dlc, false, DLCNextStep.Fund, Network); var fullySigned = builder.Finalize(psbt); dlc.BuilderState = builder.ExportStateJObject(); await Repository.SaveDLC(dlc); context.WriteTransaction(fullySigned, builder.State.Funding?.FundCoin, Network); } else { context.AssertState("name", dlc, true, DLCNextStep.Fund, Network); var key = await Repository.GetKey(dlc.FundKeyPath !); try { var sign = builder.Sign2(key, psbt); dlc.Sign = sign; dlc.BuilderState = builder.ExportStateJObject(); await Repository.SaveDLC(dlc); context.WriteObject(sign, Repository.JsonSettings); } catch (Exception ex) { throw new CommandException("signedpsbt", $"Invalid PSBT. ({ex.Message})"); } } }
public void CanCreateAccept() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); offer.SetContractPreimages( new DiscreteOutcome("Republicans_win"), new DiscreteOutcome("Democrats_win"), new DiscreteOutcome("other") ); var builder = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var fundingInputKey = new Key(); var payoffs = builder.Accept(offer); PSBT fundPSBT = GetFundingPSBT(fundingInputKey, payoffs.CalculateMinimumCollateral()); var accept = builder.FundAccept(new Key(), fundPSBT); builder = new DLCTransactionBuilder(true, offer, null, null, Network.RegTest); builder.Sign1(accept); }
public async Task <DLCState> NewDLC(OracleInfo oracleInfo, DLCTransactionBuilder builder) { var dir = Path.Combine(RepositoryDirectory, "DLCs"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var s = new DLCState() { OracleInfo = oracleInfo, BuilderState = builder.ExportStateJObject(), Id = RandomUtils.GetUInt256() }; var file = GetDLCFilePath(s.Id); await File.WriteAllTextAsync(file, JsonConvert.SerializeObject(s, JsonSettings)); return(s); }
public void FullExchange() { var offerExample = Parse <Messages.Offer>("Data/Offer2.json"); var offerKey = new Key(); var acceptKey = new Key(); var initiatorInputKey = new Key(); var acceptorInputKey = new Key(); var initiator = new DLCTransactionBuilder(true, null, null, null, Network.RegTest); var requiredFund = initiator.Offer(offerExample.OracleInfo.PubKey, offerExample.OracleInfo.RValue, DiscretePayoffs.CreateFromContractInfo(offerExample.ContractInfo, offerExample.TotalCollateral, new[] { new DiscreteOutcome("Republicans_win"), new DiscreteOutcome("Democrats_win"), new DiscreteOutcome("other") }), offerExample.Timeouts); var fund1 = GetFundingPSBT(initiatorInputKey, requiredFund); var offer = initiator.FundOffer(offerKey, fund1); var acceptor = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var acceptorPayoff = acceptor.Accept(offer); var fund2 = GetFundingPSBT(acceptorInputKey, acceptorPayoff.CalculateMinimumCollateral()); var accept = acceptor.FundAccept(acceptKey, fund2); initiator.Sign1(accept); var fundPSBT = initiator.GetFundingPSBT(); fundPSBT.SignWithKeys(initiatorInputKey); var sign = initiator.Sign2(offerKey, fundPSBT); acceptor.Finalize1(sign); fundPSBT = acceptor.GetFundingPSBT(); fundPSBT.SignWithKeys(acceptorInputKey); var fullyVerified = acceptor.Finalize(fundPSBT); foreach (var i in fullyVerified.Inputs) { Assert.NotNull(i.WitScript); } fundPSBT = acceptor.GetFundingPSBT(); if (fundPSBT.TryGetEstimatedFeeRate(out var estimated)) { Assert.True(estimated > new FeeRate(1.0m), "Fee Rate of the funding PSBT are too low"); } }
protected override async Task InvokeAsyncBase(InvocationContext context) { var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } var dlc = await GetDLC("name", name); if (dlc?.OracleInfo is null || dlc?.BuilderState is null) { throw new CommandException("name", "This DLC does not exist"); } var str = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("transaction")?.Trim(); if (str is null) { throw new CommandOptionRequiredException("transaction"); } if (!Transaction.TryParse(str, Network, out var tx) || tx is null) { throw new CommandException("transaction", "Cannot parse the transaction"); } var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); try { var key = builder.ExtractAttestation(tx); await Repository.AddAttestation(dlc.OracleInfo, key); context.Console.Out.Write(key.ToHex()); } catch (Exception ex) { throw new CommandException("transaction", $"Impossible to extract the attestation from this transaction. {ex.Message}"); } }
public void CanValidateCETSigs() { var offer = Parse <Messages.Offer>("Data/Offer2.json"); var accept = Parse <Messages.Accept>("Data/Accept2.json"); var sign = Parse <Messages.Sign>("Data/Sign2.json"); var funding = Transaction.Parse("0200000000010213a31be98d8e29a08cbb3b64de59727b0a6285e2f34338a3ca576ae5250692fc0100000000ffffffffff9208b14cf747f04a7b653debb4dfedc9b4a3291985b91c872dda54b420e7110200000000ffffffff037418f605000000002200205f4c70ba1400d2efc70a2ff32f07a2d79c26d9f6c49a65bde68efc53cf12f0d5dcfb0d700000000016001486039150dbcfd4a136243a7cb4ea2b3dad24e606bbbfd17400000000160014775277f3b5fc4c32bed192ac6b3fd90b4403f1c902483045022100a9518901afbe84053644c0f08e50d45eb373616f7aa729677ecf5718f2fac8b2022032949f92beaf8d4ee8b944fa9452f1feabbc863e59b943fd5acce1aedc89feef012102a3795336df054e4fe408e0d453ff48fc8a0ec4ba7ef9234391babfa4247aef0a024730440220320baf857ca4ff420613e89930dcaa6ee423251fdcfc2108c7a029263cab5c2302201237c4b978c65ed3ed00bb763ff1d1b6f61bdaff8a29e5ca537761e089ce5265012103af3fb0e5788dbfdc478dd5e58d27001177a9a3fb19b990c883dd3ea75aec424c00000000", Network.Main); var cet = Transaction.Parse("02000000000101ef43cda2f4e8fb1e254864a00b3b81f4915b9c8d060e41fe37523001ac03a08a0000000000feffffff0100e1f505000000001600145e8bf52af230ae61e9637ecb498a5bed8d09a59b04004730440220131ff77ab066d7bfa682691ca70ec9d229dc8e595c4055ddf23468fbed981bf602205c3a0d6b1bfb46da00ed8aad93498ece2d621a0eeacf7e30654be5135d773f0001483045022100efba114529a59517e7c39f6e38fb1c44bd935a8fe6ec63ab15e7a1912bc8b7aa02204a5d54d7f15adf102bb61fc9e4cf174c5a6be212f3c64b74687c6a717ae9104b0147522103fbae911c3acb17f06a9a544f068d925715900c69d0c58408543c09e75ba249332103f81ee1a50b2b01ce32982b2900c34f0c3a3745e98458e67ecf239086f4e8908852ae64000000", Network.Main); foreach (var input in cet.Inputs) { input.WitScript = WitScript.Empty; } foreach (var isInitiator in new[] { true, false }) { var b = new DLCTransactionBuilder(isInitiator, offer, accept, sign, Network.RegTest); b.FundingOverride = funding; var actualCet = b.BuildCET(offer.ContractInfo[0].Outcome); Assert.Equal(cet.ToString(), actualCet.ToString()); Assert.True(b.VerifyRemoteCetSigs()); Assert.True(b.VerifyRemoteRefundSignature()); } }
public void FullExchange() { var offerExample = Parse <Messages.Offer>("Data/Offer2.json"); var initiatorInputKey = new Key(); var acceptorInputKey = new Key(); var fund1 = GetFundingPSBT(initiatorInputKey, Money.Coins(0.6m)); var fund2 = GetFundingPSBT(acceptorInputKey, Money.Coins(0.4m)); var initiator = new DLCTransactionBuilder(true, null, null, null, Network.RegTest); var acceptor = new DLCTransactionBuilder(false, null, null, null, Network.RegTest); var offer = initiator.Offer(PSBTFundingTemplate.Parse(fund1), offerExample.OracleInfo, offerExample.ContractInfo, offerExample.Timeouts); var accept = acceptor.Accept(offer, PSBTFundingTemplate.Parse(fund2)); initiator.VerifySign(accept); var fundPSBT = initiator.BuildFundingPSBT(); fundPSBT.SignWithKeys(initiatorInputKey); var sign = initiator.EndSign(fundPSBT); acceptor.VerifySign(sign); fundPSBT = acceptor.BuildFundingPSBT(); fundPSBT.SignWithKeys(acceptorInputKey); fundPSBT = acceptor.CombineFunding(fundPSBT); fundPSBT = fundPSBT.Finalize(); var fullyVerified = fundPSBT.ExtractTransaction(); foreach (var i in fullyVerified.Inputs) { Assert.NotNull(i.WitScript); } if (fundPSBT.TryGetEstimatedFeeRate(out var estimated)) { Assert.True(estimated > new FeeRate(1.0m), "Fee Rate of the funding PSBT are too low"); } }
protected override async Task InvokeAsyncBase(InvocationContext context) { var offer = DLCHelpers.GetOffer(context, Repository.JsonSettings); var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } if (await this.TryGetDLC(name) != null) { throw new CommandException("name", "This DLC already exists"); } if (offer.OracleInfo is null) { throw new CommandException("offer", "Missing oracleInfo"); } if (offer.Timeouts is null) { throw new CommandException("offer", "Missing timeouts"); } if (offer.ContractInfo is null) { throw new CommandException("offer", "Missing contractInfos"); } var oracle = await Repository.GetOracle(offer.OracleInfo.PubKey); if (oracle is null) { throw new CommandException("offer", "Unknown oracle"); } var evt = await Repository.GetEvent(offer.OracleInfo.PubKey, offer.OracleInfo.RValue); if (evt is null) { throw new CommandException("offer", "Unknown event"); } var maturity = new LockTimeEstimation(offer.Timeouts.ContractMaturity, Network); var refund = new LockTimeEstimation(offer.Timeouts.ContractTimeout, Network); if (!refund.UnknownEstimation) { if (refund.EstimatedRemainingBlocks == 0) { throw new CommandException("offer", "The refund should not be immediately valid"); } if (refund.EstimatedRemainingBlocks < maturity.EstimatedRemainingBlocks) { throw new CommandException("offer", "The refund should not be valid faster than the contract execution transactions"); } } offer.SetContractPreimages(evt.Outcomes); try { var builder = new DLCTransactionBuilder(false, null, null, null, Network); builder.Accept(offer); var dlc = await Repository.NewDLC(offer.OracleInfo, builder); dlc.BuilderState = builder.ExportStateJObject(); dlc.Offer = JObject.FromObject(offer, JsonSerializer.Create(Repository.JsonSettings)); await NameRepository.AsDLCNameRepository().SetMapping(name, dlc.Id); await Repository.SaveDLC(dlc); context.Console.Out.Write($"Contract accepted, you now need to setup the DLC. For more information, run `dlc show \"{name}\"`."); } catch (Exception ex) { throw new CommandException("offer", $"Invalid offer or PSBT. ({ex.Message})"); } }
protected override async Task InvokeAsyncBase(InvocationContext context) { var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } var dlc = await GetDLC("name", name); if (dlc?.BuilderState is null || dlc?.OracleInfo is null) { throw new CommandException("name", "This DLC does not exist"); } var oracle = await Repository.GetOracle(dlc.OracleInfo.PubKey); string?oracleName = null; EventFullName eventName = new EventFullName("???", "???"); if (oracle != null) { oracleName = await NameRepository.GetName(Scopes.Oracles, new OracleId(dlc.OracleInfo.PubKey).ToString()); var ev = await Repository.GetEvent(dlc.OracleInfo); eventName = await NameRepository.AsEventRepository().ResolveName(dlc.OracleInfo) ?? eventName; } var shown = ParseShownItem(context); if (shown == ShowOption.DLC) { context.Console.Out.WriteLine($"Name: {name}"); context.Console.Out.WriteLine($"Local Id: {dlc.Id}"); var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); var role = builder.State.IsInitiator ? "Offerer" : "Acceptor"; context.Console.Out.WriteLine($"Event: {eventName}"); context.Console.Out.WriteLine($"Role: {role}"); var nextStep = dlc.GetNextStep(Network); context.Console.Out.WriteLine($"Next step: {nextStep}"); context.Console.Out.WriteLine($"Next step explanation:"); context.Console.Out.Write($"{Explain(nextStep, name, builder.State)}"); } else if (shown == ShowOption.Offer) { if (dlc.Offer is null) { throw new CommandException("offer", "No offer available for this DLC"); } context.WriteObject(dlc.Offer, Repository.JsonSettings); } else if (shown == ShowOption.Accept) { if (dlc.Accept is null) { throw new CommandException("offer", "No accept message available for this DLC"); } context.WriteObject(dlc.Accept, Repository.JsonSettings); } else if (shown == ShowOption.Funding) { try { var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); context.WritePSBT(builder.GetFundingPSBT()); } catch { throw new CommandException("funding", "No funding PSBT ready for this DLC"); } } else if (shown == ShowOption.Abort) { if (dlc.Abort is null) { throw new CommandException("abort", "No abort PSBT for this DLC"); } context.WritePSBT(dlc.Abort); } else if (shown == ShowOption.Refund) { try { var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); context.WriteTransaction(builder.BuildRefund(), Network); } catch { throw new CommandException("refund", "No refund PSBT ready for this DLC"); } } else { throw new NotSupportedException(); } }
public static JObject ExportStateJObject(this DLCTransactionBuilder builder) { return(JObject.Parse(builder.ExportState())); }
protected override async Task InvokeAsyncBase(InvocationContext context) { var name = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("name")?.Trim(); if (name is null) { throw new CommandOptionRequiredException("name"); } var dlc = await GetDLC("name", name); if (dlc?.OracleInfo is null) { throw new CommandException("name", "This DLC does not exist"); } if (dlc.BuilderState is null || dlc.FundKeyPath is null) { throw new CommandException("name", "This DLC is not in the right state to get executed"); } var evt = await Repository.GetEvent(dlc.OracleInfo.PubKey, dlc.OracleInfo.RValue); var attestation = context.ParseResult.CommandResult.GetArgumentValueOrDefault <string>("attestation")?.Trim(); Key oracleKey; try { if (attestation is null) { var k = evt?.Attestations?.Select(o => o.Value).FirstOrDefault(); if (k is null) { throw new CommandOptionRequiredException("attestation"); } oracleKey = k; } else { oracleKey = new Key(Encoders.Hex.DecodeData(attestation)); } } catch (CommandOptionRequiredException) { throw; } catch { throw new CommandException("attestation", "Cannot parse the attestation"); } var builder = new DLCTransactionBuilder(dlc.BuilderState.ToString(), Network); var key = await Repository.GetKey(dlc.FundKeyPath); try { var execution = builder.Execute(key, oracleKey); if (evt is Repository.Event) { await Repository.AddAttestation(dlc.OracleInfo, oracleKey); } context.WriteTransaction(execution.CET, Network); } catch (Exception ex) { throw new CommandException("attestation", $"Error while building the CET. ({ex.Message})"); } }