/ console / program / src / data / record / is_owner.rs
is_owner.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm 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<N: Network> Record<N, Ciphertext<N>> {
 19      /// Returns `true` if the given view key corresponds to the owner of the record.
 20      /// Decrypts `self` into plaintext using the given view key.
 21      pub fn is_owner(&self, view_key: &ViewKey<N>) -> bool {
 22          // Compute the address.
 23          let address = view_key.to_address();
 24          // Check if the address is the owner.
 25          self.is_owner_with_address_x_coordinate(view_key, &address.to_x_coordinate())
 26      }
 27  
 28      /// Returns `true` if the given view key and address x-coordinate corresponds to the owner of the record.
 29      /// Decrypts `self` into plaintext using the x-coordinate of the address corresponding to the given view key.
 30      pub fn is_owner_with_address_x_coordinate(&self, view_key: &ViewKey<N>, address_x_coordinate: &Field<N>) -> bool {
 31          // In debug mode, check that the address corresponds to the given view key.
 32          debug_assert_eq!(
 33              &view_key.to_address().to_x_coordinate(),
 34              address_x_coordinate,
 35              "Failed to check record - view key and address do not match"
 36          );
 37  
 38          match &self.owner {
 39              // If the owner is public, check if the address is the owner.
 40              Owner::Public(owner) => &owner.to_x_coordinate() == address_x_coordinate,
 41              // If the owner is private, decrypt the owner to check if it matches the address.
 42              Owner::Private(ciphertext) => {
 43                  // Compute the record view key.
 44                  let record_view_key = (self.nonce * **view_key).to_x_coordinate();
 45                  // Compute the 0th randomizer.
 46                  let randomizer = N::hash_many_psd8(&[N::encryption_domain(), record_view_key], 1);
 47                  // Decrypt the owner.
 48                  let owner_x = ciphertext[0] - randomizer[0];
 49                  // Compare the x coordinates of computed and supplied addresses.
 50                  // We can skip recomputing the address from `owner_x` due to the following reasoning.
 51                  // First, the transaction SNARK that generated the ciphertext would have checked that the ciphertext encrypts a valid address.
 52                  // Now, since a valid address is an element of the prime-order subgroup of the curve, we know that the encrypted x-coordinate corresponds to a prime-order element.
 53                  // Finally, since the SNARK + hybrid encryption
 54                  // together are an authenticated encryption scheme, we know that the ciphertext has not been malleated.
 55                  // Thus overall we know that if the x-coordinate matches that of `address`, then the underlying `address`es must also match.
 56                  // Therefore we can skip recomputing the address from `owner_x` and instead compare the x-coordinates directly.
 57                  &owner_x == address_x_coordinate
 58              }
 59          }
 60      }
 61  }
 62  
 63  #[cfg(test)]
 64  mod tests {
 65      use super::*;
 66      use crate::Literal;
 67      use alphavm_console_account::PrivateKey;
 68      use alphavm_console_network::MainnetV0;
 69      use alphavm_console_types::Field;
 70  
 71      type CurrentNetwork = MainnetV0;
 72  
 73      const ITERATIONS: u64 = 1_000;
 74  
 75      fn check_is_owner<N: Network>(
 76          view_key: ViewKey<N>,
 77          owner: Owner<N, Plaintext<N>>,
 78          rng: &mut TestRng,
 79      ) -> Result<()> {
 80          // Prepare the record.
 81          let randomizer = Scalar::rand(rng);
 82          let record = Record {
 83              owner,
 84              data: IndexMap::from_iter(vec![
 85                  (Identifier::from_str("a")?, Entry::Private(Plaintext::from(Literal::Field(Field::rand(rng))))),
 86                  (Identifier::from_str("b")?, Entry::Private(Plaintext::from(Literal::Scalar(Scalar::rand(rng))))),
 87              ]),
 88              nonce: N::g_scalar_multiply(&randomizer),
 89              version: U8::rand(rng),
 90          };
 91  
 92          // Encrypt the record.
 93          let ciphertext = record.encrypt(randomizer)?;
 94  
 95          // Ensure the record belongs to the owner.
 96          assert!(ciphertext.is_owner(&view_key));
 97  
 98          // Sample a random view key and address.
 99          let private_key = PrivateKey::<N>::new(rng)?;
100          let view_key = ViewKey::try_from(&private_key)?;
101  
102          // Ensure the random address is not the owner.
103          assert!(!ciphertext.is_owner(&view_key));
104  
105          Ok(())
106      }
107  
108      #[test]
109      fn test_is_owner() -> Result<()> {
110          let mut rng = TestRng::default();
111  
112          for _ in 0..ITERATIONS {
113              // Sample a view key and address.
114              let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng)?;
115              let view_key = ViewKey::try_from(&private_key)?;
116              let address = Address::try_from(&private_key)?;
117  
118              // Public owner.
119              let owner = Owner::Public(address);
120              check_is_owner::<CurrentNetwork>(view_key, owner, &mut rng)?;
121  
122              // Private owner.
123              let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
124              check_is_owner::<CurrentNetwork>(view_key, owner, &mut rng)?;
125          }
126          Ok(())
127      }
128  }