[NKP-0004] Skipping nonce check on payment channel

Payment channel update is (indefinitely) delayed before sending to chain and it’s hard to work with the nonce requirement. The simplest way to handle it is to skip the nonce check for payment channel txn, and use the equivalent nonce inside the channel to prevent double spending attack. We need to make sure there is no other problem (especially security one) caused by this.

(Another way is receiver create a txn and put his own nonce, but it makes the data structure more complicated as there are two signatures in a single txn, and also sender’s balance will decrease without a nonce update. I don’t consider it to be an optimal solution)

To put more context here: we’re talking about simple (one direction between 2 users) payment channels.

Flow is as such: whenever transfer amount is increased – sender generates tx of new type and send it to receiver off-chain. receiver can verify it by itself but decides whenever to send it on-chain or not. e.g. receiver can send on-chain only every 5th tx. every time tx is sent on-chain difference in amount between previous on-chain tx and current one is transferred from sender to receiver.

Actually, I don’t think second nonce is needed inside payload, because tx itself is already idempotent, i.e. there is no way (that I can think of) to double spend.

1 Like

Agree, that’s why I call it “equivalent” nonce :slight_smile:

1 Like

What is the approach for receiver in order to verify channel balance via off-chain?
Is it met on-chain error possible when receiver submitted accumulated tx?

Receiver can validate signature and can check sender’s balance.

Error is possible since sender’s funds aren’t locked, so sender could’ve spent funds elsewhere and receiver won’t be able to get them.
This is a trade-off between ease-of-use, scalability and security. Since receiver will submit tx at relatively short intervals, maximum he would lose is amount since his latest tx submission (cost of few minutes/megabytes of service he provided to sender).

Another question, what is the lifecycle of payment channel ID?
I only see NewUnidirectionalPaymentChannel but not found any close routine in PR #499
Do you means different channel ID for each receiver’s batch-submitted? It will generate mass PaymentChannel transactions should be submitted to on-chain for a longterm session.

Lifetime of channel id isn’t limited. You can use it today or year after. Since nothing is locked there is no reason to close anything.

Can you elaborate your second question? One session between 2 users will use single channel id, they can even use same channel id for subsequent sessions.

I misunderstand channel id is the unique identifier for instead of nonce in PaymentChannel transaction.
As my understand, it should contain an unique identifier in payload of signature, for prevent repetitive deduction via Replay Attack. (Such as nonce field in others transaction) But it is no such functional field in PR #499 if channel id not the unique identifier.

Let’s assumes a scene as below:
You and me had created an unidirection PaymentChannel for something trade once. Now I got your signed binary which sign(channel_id, YOU, ME, amount). Can I multiple deduct your property viciously via replay/resubmit the signed binary many times if your balance enough?

No, you can’t, because you will only receive difference between amount in the tx and already claimed amount.

If I have your tx with amount 5 and submit it onchain, then it’s saved in the blockchain that I already claimed 5 for this channel id. If I submit same tx again it’ll be rejected with error, since difference between amount in tx and already claimed amount is 0. I can only claim more if you send me another tx with bigger amount.

I got your idea. The amount is a positive & incremental number.

It extend my more details:
As tx recipient or tx witness/validator in consensus, is there something way to refer last payment quickly in order to calc the margin between amount in current & last claimed?

Forgive me.
I saw the DefaultLedger.Store.GetUnidirectionalPaymentChannelBalance in VerifyTransactionWithLedger.

Here is my original imagine:

  1. Create a channel with channel id for lock prepayment at business session beginning
  2. Sender signed payment off-chain with accumulated amount periodically during session keeping
  3. Recipient got lots of signed payment with different(incremental) amount via off-chain at session ending. He should submit the maximal payment to on-chain since he has only once chance to claim earning for each channel id.
  4. The channel id reach EOL when the recipient claim was confirmed by on-chain consensus. The unspent remainder of prepayment will be return to sender atomicity.


Only two transactions(lock & claim) will be submitted on-chain for each business session whatever long term service. Save more ledger space and more concurrent payment channels base same TPS

As current non-lock solution, recipient have to submit signed payment frequently for claim in time avoid met sender not sufficient funds. It will cause lots of micropayment during a long term business session.


It is hard to know ahead how much approximately going to spend at business session beginning. Lock prepayment will cause usability decline.

I agree that full safety cannot be achieved without prepay or lock, but I don’t treat this unidirectional micro payment channel as a general payment channel (which should be implemented in VM), and we can loosen the security assumption for more convenience and liqudity (no need for prepay & lock) because of the following reasons:

My understanding for the position of the unidirectional (micro) payment channel is that, it should only be used for the bandwidth (or other resources like CPU) consumption, and the update frequency is around ~10s or ~1min. So each single update has really low value, e.g. $0.0001 for 10MB bandwidth cost on DO. If such amount is withdrawn before the receiver submit the txn, his loss is quite limited. As long as it doesn’t affect the security of non-channel txn, I think we can tolerate that risk so that sender do not need to lock or prepay for EVERY receiver he is doing business with.

But my concern now is that, if in the future most txn has a non zero txn fee, with the current design the sender has to pay it (for every txn receiver submit to chain). So submitting as many intermediate states as possible may cause sender to loss money while the receiver is not losing anything.

How about taking fee from the amount sent in this case? So if receiver submit more frequently he will receive less and sender only lose whatever he paid.
If the amount sent doesn’t cover fee we can decline tx with error “try later” or “try bigger amount”.

What will happen if receiver will never claim? Funds will stay locked forever?

Also, what if user needs to use few services at the same time? He should lock some amount for every service he use?

The fee has to be filled up when sender creates and sends the txn, so there will be 2 potential problems: 1. when sender creates the txn, he has no idea what would be a good (small but enough) value for txn fee. 2. sender may probably only want to put 0 txn fee because that makes it less likely that the txn will be packed in a block (and thus he needs to actually pay)

For the standard payment channel @Moore mentioned, sender needs to make a separate deposit for EVERY different receiver he is working with, and that’s the liquidity problem for the payment channel. Using channel routing (in channel space, not network space) can increase liquidity to some degree but the problem still exists. That’s one of the main reason I thought we probably don’t want to use it for our super micro payment channel.

How about allow receiver to specify fee which will be taken from the amount he wants to claim? Add some field that doesn’t take part in tx signature generation and acts as channel claim tx fee amount.

Or it can have 0 fee specified and receiver will need to send onchain another tx type: ClaimTx, which will contain first tx in payload and fee for it.

The ClaimTx approach sounds good to me, and it should work if we put any other txn type as payload so it should be a quite general solution.

This NKP has been implemented