Skip to content

Add rfc6979 k generation.#80

Open
werwurm wants to merge 19 commits intomainfrom
werwurm/rfc6979
Open

Add rfc6979 k generation.#80
werwurm wants to merge 19 commits intomainfrom
werwurm/rfc6979

Conversation

@werwurm
Copy link
Contributor

@werwurm werwurm commented Feb 26, 2026

This implementation of rfc6979 nonce generation is based on the nat20 crypto context interface and is therefore independent of the bssl reference implementation.

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

LCOV of commit 597bec8 during lcov-test-coverage-report #62

Summary coverage rate:
  lines......: 95.2% (2526 of 2654 lines)
  functions..: 99.0% (200 of 202 functions)
  branches...: 85.8% (1421 of 1656 branches)

Files changed coverage rate: n/a

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds RFC 6979 deterministic nonce (k) generation for ECDSA signatures to the nat20 crypto library. The implementation is built on the nat20 crypto context interface, making it independent of the BoringSSL reference implementation. RFC 6979 provides a deterministic method to generate the nonce k from the private key and message, eliminating the security risks associated with weak random number generators in ECDSA signing.

Changes:

  • Adds new n20_rfc6979_k_generation function implementing RFC 6979 deterministic k generation for P-256 and P-384 curves
  • Implements big number utility functions for arithmetic operations (shift, subtract, compare, convert)
  • Adds comprehensive test coverage with RFC 6979 test vectors from Appendix A2.5 (P-256) and A2.6 (P-384), plus additional edge cases

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.

File Description
include/nat20/crypto/nat20/rfc6979.h Public API header defining the RFC 6979 k generation function and big number types with comprehensive documentation
src/crypto/nat20/rfc6979.c Core implementation of RFC 6979 algorithm including HMAC-DRBG-based k generation and big number utility functions
src/crypto/test/crypto_nat20.cpp Test suite with RFC 6979 standard test vectors for P-256 and P-384 curves, testing both first and second nonce candidates
CMakeLists.txt Build configuration updates to include the new source and header files

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* are wider than @p res, a return value of true may also indicate
* data loss.
*/
bool n20_bn_sub_overflow(n20_bn_t const* a, n20_bn_t const* b, n20_bn_t* res) {
Copy link
Contributor

Choose a reason for hiding this comment

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

i think MISRA will ding you for using carry as both a bool and uint32_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +183 to +201
#define N20_SHA2_224_OCTETS 28
#define N20_SHA2_256_OCTETS 32
#define N20_SHA2_384_OCTETS 48
#define N20_SHA2_512_OCTETS 64

static size_t digest_enum_to_size(n20_crypto_digest_algorithm_t alg_in) {
switch (alg_in) {
case n20_crypto_digest_algorithm_sha2_224_e:
return N20_SHA2_224_OCTETS;
case n20_crypto_digest_algorithm_sha2_256_e:
return N20_SHA2_256_OCTETS;
case n20_crypto_digest_algorithm_sha2_384_e:
return N20_SHA2_384_OCTETS;
case n20_crypto_digest_algorithm_sha2_512_e:
return N20_SHA2_512_OCTETS;
default:
return 0;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

does it make since to add these to the crypto nat20 sha implementation in case we want to reuse it in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can always move this around. Because this implementation can be used with other implementations of SHA2, I'd rather keep it it self contained here. I don't have strong opinions about this but I also don't see a natural place where this should go.

Comment on lines 230 to 247
if (m_octets != NULL) {
err = ctx->digest(ctx, digest_algorithm, m_octets, 1, h1_bytes, &h1_size);
if (err != n20_error_ok_e) {
return err;
}

h1_slice.size = h1_size < qlen_octets ? h1_size : qlen_octets;
n20_slice_to_bn(k, &h1_slice);

if (n20_bn_cmp(k, q) >= 0) {
if (n20_bn_sub_overflow(k, q, k)) {
return n20_error_crypto_implementation_specific_e;
}
}
h1_size = qlen_octets;
n20_bn_to_octets(h1_bytes, h1_size, k);
h1_slice.size = h1_size;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

should probably add similar comments to what we added to the boringssl implementation.

@werwurm werwurm requested a review from seidelrj February 27, 2026 00:50
@werwurm werwurm changed the base branch from werwurm/types to main February 27, 2026 17:56
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +363 to +364

return n20_error_crypto_implementation_specific_e;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

This return statement is unreachable because the while loop on line 301 is infinite and only exits via return statements within the loop body. The function will never reach this line during normal execution. Consider removing this unreachable code to improve code clarity.

Suggested change
return n20_error_crypto_implementation_specific_e;

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +107
bool n20_bn_sub_overflow(n20_bn_t const* a, n20_bn_t const* b, n20_bn_t* res) {
uint32_t carry = 0;
size_t i = 0;
size_t max_words = a->word_count > b->word_count ? a->word_count : b->word_count;
max_words = max_words > res->word_count ? max_words : res->word_count;

for (; i < max_words; ++i) {
uint32_t res_ = 0;
if (i < a->word_count) {
res_ = a->words[i];
}
carry = __builtin_sub_overflow(res_, carry, &res_) ? 1 : 0;
if (i < b->word_count) {
carry |= __builtin_sub_overflow(res_, b->words[i], &res_) ? 1 : 0;
}

if (i < res->word_count) {
res->words[i] = res_;
} else if (res_ != 0) {
/* Result does not fit into output bn. */
return true;
}
}

return carry;
}

void n20_slice_to_bn(n20_bn_t* bn, n20_slice_t const* slice) {
uint32_t octet = slice->size;
for (size_t i = 0; i < bn->word_count; ++i) {
bn->words[i] = 0;
for (int j = 0; j < 4; ++j) {
bn->words[i] >>= 8;
if (octet) {
octet -= 1;
bn->words[i] |= ((uint32_t)slice->buffer[octet]) << 24;
}
}
}
}

void n20_bn_to_octets(uint8_t* octets, size_t octets_len, n20_bn_t const* bn) {
for (size_t i = 0; i < octets_len; ++i) {
size_t word_index = i >> 2;
size_t byte_index = i & 0x3;
if (word_index < bn->word_count) {
octets[octets_len - i - 1] =
(uint8_t)((bn->words[word_index] >> (8 * byte_index)) & 0xff);
} else {
octets[octets_len - i - 1] = 0;
}
}
}

bool n20_bn_is_zero(n20_bn_t* bn) {
for (size_t i = 0; i < bn->word_count; ++i) {
if (bn->words[i] != 0) return false;
}
return true;
}

int n20_bn_cmp(n20_bn_t* a, n20_bn_t* b) {
size_t max_words = a->word_count > b->word_count ? a->word_count : b->word_count;
int result = 0;
for (size_t i_ = max_words; i_ > 0; --i_) {
size_t i = i_ - 1;
uint32_t aw = i < a->word_count ? a->words[i] : 0;
uint32_t bw = i < b->word_count ? b->words[i] : 0;
if (result == 0 && aw < bw) result = -1;
if (result == 0 && aw > bw) result = 1;
}
return result;
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

These helper functions are not declared in the public header file and should be marked as static to indicate they are internal implementation details. This follows the established convention in the codebase (see src/crypto/nat20/crypto.c, sha256.c, sha512.c) where internal helper functions are consistently marked static. Making these functions static improves encapsulation and prevents unintended external linkage.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants