Mint/verify_outputs_not_already_signed (#377)

* test output duplication in mint

* add comments to the db connection reuse
This commit is contained in:
callebtc
2023-12-03 19:26:07 +01:00
committed by GitHub
parent 6c8b1a858f
commit 7d4ed959e3
3 changed files with 55 additions and 5 deletions

View File

@@ -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.
"""

View File

@@ -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."""

View File

@@ -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)