-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bulletproof+ by sarang, tied to consensus #7170
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a partial review a while ago, but never got around to completing it. I will just post what I have so far. Its basically all C++ optimization stuff. IMO the biggie is the copy+pasted code that could be made available via header to prevent duplicate ASM copies.
src/ringct/bulletproofs_plus.cc
Outdated
static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; | ||
|
||
// Useful scalar constants | ||
static const rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark these as constexpr
.
// Initial transcript hash | ||
static rct::key initial_transcript; | ||
|
||
static boost::mutex init_mutex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop this, not needed in C++11+ after other proposed changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what the 'other proposed changes are', but init_exponents()
should use std::call_once()
.
src/ringct/bulletproofs_plus.cc
Outdated
static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof | ||
|
||
// Cached public generators | ||
static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something I just noticed about the original Bulletproof code too. The static init code is slightly sub-optimal for multi-threads.
Put all of these runtime computed statics in a struct as members. Then use a function static, which is required to be thread-safe construction:
class public_generators
{
public_generators()
{
// paste code from `init_generators`
}
public:
// members
public_generators(const& public_generator&) = delete;
public_generators& operator=(const& public_generator&) = delete;
static const public_generators& instance()
{
static const public_generators singleton;
return singleton;
}
};
The result will be a more efficient double-check locking scheme generated by the compiler. This verification code will never be stalled with multi-threads/cores writing/acquiring the static mutex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand on why this is faster, since the compiler would have to lock too ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-checked the concurrency for Bulletproof checks. None! The code is designed for aggregate proof checks (unlike Borromean). So the change is unimportant, although it can't hurt to prevent minor CPU stalling.
Acquiring a lock requires exclusive cacheline access for the write. Each core has to wait "in-a-line" for the cacheline, with communication stalls for each acquire. The C++11 function-static does a double-check lock, which only requires exclusive cacheline when an atomic init flag is false (before+during initialization).
Slightly longer -
I thought this was spreading the checks across cores like Borromean. The lock acquire creates a single-file "gate" that every core must pass-through. The wait time foreach CPU-core depends on the cacheline state of the CPU, and whether other CPUs are involved. Ryzen 3+ chips with 9+ cores are effectively 2-CPU systems crammed onto one die. So concurrency is secretly getting more difficult. And identifying drops in throughput are maddening because its transient (as-in, I've seen 50% drops in throughput or 0% depending on thread scheduling).
The comments about removing allocations are related - each allocation is potentially a global sync event. Allocators have moved away from this in some small allocation requests.
return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R)); | ||
} | ||
|
||
BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This argument should be a span.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but fuck this waste of time trying to get the compiler to like it. I give up, it's just so litle compared to the time this function call will take in the first place, not worth my time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its seriously not that difficult. But ok, the other bulletproof code can be updated too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you get to do it. I spend close to an hour fighting the compiler for something that's just not worth an hour of work.
// Given a value v [0..2**N) and a mask gamma, construct a range proof | ||
BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma) | ||
{ | ||
return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take the address and pass a span of 1.
src/ringct/bulletproofs_plus.cc
Outdated
data.reserve(maxN*maxM*2); | ||
for (size_t i = 0; i < maxN*maxM; ++i) | ||
{ | ||
Hi[i] = get_exponent(rct::H, i * 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this only happens during init but bothers me for some reason (it can be shortened). Unless the goal was verification ? @SarangNoether
Hi
andGi
appear to be unnecessary static variables that are never used again. Drop them regardless.get_exponent
can return age_p3
, its going to use RVO anyway. Unless the goal is an extra verification step (ge_frombytes_vartime
), which might make some sense. OtherwiseGi_p3[i] = get_exponent(rct::H, i * 2);
, etc.
src/ringct/bulletproofs_plus.cc
Outdated
sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); | ||
|
||
// Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs | ||
static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temporary/copy not needed here. Definitely drop the static
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd need to be replaced by strlen AFAICT, and ir's only at init time, who cares anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, its at init time only, thus remove the static
keyword. Its a potential waste of heap memory for little benefit (save a call to free until end of program?). The memory is trivial, but its also just erasing the keyword.
Replacement would be const boost::string_ref domain_separator(...)
which behaves identically without the alloc/free behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, I thought you meant dropping the static variable itself (and use config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT directly). I'll drop the keyword.
src/ringct/bulletproofs_plus.cc
Outdated
multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN); | ||
multiexp_data.resize(2 * maxMN); | ||
|
||
const std::vector<rct::key> inverses = invert(to_invert); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move, then clear (just to be safe).
const std::vector<rct::key> inverses = invert(std::move(to_invert));
to_invert.clear();
} | ||
|
||
// Given a set of values v [0..2**N) and masks gamma, construct a range proof | ||
BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first argument should be a span (see other comment) -> ...(epee::span<const rct::key> sv, ...)
static boost::mutex init_mutex; | ||
|
||
// Use the generator caches to compute a multiscalar multiplication | ||
static inline rct::key multiexp(const std::vector<MultiexpData> &data, size_t HiGi_size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a bunch of functions that appear to be directly copy+pasted from bulletproofs.cc. This needs to be avoided, otherwise there are two copies of the ASM will be in the binary. The functions that I've noticed:
multiexp
is_reduced
vector_exponent
vector_add
vector_subtract
vector_scalar
sm
invert
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
f460f21
to
68032bf
Compare
Looks like there is some conflicting files on this PR ... |
68032bf
to
6d446f5
Compare
|
libwallet also needs a small update |
0dc7182
to
1d43476
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments
src/ringct/rctTypes.cpp
Outdated
for (const BulletproofPlus &proof: proofs) | ||
{ | ||
size_t n2 = n_bulletproof_plus_amounts(proof); | ||
CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (because this should never be an issue on any arch), but uint32_t
and size_t
are different types (size_t
theoretical could be smaller than uint32_t
) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use uin32_t because this is consensus, so the check has to be the same on all archs. I did not think size_t could be smaller than uin32_t though, that'd on ... MSDOS ? Other bits of Monero would break first anyway.
for (const BulletproofPlus &proof: proofs) | ||
{ | ||
size_t n2 = n_bulletproof_plus_max_amounts(proof); | ||
CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here with uint32_t
.
src/ringct/bulletproofs_plus.cc
Outdated
|
||
rct::key res = ONE; | ||
if (n == 1) | ||
return res; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be return x
? I don't think this case can ever trigger though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think you are correct, fixed.
|
||
static rct::key weighted_inner_product(const rct::keyV &a, const epee::span<const rct::key> &b, const rct::key &y) | ||
{ | ||
CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be return weighted_inner_product(epee:to_span(a), b, y);
to reduce the copy-paste in here.
src/ringct/rctTypes.h
Outdated
{ | ||
uint32_t nbp = bulletproofs_plus.size(); | ||
VARINT_FIELD(nbp) | ||
ar.tag(type >= RCTTypeBulletproofPlus ? "bpp" : "bp"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should always be bpp
.
1d43476
to
61f892d
Compare
3c5dfa1
to
37e8cfe
Compare
Rebased to latest master. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just some minor comments.
LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); | ||
return false; | ||
} | ||
const size_t max_outputs = 1 << (rv.p.bulletproofs_plus[0].L.size() - 6); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const size_t max_outputs = 1 << (rv.p.bulletproofs_plus[0].L.size() - 6); | |
const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus); |
Would need to rearrange this to the end of the conditional (after .V
is set).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does" Would need to rearrange this to the end of the conditional (after .V is set)." mean ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n_bulletproof_plus_max_amounts()
requires a BP+ to be fully defined. At this point in the code, rv.p.bulletproofs_plus[0]
is only partially-defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT it just needs L and R. Or do you mean we should not rely on this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh whoops, I got mixed up going back and forth between n_bulletproof_plus_max_amounts()
and n_bulletproof_plus_amounts()
(which needs V
). The code here is valid.
// from v16, forbid bulletproofs | ||
if (hf_version > HF_VERSION_BULLETPROOF_PLUS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for a 2-step hardfork? v15 adds BP+, then v16 disables BP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This^ is how I read it. v15 should allow both BP+ and BP as written
static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; | ||
static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; | ||
static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; | |
static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; | |
static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; | |
static constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; | |
static constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; | |
static constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; |
const constexpr
is redundant here afaict.
// Initial transcript hash | ||
static rct::key initial_transcript; | ||
|
||
static boost::mutex init_mutex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what the 'other proposed changes are', but init_exponents()
should use std::call_once()
.
static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 | ||
static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 | ||
static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 | ||
static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 | ||
static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 | |
static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 | |
static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 | |
static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 | |
static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) | |
static constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 | |
static constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 | |
static constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 | |
static constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 | |
static constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) |
Again, maybe I am missing something about const constexpr
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see plenty of const constexpr in the source, whch hints it's not the same and this is not buggy, I'll keep it that way. I don't know the fine details though.
rct::key sv8, sv; | ||
sv = rct::zero(); | ||
sv.bytes[0] = outamounts[i] & 255; | ||
sv.bytes[1] = (outamounts[i] >> 8) & 255; | ||
sv.bytes[2] = (outamounts[i] >> 16) & 255; | ||
sv.bytes[3] = (outamounts[i] >> 24) & 255; | ||
sv.bytes[4] = (outamounts[i] >> 32) & 255; | ||
sv.bytes[5] = (outamounts[i] >> 40) & 255; | ||
sv.bytes[6] = (outamounts[i] >> 48) & 255; | ||
sv.bytes[7] = (outamounts[i] >> 56) & 255; | ||
sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); | ||
rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rct::key sv8, sv; | |
sv = rct::zero(); | |
sv.bytes[0] = outamounts[i] & 255; | |
sv.bytes[1] = (outamounts[i] >> 8) & 255; | |
sv.bytes[2] = (outamounts[i] >> 16) & 255; | |
sv.bytes[3] = (outamounts[i] >> 24) & 255; | |
sv.bytes[4] = (outamounts[i] >> 32) & 255; | |
sv.bytes[5] = (outamounts[i] >> 40) & 255; | |
sv.bytes[6] = (outamounts[i] >> 48) & 255; | |
sv.bytes[7] = (outamounts[i] >> 56) & 255; | |
sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); | |
rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); | |
// C[i] = (1/8)*(1 G + a[i] H) | |
rct::scalarmultKey(C[i], rct::commit(outamounts[i], I), rct::INV_EIGHT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it buggy ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's needlessly verbose.
tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, | ||
use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it ok to get rid of these old config values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@paulshapiro raised a good point to me one time that making a change like this would make it harder to make older tx types for historical research purposes. Aside from that, seems this change would have no impact on normal users
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to make old tx types, you just have to checkout the right version from git. Much easier than patching current source, which you'd have to do anyway.
tests/core_tests/bulletproofs.cpp
Outdated
@@ -193,6 +193,7 @@ bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size | |||
{ | |||
DEFINE_TESTS_ERROR_CONTEXT(context); | |||
CHECK_TEST_CONDITION(tx.version >= 2); | |||
MGINFO("type: " << (unsigned)tx.rct_signatures.type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover?
// 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. | ||
// | ||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make sure, did you actually verify that ? I tend to err on hte side of safety and I don't really fancy diving through git history to make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are bp+ files, which were written from scratch by you and sarang.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied from an existing file to start with. I suppose any leftover code, if any, is likely minimal but at least the class structure with the a_ parameters is based off original CN code.
// 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. | ||
// | ||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The higher level logic looks good to me (I didn't review the crypto). My comments are minor nits, not necessary changes for merge
@@ -412,7 +441,7 @@ namespace cryptonote | |||
{ | |||
CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); | |||
CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); | |||
CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG, | |||
CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus, | |||
std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic of this check is changing slightly s.t. the message could read: "unsupported rct_signature type in get_pruned_transaction_weight"
(it won't support newer types either now, not just older types)
@@ -416,7 +475,7 @@ namespace rct { | |||
ar.end_array(); | |||
} | |||
|
|||
if (type == RCTTypeCLSAG) | |||
if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_rct_clsag(type)
here and in other places would be nice for readability + to reduce the chance of messing up this logic in a future change. Unfortunately in this spot in particular, would need to mess with code structure a bit to get it to compile. Not a big deal, pretty minor nit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having them that way reduces the chances of messing up. You can move stuff around whlie still keeping CLSAGs, and is_clsag would still say true. Though you could also make up a case the other way.
weight += extra; | ||
|
||
// calculate deterministic CLSAG/MLSAG data size | ||
const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); | ||
if (tx.rct_signatures.type == rct::RCTTypeCLSAG) | ||
if (tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rct::is_rct_clsag(tx.rct_signatures.type)
@@ -3224,7 +3250,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr | |||
} | |||
} | |||
} | |||
else if (rv.type == rct::RCTTypeCLSAG) | |||
else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rct::is_rct_clsag(rv.type)
src/cryptonote_core/blockchain.cpp
Outdated
@@ -3551,7 +3578,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, | |||
} | |||
} | |||
|
|||
const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size(); | |||
const size_t n_sigs = rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus ? rv.p.CLSAGs.size() : rv.p.MGs.size(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rct::is_rct_clsag(rv.type)
src/cryptonote_core/blockchain.cpp
Outdated
@@ -3560,7 +3587,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, | |||
for (size_t n = 0; n < tx.vin.size(); ++n) | |||
{ | |||
bool error; | |||
if (rv.type == rct::RCTTypeCLSAG) | |||
if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rct::is_rct_clsag(rv.type)
src/wallet/wallet2.cpp
Outdated
++log_padded_outputs; | ||
size += (2 * (6 + log_padded_outputs) + 6) * 32 + 3; | ||
} | ||
else if (bulletproof) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can combine the if (bulletproof_plus)
and the else if (bulletproof)
into one :
if (bulletproof_plus || bulletproof)
{
size_t log_padded_outputs = 0;
while ((1<<log_padded_outputs) < n_outputs)
++log_padded_outputs;
size += (2 * (6 + log_padded_outputs) + (bulletproof_plus ? 6 : 9)) * 32 + 3;
}
if (!proofs.empty() && !verBulletproof(proofs)) | ||
if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) | ||
{ | ||
if (!waiter.wait()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confused why this is here:
if (!waiter.wait())
return false;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably because I was printing stuff from the results when debugging. Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually that's to avoid going past the scope while results (and possibly others) are still used by the threads. The thread dtor will wait, but it's a failsafe, you're supposed to wait first. I'll redo and add it to the other error path.
tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, | ||
use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@paulshapiro raised a good point to me one time that making a change like this would make it harder to make older tx types for historical research purposes. Aside from that, seems this change would have no impact on normal users
// from v16, forbid bulletproofs | ||
if (hf_version > HF_VERSION_BULLETPROOF_PLUS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This^ is how I read it. v15 should allow both BP+ and BP as written
There are two comments that I don't get a "reply" widget for:
I don't understand the first part about proposed changes. There is no such string in the quoted patch.
There's just that word. Missing comment/question there ? It's on a line that just has a prototype. |
Pushed with a rebase to current master, the changes from these two recent reviews are in the last commit, which will get squashed with the relevant others once reviewed |
These are replies to @vtnerd's review comments. Github shows 'prototypes' for replies that are added in later reviews. |
@@ -179,7 +179,7 @@ namespace cryptonote | |||
LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); | |||
return false; | |||
} | |||
const size_t max_outputs = 1 << (rv.p.bulletproofs_plus[0].L.size() - 6); | |||
const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must go below the line rv.p.bulletproofs_plus[0].V[i] = rv.outPk[i].mask;
. The precondition of n_bulletproof_plus_max_amounts()
is that the bp+ passed in is fully defined.
EDIT: nvm, this function only needs L
and R
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing your point, but if you do that, you'll get the max_output check after the resize, which means you can make it allocate a large amount of memory. And AFAICT n_bulletproof_plus_max_amounts does not need V.
src/ringct/rctTypes.cpp
Outdated
@@ -251,7 +251,8 @@ namespace rct { | |||
} | |||
} | |||
|
|||
size_t n_bulletproof_amounts(const Bulletproof &proof) | |||
template<typename proof_t> | |||
static size_t n_bulletproof_bulletproof_plus_amounts(const proof_t &proof) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are going to do this, then you need to assert that BULLETPROOF_MAX_OUTPUTS == BULLETPROOF_PLUS_MAX_OUTPUTS
, and maybe static assert that proof_t
is BulletProof
or BulletProofPlus
. My recommended function signature was meant to decouple the function from the implementation details of the BP proofs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point on the first one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think init_exponents()
in ringct/bulletproofs_plus.cc
should use std::call_once()
(which matches the semantics of that function), and make_dummy_bulletproof_plus()
in ringct/rctSigs.cpp
can be cleaned up. However, the PR appears correct and merge-able.
It avoids dividing by 8 when deserializing a tx, which is a slow operation, and multiplies by 8 when verifying and extracing the amount, which is much faster as well as less frequent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, only diff with my local copy is adding waiters to verRctSemanticsSimple()
.
For reference, the audit:
https://suyash67.github.io/homepage/assets/pdfs/bulletproofs_plus_audit_report_v1.1.pdf