[Lightning-dev] [DRAFT] BOLT 13(?): WatchTower protocol

Sergi Delgado Segura sergi.delgado.s at gmail.com
Thu Dec 5 16:46:23 UTC 2019


Hi Rusty, thanks for the feedback!

On Tue, Dec 3, 2019 at 1:26 AM Rusty Russell <rusty at rustcorp.com.au> wrote:

> Hi Sergi!
>
>         Great to see progress on this!  Detailed comments below.
>
> Sergi Delgado Segura <sergi.delgado.s at gmail.com> writes:
> > ## Sending and receiving appointments
> >
> > Once the client is aware of the services provided by the server, the
> former
> > can start sending appointments to the latter.
> >
> > +-------+                                    +-------+
> > |   A   |--(1)---      appointment      ---->|   B   |
> > |       |<-(2)---   accepted/rejected   -----|       |
> > +-------+                                    +-------+
> >
> > - where node A is 'client' and node B is 'server'
> >
> > ### The `appointment` message
> >
> > This message contains all the information regarding the appointment that
> > the client wants to arrange with the server.
> >
> > 1. type: ? (`appointment`)
> > 2. data:
> >    * [`16*byte`:`locator`]
> >    * [`u64 `:`start_block`]
> >    * [`u64 `:`end_block`]
>
> u32 is sufficient for start_block, fwiw.
>

OK


>
> >    * [`u16`: `encrypted_blob_len`
> >    * [`encrypted_blob_len*byte`:`encrypted_blob`]
> >    * [`u16`:`cipher`]
> >    * [`u16`: `auth_token_len`]
> >    * [`auth_token_len*byte`: `auth_token`]
> >    * [`u16`: `qos_len`]
> >    * [`qos_len*byte`: `qos_data`]
> >
> > #### Requirements
> >
> > The client:
> >
> > * MUST set `locator` as specified in [Transaction Locator and Encryption
> > Key](#transaction-locator-and-encryption-key).
> > * MUST set `start_block` to the current chain tip height.
>
> What is the purpose of this field?  Presumably the watcher knows the
> current block number.
>
> I suggest you want this to be "the first possible block in which the
> transction can occur"?
>

Indeed


>
> > * MUST set `end_block` to the block height at which he requests the
> server
> > to stop watching for breaches.
>
> I don't know how you would set that.  I think you need this to be
> separate: either as part of the payment layer (I will watch for a year),
> or an explicit "stop watching" message (assuming that you pay for
> certain capacity).
>

I think it makes sense to do it in the payment layer. A "stop watching"
command seems tricky though, how would you price it properly? It seems that
the user could be able to game it, specially if the "bounty" approach.


>
> > * MUST set `encrypted_blob` to the encryption of the
> `justice_transaction`
> > as specified in [Transaction Locator and Encryption
> > Key](#transaction-locator-and-encryption-key).
>
> The phrase used in the spec is penalty transaction, BTW.  Especially
> since its use is generally an injustice :)
>

Sorry about that >.<


>
> > * MUST set `cipher` to the cipher used to create the `encrypted_blob`.
>
> Don't do this.  Pick a cipher already used elsewhere in the protocol.
> chacha20 is a good choice.
>

Ok, We were thinking on having a certain level of configuration to avoid
broken things in the future. It makes the protocol
unnecessarily complicated though, so we'll change it to chacha20 only.


>
> > * MAY send an empty `auth_token` field.
> > * MUST set `auth_token_len` to the length of `auth_token`.
>
> The latter is already written in the spec.  But these days, optional
> fields are implemented as TLV data, so perhaps use that instead.


> > * MAY send an empty `qos_data` field.
> > * if `qos_data` is not empty:
> > *  MUST set `qos_data` according to [Quality of Service
> > data](#quality-of-service-data).
> > * MUST set `qos_len` equal to the length of `qos_data`.
>
> I would drop the idea of "qos", and again use TLV data, defining
> accountability.  Others can be added later.
>

OK, I'll give it a look.


>
> > The server:
> >
> > * MUST reject the appointment if:
> > * Authentication is required and `auth_token` is not provided.
> > * Authentication is required and `auth_token` is invalid.
>
> > * `locator` is not a `16-byte` value.
>
> This cannot happen, so eliminate it.
>

Noted


>
> > * `start_block` is further than one block behind the current chain tip.
> > * `start_block` is further than one block ahead the current chain tip.
>
> I'm still not sure what start_block is for.  It just seems to make
> things fragile if blocks arrive quickly.
>

The rationale is to prevent users to ask the tower to look for passed
events.


>
> > * `encrypted_blob` has unreasonable size.
> > * `cipher` is not among the ones he implements.
> >
> > * SHOULD reject the appointment if`end_block` is too far away in the
> future.
>
> This is a problem, since in practice we'll have to define what that is.
>

Agreed. As mentioned before it seems reasonable to do it during hiring.
Open to other ideas too though.


>
> > * MUST:
> > * truncate the remainder of the package to `qos_len`.
> > * process `qos_data` according to [Quality of Service
> > data](#quality-of-service-data) if `qos_len` is not 0.
> >
> > * MAY accept the appointment otherwise.
>
> I would suggest:
>
>         * If it accepts the appointment:
>            * MUST send `accepted`
>         * Otherwise:
>            * MUST send `rejected`
>

OK


>
> > #### Rationale
> >
> > We define appointment as the way that the WatchTower is hired / requested
> > by a client to do it's watching services.
> >
> > WatchTowers may offer their services for free (`altruistic`) or they may
> > require a payment when accepting the job (`non-altruistic`). We have
> > defined `auth_token` as an authentication mechanism between the client
> and
> > server, so the client can prove they are entitled to the service. The
> > tokens are not required to be linked to any kind of identity (e.g.
> blinded
> > tokens), but their sole purpose is to confirm the client has already paid
> > for the service.
> >
> > The transaction `locator` can be deterministically computed by both the
> > client and the server. Locators of wrong size are therefore invalid.
> >
> > `start_block` can be either one block ahead or behind the tower tip due
> to
> > network delays. A tower must not accept appointments arbitrarily ahead or
> > behind the current tip since it could increase DoS vectors. A
> `start_block`
> > long behind would force the tower to rescan block data for those
> > appointments instead of watching block by block. On the other hand, a
> > `start_time` long ahead would imply storing information way before it
> being
> > needed.
>
> I think the watchtower should respond with the start_time, instead, so
> the caller knows how much work they have to do.  And perhaps the
> end_time, but to be honest I'd add that later.
>

This looks like a good solution for the start time. That way the user knows
what the tower considers "current time". There could also be a default end
time that is picked if the tower and the user haven't agreed on something
else during the payment (or whatever we end up choosing).


> `minimum_viable_transaction_size` and `maximum_viable_transaction_size`
> > refer to the minimum/maximum size required to create a valid transaction.
> > Accepting `encrypted_blob` outside those boundaries will increase DoS
> > attacks on the server.
>
> These values need to be defined.  If the user is paying for storage, the
> watchtower doesn't care.
>

Agreed, it depends on the mode of operation from the tower though. For
example, a bounty approach should care about this since some sizes may be
impossible for a transaction to be relayed. On the other hand, if the user
is paying for a certain amount of storage beforehand the tower may not care.


> ### The `appointment_accepted` message
> >
> > This message contains information about the acceptance of an appointment
> by
> > the WatchTower.
> >
> > 1. type: ? (`appointment_accepted `)
> > 2. data:
> >    * [`16*byte `:`locator`]
> >    * [`u16`: `qos_len`]
> > * [`qos_len*byte`: `qos_data`]
> >
> > The server:
> >
> > * MUST receive `appointment` before sending an `appointment_accepted`
> > message.
> > * MUST set the `locator` to match the one received in `appointment`.
> > * if `qos_data` was requested in `appointment`:
> > *  MUST set `qos_data` according to [Quality of Service
> > data](#quality-of-service-data).
> > * MUST set `qos_len` equal to the length of `qos_data`.
> >
> > The client:
> >
> > * MUST fail the connection  if `locator` does not match any of locators
> the
> > previously sent to the server:
> >
> > * if `qos` was requested in `appointment`:
> > * MUST fail the connection if `qos_len` is 0.
> > * MUST process `qos_data` according to [Quality of Service
> > data](#quality-of-service-data).
> >
> > ### The `appointment_rejected` message
> >
> > This message contains information about the rejection of an appointment
> by
> > the WatchTower.
> >
> > 1. type: ? (`appointment_rejected `)
> > 2. data:
> >    * [`16*byte `:`locator`]
> >    * [`u16`: `rcode`]
> >    * [`u16`: `reason_len`
> >    * [`reason_len*byte`: `reason`]
> >
> > The server:
> >
> > * MUST receive `appointment` before sending an `appointment_rejected`
> > message.
> > * MUST set the `locator` to match the one received in `appointment`.
> > * MUST set `rcode` to the rejection code.
> > * MAY set and empty `reason` field.
> > * MUST set `reason_len` to length of `reason`.
>
> `rcode` needs to be defined.  In practice, that's a very difficult
> task.  You may just want to define transient vs permanently errors?
>

Can you elaborate on this? I'm not used to error definition at this level
so I could use some help.


>
> > #### Rationale
> >
> > The `appointment_rejected` message follows the approach taken by the
> > `error` message defined in [BOLT#1](
> >
> https://github.com/lightningnetwork/lightning-rfc/blob/master/01-messaging.md#the-error-message
> ):
> > error codes are mandatory, whereas reasons are optional and
> implementation
> > dependant.
> >
> > ## Quality of Service data
> >
> > `qos_data` is a list where each field specifies they type and associated
> > data of the offered/requested `qos`. The format is defined as follows:
> >
> > * [`u16`: `qos_type`]
> > * [`u16`: `data_len`]
> > * [`data_len*byte`: `data`]
> >
> > So far, only `accountability` is defined.
> >
> > ### `accountability`
> >
> > The accountability `qos` defines a pair `qos_data` blobs, associated to a
> > pair of messages: The first one is `customer_evidence` and it is provided
> > by the `client` in the `appointment` message. The second one is
> > `tower_evidence`, and is provided by the WatchTower in the
> > `appointment_accepted` message.
> >
> > #### `customer_evidence`
> >
> > The format for the `customer_evidence` is defined as follows:
> >
> > 1. type: ? (`customer_evidence`)
> > 2. data:
> > * [`u64 `:`dispute_delta`]
> > * [`u64`: `transaction_size`]
> > * [`u64`: `transaction_fee`]
> >
> > If `accountability` is being requested, the client:
> >
> > * MUST set `dispute_delta` to the CLTV value specified in the
> > `commitment_transaction`.
>
> I don't understand this field.  I assume you're talking about the
> `to_self_delay` the customer requested of its peer.  But I don't
> understand why the tower would care: isn't it just going to broadcast
> the tx as soon as it sees the violation?
>

Yes it is, but depending on the approach followed it may be useful. For
instance, if the tower has a dedicated output to top up the fee it may be
worth knowing beforehand what's the to_self_delay so the tower can have a
better strategy (not overpaying fees). The tower will learn that once
decrypted, so it may not be necessary at this level.


> * MUST set `transaction_size` to the size of the serialized
> > `justice_transaction`, in bytes.
> > * MUST set `transaction_fee` to the fee set in the `justice_transaction`,
> > in satoshis.
>
> Just use chacha20, then the size of the transaction is well-defined.
>
> But I don't understand why the fee matters.  Neither the watchtower nor
> the customer knows what fee will be needed in the future: all the
> watchtower can do is broadcast what it's given.  Unless there's some
> agreement on an output for the watchtower to CPFP (which, IMHO is best
> left to a future extension), but the fee here still doesn't really
> matter.
>

Noted, I'll think about how to fit it in an extension.


> > * MUST set the `customer_signature_algorithm` to one of the signature
> > algorithms supported by the tower.
> > * MUST set `customer_signature` to the signature of the appointment using
> > `op_customer_signature_algorithm`.
> > * MUST set `customer_public_key` to the public key that matches the
> private
> > key used to create `op_customer_signature`.
>
> These aren't defined above (and again, don't define multiple signature
> algorithms.
>

Noted. I'll simplify it to use chacha20 throughout the BOLT.


> You should define here exactly what fields are signed, since this
> message is inside the `appointment`.  But it's not clear what purpose
> this signature serves?
>

The signature is to provide an explicit agreement between the user and the
tower (part of the accountability extension). The client side may not be
necessary (as Antoine was mentioning) since the user is already providing a
signed transaction, so that could do. I'll review that.


> > If `accountability` is being offered, the server:
> >
> > * MUST compute the `customer_signature` verification using
> > `customer_public_key`.
> > * SHOULD compute the `fee_rate` set in the `justice_tx` using
> > `transaction_size` and `transaction_fee`.
> >
> > * MUST reject the appointment if:
> > * Any of the fields is missing.
> > * `transaction_size` is unreasonable.
> > * `customer_signature_algorithm` does not match any of the supported
> > signing algorithms.
> > * `customer_signature` cannot be verified using `customer_public_key`.
> >
> > * SHOULD reject the appointment if:
> > * `dispute_delta` is too small.
> > * `fee_rate` is too low.
> >
> > If `accountability` is NOT being offered:
> >
> > * The server MUST reject the appointment.
> >
> > Otherwise:
> >
> > * The server SHOULD accept the appointment.
> >
> > #### Rationale
> >
> > The concept of too small for `dispute_delta` is subjective. The
> > `dispute_delta` defines the time (in blocks) that the tower has in order
> to
> > respond after a breach is seen. The smaller the value, the more the
> server
> > risks to fail the appointment.
> >
> > `transaction_size` and `transaction_fee` help the WatchTower to decide on
> > the likelihood of an appointment being fulfilled. Appointments with
> > `fee_rate` too low may be rejected by the WatchTower. While a customer
> can
> > always fake this values, it should break ToS between the client and the
> > server and, therefore, release the WatchTower of any liability.
> >
> > By accepting the request, the tower is offering a reputationally
> > accountable watching service. If `accountability` is not offered, then
> the
> > tower will not accept appointments requesting for it.
> >
> > As well, the WatchTower must check the transaction details before
> deciding
> > whether it will accept it. If the decrypted justice transaction does not
> > satisfy the job details (e.g. too low fee), then the tower is not obliged
> > to fulfil the appointment.
> >
> > #### `tower_evidence`
> >
> > The format for the `tower_evidence` is defined as follows:
> >
> > 1. type: ? (`tower_evidence`)
> > 2. data:
> > * [`u16 `:`receipt_len`]
> > * [`receipt_len*byte `: `receipt`]
> > * [`u16`: `wt_signature_algorithm`]
> > * [`u16`: `wt_signature_len`
> > * [`wt_signature_len*byte`: `wt_signature`]
> > * [`u16`: `wt_public_key_len`]
> > * [`wt_public_key_len*byte`: `wt_public_key`]
> >
> > The server:
> >
> > * MUST set `receipt` to a receipt built according to
> > [Receipt-Format](#receipt-format).
> > * MUST set `wt_signature_algorithm` to one of the signature algorithms he
> > has announced.
> > * MUST set `wt_signature` to the signature of the appointment using
> > `wt_signature_algorithm`.
> > * MUST set `wt_public_key` to the public key that matches the private key
> > used to create `wt_signature`.
>
> What good is a proof if the watchtower can set a random public key?
>
> Presumably you want the watchtower to sign the txlocator, start_block
> and encrypted blob with a known key.  Then you have a proof that it has
> accepted it.
>

True, but the tower could have long-lasting and well known public keys. If
that's the case the key won't be required there.


>
> > The client:
> >
> > * MUST compute the `wt_signature` verification using `wt_public_key`.
> >
> > * MUST fail the connection if:
> > * Any of the fields is missing.
> > * `receipt` does not matches the format specified at
> > [Receipt-Format](#receipt-format)
> > * `receipt` fields do not match the ones sent in the `appointment`
> message.
> > * `wt_signature_algorithm` does not match any of the ones offered by the
> > server.
> > * `wt_signature` cannot be verified using `wt_public_key`.
> >
> > #### Receipt Format
> >
> > The server MUST create the receipt containing the following information:
> >
> > txlocator
> > start_block
> > end_block
> > dispute_delta
> > encrypted_blob
> > transaction_size
> > transaction_fee
> > cipher
> > customer_signature
> > wt_public_key
> >
> >
> > #### Rationale
> >
> > We assume the client has a well-known public key for the WatchTower.
> >
> > The receipt contains, mainly, the information provided by the user. The
> > WatchTower will need to sign the receipt to provide evidence of
> agreement.
> >
> > The `customer_signature` is included in the receipt to link both the
> client
> > request and the server response. Otherwise, the tower could sign a
> receipt
> > with different data that the one sent by the user, and the user would
> have
> > no way to prove whether that's true or not. By signing the customer
> > signature there the tower creates evidence of what the user sent, since
> the
> > tower cannot forge the client's signature.
> >
> > #### Receipt serialization and signature
> >
> > [FIXME: TBD]
> >
> > ## Transaction Locator and Encryption Key
> >
> > Implementations MUST compute the `locator`, `encryption_key` and
> > `encryption_iv` from the commitment transaction as defined below:
> >
> > - `locator`: first half of the commitment transaction id
> > (`commitment_txid(0,16]`)
> > - `master_key`: Hash of the second half of the commitment transaction id
> > (`SHA256(commitment_txid(16,32])`)
> > - `encryption_key`: first half of the master key (`master_key(0,16]`)
> > - `encryption_iv`: second half of the master key (`master_key(16,32]`)
> >
> >
> > The server relies on both the encryption key and iv to decrypt the
> justice
> > transaction. Furthermore, the transaction locator helps the WatchTower
> > identify a breach transaction on the blockchain.
>
> The SHA256 step here is crypto cargo-culting AFAICT.  If the watchtower
> can guess the txid, it does nothing.  If it can't, it does nothing.
>
> Setting both the IV and the key is similarly weird.  Use an IV of 0 and
> simply use the second half of the commit txid as key.
>

I actually realised that the proposed sizes are wrong from this comment
(they come from defining it with AES-GCM-128 originally). chacha20 needs
32-byte keys, so we'll need to do something like:

locator = commitment_txid(0,16]
key = sha256(commitment_txid)
iv = 0 (12-byte)

Since we're not using the second half of the txid we could define the
locator to be the txid, but having half reduces the storage requirements of
the tower. I think it's worth discussing this.


> > ## Encryption Algorithms and Parameters
> >
> > All clients and servers MUST use one of the following encryption
> > algorithms:
> >
> > - ChaCha20 (https://tools.ietf.org/html/rfc7539)
> > - AES-GCM-256 (https://tools.ietf.org/html/rfc5288)
>
> This would be the first use of AES, and you need to implement chacha20
> to speak to other peers.  Drop this.
>

Done.


>
> > Sample code (python) for the client to prepare the `encrypted_blob`:
> >
> > from hashlib import sha256
> > from binascii import hexlify
> >
> > def encrypt(justice_tx, commitment_txid):
> >    # master_key = SHA256(commitment_txid(16, 32])
> >    master_key = sha256(commitment_txid[16:]).digest()
> >
> >    # The 16 MSB of the master key will serve as the AES-GCM-256 secret
> key.
> > The 16 LSB will serve as the IV.
> >    sk = master_key[:16]
> >    nonce = master_key[16:]
> >
> >    # Encrypt the data
> >    aesgcm = AESGCM(sk)
> >    encrypted_blob = aesgcm.encrypt(nonce=iv, data=tx,
> associated_data=None)
> >    encrypted_blob = hexlify(encrypted_blob).decode()
> >
> >    return encrypted_blob
>
> > ## Payment modes
> >
> > Although this BOLT does not enforce any specific payment method to be
> > adopted, it is worth mentioning the three most common ones:
> >
> > **On-chain bounty**. An additional output is created in the justice
> > transaction that will reward the WatchTower.
>
> This has the advantage of allowing (and incentivizing!) the WatchTower
> to CPFP.  However, it has to be defined (how does client know what addr
> to pay to?), so I'd leave it for later.
>

Agreed.


>
> > **Micropayments**. A small payment is sent to the WatchTower for every
> new
> > job (e.g. over the lightning network)
> >
> > **Subscription**. WatchTower is periodically rewarded / paid for their
> > service to the customer. (e.g. over the lightning network or fiat
> > subscription).
> >
> > Both micropayments and subscriptions are favourable for a WatchTower. The
> > on-chain bounty approach is not ideal for a watching network, it lets the
> > customer hire many WatchTowers (O(N) storage for each tower) and only one
> > WatchTower will be rewarded upon collecting the bounty. On top of that,
> the
> > onchain bounty allows a network-wise DoS attack for free.
>
> > ## No compression of justice transaction
> >
> > The storage requirements for a WatchTower can be reduced (linearly) by
> > implementing [shachain](
> >
> https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
> ),
> > therefore storing the parts required to build the transaction and the
> > corresponding signing key instead of the full transaction. For now, we
> have
> > decided to keep the hiring protocol simple. Storage is relatively cheap
> and
> > we can revisit this standard if it becomes a problem.
>
> Agreed.
>
> Cheers,
> Rusty.
>


-- 
Sergi.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linuxfoundation.org/pipermail/lightning-dev/attachments/20191205/13928de1/attachment-0001.html>


More information about the Lightning-dev mailing list