{
 "number": 35688,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35688",
 "title": "crypto: accept empty HMAC keys",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-07-08T22:46:49Z",
 "updated_at": "2026-09-04T09:14:16Z",
 "age_days": 70,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "dc67c4cc39062875c6571f9188abc83cfc6e6b90",
 "head_ref": "l0rinc/crypto-empty-input-hashing",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [
  {
   "t": "2026-07-09T19:44:49Z",
   "sha": "dc67c4cc39062875c6571f9188abc83cfc6e6b90"
  }
 ],
 "additions": 9,
 "deletions": 22,
 "changed_files": 5,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/35688#pullrequestreview-5111177718"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "sedited": {
   "kind": "ack",
   "hash": "dc67c4cc39062875c6571f9188abc83cfc6e6b90",
   "t": "2026-09-04T09:14:13Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "maflcko",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-07-09T19:48:22Z",
  "last_reviewer_activity": "2026-09-04T09:14:13Z",
  "last_reviewer": "sedited",
  "author_silent_days": 69,
  "waiting_on_author_days": 13,
  "days_since_update": 13
 },
 "refs": {
  "mentioned": [
   35010
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35010,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "refactor: replace memcpy with std::copy and add size asserts in IPC capnp serialization"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/crypto/hmac_sha256.cpp"
 ],
 "body": "**Problem:** `CHMAC_SHA256` and `CHMAC_SHA512` use `memcpy` to copy key bytes.\nEmpty vectors can provide a null `data()` pointer, which triggers a UBSan null-pointer warning even for zero-length copies and forces the crypto fuzz target to avoid empty inputs.\n\n**Fix:** Use [`std::copy`](https://godbolt.org/z/fzsP1jf17), which has defined semantics for empty ranges, add empty-key HMAC test vectors, and let the crypto fuzz target pass empty byte vectors directly.\nRemove the stale `eval_script` empty-input guard in a separate commit because `ConsumeRemainingBytes()` already returns before copying when no bytes remain.",
 "commits": [
  {
   "sha": "b80907909cbde0745d1e860644fc7f34b9c8fbf7",
   "date": "2026-07-09T19:24:05Z",
   "message": "crypto: accept empty HMAC keys\n\nReplace `memcpy` with `std::copy` so empty HMAC keys do not trigger a UBSan null-pointer warning.\nAdd empty-key test vectors and let the crypto fuzz target exercise empty byte vectors directly.\n\nCo-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>"
  },
  {
   "sha": "dc67c4cc39062875c6571f9188abc83cfc6e6b90",
   "date": "2026-07-09T19:24:05Z",
   "message": "fuzz: remove stale eval_script empty-input guard\n\n`ConsumeRemainingBytes()` already returns before copying when no bytes remain, so call it directly."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-09T08:56:38Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/crypto/hmac_sha256.cpp",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "in_reply_to": null,
   "text": "Why not use `std::copy`? It has several benefits:\n\n* The args are checked for types and any casts are explicit (the `src/span.h` header exists, if any casts are really needed. Though, I don't think they are here, because it compiles fine for me without them?)\n* The code won't have to be touched again if C29 is applied to C++ (in the future, or retroactively)\n* I haven't confirmed this exhaustively, but I presume compilers will create the same output with `std::copy` anyway?   At least, it would be odd if the performance was worse?\n* copy has defined semantics for empty source ranges in all versions of C++."
  },
  {
   "t": "2026-07-09T09:59:15Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "text": "Seems fine to change this, but at least for me it is really hard to follow the commit description, as it seems to mix several different broad topics:\n\n* It claims that https://github.com/bitcoin/bitcoin/pull/35010 is related to null-annotations where both the src/dst are zero-size. This is not true, because https://github.com/bitcoin/bitcoin/pull/35010 is about size mismatches, where the dst is too small to fit the src.\n* It claims that https://github.com/bitcoin/bitcoin/pull/35010 is related to the discussion (and was picked up in the discussion) in https://github.com/bitcoin/bitcoin/pull/35118#discussion_r3508842395, however I fail to see the link.\n* Then it links to https://godbolt.org/z/jMPP8EhEr, which also has a type-check includes. However, the types here are the same: both are `uint8_t` for src and dst.\n* It mixes in the eval_script fuzz target change, which could make sense to split up into a separate commit.\n\nAgain, seems fine to change, but the commit message can be a bit shorter and basically just say something like:\n\n```\ncrypto: accept empty HMAC keys\n\nThis avoids an Ubsan nullptr warning on memcpy, when passing empty HMAC keys.\n\nWith the warning gone, workarounds in the tests can be removed.\n```\n\nThough, feel free to ignore/adjust. I think that commit messages and pull descriptions should be clean and concise, and opinions and extended discussions are better left to GH review or issue comments."
  },
  {
   "t": "2026-07-09T16:15:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/crypto/hmac_sha256.cpp",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "in_reply_to": 3550259003,
   "text": "[quoted text omitted]\n\nIt's where @ryanofsky suggested `std::copy`: https://github.com/bitcoin/bitcoin/pull/35010#issuecomment-4192440846\n\n[quoted text omitted]\nThat's why, besides the godbolt showing the behavior difference between the two (which may or may not apply here, just seemed simpler to reason about it as such)\n\n[quoted text omitted]\nNot sure what you mean, the third pane uses `-DBYTE_SOURCE  -DUSE_STD_COPY -std=c++20 -fsanitize=undefined -fno-sanitize-recover=undefined` which fails with: 'cannot convert 'std::byte' to 'unsigned char' in assignment'\n\n[quoted text omitted]\nSure, I can do that\n\n[quoted text omitted]\nIn the examples I found the empty case was always mentioned as an exception, so the failures in godbolt were used as source of truth"
  },
  {
   "t": "2026-07-09T16:40:16Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/crypto/hmac_sha256.cpp",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "in_reply_to": 3550259003,
   "text": "[quoted text omitted]\n\nI just mean that the type of `key` and `rkey` is the same: `uint8_t`. So the conversion issue seems unrelated, but not important.\n\n[quoted text omitted]\nI am pretty sure those examples claiming that `std::copy` can't handle empty data are wrong. The stdlib copy really should be fine, I've used it in the past to fix this issue: fa4b52bd16189d40761c5976b8427e30779aba23. See also https://en.cppreference.com/cpp/algorithm/copy and the C++ spec. Up to you, feel free to ignore."
  },
  {
   "t": "2026-07-09T18:12:12Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/crypto/hmac_sha256.cpp",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "in_reply_to": 3550259003,
   "text": "[quoted text omitted]\n\nYes, but I don't think they are related.\n\nThere is one issue about type-data.h (https://github.com/bitcoin/bitcoin/pull/35118#issuecomment-4859660038)\n\nand pull #35010 is some ai slop in a different file: `src/ipc/capnp/common-types.h`.\n\nLet's try to keep separate topics in separate threads. In any case, both are unrelated to this pull request and should not be in the commit message here?"
  },
  {
   "t": "2026-07-09T19:44:49Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "dc67c4cc39062875c6571f9188abc83cfc6e6b90"
  },
  {
   "t": "2026-07-09T19:48:22Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/crypto/hmac_sha256.cpp",
   "commit": "ac1ddae4f6fb568faf761ebd37eea0d7a7f6b4b4",
   "in_reply_to": 3550259003,
   "text": "Thanks, @maflcko, I switched both HMAC copies to `std::copy`, removed the unrelated references, and shortened the commit messages and PR description.\nI also added a Godbolt comparison (https://godbolt.org/z/fzsP1jf17) confirming identical assembly for the fixed 64/128-byte copies with current GCC and Clang at both `-O2` and `-O3`, and split the `eval_script` cleanup into a separate commit."
  },
  {
   "t": "2026-09-04T09:14:13Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "dc67c4cc39062875c6571f9188abc83cfc6e6b90",
   "text": "ACK dc67c4cc39062875c6571f9188abc83cfc6e6b90"
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-08T22:46:53Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-08T23:47:08Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-09T00:56:51Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-08T23:55:47Z",
   "kind": "closed",
   "who": "l0rinc"
  },
  {
   "t": "2026-07-08T23:55:49Z",
   "kind": "reopened",
   "who": "l0rinc"
  }
 ],
 "text_chars": 5250,
 "text_tokens_estimate": 1312,
 "changed_paths": [
  "src/crypto/hmac_sha256.cpp",
  "src/crypto/hmac_sha512.cpp",
  "src/test/crypto_tests.cpp",
  "src/test/fuzz/crypto.cpp",
  "src/test/fuzz/eval_script.cpp"
 ],
 "files": [
  {
   "path": "src/crypto/hmac_sha256.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/crypto/hmac_sha512.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/test/crypto_tests.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/test/fuzz/crypto.cpp",
   "add": 0,
   "del": 10
  },
  {
   "path": "src/test/fuzz/eval_script.cpp",
   "add": 1,
   "del": 10
  }
 ],
 "test_lines": 25,
 "git": {
  "head": "dc67c4cc39062875c6571f9188abc83cfc6e6b90",
  "head_matches_backup": true,
  "base": "81405fc7abbd1889f3978b8924e7acbe12b3403b",
  "commits": [
   {
    "sha": "b80907909c",
    "subject": "crypto: accept empty HMAC keys",
    "files": 4,
    "add": 8,
    "del": 12
   },
   {
    "sha": "dc67c4cc39",
    "subject": "fuzz: remove stale eval_script empty-input guard",
    "files": 1,
    "add": 1,
    "del": 10
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "40c82492ba16725e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}