From 7ba69caccb314dc6c1338166cfcc9041365cb861 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 14 Jun 2024 11:56:38 -0400 Subject: [PATCH] drop python cl spec --- coordination-layer/README.md | 25 --- coordination-layer/__init__.py | 0 coordination-layer/balance_commitment.py | 20 --- coordination-layer/constraints/__init__.py | 3 - coordination-layer/constraints/bigger.py | 21 --- coordination-layer/constraints/constraint.py | 17 -- .../constraints/noir_constraint.py | 105 ------------ coordination-layer/constraints/test_bigger.py | 14 -- .../constraints/test_noir_constraint.py | 20 --- coordination-layer/constraints/vacuous.py | 18 --- coordination-layer/crypto.py | 108 ------------- coordination-layer/noir/.gitignore | 4 - coordination-layer/noir/Nargo.toml | 2 - coordination-layer/noir/README.md | 18 --- .../noir/crates/bigger/Nargo.toml | 7 - .../noir/crates/bigger/src/main.nr | 19 --- coordination-layer/note.py | 151 ------------------ coordination-layer/notes.org | 146 ----------------- coordination-layer/partial_transaction.py | 62 ------- coordination-layer/state.py | 23 --- coordination-layer/test_balance_commitment.py | 30 ---- coordination-layer/test_crypto.py | 33 ---- coordination-layer/test_transfer.py | 73 --------- coordination-layer/transaction_bundle.py | 20 --- 24 files changed, 939 deletions(-) delete mode 100644 coordination-layer/README.md delete mode 100644 coordination-layer/__init__.py delete mode 100644 coordination-layer/balance_commitment.py delete mode 100644 coordination-layer/constraints/__init__.py delete mode 100644 coordination-layer/constraints/bigger.py delete mode 100644 coordination-layer/constraints/constraint.py delete mode 100644 coordination-layer/constraints/noir_constraint.py delete mode 100644 coordination-layer/constraints/test_bigger.py delete mode 100644 coordination-layer/constraints/test_noir_constraint.py delete mode 100644 coordination-layer/constraints/vacuous.py delete mode 100644 coordination-layer/crypto.py delete mode 100644 coordination-layer/noir/.gitignore delete mode 100644 coordination-layer/noir/Nargo.toml delete mode 100644 coordination-layer/noir/README.md delete mode 100644 coordination-layer/noir/crates/bigger/Nargo.toml delete mode 100644 coordination-layer/noir/crates/bigger/src/main.nr delete mode 100644 coordination-layer/note.py delete mode 100644 coordination-layer/notes.org delete mode 100644 coordination-layer/partial_transaction.py delete mode 100644 coordination-layer/state.py delete mode 100644 coordination-layer/test_balance_commitment.py delete mode 100644 coordination-layer/test_crypto.py delete mode 100644 coordination-layer/test_transfer.py delete mode 100644 coordination-layer/transaction_bundle.py diff --git a/coordination-layer/README.md b/coordination-layer/README.md deleted file mode 100644 index 3539c7e..0000000 --- a/coordination-layer/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Coordination Layer - -This module provides the executable specifications of the Coordination Layer (CL). - - -## Setup - -We are currently experimenting with the Noir Language. In order to run the specification, you will need to install Noir. - -Follow the instructions here: -https://noir-lang.org/docs/getting_started/installation/ - -Verify the installation by running - -```bash -noirup -``` - -## Tests - -From the repository root run: - -```bash -python -m unittest discover -v coordination-layer -``` diff --git a/coordination-layer/__init__.py b/coordination-layer/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/coordination-layer/balance_commitment.py b/coordination-layer/balance_commitment.py deleted file mode 100644 index 8ff08f8..0000000 --- a/coordination-layer/balance_commitment.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -This module holds the logic for building and verifying homomorphic balance commitments. -""" - -from constraints import Constraint -from crypto import Field, Point, prf, hash_to_curve, pederson_commit, _str_to_vec - - -def balance_commitment(value: Field, blinding: Field, unit: Point): - return pederson_commit(value, blinding, unit) - - -def fungibility_domain(unit: str, birth_cm: Field) -> Point: - """The fungibility domain of this note""" - return hash_to_curve("CL_NOTE_NULL", birth_cm, *_str_to_vec(unit)) - - -def blinding(tx_rand: Field, nf_pk: Field) -> Field: - """Blinding factor used in balance commitments""" - return prf("CL_NOTE_BAL_BLIND", tx_rand, nf_pk) diff --git a/coordination-layer/constraints/__init__.py b/coordination-layer/constraints/__init__.py deleted file mode 100644 index 217297c..0000000 --- a/coordination-layer/constraints/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .constraint import Constraint, Proof - -from .vacuous import Vacuous diff --git a/coordination-layer/constraints/bigger.py b/coordination-layer/constraints/bigger.py deleted file mode 100644 index 036be8e..0000000 --- a/coordination-layer/constraints/bigger.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass - -from .noir_constraint import NoirConstraint, NoirProof - - -@dataclass -class Bigger: - """ - The statement "I know an `x` that is bigger than `y`". - - `y` is a public parameter provided when the constraint is initialized - - `x` is a secret parameter provided at proving time - """ - - y: int - _noir = NoirConstraint("bigger") - - def prove(self, x: int) -> NoirProof: - return self._noir.prove({"x": str(x), "y": str(self.y)}) - - def verify(self, proof: NoirProof): - return self._noir.verify({"y": str(self.y)}, proof) diff --git a/coordination-layer/constraints/constraint.py b/coordination-layer/constraints/constraint.py deleted file mode 100644 index 1f0d0a8..0000000 --- a/coordination-layer/constraints/constraint.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module defines the Constraint interface. - -Constraints are the predicates that must be satisfied in order to destroy or create a note. - -The logic of a constraint is implemented in a ZK Circuit, and then wrapped in a python interface -for interacting with the rest of the the system. -""" - - -class Proof: - pass - - -class Constraint: - def hash(self) -> bytes: - raise NotImplementedError() diff --git a/coordination-layer/constraints/noir_constraint.py b/coordination-layer/constraints/noir_constraint.py deleted file mode 100644 index 0cbe286..0000000 --- a/coordination-layer/constraints/noir_constraint.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -This module provides the interface for loading, proving and verifying constraints written in noir. - -The assumptions of this module: -- noir constraints are defined as a noir package in `./noir/crates//` -- ./noir is relative to this file - -For ergonomics, one should provide python wrappers that understands the API of -the corresponding constraint. -""" - -from dataclasses import dataclass -from pathlib import Path - -import sh -import portalocker -import tempfile -import toml - - -from constraints import Proof - -NOIR_DIR = Path(__file__).resolve().parent.parent / "noir" -LOCK_FILE = NOIR_DIR / ".CL.lock" -CONSTRAINTS_DIR = NOIR_DIR / "crates" - -NARGO = sh.Command("nargo") - - -@dataclass -class NoirProof(Proof): - proof: str - - -class NoirConstraint: - """ - Provides a wrapper around the `nargo` command for interacting with a constraint - written in Noir. - - E.g. NoirConstraint("bigger") corresponds to the noir circuit defined - in "./noir/crates/bigger/". - - Calling API methods on this object will map to shelling out to `nargo` to execute - the relevant `nargo` action. - - Efforts are taken to make this wrapper thread safe.To prevent any race - conditions we limit one action at any one time across the `./noir` directory. - """ - - def __init__(self, name: str): - self.name = name - assert self.noir_package_dir.exists() and self.noir_package_dir.is_dir() - self._prepare() - - @property - def noir_package_dir(self): - return CONSTRAINTS_DIR / self.name - - def prove(self, params: dict) -> NoirProof: - """ - Attempts to prove the noir constraint with the given paramaters. - 1. Write the paramaters to the noir Prover.toml file - 2. execute `nargo prove` - 3. Retreive the proof written by nargo. - - Returns the NoirProof containing the proof of the statment. - """ - with portalocker.TemporaryFileLock(LOCK_FILE): - with open(self.noir_package_dir / "Prover.toml", "w") as prover_f: - toml.dump(params, prover_f) - - prove_res = self._nargo("prove", _return_cmd=True) - assert prove_res.exit_code == 0 - - with open(NOIR_DIR / "proofs" / f"{self.name}.proof", "r") as proof: - return NoirProof(proof.read()) - - def verify(self, params: dict, proof: NoirProof): - """ - Attempts to verify a proof given the public paramaters. - 1. Write the public paramaters to the Verifier.toml - 2. Write the proof to the location nargo expects it - 3. Execute `nargo verify` - 4. Check the process exit code. - """ - with portalocker.TemporaryFileLock(LOCK_FILE): - with open(self.noir_package_dir / "Verifier.toml", "w") as verifier_f: - toml.dump(params, verifier_f) - - with open(NOIR_DIR / "proofs" / f"{self.name}.proof", "w") as proof_file: - proof_file.write(proof.proof) - verify_res = self._nargo("verify", _ok_code=[0, 1], _return_cmd=True) - return verify_res.exit_code == 0 - - def _nargo(self, *args, **kwargs): - return NARGO(*args, **kwargs, _cwd=self.noir_package_dir) - - def _prepare(self): - """ - Verify that the Noir circuit is well defined. - """ - check = self._nargo("check", _return_cmd=True) - assert check.exit_code == 0 - compile = self._nargo("compile", _return_cmd=True) - assert compile.exit_code == 0 diff --git a/coordination-layer/constraints/test_bigger.py b/coordination-layer/constraints/test_bigger.py deleted file mode 100644 index 076a951..0000000 --- a/coordination-layer/constraints/test_bigger.py +++ /dev/null @@ -1,14 +0,0 @@ -from unittest import TestCase - -from .bigger import Bigger - - -class TestBigger(TestCase): - def test_bigger(self): - bigger = Bigger(3) - proof = bigger.prove(5) - bigger.verify(proof) - - # If we try to reuse the proof for a different Bigger instance, it fails - bigger_4 = Bigger(4) - assert not bigger_4.verify(proof) diff --git a/coordination-layer/constraints/test_noir_constraint.py b/coordination-layer/constraints/test_noir_constraint.py deleted file mode 100644 index 4b0c026..0000000 --- a/coordination-layer/constraints/test_noir_constraint.py +++ /dev/null @@ -1,20 +0,0 @@ -from unittest import TestCase - -from .noir_constraint import NoirConstraint - - -class TestNoirCoinstraint(TestCase): - def test_bigger(self): - # simple constraint that proves we know a number bigger than the provided - # public input. - bigger = NoirConstraint("bigger") - - # x is the secret input, y is the public input - proof = bigger.prove({"x": "5", "y": "3"}) - - # The proof that we know an `x` that is bigger than `y` should verify - # Note, we must provide the public input that was used in the proof. - assert bigger.verify({"y": "3"}, proof) - - # If we change the public input, the proof fails to verify. - assert not bigger.verify({"y": "4"}, proof) diff --git a/coordination-layer/constraints/vacuous.py b/coordination-layer/constraints/vacuous.py deleted file mode 100644 index 91756d1..0000000 --- a/coordination-layer/constraints/vacuous.py +++ /dev/null @@ -1,18 +0,0 @@ -from constraints import Constraint, Proof -from crypto import Field - - -class Vacuous(Constraint): - """ - This is the empty constraint, it return true for any proof - """ - - def hash(self): - # chosen by a fair 2**64 sided die. - return Field(14500592324922987342) - - def prove(self) -> Proof: - return Proof() - - def verify(self, _proof: Proof): - return True diff --git a/coordination-layer/crypto.py b/coordination-layer/crypto.py deleted file mode 100644 index af6ca29..0000000 --- a/coordination-layer/crypto.py +++ /dev/null @@ -1,108 +0,0 @@ -from py_ecc import bn128 -from keum import grumpkin -import poseidon - - -# !Important! The crypto primitives here must be in agreement with the proving system -# E.g. if you are using noir with the Barretenberg, we must use the Grumpkin curve. - -# Point = grumpkin.AffineWeierstrass -# Field = grumpkin.Fq - -Point = bn128.FQ -Field = bn128.Point2D[FQ] - - -def fake_algebraic_hash(data) -> Field: - """ - HACK: we'll fake the algebraic hash using sha256(data) % Field.ORDER - """ - assert all(isinstance(d, Field) for d in data), f"{data}\n{[type(d) for d in data]}" - data = b"".join(d.v.to_bytes(256 // 8) for d in data) - - from hashlib import sha256 - - return Field(int(sha256(data).hexdigest(), 16)) - - -def build_poseidon(): - h = poseidon.Poseidon( - p=Field.ORDER, - security_level=128, - alpha=5, - input_rate=3, - t=9, - ) - - # TODO: this is hacks on hacks to make poseidon take in arbitrary input length. - # Fix is to implement a sponge as described in section 2.1 of - # https://eprint.iacr.org/2019/458.pdf - def inner(data): - - digest = 0 - for i in range(0, len(data), h.input_rate - 1): - digest = h.run_hash([digest, *data[i : i + h.input_rate - 1]]) - return Field(int(digest)) - - return inner - - -# HASH = build_poseidon() -HASH = fake_algebraic_hash - - -def prf(domain, *elements) -> Field: - return HASH([*_str_to_vec(domain), *elements]) - - -def hash_to_curve(domain, *elements) -> Point: - # HACK: we don't currently have a proper hash_to_curve implementation - # so we hack the Point.random() function. - # - # Point.random() calls into the global `random` module to generate a - # point. We will seed the random module with the result of hashing the - # elements and then call Point.random() to retreive the point - # corresponding to the mentioned elements. - - r = prf(f"HASH_TO_CURVE_{domain}", *elements) - - import random - - random.seed(r.v) - return Point.random() - - -def comm(*elements): - """ - Returns a commitment to the sequence of elements. - - The commitmtent can be opened at index 0..len(elements) - """ - raise NotImplementedError() - - -def pederson_commit(value: Field, blinding: Field, domain: Point) -> Point: - return Point.generator().mul(value) + domain.mul(blinding) - - -def merkle_root(data) -> Field: - data = _pad_to_power_of_2(data) - nodes = [CRH(d) for d in data] - while len(nodes) > 1: - nodes = [CRH(nodes[i], nodes[i + 1]) for i in range(0, len(nodes), 2)] - - return nodes[0] - - -def _pad_to_power_of_2(data): - import math - - max_lower_bound = int(math.log2(len(data))) - if 2**max_lower_bound == len(data): - return data - to_pad = 2 ** (max_lower_bound + 1) - len(data) - return data + [Field.zero()] * to_pad - - -def _str_to_vec(s): - return [Field(ord(c)) for c in s] diff --git a/coordination-layer/noir/.gitignore b/coordination-layer/noir/.gitignore deleted file mode 100644 index 438a1ad..0000000 --- a/coordination-layer/noir/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -crates/*/Prover.toml -crates/*/Verifier.toml -proofs/*.proof -target/* diff --git a/coordination-layer/noir/Nargo.toml b/coordination-layer/noir/Nargo.toml deleted file mode 100644 index a0865ab..0000000 --- a/coordination-layer/noir/Nargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[workspace] -members = ["crates/bigger"] diff --git a/coordination-layer/noir/README.md b/coordination-layer/noir/README.md deleted file mode 100644 index 90f93ee..0000000 --- a/coordination-layer/noir/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Noir Circuits - -this directory holds all the circuits written in Noir, used by the CL specification. - -Each circuit is it's own nargo package under the `crates/` directory. - -## Creating a new circuit - -1. inside `crates/`, run `nargo new ` -2. update `./Nargo.toml` to include the new circuit in the workspace - -## Testing circuits - -Under `./noir`, simple run. - -``` -nargo test -``` diff --git a/coordination-layer/noir/crates/bigger/Nargo.toml b/coordination-layer/noir/crates/bigger/Nargo.toml deleted file mode 100644 index 4034dd3..0000000 --- a/coordination-layer/noir/crates/bigger/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bigger" -type = "bin" -authors = [""] -compiler_version = ">=0.22.0" - -[dependencies] diff --git a/coordination-layer/noir/crates/bigger/src/main.nr b/coordination-layer/noir/crates/bigger/src/main.nr deleted file mode 100644 index 476e71f..0000000 --- a/coordination-layer/noir/crates/bigger/src/main.nr +++ /dev/null @@ -1,19 +0,0 @@ -fn main(x: u32, y: pub u32) { - assert(x > y); -} - -#[test] -fn test_bigger() { - main(3, 2); -} - - -#[test(should_fail)] -fn test_equal() { - main(2, 2); -} - -#[test(should_fail)] -fn test_smaller() { - main(1, 2); -} diff --git a/coordination-layer/note.py b/coordination-layer/note.py deleted file mode 100644 index 3d73158..0000000 --- a/coordination-layer/note.py +++ /dev/null @@ -1,151 +0,0 @@ -from dataclasses import dataclass - -from crypto import ( - Field, - Point, - prf, - pederson_commit, - _str_to_vec, - merkle_root, - hash_to_curve, -) - -from constraints import Constraint, Proof - - -def nf_pk(nf_sk) -> Field: - return prf("CL_NOTE_NF", nf_sk) - - -def balance_commitment(value: Field, tx_rand: Field, funge: Point): - return pederson_commit(value, tx_rand, funge) - - -@dataclass(unsafe_hash=True) -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 - nonce: Field - rand: Field # source of randomness for note commitment - - def __post_init__(self): - if isinstance(self.value, int): - self.value = Field(self.value) - assert isinstance(self.value, Field), f"value is {type(self.value)}" - assert isinstance(self.unit, str), f"unit is {type(self.unit)}" - assert isinstance( - self.birth_constraint, Constraint - ), f"birth_constraint is {type(self.birth_constraint)}" - assert isinstance( - self.death_constraints, list - ), f"death_constraints is {type(self.death_constraints)}" - assert all( - isinstance(d, Constraint) for d in self.death_constraints - ), f"{[type(d) for d in self.death_constraints]}" - assert isinstance(self.state, Field), f"state is {type(self.state)}" - assert isinstance(self.nonce, Field), f"nonce is {type(self.nonce)}" - assert isinstance(self.rand, Field), f"rand is {type(self.rand)}" - - def verify_death(self, death_cm: Field, death_proof: Proof) -> bool: - constraint = [d for d in self.death_constraints if d.hash() == death_cm] - if len(constraint) == 0: - # given commitment was not one of the allowed death constraints - return False - - constraint = constraint[0] - - # TODO: verifying the death constraint should include a commitment to the - # partial transaction so that the death constraint can make statements - # regarding the entire 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) -> Point: - """The fungibility domain of this note""" - return hash_to_curve( - "CL_NOTE_NULL", self.birth_constraint.hash(), *_str_to_vec(self.unit) - ) - - def death_constraints_root(self) -> Field: - """ - Returns the merkle root over the set of death constraints - """ - return merkle_root(self.death_constraints) - - -@dataclass(unsafe_hash=True) -class PublicNote: - note: InnerNote - nf_pk: Field - - def blinding(self, tx_rand: Field) -> Field: - """Blinding factor used in balance commitments""" - return prf("CL_NOTE_BAL_BLIND", tx_rand, self.note.nonce, self.nf_pk) - - def balance(self, tx_rand): - """ - Returns the pederson commitment to the notes value. - """ - return balance_commitment( - self.note.value, self.blinding(tx_rand), self.note.fungibility_domain - ) - - def zero(self, tx_rand): - """ - Returns the pederson commitment to the notes value. - """ - return balance_commitment( - Field.zero(), self.blinding(tx_rand), self.note.fungibility_domain - ) - - def commit(self) -> Field: - # blinding factors between data elems ensure no information is leaked in merkle paths - return merkle_root( - self.note.r(0), - self.note.birth_constraint.hash(), - self.note.r(1), - self.note.death_constraints_root(), - self.note.r(2), - self.note.value, - self.note.r(3), - self.note.unit, - self.note.r(4), - self.note.state, - self.note.r(5), - self.note.nonce, - self.note.r(6), - self.nf_pk, - ) - - -@dataclass(unsafe_hash=True) -class SecretNote: - note: InnerNote - nf_sk: Field - - def to_public(self) -> PublicNote: - return PublicNote(note=self.note, nf_pk=nf_pk(self.nf_sk)) - - def nullifier(self): - """ - The nullifier that must be provided when spending this note along - with a proof that the nf_sk used to compute the nullifier corresponds - to the nf_pk in the public note commitment. - """ - return prf("NULLIFIER", self.nonce, self.nf_sk) diff --git a/coordination-layer/notes.org b/coordination-layer/notes.org deleted file mode 100644 index 1e247d0..0000000 --- a/coordination-layer/notes.org +++ /dev/null @@ -1,146 +0,0 @@ -* Open Issues - -** Do we need both Public, Secret and Inner Note? - -Can't we simply have a Note (currently InnerNote) and the partial transaction will introduce -a wrapper over the Note struct that will have the extra fields necessary for using notes within -a transaction system: - -#+begin_src python - - - # ----- note.py ----- - - @dataclass(unsafe_hash=True) - class Note: - value: Field - unit: str - birth_constraint: Constraint - death_constraints: list[Constraint] - state: Field - nonce: Field - rand: Field - - def verify_death(self, death_cm: Field, death_proof: Proof) -> bool: - pass - - def verify_birth(self, birth_proof: Proof) -> bool: - pass - - def verify_value(self) -> bool: - pass - - def fungibility_domain(self) -> Field: - pass - - # ----- partial_transaction.py ----- - - @dataclass - class InputNote: - note: Note - nullifier: Note - death_cm: Field - death_proof: Proof - balance_cm: Point - - @dataclass - class OutputNote: - note: PublicNote - birth_proof: Proof - balance_cm: Point - -#+end_src - - -** 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. - -Ok, lets deal with these zero commitments. - -In place of zero commitments, we can have the solver prove that the value part of a pederson -commitment is zero. - - - -** Rename transaction randonmness to tx_rand and commitment randomness to cm_rand - -Currently they are both called "rand" and it's confusing. - -** Why does PartialTransaction have a "blinding"? - -I believe it should only have a derived balance field. - -** Poseidon Constants for Grumpkin Field - -Generating the poseidon constants for the Grumpkin curve is very slow, like takes 1 minute. - -I need to pre-generate these to make the curve work. - -- I tried the other large curve where the params are already generated and it is still slow. Need to dig into this. - -** Solvers need the value of a note in order to solve it - -- do users provide a merkle proof showing the value is legit? - -- merkle proofs also reveal positional information. -- if we are to reveal partial information to solvers - we will need to blind the other leaf nodes of the tree: - - Note = VALUE || UNIT || ... - - com = ..root.. - / \ - h(h(V), h(U)) ... - / \ - h(VALUE) h(UNIT) - | | - VALUE UNIT - - Revealing the value's merkle path would reveal h(UNIT) and since UNIT is well known, h(UNIT) is also easily computable. - - Thus each component of the Note should have a blinding factor - Note = (r1, VALUE) || (r1, UNIT) || .... - -** Transferring requires recipients sk - -We need to be able to create a partial transaction where we don't know the output note's nullifier. - -What we need: A public note + balance commitment - -balance commitment is computed as - -``` -value <- known by transaction builder -unit <- known by transaction builder -blinding <- PRF_r(nullifier) !!!! <-- this is where the issue is -funge <- hash_to_curve(Unit) -balance_commit <- pedersen_commit(value, blinding, funge) -``` - -Why is the blinding a PRF of the nullifier? Can't we simply use the randomness of the transaction to derive a blinding factor? - -*** How does zcash solve this? - -They provide specific randomness for this when building the transaction. - - -** Note Unit should be a string hashed to curve point when needed. -We want human readable units (i.e. "ETH" "NMO") - -I've gone and denoted it as bytes. - - -* Solved -** Note.verify() should take a proof - -We should have two variants, verifyDeath(deathProof) verifyBirth(birthProof) - - -** Do we need note.randomness? - -It's currently used in the note commitment. But perhaps it's not really necessary? - -- it allows you to get away form content based addressing, this may be necessary for total privacy. - -Yes, we need this diff --git a/coordination-layer/partial_transaction.py b/coordination-layer/partial_transaction.py deleted file mode 100644 index 20dd49b..0000000 --- a/coordination-layer/partial_transaction.py +++ /dev/null @@ -1,62 +0,0 @@ -from dataclasses import dataclass - - -from constraints import Proof -from note import PublicNote, SecretNote -from crypto import Field, Point - - -@dataclass -class InputNote: - note: SecretNote - death_cm: Field # commitment to the death constraint we are using - death_proof: Proof - - def verify(self): - # TODO: note.note is ugly - return self.note.note.verify_death(self.death_cm, self.death_proof) - - -@dataclass -class OutputNote: - note: PublicNote - birth_proof: Proof - - def verify(self): - # TODO: note.note is ugly - return self.note.note.verify_birth(self.birth_proof) - - -@dataclass(unsafe_hash=True) -class PartialTransaction: - inputs: list[InputNote] - outputs: list[OutputNote] - rand: Field - - def verify(self) -> bool: - 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_outputs - - def balance(self) -> Point: - output_balance = sum( - (n.note.balance(self.rand) for n in self.outputs), - start=Point.zero(), - ) - input_balance = sum( - (n.note.to_public().balance(self.rand) for n in self.inputs), - start=Point.zero(), - ) - return output_balance + input_balance.negate() - - def zero(self) -> Field: - output_zero = sum( - (n.note.zero(self.rand) for n in self.outputs), - start=Point.zero(), - ) - input_zero = sum( - (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/state.py b/coordination-layer/state.py deleted file mode 100644 index c1e0cd1..0000000 --- a/coordination-layer/state.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This module maintains the state of the CL. - -Namely we are interested in: -- the set of note commitments -- the set of note nullifiers (spent notes) -- the set of constraints -""" - -from dataclasses import dataclass - -import note -import constraint - - -@dataclass -class State: - # commitments: set[note.Commitment] - # nullifiers: set[note.Nullifier] - # constraints: dict[bytes, constraint.Constraint] - - def add_constraint(self, c: constraint.Constraint): - self.constraints[c.hash()] = c diff --git a/coordination-layer/test_balance_commitment.py b/coordination-layer/test_balance_commitment.py deleted file mode 100644 index 97e14db..0000000 --- a/coordination-layer/test_balance_commitment.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest import TestCase - -from hypothesis import example, given, settings, strategies as st - -from crypto import Field, hash_to_curve -from balance_commitment import balance_commitment - - -@st.composite -def field(draw): - x = draw(st.integers(min_value=0, max_value=Field.ORDER - 1)) - return Field(x) - - -@st.composite -def point(draw): - x = draw(field()) - return hash_to_curve("T", x) - - -class TestBalanceCommitment(TestCase): - @given(r=field(), a=field(), b=field(), unit=point()) - @settings(max_examples=3) - def test_value_additive(self, r, a, b, unit): - print(r, a, b, unit) - b1 = balance_commitment(r, a, unit) - b2 = balance_commitment(r, b, unit) - b3 = balance_commitment(r, a + b, unit) - - assert b1 + b2 == b3 diff --git a/coordination-layer/test_crypto.py b/coordination-layer/test_crypto.py deleted file mode 100644 index d6ee14f..0000000 --- a/coordination-layer/test_crypto.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -This module tests that all the hacks we introduced in our crypto mocks give us -the basic behaviour that we need. -""" - -from unittest import TestCase - - -from crypto import Field, Point, hash_to_curve, prf - - -class TestCrypto(TestCase): - def test_hash_to_curve(self): - p1 = hash_to_curve("TEST", Field(0), Field(1), Field(2)) - p2 = hash_to_curve("TEST", Field(0), Field(1), Field(2)) - - assert isinstance(p1, Point) - - assert p1 == p2 - - p3 = hash_to_curve("TEST", Field(0), Field(1), Field(3)) - - assert p1 != p3 - - def test_prf(self): - r1 = prf("TEST", Field(0), Field(1), Field(2)) - r2 = prf("TEST", Field(0), Field(1), Field(2)) - - assert isinstance(r1, Field) - assert r1 == r2 - - r3 = prf("TEST", Field(0), Field(1), Field(3)) - assert r1 != r3 diff --git a/coordination-layer/test_transfer.py b/coordination-layer/test_transfer.py deleted file mode 100644 index 86509ae..0000000 --- a/coordination-layer/test_transfer.py +++ /dev/null @@ -1,73 +0,0 @@ -from unittest import TestCase -from dataclasses import dataclass - -from crypto import Field, prf -from note import InnerNote, PublicNote, SecretNote, nf_pk -from partial_transaction import PartialTransaction, InputNote, OutputNote -from transaction_bundle import TransactionBundle - -from constraints import Vacuous - - -class TestTransfer(TestCase): - def test_1_to_1_transfer(self): - # Alice wants to transfer ownership of a note to Bob. - - @dataclass - class User: - sk: Field - - @property - def pk(self) -> Field: - return nf_pk(self.sk) - - alice = User(sk=Field.random()) - bob = User(sk=Field.random()) - - alices_note = SecretNote( - note=InnerNote( - value=100, - unit="NMO", - birth_constraint=Vacuous(), - death_constraints=[Vacuous()], - state=Field.zero(), - nonce=Field.random(), - rand=Field.random(), - ), - nf_sk=alice.sk, - ) - - tx_rand = Field.random() - bobs_note = PublicNote( - note=InnerNote( - value=100, - unit="NMO", - birth_constraint=Vacuous(), - death_constraints=[Vacuous()], - state=Field.zero(), - nonce=Field.random(), - rand=Field.random(), - ), - nf_pk=bob.pk, - ) - - ptx = PartialTransaction( - inputs=[ - InputNote( - note=alices_note, - death_cm=Vacuous().hash(), - death_proof=Vacuous().prove(), - ) - ], - outputs=[ - OutputNote( - note=bobs_note, - birth_proof=Vacuous().prove(), - ) - ], - rand=tx_rand, - ) - - bundle = TransactionBundle(bundle=[ptx]) - - assert bundle.verify() diff --git a/coordination-layer/transaction_bundle.py b/coordination-layer/transaction_bundle.py deleted file mode 100644 index eb80d0f..0000000 --- a/coordination-layer/transaction_bundle.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass - -from partial_transaction import PartialTransaction -from crypto import Field, Point - - -@dataclass -class TransactionBundle: - bundle: list[PartialTransaction] - - def is_balanced(self) -> bool: - # TODO: move this to a NOIR constraint - balance_commitment = sum( - (ptx.balance() + ptx.zero().negate() for ptx in self.bundle), - start=Point.zero(), - ) - return Point.zero() == balance_commitment - - def verify(self) -> bool: - return self.is_balanced() and all(ptx.verify() for ptx in self.bundle)