Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specs updates #189

Merged
merged 8 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions book/src/intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ until the interests of all users are satisfied.
### Intent notes

Users express their preferences in their intent userVPs. For each intent userVP there is a corresponding note type derived from that userVP.
Notes of that type are responsible to make sure that the intent userVP used to derive their value base is satisfied.
Notes of that type are responsible to make sure that the intent userVP used to derive their type is satisfied.

Strictly speaking, the notes don't make the intent userVP to be satisfied,
but rather make sure that the transaction doesn't get published until the intent userVP is satisfied.

When a user specifies their intent userVP, they create an dummy intent note with the value base derived from the userVP and value 1.
When a user specifies their intent userVP, they create an dummy intent note with the type derived from the userVP and value 1.
This note gets spent (balancing the transaction) only when the corresponding intent is satisfied.
Only a fully balanced transaction can be published on the blockchain,
and balancing the intent notes requires satisfying the intent userVPs.
Expand Down
70 changes: 36 additions & 34 deletions book/src/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,37 @@
We use Halo2/IPA with [Pasta curves](https://github.com/zcash/pasta) developed by Zcash to instantiate our proving system.

### 1.1 Circuits
Let `C(x; w) ⟶ 0/1` be a circuit with up to `m` gates. The circuit is represented as polynomials over the chosen curve's **scalar field**, following [plonk-ish arithmetization](https://zcash.github.io/halo2/concepts/arithmetization.html). Commitments are generated over the curve's **base field**.
Let `C(x; w) ⟶ 0/1` be a circuit. As a group, an elliptic curve has arithmetic defined within the _scalar field_ (i.e. the prime number of points of the curve). When we formulate circuits, we use this scalar field. The circuit is represented as polynomials over the chosen curve's scalar field, following [plonk-ish arithmetization](https://zcash.github.io/halo2/concepts/arithmetization.html). When we want to _commit_ to these values in the scalar field, we end up with values in the _base field_. When using these committed values, we encounter what is known as _non-native arithmetic_. This is the motivating factor for proposing a _cycle of curves_.

### 1.2 Elliptic curves
||Name|Scalar field| Base field|Purpose|Instantiation|
|-|-|-|-|-|-|
|$E_I$|Inner curve|$\mathbb{F}_q$|$\mathbb{F}_p$|ECC gadget| [Pallas](https://github.com/zcash/pasta#pallasvesta-supporting-evidence)
|$E_M$|Main curve|$\mathbb{F}_p$|$\mathbb{F}_q$|Action and VP circuits| Vesta|
|$E_O$|Outer curve|$\mathbb{F}_q$|$\mathbb{F}_p$|Accumulation circuit| Pallas|
### 1.2 Cycle of curves
Cycles of curves serve as a solution to the problem of non-native arithmetic by employing a pair of elliptic curves, each operating in a manner that the base field of one becomes the scalar field of the other.
|Name|Base field| Scalar field|Purpose|Instantiation|
|-|-|-|-|-|
|$E_p$|$\mathbb{F}_p$|$\mathbb{F}_q$|ECC gadget, Accumulation circuit| [Pallas](https://github.com/zcash/pasta#pallasvesta-supporting-evidence)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this unification is helpful. Accumulation circuit and ECC gadget are defined over the same circuit only because Halo2 happened to have curve cycle, but they are not the same conceptually. I think having inner curve and outer curve is helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the specs are conditioned by us using Halo2, this is not an exception. We have no reference to outer or inner curves specifically in any of our documentation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still find it useful to keep inner and outer curves separately, even if in Halo2 it is sort of the same. Maybe ask @XuyangSong for the third opinion

|$E_q$|$\mathbb{F}_q$|$\mathbb{F}_p$|Action and VP circuits| Vesta|

### 1.3 Proving system interfaces
||Interface|Description|
|-|-|-|
|__Preprocess__|`preproc(C) ⟶ desc_C`|`C` is turned into a *circuit description*, which is all data the verifier needs to verify a proof. It includes the verifier key `C_vk`, but not only that.|
|__Prove__|`P(C, x, w) ⟶ π`||
|__Verify__|`V(desc_C, x, π) ⟶ 0/1`||
|__Generate Verifier key__|`keygen_vk(C) ⟶ vk`|`C` is turned into a *circuit description* or *verifying key* `vk`, a succint representation of the circuit that the verifier uses to verify a proof|
|__Generate Proving key__|`keygen_pk(C, vk) ⟶ pk`|Generate a proving key from a verifying key and an instance of circuit|
|__Prove__|`P(C, pk, x, w) ⟶ π`|Prove that a circuit is satisfied given instance `x` and witness `w`|
|__Verify__|`V(vk, x, π) ⟶ 0/1`|Verify the proof|

## 2. Notes
Note is an immutable particle of the application state.
Note is an immutable particle of the application state in the UTXO model.

### 2.1 Note structure
|Variable|Type/size|Description|
|Variable|Type|Description|
|-|-|-|
|`value_base`||Value base represents the note type|
|`app_data_dynamic`||Commitment to the note's extra data|
|`v`|${0..2^{64} - 1}$|The quantity of fungible value|
|`cm_nk`||Commitment to the nullifier key that will be used to derive the note's nullifier|
|`ρ`|$\mathbb{F}_p$|An old nullifier from the same Action description (see Orchard)|
|`ψ`|$\mathbb{F}_p$|$ψ = PRF^{ψ}(0, rseed, ρ)$|
|`is_merkle_checked`|bool|Dummy note flag. It indicates whether the note's commitment Merkle path should be checked when spending the note.|
|`rcm_note`|${0..2^{255} - 1}$|A random commitment trapdoor $rcm\_note = PRF^{\texttt{rcm\_note}}(1, rseed, ρ)$|
|`note_type`|$E_p$|Identifier of the note's application|
|`app_data_dynamic`|$\mathbb{F}_p$|Encoding of the application's extra data|
|`v`|u64|Fungible quantity specific to a note type|
|`cm_nk`|$\mathbb{F}_p$|Commitment to the nullifier key that will be used to derive the note's nullifier|
|`ρ`|$\mathbb{F}_p$|The nullifier `nf` of the consumed note is equal to the `ρ` of the created note from the same Action description (see Orchard). This guarantees the uniqueness of a note|
|`ψ`|$\mathbb{F}_p$|$ψ = PRF(0, rseed, ρ)$|
|`is_merkle_checked`|bool|Ephemeral note flag. It indicates whether the note's commitment Merkle path should be checked when consuming the note.|
|`rcm_note`|$F_q$|A random commitment trapdoor $rcm\_note = PRF^{\texttt{rcm\_note}}(1, rseed, ρ)$|

Note: the value size cannot be bigger or close to the curve's scalar field size (to avoid overflowing) but besides that there are no strict reasons for choosing 64. We can use more notes to express a value that doesn't fit in one note (splitting the value into limbs). Having bigger value size requires fewer notes to express such a value and is more efficient. For example, a value size of 128 bits would require two times less notes to express a maximum value

Expand All @@ -56,12 +57,12 @@ Each note has three fields with application data.
|Variable|Type/size|Description|
|-|-|-|
|`cm_app_vk`|| Contains the application's main VP verifier key. Used to identify the application the note belongs to. As the verifier key itself is large, the notes only store a commitment to it.|
|`app_data_static`||Contains the application data that affects fungibility of the note. Along with `cm_app_vk`, it is used to derive note's value base||
|`app_data_static`||Contains the application data that affects fungibility of the note. Along with `cm_app_vk`, it is used to derive note's type||
|`app_data_dynamic`||Contains the application data that doesn't affect the fungibility of the note|

#### Value base
#### Note type

Value base is used to distinguish note types. Notes with different value bases have different note types. The value base of a note is derived from two application-related fields: `cm_app_vk` and `app_data_static`.
Note type is used to distinguish note types. Notes with different types have different note types. The type of a note is derived from two application-related fields: `cm_app_vk` and `app_data_static`.

$VB = PRF^{vb}(cm_{\texttt{app\_vk}}, \texttt{app\_data\_static})$

Expand All @@ -81,8 +82,8 @@ $cv = [v^{in}]VB^{in} - [v^{out}]VB^{out} + [rcv]R$
|-|-|-|
|$v^{in}$|${0..2^{64} - 1}$||
|$v^{out}$|${0..2^{64} - 1}$||
|$VB^{in}$|outer curve point|Input note's value base|
|$VB^{out}$|outer curve point|Output note's value base|
|$VB^{in}$|outer curve point|Input note's type|
|$VB^{out}$|outer curve point|Output note's type|
|`R`|outer curve point|Randomness base, fixed|
|`rcv`|${0..2^{255} - 1}$|Value commitment trapdoor|
|`cv`|outer curve point||
Expand Down Expand Up @@ -136,9 +137,10 @@ $ce = Encrypt(note, sk)$

Not all of the note fields require to be encrypted (e.g. note commitment), and the encrypted fields may vary depending on the application. To make sure it is flexible enough, the encryption check is performed in VP circuits.

### 2.6 Dummy notes
In Taiga, note's value doesn't define if the note is dummy or not, unlike some other systems. Dummy notes can have non-zero value and are marked explicitly as dummy by setting `is_merkle_checked = false` meaning that for dummy notes the commitment's Merkle path is not checked when spending the note. Non-zero value dummy notes are handy for carrying additional constraints (e.g. intents) and balancing transactions.
### 2.6 Ephemeral notes and dummy notes
A note is _ephemeral_ if it doesn’t need to be inserted in the note commitment tree (i.e. created) before it can be consumed. An ephemeral note is marked ephemeral by setting the `is_merkle_checked` flag to `false`. For ephemeral notes the Merkle authentication path is not checked when consuming the note. An example of an ephemeral note is an _intent note_, since both the creation and the consumption of an intent happen within the same transaction.

As in ZCash, a note is _dummy_ if its `value` field is zero and therefore it doesn’t affect the balance of a transaction.

## 3. Circuits
### 3.1 The Action Circuit
Expand All @@ -154,26 +156,26 @@ Public inputs (`x`):
5. `cm_vp_out` - output note's application VP commitment

Private inputs (`w`):
1. `in_note = (value_base, v, cm_nk, ρ, ψ, is_merkle_checked, rcm_note)` - input note opening
1. `in_note = (note_type, v, cm_nk, ρ, ψ, is_merkle_checked, rcm_note)` - input note opening
2. `(cm_app_vk, rcm_vp)` - opening of `cm_vp_in`
3. `out_note = (value_base, v, cm_nk, ρ, ψ, is_merkle_checked, rcm_note)` - output note opening
3. `out_note = (note_type, v, cm_nk, ρ, ψ, is_merkle_checked, rcm_note)` - output note opening
4. `(cm_app_vk, rcm_vp)` - opening of `cm_vp_out`

Note: opening of a parameter is every field used to derive the parameter

#### Checks
- For input note:
- If `is_merkle_checked = true`, check that the note is a valid note in `rt`: there is a path in Merkle tree with root `rt` to a note commitment `cm` that opens to `note`
- Nullifier integrity: $nf = DeriveNullier_{nk}(note)$.
- Nullifier integrity: $nf = DeriveNullifier_{nk}(note)$.
- Application VP integrity: $cm_{vp} = VPCommit(cm_{app\_vk}, rcm_{vp})$
- Value base integrity: $vb = PRF^{vb}(cm_{app\_vk}, \texttt{app\_data\_static})$
- Note type integrity: $nt = PRF(cm_{app\_vk}, \texttt{app\_data\_static})$
- For output note:
- Commitment integrity(output note only): $cm = NoteCom(note, rcm_{note})$
- Application VP integrity: $cm_{vp} = VPCommit(cm_{\texttt{app\_vk}}, rcm_{vp})$
- Value base integrity: $vb = PRF^{vb}(cm_{app\_vk}, \texttt{app\_data\_static})$
- Value base integrity: $nt = PRF(cm_{app\_vk}, \texttt{app\_data\_static})$
- Value commitment integrity: $cv = ValueCommit(v_{in}, v_{out}, VB_{in}, VB_{out}, rcv)$

Note: unlike MASP, the value base in Taiga is not used to compute note's commitment and the Action circuit doesn't take `vb` as private input but computes it from the note fields, and it is checked for both input and output notes.
Note: unlike MASP, the type in Taiga is not used to compute note's commitment and the Action circuit doesn't take `vb` as private input but computes it from the note fields, and it is checked for both input and output notes.

### 3.2 Validity Predicate (VP) circuits
Validity predicate is a circuit containing the application logic. Validity predicates take `n` input and `n` output notes, are represented as Halo2 circuits `VP(x; w) ⟶ 0/1` and arithmetized over $\mathbb{F}_p$.
Expand Down Expand Up @@ -240,7 +242,7 @@ Certain applications might allow to create more value from less input value, whi
|$PRF^{ψ}$|Blake2b|$\mathrm{F}_p \times \mathrm{F}_p \times \mathrm{F}_p \rightarrow \mathrm{F}_p$|Used to derive ψ|
|`NKCommit`|Poseidon|$\mathrm{F}_p \rightarrow \mathrm{F}_p$|$NKCommit(nk) = Poseidon(nk,\texttt{user\_derived\_key})$; used to protect `nk` stored in a note. `user_derived_key` is currently not used
|`NoteCommit`|[Sincemilla](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html)|$\mathrm{F}_p \rightarrow \mathrm{F}_p \times \mathrm{F}_p$|
|`ValueCommit`|Pedersen with variable value base|$\mathrm{F}_p \rightarrow \mathrm{F}_q$|$cv = [v_i] * VB_i - [v_o] * VB_o + [r]R$
|`ValueCommit`|Pedersen with variable type|$\mathrm{F}_p \rightarrow \mathrm{F}_q$|$cv = [v_i] * VB_i - [v_o] * VB_o + [r]R$
|`VPCommit`|Blake2s||Efficient over both $\mathrm{F}_p$ and $\mathrm{F}_q$
|`VKCommit`|-||Efficient over the outer curve's scalar field|
|address|Poseidon|$\mathrm{F}_p \rightarrow \mathrm{F}_p$| `address = Poseidon(app_data_dynamic, cm_nk)`; compresses the data fields that contain some ownership information
Expand Down
20 changes: 10 additions & 10 deletions taiga_halo2/src/circuit/integrity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@
})
}

pub fn derive_value_base(
pub fn derive_note_type(
mut layouter: impl Layouter<pallas::Base>,
hash_to_curve_config: HashToCurveConfig,
ecc_chip: EccChip<TaigaFixedBases>,
Expand All @@ -367,20 +367,20 @@
)?;

// Assign a new `NonIdentityPoint` and constran equal to hash_to_curve point since `Point` doesn't have mul operation
// IndentityPoint is an invalid value base and it returns an error.
// IndentityPoint is an invalid note type and it returns an error.
let non_identity_point = app_vk
.value()
.zip(app_data_static.value())
.map(|(&vk, &data)| {
poseidon_to_curve::<POSEIDON_TO_CURVE_INPUT_LEN>(&[vk, data]).to_affine()

Check warning on line 375 in taiga_halo2/src/circuit/integrity.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

it looks like you're trying to convert a tuple to an array

warning: it looks like you're trying to convert a tuple to an array --> taiga_halo2/src/circuit/integrity.rs:375:63 | 375 | poseidon_to_curve::<POSEIDON_TO_CURVE_INPUT_LEN>(&[vk, data]).to_affine() | ^^^^^^^^^^ | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions = note: `-W clippy::tuple-array-conversions` implied by `-W clippy::all`

Check warning on line 375 in taiga_halo2/src/circuit/integrity.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

it looks like you're trying to convert a tuple to an array

warning: it looks like you're trying to convert a tuple to an array --> taiga_halo2/src/circuit/integrity.rs:375:63 | 375 | poseidon_to_curve::<POSEIDON_TO_CURVE_INPUT_LEN>(&[vk, data]).to_affine() | ^^^^^^^^^^ | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions = note: `-W clippy::tuple-array-conversions` implied by `-W clippy::all`
});
let non_identity_point_var = NonIdentityPoint::new(
ecc_chip,
layouter.namespace(|| "non-identity value base"),
layouter.namespace(|| "non-identity note type"),
non_identity_point,
)?;
point.constrain_equal(
layouter.namespace(|| "non-identity value base"),
layouter.namespace(|| "non-identity note type"),
&non_identity_point_var,
)?;
Ok(non_identity_point_var)
Expand All @@ -400,8 +400,8 @@
rcv: pallas::Scalar,
) -> Result<Point<pallas::Affine, EccChip<TaigaFixedBases>>, Error> {
// input value point
let value_base_input = derive_value_base(
layouter.namespace(|| "derive input value base"),
let note_type_input = derive_note_type(
layouter.namespace(|| "derive input note type"),
hash_to_curve_config.clone(),
ecc_chip.clone(),
app_address_input,
Expand All @@ -413,11 +413,11 @@
&v_input,
)?;
let (value_point_input, _) =
value_base_input.mul(layouter.namespace(|| "input value point"), v_input_scalar)?;
note_type_input.mul(layouter.namespace(|| "input value point"), v_input_scalar)?;

// output value point
let value_base_output = derive_value_base(
layouter.namespace(|| "derive output value base"),
let note_type_output = derive_note_type(
layouter.namespace(|| "derive output note type"),
hash_to_curve_config,
ecc_chip.clone(),
app_address_output,
Expand All @@ -429,7 +429,7 @@
&v_output,
)?;
let (value_point_output, _) =
value_base_output.mul(layouter.namespace(|| "output value point"), v_output_scalar)?;
note_type_output.mul(layouter.namespace(|| "output value point"), v_output_scalar)?;

// Get and constrain the negative output value point
let neg_v_point_output = Point::new(
Expand Down
2 changes: 1 addition & 1 deletion taiga_halo2/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub const ACTION_NET_VALUE_CM_Y_INSTANCE_ROW_IDX: usize = 4;

pub const POSEIDON_TO_CURVE_INPUT_LEN: usize = 3;
pub const CURVE_ID: &str = "pallas";
pub const VALUE_BASE_DOMAIN_POSTFIX: &str = "Taiga-ValueBase";
pub const VALUE_BASE_DOMAIN_POSTFIX: &str = "Taiga-NoteType";

pub const VP_CIRCUIT_NULLIFIER_ONE_INSTANCE_IDX: usize = 0;
pub const VP_CIRCUIT_OUTPUT_CM_ONE_INSTANCE_IDX: usize = 1;
Expand Down
26 changes: 13 additions & 13 deletions taiga_halo2/src/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ impl Default for NoteCommitment {
/// A note
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Note {
pub note_type: ValueBase,
/// app_data_dynamic is the data defined in application vp and will NOT be used to derive value base
pub note_type: NoteType,
/// app_data_dynamic is the data defined in application vp and will NOT be used to derive type
/// sub-vps and any other data can be encoded to the app_data_dynamic
pub app_data_dynamic: pallas::Base,
/// value denotes the amount of the note.
Expand All @@ -73,9 +73,9 @@ pub struct Note {
pub is_merkle_checked: bool,
}

/// The parameters in the ValueBase are used to derive note value base.
/// The parameters in the NoteType are used to derive note type.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ValueBase {
pub struct NoteType {
/// app_vk is the compressed verifying key of VP
pub app_vk: pallas::Base,
/// app_data_static is the encoded data that is defined in application vp
Expand Down Expand Up @@ -113,7 +113,7 @@ impl Note {
is_merkle_checked: bool,
rseed: RandomSeed,
) -> Self {
let note_type = ValueBase::new(app_vk, app_data_static);
let note_type = NoteType::new(app_vk, app_data_static);
Self {
note_type,
app_data_dynamic,
Expand All @@ -138,7 +138,7 @@ impl Note {
psi: pallas::Base,
rcm: pallas::Base,
) -> Self {
let note_type = ValueBase::new(app_vk, app_data_static);
let note_type = NoteType::new(app_vk, app_data_static);
Self {
note_type,
app_data_dynamic,
Expand Down Expand Up @@ -174,7 +174,7 @@ impl Note {
) -> Self {
let app_vk = pallas::Base::random(&mut rng);
let app_data_static = pallas::Base::random(&mut rng);
let note_type = ValueBase::new(app_vk, app_data_static);
let note_type = NoteType::new(app_vk, app_data_static);
let app_data_dynamic = pallas::Base::zero();
let value: u64 = rng.gen();
let rseed = RandomSeed::random(&mut rng);
Expand All @@ -193,7 +193,7 @@ impl Note {
pub fn dummy_zero_note<R: RngCore>(mut rng: R, rho: Nullifier) -> Self {
let app_vk = *COMPRESSED_TRIVIAL_VP_VK;
let app_data_static = pallas::Base::random(&mut rng);
let note_type = ValueBase::new(app_vk, app_data_static);
let note_type = NoteType::new(app_vk, app_data_static);
let app_data_dynamic = pallas::Base::zero();
let nk = NullifierKeyContainer::random_key(&mut rng);
let rseed = RandomSeed::random(&mut rng);
Expand Down Expand Up @@ -278,8 +278,8 @@ impl Note {
self.nk_container.get_commitment()
}

pub fn get_value_base(&self) -> pallas::Point {
self.note_type.derive_value_base()
pub fn get_note_type(&self) -> pallas::Point {
self.note_type.derive_note_type()
}

pub fn get_app_vk(&self) -> pallas::Base {
Expand Down Expand Up @@ -393,21 +393,21 @@ impl BorshDeserialize for Note {
}
}

impl ValueBase {
impl NoteType {
pub fn new(vk: pallas::Base, data: pallas::Base) -> Self {
Self {
app_vk: vk,
app_data_static: data,
}
}

pub fn derive_value_base(&self) -> pallas::Point {
pub fn derive_note_type(&self) -> pallas::Point {
let inputs = [self.app_vk, self.app_data_static];
poseidon_to_curve::<POSEIDON_TO_CURVE_INPUT_LEN>(&inputs)
}
}

impl Hash for ValueBase {
impl Hash for NoteType {
fn hash<H: Hasher>(&self, state: &mut H) {
self.app_vk.to_repr().as_ref().hash(state);
self.app_data_static.to_repr().as_ref().hash(state);
Expand Down
4 changes: 2 additions & 2 deletions taiga_halo2/src/value_commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pub struct ValueCommitment(pallas::Point);

impl ValueCommitment {
pub fn new(input_note: &Note, output_note: &Note, blind_r: &pallas::Scalar) -> Self {
let base_input = input_note.get_value_base();
let base_output = output_note.get_value_base();
let base_input = input_note.get_note_type();
let base_output = output_note.get_note_type();
ValueCommitment(
base_input * pallas::Scalar::from(input_note.value)
- base_output * pallas::Scalar::from(output_note.value)
Expand Down
Loading