Why Nostr? What is Njump?
2023-06-09 12:47:10
in reply to

Rusty Russell [ARCHIVE] on Nostr: 📅 Original date posted:2017-05-04 📝 Original message: Fabrice Drouin ...

📅 Original date posted:2017-05-04
📝 Original message:
Fabrice Drouin <fabrice.drouin at acinq.fr> writes:
> Hi Rusty,
>
> Payment requests should also include a timestamp and an expiry date (they
> could be optional tagged items but I think it makes more sense to make them
> mandatory)

Excellent point. Provability definitely requires a timestamp, but the
duration could be optional.

Here's the patch I just pushed:

Subject: Add timestamp and (optional) expiry.

We take advantage of the variable length encoding for the expiry timestamp,
and 32 bits for the offer time (wake me in 2106 to update the spec).

I chose a reasonable default expiry of 1 hour; the intention is that
the software should warn if this expiry approaches.

Suggested-by: Fabrice Drouin <fabrice.drouin at acinq.fr>
Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>

diff --git a/README.md b/README.md
index 3039333..6da4150 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ Human readable part:

And data part:
1. Version: 0 (5 bits)
+1. UTC timestamp in seconds-since-Unix-epoch (32 bits)
1. Payment hash (256 bits)
1. Zero or more tagged parts.
1. Signature (bitcoin-style, of SHA256(SHA256(), plus recovery byte) of above. (520 bits)
@@ -25,6 +26,7 @@ Currently defined tagged parts are:
1. h: description of purpose of payment (SHA256). This is used to commit
to an associated description which is too long to fit, such as may
be contained in a web page.
+1. x: expiry time in seconds. Default is 3600 (1 hour) if not specified.
1. f: fallback onchain-address. 20 bytes == p2pkh. 21 bytes == p2wpkh, 33 bytes == p2wsh.
1. r: extra routing information. This should be appended to the route
to allow routing to non-public nodes; there may be more
diff --git a/examples.sh b/examples.sh
index 78c2be8..f526223 100755
--- a/examples.sh
+++ b/examples.sh
@@ -11,8 +11,8 @@ echo "# Please send 10 satoshi using rhash $RHASH to me @$PUBKEY"
./lightning-address.py encode 10000 $RHASH $PRIVKEY
echo

-echo "# Please send \$3 for a cup of coffee to the same peer"
-./lightning-address.py encode --description='1 cup coffee' $((3 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY
+echo "# Please send \$3 for a cup of coffee to the same peer, within 1 minute"
+./lightning-address.py encode --description='1 cup coffee' $((3 * 100000000000 / $CONVERSION_RATE)) --expires=60 $RHASH $PRIVKEY
echo

echo "# Now send \$24 for an entire list of things (hashed)"
diff --git a/lightning-address.py b/lightning-address.py
index c43d020..7b72314 100755
--- a/lightning-address.py
+++ b/lightning-address.py
@@ -3,6 +3,7 @@ import argparse
import hashlib
import re
import sys
+import time
# Try 'pip3 install secp256k1'
import secp256k1

@@ -138,9 +139,24 @@ def u32list(val):
assert val < (1 << 32)
return bytearray([(val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff])

+# Represent big-endian number with as many bytes as it takes.
+def varlist(val):
+ b = bytearray()
+ while val != 0:
+ b.append(val & 0xFF)
+ val = val // 256
+ b.reverse()
+ return b
+
def from_u32list(l):
return (l[0] << 24) + (l[1] << 16) + (l[2] << 8) + l[3]

+def from_varlist(l):
+ total = 0
+ for v in l:
+ total = total * 256 + v
+ return total
+
def tagged(char, l):
bits=convertbits(l, 8, 5)
assert len(bits) < (1 << 10)
@@ -169,9 +185,12 @@ def lnencode(options):

hrp = 'ln' + options.currency + amount

- # version + paymenthash
- data = [0] + convertbits(bytearray.fromhex(options.paymenthash), 8, 5)
+ # version + timestamp + paymenthash
+ now = int(time.time())
+ assert len(u32list(now) + bytearray.fromhex(options.paymenthash)) == 4 + 32
+ data = [0] + convertbits(u32list(now) + bytearray.fromhex(options.paymenthash), 8, 5)

+
for r in options.route:
pubkey,channel,fee,cltv = r.split('/')
route = bytearray.fromhex(pubkey) + bytearray.fromhex(channel) + u32list(int(fee)) + u32list(int(cltv))
@@ -183,7 +202,10 @@ def lnencode(options):

if options.description:
data = data + tagged('d', [ord(c) for c in options.description])
-
+
+ if options.expires:
+ data = data + tagged('x', varlist(options.expires))
+
if options.description_hashed:
data = data + tagged('h', hashlib.sha256(options.description_hashed.encode('utf-8')).digest())

@@ -239,14 +261,16 @@ def lndecode(options):
if options.rate:
print("(Conversion: {})".format(amount / 10**11 * float(options.rate)))

- # 32 bytes turns into 52 bytes when base32 encoded.
- if len(data) < 52:
- sys.exit("Not long enough to contain payment hash")
+ # 4 + 32 bytes turns into 58 bytes when base32 encoded.
+ if len(data) < 58:
+ sys.exit("Not long enough to contain timestamp and payment hash")

- decoded = convertbits(data[:52], 5, 8, False)
- data = data[52:]
- assert len(decoded) == 32
- print("Payment hash: {}".format(bytearray(decoded).hex()))
+ decoded = convertbits(data[:58], 5, 8, False)
+ data = data[58:]
+ assert len(decoded) == 4 + 32
+ tstamp = from_u32list(decoded[0:4])
+ print("Timestamp: {} ({})".format(tstamp, time.ctime(tstamp)))
+ print("Payment hash: {}".format(bytearray(decoded[4:]).hex()))

while len(data) > 0:
tag,tagdata,data = pull_tagged(data)
@@ -265,6 +289,8 @@ def lndecode(options):
print("Description: {}".format(''.join(chr(c) for c in tagdata)))
elif tag == 'h':
print("Description hash: {}".format(bytearray(tagdata).hex()))
+ elif tag == 'x':
+ print("Expiry (seconds): {}".format(from_varlist(tagdata)))
else:
print("UNKNOWN TAG {}: {}".format(tag, bytearray(tagdata).hex()))

@@ -286,6 +312,8 @@ parser_enc.add_argument('--description',
help='What is being purchased')
parser_enc.add_argument('--description-hashed',
help='What is being purchased (for hashing)')
+parser_enc.add_argument('--expires', type=int,
+ help='Seconds before offer expires')
parser_enc.add_argument('amount', type=int, help='Amount in millisatoshi')
parser_enc.add_argument('paymenthash', help='Payment hash (in hex)')
parser_enc.add_argument('privkey', help='Private key (in hex)')
Author Public Key
npub1zw7cc8z78v6s3grujfvcv3ckpvg6kr0w7nz9yzvwyglyg0qu5sjsqhkhpx