[Lightning-dev] Data piggybacking within the payment_preimage for offline payments in wallets

JOSE FEMENIAS CAÑUELO jose.femenias at gmail.com
Sat Dec 15 11:53:12 UTC 2018

Hi, I'd like to share an idea to piggyback information within the payment_preimage. (The intended use case is for offline payments, but I suppose there could be more). 
AFIK the Bolt documentation doesn't have any provision for a scheme like this, but I am am still trying to digest all the Bolt documentation, so maybe I am missing something.

Also, maybe there is some functional problem or major security concern that makes this scheme unfeasible, so please correct me if I'm wrong. 

One paragraph description: 

An offline device and a online LN Node share a common secret seed, so they can both independently calculate the same per_transaction_secret (PIN). Right before payment, the LN Node encrypts the PIN within the payment_preimage and sends the LN Wallet the encryption key.  Upon payment, having received the payment_preimage, the LN Wallet decrypts and shows the PIN. The user provides this PIN to the offline device so it can verify the payment. A standard is needed so that every compliant wallet knows how to decode and present the information to the user.

The scheme could work like this:

	1• An offline device (say a vending machine) shares a secret S1 with an online LN Node.
	2• When a payment is due, the offline device uses that secret S1, the Device_Id, the Product_Id and a Transaction_Id to generate a second secret S2 (let's say a six digit PIN) which will act as proof of payment to the offline device. S2 is specific for each transaction and is calculated using an HTOP algorithm.
	3• Both S1 and S2 stay within the offline device at all times.
	4• When the user selects a product, the offline device provides the LN Wallet (possibly through a QR code) with three pieces of information (Transaction_Id, Product_Id and Device_Id) which the LN Wallet relays to the online LN Node. 

Note: To support payments with static QR codes, the Transaction_Id  doesn't need to be sent. It can be replaced with an implicit time stamp (the way Google Authenticator works).
	5• The LN Node, using the information from [4] and the shared secret S1, calculates independently the secret S2 and encodes it using L bits.
	6• The LN Node generates a random number R, and slices it into two parts, the first L bits OTP = R[0..L-1] and the remaining 256-L bits R2 = R[L..255].
	7• OTP is sent back to the LN Wallet BEFORE the payment is made.
	8• The LN Node uses OTP as a one time pad to encrypt the secret S2 using a XOR operation: S3 = S2 XOR .OTP
	9• The LN Node creates a payment_preimage by concatenating the encrypted secret with the last bits of the random number from [6]: payment_preimage = S3 & R2 
	10• The LN Wallet, upon payment of the invoice, receives the payment_preimage.
	11• The LN Wallet, uses the payment_preimage from [10] and extracts the piggybacked information S3 = payment_preimage[0..L-1]. Then, it uses the one time pad received in [7] to decrypt S2 =  S3 XOR OTP.
	12• The LN Wallet shows the user S2 (a six digit PIN in our example), so she can type it in the offline device to claim her product.

The key thing for a scheme like this to be useful is to have a standard way for the LN Wallet to extract and handle the secret S2 from the payment_preimage. This way it can know that it has to show -let's say- a six digit numerical PIN instead of a four digit alphanumeric PIN or a QR code. I envision it as a MAGIC_NUMBER encoded within the secret, for which it should be somewhere in the standard documentation, so that every LN Wallet knows how to handle each use case.

Below, I rephrase the same scheme with a more detailed implementation example for a vending machine.

USER EXPERIENCE (for a vending machine):

A customer of an offline vending machine  selects a product. The display of the vending machine shows a QR code that the user reads into her LN Wallet App. Upon payment of the required amount, the LN Wallet App shows her a six digit PIN that she types in the machine to get the chosen product.


A payment server and a vending machine share a common secret, a seed for an HTOP algorithm (HMac One Time Password). The vending machine can be offline at all times.

When the customer selects a product, the vending machine generates a Transaction_Id (say 28481117450284) and encodes a payment url within a QR code for the user to read.

The url could be something like this:



Pid = 231 is the id of the selected product.
Tid = 28481117450284 is a random number to identify this transaction.
Vid = B4AF2F is a unique identifier for this particular vending machine.

The vending machine uses this information, along with the shared secret seed, to calculate a secret PIN, unique to this transaction, using the HTOP algorithm. Let's say the result is a 6 digit PIN like 998376 (hex 0x000F8B3E), which the vending machine MUST NOT show.

With all this information plus the shared secret, the server at example.com calculates the same PIN number (0x000F8B3E) and it then commands a LN node to create an invoice with the proper payment for the product 231 that the customer has chosen.

The LN node generates a random 32 byte number; let's say:


It then slices the number into two fields, namely:

	• OneTimePad: The first 10 bytes (ea8caa790486e66e70fa)
	• Entropy: The last 22 bytes (2da6fff89d1ba1678b834fe162c58fcbbef25ea3177c)

The piggybacked data will also be (in this example) 10 bytes, of which the first 6 constitute the magic number (0x000000001234 in the example) and the next 4 are the PIN (0x000F8B3E)

So the third field will be:

c) Piggybacked data (raw): 000000001234000F8B3E2

Now, it encrypts the Piggybacked data by XORing a) and c) to obtain:

d) Piggybacked data (encrypted): ea8caa7916b2e661fbc40 

It then concatenates d) to b) to obtain the payment_preimage:


The payment gateway sends the wallet the OneTimePad a) and the invoice to pay.

After paying the proper amount, the mobile wallet of the customer, receives this payment_preimage that contains the -encrypted- piggybacked information.

It then uses the OneTimePad a), XORs it with the first 10 bytes of the preimage and recovers c) the Piggybacked data 000000001234000F8B3E2

Upon recognizing the first six bytes as the magic number for this use case, the LN Wallet App decodes the next 4 bytes and presents the user with the six digit PIN (decimal 998376). The customer then enters this PIN in the vending machine and gets her product.

More information about the Lightning-dev mailing list