diff --git a/coordination-layer/constraint.py b/coordination-layer/constraint.py new file mode 100644 index 0000000..76c8066 --- /dev/null +++ b/coordination-layer/constraint.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + + +@dataclass +class Constraint: + + def hash(self) -> bytes: + raise NotImplementedError() diff --git a/coordination-layer/crypto.py b/coordination-layer/crypto.py new file mode 100644 index 0000000..2ae1daa --- /dev/null +++ b/coordination-layer/crypto.py @@ -0,0 +1,71 @@ +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 + + +def poseidon_grumpkin_field(): + # TODO: These parameters are made up. + # return poseidon.Poseidon( + # p=Field.ORDER, + # security_level=128, + # alpha=5, + # input_rate=3, + # t=9, + # ) + h, _ = poseidon.case_simple() + + # TODO: this is a hack 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 digest + + return inner + + +POSEIDON = poseidon_grumpkin_field() + + +def prf(domain, *elements): + return POSEIDON(str_to_vec(domain) + elements) + + +def comm(*elements): + """ + Returns a commitment to the sequence of elements. + + The commitmtent can be opened at index 0..len(elements) + """ + raise NotImplementedError() + + +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 + [0] * to_pad + + +def _str_to_vec(s): + return list(map(ord, s)) diff --git a/coordination-layer/note.py b/coordination-layer/note.py new file mode 100644 index 0000000..9e43ed5 --- /dev/null +++ b/coordination-layer/note.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass + +from crypto import Field, Point + + +@dataclass +class Commitment: + cm: bytes + + +@dataclass +class Nullifier: + nf: bytes + + +@dataclass +class SecretNote: + note: InnerNote + nf_sk: Field + + def to_public_note(self) -> PublicNote: + return PublicNote( + note=self.note, + nf_pk=Point.generator().mul(self.nf_sk), + ) + + +@dataclass +class PublicNote: + note: InnerNote + nf_pk: Point + + def commit(self) -> Commitment: + return crypto.COMM( + self.note.birth_constraint.hash(), + self.note.death_constraints_root(), + self.note.value, + self.note.unit, + self.note.state, + self.note.nonce, + self.nf_pk, + ) + + +@dataclass +class InnerNote: + value: Field + unit: str + birth_constraint: Constraint + death_constraints: set[Constraint] + state: Field + nonce: Field + rand: Field + + def death_constraints_root(self) -> Field: + """ + Returns the merkle root over the set of death constraints + """ + return crypto.merkle_root(self.death_constraints) diff --git a/coordination-layer/state.py b/coordination-layer/state.py new file mode 100644 index 0000000..358377c --- /dev/null +++ b/coordination-layer/state.py @@ -0,0 +1,23 @@ +""" +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/requirements.txt b/requirements.txt index b0bbe16..e26da00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,8 @@ pysphinx==0.0.1 scipy==1.11.4 black==23.12.1 sympy==1.12 -sh==2.0.6 -toml==0.10.2 -portalocker==2.8.2 +sh==2.0.6 # used for shelling out to noir +toml==0.10.2 # used for noir +portalocker==2.8.2 # portable file locking +keum==0.2.0 # for CL's use of more obscure curves +poseidon-hash==0.1.4 # used as the algebraic hash in CL