The legacy unlock_time field in Monero is being deprecated. It's planned that the next hard fork will enforce a value of 0 in all transactions.
The arguments against unlock_time are:
- It's not actually useful for any decentralized protocol (payment channels, atomic swaps).
- It's a DoS vector on the FCMP++ tree.
- It creates anonymity puddles.
- There have been issues with merchants receiving maliciously locked payments.
Relative locks
Relative locks are useful for a variety of decentralized protocols. Notably, payment channels are notoriously hard to design for Monero. The simplest payment channel protocol only requires adaptor signatures and relative locks. Adaptor signatures are already possible with FCMP++ (and the separate membership proof further facilitates pre-signed transactions). The only missing feature is a simple relative lock.
Note that Monero already enforces a relative 10-block lock. For FCMP++, it means the tree root R used for the membership proof must be at least 10 blocks old. This proposal adds the option to make the lock longer than 10 blocks.
Let Rh be the FCMP tree root at height h and ref be the explicit transaction reference height (the earliest height the transaction is mineable), which is specified in the membership proof.
This proposal completely changes the way locked transactions work after the FCMP++ hard fork. The lock is not applied to the transaction outputs. It's applied to transaction inclusion in the blockchain. Locked transactions cannot even enter the mempool until they unlock.
Simple "leaky" locks
The simplest way to implement a relative lock with virtually zero cost would be to disable the original Monero lock mechanism but allow unlock_time to have a value of 0 or 1. The relative lock would be enforced by the consensus layer by forcing a specific value for the FCMP tree root R:
unlock_time |
R |
Approx. lock duration |
0 |
Rref-10 |
20 minutes |
1 |
Rref-720 |
24 hours |
Transactions with unlock_time = 0 enforce only the default 10-block lock, while transactions with unlock_time = 1 enforce a 720-block lock, which corresponds to a lock time of 24 hours, which is sufficient for the dispute mechanism in decentralized payment channel protocols.
Because the unlock_time field is public, I'm proposing only 2 values for the lock time to limit the impact of the resulting anonymity puddles. Time-locked transactions would be easily identifiable in the blockchain. To avoid leaking the fact that the transaction was time-locked, wallets could randomly set unlock_time to 1 when spending an output older than the lock time with a small probability. The leakage would be limited to the fact that the age of the spent output was at least 24 hours.
Hiding locks with a ring signature
@UkoeHB suggested a more private solution based on a ring signature on the root hash comitment.
The protocol will support a few possible lock times, for example:
i |
Ri |
Approx. lock duration |
0 |
Rref-10 |
20 minutes |
1 |
Rref-90 |
3 hours |
2 |
Rref-720 |
1 day |
3 |
Rref-5040 |
1 week |
4 |
Rref-40320 |
8 weeks |
5 |
Rref-262800 |
1 year |
6 |
Rref-2102400 |
8 years |
Each transaction will include an additional commitment L = j H + x G with j equal to one of the i values from the table above (i.e. 0 <= j <= 6). The value j = 0 corresponds to the default 10-block lock. The commitment L is signed by the spend authorization proof.
The FCMP++ proof outputs a tree root commitment R = Rj + y G where Rj is one of the Ri values from the table.
We then define the ring (L - i H, R - Ri) for each i = 0..6. Note that the prover will know the opening of exactly the j-th pair from the ring. This can be proven with a standard ring signature. The approximate size of a 7-ring signature is 256 bytes, which is relatively small compared to the size of the FCMP++ proof.
The purpose of this construction is for the pre-signed commitment L to force a relative lock without publicly revealing that the transaction was pre-signed and time-locked.
Payment channel protocol sketch
Here is a sketch of a trustless payment channel protocol that required a relative lock on a pre-signed transaction. The payment channel is bidirectional between Alice and Bob. Transactions fees are ignored for clarity.
Opening the channel
The channel is opened by Alice and Bob jointly signing 4 transactions: Fund, CloseA, CloseB and Recover. The Fund transaction is immediately submitted to the network. Alice keeps CloseA and Recover as her offline transactions and Bob keeps CloseB and Recover as his offline transactions.
The channel opening process is:
- Alice and Bob agree on the initial channel balances.
- Alice and Bob agree on a 2-of-2 stealth address
Kfund.
- Alice and Bob agree on a 2-of-2 stealth address
Kclose0.
- Alice and Bob jointly sign the initial Recover transaction, which spends from
Kclose0 to Alice's and Bob's private addresses according to the correct channel balances. The Recover transaction is time-locked relative to the preceding Close transaction.
- Alice generates a key pair and shares the public key
TA0 with Bob.
- Bob generates a key pair and shares the public key
TB0 with Alice.
- Alice signs her half of the CloseB transaction, which spends the entire channel balance from
Kfund to Kclose0. Alice uses R + TB0 as the signature nonce. This creates an adaptor signature only Bob can complete.
- Bob signs his half of the CloseA transaction, which spends the entire channel balance from
Kfund to Kclose0. Bob uses R + TA0 as the signature nonce. This creates an adaptor signature only Alice can complete.
- Finally, Alice and Bob jointly sign the Fund transaction, which spends their private inputs into the stealth address
Kfund.
The channel opening is complete when the Fund transaction is confirmed on the Monero network.
[Fund]
|
___________|_____________
| |
v V
[CloseA(0)] [CloseB(0)]
| |
|_______________________|
|
v
[Recover(0)(time-locked)]
Channel state update
The channel transitions from state N-1 to state N (with new channel balances for Alice and Bob) as follows:
- Alice and Bob agree on new channel balances.
- Alice and Bob agree on a 2-of-2 stealth address
KcloseN.
- Alice and Bob jointly sign the N-state Recover transaction, which spends from
KcloseN to Alice's and Bob's private address according to the correct channel balances. The Recover transaction is time-locked relative to the preceding Close transaction.
- Alice generates a key pair and shares the public key
TAN with Bob.
- Bob generates a key pair and shares the public key
TBN with Alice.
- Alice signs her half of the N-state CloseB transaction, which spends the entire channel balance from
Kfund to KcloseN. Alice uses R + TBN as the signature nonce. This creates an adaptor signature only Bob can complete.
- Bob signs his half of the CloseA transaction, which spends the entire channel balance from
Kfund to KcloseN. Bob uses R + TAN as the signature nonce. This creates an adaptor signature only Alice can complete.
- Alice signs her half of the N-state PunishA transaction, which spends the entire channel balance from
KcloseN-1 to Bob's private address. Alice uses R + TAN-1 as the signature nonce, but omits tAN-1 from the signature response, which makes the signature invalid.
- Bob signs his half of the N-state PunishB transaction, which spends the entire channel balance from
KcloseN-1 to Alice's private address. Bob uses R + TBN-1 as the signature nonce, but omits tBN-1 from the signature response, which makes the signature invalid.
The new transaction graph looks like this:
[Fund]
|
___________|_____________
| |
v V
[CloseA(N-1)] [CloseB(N-1)]
| |
|_______________________|
|
|
-> [Recover(N-1)(time-locked)]
|
|
-> [PunishA(N)]
|
|
-> [PunishB(N)]
[Fund]
|
___________|_____________
| |
v V
[CloseA(N)] [CloseB(N)]
| |
|_______________________|
|
v
[Recover(N)(time-locked)]
Closing the channel
| Type |
Description |
On-chain transactions |
| collaborative close |
Alice and Bob jointly close the channel |
Fund, Withdraw |
| undisputed force close |
one party forces the channel to close |
Fund, Close, Recover (time-locked) |
| disputed force close |
one party forces the channel to close with incorrect state |
Fund, Close, Punish |
Collaborative close
The happy path is if Alice and Bob both agree to close the channel. In that case, they jointly sign a new Withdraw transaction, which spends from Kfund to Alice's and Bob's private addresses according to the final channel balances.
Undisputed force close
If Bob is unresponsive, Alice can force the channel to close by publishing the CloseA(N) transaction. She then has to wait for 24 hours and then publish the Recover(N) transaction, which will distribute the funds in the channel according to the final state.
Note that this is the only case when a time-locked transaction with appears on the chain.
Disputed force close
Alice can try to maliciously close the channel with an old state by publishing CloseA(i) with i < N.
When CloseA(i) appears on the chain, Bob can recover tAi from the valid transaction signature (using the adaptor saved in step i+1). This allows Bob to complete the signature of the PunishA(i+1) transaction. Since the Punish transaction is not locked, Bob can front-run Alice's Recover transaction and punish Alice by taking the entire channel balance.
The legacy
unlock_timefield in Monero is being deprecated. It's planned that the next hard fork will enforce a value of 0 in all transactions.The arguments against
unlock_timeare:Relative locks
Relative locks are useful for a variety of decentralized protocols. Notably, payment channels are notoriously hard to design for Monero. The simplest payment channel protocol only requires adaptor signatures and relative locks. Adaptor signatures are already possible with FCMP++ (and the separate membership proof further facilitates pre-signed transactions). The only missing feature is a simple relative lock.
Note that Monero already enforces a relative 10-block lock. For FCMP++, it means the tree root
Rused for the membership proof must be at least 10 blocks old. This proposal adds the option to make the lock longer than 10 blocks.Let
Rhbe the FCMP tree root at heighthandrefbe the explicit transaction reference height (the earliest height the transaction is mineable), which is specified in the membership proof.This proposal completely changes the way locked transactions work after the FCMP++ hard fork. The lock is not applied to the transaction outputs. It's applied to transaction inclusion in the blockchain. Locked transactions cannot even enter the mempool until they unlock.
Simple "leaky" locks
The simplest way to implement a relative lock with virtually zero cost would be to disable the original Monero lock mechanism but allow
unlock_timeto have a value of 0 or 1. The relative lock would be enforced by the consensus layer by forcing a specific value for the FCMP tree rootR:unlock_timeR0Rref-101Rref-720Transactions with
unlock_time = 0enforce only the default 10-block lock, while transactions withunlock_time = 1enforce a 720-block lock, which corresponds to a lock time of 24 hours, which is sufficient for the dispute mechanism in decentralized payment channel protocols.Because the
unlock_timefield is public, I'm proposing only 2 values for the lock time to limit the impact of the resulting anonymity puddles. Time-locked transactions would be easily identifiable in the blockchain. To avoid leaking the fact that the transaction was time-locked, wallets could randomly setunlock_timeto 1 when spending an output older than the lock time with a small probability. The leakage would be limited to the fact that the age of the spent output was at least 24 hours.Hiding locks with a ring signature
@UkoeHB suggested a more private solution based on a ring signature on the root hash comitment.
The protocol will support a few possible lock times, for example:
iRi0Rref-101Rref-902Rref-7203Rref-50404Rref-403205Rref-2628006Rref-2102400Each transaction will include an additional commitment
L = j H + x Gwith j equal to one of the i values from the table above (i.e. 0 <= j <= 6). The value j = 0 corresponds to the default 10-block lock. The commitmentLis signed by the spend authorization proof.The FCMP++ proof outputs a tree root commitment
R = Rj + y GwhereRjis one of theRivalues from the table.We then define the ring
(L - i H, R - Ri)for eachi = 0..6. Note that the prover will know the opening of exactly the j-th pair from the ring. This can be proven with a standard ring signature. The approximate size of a 7-ring signature is 256 bytes, which is relatively small compared to the size of the FCMP++ proof.The purpose of this construction is for the pre-signed commitment
Lto force a relative lock without publicly revealing that the transaction was pre-signed and time-locked.Payment channel protocol sketch
Here is a sketch of a trustless payment channel protocol that required a relative lock on a pre-signed transaction. The payment channel is bidirectional between Alice and Bob. Transactions fees are ignored for clarity.
Opening the channel
The channel is opened by Alice and Bob jointly signing 4 transactions: Fund, CloseA, CloseB and Recover. The Fund transaction is immediately submitted to the network. Alice keeps CloseA and Recover as her offline transactions and Bob keeps CloseB and Recover as his offline transactions.
The channel opening process is:
Kfund.Kclose0.Kclose0to Alice's and Bob's private addresses according to the correct channel balances. The Recover transaction is time-locked relative to the preceding Close transaction.TA0with Bob.TB0with Alice.KfundtoKclose0. Alice usesR + TB0as the signature nonce. This creates an adaptor signature only Bob can complete.KfundtoKclose0. Bob usesR + TA0as the signature nonce. This creates an adaptor signature only Alice can complete.Kfund.The channel opening is complete when the Fund transaction is confirmed on the Monero network.
Channel state update
The channel transitions from state N-1 to state N (with new channel balances for Alice and Bob) as follows:
KcloseN.KcloseNto Alice's and Bob's private address according to the correct channel balances. The Recover transaction is time-locked relative to the preceding Close transaction.TANwith Bob.TBNwith Alice.KfundtoKcloseN. Alice usesR + TBNas the signature nonce. This creates an adaptor signature only Bob can complete.KfundtoKcloseN. Bob usesR + TANas the signature nonce. This creates an adaptor signature only Alice can complete.KcloseN-1to Bob's private address. Alice usesR + TAN-1as the signature nonce, but omitstAN-1from the signature response, which makes the signature invalid.KcloseN-1to Alice's private address. Bob usesR + TBN-1as the signature nonce, but omitstBN-1from the signature response, which makes the signature invalid.The new transaction graph looks like this:
Closing the channel
Collaborative close
The happy path is if Alice and Bob both agree to close the channel. In that case, they jointly sign a new Withdraw transaction, which spends from
Kfundto Alice's and Bob's private addresses according to the final channel balances.Undisputed force close
If Bob is unresponsive, Alice can force the channel to close by publishing the CloseA(N) transaction. She then has to wait for 24 hours and then publish the Recover(N) transaction, which will distribute the funds in the channel according to the final state.
Note that this is the only case when a time-locked transaction with appears on the chain.
Disputed force close
Alice can try to maliciously close the channel with an old state by publishing CloseA(i) with
i < N.When CloseA(i) appears on the chain, Bob can recover
tAifrom the valid transaction signature (using the adaptor saved in stepi+1). This allows Bob to complete the signature of the PunishA(i+1) transaction. Since the Punish transaction is not locked, Bob can front-run Alice's Recover transaction and punish Alice by taking the entire channel balance.