Generalised Replay Protection for Future Hard Forks

Generalised Replay Protection for Future Hard Forks
[not found] ` <CAAUaCyii2U5VBLS+Va+F3h4Hka0OWDnFFmjtsvyaaD4TKVzV3Q@mail.gmail.com>
  
From: Mats Jerratsch @ 2017-11-05 23:48 UTC (permalink / raw)
  To: bitcoin-dev


[-- Attachment #1.1: Type: text/plain, Size: 4633 bytes --]


Presented is a generalised way of providing replay protection for future hard forks. On top of replay protection, this schema also allows for fork-distinct addresses and potentially a way to opt-out of replay protection of any fork, where deemed necessary (can be beneficial for some L2 applications).

## Rationale

Currently when a hard fork happens, there is ad-hoc replay protection built within days with little review at best, or no replay protection at all. Often this is either resource problem, where not enough time and developers are available to sufficiently address replay protection, or the idea that not breaking compatibility is favourable. Furthermore, this is potentially a recurring problem with no generally accepted solution yet. Services that want to deal in multiple forks are expected to closely follow all projects. Since there is no standard, the solutions differ for each project, requiring custom code for every fork. By integrating replay protection into the protocol, we advocate the notion of non-hostile forks.

Users are protected against accidentally sending coins on the wrong chain through the introduction of a fork-specific incompatible address space. The coin/token type is encoded in the address itself, removing some of the importance around the question _What is Bitcoin?_. By giving someone an address, it is explicitly stated _I will only honour a payment of token X_, enforcing the idea of validating the payment under the rules chosen by the payee.

## Iterative Forks

In this schema, any hard fork is given an incremented id, `nForkId`. `nForkId` starts at `1`, with `0` being reserved as a wildcard. When project X decides to make an incompatible change to the protocol, it will get assigned a new unique `nForkId` for this fork. A similar approach like for BIP43 can be taken here. Potentially `nForkId` can be reused if a project has not gained any amount of traction.

When preparing the transaction for signing or validation, `nForkId` is appended to the final template as a 4B integer (similar to [1]). Amending BIP143, this would result in

```
 Double SHA256 of the serialization of:
     1. nVersion of the transaction (4-byte little endian)
     2. hashPrevouts (32-byte hash)
     3. hashSequence (32-byte hash)
     4. outpoint (32-byte hash + 4-byte little endian)
     5. scriptCode of the input (serialized as scripts inside CTxOuts)
     6. value of the output spent by this input (8-byte little endian)
     7. nSequence of the input (4-byte little endian)
     8. hashOutputs (32-byte hash)
     9. nLocktime of the transaction (4-byte little endian)
    10. sighash type of the signature (4-byte little endian)
    11. nForkId (4-byte little endian)
```


For `nForkId=0` this step is ommitted. This will immediately invalidate signatures for any other branch of the blockchain than this specific fork. To distinguish between `nForkId=0` and `nForkId` hardcoded into the software, another bit has to be set in the 1B SigHashId present at the end of signatures.

To make this approach more generic, payment addresses will contain the fork id, depending on which tokens a payee expects payments in. This would require a change on bech32 addresses, maybe to use a similar format used in lightning-rfc [2]. A wallet will parse the address, it will extract `nForkId`, and it displays which token the user is about to spend. When signing the transaction, it will use `nForkId`, such that the transaction is only valid for this specific token. This can be generalised in software to the point where replay protection *and* a new address space can be introduced for forks without breaking existing clients.

For light clients, this can be extended by enforcing the coinbase/block header to contain the `nForkId` of the block. Then the client can distinguish between different chains and tokens it received on each. Alternatively, a new P2P message type for sending transactions could be introduced, where prevOut and `nForkId` is transmitted, such that the lite client can check for himself, which token he received.

Allowing signatures with `nForkId=1` can be achieved with a soft fork by incrementing the script version of SegWit, making this a fully backwards compatible change.

[1]
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-February/013542.html <https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-February/013542.html>
[2]
https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md <https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md>

[-- Attachment #1.2: Type: text/html, Size: 15581 bytes --]

[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 842 bytes --]

 permalink raw reply	[|nested]
[not found] ` <CAAUaCyii2U5VBLS+Va+F3h4Hka0OWDnFFmjtsvyaaD4TKVzV3Q@mail.gmail.com>

  2017-11-08 16:45     ` Mats Jerratsch
  
From: Jacob Eliosoff @ 2017-11-06 19:21 UTC (permalink / raw)
  To: Mats Jerratsch, Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 5731 bytes --]

Thanks Mats, this proposal makes sense to me (especially the idea of
fork-specific addresses).  It prevents replay across forks, and makes it
easy for client software, and thus potentially users, to specify which fork
a tx is for.  But, like other (rougher) past proposals I've seen, it does
little to prevent users from accidentally sending on the wrong fork.

Take the specific and common case of non-upgraded wallet software.  Suppose
a HF happens, and becomes the network used by 90% of users.  Will old
wallets still default to the old nForkId (10% legacy chain)?  If so, I'd
expect a lot of accidental mis-sends on that chain.

This is just a gap in your proposal, not a flaw, but it's worth thinking
about less hazard-prone ways wallets could default nForkId.  Perhaps they
could listen to all forks, and default to the one whose last (recent) block
had the highest difficulty?  Or just check those blocks to see if multiple
forks are (nontrivially) active, and if so warn the user and force them to
confirm?  Something like that.


On Nov 6, 2017 7:05 AM, "Mats Jerratsch via bitcoin-dev" <
bitcoin-dev@lists•linuxfoundation.org> wrote:


Presented is a generalised way of providing replay protection for future
hard forks. On top of replay protection, this schema also allows for
fork-distinct addresses and potentially a way to opt-out of replay
protection of any fork, where deemed necessary (can be beneficial for some
L2 applications).

## Rationale

Currently when a hard fork happens, there is ad-hoc replay protection built
within days with little review at best, or no replay protection at all.
Often this is either resource problem, where not enough time and developers
are available to sufficiently address replay protection, or the idea that
not breaking compatibility is favourable. Furthermore, this is potentially
a recurring problem with no generally accepted solution yet. Services that
want to deal in multiple forks are expected to closely follow all projects.
Since there is no standard, the solutions differ for each project,
requiring custom code for every fork. By integrating replay protection into
the protocol, we advocate the notion of non-hostile forks.

Users are protected against accidentally sending coins on the wrong chain
through the introduction of a fork-specific incompatible address space. The
coin/token type is encoded in the address itself, removing some of the
importance around the question _What is Bitcoin?_. By giving someone an
address, it is explicitly stated _I will only honour a payment of token X_,
enforcing the idea of validating the payment under the rules chosen by the
payee.

## Iterative Forks

In this schema, any hard fork is given an incremented id, `nForkId`.
`nForkId` starts at `1`, with `0` being reserved as a wildcard. When
project X decides to make an incompatible change to the protocol, it will
get assigned a new unique `nForkId` for this fork. A similar approach like
for BIP43 can be taken here. Potentially `nForkId` can be reused if a
project has not gained any amount of traction.

When preparing the transaction for signing or validation, `nForkId` is
appended to the final template as a 4B integer (similar to [1]). Amending
BIP143, this would result in

```
Double SHA256 of the serialization of:
    1. nVersion of the transaction (4-byte little endian)
    2. hashPrevouts (32-byte hash)
    3. hashSequence (32-byte hash)
    4. outpoint (32-byte hash + 4-byte little endian)
    5. scriptCode of the input (serialized as scripts inside CTxOuts)
    6. value of the output spent by this input (8-byte little endian)
    7. nSequence of the input (4-byte little endian)
    8. hashOutputs (32-byte hash)
    9. nLocktime of the transaction (4-byte little endian)
   10. sighash type of the signature (4-byte little endian)
   11. nForkId (4-byte little endian)
```


For `nForkId=0` this step is ommitted. This will immediately invalidate
signatures for any other branch of the blockchain than this specific fork.
To distinguish between `nForkId=0` and `nForkId` hardcoded into the
software, another bit has to be set in the 1B SigHashId present at the end
of signatures.

To make this approach more generic, payment addresses will contain the fork
id, depending on which tokens a payee expects payments in. This would
require a change on bech32 addresses, maybe to use a similar format used in
lightning-rfc [2]. A wallet will parse the address, it will extract
`nForkId`, and it displays which token the user is about to spend. When
signing the transaction, it will use `nForkId`, such that the transaction
is only valid for this specific token. This can be generalised in software
to the point where replay protection *and* a new address space can be
introduced for forks without breaking existing clients.

For light clients, this can be extended by enforcing the coinbase/block
header to contain the `nForkId` of the block. Then the client can
distinguish between different chains and tokens it received on each.
Alternatively, a new P2P message type for sending transactions could be
introduced, where prevOut and `nForkId` is transmitted, such that the lite
client can check for himself, which token he received.

Allowing signatures with `nForkId=1` can be achieved with a soft fork by
incrementing the script version of SegWit, making this a fully backwards
compatible change.

[1]
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/
2017-February/013542.html

[2]
https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-
encoding.md

_______________________________________________
bitcoin-dev mailing list
bitcoin-dev@lists•linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev

[-- Attachment #2: Type: text/html, Size: 14967 bytes --]

 permalink raw reply	[|nested]
2017-11-06 19:21   ` Jacob Eliosoff

  2017-11-09 20:45       ` Jacob Eliosoff
  
From: Mats Jerratsch @ 2017-11-08 16:45 UTC (permalink / raw)
  To: Jacob Eliosoff;  Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 1530 bytes --]

Hey Jacob!

> Take the specific and common case of non-upgraded wallet software.  Suppose a HF happens, and becomes the network used by 90% of users.  Will old wallets still default to the old nForkId (10% legacy chain)?  If so, I'd expect a lot of accidental mis-sends on that chain.

With this proposal implemented, a 'mis-send' is fundamentally impossible. The address contains the identifier of the token that should be sent.

If anything, it's possible to 'mis-receive'.
That is, the receiving wallet was not aware of a newer chain, and the receiver actually wanted to receive the newer token, but instead his wallet created an address for the old token. It is the responsibility of the receiver to write a correct invoice. This is the case everywhere else in the world too, so this seems like a reasonable trade-off.

I would even argue that this should hold in a legal case, where the receiver cannot claim that he was expecting a payment in another token (contrary to how it is today, like when users send BTC to a BCH address, losing their funds with potentially no legal right for reimbursement). If I sent someone an invoice over 100€, I cannot later proclaim that I actually expected $100.

With this proposal, wallets are finally able to distinguish between different tokens. With this ability, I expect to see different implementations, some wallets which advertise staying conservative, following a strict ruleset, and other wallets being more experimental, following hashing rate or other metrics.


[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 842 bytes --]

 permalink raw reply	[|nested]
2017-11-08 16:45     ` Mats Jerratsch

  2017-11-09 21:01         ` Sjors Provoost
  
From: Jacob Eliosoff @ 2017-11-09 20:45 UTC (permalink / raw)
  To: Mats Jerratsch;  Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 2279 bytes --]

OK, I see.  On the whole this is the best replay protection solution I've
seen.  In particular, I hope developers of Bech32 and other new address
formats will take a close look at incorporating a fork ID this way.

As I understand you, a private key in cold storage would (of course) remain
valid across HFs, but an *address* would be valid only for the nForkId it
was generated for.  There may be cold-storage-type cases where it's
important for an address to be valid across all chains, ie, to
intentionally allow replay?  But I guess this could just be a special
nForkId value, say -1?


On Nov 8, 2017 9:45 AM, "Mats Jerratsch" <mats@blockchain•com> wrote:

> Hey Jacob!
>
> > Take the specific and common case of non-upgraded wallet software.
> Suppose a HF happens, and becomes the network used by 90% of users.  Will
> old wallets still default to the old nForkId (10% legacy chain)?  If so,
> I'd expect a lot of accidental mis-sends on that chain.
>
> With this proposal implemented, a 'mis-send' is fundamentally impossible.
> The address contains the identifier of the token that should be sent.
>
> If anything, it's possible to 'mis-receive'.
> That is, the receiving wallet was not aware of a newer chain, and the
> receiver actually wanted to receive the newer token, but instead his wallet
> created an address for the old token. It is the responsibility of the
> receiver to write a correct invoice. This is the case everywhere else in
> the world too, so this seems like a reasonable trade-off.
>
> I would even argue that this should hold in a legal case, where the
> receiver cannot claim that he was expecting a payment in another token
> (contrary to how it is today, like when users send BTC to a BCH address,
> losing their funds with potentially no legal right for reimbursement). If I
> sent someone an invoice over 100€, I cannot later proclaim that I actually
> expected $100.
>
> With this proposal, wallets are finally able to distinguish between
> different tokens. With this ability, I expect to see different
> implementations, some wallets which advertise staying conservative,
> following a strict ruleset, and other wallets being more experimental,
> following hashing rate or other metrics.
>
>

[-- Attachment #2: Type: text/html, Size: 2741 bytes --]

 permalink raw reply	[|nested]
2017-11-09 20:45       ` Jacob Eliosoff

       [not found]           ` <CAAUaCygeOxAK=EpzfWndx6uVvVO9B+=YTs1m-jHa3BFp82jA4w@mail.gmail.com>
  
From: Sjors Provoost @ 2017-11-09 21:01 UTC (permalink / raw)
  To: Bitcoin Protocol Discussion, Jacob Eliosoff, Mats Jerratsch

[-- Attachment #1: Type: text/plain, Size: 1827 bytes --]


> Op 9 nov. 2017, om 21:45 heeft Jacob Eliosoff via bitcoin-dev <bitcoin-dev@lists•linuxfoundation.org> het volgende geschreven:
> 
> As I understand you, a private key in cold storage would (of course) remain valid across HFs, but an address would be valid only for the nForkId it was generated for.  There may be cold-storage-type cases where it's important for an address to be valid across all chains, ie, to intentionally allow replay?  But I guess this could just be a special nForkId value, say -1?

If I understand the proposal correctly, you can always spend coins; it's the next transaction that is replay protected.

I like the idea of specifying the fork in bech32 [0]. On the other hand, the standard already has a human readable part. Perhaps the human readable part can be used as the fork id?

Note that in your currently proposal nForkId is only in the transaction signature pre-image. It's not in the serialized transaction, so a node would just have to try to see if the signature is valid. I don't know if that's a problem.

Can you clarify what you mean with:
> Allowing signatures with `nForkId=1` can be achieved with a soft fork by incrementing the script version of SegWit, making this a fully backwards compatible change.

What's the purpose of nForkId 1?

>  potentially a way to opt-out of replay protection of any fork, where deemed necessary (can be beneficial for some L2 applications).

Can you give an example of where this opt-out would be useful? Why wouldn't it be enough to just sign one transaction for each fork?

In Spoonnet, the version number is added to the SIGHASH_TYPE in the pre-image. Your solution of just adding another field seems easier, but maybe there's a downside?

Sjors

[0] https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32

[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

 permalink raw reply	[|nested]
[not found]                       ` <CAAUaCyibOEHqw1J5O8yEp8v=j8t9sovn2vn=S8bZPZCzCY-gRw@mail.gmail.com>

  2017-11-11  5:18                           ` Jacob Eliosoff
  
From: Mats Jerratsch @ 2017-11-10 11:28 UTC (permalink / raw)
  To: Jacob Eliosoff, Bitcoin Dev


[-- Attachment #1.1: Type: text/plain, Size: 3094 bytes --]

I guess I wasn't clear on the wildcard, `nForkId=0`

This proposal puts Bitcoin at `nForkId=1`, with the purpose of having `nForkId=0` valid on *all* future forks. This means you can create a `nLockTime` transaction, delete the private key and still be assured to not lose potential future tokens.

In theory `nForkId=0` could be used for an address too, the sending wallet should display a warning message about unknown side effects though. This address would be future-safe, and you can put it into a safe-deposit box (even though I see little reason to back up an _address_. You would always back up a _private key_, which translates into funds on any fork.)

Furthermore, `nForkId=0` can be used for L2 applications. Let's say Alice and Bob open a payment channel. One week later, project X decides to fork the network into a new token, implementing a custom way of providing strong two-way replay protection. The protocol Alice and Bob use for the payment channel has not implemented this new form of replay protection. Alice and Bob now have to make a choice:

(1) Ignore this new token. This comes with an evaluation of how much this new token could be worth in the future. They will continue normal channel operation, knowing that their funds on the other branch will be locked up until eternity. When they close their payment channel, the closing transaction will get rejected from the other network, because it's not following the format for replay protected transactions.

(2) Close the payment channel before the fork. The transaction, which closes the payment channel has to be mined before the fork, potentially paying a higher-than-normal fee.

With this proposal implemented, there are two additional choices

(3) Create the commitment transactions with `nForkId=0`. This ensures that when the channel gets closed, funds on other chains are released accordingly. This also means that after the fork, payments on the channel move both, the original token and the new token. Potentially, Alice and Bob want to wait before further transacting on the channel, to see if the token has substantial value. If it has, they can *then* close the channel and open a new channel again. (Note: The funding transaction can use a specific `nForkId`, preventing you from locking up multiple coins when funding the channel, but you can choose to settle with `nForkId=0` to not lock up future coins)

(4) Make the protocol aware of different `nForkId`. After the fork, the participants can chose to *only* close the payment channel on the new token, making the payment channel Bitcoin-only again. This is the preferred option, as it means no disruption to the original network.

> I like the idea of specifying the fork in bech32 [0]. On the other hand, the standard already has a human readable part. Perhaps the human readable part can be used as the fork id?

I was considering this too. On the other hand, it's only _human readable_ because thy bytes used currently encode 'bc'. For future forks, this would just be two random letters than, but potentially acceptable.


[-- Attachment #1.2: Type: text/html, Size: 4407 bytes --]

[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 842 bytes --]

 permalink raw reply	[|nested]
2017-11-10 11:28                         ` Mats Jerratsch

  2017-11-13 10:03                             ` Mats Jerratsch
  
From: Jacob Eliosoff @ 2017-11-11  5:18 UTC (permalink / raw)
  To: Mats Jerratsch;  Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 4155 bytes --]

OK, so nForkId 0 is exactly the "valid on all chains" specifier I was
asking about, cool.  And your LN example (and nLockTime txs in general)
illustrate why it's preferable to implement a generic replay protection
scheme like yours *in advance*, rather than before each fork: all ad hoc RP
schemes I know of break old txs on one of the chains, even when that's not
desirable - ie, they offer no wildcard like nForkId 0.

One comment on your LN example: users would have to take note that nForkId
0 txs would be valid not only on future forks, but on *past* forks too.
Eg, if BCH had been deployed with nForkId 2, then a user setting up BTC LN
txs now with nForkId 0 would have to be aware that those txs would be valid
for BCH too.  Of course the user could avoid this by funding from a
BTC-only address, but it is a potential minor pitfall of nForkId 0.  (Which
I don't see any clean way around.)


On Fri, Nov 10, 2017 at 6:28 AM, Mats Jerratsch <mats@blockchain•com> wrote:

> I guess I wasn't clear on the wildcard, `nForkId=0`
>
> This proposal puts Bitcoin at `nForkId=1`, with the purpose of having
> `nForkId=0` valid on *all* future forks. This means you can create a
> `nLockTime` transaction, delete the private key and still be assured to not
> lose potential future tokens.
>
> In theory `nForkId=0` could be used for an address too, the sending wallet
> should display a warning message about unknown side effects though. This
> address would be future-safe, and you can put it into a safe-deposit box
> (even though I see little reason to back up an _address_. You would always
> back up a _private key_, which translates into funds on any fork.)
>
> Furthermore, `nForkId=0` can be used for L2 applications. Let's say Alice
> and Bob open a payment channel. One week later, project X decides to fork
> the network into a new token, implementing a custom way of providing strong
> two-way replay protection. The protocol Alice and Bob use for the payment
> channel has not implemented this new form of replay protection. Alice and
> Bob now have to make a choice:
>
> (1) Ignore this new token. This comes with an evaluation of how much this
> new token could be worth in the future. They will continue normal channel
> operation, knowing that their funds on the other branch will be locked up
> until eternity. When they close their payment channel, the closing
> transaction will get rejected from the other network, because it's not
> following the format for replay protected transactions.
>
> (2) Close the payment channel before the fork. The transaction, which
> closes the payment channel has to be mined before the fork, potentially
> paying a higher-than-normal fee.
>
> With this proposal implemented, there are two additional choices
>
> (3) Create the commitment transactions with `nForkId=0`. This ensures that
> when the channel gets closed, funds on other chains are released
> accordingly. This also means that after the fork, payments on the channel
> move both, the original token and the new token. Potentially, Alice and Bob
> want to wait before further transacting on the channel, to see if the token
> has substantial value. If it has, they can *then* close the channel and
> open a new channel again. (Note: The funding transaction can use a specific
> `nForkId`, preventing you from locking up multiple coins when funding the
> channel, but you can choose to settle with `nForkId=0` to not lock up
> future coins)
>
> (4) Make the protocol aware of different `nForkId`. After the fork, the
> participants can chose to *only* close the payment channel on the new
> token, making the payment channel Bitcoin-only again. This is the preferred
> option, as it means no disruption to the original network.
>
> > I like the idea of specifying the fork in bech32 [0]. On the other hand,
> the standard already has a human readable part. Perhaps the human readable
> part can be used as the fork id?
>
> I was considering this too. On the other hand, it's only _human readable_
> because thy bytes used currently encode 'bc'. For future forks, this would
> just be two random letters than, but potentially acceptable.
>
>

[-- Attachment #2: Type: text/html, Size: 5282 bytes --]

 permalink raw reply	[|nested]
2017-11-11  5:18                           ` Jacob Eliosoff

  2017-11-13 15:31                               ` Jacob Eliosoff
  
From: Mats Jerratsch @ 2017-11-13 10:03 UTC (permalink / raw)
  To: Jacob Eliosoff;  Bitcoin Dev


[-- Attachment #1.1: Type: text/plain, Size: 1920 bytes --]


> OK, so nForkId 0 is exactly the "valid on all chains" specifier I was asking about, cool.  And your LN example (and nLockTime txs in general) illustrate why it's preferable to implement a generic replay protection scheme like yours in advance, rather than before each fork: all ad hoc RP schemes I know of break old txs on one of the chains, even when that's not desirable - ie, they offer no wildcard like nForkId 0.

Exactly!

> One comment on your LN example: users would have to take note that nForkId 0 txs would be valid not only on future forks, but on past forks too.  Eg, if BCH had been deployed with nForkId 2, then a user setting up BTC LN txs now with nForkId 0 would have to be aware that those txs would be valid for BCH too.  Of course the user could avoid this by funding from a BTC-only address, but it is a potential minor pitfall of nForkId 0.  (Which I don't see any clean way around.)

This is actually incorrect. There are two transactions involved in LN. The funding transaction, which opens a payment channel, and a commitment transaction, which closes the channel when broadcasted to the network (the cooperative closing transaction can be considered a commitment transaction in a loose sense).

Now you want to protect the funding transaction, as otherwise you would be subject to a replay-attack on all *past* forks. So you will set `nForkId>=1` for the funding transaction, making this payment channel non-existent on any *past* forks. However, if during the lifetime of the payment channel another fork happens, the payment channel exists for both tokens. So for the commitment transaction, you will have `nForkId=0`, making it valid on both of these chains. While this `nForkId` is valid on all chains, the parent transaction it tries to spend (the funding transaction) does only exist on two chains, the original one you created the channel for and the one that forked away.

[-- Attachment #1.2: Type: text/html, Size: 2559 bytes --]

[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 842 bytes --]

 permalink raw reply	[|nested]
2017-11-13 10:03                             ` Mats Jerratsch

  2017-11-13 17:02                                 ` Spartacus Rex
  
From: Jacob Eliosoff @ 2017-11-13 15:31 UTC (permalink / raw)
  To: Mats Jerratsch;  Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 3756 bytes --]

>
> This is actually incorrect. There are two transactions involved in LN. The
> funding transaction, which opens a payment channel, and a commitment
> transaction, which closes the channel when broadcasted to the network (the
> cooperative closing transaction can be considered a commitment transaction
> in a loose sense).
>
> Now you want to protect the funding transaction, as otherwise you would be
> subject to a replay-attack on all *past* forks. So you will set
> `nForkId>=1` for the funding transaction, making this payment channel
> non-existent on any *past* forks. However, if during the lifetime of the
> payment channel another fork happens, the payment channel exists for both
> tokens. So for the commitment transaction, you will have `nForkId=0`,
> making it valid on both of these chains. While this `nForkId` is valid on
> all chains, the parent transaction it tries to spend (the funding
> transaction) does only exist on two chains, the original one you created
> the channel for and the one that forked away.
>

Thanks for the clarification.  How would a tx specify a constraint like
"nForkId>=1"?  I was thinking of it just as a number set on the tx.

Also note that since forks form a partial order, but IDs (numbers) form a
total order, ">=" will miss some cases.  Eg, suppose BCH had forked with
nForkId 2, and then you set up a LN funding tx on BCH with nForkId>=2, and
then Segwit2x forked (from BTC!) with nForkId 3.  The BCH funding tx would
be valid on Segwit2x.  This is more of a fundamental problem than a bug -
to avoid it you'd have to get into stuff like making each fork reference
its parent-fork's first block or something, someone has written about
this...


On Mon, Nov 13, 2017 at 5:03 AM, Mats Jerratsch <mats@blockchain•com> wrote:

>
> OK, so nForkId 0 is exactly the "valid on all chains" specifier I was
> asking about, cool.  And your LN example (and nLockTime txs in general)
> illustrate why it's preferable to implement a generic replay protection
> scheme like yours *in advance*, rather than before each fork: all ad hoc
> RP schemes I know of break old txs on one of the chains, even when that's
> not desirable - ie, they offer no wildcard like nForkId 0.
>
>
> Exactly!
>
> One comment on your LN example: users would have to take note that nForkId
> 0 txs would be valid not only on future forks, but on *past* forks too.
> Eg, if BCH had been deployed with nForkId 2, then a user setting up BTC LN
> txs now with nForkId 0 would have to be aware that those txs would be valid
> for BCH too.  Of course the user could avoid this by funding from a
> BTC-only address, but it is a potential minor pitfall of nForkId 0.  (Which
> I don't see any clean way around.)
>
>
> This is actually incorrect. There are two transactions involved in LN. The
> funding transaction, which opens a payment channel, and a commitment
> transaction, which closes the channel when broadcasted to the network (the
> cooperative closing transaction can be considered a commitment transaction
> in a loose sense).
>
> Now you want to protect the funding transaction, as otherwise you would be
> subject to a replay-attack on all *past* forks. So you will set
> `nForkId>=1` for the funding transaction, making this payment channel
> non-existent on any *past* forks. However, if during the lifetime of the
> payment channel another fork happens, the payment channel exists for both
> tokens. So for the commitment transaction, you will have `nForkId=0`,
> making it valid on both of these chains. While this `nForkId` is valid on
> all chains, the parent transaction it tries to spend (the funding
> transaction) does only exist on two chains, the original one you created
> the channel for and the one that forked away.
>

[-- Attachment #2: Type: text/html, Size: 4678 bytes --]

 permalink raw reply	[|nested]
2017-11-13 15:31                               ` Jacob Eliosoff

  2017-11-14 13:49                                   ` Mats Jerratsch
  
From: Spartacus Rex @ 2017-11-13 17:02 UTC (permalink / raw)
  To: Jacob Eliosoff, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 4712 bytes --]

Totally agree something like this required..

I've been burned.

But I like the 'old' idea of putting the hash of a block that MUST be on
the chain that this txn can eventually be added to. If the hash is not a
valid block on the chain, the txn can't be added.

It means you can choose exactly which forks you want to allow your txn on,
pre-fork for both, post-fork for only one, and gets round the issue of who
gets to decide the nForkid value.. since you don't need one. Also, all the
old outputs work fine, and LN not an issue.

I'm missing why this scheme would be better ?


On Nov 13, 2017 15:35, "Jacob Eliosoff via bitcoin-dev" <
bitcoin-dev@lists•linuxfoundation.org> wrote:

> This is actually incorrect. There are two transactions involved in LN. The
>> funding transaction, which opens a payment channel, and a commitment
>> transaction, which closes the channel when broadcasted to the network (the
>> cooperative closing transaction can be considered a commitment transaction
>> in a loose sense).
>>
>> Now you want to protect the funding transaction, as otherwise you would
>> be subject to a replay-attack on all *past* forks. So you will set
>> `nForkId>=1` for the funding transaction, making this payment channel
>> non-existent on any *past* forks. However, if during the lifetime of the
>> payment channel another fork happens, the payment channel exists for both
>> tokens. So for the commitment transaction, you will have `nForkId=0`,
>> making it valid on both of these chains. While this `nForkId` is valid on
>> all chains, the parent transaction it tries to spend (the funding
>> transaction) does only exist on two chains, the original one you created
>> the channel for and the one that forked away.
>>
>
> Thanks for the clarification.  How would a tx specify a constraint like
> "nForkId>=1"?  I was thinking of it just as a number set on the tx.
>
> Also note that since forks form a partial order, but IDs (numbers) form a
> total order, ">=" will miss some cases.  Eg, suppose BCH had forked with
> nForkId 2, and then you set up a LN funding tx on BCH with nForkId>=2, and
> then Segwit2x forked (from BTC!) with nForkId 3.  The BCH funding tx would
> be valid on Segwit2x.  This is more of a fundamental problem than a bug -
> to avoid it you'd have to get into stuff like making each fork reference
> its parent-fork's first block or something, someone has written about
> this...
>
>
> On Mon, Nov 13, 2017 at 5:03 AM, Mats Jerratsch <mats@blockchain•com>
> wrote:
>
>>
>> OK, so nForkId 0 is exactly the "valid on all chains" specifier I was
>> asking about, cool.  And your LN example (and nLockTime txs in general)
>> illustrate why it's preferable to implement a generic replay protection
>> scheme like yours *in advance*, rather than before each fork: all ad hoc
>> RP schemes I know of break old txs on one of the chains, even when that's
>> not desirable - ie, they offer no wildcard like nForkId 0.
>>
>>
>> Exactly!
>>
>> One comment on your LN example: users would have to take note that
>> nForkId 0 txs would be valid not only on future forks, but on *past*
>> forks too.  Eg, if BCH had been deployed with nForkId 2, then a user
>> setting up BTC LN txs now with nForkId 0 would have to be aware that those
>> txs would be valid for BCH too.  Of course the user could avoid this by
>> funding from a BTC-only address, but it is a potential minor pitfall of
>> nForkId 0.  (Which I don't see any clean way around.)
>>
>>
>> This is actually incorrect. There are two transactions involved in LN.
>> The funding transaction, which opens a payment channel, and a commitment
>> transaction, which closes the channel when broadcasted to the network (the
>> cooperative closing transaction can be considered a commitment transaction
>> in a loose sense).
>>
>> Now you want to protect the funding transaction, as otherwise you would
>> be subject to a replay-attack on all *past* forks. So you will set
>> `nForkId>=1` for the funding transaction, making this payment channel
>> non-existent on any *past* forks. However, if during the lifetime of the
>> payment channel another fork happens, the payment channel exists for both
>> tokens. So for the commitment transaction, you will have `nForkId=0`,
>> making it valid on both of these chains. While this `nForkId` is valid on
>> all chains, the parent transaction it tries to spend (the funding
>> transaction) does only exist on two chains, the original one you created
>> the channel for and the one that forked away.
>>
>
>
> _______________________________________________
> bitcoin-dev mailing list
> bitcoin-dev@lists•linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>
>

[-- Attachment #2: Type: text/html, Size: 6357 bytes --]

 permalink raw reply	[|nested]
2017-11-13 17:02                                 ` Spartacus Rex

  2017-11-15  5:02                                     ` Jacob Eliosoff
  
From: Mats Jerratsch @ 2017-11-14 13:49 UTC (permalink / raw)
  To: Spartacus Rex;  Bitcoin Protocol Discussion


[-- Attachment #1.1: Type: text/plain, Size: 2937 bytes --]


> But I like the 'old' idea of putting the hash of a block that MUST be on the chain that this txn can eventually be added to. If the hash is not a valid block on the chain, the txn can't be added.
> 
> It means you can choose exactly which forks you want to allow your txn on, pre-fork for both, post-fork for only one, and gets round the issue of who gets to decide the nForkid value.. since you don't need one. Also, all the old outputs work fine, and LN not an issue.
> 
> I'm missing why this scheme would be better ?

I do agree that solutions like `SIGHASH_BLOCKCOMMIT` are superior in the sense that they are very difficult to circumvent. However, a fork could also follow the original chain in SPV mode and allow transactions protected with these mechanism. Since it's fundamentally impossible to disallow transactions in future projects, the goal shouldn't be to make this overly complicated.

Furthermore, this schema is not just adding replay protection. It makes transacting safer overall (due to a dedicated address format per fork) and allows light clients to differentiate between multiple forks. In the past three months, at least $600k has been lost by users sending BCH to a BTC address [1].

> Thanks for the clarification.  How would a tx specify a constraint like "nForkId>=1"?  I was thinking of it just as a number set on the tx.

Whether the transaction is replay protected or not is specified by setting a bit in the `SigHashId`. If this bit is set, then the signature *preimage* MUST have `nForkId` appended. `nForkId` is not part of the final transaction, someone who wants to verify the transaction must know which `nForkId` it was created with.

If the bit isn't set, it means `nForkId=0`, which allows other forks to validate the signature.

> Also note that since forks form a partial order, but IDs (numbers) form a total order, ">=" will miss some cases.  Eg, suppose BCH had forked with nForkId 2, and then you set up a LN funding tx on BCH with nForkId>=2, and then Segwit2x forked (from BTC!) with nForkId 3.  The BCH funding tx would be valid on Segwit2x.  This is more of a fundamental problem than a bug - to avoid it you'd have to get into stuff like making each fork reference its parent-fork's first block or something, someone has written about this...

Sorry, I was careless with the use of `>=` there. You are correct, forks form a tree. For this proposal, every leaf must be assigned a unique `nForkId`. The relationship between `nForkId` is irrelevant (e.g. which number is bigger), as long as they are unique. Transactions are only valid IFF `nForkId` matches exactly the `nForkId` of the software validating it. As described above, the transaction doesn't even contain `nForkId`, and the node surely is not starting to guess which one it could be.

[1]
https://twitter.com/khannib/status/930223617744437253 <https://twitter.com/khannib/status/930223617744437253>

[-- Attachment #1.2: Type: text/html, Size: 4309 bytes --]

[-- Attachment #2: Message signed with OpenPGP --]
[-- Type: application/pgp-signature, Size: 842 bytes --]

 permalink raw reply	[|nested]
2017-11-14 13:49                                   ` Mats Jerratsch

  
From: Jacob Eliosoff @ 2017-11-15  5:02 UTC ( / )
  To: Mats Jerratsch;  Bitcoin Protocol Discussion



>
> Sorry, I was careless with the use of `>=` there. You are correct, forks
> form a tree. For this proposal, every leaf must be assigned a unique
> `nForkId`. The relationship between `nForkId` is irrelevant (e.g. which
> number is bigger), as long as they are unique. Transactions are only valid
> IFF `nForkId` matches exactly the `nForkId` of the software validating it.
> As described above, the transaction doesn't even contain `nForkId`, and the
> node surely is not starting to guess which one it could be.
>

OK, but then it seems to me you have a dilemma for, eg, your LN commitment
tx.  You either give it the specific nForkId of the fork it's created on -
making it invalid on *all* other forks (eg, any future "non-contentious
upgrade" HF that replaces that fork).  Or you give it nForkId 0 - which has
the "BCH tx valid on Segwit2x (& vice versa)" flaw.

It may make sense to revise your proposal to incorporate Luke's
OP_CHECKBLOCKATHEIGHT
<https://github.com/bitcoin/bips/blob/master/bip-0115.mediawiki>, and make
the fork ID a (block height, hash) pair rather than just a number.  But I
still think the idea of fork-specific addresses is a keeper!


On Tue, Nov 14, 2017 at 8:49 AM, Mats Jerratsch <mats@blockchain•com> wrote:

>
> But I like the 'old' idea of putting the hash of a block that MUST be on
> the chain that this txn can eventually be added to. If the hash is not a
> valid block on the chain, the txn can't be added.
>
> It means you can choose exactly which forks you want to allow your txn on,
> pre-fork for both, post-fork for only one, and gets round the issue of who
> gets to decide the nForkid value.. since you don't need one. Also, all the
> old outputs work fine, and LN not an issue.
>
> I'm missing why this scheme would be better ?
>
>
> I do agree that solutions like `SIGHASH_BLOCKCOMMIT` are superior in the
> sense that they are very difficult to circumvent. However, a fork could
> also follow the original chain in SPV mode and allow transactions protected
> with these mechanism. Since it's fundamentally impossible to disallow
> transactions in future projects, the goal shouldn't be to make this overly
> complicated.
>
> Furthermore, this schema is not just adding replay protection. It makes
> transacting safer overall (due to a dedicated address format per fork) and
> allows light clients to differentiate between multiple forks. In the past
> three months, at least $600k has been lost by users sending BCH to a BTC
> address [1].
>
> Thanks for the clarification.  How would a tx specify a constraint like
>> "nForkId>=1"?  I was thinking of it just as a number set on the tx.
>>
>
> Whether the transaction is replay protected or not is specified by setting
> a bit in the `SigHashId`. If this bit is set, then the signature *preimage*
> MUST have `nForkId` appended. `nForkId` is not part of the final
> transaction, someone who wants to verify the transaction must know which
> `nForkId` it was created with.
>
> If the bit isn't set, it means `nForkId=0`, which allows other forks to
> validate the signature.
>
> Also note that since forks form a partial order, but IDs (numbers) form a
>> total order, ">=" will miss some cases.  Eg, suppose BCH had forked with
>> nForkId 2, and then you set up a LN funding tx on BCH with nForkId>=2, and
>> then Segwit2x forked (from BTC!) with nForkId 3.  The BCH funding tx would
>> be valid on Segwit2x.  This is more of a fundamental problem than a bug -
>> to avoid it you'd have to get into stuff like making each fork reference
>> its parent-fork's first block or something, someone has written about
>> this...
>>
>
> Sorry, I was careless with the use of `>=` there. You are correct, forks
> form a tree. For this proposal, every leaf must be assigned a unique
> `nForkId`. The relationship between `nForkId` is irrelevant (e.g. which
> number is bigger), as long as they are unique. Transactions are only valid
> IFF `nForkId` matches exactly the `nForkId` of the software validating it.
> As described above, the transaction doesn't even contain `nForkId`, and the
> node surely is not starting to guess which one it could be.
>
> [1]
> https://twitter.com/khannib/status/930223617744437253
>