From 8e98d89a0b4852cd3da6d53053f622c2a195b561 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 30 May 2024 16:01:41 +0400 Subject: [PATCH] cl/ptx-note-proofs: start data modelling input and outputs --- coordination-layer/note.py | 19 +++++++-- coordination-layer/notes.org | 4 ++ coordination-layer/partial_transaction.py | 51 +++++++++++++++++------ coordination-layer/test_transfer.py | 32 +++++++++----- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/coordination-layer/note.py b/coordination-layer/note.py index c966609..37eba21 100644 --- a/coordination-layer/note.py +++ b/coordination-layer/note.py @@ -10,7 +10,7 @@ from crypto import ( hash_to_curve, ) -from constraints import Constraint +from constraints import Constraint, Proof # TODO: is this used? @@ -39,6 +39,8 @@ def balance_commitment(value: Field, tx_rand: Field, funge: Point): class InnerNote: value: Field unit: str + # TODO: inner notes should hold commitments to constraints. + # Constraints themselves will be stored in a key-value store birth_constraint: Constraint death_constraints: list[Constraint] state: Field @@ -63,12 +65,23 @@ class InnerNote: assert isinstance(self.nonce, Field), f"nonce is {type(self.nonce)}" assert isinstance(self.rand, Field), f"rand is {type(self.rand)}" - def r(self, index: int): - prf("CL_NOTE_COMM_RAND", self.rand, index) + def verify_death(self, death_cm: Field, death_proof: Proof) -> bool: + constraint = [d for d in self.death_constraints if d.hash() == deah_cm][0] + # TODO: verifying the death constraint should include a commitment to the + # partial transaction. + return constraint.verify(death_proof) + + def verify_birth(self, birth_proof: Proof) -> bool: + # TODO: Should verifying the birth constraint include a commitment + # to the partial transaction? + return self.birth_constraint.verify(birth_proof) def verify_value(self) -> bool: return 0 <= self.value and value <= 2**64 + def r(self, index: int): + return prf("CL_NOTE_COMM_RAND", self.rand, index) + @property def fungibility_domain(self) -> Field: """The fungibility domain of this note""" diff --git a/coordination-layer/notes.org b/coordination-layer/notes.org index 2e29f42..4302f56 100644 --- a/coordination-layer/notes.org +++ b/coordination-layer/notes.org @@ -1,5 +1,9 @@ * Open Issues +** Note.verify() should take a proof + +We should have two variants, verifyDeath(deathProof) verifyBirth(birthProof) + ** provided commitment to zero may removing the blinding of the pederson commitment Since you can subtract the randomness from the commitment to get just the binding part. diff --git a/coordination-layer/partial_transaction.py b/coordination-layer/partial_transaction.py index c358f09..f580bfb 100644 --- a/coordination-layer/partial_transaction.py +++ b/coordination-layer/partial_transaction.py @@ -1,9 +1,30 @@ from dataclasses import dataclass + +from constraints import Proof from note import PublicNote, SecretNote from crypto import Field, Point +@dataclass +class InputNote: + note: SecretNote + death_proof: Proof + + def verify(self): + return self.note.verify_death(self.death_proof) + + +@dataclass +class OutputNote: + note: PublicNote + birth_proof: Proof + + def verify(self): + return self.note.verify_birth(self.birth_proof) + + +# TODO: is this used? @dataclass class Output: note: PublicNote @@ -15,20 +36,23 @@ class Output: @dataclass(unsafe_hash=True) class PartialTransaction: - inputs: list[SecretNote] - outputs: list[Output] + inputs: list[InputNote] + outputs: list[OutputNote] rand: Field def verify(self) -> bool: - raise NotImplementedError() + valid_inputs = all(i.verify() for i in self.inputs) + valid_outputs = all(o.verify() for o in self.outputs) + return valid_inputs and valid_output def balance(self) -> Point: - output_balance = sum((n.balance for n in self.outputs), start=Point.zero()) - # TODO: once again just mentioning this inefficiency. we are converting our private - # inputs to public inputs to compute the balance, so we don't need an Output class, - # we can directly compute the balance commitment from the public output notes. + output_balance = sum( + (n.note.balance(self.rand) for n in self.outputs), + start=Point.zero(), + ) input_balance = sum( - (n.to_public().balance(self.rand) for n in self.inputs), start=Point.zero() + (n.note.to_public().balance(self.rand) for n in self.inputs), + start=Point.zero(), ) return output_balance + input_balance.negate() @@ -37,12 +61,13 @@ class PartialTransaction: return sum(outputs.blinding(self.rand)) - sum(outputs.blinding(self.rand)) def zero(self) -> Field: - output_zero = sum((n.zero for n in self.outputs), start=Point.zero()) - # TODO: once again just mentioning this inefficiency. we are converting our private - # inputs to public inputs to compute the zero commitment, so we don't need an Output class, - # we can directly compute the zero commitment from the public output notes. + output_zero = sum( + (n.note.zero(self.rand) for n in self.outputs), + start=Point.zero(), + ) input_zero = sum( - (n.to_public().zero(self.rand) for n in self.inputs), start=Point.zero() + (n.note.to_public().zero(self.rand) for n in self.inputs), + start=Point.zero(), ) return output_zero + input_zero.negate() diff --git a/coordination-layer/test_transfer.py b/coordination-layer/test_transfer.py index f4597de..cc7b88b 100644 --- a/coordination-layer/test_transfer.py +++ b/coordination-layer/test_transfer.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from crypto import Field, prf from note import InnerNote, PublicNote, SecretNote, nf_pk -from partial_transaction import PartialTransaction, Output +from partial_transaction import PartialTransaction, InputNote, OutputNote from transaction_bundle import TransactionBundle import constraints @@ -50,17 +50,29 @@ class TestTransfer(TestCase): ), nf_pk=bob.pk, ) - tx_output = Output( - note=bobs_note, - # TODO: why do we need an Output struct if we can - # compute the balance and zero commitment form the - # PublicNote itself? - balance=bobs_note.balance(tx_rand), - zero=bobs_note.zero(tx_rand), - ) + # tx_output = Output( + # note=bobs_note, + # # TODO: why do we need an Output struct if we can + # # compute the balance and zero commitment form the + # # PublicNote itself? + # balance=bobs_note.balance(tx_rand), + # zero=bobs_note.zero(tx_rand), + # ) ptx = PartialTransaction( - inputs=[alices_note], outputs=[tx_output], rand=tx_rand + inputs=[ + InputNote( + note=alices_note, + death_proof=constraints.Vacuous().prove(), + ) + ], + outputs=[ + OutputNote( + note=bobs_note, + birth_proof=constraints.Vacuous().prove(), + ) + ], + rand=tx_rand, ) bundle = TransactionBundle(bundle=[ptx])