Rusty Russell [ARCHIVE] on Nostr: 📅 Original date posted:2017-05-04 📝 Original message: Pierre <pm+lists at ...
📅 Original date posted:2017-05-04
📝 Original message:
Pierre <pm+lists at acinq.fr> writes:
>>> On the topic of signatures: as is proposed now, a user isn't able to verify
>>> the validity of the signature (and thereby authenticity of the payreq and
>>> integrity of the contents) without first having a (direction || chanID) ->
>>> pubKey mapping. In my opinion, the payreqs are already so long that
>>> optimizing for size is a bit of a waste. By replacing the chanID with the
>>> compressed serialized public key, users will be able to immediately verify
>>> the signature without the use of an external mapping.
>
> As much as I pushed for using the short chanID in the onion, I too am
> a reluctant to use
> it here. In addition to laolu's arguments, I would say:
> - making the assumption that the network is well-known doesn't take
> into account the fact
> that announcements take time to propagate through the network
> (typically a few minutes with
> staggered broadcast every minute); ok it doesn't change often, but now
> we will need to worry
> about not using our most recently created/closed channels as reference.
> - we already know that we won't always be able to have a full view of
> the network in
> the future, so I feel like we should rely on it as less as possible
> - since payment requests are sent out-of-band, optimizing their size
> is maybe not as
> important as messages exchanged on the p2p network?
>
>>As you pointed out offline, we can do key recovery from the signature[1],
>>so the information is there already in fact :) The chanid is really a
>>courtesy, from this POV.
>
> That is really amazing! Why not completely ditch the chanid then? ;-)
OK, I have done this. I added another byte which you need to figure out
which key to recover as there may be multiples.
I will add a dependency on the python libsecp256k1 so I can update the
tool.
Note: we will lose this ability when we switch to Schnorr, apparently.
But then we'll have more data on actual usage by which to decide on
tradeoffs.
I've pushed this change. Here's the patch:
Subject: Drop channel ID in favor of key recovery.
As suggested by roasbeef (Olaoluwa Osuntokun). We add a byte to the
signature, even though we only need 1 or 2 bits for recovery ID.
The signature is actually over human readable part and data in 5-bit
bytes, for simplicity of implementation (all current bech32 encoders
work this way).
Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>
diff --git a/README.md b/README.md
index b6ac088..3039333 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,8 @@ Human readable part:
And data part:
1. Version: 0 (5 bits)
1. Payment hash (256 bits)
-1. Dest channel id (high bit == which end) (64 bits)
1. Zero or more tagged parts.
-1. Signature (bitcoin-style, of SHA256(SHA256()) of above. (256 bits)
+1. Signature (bitcoin-style, of SHA256(SHA256(), plus recovery byte) of above. (520 bits)
Tagged parts are of format:
1. type (5 bits)
diff --git a/examples.sh b/examples.sh
index 3da33ab..78c2be8 100755
--- a/examples.sh
+++ b/examples.sh
@@ -2,28 +2,27 @@
# Dummy placeholder values
RHASH=0001020304050607080900010203040506070809000102030405060708090102
-CHANID=1122334455667788
CONVERSION_RATE=1200
# Random keypair for testing.
PRIVKEY=e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734
PUBKEY=03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad
-echo '# Please send 10 satoshi using rhash $RHASH to node at channel $CHANID (numerically-lesser end of those peers).'
-./lightning-address.py encode 10000 $RHASH $CHANID $PRIVKEY
+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 $CHANID $PRIVKEY
+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
-echo '# Now send $24 for an entire list of things (hashed)'
-./lightning-address.py encode --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY
+echo "# Now send \$24 for an entire list of things (hashed)"
+./lightning-address.py encode --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY
echo
# NOTE: Does not implement real fallback format yet!
echo '# The same, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'
-./lightning-address.py encode --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY
+./lightning-address.py encode --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY
echo
echo '# The same, with extra routing info to get to node 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'
-./lightning-address.py encode --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/20/3 --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY
+./lightning-address.py encode --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/20/3 --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY
diff --git a/lightning-address.py b/lightning-address.py
index 1a3f670..c43d020 100755
--- a/lightning-address.py
+++ b/lightning-address.py
@@ -169,8 +169,8 @@ def lnencode(options):
hrp = 'ln' + options.currency + amount
- # version + paymenthash + channelid
- data = [0] + convertbits(bytearray.fromhex(options.paymenthash) + bytearray.fromhex(options.channelid), 8, 5)
+ # version + paymenthash
+ data = [0] + convertbits(bytearray.fromhex(options.paymenthash), 8, 5)
for r in options.route:
pubkey,channel,fee,cltv = r.split('/')
@@ -189,9 +189,10 @@ def lnencode(options):
# We actually sign the hrp, then the array of 5-bit values as bytes.
privkey = secp256k1.PrivateKey(bytes(bytearray.fromhex(options.privkey)))
- sig = privkey.ecdsa_serialize_compact(privkey.ecdsa_sign(
- bytearray([ord(c) for c in hrp] + data)))
- data = data + convertbits(sig, 8, 5)
+ sig = privkey.ecdsa_sign_recoverable(bytearray([ord(c) for c in hrp] + data))
+ # This doesn't actually serialize, but returns a pair of values :(
+ sig,recid = privkey.ecdsa_recoverable_serialize(sig)
+ data = data + convertbits(bytes(sig) + bytes([recid]), 8, 5)
print(bech32_encode(hrp, data))
@@ -205,27 +206,19 @@ def lndecode(options):
if data[0] != 0:
sys.exit("Unknown version {}".format(data[0]))
+ data = data[1:]
- # Final signature takes 103 bytes (64 bytes base32 encoded)
+ # Final signature takes 104 bytes (65 bytes base32 encoded)
if len(data) < 103:
sys.exit("Too short to contain signature")
- sigdecoded = convertbits(data[-103:], 5, 8, False)
- data = data[:-103]
-
- if options.pubkey:
- pubkey = secp256k1.PublicKey()
- pubkey.deserialize(bytes(bytearray.fromhex(options.pubkey)))
- sig = pubkey.ecdsa_deserialize_compact(sigdecoded)
- if not pubkey.ecdsa_verify(bytearray([ord(c) for c in hrp] + data), sig):
- sys.exit("Bad signature")
- print("Signature: OK")
- else:
- print("Signature: Unknown")
-
- # Preserve original data for signature check.
- origdata = data
- data = data[1:]
+ sigdecoded = convertbits(data[-104:], 5, 8, False)
+ data = data[:-104]
+ pubkey = secp256k1.PublicKey(flags=secp256k1.ALL_FLAGS)
+ sig = pubkey.ecdsa_recoverable_deserialize(sigdecoded[0:64], sigdecoded[64])
+ pubkey.public_key = pubkey.ecdsa_recover(bytearray([ord(c) for c in hrp] + data), sig)
+ print("Signed with public key: {}".format(bytearray(pubkey.serialize()).hex()))
+
m = re.search("\d+", hrp)
if not m:
sys.exit("Does not contain amount")
@@ -246,15 +239,14 @@ def lndecode(options):
if options.rate:
print("(Conversion: {})".format(amount / 10**11 * float(options.rate)))
- # (32 + 8) bytes turns into 64 bytes when base32 encoded.
- if len(data) < 64:
- sys.exit("Not long enough ton contain payment hash and channel id")
+ # 32 bytes turns into 52 bytes when base32 encoded.
+ if len(data) < 52:
+ sys.exit("Not long enough to contain payment hash")
- decoded = convertbits(data[:64], 5, 8, False)
- data = data[64:]
- assert len(decoded) == 32 + 8
- print("Payment hash: {}".format(bytearray(decoded[0:32]).hex()))
- print("Channel id: {}".format(bytearray(decoded[32:40]).hex()))
+ decoded = convertbits(data[:52], 5, 8, False)
+ data = data[52:]
+ assert len(decoded) == 32
+ print("Payment hash: {}".format(bytearray(decoded).hex()))
while len(data) > 0:
tag,tagdata,data = pull_tagged(data)
@@ -297,7 +288,6 @@ parser_enc.add_argument('--description-hashed',
help='What is being purchased (for hashing)')
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('channelid', help='Channel id (in hex)')
parser_enc.add_argument('privkey', help='Private key (in hex)')
parser_enc.set_defaults(func=lnencode)
Published at
2023-06-09 12:47:10Event JSON
{
"id": "5d033e825e317a95a8bff9267d33881f9dcebe8fba9eb82400fdfaeae24c71a0",
"pubkey": "13bd8c1c5e3b3508a07c92598647160b11ab0deef4c452098e223e443c1ca425",
"created_at": 1686314830,
"kind": 1,
"tags": [
[
"e",
"56742c88f6265e0090cd0ac17729d1bb4693d9767822b89dc939db0c32d43bbb",
"",
"root"
],
[
"e",
"78e03c42d9e0d11e38cf550ce40b5b455ee9f9e6313be8cfa3ff8188ea2b599d",
"",
"reply"
],
[
"p",
"13bd8c1c5e3b3508a07c92598647160b11ab0deef4c452098e223e443c1ca425"
]
],
"content": "📅 Original date posted:2017-05-04\n📝 Original message:\nPierre \u003cpm+lists at acinq.fr\u003e writes:\n\u003e\u003e\u003e On the topic of signatures: as is proposed now, a user isn't able to verify\n\u003e\u003e\u003e the validity of the signature (and thereby authenticity of the payreq and\n\u003e\u003e\u003e integrity of the contents) without first having a (direction || chanID) -\u003e\n\u003e\u003e\u003e pubKey mapping. In my opinion, the payreqs are already so long that\n\u003e\u003e\u003e optimizing for size is a bit of a waste. By replacing the chanID with the\n\u003e\u003e\u003e compressed serialized public key, users will be able to immediately verify\n\u003e\u003e\u003e the signature without the use of an external mapping.\n\u003e\n\u003e As much as I pushed for using the short chanID in the onion, I too am\n\u003e a reluctant to use\n\u003e it here. In addition to laolu's arguments, I would say:\n\u003e - making the assumption that the network is well-known doesn't take\n\u003e into account the fact\n\u003e that announcements take time to propagate through the network\n\u003e (typically a few minutes with\n\u003e staggered broadcast every minute); ok it doesn't change often, but now\n\u003e we will need to worry\n\u003e about not using our most recently created/closed channels as reference.\n\u003e - we already know that we won't always be able to have a full view of\n\u003e the network in\n\u003e the future, so I feel like we should rely on it as less as possible\n\u003e - since payment requests are sent out-of-band, optimizing their size\n\u003e is maybe not as\n\u003e important as messages exchanged on the p2p network?\n\u003e\n\u003e\u003eAs you pointed out offline, we can do key recovery from the signature[1],\n\u003e\u003eso the information is there already in fact :) The chanid is really a\n\u003e\u003ecourtesy, from this POV.\n\u003e\n\u003e That is really amazing! Why not completely ditch the chanid then? ;-)\n\nOK, I have done this. I added another byte which you need to figure out\nwhich key to recover as there may be multiples.\n\nI will add a dependency on the python libsecp256k1 so I can update the\ntool.\n\nNote: we will lose this ability when we switch to Schnorr, apparently.\nBut then we'll have more data on actual usage by which to decide on\ntradeoffs.\n\nI've pushed this change. Here's the patch:\n\nSubject: Drop channel ID in favor of key recovery.\n\nAs suggested by roasbeef (Olaoluwa Osuntokun). We add a byte to the\nsignature, even though we only need 1 or 2 bits for recovery ID.\n\nThe signature is actually over human readable part and data in 5-bit\nbytes, for simplicity of implementation (all current bech32 encoders\nwork this way).\n\nSigned-off-by: Rusty Russell \u003crusty at rustcorp.com.au\u003e\n\ndiff --git a/README.md b/README.md\nindex b6ac088..3039333 100644\n--- a/README.md\n+++ b/README.md\n@@ -12,9 +12,8 @@ Human readable part:\n And data part:\n 1. Version: 0 (5 bits)\n 1. Payment hash (256 bits)\n-1. Dest channel id (high bit == which end) (64 bits)\n 1. Zero or more tagged parts.\n-1. Signature (bitcoin-style, of SHA256(SHA256()) of above. (256 bits)\n+1. Signature (bitcoin-style, of SHA256(SHA256(), plus recovery byte) of above. (520 bits)\n \n Tagged parts are of format:\n 1. type (5 bits)\ndiff --git a/examples.sh b/examples.sh\nindex 3da33ab..78c2be8 100755\n--- a/examples.sh\n+++ b/examples.sh\n@@ -2,28 +2,27 @@\n \n # Dummy placeholder values\n RHASH=0001020304050607080900010203040506070809000102030405060708090102\n-CHANID=1122334455667788\n CONVERSION_RATE=1200\n # Random keypair for testing.\n PRIVKEY=e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734\n PUBKEY=03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad\n \n-echo '# Please send 10 satoshi using rhash $RHASH to node at channel $CHANID (numerically-lesser end of those peers).'\n-./lightning-address.py encode 10000 $RHASH $CHANID $PRIVKEY\n+echo \"# Please send 10 satoshi using rhash $RHASH to me @$PUBKEY\"\n+./lightning-address.py encode 10000 $RHASH $PRIVKEY\n echo\n \n-echo '# Please send $3 for a cup of coffee to the same peer'\n-./lightning-address.py encode --description='1 cup coffee' $((3 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY\n+echo \"# Please send \\$3 for a cup of coffee to the same peer\"\n+./lightning-address.py encode --description='1 cup coffee' $((3 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY\n echo\n \n-echo '# Now send $24 for an entire list of things (hashed)'\n-./lightning-address.py encode --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY\n+echo \"# Now send \\$24 for an entire list of things (hashed)\"\n+./lightning-address.py encode --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY\n echo\n \n # NOTE: Does not implement real fallback format yet!\n echo '# The same, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'\n-./lightning-address.py encode --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY\n+./lightning-address.py encode --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY\n echo\n \n echo '# The same, with extra routing info to get to node 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'\n-./lightning-address.py encode --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/20/3 --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $CHANID $PRIVKEY\n+./lightning-address.py encode --route=029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255/0102030405060708/20/3 --fallback=mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP --description-hashed='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' $((24 * 100000000000 / $CONVERSION_RATE)) $RHASH $PRIVKEY\ndiff --git a/lightning-address.py b/lightning-address.py\nindex 1a3f670..c43d020 100755\n--- a/lightning-address.py\n+++ b/lightning-address.py\n@@ -169,8 +169,8 @@ def lnencode(options):\n \n hrp = 'ln' + options.currency + amount\n \n- # version + paymenthash + channelid\n- data = [0] + convertbits(bytearray.fromhex(options.paymenthash) + bytearray.fromhex(options.channelid), 8, 5)\n+ # version + paymenthash\n+ data = [0] + convertbits(bytearray.fromhex(options.paymenthash), 8, 5)\n \n for r in options.route:\n pubkey,channel,fee,cltv = r.split('/')\n@@ -189,9 +189,10 @@ def lnencode(options):\n \n # We actually sign the hrp, then the array of 5-bit values as bytes.\n privkey = secp256k1.PrivateKey(bytes(bytearray.fromhex(options.privkey)))\n- sig = privkey.ecdsa_serialize_compact(privkey.ecdsa_sign(\n- bytearray([ord(c) for c in hrp] + data)))\n- data = data + convertbits(sig, 8, 5)\n+ sig = privkey.ecdsa_sign_recoverable(bytearray([ord(c) for c in hrp] + data))\n+ # This doesn't actually serialize, but returns a pair of values :(\n+ sig,recid = privkey.ecdsa_recoverable_serialize(sig)\n+ data = data + convertbits(bytes(sig) + bytes([recid]), 8, 5)\n \n print(bech32_encode(hrp, data))\n \n@@ -205,27 +206,19 @@ def lndecode(options):\n \n if data[0] != 0:\n sys.exit(\"Unknown version {}\".format(data[0]))\n+ data = data[1:]\n \n- # Final signature takes 103 bytes (64 bytes base32 encoded)\n+ # Final signature takes 104 bytes (65 bytes base32 encoded)\n if len(data) \u003c 103:\n sys.exit(\"Too short to contain signature\")\n- sigdecoded = convertbits(data[-103:], 5, 8, False)\n- data = data[:-103]\n-\n- if options.pubkey:\n- pubkey = secp256k1.PublicKey()\n- pubkey.deserialize(bytes(bytearray.fromhex(options.pubkey)))\n- sig = pubkey.ecdsa_deserialize_compact(sigdecoded)\n- if not pubkey.ecdsa_verify(bytearray([ord(c) for c in hrp] + data), sig):\n- sys.exit(\"Bad signature\")\n- print(\"Signature: OK\")\n- else:\n- print(\"Signature: Unknown\")\n-\n- # Preserve original data for signature check.\n- origdata = data\n- data = data[1:]\n+ sigdecoded = convertbits(data[-104:], 5, 8, False)\n+ data = data[:-104]\n \n+ pubkey = secp256k1.PublicKey(flags=secp256k1.ALL_FLAGS)\n+ sig = pubkey.ecdsa_recoverable_deserialize(sigdecoded[0:64], sigdecoded[64])\n+ pubkey.public_key = pubkey.ecdsa_recover(bytearray([ord(c) for c in hrp] + data), sig)\n+ print(\"Signed with public key: {}\".format(bytearray(pubkey.serialize()).hex()))\n+ \n m = re.search(\"\\d+\", hrp)\n if not m:\n sys.exit(\"Does not contain amount\")\n@@ -246,15 +239,14 @@ def lndecode(options):\n if options.rate:\n print(\"(Conversion: {})\".format(amount / 10**11 * float(options.rate)))\n \n- # (32 + 8) bytes turns into 64 bytes when base32 encoded.\n- if len(data) \u003c 64:\n- sys.exit(\"Not long enough ton contain payment hash and channel id\")\n+ # 32 bytes turns into 52 bytes when base32 encoded.\n+ if len(data) \u003c 52:\n+ sys.exit(\"Not long enough to contain payment hash\")\n \n- decoded = convertbits(data[:64], 5, 8, False)\n- data = data[64:]\n- assert len(decoded) == 32 + 8\n- print(\"Payment hash: {}\".format(bytearray(decoded[0:32]).hex()))\n- print(\"Channel id: {}\".format(bytearray(decoded[32:40]).hex()))\n+ decoded = convertbits(data[:52], 5, 8, False)\n+ data = data[52:]\n+ assert len(decoded) == 32\n+ print(\"Payment hash: {}\".format(bytearray(decoded).hex()))\n \n while len(data) \u003e 0:\n tag,tagdata,data = pull_tagged(data)\n@@ -297,7 +288,6 @@ parser_enc.add_argument('--description-hashed',\n help='What is being purchased (for hashing)')\n parser_enc.add_argument('amount', type=int, help='Amount in millisatoshi')\n parser_enc.add_argument('paymenthash', help='Payment hash (in hex)')\n-parser_enc.add_argument('channelid', help='Channel id (in hex)')\n parser_enc.add_argument('privkey', help='Private key (in hex)')\n parser_enc.set_defaults(func=lnencode)",
"sig": "753d7bd38a420cf76a6ada6d2f02b569bf1a9c89daaf2ad747a9f873bfaaf6bc258913072faa92904723c69e87d6ef33d7c23be94fff06992219f439df6258a7"
}