/ circuit / program / src / data / record / equal.rs
equal.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use super::*;
 17  
 18  impl<A: Alpha, Private: Visibility<A>> Equal<Self> for Record<A, Private> {
 19      type Output = Boolean<A>;
 20  
 21      /// Returns `true` if `self` and `other` are equal.
 22      ///
 23      /// Note: This method does **not** check the `nonce` equality.
 24      fn is_equal(&self, other: &Self) -> Self::Output {
 25          // Ensure the `data` are the same length.
 26          if self.data.len() != other.data.len() {
 27              return Boolean::constant(false);
 28          }
 29          // Recursively check each entry for equality.
 30          let mut equal = Boolean::constant(true);
 31          for ((name_a, entry_a), (name_b, entry_b)) in self.data.iter().zip_eq(other.data.iter()) {
 32              equal = equal & name_a.is_equal(name_b) & entry_a.is_equal(entry_b);
 33          }
 34  
 35          // Check the `owner`, `data`, `nonce`, and `version`.
 36          self.owner.is_equal(&other.owner)
 37              & equal
 38              & self.nonce.is_equal(&other.nonce)
 39              & self.version.is_equal(&other.version)
 40      }
 41  
 42      /// Returns `true` if `self` and `other` are *not* equal.
 43      ///
 44      /// Note: This method does **not** check the `nonce` equality.
 45      fn is_not_equal(&self, other: &Self) -> Self::Output {
 46          // Check the `data` lengths.
 47          if self.data.len() != other.data.len() {
 48              return Boolean::constant(true);
 49          }
 50          // Recursively check each entry for inequality.
 51          let mut not_equal = Boolean::constant(false);
 52          for ((name_a, entry_a), (name_b, entry_b)) in self.data.iter().zip_eq(other.data.iter()) {
 53              not_equal = not_equal | name_a.is_not_equal(name_b) | entry_a.is_not_equal(entry_b);
 54          }
 55  
 56          // Check the `owner`, `data`, `nonce`, and `version`.
 57          self.owner.is_not_equal(&other.owner)
 58              | not_equal
 59              | self.nonce.is_not_equal(&other.nonce)
 60              | self.version.is_not_equal(&other.version)
 61      }
 62  }
 63  
 64  #[cfg(test)]
 65  mod tests {
 66      use super::*;
 67      use crate::Circuit;
 68  
 69      fn sample_record(mode: Mode) -> Record<Circuit, Plaintext<Circuit>> {
 70          let record = console::Record::<
 71              <Circuit as Environment>::Network,
 72              console::Plaintext<<Circuit as Environment>::Network>,
 73          >::from_str(
 74              r"{
 75      owner: dx14tlamssdmg3d0p5zmljma573jghe2q9n6wz29qf36re2glcedcpqfg4add.private,
 76      a: true.private,
 77      b: 123456789field.public,
 78      c: 0group.private,
 79      d: {
 80          e: true.private,
 81          f: 123456789field.private,
 82          g: 0group.private
 83      },
 84      _nonce: 0group.public,
 85      _version: 0u8.public
 86  }",
 87          )
 88          .unwrap();
 89          Record::new(mode, record)
 90      }
 91  
 92      fn sample_mismatched_record(mode: Mode) -> Record<Circuit, Plaintext<Circuit>> {
 93          let record = console::Record::<
 94              <Circuit as Environment>::Network,
 95              console::Plaintext<<Circuit as Environment>::Network>,
 96          >::from_str(
 97              r"{
 98      owner: dx14tlamssdmg3d0p5zmljma573jghe2q9n6wz29qf36re2glcedcpqfg4add.private,
 99      a: true.public,
100      b: 123456789field.public,
101      c: 0group.private,
102      d: {
103          e: true.private,
104          f: 123456789field.private,
105          g: 0group.private
106      },
107      _nonce: 0group.public,
108      _version: 0u8.public
109  }",
110          )
111          .unwrap();
112          Record::new(mode, record)
113      }
114  
115      fn check_is_equal(
116          mode: Mode,
117          num_constants: u64,
118          num_public: u64,
119          num_private: u64,
120          num_constraints: u64,
121      ) -> Result<()> {
122          // Sample the record.
123          let record = sample_record(mode);
124          let mismatched_record = sample_mismatched_record(mode);
125  
126          Circuit::scope(format!("{mode}"), || {
127              let candidate = record.is_equal(&record);
128              assert!(candidate.eject_value());
129              assert_scope!(num_constants, num_public, num_private, num_constraints);
130          });
131  
132          Circuit::scope(format!("{mode}"), || {
133              let candidate = record.is_equal(&mismatched_record);
134              assert!(!candidate.eject_value());
135          });
136  
137          Circuit::reset();
138          Ok(())
139      }
140  
141      fn check_is_not_equal(
142          mode: Mode,
143          num_constants: u64,
144          num_public: u64,
145          num_private: u64,
146          num_constraints: u64,
147      ) -> Result<()> {
148          // Sample the record.
149          let record = sample_record(mode);
150          let mismatched_record = sample_mismatched_record(mode);
151  
152          Circuit::scope(format!("{mode}"), || {
153              let candidate = record.is_not_equal(&mismatched_record);
154              assert!(candidate.eject_value());
155              assert_scope!(num_constants, num_public, num_private, num_constraints);
156          });
157  
158          Circuit::scope(format!("{mode}"), || {
159              let candidate = record.is_not_equal(&record);
160              assert!(!candidate.eject_value());
161          });
162  
163          Circuit::reset();
164          Ok(())
165      }
166  
167      #[test]
168      fn test_is_equal_constant() -> Result<()> {
169          // Note: This is correct. At this (high) level of a program, we override the default mode in the `Record` case,
170          // based on the user-defined visibility in the record type. Thus, we have nonzero private and constraint values.
171          check_is_equal(Mode::Constant, 18, 0, 25, 36)
172      }
173  
174      #[test]
175      fn test_is_equal_public() -> Result<()> {
176          check_is_equal(Mode::Public, 18, 0, 25, 36)
177      }
178  
179      #[test]
180      fn test_is_equal_private() -> Result<()> {
181          check_is_equal(Mode::Private, 18, 0, 25, 36)
182      }
183  
184      #[test]
185      fn test_is_not_equal_constant() -> Result<()> {
186          // Note: This is correct. At this (high) level of a program, we override the default mode in the `Record` case,
187          // based on the user-defined visibility in the record type. Thus, we have nonzero private and constraint values.
188          check_is_not_equal(Mode::Constant, 7, 0, 29, 29)
189      }
190  
191      #[test]
192      fn test_is_not_equal_public() -> Result<()> {
193          check_is_not_equal(Mode::Public, 7, 0, 29, 29)
194      }
195  
196      #[test]
197      fn test_is_not_equal_private() -> Result<()> {
198          check_is_not_equal(Mode::Private, 7, 0, 29, 29)
199      }
200  }