Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include-internal/cbmpc/internal/protocol/ecdsa_2p.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ error_t refresh(job_2p_t& job, const key_t& key, key_t& new_key);
* @notes:
* - The input message must be the hash of the actual message.
* - This is the variant that contains `ZK-Two-Party-ECDSA-Sign-Integer-Commit`
* - `sigs` is cleared before execution and populated only if every signature verifies successfully.
*/
error_t sign(job_2p_t& job, buf_t& sid, const key_t& key, const mem_t msg, buf_t& sig);
error_t sign_batch(job_2p_t& job, buf_t& sid, const key_t& key, const std::vector<mem_t>& msgs,
Expand Down
1 change: 1 addition & 0 deletions include-internal/cbmpc/internal/protocol/ecdsa_mp.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ error_t refresh_ac(job_mp_t& job, ecurve_t curve, buf_t& sid, const crypto::ss::
* The proper more efficient way is to generate Base OTs one outside this function, then during the run of the
* protocol, use OT Extension to generate extra values and output them to be used as base OT for the next execution of
* the protocol.
* - `sig` is cleared before execution and populated only if verification succeeds.
*/
error_t sign(job_mp_t& job, key_t& key, mem_t msg, const party_idx_t sig_receiver,
const std::vector<std::vector<int>>& ot_role_map, buf_t& sig);
Expand Down
21 changes: 15 additions & 6 deletions src/cbmpc/protocol/ecdsa_2p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ error_t sign_batch_impl(job_2p_t& job, buf_t& sid, const key_t& key, const std::

auto n_sigs = msgs.size();
if (n_sigs == 0) return coinbase::error(E_BADARG, "ecdsa_2p: empty batch");
sigs.resize(n_sigs);
// Clear caller-visible output up front so a recycled buffer cannot retain
// stale (possibly valid) signatures from a prior call on any failure path.
sigs.clear();
const ecurve_t curve = key.curve;
const auto& G = curve.generator();
const mod_t& q = curve.order();
Expand Down Expand Up @@ -366,6 +368,7 @@ error_t sign_batch_impl(job_2p_t& job, buf_t& sid, const key_t& key, const std::
// N is odd, so (N + 1) / 2 is the first integer in the upper half of [0, N).
const bn_t N_half = (N.value() + 1) >> 1;
const bn_t N_mod_q = q.mod(N.value());
std::vector<buf_t> candidate_sigs(n_sigs);
for (int i = 0; i < n_sigs; i++) {
r[i] = R[i].get_x() % q;

Expand Down Expand Up @@ -398,16 +401,20 @@ error_t sign_batch_impl(job_2p_t& job, buf_t& sid, const key_t& key, const std::
if (q_minus_s < s) s = q_minus_s;

crypto::ecdsa_signature_t sig(curve, r[i], s);
sigs[i] = sig.to_der();
// Verify into a local candidate buffer; commit to the caller's `sigs`
// only after every signature in the batch verifies (same pattern as
// schnorr_2p). Publishing mid-loop would leave partial output on error.
candidate_sigs[i] = sig.to_der();

// verify
crypto::ecc_pub_key_t ecc_verification_key(key.Q);
if (rv = ecc_verification_key.verify(msgs[i], sigs[i]))
if (rv = ecc_verification_key.verify(msgs[i], candidate_sigs[i])) {
if (global_abort_mode)
return coinbase::error(E_ECDSA_2P_BIT_LEAK, "signature verification failed");
else
return coinbase::error(rv, "signature verification failed");
}
}
sigs = std::move(candidate_sigs);
}

return SUCCESS;
Expand All @@ -420,10 +427,11 @@ error_t sign_batch(job_2p_t& job, buf_t& sid, const key_t& key, const std::vecto

error_t sign(job_2p_t& job, buf_t& sid, const key_t& key, const mem_t msg, buf_t& sig) {
error_t rv = UNINITIALIZED_ERROR;
sig.clear();
std::vector<mem_t> msgs(1, msg);
std::vector<buf_t> sigs;
if (rv = sign_batch(job, sid, key, msgs, sigs)) return rv;
sig = sigs[0];
sig = std::move(sigs[0]);
return SUCCESS;
}

Expand All @@ -434,10 +442,11 @@ error_t sign_with_global_abort_batch(job_2p_t& job, buf_t& sid, const key_t& key

error_t sign_with_global_abort(job_2p_t& job, buf_t& sid, const key_t& key, const mem_t msg, buf_t& sig) {
error_t rv = UNINITIALIZED_ERROR;
sig.clear();
std::vector<mem_t> msgs(1, msg);
std::vector<buf_t> sigs;
if (rv = sign_with_global_abort_batch(job, sid, key, msgs, sigs)) return rv;
sig = sigs[0];
sig = std::move(sigs[0]);
return SUCCESS;
}

Expand Down
11 changes: 9 additions & 2 deletions src/cbmpc/protocol/ecdsa_mp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ error_t refresh_ac(job_mp_t& job, ecurve_t curve, buf_t& sid, const crypto::ss::
error_t sign(job_mp_t& job, key_t& key, mem_t msg, const party_idx_t sig_receiver,
const std::vector<std::vector<int>>& ot_role_map, buf_t& sig) {
error_t rv = UNINITIALIZED_ERROR;
// Clear caller-visible output so a recycled buffer cannot retain a stale
// signature if any earlier round fails before the final publish.
sig.clear();

int peers_count = job.get_n_parties();
int peer_index = job.get_party_idx();
Expand Down Expand Up @@ -477,9 +480,13 @@ error_t sign(job_mp_t& job, key_t& key, mem_t msg, const party_idx_t sig_receive

bn_t s_reduced = q - s;
if (s_reduced < s) s = s_reduced;
sig = crypto::ecdsa_signature_t(curve, r, s).to_der();
// Verify from a local buffer and only publish to the caller's output on
// success (same candidate-then-commit pattern as schnorr_2p and
// ecdsa_2p::sign_batch_impl).
buf_t der = crypto::ecdsa_signature_t(curve, r, s).to_der();
crypto::ecc_pub_key_t pub(key.Q);
if (rv = pub.verify(msg, sig)) return rv;
if (rv = pub.verify(msg, der)) return rv;
sig = std::move(der);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local-buffer fix is correct for the unverified write. The same
clear-on-entry consideration applies to the single sig output on failure —
a recycled caller buffer keeps its stale bytes — and a matching header note
on sign would document the contract.

}

return SUCCESS;
Expand Down