From 5c2b56c78ea16d55dc4f25a3e0e74a3e80660bf0 Mon Sep 17 00:00:00 2001
From: Boog900 <boog900@tutanota.com>
Date: Thu, 6 Mar 2025 19:03:25 +0000
Subject: [PATCH] Update to experimental monero oxide api (#386)

* add specific method for context

* add new statemachine for tx verification

* fix consensus crates build

* working builds

* fix CI

* add docs

* fix CI

* fix docs

* fix clippy

* cleanup

* add docs to `blockchain_context`

* fix doc tests

* add output cache

* new monero-serai

* todo

* todo

* Revert "new monero-serai"

This reverts commit fe3f6acc676fe59e794d5f92f07f76445db35199.

* use indexmap to request outputs

* clean up

* fix typos

* fix CI

* fix cargo hack

* fix reorgs

* check if a block is already present before adding it to the alt block cache

* fmt

* update to new monero oxide API

* fmt & fix cache

* update config values

* fix tests

* fix no-std builds
---
 Cargo.lock                                    | 30 ++++++++--------
 Cargo.toml                                    |  6 ++--
 binaries/cuprated/config/0.0.1.toml           |  4 +--
 .../src/blockchain/manager/handler.rs         |  2 +-
 binaries/cuprated/src/config/p2p.rs           |  4 +--
 consensus/rules/src/transactions.rs           | 14 +++++---
 .../rules/src/transactions/contextual_data.rs |  6 ++--
 consensus/rules/src/transactions/ring_ct.rs   | 23 +++++++++---
 consensus/rules/src/transactions/tests.rs     |  8 ++---
 consensus/src/transactions.rs                 |  2 +-
 consensus/src/transactions/contextual_data.rs | 34 ++++++------------
 consensus/tests/verify_correct_txs.rs         | 17 +++------
 helper/src/crypto.rs                          | 35 ++++++++++++-------
 helper/src/tx.rs                              |  4 +--
 storage/blockchain/src/ops/output.rs          | 13 ++-----
 storage/blockchain/src/ops/tx.rs              |  6 ++--
 storage/txpool/src/ops/key_images.rs          |  2 +-
 types/src/json/tx.rs                          |  4 +--
 types/src/output_cache.rs                     |  7 ++--
 types/src/types.rs                            |  6 ++--
 20 files changed, 113 insertions(+), 114 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 3ba27d66..2ad83bd3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1152,7 +1152,7 @@ dependencies = [
 [[package]]
 name = "dalek-ff-group"
 version = "0.4.1"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "crypto-bigint",
  "curve25519-dalek",
@@ -1317,7 +1317,7 @@ dependencies = [
 [[package]]
 name = "flexible-transcript"
 version = "0.3.2"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "blake2",
  "digest",
@@ -2023,7 +2023,7 @@ dependencies = [
 [[package]]
 name = "monero-address"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "monero-io",
@@ -2036,7 +2036,7 @@ dependencies = [
 [[package]]
 name = "monero-borromean"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "monero-generators",
@@ -2049,7 +2049,7 @@ dependencies = [
 [[package]]
 name = "monero-bulletproofs"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "monero-generators",
@@ -2064,7 +2064,7 @@ dependencies = [
 [[package]]
 name = "monero-clsag"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "dalek-ff-group",
@@ -2084,7 +2084,7 @@ dependencies = [
 [[package]]
 name = "monero-generators"
 version = "0.4.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "dalek-ff-group",
@@ -2098,7 +2098,7 @@ dependencies = [
 [[package]]
 name = "monero-io"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "std-shims",
@@ -2107,7 +2107,7 @@ dependencies = [
 [[package]]
 name = "monero-mlsag"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "monero-generators",
@@ -2121,7 +2121,7 @@ dependencies = [
 [[package]]
 name = "monero-primitives"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "monero-generators",
@@ -2134,7 +2134,7 @@ dependencies = [
 [[package]]
 name = "monero-rpc"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "hex",
@@ -2150,7 +2150,7 @@ dependencies = [
 [[package]]
 name = "monero-serai"
 version = "0.1.4-alpha"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "curve25519-dalek",
  "hex-literal",
@@ -2168,7 +2168,7 @@ dependencies = [
 [[package]]
 name = "monero-simple-request-rpc"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "digest_auth",
  "hex",
@@ -2865,7 +2865,7 @@ dependencies = [
 [[package]]
 name = "simple-request"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "http-body-util",
  "hyper",
@@ -2931,7 +2931,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 [[package]]
 name = "std-shims"
 version = "0.1.1"
-source = "git+https://github.com/Cuprate/serai.git?rev=e6fdef6#e6fdef6d0b4481932ac9647796eb3fa56197ed66"
+source = "git+https://github.com/Cuprate/serai.git?rev=e6ae8c2#e6ae8c2b1f9d791f35ea225032cc0a3f79dec99d"
 dependencies = [
  "hashbrown 0.14.5",
  "spin",
diff --git a/Cargo.toml b/Cargo.toml
index 70cf0b5b..1192a6b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -121,7 +121,7 @@ futures               = { version = "0.3", default-features = false }
 hex                   = { version = "0.4", default-features = false }
 hex-literal           = { version = "0.4", default-features = false }
 indexmap              = { version = "2", default-features = false }
-monero-serai          = { git = "https://github.com/Cuprate/serai.git", rev = "e6fdef6", default-features = false }
+monero-serai          = { git = "https://github.com/Cuprate/serai.git", rev = "e6ae8c2", default-features = false }
 nu-ansi-term          = { version = "0.46", default-features = false }
 paste                 = { version = "1", default-features = false }
 pin-project           = { version = "1", default-features = false }
@@ -145,8 +145,8 @@ tracing-subscriber    = { version = "0.3", default-features = false }
 tracing               = { version = "0.1", default-features = false }
 
 ## workspace.dev-dependencies
-monero-rpc                = { git = "https://github.com/Cuprate/serai.git", rev = "e6fdef6" }
-monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "e6fdef6" }
+monero-rpc                = { git = "https://github.com/Cuprate/serai.git", rev = "e6ae8c2" }
+monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "e6ae8c2" }
 tempfile                  = { version = "3" }
 pretty_assertions         = { version = "1" }
 proptest                  = { version = "1" }
diff --git a/binaries/cuprated/config/0.0.1.toml b/binaries/cuprated/config/0.0.1.toml
index 95804dcc..75995e06 100644
--- a/binaries/cuprated/config/0.0.1.toml
+++ b/binaries/cuprated/config/0.0.1.toml
@@ -43,9 +43,9 @@ peer_save_period = { secs = 90, nanos = 0 }
 ## The block downloader config.
 [p2p.block_downloader]
 ## The size of the buffer of sequential blocks waiting to be verified and added to the chain (bytes).
-buffer_bytes = 50_000_000
+buffer_bytes = 1_000_000_000
 ## The size of the queue of blocks which are waiting for a parent block to be downloaded (bytes).
-in_progress_queue_bytes = 50_000_000
+in_progress_queue_bytes = 500_000_000
 ## The target size of a batch of blocks (bytes), must not exceed 100MB.
 target_batch_bytes = 10_000_000
 ## The amount of time between checking the pool of connected peers for free peers to download blocks.
diff --git a/binaries/cuprated/src/blockchain/manager/handler.rs b/binaries/cuprated/src/blockchain/manager/handler.rs
index c1ad7b17..0c8264bf 100644
--- a/binaries/cuprated/src/blockchain/manager/handler.rs
+++ b/binaries/cuprated/src/blockchain/manager/handler.rs
@@ -452,7 +452,7 @@ impl super::BlockchainManager {
             .iter()
             .flat_map(|tx| {
                 tx.tx.prefix().inputs.iter().map(|input| match input {
-                    Input::ToKey { key_image, .. } => key_image.compress().0,
+                    Input::ToKey { key_image, .. } => key_image.0,
                     Input::Gen(_) => unreachable!(),
                 })
             })
diff --git a/binaries/cuprated/src/config/p2p.rs b/binaries/cuprated/src/config/p2p.rs
index da9c7bc2..0fe71e53 100644
--- a/binaries/cuprated/src/config/p2p.rs
+++ b/binaries/cuprated/src/config/p2p.rs
@@ -47,8 +47,8 @@ impl From<BlockDownloaderConfig> for cuprate_p2p::block_downloader::BlockDownloa
 impl Default for BlockDownloaderConfig {
     fn default() -> Self {
         Self {
-            buffer_bytes: 50_000_000,
-            in_progress_queue_bytes: 50_000_000,
+            buffer_bytes: 1_000_000_000,
+            in_progress_queue_bytes: 500_000_000,
             check_client_pool_interval: Duration::from_secs(30),
             target_batch_bytes: 10_000_000,
         }
diff --git a/consensus/rules/src/transactions.rs b/consensus/rules/src/transactions.rs
index b4eac191..bb3bec78 100644
--- a/consensus/rules/src/transactions.rs
+++ b/consensus/rules/src/transactions.rs
@@ -1,17 +1,20 @@
 use std::cmp::Ordering;
 
+use curve25519_dalek::EdwardsPoint;
 use monero_serai::{
+    io::decompress_point,
     ringct::RctType,
     transaction::{Input, Output, Timelock, Transaction},
 };
 
-pub use cuprate_types::TxVersion;
-
 use crate::{
     batch_verifier::BatchVerifier, blocks::penalty_free_zone, check_point_canonically_encoded,
     is_decomposed_amount, HardFork,
 };
 
+// re-export.
+pub use cuprate_types::TxVersion;
+
 mod contextual_data;
 mod ring_ct;
 mod ring_signatures;
@@ -327,7 +330,10 @@ fn check_key_images(input: &Input) -> Result<(), TransactionError> {
     match input {
         Input::ToKey { key_image, .. } => {
             // this happens in monero-serai but we may as well duplicate the check.
-            if !key_image.is_torsion_free() {
+            if !decompress_point(*key_image)
+                .as_ref()
+                .is_some_and(EdwardsPoint::is_torsion_free)
+            {
                 return Err(TransactionError::KeyImageIsNotInPrimeSubGroup);
             }
         }
@@ -388,7 +394,7 @@ fn check_ring_members_unique(input: &Input, hf: HardFork) -> Result<(), Transact
 /// ref: <https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#sorted-inputs>
 fn check_inputs_sorted(inputs: &[Input], hf: HardFork) -> Result<(), TransactionError> {
     let get_ki = |inp: &Input| match inp {
-        Input::ToKey { key_image, .. } => Ok(key_image.compress().to_bytes()),
+        Input::ToKey { key_image, .. } => Ok(key_image.to_bytes()),
         Input::Gen(_) => Err(TransactionError::IncorrectInputType),
     };
 
diff --git a/consensus/rules/src/transactions/contextual_data.rs b/consensus/rules/src/transactions/contextual_data.rs
index 35f8e47d..50e5895d 100644
--- a/consensus/rules/src/transactions/contextual_data.rs
+++ b/consensus/rules/src/transactions/contextual_data.rs
@@ -1,6 +1,6 @@
 use std::cmp::{max, min};
 
-use curve25519_dalek::EdwardsPoint;
+use curve25519_dalek::edwards::CompressedEdwardsY;
 use indexmap::{IndexMap, IndexSet};
 use monero_serai::transaction::{Input, Timelock};
 
@@ -57,9 +57,9 @@ pub fn insert_ring_member_ids(
 #[derive(Debug)]
 pub enum Rings {
     /// Legacy, pre-ringCT, rings.
-    Legacy(Vec<Vec<EdwardsPoint>>),
+    Legacy(Vec<Vec<CompressedEdwardsY>>),
     /// `RingCT` rings, (outkey, amount commitment).
-    RingCT(Vec<Vec<[EdwardsPoint; 2]>>),
+    RingCT(Vec<Vec<[CompressedEdwardsY; 2]>>),
 }
 
 /// Information on the outputs the transaction is referencing for inputs (ring members).
diff --git a/consensus/rules/src/transactions/ring_ct.rs b/consensus/rules/src/transactions/ring_ct.rs
index 32cedd47..1d56e90e 100644
--- a/consensus/rules/src/transactions/ring_ct.rs
+++ b/consensus/rules/src/transactions/ring_ct.rs
@@ -2,6 +2,7 @@ use curve25519_dalek::{EdwardsPoint, Scalar};
 use hex_literal::hex;
 use monero_serai::{
     generators::H,
+    io::decompress_point,
     ringct::{
         clsag::ClsagError,
         mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix},
@@ -74,9 +75,21 @@ fn simple_type_balances(rct_sig: &RctProofs) -> Result<(), RingCTError> {
         }
     };
 
-    let sum_inputs = pseudo_outs.iter().sum::<EdwardsPoint>();
-    let sum_outputs =
-        rct_sig.base.commitments.iter().sum::<EdwardsPoint>() + Scalar::from(rct_sig.base.fee) * *H;
+    let sum_inputs = pseudo_outs
+        .iter()
+        .copied()
+        .map(decompress_point)
+        .sum::<Option<EdwardsPoint>>()
+        .ok_or(RingCTError::SimpleAmountDoNotBalance)?;
+    let sum_outputs = rct_sig
+        .base
+        .commitments
+        .iter()
+        .copied()
+        .map(decompress_point)
+        .sum::<Option<EdwardsPoint>>()
+        .ok_or(RingCTError::SimpleAmountDoNotBalance)?
+        + Scalar::from(rct_sig.base.fee) * *H;
 
     if sum_inputs == sum_outputs {
         Ok(())
@@ -178,7 +191,7 @@ pub(crate) fn check_input_signatures(
                 .collect::<Vec<_>>();
 
             let mut matrix =
-                AggregateRingMatrixBuilder::new(&proofs.base.commitments, proofs.base.fee);
+                AggregateRingMatrixBuilder::new(&proofs.base.commitments, proofs.base.fee)?;
 
             rings.iter().try_for_each(|ring| matrix.push_ring(ring))?;
 
@@ -210,7 +223,7 @@ pub(crate) fn check_input_signatures(
                     panic!("How did we build a ring with no decoys?");
                 };
 
-                Ok(clsags.verify(ring, key_image, pseudo_out, msg)?)
+                Ok(clsags.verify(ring.clone(), key_image, pseudo_out, msg)?)
             }),
     }
 }
diff --git a/consensus/rules/src/transactions/tests.rs b/consensus/rules/src/transactions/tests.rs
index e154396b..2ce37fca 100644
--- a/consensus/rules/src/transactions/tests.rs
+++ b/consensus/rules/src/transactions/tests.rs
@@ -1,7 +1,7 @@
 use std::ops::Range;
 
 use curve25519_dalek::{
-    constants::{ED25519_BASEPOINT_POINT, EIGHT_TORSION},
+    constants::{ED25519_BASEPOINT_COMPRESSED, EIGHT_TORSION},
     edwards::CompressedEdwardsY,
     EdwardsPoint,
 };
@@ -92,7 +92,7 @@ fn test_decoy_info() {
 fn test_torsion_ki() {
     for &key_image in &EIGHT_TORSION[1..] {
         assert!(check_key_images(&Input::ToKey {
-            key_image,
+            key_image: key_image.compress(),
             amount: None,
             key_offsets: vec![],
         })
@@ -262,13 +262,13 @@ proptest! {
     #[test]
     fn test_check_input_has_decoys(key_offsets in vec(any::<u64>(), 1..10_000)) {
         assert!(check_input_has_decoys(&Input::ToKey {
-            key_image: ED25519_BASEPOINT_POINT,
+            key_image: ED25519_BASEPOINT_COMPRESSED,
             amount: None,
             key_offsets,
         }).is_ok());
 
         assert!(check_input_has_decoys(&Input::ToKey {
-            key_image: ED25519_BASEPOINT_POINT,
+            key_image: ED25519_BASEPOINT_COMPRESSED,
             amount: None,
             key_offsets: vec![],
         }).is_err());
diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs
index 7b6222eb..ec6dfdad 100644
--- a/consensus/src/transactions.rs
+++ b/consensus/src/transactions.rs
@@ -289,7 +289,7 @@ pub(crate) async fn check_kis_unique<D: Database>(
     txs.try_for_each(|tx| {
         tx.tx.prefix().inputs.iter().try_for_each(|input| {
             if let Input::ToKey { key_image, .. } = input {
-                if !spent_kis.insert(key_image.compress().0) {
+                if !spent_kis.insert(key_image.0) {
                     tracing::debug!("Duplicate key image found in batch.");
                     return Err(ConsensusError::Transaction(TransactionError::KeyImageSpent));
                 }
diff --git a/consensus/src/transactions/contextual_data.rs b/consensus/src/transactions/contextual_data.rs
index 72236690..46533731 100644
--- a/consensus/src/transactions/contextual_data.rs
+++ b/consensus/src/transactions/contextual_data.rs
@@ -96,27 +96,19 @@ pub fn new_ring_member_info(
                     .collect::<Vec<_>>()
             })
             .collect(),
-        rings: new_rings(used_outs, tx_version)?,
+        rings: new_rings(used_outs, tx_version),
         decoy_info,
     })
 }
 
 /// Builds the [`Rings`] for the transaction inputs, from the given outputs.
-fn new_rings(
-    outputs: Vec<Vec<OutputOnChain>>,
-    tx_version: TxVersion,
-) -> Result<Rings, TransactionError> {
-    Ok(match tx_version {
+fn new_rings(outputs: Vec<Vec<OutputOnChain>>, tx_version: TxVersion) -> Rings {
+    match tx_version {
         TxVersion::RingSignatures => Rings::Legacy(
             outputs
                 .into_iter()
-                .map(|inp_outs| {
-                    inp_outs
-                        .into_iter()
-                        .map(|out| out.key.ok_or(TransactionError::RingMemberNotFoundOrInvalid))
-                        .collect::<Result<Vec<_>, TransactionError>>()
-                })
-                .collect::<Result<Vec<_>, TransactionError>>()?,
+                .map(|inp_outs| inp_outs.into_iter().map(|out| out.key).collect::<Vec<_>>())
+                .collect::<Vec<_>>(),
         ),
         TxVersion::RingCT => Rings::RingCT(
             outputs
@@ -124,18 +116,12 @@ fn new_rings(
                 .map(|inp_outs| {
                     inp_outs
                         .into_iter()
-                        .map(|out| {
-                            Ok([
-                                out.key
-                                    .ok_or(TransactionError::RingMemberNotFoundOrInvalid)?,
-                                out.commitment,
-                            ])
-                        })
-                        .collect::<Result<_, TransactionError>>()
+                        .map(|out| [out.key, out.commitment])
+                        .collect::<_>()
                 })
-                .collect::<Result<_, _>>()?,
+                .collect::<_>(),
         ),
-    })
+    }
 }
 
 /// Retrieves an [`OutputCache`] for the list of transactions.
@@ -158,7 +144,7 @@ pub async fn get_output_cache<D: Database>(
         .call(BlockchainReadRequest::Outputs(output_ids))
         .await?
     else {
-        panic!("Database sent incorrect response!")
+        unreachable!();
     };
 
     Ok(outputs)
diff --git a/consensus/tests/verify_correct_txs.rs b/consensus/tests/verify_correct_txs.rs
index 1166e7aa..0c8171e0 100644
--- a/consensus/tests/verify_correct_txs.rs
+++ b/consensus/tests/verify_correct_txs.rs
@@ -7,7 +7,7 @@ use std::{
     sync::Arc,
 };
 
-use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY};
+use curve25519_dalek::{constants::ED25519_BASEPOINT_COMPRESSED, edwards::CompressedEdwardsY};
 use indexmap::IndexMap;
 use monero_serai::transaction::{Timelock, Transaction};
 use tower::service_fn;
@@ -71,13 +71,8 @@ macro_rules! test_verify_valid_v2_tx {
                 OutputOnChain {
                     height: 0,
                     time_lock: Timelock::None,
-                    commitment: CompressedEdwardsY::from_slice(&hex_literal::hex!($commitment))
-                        .unwrap()
-                        .decompress()
-                        .unwrap(),
-                    key: CompressedEdwardsY::from_slice(&hex_literal::hex!($ring_member))
-                        .unwrap()
-                        .decompress(),
+                    commitment: CompressedEdwardsY(hex_literal::hex!($commitment)),
+                    key: CompressedEdwardsY(hex_literal::hex!($ring_member)),
                 }),)+)+
             ];
 
@@ -103,10 +98,8 @@ macro_rules! test_verify_valid_v2_tx {
                 OutputOnChain {
                     height: 0,
                     time_lock: Timelock::None,
-                    commitment: ED25519_BASEPOINT_POINT,
-                    key: CompressedEdwardsY::from_slice(&hex_literal::hex!($ring_member))
-                        .unwrap()
-                        .decompress(),
+                    commitment: ED25519_BASEPOINT_COMPRESSED,
+                    key: CompressedEdwardsY(hex_literal::hex!($ring_member)),
                 }),)+)+
             ];
 
diff --git a/helper/src/crypto.rs b/helper/src/crypto.rs
index 1a27cd30..8a2328e2 100644
--- a/helper/src/crypto.rs
+++ b/helper/src/crypto.rs
@@ -4,8 +4,11 @@
 use std::sync::LazyLock;
 
 use curve25519_dalek::{
-    constants::ED25519_BASEPOINT_POINT, edwards::VartimeEdwardsPrecomputation,
-    traits::VartimePrecomputedMultiscalarMul, EdwardsPoint, Scalar,
+    constants::{ED25519_BASEPOINT_COMPRESSED, ED25519_BASEPOINT_POINT},
+    edwards::CompressedEdwardsY,
+    edwards::VartimeEdwardsPrecomputation,
+    traits::VartimePrecomputedMultiscalarMul,
+    Scalar,
 };
 use monero_serai::generators::H;
 
@@ -49,15 +52,16 @@ static H_PRECOMP: LazyLock<VartimeEdwardsPrecomputation> =
 /// # Invariant
 /// This function assumes that the [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`]
 /// table is sorted.
-pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[EdwardsPoint; 172]> = LazyLock::new(|| {
-    let mut lookup_table: [EdwardsPoint; 172] = [ED25519_BASEPOINT_POINT; 172];
+pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[CompressedEdwardsY; 172]> =
+    LazyLock::new(|| {
+        let mut lookup_table: [CompressedEdwardsY; 172] = [ED25519_BASEPOINT_COMPRESSED; 172];
 
-    for (i, amount) in ZERO_COMMITMENT_DECOMPOSED_AMOUNT.into_iter().enumerate() {
-        lookup_table[i] = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
-    }
+        for (i, amount) in ZERO_COMMITMENT_DECOMPOSED_AMOUNT.into_iter().enumerate() {
+            lookup_table[i] = (ED25519_BASEPOINT_POINT + *H * Scalar::from(amount)).compress();
+        }
 
-    lookup_table
-});
+        lookup_table
+    });
 
 //---------------------------------------------------------------------------------------------------- Free functions
 
@@ -66,7 +70,7 @@ pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[EdwardsPoint; 172]> = LazyLoc
 /// It will first attempt to lookup into the table of known Pre-RCT value.
 /// Compute it otherwise.
 #[expect(clippy::cast_possible_truncation)]
-pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint {
+pub fn compute_zero_commitment(amount: u64) -> CompressedEdwardsY {
     // OPTIMIZATION: Unlike monerod which execute a linear search across its lookup
     // table (O(n)). Cuprate is making use of an arithmetic based constant time
     // version (O(1)). It has been benchmarked in both hit and miss scenarios against
@@ -78,7 +82,7 @@ pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint {
     // the amount without its most significant digit.
     let Some(log) = amount.checked_ilog10() else {
         // amount = 0 so H component is 0.
-        return ED25519_BASEPOINT_POINT;
+        return ED25519_BASEPOINT_COMPRESSED;
     };
     let div = 10_u64.pow(log);
 
@@ -89,7 +93,9 @@ pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint {
     // there aren't only trailing zeroes behind the most significant digit.
     // The amount is not part of the table and can calculated apart.
     if most_significant_digit * div != amount {
-        return H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
+        return H_PRECOMP
+            .vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE])
+            .compress();
     }
 
     // Calculating the index back by progressing within the powers of 10.
@@ -116,7 +122,10 @@ mod test {
     fn compare_lookup_with_computation() {
         for amount in ZERO_COMMITMENT_DECOMPOSED_AMOUNT {
             let commitment = H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
-            assert!(commitment == compute_zero_commitment(amount));
+            assert_eq!(
+                commitment,
+                compute_zero_commitment(amount).decompress().unwrap()
+            );
         }
     }
 }
diff --git a/helper/src/tx.rs b/helper/src/tx.rs
index 53706ecf..263bb1e9 100644
--- a/helper/src/tx.rs
+++ b/helper/src/tx.rs
@@ -35,7 +35,7 @@ pub fn tx_fee(tx: &Transaction) -> u64 {
 
 #[cfg(test)]
 mod test {
-    use curve25519_dalek::{edwards::CompressedEdwardsY, EdwardsPoint};
+    use curve25519_dalek::edwards::CompressedEdwardsY;
     use monero_serai::transaction::{NotPruned, Output, Timelock, TransactionPrefix};
 
     use super::*;
@@ -46,7 +46,7 @@ mod test {
         let input = Input::ToKey {
             amount: Some(u64::MAX),
             key_offsets: vec![],
-            key_image: EdwardsPoint::default(),
+            key_image: CompressedEdwardsY::default(),
         };
 
         let output = Output {
diff --git a/storage/blockchain/src/ops/output.rs b/storage/blockchain/src/ops/output.rs
index 96d94bb1..002ac189 100644
--- a/storage/blockchain/src/ops/output.rs
+++ b/storage/blockchain/src/ops/output.rs
@@ -165,9 +165,7 @@ pub fn output_to_output_on_chain(
         Timelock::None
     };
 
-    let key = CompressedEdwardsY::from_slice(&output.key)
-        .map(|y| y.decompress())
-        .unwrap_or(None);
+    let key = CompressedEdwardsY(output.key);
 
     Ok(OutputOnChain {
         height: output.height as usize,
@@ -191,10 +189,7 @@ pub fn rct_output_to_output_on_chain(
     table_tx_unlock_time: &impl DatabaseRo<TxUnlockTime>,
 ) -> DbResult<OutputOnChain> {
     // INVARIANT: Commitments stored are valid when stored by the database.
-    let commitment = CompressedEdwardsY::from_slice(&rct_output.commitment)
-        .unwrap()
-        .decompress()
-        .unwrap();
+    let commitment = CompressedEdwardsY(rct_output.commitment);
 
     let time_lock = if rct_output
         .output_flags
@@ -205,9 +200,7 @@ pub fn rct_output_to_output_on_chain(
         Timelock::None
     };
 
-    let key = CompressedEdwardsY::from_slice(&rct_output.key)
-        .map(|y| y.decompress())
-        .unwrap_or(None);
+    let key = CompressedEdwardsY(rct_output.key);
 
     Ok(OutputOnChain {
         height: rct_output.height as usize,
diff --git a/storage/blockchain/src/ops/tx.rs b/storage/blockchain/src/ops/tx.rs
index 0312f215..0617f8db 100644
--- a/storage/blockchain/src/ops/tx.rs
+++ b/storage/blockchain/src/ops/tx.rs
@@ -96,7 +96,7 @@ pub fn add_tx(
         match inputs {
             // Key images.
             Input::ToKey { key_image, .. } => {
-                add_key_image(key_image.compress().as_bytes(), tables.key_images_mut())?;
+                add_key_image(key_image.as_bytes(), tables.key_images_mut())?;
             }
             // This is a miner transaction, set it for later use.
             Input::Gen(_) => miner_tx = true,
@@ -154,7 +154,7 @@ pub fn add_tx(
                         height,
                         output_flags,
                         tx_idx: tx_id,
-                        commitment: commitment.compress().0,
+                        commitment: commitment.0,
                     },
                     tables.rct_outputs_mut(),
                 )
@@ -219,7 +219,7 @@ pub fn remove_tx(tx_hash: &TxHash, tables: &mut impl TablesMut) -> DbResult<(TxI
         match inputs {
             // Key images.
             Input::ToKey { key_image, .. } => {
-                remove_key_image(key_image.compress().as_bytes(), tables.key_images_mut())?;
+                remove_key_image(key_image.as_bytes(), tables.key_images_mut())?;
             }
             // This is a miner transaction, set it for later use.
             Input::Gen(_) => miner_tx = true,
diff --git a/storage/txpool/src/ops/key_images.rs b/storage/txpool/src/ops/key_images.rs
index 76cae141..7933f3af 100644
--- a/storage/txpool/src/ops/key_images.rs
+++ b/storage/txpool/src/ops/key_images.rs
@@ -48,7 +48,7 @@ pub(super) fn remove_tx_key_images(
 /// This function will panic if the [`Input`] is not [`Input::ToKey`]
 fn ki_from_input(input: &Input) -> [u8; 32] {
     match input {
-        Input::ToKey { key_image, .. } => key_image.compress().0,
+        Input::ToKey { key_image, .. } => key_image.0,
         Input::Gen(_) => panic!("miner tx cannot be added to the txpool"),
     }
 }
diff --git a/types/src/json/tx.rs b/types/src/json/tx.rs
index a18dc89a..8aa6b20b 100644
--- a/types/src/json/tx.rs
+++ b/types/src/json/tx.rs
@@ -71,7 +71,7 @@ impl From<transaction::Transaction> for Transaction {
                         let key = Key {
                             amount: amount.unwrap_or(0),
                             key_offsets,
-                            k_image: HexBytes::<32>(key_image.compress().0),
+                            k_image: HexBytes::<32>(key_image.0),
                         };
 
                         Some(Input { key })
@@ -169,7 +169,7 @@ impl From<transaction::Transaction> for Transaction {
                     .base
                     .commitments
                     .into_iter()
-                    .map(|point| HexBytes::<32>(point.compress().0))
+                    .map(|point| HexBytes::<32>(point.0))
                     .collect();
 
                 let rct_signatures = RctSignatures::NonCoinbase {
diff --git a/types/src/output_cache.rs b/types/src/output_cache.rs
index 04e53bf6..00338e25 100644
--- a/types/src/output_cache.rs
+++ b/types/src/output_cache.rs
@@ -1,4 +1,4 @@
-use curve25519_dalek::EdwardsPoint;
+use curve25519_dalek::edwards::CompressedEdwardsY;
 use indexmap::{IndexMap, IndexSet};
 use monero_serai::transaction::Transaction;
 
@@ -87,8 +87,7 @@ impl OutputCache {
                         OutputOnChain {
                             height,
                             time_lock: tx.prefix().additional_timelock,
-                            // TODO: this needs to check the point is canonical.
-                            key: out.key.decompress(),
+                            key: out.key,
                             commitment: get_output_commitment(tx, i),
                         },
                     );
@@ -111,7 +110,7 @@ impl OutputCache {
 }
 
 /// Returns the amount commitment for the output at the given index `i` in the [`Transaction`]
-fn get_output_commitment(tx: &Transaction, i: usize) -> EdwardsPoint {
+fn get_output_commitment(tx: &Transaction, i: usize) -> CompressedEdwardsY {
     match tx {
         Transaction::V1 { prefix, .. } => {
             compute_zero_commitment(prefix.outputs[i].amount.unwrap_or_default())
diff --git a/types/src/types.rs b/types/src/types.rs
index 8a5b5aad..61de9c32 100644
--- a/types/src/types.rs
+++ b/types/src/types.rs
@@ -2,7 +2,7 @@
 
 use std::num::NonZero;
 
-use curve25519_dalek::edwards::EdwardsPoint;
+use curve25519_dalek::edwards::CompressedEdwardsY;
 use monero_serai::{
     block::Block,
     transaction::{Timelock, Transaction},
@@ -142,9 +142,9 @@ pub struct OutputOnChain {
     /// The timelock of this output, if any.
     pub time_lock: Timelock,
     /// The public key of this output, if any.
-    pub key: Option<EdwardsPoint>,
+    pub key: CompressedEdwardsY,
     /// The output's commitment.
-    pub commitment: EdwardsPoint,
+    pub commitment: CompressedEdwardsY,
 }
 
 /// Input required to generate an output histogram.