mirror of
https://github.com/lnbits/lnbits.git
synced 2025-09-28 12:56:16 +02:00
feat: secret stuff
This commit is contained in:
@@ -6,7 +6,7 @@ Alice:
|
|||||||
A = a*G
|
A = a*G
|
||||||
return A
|
return A
|
||||||
Bob:
|
Bob:
|
||||||
Y = hash_to_point(secret_message)
|
Y = hash_to_curve(secret_message)
|
||||||
r = random blinding factor
|
r = random blinding factor
|
||||||
B'= Y + r*G
|
B'= Y + r*G
|
||||||
return B'
|
return B'
|
||||||
@@ -20,7 +20,7 @@ C = C' - r*A
|
|||||||
(= a*Y)
|
(= a*Y)
|
||||||
return C, secret_message
|
return C, secret_message
|
||||||
Alice:
|
Alice:
|
||||||
Y = hash_to_point(secret_message)
|
Y = hash_to_curve(secret_message)
|
||||||
C == a*Y
|
C == a*Y
|
||||||
If true, C must have originated from Alice
|
If true, C must have originated from Alice
|
||||||
"""
|
"""
|
||||||
@@ -30,28 +30,23 @@ import hashlib
|
|||||||
from secp256k1 import PrivateKey, PublicKey
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
def hash_to_point(secret_msg):
|
def hash_to_curve(message: bytes):
|
||||||
"""Generates x coordinate from the message hash and checks if the point lies on the curve.
|
"""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."""
|
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
|
||||||
point = None
|
point = None
|
||||||
msg = secret_msg
|
msg_to_hash = message
|
||||||
while point is None:
|
while point is None:
|
||||||
_hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
|
|
||||||
try:
|
try:
|
||||||
# We construct compressed pub which has x coordinate encoded with even y
|
_hash = hashlib.sha256(msg_to_hash).digest()
|
||||||
_hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
|
point = PublicKey(b"\x02" + _hash, raw=True)
|
||||||
_hash[0] = 0x02 # set first byte to represent even y coord
|
|
||||||
_hash = bytes(_hash)
|
|
||||||
point = PublicKey(_hash, raw=True)
|
|
||||||
except:
|
except:
|
||||||
msg = _hash
|
msg_to_hash = _hash
|
||||||
|
|
||||||
return point
|
return point
|
||||||
|
|
||||||
|
|
||||||
def step1_alice(secret_msg):
|
def step1_alice(secret_msg):
|
||||||
secret_msg = secret_msg.encode("utf-8")
|
secret_msg = secret_msg
|
||||||
Y = hash_to_point(secret_msg)
|
Y = hash_to_curve(secret_msg)
|
||||||
r = PrivateKey()
|
r = PrivateKey()
|
||||||
B_ = Y + r.pubkey
|
B_ = Y + r.pubkey
|
||||||
return B_, r
|
return B_, r
|
||||||
@@ -68,7 +63,7 @@ def step3_alice(C_, r, A):
|
|||||||
|
|
||||||
|
|
||||||
def verify(a, C, secret_msg):
|
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)
|
return C == Y.mult(a)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import base64
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
from .core.b_dhke import verify
|
from .core.b_dhke import verify
|
||||||
@@ -32,14 +33,16 @@ def derive_pubkeys(keys: List[PrivateKey]):
|
|||||||
# async required?
|
# async required?
|
||||||
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
|
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
|
||||||
"""Verifies that the proof of promise was issued by this ledger."""
|
"""Verifies that the proof of promise was issued by this ledger."""
|
||||||
if proof.secret in proofs_used:
|
# if proof.secret in proofs_used:
|
||||||
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
# raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
||||||
|
|
||||||
secret_key = derive_keys(master_prvkey)[
|
secret_key = derive_keys(master_prvkey)[
|
||||||
proof.amount
|
proof.amount
|
||||||
] # Get the correct key to check against
|
] # Get the correct key to check against
|
||||||
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
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:
|
if validMintSig != True:
|
||||||
raise Exception(f"tokens not valid. Secret: {proof.secret}")
|
raise Exception(f"tokens not valid. Secret: {proof.secret}")
|
||||||
|
|
||||||
|
40
lnbits/extensions/cashu/static/js/base64.js
Normal file
40
lnbits/extensions/cashu/static/js/base64.js
Normal file
@@ -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;
|
||||||
|
|
||||||
|
}({}));
|
@@ -1,15 +1,19 @@
|
|||||||
async function hashToCurve(secretMessage) {
|
async function hashToCurve(secretMessage) {
|
||||||
|
console.log(
|
||||||
|
'### secretMessage',
|
||||||
|
nobleSecp256k1.utils.bytesToHex(secretMessage)
|
||||||
|
)
|
||||||
let point
|
let point
|
||||||
while (!point) {
|
while (!point) {
|
||||||
const hash = await nobleSecp256k1.utils.sha256(secretMessage)
|
const hash = await nobleSecp256k1.utils.sha256(secretMessage)
|
||||||
|
const hashHex = nobleSecp256k1.utils.bytesToHex(hash)
|
||||||
|
const pointX = '02' + hashHex
|
||||||
|
console.log('### pointX', pointX)
|
||||||
try {
|
try {
|
||||||
point = nobleSecp256k1.Point.fromHex(hash)
|
point = nobleSecp256k1.Point.fromHex(pointX)
|
||||||
|
console.log('### point', point.toHex())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error(error)
|
secretMessage = await nobleSecp256k1.utils.sha256(secretMessage)
|
||||||
// const x = bytesToNumber(hash) + ''
|
|
||||||
// const msg = await nobleSecp256k1.utils.sha256(x)
|
|
||||||
secretMessage = await nobleSecp256k1.utils.sha256(hash)
|
|
||||||
// secretMessage = nobleSecp256k1.utils.bytesToHex(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return point
|
return point
|
||||||
@@ -26,6 +30,7 @@ async function step1Bob(secretMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function step3Bob(C_, r, A) {
|
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
|
return C
|
||||||
}
|
}
|
||||||
|
@@ -240,7 +240,8 @@ page_container %}
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="q-mb-lg">
|
<div v-else class="q-mb-lg">
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<strong>Amount:</strong> {{ sellData.invoice.sat }} <strong>sats</strong><br />
|
<strong>Amount:</strong> {{ sellData.invoice.sat }}
|
||||||
|
<strong>sats</strong><br />
|
||||||
<strong>Description:</strong> {{ sellData.invoice.description }}<br />
|
<strong>Description:</strong> {{ sellData.invoice.description }}<br />
|
||||||
<strong>Expire date:</strong> {{ sellData.invoice.expireDate }}<br />
|
<strong>Expire date:</strong> {{ sellData.invoice.expireDate }}<br />
|
||||||
<strong>Expired:</strong> {{ sellData.invoice.expired }}<br />
|
<strong>Expired:</strong> {{ sellData.invoice.expired }}<br />
|
||||||
@@ -315,6 +316,7 @@ page_container %}
|
|||||||
|
|
||||||
mintId: '',
|
mintId: '',
|
||||||
mintName: '',
|
mintName: '',
|
||||||
|
keys: '',
|
||||||
buyOrders: [],
|
buyOrders: [],
|
||||||
buyData: {
|
buyData: {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
@@ -782,9 +784,14 @@ page_container %}
|
|||||||
const secrets = []
|
const secrets = []
|
||||||
const randomBlindingFactors = []
|
const randomBlindingFactors = []
|
||||||
for (let i = 0; i < amounts.length; i++) {
|
for (let i = 0; i < amounts.length; i++) {
|
||||||
// const secret = bytesToNumber(nobleSecp256k1.utils.randomBytes(32)) + ''
|
// const secret = nobleSecp256k1.utils.randomBytes(32)
|
||||||
const secret = nobleSecp256k1.utils.randomBytes(32)
|
const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
|
||||||
secrets.push(secret)
|
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)
|
const {B_, randomBlindingFactor} = await step1Bob(secret)
|
||||||
randomBlindingFactors.push(randomBlindingFactor)
|
randomBlindingFactors.push(randomBlindingFactor)
|
||||||
blindedMessages.push({amount: amounts[i], B_: B_})
|
blindedMessages.push({amount: amounts[i], B_: B_})
|
||||||
@@ -798,28 +805,6 @@ page_container %}
|
|||||||
status: 'pending'
|
status: 'pending'
|
||||||
}
|
}
|
||||||
return newTokens
|
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 () {
|
checkInvoice: function () {
|
||||||
@@ -869,6 +854,57 @@ page_container %}
|
|||||||
|
|
||||||
sellTokens: async function () {
|
sellTokens: async function () {
|
||||||
console.log('#### sell tokens')
|
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 () {
|
storeBuyOrders: function () {
|
||||||
@@ -929,12 +965,19 @@ page_container %}
|
|||||||
this.mintName = this.$q.localStorage.getItem('cashu.name')
|
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(
|
this.buyOrders = JSON.parse(
|
||||||
localStorage.getItem('cashu.buyOrders') || '[]'
|
localStorage.getItem('cashu.buyOrders') || '[]'
|
||||||
)
|
)
|
||||||
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
|
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
|
||||||
console.log('### buyOrders',this.buyOrders)
|
console.log('### buyOrders', this.buyOrders)
|
||||||
console.table('### tokens',this.tokens)
|
console.table('### tokens', this.tokens)
|
||||||
console.log('#### this.mintId', this.mintId)
|
console.log('#### this.mintId', this.mintId)
|
||||||
console.log('#### this.mintName', this.mintName)
|
console.log('#### this.mintName', this.mintName)
|
||||||
|
|
||||||
@@ -945,4 +988,5 @@ page_container %}
|
|||||||
<script src="{{ url_for('cashu_static', path='js/noble-secp256k1.js') }}"></script>
|
<script src="{{ url_for('cashu_static', path='js/noble-secp256k1.js') }}"></script>
|
||||||
<script src="{{ url_for('cashu_static', path='js/utils.js') }}"></script>
|
<script src="{{ url_for('cashu_static', path='js/utils.js') }}"></script>
|
||||||
<script src="{{ url_for('cashu_static', path='js/dhke.js') }}"></script>
|
<script src="{{ url_for('cashu_static', path='js/dhke.js') }}"></script>
|
||||||
|
<script src="{{ url_for('cashu_static', path='js/base64.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user