Skip to content

Commit

Permalink
Removed is_atomic, sign_dlc with the first key of the active keys…
Browse files Browse the repository at this point in the history
…et for the relevant unit,

`sign_dlc` does not include the unit in the hash,
connected `register_dlc` to the router.
  • Loading branch information
lollerfirst committed Jul 31, 2024
1 parent 1dd7abf commit a01a77f
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 37 deletions.
4 changes: 0 additions & 4 deletions cashu/core/crypto/dlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,28 +65,24 @@ def list_hash(leaves: List[str]) -> List[bytes]:
def sign_dlc(
dlc_root: str,
funding_amount: int,
fa_unit: str,
privkey: PrivateKey,
) -> bytes:
message = (
bytes.fromhex(dlc_root)
+str(funding_amount).encode("utf-8")
+fa_unit.encode("utf-8")
)
message_hash = sha256(message).digest()
return privkey.schnorr_sign(message_hash, None, raw=True)

def verify_dlc_signature(
dlc_root: str,
funding_amount: int,
fa_unit: str,
signature: bytes,
pubkey: PublicKey,
) -> bool:
message = (
bytes.fromhex(dlc_root)
+str(funding_amount).encode("utf-8")
+fa_unit.encode("utf-8")
)
message_hash = sha256(message).digest()
return pubkey.schnorr_verify(message_hash, signature, None, raw=True)
1 change: 0 additions & 1 deletion cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ def __init__(self, **data):
# ------- API: DLC REGISTRATION -------

class PostDlcRegistrationRequest(BaseModel):
atomic: Optional[bool]
registrations: List[DiscreteLogContract]

class PostDlcRegistrationResponse(BaseModel):
Expand Down
54 changes: 37 additions & 17 deletions cashu/mint/db/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,22 @@ async def _unset_melt_quote_pending(
async def _verify_proofs_and_dlc_registrations(
self,
registrations: List[Tuple[DiscreteLogContract, DlcFundingProof]],
is_atomic: bool,
) -> Tuple[List[Tuple[DiscreteLogContract, DlcFundingProof]], List[DlcFundingProof]]:
ok: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
"""
Method to check if proofs are already spent or registrations already registered. If they are not, we
set them as spent and registered respectively
Args:
registrations (List[Tuple[DiscreteLogContract, DlcFundingProof]]): List of registrations.
Returns:
List[Tuple[DiscreteLogContract, DlcFundingProof]]: a list of registered DLCs
List[DlcFundingProof]: a list of errors
"""
checked: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
registered: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
errors: List[DlcFundingProof]= []
if len(registrations) == 0:
logger.trace("Received 0 registrations")
return [], []
logger.trace("_verify_proofs_and_dlc_registrations acquiring lock")
async with self.db.get_connection(lock_table="proofs_used") as conn:
for registration in registrations:
Expand All @@ -245,7 +257,7 @@ async def _verify_proofs_and_dlc_registrations(
assert reg.inputs
await self.db_read._verify_proofs_spendable(reg.inputs, conn)
await self.db_read._verify_dlc_registrable(reg.dlc_root, conn)
ok.append(registration)
checked.append(registration)
except (TokenAlreadySpentError, DlcAlreadyRegisteredError) as e:
logger.trace(f"Proofs already spent for registration {reg.dlc_root}")
errors.append(DlcFundingProof(
Expand All @@ -255,20 +267,28 @@ async def _verify_proofs_and_dlc_registrations(
detail=e.detail
)]
))

# Do not continue if errors on atomic
if is_atomic and len(errors) > 0:
return (ok, errors)

for registration in ok:

for registration in checked:
reg = registration[0]
assert reg.inputs
for p in reg.inputs:
logger.trace(f"Invalidating proof {p.Y}")
await self.crud.invalidate_proof(
proof=p, db=self.db, conn=conn
)
logger.trace(f"Registering DLC {reg.dlc_root}")
await self.crud.store_dlc(reg, self.db, conn)
try:
for p in reg.inputs:
logger.trace(f"Invalidating proof {p.Y}")
await self.crud.invalidate_proof(
proof=p, db=self.db, conn=conn
)

logger.trace(f"Registering DLC {reg.dlc_root}")
await self.crud.store_dlc(reg, self.db, conn)
registered.append(registration)
except Exception as e:
logger.trace(f"Failed to register {reg.dlc_root}: {str(e)}")
errors.append(DlcFundingProof(
dlc_root=reg.dlc_root,
bad_inputs=[DlcBadInput(
index=-1,
detail=str(e)
)]
))
logger.trace("_verify_proofs_and_dlc_registrations lock released")
return (ok, errors)
return (registered, errors)
36 changes: 21 additions & 15 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,8 +1096,13 @@ async def _generate_promises(
return signatures

async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegistrationResponse:
"""Validates and registers DiscreteLogContracts
Args:
request (PostDlcRegistrationRequest): a request formatted following NUT-DLC spec
Returns:
PostDlcRegistrationResponse: Indicating the funded and registered DLCs as well as the errors.
"""
logger.trace("register called")
is_atomic = request.atomic or False
funded: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
errors: List[DlcFundingProof] = []
for registration in request.registrations:
Expand All @@ -1111,15 +1116,22 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi
registration.inputs
)
await self._verify_dlc_amount_threshold(amount_provided, registration.inputs)

# At this point we can put this dlc into the funded list and create a signature for it
# We use the funding proof private key
'''
# We use the first key from the active keyset of the unit specified in the contract.
active_keyset_for_unit = next(
filter(
lambda k: k.active and k.unit == Unit[registration.unit],
self.keysets.values()
)
)
funding_privkey = next(iter(active_keyset_for_unit.private_keys.values()))
signature = sign_dlc(
registration.dlc_root,
registration.funding_amount,
registration.unit,
self.funding_proof_private_key
funding_privkey,
)

funding_proof = DlcFundingProof(
dlc_root=registration.dlc_root,
signature=signature.hex()
Expand All @@ -1132,7 +1144,6 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi
unit=registration.unit,
)
funded.append((dlc, funding_proof))
'''
except (TransactionError, DlcVerificationFail) as e:
logger.error(f"registration {registration.dlc_root} failed")
# Generic Error
Expand All @@ -1150,17 +1161,12 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi
dlc_root=registration.dlc_root,
bad_inputs=e.bad_inputs,
))
# If `atomic` register and there are errors, abort
if is_atomic and len(errors) > 0:
return PostDlcRegistrationResponse(errors=errors)
# Database dance:
funded, db_errors = await self.db_write._verify_proofs_and_dlc_registrations(funded, is_atomic)
# Database dance
registered, db_errors = await self.db_write._verify_proofs_and_dlc_registrations(funded)
errors += db_errors
if is_atomic and len(errors) > 0:
return PostDlcRegistrationResponse(errors=errors)

# ALL OK
# Return funded DLCs and errors
return PostDlcRegistrationResponse(
funded=[f[1] for f in funded],
funded=[reg[1] for reg in registered],
errors=errors if len(errors) > 0 else None,
)
17 changes: 17 additions & 0 deletions cashu/mint/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
PostRestoreResponse,
PostSwapRequest,
PostSwapResponse,
PostDlcRegistrationRequest,
PostDlcRegistrationResponse
)
from ..core.settings import settings
from ..mint.startup import ledger
Expand Down Expand Up @@ -375,3 +377,18 @@ async def restore(payload: PostRestoreRequest) -> PostRestoreResponse:
assert payload.outputs, Exception("no outputs provided.")
outputs, signatures = await ledger.restore(payload.outputs)
return PostRestoreResponse(outputs=outputs, signatures=signatures)

@router.post(
"v1/register",
name="Register",
summary="Register a DLC batch",
response_model=PostDlcRegistrationResponse,
response_description=(
"Two lists describing which DLC were registered and which encountered errors respectively."
)
)
@limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute")
async def register(request: Request, payload: PostDlcRegistrationRequest) -> PostDlcRegistrationResponse:
logger.trace(f"> POST /v1/register: {payload}")
assert len(payload.registrations) > 0, "No registrations provided"
return await ledger.register_dlc(payload)

0 comments on commit a01a77f

Please sign in to comment.