Merge pull request #10358

ed461a6 FCMP++: output_to_pre_leaf_tuple (j-berman)
99bda32 FCMP++: output_to_tuple {output pubkey, commitment} -> {O,I,C} (j-berman)

ACKs: UkoeHB, jeffro256
This commit is contained in:
tobtoht
2026-07-22 16:11:59 +00:00
8 changed files with 501 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})
+158
View File
@@ -0,0 +1,158 @@
// 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
{
// Struct composed of ec elems needed to get a full-fledged leaf tuple
struct PreLeafTuple final
{
fcmp_pp::EdDerivatives O_derivatives;
fcmp_pp::EdDerivatives I_derivatives;
fcmp_pp::EdDerivatives C_derivatives;
};
}
namespace fcmp_pp
{
namespace curve_trees
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// Public helper functions
//----------------------------------------------------------------------------------------------------------------------
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);
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// Static functions
//----------------------------------------------------------------------------------------------------------------------
static PreLeafTuple output_tuple_to_pre_leaf_tuple(const OutputTuple &o)
{
TIME_MEASURE_NS_START(point_to_ed_derivatives_ns);
const crypto::ec_point &O = (crypto::ec_point&) o.O;
const crypto::ec_point &I = (crypto::ec_point&) o.I;
const crypto::ec_point &C = (crypto::ec_point&) o.C;
/*
TODO: investigate perf impact of the following extraneous ops:
- Decompressing O and C when checking points for torsion and again here.
- Compressing I in derive_key_image_generator and decompressing again here.
*/
PreLeafTuple plt;
if (!fcmp_pp::point_to_ed_derivatives(O, plt.O_derivatives))
throw std::runtime_error("failed to get ed derivatives from O");
if (!fcmp_pp::point_to_ed_derivatives(I, plt.I_derivatives))
throw std::runtime_error("failed to get ed derivatives from I");
if (!fcmp_pp::point_to_ed_derivatives(C, plt.C_derivatives))
throw std::runtime_error("failed to get ed derivatives from C");
TIME_MEASURE_NS_FINISH(point_to_ed_derivatives_ns);
LOG_PRINT_L3("point_to_ed_derivatives_ns: " << point_to_ed_derivatives_ns);
return plt;
}
//----------------------------------------------------------------------------------------------------------------------
static PreLeafTuple output_to_pre_leaf_tuple(const OutputPair &output_pair)
{
const auto o = output_to_tuple(output_pair);
return output_tuple_to_pre_leaf_tuple(o);
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
} //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; }