From 791c5eb41df1fb63392dcf77093b249343bf4923 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 10 Oct 2022 22:56:03 +0300 Subject: [PATCH] feat: secret stuff --- lnbits/extensions/cashu/core/b_dhke.py | 37 +++---- lnbits/extensions/cashu/mint_helper.py | 9 +- lnbits/extensions/cashu/static/js/base64.js | 40 +++++++ lnbits/extensions/cashu/static/js/dhke.js | 19 ++-- .../cashu/templates/cashu/wallet.html | 100 +++++++++++++----- 5 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 lnbits/extensions/cashu/static/js/base64.js diff --git a/lnbits/extensions/cashu/core/b_dhke.py b/lnbits/extensions/cashu/core/b_dhke.py index be9a141bb..8855481cf 100644 --- a/lnbits/extensions/cashu/core/b_dhke.py +++ b/lnbits/extensions/cashu/core/b_dhke.py @@ -6,7 +6,7 @@ Alice: A = a*G return A Bob: -Y = hash_to_point(secret_message) +Y = hash_to_curve(secret_message) r = random blinding factor B'= Y + r*G return B' @@ -20,7 +20,7 @@ C = C' - r*A (= a*Y) return C, secret_message Alice: -Y = hash_to_point(secret_message) +Y = hash_to_curve(secret_message) C == a*Y If true, C must have originated from Alice """ @@ -30,28 +30,23 @@ import hashlib from secp256k1 import PrivateKey, PublicKey -def hash_to_point(secret_msg): - """Generates x coordinate from the message hash and checks if the point lies on the curve. - If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" - point = None - msg = secret_msg - while point is None: - _hash = hashlib.sha256(msg).hexdigest().encode("utf-8") - try: - # We construct compressed pub which has x coordinate encoded with even y - _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes - _hash[0] = 0x02 # set first byte to represent even y coord - _hash = bytes(_hash) - point = PublicKey(_hash, raw=True) - except: - msg = _hash - +def hash_to_curve(message: bytes): + """Generates a point from the message hash and checks if the point lies on the curve. + If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" + point = None + msg_to_hash = message + while point is None: + try: + _hash = hashlib.sha256(msg_to_hash).digest() + point = PublicKey(b"\x02" + _hash, raw=True) + except: + msg_to_hash = _hash return point def step1_alice(secret_msg): - secret_msg = secret_msg.encode("utf-8") - Y = hash_to_point(secret_msg) + secret_msg = secret_msg + Y = hash_to_curve(secret_msg) r = PrivateKey() B_ = Y + r.pubkey return B_, r @@ -68,7 +63,7 @@ def step3_alice(C_, r, A): def verify(a, C, secret_msg): - Y = hash_to_point(secret_msg.encode("utf-8")) + Y = hash_to_curve(secret_msg) return C == Y.mult(a) diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py index 5c96d8310..3892c67f6 100644 --- a/lnbits/extensions/cashu/mint_helper.py +++ b/lnbits/extensions/cashu/mint_helper.py @@ -1,4 +1,5 @@ import hashlib +import base64 from typing import List, Set from .core.b_dhke import verify @@ -32,14 +33,16 @@ def derive_pubkeys(keys: List[PrivateKey]): # async required? async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof): """Verifies that the proof of promise was issued by this ledger.""" - if proof.secret in proofs_used: - raise Exception(f"tokens already spent. Secret: {proof.secret}") + # if proof.secret in proofs_used: + # raise Exception(f"tokens already spent. Secret: {proof.secret}") secret_key = derive_keys(master_prvkey)[ proof.amount ] # Get the correct key to check against C = PublicKey(bytes.fromhex(proof.C), raw=True) - validMintSig = verify(secret_key, C, proof.secret) + secret = base64.urlsafe_b64decode(proof.secret) + print('### secret', secret) + validMintSig = verify(secret_key, C, secret) if validMintSig != True: raise Exception(f"tokens not valid. Secret: {proof.secret}") diff --git a/lnbits/extensions/cashu/static/js/base64.js b/lnbits/extensions/cashu/static/js/base64.js new file mode 100644 index 000000000..2cd19a836 --- /dev/null +++ b/lnbits/extensions/cashu/static/js/base64.js @@ -0,0 +1,40 @@ +function unescapeBase64Url (str) { + return (str + '==='.slice((str.length + 3) % 4)) + .replace(/-/g, '+') + .replace(/_/g, '/') + } + + function escapeBase64Url (str) { + return str.replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, '') + } + + const uint8ToBase64 = (function (exports) { + 'use strict'; + + var fromCharCode = String.fromCharCode; + var encode = function encode(uint8array) { + var output = []; + + for (var i = 0, length = uint8array.length; i < length; i++) { + output.push(fromCharCode(uint8array[i])); + } + + return btoa(output.join('')); + }; + + var asCharCode = function asCharCode(c) { + return c.charCodeAt(0); + }; + + var decode = function decode(chars) { + return Uint8Array.from(atob(chars), asCharCode); + }; + + exports.decode = decode; + exports.encode = encode; + + return exports; + + }({})); \ No newline at end of file diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js index 959ba6f1c..c9e2d146e 100644 --- a/lnbits/extensions/cashu/static/js/dhke.js +++ b/lnbits/extensions/cashu/static/js/dhke.js @@ -1,15 +1,19 @@ async function hashToCurve(secretMessage) { + console.log( + '### secretMessage', + nobleSecp256k1.utils.bytesToHex(secretMessage) + ) let point while (!point) { const hash = await nobleSecp256k1.utils.sha256(secretMessage) + const hashHex = nobleSecp256k1.utils.bytesToHex(hash) + const pointX = '02' + hashHex + console.log('### pointX', pointX) try { - point = nobleSecp256k1.Point.fromHex(hash) + point = nobleSecp256k1.Point.fromHex(pointX) + console.log('### point', point.toHex()) } catch (error) { - // console.error(error) - // const x = bytesToNumber(hash) + '' - // const msg = await nobleSecp256k1.utils.sha256(x) - secretMessage = await nobleSecp256k1.utils.sha256(hash) - // secretMessage = nobleSecp256k1.utils.bytesToHex(msg) + secretMessage = await nobleSecp256k1.utils.sha256(secretMessage) } } return point @@ -26,6 +30,7 @@ async function step1Bob(secretMessage) { } function step3Bob(C_, r, A) { - const C = C_.subtract(A.multiply(r)) + const rInt = BigInt(r) + const C = C_.subtract(A.multiply(rInt)) return C } diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html index b3ccbc8b9..cef80d424 100644 --- a/lnbits/extensions/cashu/templates/cashu/wallet.html +++ b/lnbits/extensions/cashu/templates/cashu/wallet.html @@ -240,7 +240,8 @@ page_container %}
{% raw %} - Amount: {{ sellData.invoice.sat }} sats
+ Amount: {{ sellData.invoice.sat }} + sats
Description: {{ sellData.invoice.description }}
Expire date: {{ sellData.invoice.expireDate }}
Expired: {{ sellData.invoice.expired }}
@@ -315,6 +316,7 @@ page_container %} mintId: '', mintName: '', + keys: '', buyOrders: [], buyData: { amount: 0, @@ -782,9 +784,14 @@ page_container %} const secrets = [] const randomBlindingFactors = [] for (let i = 0; i < amounts.length; i++) { - // const secret = bytesToNumber(nobleSecp256k1.utils.randomBytes(32)) + '' - const secret = nobleSecp256k1.utils.randomBytes(32) - secrets.push(secret) + // const secret = nobleSecp256k1.utils.randomBytes(32) + const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003') + const encodedSecret = uint8ToBase64.encode(secret) + console.log('### encodedSecret', encodedSecret) + const decodedSecret = uint8ToBase64.decode(encodedSecret) + const hexSecret = nobleSecp256k1.utils.bytesToHex(decodedSecret) + console.log('### decodedSecret', hexSecret) + secrets.push(encodedSecret) const {B_, randomBlindingFactor} = await step1Bob(secret) randomBlindingFactors.push(randomBlindingFactor) blindedMessages.push({amount: amounts[i], B_: B_}) @@ -798,28 +805,6 @@ page_container %} status: 'pending' } return newTokens - // console.log('### payloadsJson.payloads', payloadsJson.payloads) - // const promises = await mintApi.mint(payloadsJson.payloads, paymentHash) - // if (promises.error) { - // throw new Error(promises.error) - // } - // return this._constructProofs(promises, randomBlindingFactors, secrets) - }, - - _constructProofs: function (promises, randomBlindingFactors, secrets) { - return promises.map((p, i) => { - const C_ = nobleSecp256k1.Point.fromHex(p['C_']) - const A = this.keys[p.amount] - const C = step3Bob( - C_, - randomBlindingFactors[i], - nobleSecp256k1.Point.fromHex(A) - ).toHex() - return { - amount: p.amount, - C: {C, secret: secrets[i]} - } - }) }, checkInvoice: function () { @@ -869,6 +854,57 @@ page_container %} sellTokens: async function () { console.log('#### sell tokens') + const amount = this.sellData.invoice.sat + const token = this.tokens + .filter(t => t.promises?.length) + .find(t => t.promises.find(b => b.amount === amount)) + console.log('### token', token) + if (token) { + const promiseIndex = token.promises + .map(p => `${p.amount}`) + .indexOf(`${amount}`) + const promise = token.promises[promiseIndex] + console.log('### promise', promise) + + const secret = token.secrets[promiseIndex] + const randomBlindingFactor = token.randomBlindingFactors[promiseIndex] + + const C_ = nobleSecp256k1.Point.fromHex(promise['C_']) + const A = this.keys[promise.amount] // todo + + console.log('#### C_', C_) + console.log('#### A', A) + + const C = step3Bob( + C_, + randomBlindingFactor, + nobleSecp256k1.Point.fromHex(A) + ) + + const proofs = [ + { + amount, + secret, + C: C.toHex(true) + } + ] + + const payload = { + proofs, + amount, + invoice: this.sellData.bolt11 + } + console.log('#### payload', JSON.stringify(payload)) + } + }, + + fetchMintKeys: async function () { + const {data} = await LNbits.api.request( + 'GET', + `/cashu/api/v1/cashu/${this.mintId}/keys` + ) + this.keys = data + localStorage.setItem('cashu.keys', JSON.stringify(data)) }, storeBuyOrders: function () { @@ -929,12 +965,19 @@ page_container %} this.mintName = this.$q.localStorage.getItem('cashu.name') } + const keysJson = localStorage.getItem('cashu.keys') + if (!keysJson) { + this.fetchMintKeys() + } else { + this.keys = JSON.parse(keysJson) + } + this.buyOrders = JSON.parse( localStorage.getItem('cashu.buyOrders') || '[]' ) this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]') - console.log('### buyOrders',this.buyOrders) - console.table('### tokens',this.tokens) + console.log('### buyOrders', this.buyOrders) + console.table('### tokens', this.tokens) console.log('#### this.mintId', this.mintId) console.log('#### this.mintName', this.mintName) @@ -945,4 +988,5 @@ page_container %} + {% endblock %}