FCMP++: output_to_tuple {output pubkey, commitment} -> {O,I,C}

- Function to convert an {output pubkey, commitment} to an output
tuple {O,I,C} in prepartion to insert the output tuple into the
curve tree.
- O = torsion cleared valid output pubkey checked for identity.
- I = key image generator.
- C = torsion cleared valid Commitment checked for identity.
- None of {O,I,C} should have torsion nor == identity.
- Introduces the OutputPair variant, which can either be Legacy
or Carrot V1 types. Legacy outputs are not checked for torsion
at consensus, and use the legacy biased hash to point fn to derive
the key image generator (I). Carrot V1 outputs **are** checked for
torsion at consensus, and use the unbiased hash to point to derive
the key image generator (I).
This commit is contained in:
j-berman
2026-07-21 02:35:55 -07:00
parent 349e5884a1
commit 99bda32d0f
8 changed files with 452 additions and 1 deletions
+5 -1
View File
@@ -27,10 +27,14 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(fcmp_pp_sources
fcmp_pp_crypto.cpp)
curve_trees.cpp
fcmp_pp_crypto.cpp
fcmp_pp_types.cpp)
monero_find_all_headers(fcmp_pp_headers "${CMAKE_CURRENT_SOURCE_DIR}")
add_subdirectory(fcmp_pp_rust)
monero_add_library(fcmp_pp
${fcmp_pp_sources}
${fcmp_pp_headers})
+109
View File
@@ -0,0 +1,109 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "curve_trees.h"
#include "crypto/crypto.h"
#include "fcmp_pp_crypto.h"
#include "fcmp_pp_types.h"
#include "profile_tools.h"
namespace fcmp_pp
{
namespace curve_trees
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
OutputTuple output_to_tuple(const OutputPair &output_pair)
{
const crypto::public_key &output_pubkey = output_pubkey_cref(output_pair);
const crypto::ec_point &commitment = commitment_cref(output_pair);
TIME_MEASURE_NS_START(derive_key_image_generator_ns);
// Derive key image generator using the original output pubkey.
// CRITICAL: it's CRITICALLY important that we use the original output pubkey here, and not `O` calculated later.
// If we were to use `O`, then an output with torsion could be double spent before and after the FCMP++ fork, since
// it would have 1 key image before the fork (tied to the output pubkey) and a distinct key image (tied to torsion
// cleared `O`).
crypto::ec_point I;
crypto::derive_key_image_generator(output_pubkey, use_biased_hash_to_point(output_pair), I);
TIME_MEASURE_NS_FINISH(derive_key_image_generator_ns);
LOG_PRINT_L3("derive_key_image_generator_ns: " << derive_key_image_generator_ns);
// Get O and C from output pubkey and commitment
crypto::ec_point O = output_pubkey;
crypto::ec_point C = commitment;
// If the output has already been checked for torsion, then we don't need to clear torsion here
if (!output_checked_for_torsion(output_pair))
{
TIME_MEASURE_NS_START(clear_torsion_ns);
if (!fcmp_pp::get_valid_torsion_cleared_point_vartime(output_pubkey, O))
throw std::runtime_error("O is invalid for insertion to tree");
if (!fcmp_pp::get_valid_torsion_cleared_point_vartime(commitment, C))
throw std::runtime_error("C is invalid for insertion to tree");
if (O != output_pubkey)
LOG_PRINT_L2("Output pubkey has torsion: " << output_pubkey);
if (C != commitment)
LOG_PRINT_L2("Commitment has torsion: " << commitment);
TIME_MEASURE_NS_FINISH(clear_torsion_ns);
LOG_PRINT_L3("clear_torsion_ns: " << clear_torsion_ns);
}
#if !defined(NDEBUG)
{
// Debug build safety checks
crypto::ec_point O_debug;
crypto::ec_point C_debug;
assert(fcmp_pp::get_valid_torsion_cleared_point_vartime(output_pubkey, O_debug));
assert(fcmp_pp::get_valid_torsion_cleared_point_vartime(commitment, C_debug));
assert(O == O_debug);
assert(C == C_debug);
}
#endif
// Redundant check for safety
if (O == fcmp_pp::EC_I)
throw std::runtime_error("O cannot equal identity");
if (C == fcmp_pp::EC_I)
throw std::runtime_error("C cannot equal identity");
return output_tuple_from_bytes(O, I, C);
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
} //namespace curve_trees
} //namespace fcmp_pp
+46
View File
@@ -0,0 +1,46 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "crypto/crypto.h"
#include "fcmp_pp_types.h"
#include "misc_log_ex.h"
namespace fcmp_pp
{
namespace curve_trees
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
OutputTuple output_to_tuple(const OutputPair &output_pair);
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
} //namespace curve_trees
} //namespace fcmp_pp
+27
View File
@@ -0,0 +1,27 @@
# Copyright (c) 2024, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdint.h>
struct OutputTuple
{
uint8_t O[32];
uint8_t I[32];
uint8_t C[32];
};
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "fcmp_pp_types.h"
#include "misc_log_ex.h"
namespace fcmp_pp
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------------------------------------------------------
OutputTuple output_tuple_from_bytes(const crypto::ec_point &O, const crypto::ec_point &I, const crypto::ec_point &C)
{
OutputTuple output_tuple;
static_assert(sizeof(output_tuple.O) == sizeof(O), "unexpected sizeof O");
static_assert(sizeof(output_tuple.I) == sizeof(I), "unexpected sizeof I");
static_assert(sizeof(output_tuple.C) == sizeof(C), "unexpected sizeof C");
memcpy(output_tuple.O, &O, sizeof(O));
memcpy(output_tuple.I, &I, sizeof(I));
memcpy(output_tuple.C, &C, sizeof(C));
return output_tuple;
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
const crypto::public_key &output_pubkey_cref(const OutputPair &output_pair)
{
struct output_pair_visitor
{
const crypto::public_key &operator()(const LegacyOutputPair &o) const
{ return o.output_pubkey; }
const crypto::public_key &operator()(const CarrotOutputPairV1 &o) const
{ return o.output_pubkey; }
};
return std::visit(output_pair_visitor{}, output_pair);
}
//----------------------------------------------------------------------------------------------------------------------
const crypto::ec_point &commitment_cref(const OutputPair &output_pair)
{
struct output_pair_visitor
{
const crypto::ec_point &operator()(const LegacyOutputPair &o) const
{ return o.commitment; }
const crypto::ec_point &operator()(const CarrotOutputPairV1 &o) const
{ return o.commitment; }
};
return std::visit(output_pair_visitor{}, output_pair);
}
//----------------------------------------------------------------------------------------------------------------------
bool output_checked_for_torsion(const OutputPair &output_pair)
{
struct output_pair_visitor
{
bool operator()(const LegacyOutputPair&) const
{ return false; }
bool operator()(const CarrotOutputPairV1&) const
{ return true; }
};
return std::visit(output_pair_visitor{}, output_pair);
}
//----------------------------------------------------------------------------------------------------------------------
bool use_biased_hash_to_point(const OutputPair &output_pair)
{
struct output_pair_visitor
{
bool operator()(const LegacyOutputPair&) const
{ return true; }
bool operator()(const CarrotOutputPairV1&) const
{ return false; }
};
return std::visit(output_pair_visitor{}, output_pair);
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
}//namespace fcmp_pp
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstring>
#include <type_traits>
#include <variant>
#include "crypto/crypto.h"
#include "fcmp_pp_rust/fcmp++.h"
namespace fcmp_pp
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// Rust types
//----------------------------------------------------------------------------------------------------------------------
using OutputTuple = ::OutputTuple;
//----------------------------------------------------------------------------------------------------------------------
OutputTuple output_tuple_from_bytes(const crypto::ec_point &O, const crypto::ec_point &I, const crypto::ec_point &C);
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// C++ types
//----------------------------------------------------------------------------------------------------------------------
// Curve trees types
//----------------------------------------------------------------------------------------------------------------------
// Output pubkey and commitment, ready to be converted to a leaf tuple
// - From {output_pubkey,commitment} -> {O,C} -> {O,I,C} -> {O.x,O.y,I.x,I.y,C.x,C.y}
// - Output pairs do NOT necessarily have torsion cleared. We need the output pubkey as it exists in the chain in order
// to derive the correct I (when deriving {O.x,O.y,I.x,I.y,C.x,C.y}). Torsion clearing O before deriving I from O
// would enable spending a torsioned output once before FCMP++ fork and again with a different key image via FCMP++.
template<typename T>
struct OutputPairTemplate
{
crypto::public_key output_pubkey;
// Uses the ec_point type to avoid a circular dep to ringct/rctTypes.h, and to differentiate from output_pubkey
crypto::ec_point commitment;
OutputPairTemplate(const crypto::public_key &_output_pubkey, const crypto::ec_point &_commitment):
output_pubkey(_output_pubkey),
commitment(_commitment)
{};
OutputPairTemplate():
output_pubkey{},
commitment{}
{};
// WARNING: not constant time
bool operator==(const OutputPairTemplate &other) const
{
return output_pubkey == other.output_pubkey
&& commitment == other.commitment;
}
};
// May have torsion, use biased key image generator for I
struct LegacyOutputPair : public OutputPairTemplate<LegacyOutputPair>{};
// No torsion, use unbiased key image generator for I
struct CarrotOutputPairV1 : public OutputPairTemplate<CarrotOutputPairV1>{};
static_assert(sizeof(LegacyOutputPair) == (32+32), "sizeof LegacyOutputPair unexpected");
static_assert(sizeof(CarrotOutputPairV1) == (32+32), "sizeof CarrotOutputPairV1 unexpected");
static_assert(std::has_unique_object_representations_v<LegacyOutputPair>);
static_assert(std::has_unique_object_representations_v<CarrotOutputPairV1>);
using OutputPair = std::variant<LegacyOutputPair, CarrotOutputPairV1>;
const crypto::public_key &output_pubkey_cref(const OutputPair &output_pair);
const crypto::ec_point &commitment_cref(const OutputPair &output_pair);
bool output_checked_for_torsion(const OutputPair &output_pair);
bool use_biased_hash_to_point(const OutputPair &output_pair);
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
}//namespace fcmp_pp
// WARNING: not constant time
inline bool operator==(const fcmp_pp::OutputTuple &a, const fcmp_pp::OutputTuple &b)
{
static_assert(sizeof(fcmp_pp::OutputTuple) == (sizeof(a.O) + sizeof(a.I) + sizeof(a.C)),
"unexpected sizeof OutputTuple for == implementation");
return
(memcmp(a.O, b.O, sizeof(a.O)) == 0) &&
(memcmp(a.I, b.I, sizeof(a.I)) == 0) &&
(memcmp(a.C, b.C, sizeof(a.C)) == 0);
}
+3
View File
@@ -0,0 +1,3 @@
#include "fcmp++.h"
int main() { return 0; }