darkfi_sdk/crypto/constants/
sinsemilla.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19//! Sinsemilla generators
20use super::{util::i2lebsp, OrchardFixedBases, OrchardFixedBasesFull};
21use halo2_gadgets::sinsemilla::{CommitDomains, HashDomains};
22
23use pasta_curves::{arithmetic::CurveAffine, group::ff::PrimeField, pallas};
24
25/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$
26pub const K: usize = 10;
27
28/// $\frac{1}{2^K}$
29pub const INV_TWO_POW_K: [u8; 32] = [
30    1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31    0, 0, 0, 0, 0, 240, 63,
32];
33
34/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order
35/// of Pallas.
36pub const C: usize = 253;
37
38/// $\ell^\mathsf{Orchard}_\mathsf{Merkle}$
39pub(crate) const L_ORCHARD_MERKLE: usize = 255;
40
41/// SWU hash-to-curve personalization for the Merkle CRH generator
42pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
43
44/// Generator used in SinsemillaHashToPoint for note commitment
45pub const Q_NOTE_COMMITMENT_M_GENERATOR: ([u8; 32], [u8; 32]) = (
46    [
47        93, 116, 168, 64, 9, 186, 14, 50, 42, 221, 70, 253, 90, 15, 150, 197, 93, 237, 176, 121,
48        180, 242, 159, 247, 13, 205, 251, 86, 160, 7, 128, 23,
49    ],
50    [
51        99, 172, 73, 115, 90, 10, 39, 135, 158, 94, 219, 129, 136, 18, 34, 136, 44, 201, 244, 110,
52        217, 194, 190, 78, 131, 112, 198, 138, 147, 88, 160, 50,
53    ],
54);
55
56/// Generator used in SinsemillaHashToPoint for IVK commitment
57pub const Q_COMMIT_IVK_M_GENERATOR: ([u8; 32], [u8; 32]) = (
58    [
59        242, 130, 15, 121, 146, 47, 203, 107, 50, 162, 40, 81, 36, 204, 27, 66, 250, 65, 162, 90,
60        184, 129, 204, 125, 17, 200, 169, 74, 241, 12, 188, 5,
61    ],
62    [
63        190, 222, 173, 207, 206, 229, 90, 190, 241, 165, 109, 201, 29, 53, 196, 70, 75, 5, 222, 32,
64        70, 7, 89, 239, 230, 190, 26, 212, 246, 76, 1, 27,
65    ],
66);
67
68/// Generator used in SinsemillaHashToPoint for Merkle collision-resistant hash
69pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = (
70    [
71        160, 198, 41, 127, 249, 199, 185, 248, 112, 16, 141, 192, 85, 185, 190, 201, 153, 14, 137,
72        239, 90, 54, 15, 160, 185, 24, 168, 99, 150, 210, 22, 22,
73    ],
74    [
75        98, 234, 242, 37, 206, 174, 233, 134, 150, 21, 116, 5, 234, 150, 28, 226, 121, 89, 163, 79,
76        62, 242, 196, 45, 153, 32, 175, 227, 163, 66, 134, 53,
77    ],
78);
79
80#[allow(dead_code)]
81pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 {
82    assert!(bits.len() == K);
83    bits.iter().enumerate().fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 })
84}
85
86/// The sequence of K bits in little-endian order representing an integer
87/// up to `2^K` - 1.
88pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] {
89    assert!(int < (1 << K));
90    i2lebsp(int as u64)
91}
92
93#[derive(Clone, Debug, Eq, PartialEq)]
94pub enum OrchardHashDomains {
95    NoteCommit,
96    CommitIvk,
97    MerkleCrh,
98}
99
100#[allow(non_snake_case)]
101impl HashDomains<pallas::Affine> for OrchardHashDomains {
102    fn Q(&self) -> pallas::Affine {
103        match self {
104            OrchardHashDomains::CommitIvk => pallas::Affine::from_xy(
105                pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap(),
106                pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap(),
107            )
108            .unwrap(),
109            OrchardHashDomains::NoteCommit => pallas::Affine::from_xy(
110                pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(),
111                pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(),
112            )
113            .unwrap(),
114            OrchardHashDomains::MerkleCrh => pallas::Affine::from_xy(
115                pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap(),
116                pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap(),
117            )
118            .unwrap(),
119        }
120    }
121}
122
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub enum OrchardCommitDomains {
125    NoteCommit,
126    CommitIvk,
127}
128
129impl CommitDomains<pallas::Affine, OrchardFixedBases, OrchardHashDomains> for OrchardCommitDomains {
130    fn r(&self) -> OrchardFixedBasesFull {
131        match self {
132            Self::NoteCommit => OrchardFixedBasesFull::NoteCommitR,
133            Self::CommitIvk => OrchardFixedBasesFull::CommitIvkR,
134        }
135    }
136
137    fn hash_domain(&self) -> OrchardHashDomains {
138        match self {
139            Self::NoteCommit => OrchardHashDomains::NoteCommit,
140            Self::CommitIvk => OrchardHashDomains::CommitIvk,
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::crypto::constants::fixed_bases::{
149        COMMIT_IVK_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION,
150    };
151    use halo2_gadgets::sinsemilla::primitives::{CommitDomain, HashDomain};
152    use pasta_curves::group::Curve;
153    use rand::{rngs::OsRng, Rng};
154
155    #[test]
156    // Nodes in the Merkle tree are Pallas base field elements.
157    fn l_orchard_merkle() {
158        assert_eq!(super::L_ORCHARD_MERKLE, pallas::Base::NUM_BITS as usize);
159    }
160
161    #[test]
162    fn lebs2ip_k_round_trip() {
163        let mut rng = OsRng;
164        {
165            let int = rng.gen_range(0..(1 << K));
166            assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int);
167        }
168
169        assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0);
170        assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1);
171    }
172
173    #[test]
174    fn i2lebsp_k_round_trip() {
175        {
176            let bitstring = [0; K].map(|_| rand::random());
177            assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring);
178        }
179
180        {
181            let bitstring = [false; K];
182            assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring);
183        }
184
185        {
186            let bitstring = [true; K];
187            assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring);
188        }
189    }
190
191    #[test]
192    fn q_note_commitment_m() {
193        let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION);
194        let point = domain.Q();
195        let coords = point.to_affine().coordinates().unwrap();
196
197        assert_eq!(*coords.x(), pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap());
198        assert_eq!(*coords.y(), pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap());
199    }
200
201    #[test]
202    fn q_commit_ivk_m() {
203        let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
204        let point = domain.Q();
205        let coords = point.to_affine().coordinates().unwrap();
206
207        assert_eq!(*coords.x(), pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap());
208        assert_eq!(*coords.y(), pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap());
209    }
210
211    #[test]
212    fn q_merkle_crh() {
213        let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION);
214        let point = domain.Q();
215        let coords = point.to_affine().coordinates().unwrap();
216
217        assert_eq!(*coords.x(), pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap());
218        assert_eq!(*coords.y(), pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap());
219    }
220
221    #[test]
222    fn inv_two_pow_k() {
223        let two_pow_k = pallas::Base::from(1u64 << K);
224        let inv_two_pow_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap();
225
226        assert_eq!(two_pow_k * inv_two_pow_k, pallas::Base::one());
227    }
228}