Support ECIES encryption for payment destination
This commit is contained in:
parent
d51e27689f
commit
5e69a9bedf
40
package-lock.json
generated
40
package-lock.json
generated
@ -1,15 +1,18 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"name": "lnurl-pay.me",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "svelte-app",
|
||||
"name": "lnurl-pay.me",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@imask/svelte": "^6.1.0",
|
||||
"@noble/hashes": "^0.5.9",
|
||||
"@noble/secp256k1": "^1.4.0",
|
||||
"bech32": "^2.0.0",
|
||||
"jscrypto": "^1.0.2",
|
||||
"kjua": "^0.9.0",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"sveltestrap": "^5.4.0",
|
||||
@ -74,6 +77,16 @@
|
||||
"svelte": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
|
||||
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
|
||||
},
|
||||
"node_modules/@noble/secp256k1": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.4.0.tgz",
|
||||
"integrity": "sha512-cYpUbQ2uitPgf5QuQnpi8Nu+ZmQjSDunFKw6vvxaOSkbMUhCf4K723WLUuuK1K/sf6H/dvqKbmEAeop5i3qTJg=="
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
"version": "1.0.0-next.15",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
||||
@ -629,6 +642,14 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jscrypto": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.2.tgz",
|
||||
"integrity": "sha512-r+oNJLGTv1nkNMBBq3c70xYrFDgJOYVgs2OHijz5Ht+0KJ0yObD0oYxC9mN72KLzVfXw+osspg6t27IZvuTUxw==",
|
||||
"bin": {
|
||||
"jscrypto": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/kjua": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
||||
@ -1220,6 +1241,16 @@
|
||||
"imask": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"@noble/hashes": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
|
||||
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
|
||||
},
|
||||
"@noble/secp256k1": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.4.0.tgz",
|
||||
"integrity": "sha512-cYpUbQ2uitPgf5QuQnpi8Nu+ZmQjSDunFKw6vvxaOSkbMUhCf4K723WLUuuK1K/sf6H/dvqKbmEAeop5i3qTJg=="
|
||||
},
|
||||
"@polka/url": {
|
||||
"version": "1.0.0-next.15",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
||||
@ -1660,6 +1691,11 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jscrypto": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.2.tgz",
|
||||
"integrity": "sha512-r+oNJLGTv1nkNMBBq3c70xYrFDgJOYVgs2OHijz5Ht+0KJ0yObD0oYxC9mN72KLzVfXw+osspg6t27IZvuTUxw=="
|
||||
},
|
||||
"kjua": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
||||
|
@ -21,7 +21,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@imask/svelte": "^6.1.0",
|
||||
"@noble/hashes": "^0.5.9",
|
||||
"@noble/secp256k1": "^1.4.0",
|
||||
"bech32": "^2.0.0",
|
||||
"jscrypto": "^1.0.2",
|
||||
"kjua": "^0.9.0",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"sveltestrap": "^5.4.0",
|
||||
|
@ -8,6 +8,7 @@
|
||||
import InputMask from './InputMask.svelte';
|
||||
|
||||
import payways from './payways.js';
|
||||
|
||||
import QR from './QR.svelte';
|
||||
|
||||
import CTC from './CTC.svelte';
|
||||
@ -17,6 +18,7 @@
|
||||
import UTF8 from 'utf-8'
|
||||
|
||||
import PayFlow from './PayFlow.svelte';
|
||||
import { ecEncrypt } from './ecies.js';
|
||||
|
||||
let payway = payways[0];
|
||||
|
||||
@ -28,8 +30,12 @@
|
||||
let inputId;
|
||||
let amountMask;
|
||||
let realAmount;
|
||||
let extAccount;
|
||||
let briefAccount;
|
||||
let encrypt = false;
|
||||
|
||||
$: inputId = payway.iid||payway.id;
|
||||
$: accountReady = accountComplete && accounts[inputId]
|
||||
$: amountMask = {
|
||||
mask: Number, scale:2,
|
||||
min: payway.min, max:payway.max,
|
||||
@ -40,6 +46,10 @@
|
||||
$: realAmount = amounts[payway.id] && (
|
||||
Math.max(Math.min(amounts[payway.id], payway.max),payway.min))
|
||||
|
||||
$: extAccount = accountReady ? (encrypt? "0g" + ecEncrypt(accounts[inputId].padEnd(8)): accounts[inputId]):"";
|
||||
|
||||
$: briefAccount = extAccount ? (encrypt ? extAccount.slice(0,28)+"…" : extAccount):""
|
||||
|
||||
function genAutoMemo(payway,account,amount) {
|
||||
if (!account)
|
||||
return "...automatic";
|
||||
@ -52,16 +62,12 @@
|
||||
}
|
||||
|
||||
let autoMemo;
|
||||
$: autoMemo = genAutoMemo(payway,
|
||||
accountComplete && accounts[inputId],
|
||||
realAmount)
|
||||
$: autoMemo = genAutoMemo(payway, briefAccount, realAmount)
|
||||
let lightningAddress;
|
||||
$: lightningAddress = accountComplete ?
|
||||
genAddress(payway, accounts[inputId], realAmount):""
|
||||
$: lightningAddress = accountComplete ? genAddress(payway, extAccount, realAmount):""
|
||||
|
||||
function lnurlEncode(url) {
|
||||
return bech32.encode("LNURL",
|
||||
bech32.toWords(UTF8.setBytesFromString(url)),2048)
|
||||
return bech32.encode("LNURL", bech32.toWords(UTF8.setBytesFromString(url)),2048)
|
||||
}
|
||||
|
||||
function toHexString(byteArray) {
|
||||
@ -69,6 +75,7 @@
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||
}).join('')
|
||||
}
|
||||
|
||||
function genAddress(payway,account,amount) {
|
||||
if (!account || !payway)
|
||||
return ""
|
||||
@ -80,11 +87,12 @@
|
||||
return prefix + account + "@" + payway.id + ".lnurl-pay.me"
|
||||
}
|
||||
|
||||
function genLNURL(payway,account,amount,memo) {
|
||||
function genLNURL(payway,account,amount,memo,isEncrypted) {
|
||||
let params = new URLSearchParams();
|
||||
params.set("mtg","pay");
|
||||
params.set("p",payway.id);
|
||||
params.set("acc",toHexString(UTF8.setBytesFromString(account)))
|
||||
// don't hex-encode bech32-encoded account
|
||||
params.set("acc",isEncrypted? account: toHexString(UTF8.setBytesFromString(account)))
|
||||
params.set("v","1")
|
||||
if (amount) {
|
||||
params.set(payway.currency.toLowerCase(),amount)
|
||||
@ -95,6 +103,7 @@
|
||||
return lnurlEncode("https://lnurl-pay.me/pay?"+params.toString()).toUpperCase()
|
||||
}
|
||||
|
||||
|
||||
let accounts = {};
|
||||
let amounts = {};
|
||||
let memo;
|
||||
@ -102,7 +111,7 @@
|
||||
let lnurl;
|
||||
|
||||
$: lnurl = accountComplete ?
|
||||
genLNURL(payway, accounts[inputId], realAmount, memo) : "";
|
||||
genLNURL(payway, extAccount, realAmount, memo, encrypt) : "";
|
||||
|
||||
const curr = {
|
||||
"UAH":"₴",
|
||||
@ -138,6 +147,7 @@
|
||||
|
||||
{#key payway}
|
||||
<label class="form-label mb-3">{payway.acc}
|
||||
<div class="input-group">
|
||||
<InputMask
|
||||
unmask
|
||||
bind:value={accounts[inputId]}
|
||||
@ -147,6 +157,16 @@
|
||||
inputmode={payway.inputmode||"numeric"}
|
||||
placeholder={payway.placeholder}
|
||||
class="form-control"/>
|
||||
<span class="input-group-text">
|
||||
<div class="form-check-inline form-check input-group-prepend">
|
||||
<input class="form-check-input" type="checkbox" id="cbEncrypt"
|
||||
bind:checked={encrypt}>
|
||||
<label class="form-check-label" for="cbEncrypt">
|
||||
Encrypt
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-label mb-3">
|
||||
Amount: {payway.currency} {payway.min} – {payway.max}
|
||||
@ -243,7 +263,6 @@
|
||||
</CTC>
|
||||
{/if}
|
||||
|
||||
|
||||
</SiteCard>
|
||||
</SiteDeck>
|
||||
|
||||
|
38
src/ecies.js
Normal file
38
src/ecies.js
Normal file
@ -0,0 +1,38 @@
|
||||
const receiverPublicKey = "02d38170b929dc26be2192071756edd5fb94c4e0cf1aec352ce4c8245e635d9bed";
|
||||
|
||||
import {hkdf} from "@noble/hashes/hkdf";
|
||||
import {sha256} from "@noble/hashes/sha256";
|
||||
import * as jscrypto from "jscrypto";
|
||||
import * as secp from "@noble/secp256k1";
|
||||
import { bech32 } from 'bech32';
|
||||
|
||||
let receiverPoint;
|
||||
|
||||
function getReceiverPoint() {
|
||||
if (receiverPoint)
|
||||
return receiverPoint;
|
||||
receiverPoint = secp.Point.fromHex(receiverPublicKey);
|
||||
return receiverPoint;
|
||||
}
|
||||
|
||||
const noIV = jscrypto.Hex.parse("00000000000000000000000000000000");
|
||||
|
||||
export function ecEncrypt(plainText) {
|
||||
const pub = getReceiverPoint();
|
||||
const iv = noIV;
|
||||
const ek = secp.utils.randomPrivateKey();
|
||||
const epk = secp.Point.fromPrivateKey(ek);
|
||||
const sharedSecret = secp.getSharedSecret(ek, pub);
|
||||
const key = hkdf(sha256,new Uint8Array([...epk.toRawBytes(),...sharedSecret]));
|
||||
const jskkey = new jscrypto.Word32Array(key);
|
||||
|
||||
const ed = jscrypto.AES.encrypt(jscrypto.Utf8.parse(plainText),
|
||||
jskkey,{mode:jscrypto.mode.GCM, padding:jscrypto.pad.NoPadding, iv: noIV});
|
||||
const at = jscrypto.mode.GCM.mac(jscrypto.AES,jskkey,noIV,null,ed.cipherText,16);
|
||||
|
||||
const ctbytes = new Uint8Array([...at.toUint8Array(),
|
||||
...epk.toRawBytes(true),
|
||||
...ed.cipherText.toUint8Array()]);
|
||||
const bech = bech32.encode("",bech32.toWords(ctbytes), 1024);
|
||||
return bech.slice(1,-6)
|
||||
}
|
Loading…
Reference in New Issue
Block a user