public async Task <IActionResult> BeginSignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException(nameof(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(this.Parameters.Network.Consensus)) { 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); Logs.Tumbler.LogDebug($"MainControllers.BeginSignVoucher() - escrowedCoin.Outpoint.Hash : {escrowedCoin.Outpoint.Hash}, escrowedCoin.Outpoint.N : {escrowedCoin.Outpoint.N}, escrowKey.GetWif() : {key.GetWif(this.Parameters.Network)}"); solverServerSession.ConfigureEscrowedCoin(escrowedCoin, key); await Services.BlockExplorerService.TrackAsync(escrowedCoin.ScriptPubKey); //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); QueueWork(async() => { try { if (!await Services.BlockExplorerService.TrackPrunedTransactionAsync(request.Transaction, request.MerkleProof)) { Logs.Tumbler.LogDebug("Invalid merkleproof for " + transaction.GetHash()); return; } 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); Repository.SaveSignedVoucher(cycle.Start, request.ChannelId, solution); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Proof of Escrow signed for " + transaction.GetHash()); } catch (Exception ex) { Logs.Tumbler.LogCritical(new EventId(), ex, "Unhandled error during while signing voucher"); } }); return(Ok()); }
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); }
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(uint160.Zero, 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, out bool cached); Assert.False(cached); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerCoin); Assert.True(txBuilder.Verify(resigned)); //Test again to see if cached signature works well resigned = fulfill.ReSign(offerCoin, out cached); Assert.True(cached); 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); }
public IActionResult SignVoucher( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] SignVoucherRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } if (request.UnsignedVoucher == null) { return(BadRequest("Missing UnsignedVoucher")); } if (request.MerkleProof == null) { return(BadRequest("Missing MerkleProof")); } if (request.Transaction == null) { return(BadRequest("Missing Transaction")); } if (request.ClientEscrowKey == null) { return(BadRequest("Missing ClientEscrowKey")); } if (request.MerkleProof.PartialMerkleTree .GetMatchedTransactions() .FirstOrDefault() != request.Transaction.GetHash() || !request.MerkleProof.Header.CheckProofOfWork()) { return(BadRequest("invalid-merkleproof")); } var confirmations = Services.BlockExplorerService.GetBlockConfirmations(request.MerkleProof.Header.GetHash()); if ((confirmations < Parameters.CycleGenerator.FirstCycle.SafetyPeriodDuration)) { return(BadRequest("not-enough-confirmation")); } var transaction = request.Transaction; if (transaction.Outputs.Count > 2) { return(BadRequest("invalid-transaction")); } var cycle = GetCycle(request.Cycle); var height = Services.BlockExplorerService.GetCurrentHeight(); if (!cycle.IsInPhase(CyclePhase.ClientChannelEstablishment, height)) { return(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) { return(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)) { return(BadRequest("invalid-merkleproof")); } if (!Repository.MarkUsedNonce(cycle.Start, new uint160(key.PubKey.Hash.ToBytes()))) { return(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(Json(solution)); } catch (PuzzleException) { return(BadRequest("invalid-transaction")); } }