public void Save(int cycleId, PromiseServerSession 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); }); }
public PromiseServerSession SetSignedTransaction(Transaction transaction) { AssertState(BobServerChannelNegotiationStates.WaitingSignedTransaction); var escrow = BuildEscrowTxOut(); var output = transaction.Outputs.AsIndexedOutputs() .Single(o => o.TxOut.ScriptPubKey == escrow.ScriptPubKey && o.TxOut.Value == escrow.Value); var escrowedCoin = new Coin(output).ToScriptCoin(CreateEscrowScript()); PromiseServerSession session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); session.ConfigureEscrowedCoin(escrowedCoin, InternalState.EscrowKey, InternalState.RedeemKey); InternalState.EscrowKey = null; InternalState.RedeemKey = null; InternalState.Status = BobServerChannelNegotiationStates.Completed; return(session); }
private void RoundTrip(ref PromiseServerSession server, PromiseParameters parameters) { var clone = Serializer.Clone(server.GetInternalState()); server = new PromiseServerSession(clone, parameters); }
public void TestPuzzlePromise() { RsaKey key = TestKeys.Default; Key serverEscrow = new Key(); Key clientEscrow = new Key(); var parameters = new PromiseParameters(key.PubKey) { FakeTransactionCount = 5, RealTransactionCount = 5 }; var client = new PromiseClientSession(parameters); var server = new PromiseServerSession(parameters); var coin = CreateEscrowCoin(serverEscrow.PubKey, clientEscrow.PubKey); client.ConfigureEscrowedCoin(coin, clientEscrow); SignaturesRequest request = client.CreateSignatureRequest(clientEscrow.PubKey.Hash, FeeRate); RoundTrip(ref client, parameters); RoundTrip(ref request); server.ConfigureEscrowedCoin(coin, serverEscrow, new Key().ScriptPubKey); PuzzlePromise.ServerCommitment[] commitments = server.SignHashes(request); RoundTrip(ref server, parameters); RoundTrip(ref commitments); PuzzlePromise.ClientRevelation revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); ServerCommitmentsProof proof = server.CheckRevelation(revelation); RoundTrip(ref server, parameters); RoundTrip(ref proof); var puzzleToSolve = client.CheckCommitmentProof(proof); RoundTrip(ref client, parameters); Assert.NotNull(puzzleToSolve); var solution = key.SolvePuzzle(puzzleToSolve); var transactions = client.GetSignedTransactions(solution).ToArray(); RoundTrip(ref client, parameters); Assert.True(transactions.Length == parameters.RealTransactionCount); var escrow = server.GetInternalState().EscrowedCoin; // In case things do not go well and timeout is hit... var redeemTransaction = server.CreateRedeemTransaction(FeeRate); var resigned = redeemTransaction.ReSign(escrow); TransactionBuilder bb = new TransactionBuilder(); bb.AddCoins(server.GetInternalState().EscrowedCoin); Assert.True(bb.Verify(resigned)); //Check can ve reclaimed if malleated bb = new TransactionBuilder(); escrow.Outpoint = new OutPoint(escrow.Outpoint.Hash, 10); bb.AddCoins(escrow); resigned = redeemTransaction.ReSign(escrow); Assert.False(bb.Verify(redeemTransaction.Transaction)); Assert.True(bb.Verify(resigned)); }
public async Task <uint160.MutableUint160> BeginOpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); if (Repository.IsUsed(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = await Services.FeeService.GetFeeRateAsync(); try { if (!Parameters.VoucherKey.PublicKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().WitHash.ScriptPubKey.Hash); var unused = Services.WalletService.FundTransactionAsync(txOut, fee) .ContinueWith(async(Task <Transaction> task) => { try { var tx = await task.ConfigureAwait(false); var correlation = new CorrelationId(channelId); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); //Logging/Broadcast per funding TX one time if (Repository.MarkUsedNonce(cycle.Start, new uint160(tx.GetHash().ToBytes().Take(20).ToArray()))) { var bobCount = Parameters.CountEscrows(tx, Client.Identity.Bob); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} channel created {tx.GetHash()} with {bobCount} users"); await Services.BroadcastService.BroadcastAsync(tx).ConfigureAwait(false); } await Services.BlockExplorerService.TrackAsync(txOut.ScriptPubKey).ConfigureAwait(false); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = await Services.WalletService.GenerateAddressAsync().ConfigureAwait(false); session.ConfigureEscrowedCoin(channelId, coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); var redeemTx = session.CreateRedeemTransaction(fee); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); Repository.Save(cycle.Start, session); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); } catch (Exception ex) { Logs.Tumbler.LogCritical(new EventId(), ex, "Error during escrow transaction callback"); } }); return(channelId.AsBitcoinSerializable()); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public ScriptCoinModel OpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = Services.FeeService.GetFeeRate(); try { if (!Parameters.VoucherKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("nonce-already-used")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().Hash); var tx = Services.WalletService.FundTransaction(txOut, fee); var correlation = escrow.GetCorrelation(); var escrowTumblerLabel = $"Cycle {cycle.Start} Tumbler Escrow"; Services.BlockExplorerService.Track(txOut.ScriptPubKey); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Channel created " + tx.GetHash()); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = Services.WalletService.GenerateAddress(); session.ConfigureEscrowedCoin(coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); Repository.Save(cycle.Start, session); Services.BroadcastService.Broadcast(tx); var redeemTx = session.CreateRedeemTransaction(fee); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); return(new ScriptCoinModel(session.EscrowedCoin)); } catch (PuzzleException) { throw new ActionResultException(BadRequest("incorrect-voucher")); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public void TestPuzzlePromise() { RsaKey key = TestKeys.Default; Key serverEscrow = new Key(); Key clientEscrow = new Key(); var parameters = new PromiseParameters(key.PubKey) { FakeTransactionCountPerLevel = 5, RealTransactionCountPerLevel = 5, PaymentsCount = 5 //Not sure if this is the way to go. }; var client = new PromiseClientSession(parameters); var server = new PromiseServerSession(parameters); var coin = CreateEscrowCoin(serverEscrow.PubKey, clientEscrow.PubKey); client.ConfigureEscrowedCoin(coin, clientEscrow); SignaturesRequest request = client.CreateSignatureRequest(clientEscrow.PubKey.Hash, FeeRate); RoundTrip(ref client, parameters); RoundTrip(ref request); server.ConfigureEscrowedCoin(uint160.Zero, coin, serverEscrow, new Key().ScriptPubKey); PuzzlePromise.ServerCommitment[][] commitments = server.SignHashes(request); RoundTrip(ref server, parameters); RoundTrip(ref commitments); PuzzlePromise.ClientRevelation revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); ServerCommitmentsProof proof = server.CheckRevelation(revelation, clientEscrow.PubKey.Hash, FeeRate); RoundTrip(ref server, parameters); RoundTrip(ref proof); var puzzlesToSolve = client.CheckCommitmentProof(proof); RoundTrip(ref client, parameters); foreach (var puzzle in puzzlesToSolve) { Assert.NotNull(puzzle); } for (int i = 0; i < puzzlesToSolve.Length; i++) { // Doesn't work for now! Need to figure how Bob will be spending the puzzles. var solution = key.SolvePuzzle(puzzlesToSolve[i]); // I'm not sure if GetSignedTransactions should handle all payments or only one payment at a time. var transactions = client.GetSignedTransactions(solution, i).ToArray(); RoundTrip(ref client, parameters); Assert.True(transactions.Length == parameters.RealTransactionCountPerLevel); } var escrow = server.GetInternalState().EscrowedCoin; // In case things do not go well and timeout is hit... var redeemTransaction = server.CreateRedeemTransaction(FeeRate); var resigned = redeemTransaction.ReSign(escrow); TransactionBuilder bb = new TransactionBuilder(); bb.AddCoins(server.GetInternalState().EscrowedCoin); Assert.True(bb.Verify(resigned)); //Check can ve reclaimed if malleated bb = new TransactionBuilder(); escrow.Outpoint = new OutPoint(escrow.Outpoint.Hash, 10); bb.AddCoins(escrow); resigned = redeemTransaction.ReSign(escrow); Assert.False(bb.Verify(redeemTransaction.Transaction)); Assert.True(bb.Verify(resigned)); }