diff --git a/book/src/intent.md b/book/src/intent.md index 09606936..be18670b 100644 --- a/book/src/intent.md +++ b/book/src/intent.md @@ -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. diff --git a/book/src/spec.md b/book/src/spec.md index b9a6444e..7b8c78f9 100644 --- a/book/src/spec.md +++ b/book/src/spec.md @@ -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) +|$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 @@ -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})$ @@ -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|| @@ -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 @@ -154,9 +156,9 @@ 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 @@ -164,16 +166,16 @@ 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$. @@ -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 diff --git a/taiga_halo2/src/circuit/integrity.rs b/taiga_halo2/src/circuit/integrity.rs index 76c42d60..5880b559 100644 --- a/taiga_halo2/src/circuit/integrity.rs +++ b/taiga_halo2/src/circuit/integrity.rs @@ -352,7 +352,7 @@ pub fn check_output_note( }) } -pub fn derive_value_base( +pub fn derive_note_type( mut layouter: impl Layouter, hash_to_curve_config: HashToCurveConfig, ecc_chip: EccChip, @@ -367,7 +367,7 @@ pub fn derive_value_base( )?; // 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()) @@ -376,11 +376,11 @@ pub fn derive_value_base( }); 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) @@ -400,8 +400,8 @@ pub fn compute_value_commitment( rcv: pallas::Scalar, ) -> Result>, 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, @@ -413,11 +413,11 @@ pub fn compute_value_commitment( &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, @@ -429,7 +429,7 @@ pub fn compute_value_commitment( &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( diff --git a/taiga_halo2/src/constant.rs b/taiga_halo2/src/constant.rs index 48a93968..ca306e2f 100644 --- a/taiga_halo2/src/constant.rs +++ b/taiga_halo2/src/constant.rs @@ -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; diff --git a/taiga_halo2/src/note.rs b/taiga_halo2/src/note.rs index e4d078e2..99f470be 100644 --- a/taiga_halo2/src/note.rs +++ b/taiga_halo2/src/note.rs @@ -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. @@ -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 @@ -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, @@ -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, @@ -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); @@ -193,7 +193,7 @@ impl Note { pub fn dummy_zero_note(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); @@ -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 { @@ -393,7 +393,7 @@ impl BorshDeserialize for Note { } } -impl ValueBase { +impl NoteType { pub fn new(vk: pallas::Base, data: pallas::Base) -> Self { Self { app_vk: vk, @@ -401,13 +401,13 @@ impl ValueBase { } } - 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::(&inputs) } } -impl Hash for ValueBase { +impl Hash for NoteType { fn hash(&self, state: &mut H) { self.app_vk.to_repr().as_ref().hash(state); self.app_data_static.to_repr().as_ref().hash(state); diff --git a/taiga_halo2/src/value_commitment.rs b/taiga_halo2/src/value_commitment.rs index e5d36e41..1b7206b9 100644 --- a/taiga_halo2/src/value_commitment.rs +++ b/taiga_halo2/src/value_commitment.rs @@ -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)