mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-22 03:24:18 +01:00
@@ -1,40 +1,32 @@
|
|||||||
|
# Don't trust me with cryptography.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
|
Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
|
||||||
|
|
||||||
Alice:
|
Alice:
|
||||||
A = a*G
|
A = a*G
|
||||||
return A
|
return A
|
||||||
|
|
||||||
Bob:
|
Bob:
|
||||||
Y = hash_to_curve(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'
|
||||||
|
|
||||||
Alice:
|
Alice:
|
||||||
C' = a*B'
|
C' = a*B'
|
||||||
(= a*Y + a*r*G)
|
(= a*Y + a*r*G)
|
||||||
return C'
|
return C'
|
||||||
|
|
||||||
Bob:
|
Bob:
|
||||||
C = C' - r*A
|
C = C' - r*A
|
||||||
(= C' - a*r*G)
|
(= C' - a*r*G)
|
||||||
(= a*Y)
|
(= a*Y)
|
||||||
return C, secret_message
|
return C, secret_message
|
||||||
|
|
||||||
Alice:
|
Alice:
|
||||||
Y = hash_to_curve(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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
from ecc.curve import Point, secp256k1
|
|
||||||
from ecc.key import gen_keypair
|
|
||||||
|
|
||||||
G = secp256k1.G
|
|
||||||
|
|
||||||
|
|
||||||
def hash_to_curve(secret_msg):
|
def hash_to_curve(secret_msg):
|
||||||
@@ -43,13 +35,15 @@ def hash_to_curve(secret_msg):
|
|||||||
point = None
|
point = None
|
||||||
msg = secret_msg
|
msg = secret_msg
|
||||||
while point is None:
|
while point is None:
|
||||||
x_coord = int(hashlib.sha256(msg).hexdigest().encode("utf-8"), 16)
|
_hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
|
||||||
y_coord = secp256k1.compute_y(x_coord)
|
|
||||||
try:
|
try:
|
||||||
# Fails if the point is not on the curve
|
# We construct compressed pub which has x coordinate encoded with even y
|
||||||
point = Point(x_coord, y_coord, secp256k1)
|
_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:
|
except:
|
||||||
msg = str(x_coord).encode("utf-8")
|
msg = _hash
|
||||||
|
|
||||||
return point
|
return point
|
||||||
|
|
||||||
@@ -57,35 +51,42 @@ def hash_to_curve(secret_msg):
|
|||||||
def step1_bob(secret_msg):
|
def step1_bob(secret_msg):
|
||||||
secret_msg = secret_msg.encode("utf-8")
|
secret_msg = secret_msg.encode("utf-8")
|
||||||
Y = hash_to_curve(secret_msg)
|
Y = hash_to_curve(secret_msg)
|
||||||
r, _ = gen_keypair(secp256k1)
|
r = PrivateKey()
|
||||||
B_ = Y + r * G
|
B_ = Y + r.pubkey
|
||||||
return B_, r
|
return B_, r
|
||||||
|
|
||||||
|
|
||||||
def step2_alice(B_, a):
|
def step2_alice(B_, a):
|
||||||
C_ = a * B_
|
C_ = B_.mult(a)
|
||||||
return C_
|
return C_
|
||||||
|
|
||||||
|
|
||||||
def step3_bob(C_, r, A):
|
def step3_bob(C_, r, A):
|
||||||
C = C_ - r * A
|
C = C_ - A.mult(r)
|
||||||
return C
|
return C
|
||||||
|
|
||||||
|
|
||||||
def verify(a, C, secret_msg):
|
def verify(a, C, secret_msg):
|
||||||
Y = hash_to_curve(secret_msg.encode("utf-8"))
|
Y = hash_to_curve(secret_msg.encode("utf-8"))
|
||||||
return C == a * Y
|
return C == Y.mult(a)
|
||||||
|
|
||||||
|
|
||||||
### Below is a test of a simple positive and negative case
|
### Below is a test of a simple positive and negative case
|
||||||
|
|
||||||
# # Alice private key
|
# # Alice's keys
|
||||||
# a, A = gen_keypair(secp256k1)
|
# a = PrivateKey()
|
||||||
|
# A = a.pubkey
|
||||||
# secret_msg = "test"
|
# secret_msg = "test"
|
||||||
# B_, r = step1_bob(secret_msg)
|
# B_, r = step1_bob(secret_msg)
|
||||||
# C_ = step2_alice(B_, a)
|
# C_ = step2_alice(B_, a)
|
||||||
# C = step3_bob(C_, r, A)
|
# C = step3_bob(C_, r, A)
|
||||||
# print("C:{}, secret_msg:{}".format(C, secret_msg))
|
# print("C:{}, secret_msg:{}".format(C, secret_msg))
|
||||||
|
|
||||||
# assert verify(a, C, secret_msg)
|
# assert verify(a, C, secret_msg)
|
||||||
# assert verify(a, C + 1*G, secret_msg) == False # adding 1*G shouldn't pass
|
# assert verify(a, C + C, secret_msg) == False # adding C twice shouldn't pass
|
||||||
|
# assert verify(a, A, secret_msg) == False # A shouldn't pass
|
||||||
|
|
||||||
|
# # Test operations
|
||||||
|
# b = PrivateKey()
|
||||||
|
# B = b.pubkey
|
||||||
|
# assert -A -A + A == -A # neg
|
||||||
|
# assert B.mult(a) == A.mult(b) # a*B = A*b
|
||||||
|
|||||||
32
core/base.py
32
core/base.py
@@ -4,16 +4,9 @@ from typing import List
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class BasePoint(BaseModel):
|
|
||||||
"""Named BasePoint because it conflicts with ecc.curve.Point"""
|
|
||||||
|
|
||||||
x: int
|
|
||||||
y: int
|
|
||||||
|
|
||||||
|
|
||||||
class Proof(BaseModel):
|
class Proof(BaseModel):
|
||||||
amount: int
|
amount: int
|
||||||
C: BasePoint
|
C: str
|
||||||
secret: str
|
secret: str
|
||||||
reserved: bool = False # whether this proof is reserved for sending
|
reserved: bool = False # whether this proof is reserved for sending
|
||||||
|
|
||||||
@@ -21,22 +14,16 @@ class Proof(BaseModel):
|
|||||||
def from_row(cls, row: Row):
|
def from_row(cls, row: Row):
|
||||||
return cls(
|
return cls(
|
||||||
amount=row[0],
|
amount=row[0],
|
||||||
C=dict(
|
C=row[1],
|
||||||
x=int(row[1]),
|
secret=row[2],
|
||||||
y=int(row[2]),
|
reserved=row[3] or False,
|
||||||
),
|
|
||||||
secret=row[3],
|
|
||||||
reserved=row[4] or False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, d: dict):
|
def from_dict(cls, d: dict):
|
||||||
return cls(
|
return cls(
|
||||||
amount=d["amount"],
|
amount=d["amount"],
|
||||||
C=dict(
|
C=d["C"],
|
||||||
x=int(d["C"]["x"]),
|
|
||||||
y=int(d["C"]["y"]),
|
|
||||||
),
|
|
||||||
secret=d["secret"],
|
secret=d["secret"],
|
||||||
reserved=d["reserved"] or False,
|
reserved=d["reserved"] or False,
|
||||||
)
|
)
|
||||||
@@ -72,21 +59,18 @@ class Invoice(BaseModel):
|
|||||||
|
|
||||||
class BlindedMessage(BaseModel):
|
class BlindedMessage(BaseModel):
|
||||||
amount: int
|
amount: int
|
||||||
B_: BasePoint
|
B_: str
|
||||||
|
|
||||||
|
|
||||||
class BlindedSignature(BaseModel):
|
class BlindedSignature(BaseModel):
|
||||||
amount: int
|
amount: int
|
||||||
C_: BasePoint
|
C_: str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, d: dict):
|
def from_dict(cls, d: dict):
|
||||||
return cls(
|
return cls(
|
||||||
amount=d["amount"],
|
amount=d["amount"],
|
||||||
C_=dict(
|
C_=d["C_"],
|
||||||
x=int(d["C_"]["x"]),
|
|
||||||
y=int(d["C_"]["y"]),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
52
core/secp.py
Normal file
52
core/secp.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
|
# We extend the public key to define some operations on points
|
||||||
|
# Picked from https://github.com/WTRMQDev/secp256k1-zkp-py/blob/master/secp256k1_zkp/__init__.py
|
||||||
|
class PublicKeyExt(PublicKey):
|
||||||
|
def __add__(self, pubkey2):
|
||||||
|
if isinstance(pubkey2, PublicKey):
|
||||||
|
new_pub = PublicKey()
|
||||||
|
new_pub.combine([self.public_key, pubkey2.public_key])
|
||||||
|
return new_pub
|
||||||
|
else:
|
||||||
|
raise TypeError("Cant add pubkey and %s" % pubkey2.__class__)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
serialized = self.serialize()
|
||||||
|
first_byte, remainder = serialized[:1], serialized[1:]
|
||||||
|
# flip odd/even byte
|
||||||
|
first_byte = {b"\x03": b"\x02", b"\x02": b"\x03"}[first_byte]
|
||||||
|
return PublicKey(first_byte + remainder, raw=True)
|
||||||
|
|
||||||
|
def __sub__(self, pubkey2):
|
||||||
|
if isinstance(pubkey2, PublicKey):
|
||||||
|
return self + (-pubkey2)
|
||||||
|
else:
|
||||||
|
raise TypeError("Can't add pubkey and %s" % pubkey2.__class__)
|
||||||
|
|
||||||
|
def mult(self, privkey):
|
||||||
|
if isinstance(privkey, PrivateKey):
|
||||||
|
return self.tweak_mul(privkey.private_key)
|
||||||
|
else:
|
||||||
|
raise TypeError("Can't multiply with non privatekey")
|
||||||
|
|
||||||
|
def __eq__(self, pubkey2):
|
||||||
|
if isinstance(pubkey2, PublicKey):
|
||||||
|
seq1 = self.to_data()
|
||||||
|
seq2 = pubkey2.to_data()
|
||||||
|
return seq1 == seq2
|
||||||
|
else:
|
||||||
|
raise TypeError("Can't compare pubkey and %s" % pubkey2.__class__)
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
return [self.public_key.data[i] for i in range(64)]
|
||||||
|
|
||||||
|
|
||||||
|
# Horrible monkeypatching
|
||||||
|
PublicKey.__add__ = PublicKeyExt.__add__
|
||||||
|
PublicKey.__neg__ = PublicKeyExt.__neg__
|
||||||
|
PublicKey.__sub__ = PublicKeyExt.__sub__
|
||||||
|
PublicKey.mult = PublicKeyExt.mult
|
||||||
|
PublicKey.__eq__ = PublicKeyExt.__eq__
|
||||||
|
PublicKey.to_data = PublicKeyExt.to_data
|
||||||
11
mint/app.py
11
mint/app.py
@@ -5,10 +5,11 @@ from typing import Union
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from ecc.curve import Point, secp256k1
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from secp256k1 import PublicKey
|
||||||
|
|
||||||
import core.settings as settings
|
import core.settings as settings
|
||||||
from core.base import MintPayloads, SplitPayload, MeltPayload, CheckPayload
|
from core.base import MintPayloads, SplitPayload, MeltPayload, CheckPayload
|
||||||
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
|
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
|
||||||
@@ -124,12 +125,8 @@ async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
|
|||||||
amounts = []
|
amounts = []
|
||||||
B_s = []
|
B_s = []
|
||||||
for payload in payloads.blinded_messages:
|
for payload in payloads.blinded_messages:
|
||||||
v = payload.dict()
|
amounts.append(payload.amount)
|
||||||
amounts.append(v["amount"])
|
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
|
||||||
x = int(v["B_"]["x"])
|
|
||||||
y = int(v["B_"]["y"])
|
|
||||||
B_ = Point(x, y, secp256k1)
|
|
||||||
B_s.append(B_)
|
|
||||||
try:
|
try:
|
||||||
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
|
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
|
||||||
return promises
|
return promises
|
||||||
|
|||||||
23
mint/crud.py
23
mint/crud.py
@@ -7,10 +7,8 @@ from core.db import Connection, Database
|
|||||||
|
|
||||||
async def store_promise(
|
async def store_promise(
|
||||||
amount: int,
|
amount: int,
|
||||||
B_x: str,
|
B_: str,
|
||||||
B_y: str,
|
C_: str,
|
||||||
C_x: str,
|
|
||||||
C_y: str,
|
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
):
|
||||||
@@ -18,15 +16,13 @@ async def store_promise(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO promises
|
INSERT INTO promises
|
||||||
(amount, B_x, B_y, C_x, C_y)
|
(amount, B_b, C_b)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
amount,
|
amount,
|
||||||
str(B_x),
|
str(B_),
|
||||||
str(B_y),
|
str(C_),
|
||||||
str(C_x),
|
|
||||||
str(C_y),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,13 +50,12 @@ async def invalidate_proof(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO proofs_used
|
INSERT INTO proofs_used
|
||||||
(amount, C_x, C_y, secret)
|
(amount, C, secret)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
proof.amount,
|
proof.amount,
|
||||||
str(proof.C.x),
|
str(proof.C),
|
||||||
str(proof.C.y),
|
|
||||||
str(proof.secret),
|
str(proof.secret),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import math
|
from core.secp import PrivateKey, PublicKey
|
||||||
from ecc.curve import Point, secp256k1
|
|
||||||
from ecc.key import gen_keypair
|
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Set
|
||||||
from core.base import Proof, BlindedMessage, BlindedSignature, BasePoint
|
from core.base import Proof, BlindedMessage, BlindedSignature
|
||||||
|
|
||||||
import core.b_dhke as b_dhke
|
import core.b_dhke as b_dhke
|
||||||
from core.base import Invoice
|
from core.base import Invoice
|
||||||
@@ -30,49 +28,49 @@ from mint.crud import (
|
|||||||
|
|
||||||
class Ledger:
|
class Ledger:
|
||||||
def __init__(self, secret_key: str, db: str):
|
def __init__(self, secret_key: str, db: str):
|
||||||
self.proofs_used = set()
|
self.proofs_used: Set[str] = set()
|
||||||
|
|
||||||
self.master_key = secret_key
|
self.master_key: str = secret_key
|
||||||
self.keys = self._derive_keys(self.master_key)
|
self.keys: List[PrivateKey] = self._derive_keys(self.master_key)
|
||||||
self.pub_keys = self._derive_pubkeys(self.keys)
|
self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys)
|
||||||
self.db = Database("mint", db)
|
self.db: Database = Database("mint", db)
|
||||||
|
|
||||||
async def load_used_proofs(self):
|
async def load_used_proofs(self):
|
||||||
self.proofs_used = set(await get_proofs_used(db=self.db))
|
self.proofs_used = set(await get_proofs_used(db=self.db))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _derive_keys(master_key):
|
def _derive_keys(master_key: str):
|
||||||
"""Deterministic derivation of keys for 2^n values."""
|
"""Deterministic derivation of keys for 2^n values."""
|
||||||
return {
|
return {
|
||||||
2
|
2
|
||||||
** i: int(
|
** i: PrivateKey(
|
||||||
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
|
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
|
||||||
.hexdigest()
|
.hexdigest()
|
||||||
.encode("utf-8"),
|
.encode("utf-8")[:32],
|
||||||
16,
|
raw=True,
|
||||||
)
|
)
|
||||||
for i in range(MAX_ORDER)
|
for i in range(MAX_ORDER)
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _derive_pubkeys(keys):
|
def _derive_pubkeys(keys: List[PrivateKey]):
|
||||||
return {
|
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
|
||||||
amt: keys[amt] * secp256k1.G for amt in [2**i for i in range(MAX_ORDER)]
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _generate_promises(self, amounts, B_s):
|
async def _generate_promises(self, amounts, B_s):
|
||||||
"""Generates promises that sum to the given amount."""
|
"""Generates promises that sum to the given amount."""
|
||||||
return [
|
return [
|
||||||
await self._generate_promise(amount, Point(B_.x, B_.y, secp256k1))
|
await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
|
||||||
for (amount, B_) in zip(amounts, B_s)
|
for (amount, B_) in zip(amounts, B_s)
|
||||||
]
|
]
|
||||||
|
|
||||||
async def _generate_promise(self, amount: int, B_):
|
async def _generate_promise(self, amount: int, B_: PublicKey):
|
||||||
"""Generates a promise for given amount and returns a pair (amount, C')."""
|
"""Generates a promise for given amount and returns a pair (amount, C')."""
|
||||||
secret_key = self.keys[amount] # Get the correct key
|
secret_key = self.keys[amount] # Get the correct key
|
||||||
C_ = b_dhke.step2_alice(B_, secret_key)
|
C_ = b_dhke.step2_alice(B_, secret_key)
|
||||||
await store_promise(amount, B_x=B_.x, B_y=B_.y, C_x=C_.x, C_y=C_.y, db=self.db)
|
await store_promise(
|
||||||
return BlindedSignature(amount=amount, C_=BasePoint(x=C_.x, y=C_.y))
|
amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
|
||||||
|
)
|
||||||
|
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
|
||||||
|
|
||||||
def _check_spendable(self, proof: Proof):
|
def _check_spendable(self, proof: Proof):
|
||||||
"""Checks whether the proof was already spent."""
|
"""Checks whether the proof was already spent."""
|
||||||
@@ -83,10 +81,12 @@ class Ledger:
|
|||||||
if not self._check_spendable(proof):
|
if not self._check_spendable(proof):
|
||||||
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
||||||
secret_key = self.keys[proof.amount] # Get the correct key to check against
|
secret_key = self.keys[proof.amount] # Get the correct key to check against
|
||||||
C = Point(proof.C.x, proof.C.y, secp256k1)
|
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
||||||
return b_dhke.verify(secret_key, C, proof.secret)
|
return b_dhke.verify(secret_key, C, proof.secret)
|
||||||
|
|
||||||
def _verify_outputs(self, total: int, amount: int, output_data):
|
def _verify_outputs(
|
||||||
|
self, total: int, amount: int, output_data: List[BlindedMessage]
|
||||||
|
):
|
||||||
"""Verifies the expected split was correctly computed"""
|
"""Verifies the expected split was correctly computed"""
|
||||||
fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
|
fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
|
||||||
fst_outputs = amount_split(fst_amt)
|
fst_outputs = amount_split(fst_amt)
|
||||||
@@ -95,16 +95,18 @@ class Ledger:
|
|||||||
given = [o.amount for o in output_data]
|
given = [o.amount for o in output_data]
|
||||||
return given == expected
|
return given == expected
|
||||||
|
|
||||||
def _verify_no_duplicates(self, proofs: List[Proof], output_data):
|
def _verify_no_duplicates(
|
||||||
|
self, proofs: List[Proof], output_data: List[BlindedMessage]
|
||||||
|
):
|
||||||
secrets = [p.secret for p in proofs]
|
secrets = [p.secret for p in proofs]
|
||||||
if len(secrets) != len(list(set(secrets))):
|
if len(secrets) != len(list(set(secrets))):
|
||||||
return False
|
return False
|
||||||
B_xs = [od.B_.x for od in output_data]
|
B_s = [od.B_ for od in output_data]
|
||||||
if len(B_xs) != len(list(set(B_xs))):
|
if len(B_s) != len(list(set(B_s))):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _verify_split_amount(self, amount):
|
def _verify_split_amount(self, amount: int):
|
||||||
"""Split amount like output amount can't be negative or too big."""
|
"""Split amount like output amount can't be negative or too big."""
|
||||||
try:
|
try:
|
||||||
self._verify_amount(amount)
|
self._verify_amount(amount)
|
||||||
@@ -179,7 +181,7 @@ class Ledger:
|
|||||||
# Public methods
|
# Public methods
|
||||||
def get_pubkeys(self):
|
def get_pubkeys(self):
|
||||||
"""Returns public keys for possible amounts."""
|
"""Returns public keys for possible amounts."""
|
||||||
return self.pub_keys
|
return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
|
||||||
|
|
||||||
async def request_mint(self, amount):
|
async def request_mint(self, amount):
|
||||||
"""Returns Lightning invoice and stores it in the db."""
|
"""Returns Lightning invoice and stores it in the db."""
|
||||||
@@ -239,7 +241,7 @@ class Ledger:
|
|||||||
if not all([self._verify_proof(p) for p in proofs]):
|
if not all([self._verify_proof(p) for p in proofs]):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
total = sum([p["amount"] for p in proofs])
|
total = sum([p.amount for p in proofs])
|
||||||
|
|
||||||
if not self._verify_no_duplicates(proofs, output_data):
|
if not self._verify_no_duplicates(proofs, output_data):
|
||||||
raise Exception("duplicate proofs or promises")
|
raise Exception("duplicate proofs or promises")
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ async def m001_initial(db: Database):
|
|||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS promises (
|
CREATE TABLE IF NOT EXISTS promises (
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
B_x TEXT NOT NULL,
|
B_b TEXT NOT NULL,
|
||||||
B_y TEXT NOT NULL,
|
C_b TEXT NOT NULL,
|
||||||
C_x TEXT NOT NULL,
|
|
||||||
C_y TEXT NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE (B_x, B_y)
|
UNIQUE (B_b)
|
||||||
|
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
@@ -32,8 +30,7 @@ async def m001_initial(db: Database):
|
|||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS proofs_used (
|
CREATE TABLE IF NOT EXISTS proofs_used (
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
C_x TEXT NOT NULL,
|
C TEXT NOT NULL,
|
||||||
C_y TEXT NOT NULL,
|
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
|
|
||||||
UNIQUE (secret)
|
UNIQUE (secret)
|
||||||
|
|||||||
44
poetry.lock
generated
44
poetry.lock
generated
@@ -127,40 +127,6 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dataclasses"
|
|
||||||
version = "0.6"
|
|
||||||
description = "A backport of the dataclasses module for Python 3.6"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ecc"
|
|
||||||
version = "0.0.1"
|
|
||||||
description = "Pure Python implementation of an elliptic curve cryptosystem based on FIPS 186-3"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ecc-pycrypto"
|
|
||||||
version = "1.0.0"
|
|
||||||
description = ""
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
develop = false
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
dataclasses = "*"
|
|
||||||
|
|
||||||
[package.source]
|
|
||||||
type = "git"
|
|
||||||
url = "https://github.com/lc6chang/ecc-pycrypto.git"
|
|
||||||
reference = "v1.0.1"
|
|
||||||
resolved_reference = "eb8b8c19a81a52d9cf705d90a597a78cdaf2b6f6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -707,7 +673,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "27d48020dabbc74117941ab884e1794d502d38c1380c777ee0edb203ada3c7b1"
|
content-hash = "8046f708fe138fcdb9c1e39e18e4c466292f183f0d1736ecee66ec4854ad54cc"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
anyio = [
|
anyio = [
|
||||||
@@ -838,14 +804,6 @@ colorama = [
|
|||||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||||
]
|
]
|
||||||
dataclasses = [
|
|
||||||
{file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"},
|
|
||||||
{file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"},
|
|
||||||
]
|
|
||||||
ecc = [
|
|
||||||
{file = "ecc-0.0.1.zip", hash = "sha256:4bbcd46e9963ca37422d3244ab503af9dce95cbd35f676f7f9a4dd6306e23538"},
|
|
||||||
]
|
|
||||||
ecc-pycrypto = []
|
|
||||||
ecdsa = [
|
ecdsa = [
|
||||||
{file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
|
{file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
|
||||||
{file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
|
{file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ license = "MIT"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
pycrypto = "^2.6.1"
|
pycrypto = "^2.6.1"
|
||||||
ecc = "0.0.1"
|
|
||||||
requests = "2.27.1"
|
requests = "2.27.1"
|
||||||
pytest-asyncio = "0.19.0"
|
pytest-asyncio = "0.19.0"
|
||||||
SQLAlchemy = "1.3.24"
|
SQLAlchemy = "1.3.24"
|
||||||
@@ -22,7 +21,6 @@ Jinja2 = "3.0.3"
|
|||||||
MarkupSafe = "2.1.1"
|
MarkupSafe = "2.1.1"
|
||||||
urllib3 = "1.23"
|
urllib3 = "1.23"
|
||||||
Werkzeug = "2.2.2"
|
Werkzeug = "2.2.2"
|
||||||
ecc-pycrypto = {git = "https://github.com/lc6chang/ecc-pycrypto.git", rev = "v1.0.1"}
|
|
||||||
asgiref = "^3.5.2"
|
asgiref = "^3.5.2"
|
||||||
pydantic = "^1.10.2"
|
pydantic = "^1.10.2"
|
||||||
bech32 = "^1.2.0"
|
bech32 = "^1.2.0"
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ async def store_proof(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO proofs
|
INSERT INTO proofs
|
||||||
(amount, C_x, C_y, secret)
|
(amount, C, secret)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
proof.amount,
|
proof.amount,
|
||||||
str(proof.C.x),
|
str(proof.C),
|
||||||
str(proof.C.y),
|
|
||||||
str(proof.secret),
|
str(proof.secret),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -55,13 +54,12 @@ async def invalidate_proof(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO proofs_used
|
INSERT INTO proofs_used
|
||||||
(amount, C_x, C_y, secret)
|
(amount, C, secret)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
proof.amount,
|
proof.amount,
|
||||||
str(proof.C.x),
|
str(proof.C),
|
||||||
str(proof.C.y),
|
|
||||||
str(proof.secret),
|
str(proof.secret),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ async def m001_initial(db: Database):
|
|||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS proofs (
|
CREATE TABLE IF NOT EXISTS proofs (
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
C_x TEXT NOT NULL,
|
C TEXT NOT NULL,
|
||||||
C_y TEXT NOT NULL,
|
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
|
|
||||||
UNIQUE (secret)
|
UNIQUE (secret)
|
||||||
@@ -31,8 +30,7 @@ async def m001_initial(db: Database):
|
|||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS proofs_used (
|
CREATE TABLE IF NOT EXISTS proofs_used (
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
C_x TEXT NOT NULL,
|
C TEXT NOT NULL,
|
||||||
C_y TEXT NOT NULL,
|
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
|
|
||||||
UNIQUE (secret)
|
UNIQUE (secret)
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import random
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from ecc.curve import Point, secp256k1
|
from core.secp import PublicKey
|
||||||
|
|
||||||
import core.b_dhke as b_dhke
|
import core.b_dhke as b_dhke
|
||||||
from core.base import (
|
from core.base import (
|
||||||
BasePoint,
|
|
||||||
BlindedMessage,
|
BlindedMessage,
|
||||||
MintPayloads,
|
MintPayloads,
|
||||||
Proof,
|
Proof,
|
||||||
@@ -29,7 +28,8 @@ class LedgerAPI:
|
|||||||
def _get_keys(url):
|
def _get_keys(url):
|
||||||
resp = requests.get(url + "/keys").json()
|
resp = requests.get(url + "/keys").json()
|
||||||
return {
|
return {
|
||||||
int(amt): Point(val["x"], val["y"], secp256k1) for amt, val in resp.items()
|
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||||
|
for amt, val in resp.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -46,10 +46,9 @@ class LedgerAPI:
|
|||||||
"""Returns proofs of promise from promises."""
|
"""Returns proofs of promise from promises."""
|
||||||
proofs = []
|
proofs = []
|
||||||
for promise, (r, secret) in zip(promises, secrets):
|
for promise, (r, secret) in zip(promises, secrets):
|
||||||
C_ = Point(promise.C_.x, promise.C_.y, secp256k1)
|
C_ = PublicKey(bytes.fromhex(promise.C_), raw=True)
|
||||||
C = b_dhke.step3_bob(C_, r, self.keys[promise.amount])
|
C = b_dhke.step3_bob(C_, r, self.keys[promise.amount])
|
||||||
c_point = BasePoint(x=C.x, y=C.y)
|
proof = Proof(amount=promise.amount, C=C.serialize().hex(), secret=secret)
|
||||||
proof = Proof(amount=promise.amount, C=c_point, secret=secret)
|
|
||||||
proofs.append(proof)
|
proofs.append(proof)
|
||||||
return proofs
|
return proofs
|
||||||
|
|
||||||
@@ -63,13 +62,14 @@ class LedgerAPI:
|
|||||||
payloads: MintPayloads = MintPayloads()
|
payloads: MintPayloads = MintPayloads()
|
||||||
secrets = []
|
secrets = []
|
||||||
rs = []
|
rs = []
|
||||||
for i, amount in enumerate(amounts):
|
for amount in amounts:
|
||||||
secret = str(random.getrandbits(128))
|
secret = str(random.getrandbits(128))
|
||||||
secrets.append(secret)
|
secrets.append(secret)
|
||||||
B_, r = b_dhke.step1_bob(secret)
|
B_, r = b_dhke.step1_bob(secret)
|
||||||
rs.append(r)
|
rs.append(r)
|
||||||
blinded_point = BasePoint(x=str(B_.x), y=str(B_.y))
|
payload: BlindedMessage = BlindedMessage(
|
||||||
payload: BlindedMessage = BlindedMessage(amount=amount, B_=blinded_point)
|
amount=amount, B_=B_.serialize().hex()
|
||||||
|
)
|
||||||
payloads.blinded_messages.append(payload)
|
payloads.blinded_messages.append(payload)
|
||||||
promises_dict = requests.post(
|
promises_dict = requests.post(
|
||||||
self.url + "/mint",
|
self.url + "/mint",
|
||||||
@@ -94,9 +94,8 @@ class LedgerAPI:
|
|||||||
secret = str(random.getrandbits(128))
|
secret = str(random.getrandbits(128))
|
||||||
B_, r = b_dhke.step1_bob(secret)
|
B_, r = b_dhke.step1_bob(secret)
|
||||||
secrets.append((r, secret))
|
secrets.append((r, secret))
|
||||||
blinded_point = BasePoint(x=str(B_.x), y=str(B_.y))
|
|
||||||
payload: BlindedMessage = BlindedMessage(
|
payload: BlindedMessage = BlindedMessage(
|
||||||
amount=output_amt, B_=blinded_point
|
amount=output_amt, B_=B_.serialize().hex()
|
||||||
)
|
)
|
||||||
payloads.blinded_messages.append(payload)
|
payloads.blinded_messages.append(payload)
|
||||||
split_payload = SplitPayload(proofs=proofs, amount=amount, output_data=payloads)
|
split_payload = SplitPayload(proofs=proofs, amount=amount, output_data=payloads)
|
||||||
|
|||||||
Reference in New Issue
Block a user