mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
Mint/verify_outputs_not_already_signed (#377)
* test output duplication in mint * add comments to the db connection reuse
This commit is contained in:
@@ -158,6 +158,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerLightning):
|
||||
|
||||
Args:
|
||||
proofs (List[Proof]): Proofs to add to known secret table.
|
||||
conn: (Optional[Connection], optional): Database connection to reuse. Will create a new one if not given. Defaults to None.
|
||||
"""
|
||||
secrets = set([p.secret for p in proofs])
|
||||
self.secrets_used |= secrets
|
||||
@@ -316,7 +317,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerLightning):
|
||||
await self.crud.update_lightning_invoice(id=id, issued=True, db=self.db)
|
||||
del self.locks[id]
|
||||
|
||||
self._verify_outputs(B_s)
|
||||
await self._verify_outputs(B_s)
|
||||
|
||||
promises = await self._generate_promises(B_s, keyset)
|
||||
logger.trace("generated promises")
|
||||
@@ -479,6 +480,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerLightning):
|
||||
|
||||
# Mark proofs as used and prepare new promises
|
||||
async with get_db_connection(self.db) as conn:
|
||||
# we do this in a single db transaction
|
||||
promises = await self._generate_promises(outputs, keyset, conn)
|
||||
await self._invalidate_proofs(proofs, conn)
|
||||
|
||||
@@ -525,10 +527,15 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerLightning):
|
||||
) -> list[BlindedSignature]:
|
||||
"""Generates a promises (Blind signatures) for given amount and returns a pair (amount, C').
|
||||
|
||||
Important: When a promises is once created it should be considered issued to the user since the user
|
||||
will always be able to restore promises later through the backup restore endpoint. That means that additional
|
||||
checks in the code that might decide not to return these promises should be avoided once this function is
|
||||
called. Only call this function if the transaction is fully validated!
|
||||
|
||||
Args:
|
||||
B_s (List[BlindedMessage]): Blinded secret (point on curve)
|
||||
keyset (Optional[MintKeyset], optional): Which keyset to use. Private keys will be taken from this keyset. Defaults to None.
|
||||
|
||||
conn: (Optional[Connection], optional): Database connection to reuse. Will create a new one if not given. Defaults to None.
|
||||
Returns:
|
||||
list[BlindedSignature]: Generated BlindedSignatures.
|
||||
"""
|
||||
|
||||
@@ -78,7 +78,7 @@ class LedgerVerification(LedgerSpendingConditions, SupportsKeysets, SupportsDb):
|
||||
self._verify_equation_balanced(proofs, outputs)
|
||||
|
||||
# Verify outputs
|
||||
self._verify_outputs(outputs)
|
||||
await self._verify_outputs(outputs)
|
||||
|
||||
# Verify inputs and outputs together
|
||||
if not self._verify_input_output_amounts(proofs, outputs):
|
||||
@@ -87,7 +87,7 @@ class LedgerVerification(LedgerSpendingConditions, SupportsKeysets, SupportsDb):
|
||||
if outputs and not self._verify_output_spending_conditions(proofs, outputs):
|
||||
raise TransactionError("validation of output spending conditions failed.")
|
||||
|
||||
def _verify_outputs(self, outputs: List[BlindedMessage]):
|
||||
async def _verify_outputs(self, outputs: List[BlindedMessage]):
|
||||
"""Verify that the outputs are valid."""
|
||||
# Verify amounts of outputs
|
||||
if not all([self._verify_amount(o.amount) for o in outputs]):
|
||||
@@ -95,6 +95,28 @@ class LedgerVerification(LedgerSpendingConditions, SupportsKeysets, SupportsDb):
|
||||
# verify that only unique outputs were used
|
||||
if not self._verify_no_duplicate_outputs(outputs):
|
||||
raise TransactionError("duplicate outputs.")
|
||||
# verify that outputs have not been signed previously
|
||||
if any(await self._check_outputs_issued_before(outputs)):
|
||||
raise TransactionError("outputs have already been signed before.")
|
||||
|
||||
async def _check_outputs_issued_before(self, outputs: List[BlindedMessage]):
|
||||
"""Checks whether the provided outputs have previously been signed by the mint
|
||||
(which would lead to a duplication error later when trying to store these outputs again).
|
||||
|
||||
Args:
|
||||
outputs (List[BlindedMessage]): Outputs to check
|
||||
|
||||
Returns:
|
||||
result (List[bool]): Whether outputs are already present in the database.
|
||||
"""
|
||||
result = []
|
||||
async with self.db.connect() as conn:
|
||||
for output in outputs:
|
||||
promise = await self.crud.get_promise(
|
||||
B_=output.B_, db=self.db, conn=conn
|
||||
)
|
||||
result.append(False if promise is None else True)
|
||||
return result
|
||||
|
||||
async def _check_proofs_spendable(self, proofs: List[Proof]) -> List[bool]:
|
||||
"""Checks whether the proof was already spent."""
|
||||
|
||||
@@ -142,7 +142,7 @@ async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
||||
# try to spend other proofs with the same outputs again
|
||||
await assert_err(
|
||||
ledger.split(proofs=inputs2, outputs=outputs),
|
||||
"UNIQUE constraint failed: promises.B_b",
|
||||
"outputs have already been signed before.",
|
||||
)
|
||||
|
||||
# try to spend inputs2 again with new outputs
|
||||
@@ -155,6 +155,27 @@ async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
||||
await ledger.split(proofs=inputs2, outputs=outputs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
output_amounts = [128]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
await ledger.mint(outputs, id=invoice.id)
|
||||
|
||||
# now try to mint with the same outputs again
|
||||
invoice2 = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice2.bolt11)
|
||||
|
||||
await assert_err(
|
||||
ledger.mint(outputs, id=invoice2.id),
|
||||
"outputs have already been signed before.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
|
||||
Reference in New Issue
Block a user