public SolverServerSession ConfirmClientEscrow(Transaction transaction, out PuzzleSolution solvedVoucher) { AssertState(AliceServerChannelNegotiationStates.WaitingClientEscrow); solvedVoucher = null; var escrow = CreateEscrowScript(); var coin = transaction.Outputs.AsCoins().FirstOrDefault(txout => txout.ScriptPubKey == escrow.Hash.ScriptPubKey); if (coin == null) { throw new PuzzleException("No output containing the escrowed coin"); } if (coin.Amount != Parameters.Denomination + Parameters.Fee) { throw new PuzzleException("Incorrect amount"); } var voucher = InternalState.UnsignedVoucher; var escrowedCoin = coin.ToScriptCoin(escrow); var session = new SolverServerSession(TumblerKey, Parameters.CreateSolverParamaters()); session.ConfigureEscrowedCoin(escrowedCoin, InternalState.EscrowKey); InternalState.UnsignedVoucher = null; InternalState.OtherEscrowKey = null; InternalState.RedeemKey = null; InternalState.EscrowKey = null; solvedVoucher = voucher.WithRsaKey(VoucherKey.PubKey).Solve(VoucherKey); InternalState.Status = AliceServerChannelNegotiationStates.Completed; return(session); }
public void Save(int cycleId, SolverServerSession session) { Repository.UpdateOrInsert(GetCyclePartition(cycleId), session.Id.ToString(), session.GetInternalState(), (o, n) => { if (o.ETag != n.ETag) { throw new InvalidOperationException("Optimistic concurrency failure"); } n.ETag++; return(n); }); }
private void RoundTrip(ref SolverServerSession server, SolverParameters parameters, RsaKey key) { var clone = Serializer.Clone(server.GetInternalState()); server = new SolverServerSession(key, parameters, clone); }
public void TestPuzzleSolver() { RsaKey key = TestKeys.Default; PuzzleSolution expectedSolution = null; Puzzle puzzle = key.PubKey.GeneratePuzzle(ref expectedSolution); var parameters = new SolverParameters { FakePuzzleCount = 50, RealPuzzleCount = 10, ServerKey = key.PubKey }; SolverClientSession client = new SolverClientSession(parameters); SolverServerSession server = new SolverServerSession(key, parameters); var clientEscrow = new Key(); var serverEscrow = new Key(); var escrow = CreateEscrowCoin(clientEscrow.PubKey, serverEscrow.PubKey); var redeemDestination = new Key().ScriptPubKey; client.ConfigureEscrowedCoin(escrow, clientEscrow, redeemDestination); client.AcceptPuzzle(puzzle.PuzzleValue); RoundTrip(ref client, parameters); Assert.True(client.GetInternalState().RedeemDestination == redeemDestination); PuzzleValue[] puzzles = client.GeneratePuzzles(); RoundTrip(ref client, parameters); RoundTrip(ref puzzles); server.ConfigureEscrowedCoin(escrow, serverEscrow); var commitments = server.SolvePuzzles(puzzles); RoundTrip(ref server, parameters, key); RoundTrip(ref commitments); var revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); SolutionKey[] fakePuzzleKeys = server.CheckRevelation(revelation); RoundTrip(ref server, parameters, key); RoundTrip(ref fakePuzzleKeys); BlindFactor[] blindFactors = client.GetBlindFactors(fakePuzzleKeys); RoundTrip(ref client, parameters); RoundTrip(ref blindFactors); var offerInformation = server.CheckBlindedFactors(blindFactors, FeeRate); RoundTrip(ref server, parameters, key); var clientOfferSig = client.SignOffer(offerInformation); //Verify if the scripts are correctly created var fulfill = server.FulfillOffer(clientOfferSig, new Key().ScriptPubKey, FeeRate); var offerRedeem = client.CreateOfferRedeemTransaction(FeeRate); var offerTransaction = server.GetSignedOfferTransaction(); var offerCoin = offerTransaction.Transaction.Outputs.AsCoins().First(); var resigned = offerTransaction.ReSign(client.EscrowedCoin); TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder.AddCoins(client.EscrowedCoin); Assert.True(txBuilder.Verify(resigned)); resigned = fulfill.ReSign(offerCoin); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerCoin); Assert.True(txBuilder.Verify(resigned)); var offerRedeemTx = offerRedeem.ReSign(offerCoin); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerCoin); Assert.True(txBuilder.Verify(offerRedeemTx)); client.CheckSolutions(fulfill.Transaction); RoundTrip(ref client, parameters); var clientEscapeSignature = client.SignEscape(); var escapeTransaction = server.GetSignedEscapeTransaction(clientEscapeSignature, FeeRate, new Key().ScriptPubKey); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(client.EscrowedCoin); Assert.True(txBuilder.Verify(escapeTransaction)); var solution = client.GetSolution(); RoundTrip(ref client, parameters); Assert.True(solution == expectedSolution); }
private static CorrelationId GetCorrelation(SolverServerSession session) { return(new CorrelationId(session.Id)); }
public async Task <PuzzleSolution> SignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } if (request.UnsignedVoucher == null) { throw new ActionResultException(BadRequest("Missing UnsignedVoucher")); } if (request.MerkleProof == null) { throw new ActionResultException(BadRequest("Missing MerkleProof")); } if (request.Transaction == null) { throw new ActionResultException(BadRequest("Missing Transaction")); } if (request.ClientEscrowKey == null) { throw new ActionResultException(BadRequest("Missing ClientEscrowKey")); } if (request.ChannelId == null) { throw new ActionResultException(BadRequest("Missing ChannelId")); } var cycle = GetCycle(request.Cycle); var height = Services.BlockExplorerService.GetCurrentHeight(); if (!cycle.IsInPhase(CyclePhase.ClientChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } if (request.MerkleProof.PartialMerkleTree .GetMatchedTransactions() .FirstOrDefault() != request.Transaction.GetHash() || !request.MerkleProof.Header.CheckProofOfWork()) { Logs.Tumbler.LogDebug("Invalid transaction merkle proof"); throw new ActionResultException(BadRequest("invalid-merkleproof")); } var confirmations = Services.BlockExplorerService.GetBlockConfirmations(request.MerkleProof.Header.GetHash()); if ((confirmations < Parameters.CycleGenerator.FirstCycle.SafetyPeriodDuration)) { Logs.Tumbler.LogDebug("Not enough confirmations"); throw new ActionResultException(BadRequest("not-enough-confirmation")); } var transaction = request.Transaction; if (transaction.Outputs.Count > 2) { Logs.Tumbler.LogDebug("Incorrect number of outputs"); throw new ActionResultException(BadRequest("invalid-transaction")); } var key = Repository.GetKey(cycle.Start, request.KeyReference); if (Repository.IsUsed(cycle.Start, request.ChannelId)) { throw new ActionResultException(BadRequest("duplicate-query")); } var expectedEscrow = new EscrowScriptPubKeyParameters(request.ClientEscrowKey, key.PubKey, cycle.GetClientLockTime()); var expectedTxOut = new TxOut(Parameters.Denomination + Parameters.Fee, expectedEscrow.ToScript().WitHash.ScriptPubKey.Hash); var escrowedCoin = transaction .Outputs .AsCoins() .Where(c => c.TxOut.Value == expectedTxOut.Value && c.TxOut.ScriptPubKey == expectedTxOut.ScriptPubKey) .Select(c => c.ToScriptCoin(expectedEscrow.ToScript())) .FirstOrDefault(); if (escrowedCoin == null) { Logs.Tumbler.LogDebug("Could not find escrowed coin"); throw new ActionResultException(BadRequest("invalid-transaction")); } var solverServerSession = new SolverServerSession(Runtime.TumblerKey, Parameters.CreateSolverParamaters()); solverServerSession.SetChannelId(request.ChannelId); solverServerSession.ConfigureEscrowedCoin(escrowedCoin, key); await Services.BlockExplorerService.TrackAsync(escrowedCoin.ScriptPubKey); if (!await Services.BlockExplorerService.TrackPrunedTransactionAsync(request.Transaction, request.MerkleProof)) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } //Without this one, someone could spam the nonce db by replaying this request with different channelId if (!Repository.MarkUsedNonce(cycle.Start, Hashes.Hash160(escrowedCoin.Outpoint.ToBytes()))) { throw new ActionResultException(BadRequest("duplicate-query")); } AssertNotDuplicateQuery(cycle.Start, request.ChannelId); Repository.Save(cycle.Start, solverServerSession); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Proof of Escrow signed for " + transaction.GetHash()); var correlation = GetCorrelation(solverServerSession); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, request.Transaction.GetHash(), correlation); var solution = request.UnsignedVoucher.WithRsaKey(Runtime.VoucherKey.PubKey).Solve(Runtime.VoucherKey); return(solution); }
private static CorrelationId GetCorrelation(SolverServerSession session) { return(EscrowScriptPubKeyParameters.GetFromCoin(session.EscrowedCoin).GetCorrelation()); }
public PuzzleSolution SignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } if (request.UnsignedVoucher == null) { throw new ActionResultException(BadRequest("Missing UnsignedVoucher")); } if (request.MerkleProof == null) { throw new ActionResultException(BadRequest("Missing MerkleProof")); } if (request.Transaction == null) { throw new ActionResultException(BadRequest("Missing Transaction")); } if (request.ClientEscrowKey == null) { throw new ActionResultException(BadRequest("Missing ClientEscrowKey")); } if (request.MerkleProof.PartialMerkleTree .GetMatchedTransactions() .FirstOrDefault() != request.Transaction.GetHash() || !request.MerkleProof.Header.CheckProofOfWork()) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } var confirmations = Services.BlockExplorerService.GetBlockConfirmations(request.MerkleProof.Header.GetHash()); if ((confirmations < Parameters.CycleGenerator.FirstCycle.SafetyPeriodDuration)) { throw new ActionResultException(BadRequest("not-enough-confirmation")); } var transaction = request.Transaction; if (transaction.Outputs.Count > 2) { throw new ActionResultException(BadRequest("invalid-transaction")); } var cycle = GetCycle(request.Cycle); var height = Services.BlockExplorerService.GetCurrentHeight(); if (!cycle.IsInPhase(CyclePhase.ClientChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var key = Repository.GetKey(cycle.Start, request.KeyReference); var expectedEscrow = new EscrowScriptPubKeyParameters(request.ClientEscrowKey, key.PubKey, cycle.GetClientLockTime()); var expectedTxOut = new TxOut(Parameters.Denomination + Parameters.Fee, expectedEscrow.ToScript().Hash); var escrowedCoin = transaction .Outputs .AsCoins() .Where(c => c.TxOut.Value == expectedTxOut.Value && c.TxOut.ScriptPubKey == expectedTxOut.ScriptPubKey) .Select(c => c.ToScriptCoin(expectedEscrow.ToScript())) .FirstOrDefault(); if (escrowedCoin == null) { throw new ActionResultException(BadRequest("invalid-transaction")); } try { var solverServerSession = new SolverServerSession(Runtime.TumblerKey, Parameters.CreateSolverParamaters()); solverServerSession.ConfigureEscrowedCoin(escrowedCoin, key); Services.BlockExplorerService.Track(escrowedCoin.ScriptPubKey); if (!Services.BlockExplorerService.TrackPrunedTransaction(request.Transaction, request.MerkleProof)) { throw new ActionResultException(BadRequest("invalid-merkleproof")); } if (!Repository.MarkUsedNonce(cycle.Start, new uint160(key.PubKey.Hash.ToBytes()))) { throw new ActionResultException(BadRequest("invalid-transaction")); } Repository.Save(cycle.Start, solverServerSession); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Proof of Escrow signed for " + transaction.GetHash()); var correlation = GetCorrelation(solverServerSession); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, request.Transaction.GetHash(), correlation); var solution = request.UnsignedVoucher.WithRsaKey(Runtime.VoucherKey.PubKey).Solve(Runtime.VoucherKey); return(solution); } catch (PuzzleException) { throw new ActionResultException(BadRequest("invalid-transaction")); } }
public void TestPuzzleSolver() { RsaKey key = TestKeys.Default; PuzzleSolution expectedSolution = null; Puzzle puzzle = key.PubKey.GeneratePuzzle(ref expectedSolution); var parameters = new SolverParameters { FakePuzzleCount = 50, RealPuzzleCount = 10, ServerKey = key.PubKey }; SolverClientSession client = new SolverClientSession(parameters); SolverServerSession server = new SolverServerSession(key, parameters); var clientEscrow = new Key(); var serverEscrow = new Key(); var clientRedeem = new Key(); var escrow = CreateEscrowCoin(clientEscrow.PubKey, serverEscrow.PubKey, clientRedeem.PubKey); client.ConfigureEscrowedCoin(escrow, clientEscrow, clientRedeem); client.AcceptPuzzle(puzzle.PuzzleValue); RoundTrip(ref client, parameters); PuzzleValue[] puzzles = client.GeneratePuzzles(); RoundTrip(ref client, parameters); RoundTrip(ref puzzles); server.ConfigureEscrowedCoin(escrow, serverEscrow); var commitments = server.SolvePuzzles(puzzles); RoundTrip(ref server, parameters, key); RoundTrip(ref commitments); var revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); SolutionKey[] fakePuzzleKeys = server.CheckRevelation(revelation); RoundTrip(ref server, parameters, key); RoundTrip(ref fakePuzzleKeys); BlindFactor[] blindFactors = client.GetBlindFactors(fakePuzzleKeys); RoundTrip(ref client, parameters); RoundTrip(ref blindFactors); var offerInformation = server.CheckBlindedFactors(blindFactors, FeeRate); RoundTrip(ref server, parameters, key); var clientOfferSig = client.SignOffer(offerInformation); //Verify if the scripts are correctly created var fulfill = server.FulfillOffer(clientOfferSig, new Key().ScriptPubKey, FeeRate); var offerTransaction = server.GetSignedOfferTransaction(); TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder.AddCoins(client.EscrowedCoin); Assert.True(txBuilder.Verify(offerTransaction.Transaction)); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerTransaction.Transaction.Outputs.AsCoins().ToArray()); Assert.True(txBuilder.Verify(fulfill.Transaction)); //Check if can resign fulfill in case offer get malleated offerTransaction.Transaction.LockTime = new LockTime(1); fulfill.Transaction.Inputs[0].PrevOut = offerTransaction.Transaction.Outputs.AsCoins().First().Outpoint; txBuilder = new TransactionBuilder(); txBuilder.Extensions.Add(new OfferBuilderExtension()); txBuilder.AddKeys(server.GetInternalState().FulfillKey); txBuilder.AddCoins(offerTransaction.Transaction.Outputs.AsCoins().ToArray()); txBuilder.SignTransactionInPlace(fulfill.Transaction); Assert.True(txBuilder.Verify(fulfill.Transaction)); //////////////////////////////////////////////// client.CheckSolutions(fulfill.Transaction); RoundTrip(ref client, parameters); var solution = client.GetSolution(); RoundTrip(ref client, parameters); Assert.True(solution == expectedSolution); }