1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use ark_ff::ToConstraintField;
use ark_r1cs_std::prelude::*;
use ark_relations::r1cs::SynthesisError;
use decaf377::{
    r1cs::{ElementVar, FqVar},
    Fq,
};
use penumbra_asset::ValueVar;
use penumbra_keys::address::AddressVar;
use penumbra_tct::r1cs::StateCommitmentVar;

use crate::Note;

use super::NOTECOMMIT_DOMAIN_SEP;

pub struct NoteVar {
    pub value: ValueVar,
    pub note_blinding: FqVar,
    pub address: AddressVar,
}

impl NoteVar {
    pub fn amount(&self) -> FqVar {
        self.value.amount()
    }

    pub fn value(&self) -> ValueVar {
        self.value.clone()
    }

    #[allow(dead_code)]
    pub fn asset_id(&self) -> FqVar {
        self.value.asset_id()
    }

    #[allow(dead_code)]
    pub fn note_blinding(&self) -> FqVar {
        self.note_blinding.clone()
    }

    pub fn diversified_generator(&self) -> ElementVar {
        self.address.diversified_generator.clone()
    }

    pub fn transmission_key(&self) -> ElementVar {
        self.address.transmission_key.clone()
    }

    #[allow(dead_code)]
    pub fn clue_key(&self) -> FqVar {
        self.address.clue_key.clone()
    }
}

impl AllocVar<Note, Fq> for NoteVar {
    fn new_variable<T: std::borrow::Borrow<Note>>(
        cs: impl Into<ark_relations::r1cs::Namespace<Fq>>,
        f: impl FnOnce() -> Result<T, SynthesisError>,
        mode: ark_r1cs_std::prelude::AllocationMode,
    ) -> Result<Self, SynthesisError> {
        // TODO: figure out how to use namespaces
        let ns = cs.into();
        let cs = ns.cs();
        let note1 = f()?;
        let note: &Note = note1.borrow();
        let note_blinding = FqVar::new_variable(cs.clone(), || Ok(note.note_blinding()), mode)?;
        let value = ValueVar::new_variable(cs.clone(), || Ok(note.value()), mode)?;
        let address = AddressVar::new_variable(cs, || Ok(note.address()), mode)?;

        Ok(Self {
            note_blinding,
            value,
            address,
        })
    }
}

impl ToConstraintField<Fq> for Note {
    fn to_field_elements(&self) -> Option<Vec<Fq>> {
        let mut elements = Vec::new();
        let note_blinding = self.note_blinding();
        elements.extend([note_blinding]);
        elements.extend(self.value().to_field_elements()?);
        elements.extend(self.address().to_field_elements()?);
        Some(elements)
    }
}

// We do not implement `R1CSVar` for `NoteVar` since the associated type
// should be `Note` which we cannot construct from the R1CS variable
// since we do not have the rseed in-circuit.

impl NoteVar {
    pub fn commit(&self) -> Result<StateCommitmentVar, SynthesisError> {
        let cs = self.amount().cs();
        let domain_sep = FqVar::new_constant(cs.clone(), *NOTECOMMIT_DOMAIN_SEP)?;
        let compressed_g_d = self.address.diversified_generator().compress_to_field()?;

        let commitment = poseidon377::r1cs::hash_6(
            cs,
            &domain_sep,
            (
                self.note_blinding.clone(),
                self.value.amount(),
                self.value.asset_id(),
                compressed_g_d,
                self.address.transmission_key().compress_to_field()?,
                self.address.clue_key(),
            ),
        )?;

        Ok(StateCommitmentVar { inner: commitment })
    }
}