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",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "svelte-app",
|
"name": "lnurl-pay.me",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@imask/svelte": "^6.1.0",
|
"@imask/svelte": "^6.1.0",
|
||||||
|
"@noble/hashes": "^0.5.9",
|
||||||
|
"@noble/secp256k1": "^1.4.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
"jscrypto": "^1.0.2",
|
||||||
"kjua": "^0.9.0",
|
"kjua": "^0.9.0",
|
||||||
"sirv-cli": "^1.0.0",
|
"sirv-cli": "^1.0.0",
|
||||||
"sveltestrap": "^5.4.0",
|
"sveltestrap": "^5.4.0",
|
||||||
@ -74,6 +77,16 @@
|
|||||||
"svelte": ">=3.0.0"
|
"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": {
|
"node_modules/@polka/url": {
|
||||||
"version": "1.0.0-next.15",
|
"version": "1.0.0-next.15",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
||||||
@ -629,6 +642,14 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/kjua": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
||||||
@ -1220,6 +1241,16 @@
|
|||||||
"imask": "^6.1.0"
|
"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": {
|
"@polka/url": {
|
||||||
"version": "1.0.0-next.15",
|
"version": "1.0.0-next.15",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
||||||
@ -1660,6 +1691,11 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true
|
"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": {
|
"kjua": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz",
|
||||||
|
@ -21,7 +21,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@imask/svelte": "^6.1.0",
|
"@imask/svelte": "^6.1.0",
|
||||||
|
"@noble/hashes": "^0.5.9",
|
||||||
|
"@noble/secp256k1": "^1.4.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
"jscrypto": "^1.0.2",
|
||||||
"kjua": "^0.9.0",
|
"kjua": "^0.9.0",
|
||||||
"sirv-cli": "^1.0.0",
|
"sirv-cli": "^1.0.0",
|
||||||
"sveltestrap": "^5.4.0",
|
"sveltestrap": "^5.4.0",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import InputMask from './InputMask.svelte';
|
import InputMask from './InputMask.svelte';
|
||||||
|
|
||||||
import payways from './payways.js';
|
import payways from './payways.js';
|
||||||
|
|
||||||
import QR from './QR.svelte';
|
import QR from './QR.svelte';
|
||||||
|
|
||||||
import CTC from './CTC.svelte';
|
import CTC from './CTC.svelte';
|
||||||
@ -17,7 +18,8 @@
|
|||||||
import UTF8 from 'utf-8'
|
import UTF8 from 'utf-8'
|
||||||
|
|
||||||
import PayFlow from './PayFlow.svelte';
|
import PayFlow from './PayFlow.svelte';
|
||||||
|
import { ecEncrypt } from './ecies.js';
|
||||||
|
|
||||||
let payway = payways[0];
|
let payway = payways[0];
|
||||||
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
@ -28,8 +30,12 @@
|
|||||||
let inputId;
|
let inputId;
|
||||||
let amountMask;
|
let amountMask;
|
||||||
let realAmount;
|
let realAmount;
|
||||||
|
let extAccount;
|
||||||
|
let briefAccount;
|
||||||
|
let encrypt = false;
|
||||||
|
|
||||||
$: inputId = payway.iid||payway.id;
|
$: inputId = payway.iid||payway.id;
|
||||||
|
$: accountReady = accountComplete && accounts[inputId]
|
||||||
$: amountMask = {
|
$: amountMask = {
|
||||||
mask: Number, scale:2,
|
mask: Number, scale:2,
|
||||||
min: payway.min, max:payway.max,
|
min: payway.min, max:payway.max,
|
||||||
@ -39,6 +45,10 @@
|
|||||||
|
|
||||||
$: realAmount = amounts[payway.id] && (
|
$: realAmount = amounts[payway.id] && (
|
||||||
Math.max(Math.min(amounts[payway.id], payway.max),payway.min))
|
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) {
|
function genAutoMemo(payway,account,amount) {
|
||||||
if (!account)
|
if (!account)
|
||||||
@ -52,16 +62,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let autoMemo;
|
let autoMemo;
|
||||||
$: autoMemo = genAutoMemo(payway,
|
$: autoMemo = genAutoMemo(payway, briefAccount, realAmount)
|
||||||
accountComplete && accounts[inputId],
|
|
||||||
realAmount)
|
|
||||||
let lightningAddress;
|
let lightningAddress;
|
||||||
$: lightningAddress = accountComplete ?
|
$: lightningAddress = accountComplete ? genAddress(payway, extAccount, realAmount):""
|
||||||
genAddress(payway, accounts[inputId], realAmount):""
|
|
||||||
|
|
||||||
function lnurlEncode(url) {
|
function lnurlEncode(url) {
|
||||||
return bech32.encode("LNURL",
|
return bech32.encode("LNURL", bech32.toWords(UTF8.setBytesFromString(url)),2048)
|
||||||
bech32.toWords(UTF8.setBytesFromString(url)),2048)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toHexString(byteArray) {
|
function toHexString(byteArray) {
|
||||||
@ -69,6 +75,7 @@
|
|||||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
||||||
}).join('')
|
}).join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
function genAddress(payway,account,amount) {
|
function genAddress(payway,account,amount) {
|
||||||
if (!account || !payway)
|
if (!account || !payway)
|
||||||
return ""
|
return ""
|
||||||
@ -80,11 +87,12 @@
|
|||||||
return prefix + account + "@" + payway.id + ".lnurl-pay.me"
|
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();
|
let params = new URLSearchParams();
|
||||||
params.set("mtg","pay");
|
params.set("mtg","pay");
|
||||||
params.set("p",payway.id);
|
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")
|
params.set("v","1")
|
||||||
if (amount) {
|
if (amount) {
|
||||||
params.set(payway.currency.toLowerCase(),amount)
|
params.set(payway.currency.toLowerCase(),amount)
|
||||||
@ -95,6 +103,7 @@
|
|||||||
return lnurlEncode("https://lnurl-pay.me/pay?"+params.toString()).toUpperCase()
|
return lnurlEncode("https://lnurl-pay.me/pay?"+params.toString()).toUpperCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let accounts = {};
|
let accounts = {};
|
||||||
let amounts = {};
|
let amounts = {};
|
||||||
let memo;
|
let memo;
|
||||||
@ -102,7 +111,7 @@
|
|||||||
let lnurl;
|
let lnurl;
|
||||||
|
|
||||||
$: lnurl = accountComplete ?
|
$: lnurl = accountComplete ?
|
||||||
genLNURL(payway, accounts[inputId], realAmount, memo) : "";
|
genLNURL(payway, extAccount, realAmount, memo, encrypt) : "";
|
||||||
|
|
||||||
const curr = {
|
const curr = {
|
||||||
"UAH":"₴",
|
"UAH":"₴",
|
||||||
@ -138,15 +147,26 @@
|
|||||||
|
|
||||||
{#key payway}
|
{#key payway}
|
||||||
<label class="form-label mb-3">{payway.acc}
|
<label class="form-label mb-3">{payway.acc}
|
||||||
<InputMask
|
<div class="input-group">
|
||||||
unmask
|
<InputMask
|
||||||
bind:value={accounts[inputId]}
|
unmask
|
||||||
bind:isComplete={accountComplete}
|
bind:value={accounts[inputId]}
|
||||||
imask={payway.imask||{mask:/.*/}}
|
bind:isComplete={accountComplete}
|
||||||
autocomplete={payway.autocomplete}
|
imask={payway.imask||{mask:/.*/}}
|
||||||
inputmode={payway.inputmode||"numeric"}
|
autocomplete={payway.autocomplete}
|
||||||
placeholder={payway.placeholder}
|
inputmode={payway.inputmode||"numeric"}
|
||||||
class="form-control"/>
|
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>
|
||||||
<label class="form-label mb-3">
|
<label class="form-label mb-3">
|
||||||
Amount: {payway.currency} {payway.min} – {payway.max}
|
Amount: {payway.currency} {payway.min} – {payway.max}
|
||||||
@ -243,7 +263,6 @@
|
|||||||
</CTC>
|
</CTC>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
</SiteCard>
|
</SiteCard>
|
||||||
</SiteDeck>
|
</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