diff --git a/taiga_halo2/Cargo.toml b/taiga_halo2/Cargo.toml index 83652e9f..251bfd69 100644 --- a/taiga_halo2/Cargo.toml +++ b/taiga_halo2/Cargo.toml @@ -4,10 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] +rustler = {version = "0.29.1", optional = true} rand = "0.8" lazy_static = "1.4" blake2b_simd = "1.0" -pasta_curves = {git = "https://github.com/heliaxdev/pasta_curves", branch = "taiga"} +pasta_curves = {git = "https://github.com/heliaxdev/pasta_curves", branch = "taiga", features = ["repr-erlang"]} ff = "0.13" group = "0.13" halo2_gadgets = {git = "https://github.com/heliaxdev/halo2", branch = "taiga", features = ["test-dependencies"]} @@ -26,6 +27,10 @@ num-bigint = "0.4" criterion = "0.5" proptest = "1.2" +[features] +default = [] +nif = ["rustler", "pasta_curves/repr-erlang"] + [[bench]] name = "action_proof" harness = false diff --git a/taiga_halo2/src/action.rs b/taiga_halo2/src/action.rs index 1edf8251..8f2ca5e4 100644 --- a/taiga_halo2/src/action.rs +++ b/taiga_halo2/src/action.rs @@ -10,10 +10,14 @@ use ff::PrimeField; use halo2_proofs::arithmetic::Field; use pasta_curves::pallas; use rand::RngCore; +#[cfg(feature = "nif")] +use rustler::NifStruct; use std::io; /// The action result used in transaction. #[derive(Copy, Debug, Clone)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Action.Instance")] pub struct ActionInstance { /// The root of the note commitment Merkle tree. pub anchor: pallas::Base, diff --git a/taiga_halo2/src/circuit/vp_circuit.rs b/taiga_halo2/src/circuit/vp_circuit.rs index 16f5c57c..37dd5158 100644 --- a/taiga_halo2/src/circuit/vp_circuit.rs +++ b/taiga_halo2/src/circuit/vp_circuit.rs @@ -55,6 +55,11 @@ use vamp_ir::halo2::synth::{make_constant, Halo2Module, PrimeFieldOps}; use vamp_ir::transform::compile; use vamp_ir::util::{read_inputs_from_file, Config}; +#[cfg(feature = "nif")] +use rustler::types::atom; +#[cfg(feature = "nif")] +use rustler::{Decoder, Encoder, Env, NifResult, Term}; + pub type ValidityPredicate = dyn ValidityPredicateVerifyingInfo; #[derive(Debug, Clone)] @@ -64,9 +69,66 @@ pub struct VPVerifyingInfo { pub public_inputs: ValidityPredicatePublicInputs, } +#[cfg(feature = "nif")] +rustler::atoms! {verifying_info} + +#[cfg(feature = "nif")] +impl Encoder for VPVerifyingInfo { + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + ( + verifying_info().encode(env), + self.vk.to_bytes().encode(env), + self.proof.encode(env), + self.public_inputs.encode(env), + ) + .encode(env) + } +} + +#[cfg(feature = "nif")] +impl<'a> Decoder<'a> for VPVerifyingInfo { + fn decode(term: Term<'a>) -> NifResult { + let (term, vk, proof, public_inputs): ( + atom::Atom, + Vec, + Proof, + ValidityPredicatePublicInputs, + ) = term.decode()?; + if term == verifying_info() { + use crate::circuit::vp_examples::TrivialValidityPredicateCircuit; + let params = SETUP_PARAMS_MAP.get(&VP_CIRCUIT_PARAMS_SIZE).unwrap(); + let vk = VerifyingKey::from_bytes::(&vk, params) + .map_err(|_e| rustler::Error::Atom("failure to decode"))?; + Ok(VPVerifyingInfo { + vk, + proof, + public_inputs, + }) + } else { + Err(rustler::Error::BadArg) + } + } +} + #[derive(Clone, Debug)] pub struct ValidityPredicatePublicInputs([pallas::Base; VP_CIRCUIT_PUBLIC_INPUT_NUM]); +#[cfg(feature = "nif")] +impl Encoder for ValidityPredicatePublicInputs { + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + self.0.to_vec().encode(env) + } +} + +#[cfg(feature = "nif")] +impl<'a> Decoder<'a> for ValidityPredicatePublicInputs { + fn decode(term: Term<'a>) -> NifResult { + let val: Vec = Decoder::decode(term)?; + val.try_into() + .map_err(|_e| rustler::Error::Atom("failure to decode")) + } +} + impl VPVerifyingInfo { pub fn verify(&self) -> Result<(), Error> { let params = SETUP_PARAMS_MAP.get(&VP_CIRCUIT_PARAMS_SIZE).unwrap(); diff --git a/taiga_halo2/src/circuit/vp_examples.rs b/taiga_halo2/src/circuit/vp_examples.rs index c647ce74..1246a5f6 100644 --- a/taiga_halo2/src/circuit/vp_examples.rs +++ b/taiga_halo2/src/circuit/vp_examples.rs @@ -16,6 +16,8 @@ use halo2_proofs::{ use lazy_static::lazy_static; use pasta_curves::pallas; use rand::{rngs::OsRng, RngCore}; +#[cfg(feature = "nif")] +use rustler::{Decoder, Encoder, Env, NifResult, NifStruct, Term}; pub mod cascade_intent; mod field_addition; @@ -39,6 +41,16 @@ pub struct TrivialValidityPredicateCircuit { pub output_notes: [Note; NUM_NOTE], } +// I only exist to allow trivial derivation of the nifstruct +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.VP.Trivial")] +struct TrivialValidtyPredicateCircuitProxy { + owned_note_pub_id: pallas::Base, + input_notes: Vec, + output_notes: Vec, +} + impl TrivialValidityPredicateCircuit { pub fn new( owned_note_pub_id: pallas::Base, @@ -51,6 +63,41 @@ impl TrivialValidityPredicateCircuit { output_notes, } } + + fn to_proxy(&self) -> TrivialValidtyPredicateCircuitProxy { + TrivialValidtyPredicateCircuitProxy { + owned_note_pub_id: self.owned_note_pub_id, + input_notes: self.input_notes.to_vec(), + output_notes: self.output_notes.to_vec(), + } + } +} + +impl TrivialValidtyPredicateCircuitProxy { + fn to_concrete(&self) -> Option { + let input_notes = self.input_notes.clone().try_into().ok()?; + let output_notes = self.output_notes.clone().try_into().ok()?; + let owned_note_pub_id = self.owned_note_pub_id; + Some(TrivialValidityPredicateCircuit { + owned_note_pub_id, + input_notes, + output_notes, + }) + } +} +#[cfg(feature = "nif")] +impl Encoder for TrivialValidityPredicateCircuit { + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + self.to_proxy().encode(env) + } +} +#[cfg(feature = "nif")] +impl<'a> Decoder<'a> for TrivialValidityPredicateCircuit { + fn decode(term: Term<'a>) -> NifResult { + let val: TrivialValidtyPredicateCircuitProxy = Decoder::decode(term)?; + val.to_concrete() + .ok_or(rustler::Error::RaiseAtom("Could not decode proxy")) + } } impl ValidityPredicateCircuit for TrivialValidityPredicateCircuit { diff --git a/taiga_halo2/src/note.rs b/taiga_halo2/src/note.rs index 8da08bfc..ef3b68e0 100644 --- a/taiga_halo2/src/note.rs +++ b/taiga_halo2/src/note.rs @@ -24,6 +24,8 @@ use pasta_curves::{ pallas, }; use rand::RngCore; +#[cfg(feature = "nif")] +use rustler::{NifStruct, NifTuple}; use std::{ hash::{Hash, Hasher}, io, @@ -31,6 +33,7 @@ use std::{ /// A commitment to a note. #[derive(Copy, Debug, Clone)] +#[cfg_attr(feature = "nif", derive(NifTuple))] pub struct NoteCommitment(pallas::Point); impl NoteCommitment { @@ -55,6 +58,8 @@ impl Default for NoteCommitment { /// A note #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Note")] pub struct Note { pub note_type: NoteType, /// app_data_dynamic is the data defined in application vp and will NOT be used to derive type @@ -76,6 +81,8 @@ pub struct Note { /// The parameters in the NoteType are used to derive note type. #[derive(Debug, Clone, Copy, Default, Eq)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.NoteType")] pub struct NoteType { /// app_vk is the compressed verifying key of VP pub app_vk: pallas::Base, diff --git a/taiga_halo2/src/nullifier.rs b/taiga_halo2/src/nullifier.rs index 185fc469..c69bf6ba 100644 --- a/taiga_halo2/src/nullifier.rs +++ b/taiga_halo2/src/nullifier.rs @@ -11,14 +11,18 @@ use pasta_curves::group::cofactor::CofactorCurveAffine; use pasta_curves::group::ff::PrimeField; use pasta_curves::pallas; use rand::RngCore; +#[cfg(feature = "nif")] +use rustler::{NifTaggedEnum, NifTuple}; use subtle::CtOption; /// The unique nullifier. #[derive(Copy, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "nif", derive(NifTuple))] pub struct Nullifier(pallas::Base); /// The NullifierKeyContainer contains the nullifier_key or the nullifier_key commitment #[derive(Copy, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "nif", derive(NifTaggedEnum))] pub enum NullifierKeyContainer { // The NullifierKeyContainer::Commitment is the commitment of NullifierKeyContainer::Key `nk_com = Commitment(nk, 0)` Commitment(pallas::Base), diff --git a/taiga_halo2/src/proof.rs b/taiga_halo2/src/proof.rs index c6874efe..c2024a3a 100644 --- a/taiga_halo2/src/proof.rs +++ b/taiga_halo2/src/proof.rs @@ -6,8 +6,11 @@ use halo2_proofs::{ }; use pasta_curves::{pallas, vesta}; use rand::RngCore; +#[cfg(feature = "nif")] +use rustler::NifTuple; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "nif", derive(NifTuple))] pub struct Proof(Vec); impl Proof { diff --git a/taiga_halo2/src/shielded_ptx.rs b/taiga_halo2/src/shielded_ptx.rs index c9f3bd63..241f1088 100644 --- a/taiga_halo2/src/shielded_ptx.rs +++ b/taiga_halo2/src/shielded_ptx.rs @@ -14,6 +14,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use halo2_proofs::plonk::Error; use pasta_curves::pallas; use rand::RngCore; +#[cfg(feature = "nif")] +use rustler::{Decoder, Encoder, Env, NifResult, NifStruct, Term}; #[derive(Debug, Clone)] pub struct ShieldedPartialTransaction { @@ -23,12 +25,16 @@ pub struct ShieldedPartialTransaction { } #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Action.VerifyingInfo")] pub struct ActionVerifyingInfo { action_proof: Proof, action_instance: ActionInstance, } #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Note.VerifyingInfo")] pub struct NoteVPVerifyingInfoSet { app_vp_verifying_info: VPVerifyingInfo, app_dynamic_vp_verifying_info: Vec, @@ -36,6 +42,16 @@ pub struct NoteVPVerifyingInfoSet { // When the verifier proof is added, we may need to reconsider the structure of `VPVerifyingInfo` } +// Is easier to derive traits for +#[derive(Debug, Clone)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Shielded.PTX")] +struct ShieldedPartialTransactionProxy { + actions: Vec, + inputs: Vec, + outputs: Vec, +} + impl ShieldedPartialTransaction { pub fn build( input_info: [InputNoteProvingInfo; NUM_NOTE], @@ -163,6 +179,28 @@ impl ShieldedPartialTransaction { } Ok(()) } + + // Conversion to the generic length proxy + fn to_proxy(&self) -> ShieldedPartialTransactionProxy { + ShieldedPartialTransactionProxy { + actions: self.actions.to_vec(), + inputs: self.inputs.to_vec(), + outputs: self.outputs.to_vec(), + } + } +} + +impl ShieldedPartialTransactionProxy { + fn to_concrete(&self) -> Option { + let actions = self.actions.clone().try_into().ok()?; + let inputs = self.inputs.clone().try_into().ok()?; + let outputs = self.outputs.clone().try_into().ok()?; + Some(ShieldedPartialTransaction { + actions, + inputs, + outputs, + }) + } } impl Executable for ShieldedPartialTransaction { @@ -238,6 +276,23 @@ impl BorshDeserialize for ShieldedPartialTransaction { }) } } + +#[cfg(feature = "nif")] +impl Encoder for ShieldedPartialTransaction { + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + self.to_proxy().encode(env) + } +} + +#[cfg(feature = "nif")] +impl<'a> Decoder<'a> for ShieldedPartialTransaction { + fn decode(term: Term<'a>) -> NifResult { + let val: ShieldedPartialTransactionProxy = Decoder::decode(term)?; + val.to_concrete() + .ok_or(rustler::Error::RaiseAtom("Could not decode proxy")) + } +} + impl ActionVerifyingInfo { pub fn create(action_info: ActionInfo, mut rng: R) -> Result { let (action_instance, circuit) = action_info.build(); diff --git a/taiga_halo2/src/transaction.rs b/taiga_halo2/src/transaction.rs index dd21b9ba..1000ee8a 100644 --- a/taiga_halo2/src/transaction.rs +++ b/taiga_halo2/src/transaction.rs @@ -13,6 +13,10 @@ use pasta_curves::{ pallas, }; use rand::{CryptoRng, RngCore}; +#[cfg(feature = "nif")] +use rustler::types::atom; +#[cfg(feature = "nif")] +use rustler::{atoms, Decoder, Env, NifRecord, NifResult, NifStruct, Term}; #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct Transaction { @@ -30,11 +34,15 @@ pub enum InProgressBindingSignature { } #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +#[cfg_attr(feature = "nif", derive(NifRecord))] +#[cfg_attr(feature = "nif", tag = "bundle")] pub struct ShieldedPartialTxBundle { partial_txs: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "nif", derive(NifStruct))] +#[cfg_attr(feature = "nif", module = "Taiga.Transaction.Result")] pub struct ShieldedResult { anchors: Vec, nullifiers: Vec, @@ -198,6 +206,51 @@ impl Transaction { } } +#[cfg(feature = "nif")] +atoms! { transaction } + +#[cfg(feature = "nif")] +impl rustler::Encoder for Transaction { + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + ( + transaction().encode(env), + self.shielded_ptx_bundle.encode(env), + self.transparent_ptx_bundle + .try_to_vec() + .unwrap_or(vec![]) + .encode(env), + self.signature.try_to_vec().unwrap_or(vec![]).encode(env), + ) + .encode(env) + } +} + +#[cfg(feature = "nif")] +impl<'a> Decoder<'a> for Transaction { + fn decode(term: Term<'a>) -> NifResult { + let (term, shielded_ptx_bundle, transparent_bytes, sig_bytes): ( + atom::Atom, + Option, + Vec, + Vec, + ) = term.decode()?; + if term == transaction() { + let transparent_ptx_bundle = + BorshDeserialize::deserialize(&mut transparent_bytes.as_slice()) + .map_err(|_e| rustler::Error::Atom("Failure to decode"))?; + let signature = BorshDeserialize::deserialize(&mut sig_bytes.as_slice()) + .map_err(|_e| rustler::Error::Atom("Failure to decode"))?; + Ok(Transaction { + shielded_ptx_bundle, + signature, + transparent_ptx_bundle, + }) + } else { + Err(rustler::Error::BadArg) + } + } +} + impl ShieldedPartialTxBundle { pub fn new() -> Self { Self { diff --git a/taiga_halo2/src/value_commitment.rs b/taiga_halo2/src/value_commitment.rs index 1b7206b9..bf6d0309 100644 --- a/taiga_halo2/src/value_commitment.rs +++ b/taiga_halo2/src/value_commitment.rs @@ -4,9 +4,12 @@ use halo2_proofs::arithmetic::CurveAffine; use pasta_curves::group::cofactor::CofactorCurveAffine; use pasta_curves::group::{Curve, Group, GroupEncoding}; use pasta_curves::pallas; +#[cfg(feature = "nif")] +use rustler::NifTuple; use subtle::CtOption; #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "nif", derive(NifTuple))] pub struct ValueCommitment(pallas::Point); impl ValueCommitment {