diff --git a/examples.sh b/examples.sh index dd7368d..79591dd 100755 --- a/examples.sh +++ b/examples.sh @@ -8,7 +8,8 @@ PRIVKEY=e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734 PUBKEY=03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad # Reproducable example, set timestamp. TIMESTAMP=--timestamp=1496314658 - +# Secret hash for the example. +SECRETE='1111111111111111111111111111111111111111111111111111111111111111' LONG_DESCRIPTION='One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon' to_btc() @@ -17,33 +18,36 @@ to_btc() } echo "### Please make a donation of any amount using rhash $RHASH to me @$PUBKEY" -./lightning-address.py encode $TIMESTAMP --no-amount 0 --description='Please consider supporting this project' $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --no-amount 0 --description='Please consider supporting this project' $RHASH $PRIVKEY $SECRETE echo echo "### Please send \$3 for a cup of nonsense (ナンセンス 1杯) to the same peer, within 1 minute" -./lightning-address.py encode $TIMESTAMP --description='ナンセンス 1杯' $(to_btc 3) --expires=60 $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --description='ナンセンス 1杯' $(to_btc 3) --expires=60 $RHASH $PRIVKEY $SECRETE echo echo "### Now send \$24 for an entire list of things (hashed)" -./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY $SECRETE echo echo '### The same, on testnet, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP' -./lightning-address.py encode $TIMESTAMP --currency=tb --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --currency=tb --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY $SECRETE echo echo '### On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255' -./lightning-address.py encode $TIMESTAMP --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/1/20/3/039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/030405060708090a/2/30/4 --fallback=1RustyRX2oai4EYYDpQGWvEL62BBGqN9T --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/1/20/3/039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/030405060708090a/2/30/4 --fallback=1RustyRX2oai4EYYDpQGWvEL62BBGqN9T --description-hashed="$LONG_DESCRIPTION" $(to_btc 24) $RHASH $PRIVKEY $SECRETE echo echo '### On mainnet, with fallback (p2sh) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX' -./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX $(to_btc 24) $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX $(to_btc 24) $RHASH $PRIVKEY $SECRETE echo echo '### On mainnet, with fallback (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' -./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 $(to_btc 24) $RHASH $PRIVKEY +./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 $(to_btc 24) $RHASH $PRIVKEY $SECRETE echo -echo '### On mainnet, with fallback (p2wsh) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3' -./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3 $(to_btc 24) $RHASH $PRIVKEY +echo '### On mainnet, with fallback (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' features='100000100000000' +./lightning-address.py encode --timestamp=1496314658 --description="One piece of chocolate cake" --fallback=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 --features='100000100000000' $(to_btc 24) $RHASH $PRIVKEY $SECRETE +echo +echo '### On mainnet, with fallback (p2wsh) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3' +./lightning-address.py encode $TIMESTAMP --description-hashed="$LONG_DESCRIPTION" --fallback=bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3 $(to_btc 24) $RHASH $PRIVKEY $SECRETE diff --git a/lightning-address.py b/lightning-address.py index 8c0d7fa..f79d33a 100755 --- a/lightning-address.py +++ b/lightning-address.py @@ -29,6 +29,11 @@ def encode(options): if options.fallback: addr.tags.append(('f', options.fallback)) + if options.features: + addr.tags.append(('9', int(options.features, 2))) + + addr.tags.append(('s', unhexlify(options.secret))) + for r in options.route: splits = r.split('/') route=[] @@ -78,7 +83,14 @@ def tags_by_name(name, tags): if expiry: print("Expiry (seconds):", expiry[0]) - for t in [t for t in a.tags if t[0] not in 'rdfhx']: + secret = tags_by_name('s', a.tags) + if secret: + print("Secret:", hexlify(secret[0])) + + for f in tags_by_name('9', a.tags): + print("Features: {}".format(bin(f).zfill(8))) + + for t in [t for t in a.tags if t[0] not in 'rdfhxs9']: print("UNKNOWN TAG {}: {}".format(t[0], hexlify(t[1]))) parser = argparse.ArgumentParser(description='Encode lightning address') @@ -104,9 +116,11 @@ def tags_by_name(name, tags): help='Timestamp (seconds after epoch) instead of now') parser_enc.add_argument('--no-amount', action="store_true", help="Don't encode amount") +parser_enc.add_argument('--features', help='Features to encode in binary string (e.g., 10000010)') parser_enc.add_argument('amount', type=float, help='Amount in currency') parser_enc.add_argument('paymenthash', help='Payment hash (in hex)') parser_enc.add_argument('privkey', help='Private key (in hex)') +parser_enc.add_argument('secret', help='payment address (payment secret in hex)') parser_enc.set_defaults(func=encode) parser_dec.add_argument('lnaddress', help='Address to decode') diff --git a/lnaddr.py b/lnaddr.py index 2d88a87..d3c0535 100755 --- a/lnaddr.py +++ b/lnaddr.py @@ -181,8 +181,8 @@ def lnencode(addr, privkey): # BOLT #11: # - # A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields, - if k in ('d', 'h', 'n', 'x'): + # A writer MUST NOT include more than one `s`, `d`, `h`, `n` or `x` fields, + if k in ('s', 'd', 'h', 'n', 'x'): if k in tags_set: raise ValueError("Duplicate '{}' tag".format(k)) @@ -206,6 +206,14 @@ def lnencode(addr, privkey): data += tagged_bytes('h', hashlib.sha256(v.encode('utf-8')).digest()) elif k == 'n': data += tagged_bytes('n', v) + elif k == 's': + data += tagged_bytes('s', v) + elif k == '9': + if v == 0: + continue + # Calcular quantos bits são necessários para representar 'v' + bits_needed = v.bit_length() or 1 + data += tagged('9', bitstring.pack(f'uint:{bits_needed}', v)) else: # FIXME: Support unknown tags? raise ValueError("Unknown tag {}".format(k)) @@ -240,6 +248,8 @@ def __init__(self, paymenthash=None, amount=None, currency='bc', tags=None, date self.pubkey = None self.currency = currency self.amount = amount + self.features = None + self.secret = None def __str__(self): return "LnAddr[{}, amount={}{} tags=[{}]]".format( @@ -347,6 +357,16 @@ def lndecode(a, verbose=False): continue addr.pubkey = secp256k1.PublicKey(flags=secp256k1.ALL_FLAGS) addr.pubkey.deserialize(trim_to_bytes(tagdata)) + elif tag == 's': + if data_length != 52: + addr.unknown_tags.append((tag, tagdata)) + continue + addr.tags.append(('s', trim_to_bytes(tagdata))) + elif tag == '9': + feature = tagdata.uint + if feature == 0: + continue + addr.tags.append((tag, feature)) else: addr.unknown_tags.append((tag, tagdata))