The current const ChainCode global compiles the assignment to a direct 32-byte copy, no call.
ah, true!
after compiling a release bitcoind build with debug symbols using GCC on a x86_64/Linux machine, i checked the generated x86 disassembly for CreateMuSig2SyntheticXpub (since it's the only MUSIG_CHAINCODE reader in the codebase right now), then demangled the symbols with objdump -d -S --demangle build/bin/bitcoind:
<details>
<summary>
click to see the disassembly from objdump
</summary>
00000000006f3520 <CreateMuSig2SyntheticXpub(CPubKey const&)>:
{
...
extpub.chaincode = MUSIG_CHAINCODE;
6f3552: 66 0f 6f 0d 26 e8 61 movdqa 0x61e826(%rip),%xmm1 # d11d80 <MUSIG_CHAINCODE>
6f3559: 00
6f355a: 0f 11 47 20 movups %xmm0,0x20(%rdi)
6f355e: 0f 11 4f 10 movups %xmm1,0x10(%rdi)
6f3562: 66 0f 6f 15 26 e8 61 movdqa 0x61e826(%rip),%xmm2 # d11d90 <MUSIG_CHAINCODE+0x10>
6f3569: 00
...
...
6f35b3: c3 ret
</details>
which confirms that, with optimisation enabled, the compiler lowers the assignment to direct SIMD loads/stores with RIP-relative addressing, with no helper call involved.
The ChainCode{MUSIG_CHAINCODE} cast builds a temporary whose destructor runs memory_cleanse on every call (non-elidable by design), so it's the heavier option at the call site, in paths like signing and descriptor expansion.
oh, i see it now. leaning the same way on keeping it as-is
The bytes sit in .rodata/.data either way, and the constant has internal linkage, so there's no cross-TU lookup.
true, i found no cross-TU lookup either.
the disassembly above confirms that MUSIG_CHAINCODE is accessed directly via RIP-relative offsets from the executing code, which shows the compiler treats it as a local data-segment object. there are also no guard-variable checks or initialization thunks before the loads, which rules out cross-TU initialization dependencies or any dynamic lookup overhead
That's a tiny waste, but not UB.
yes, agreed. my concern there was more theoretical than something i had actually verified in practice (should have checked the generated code first, my bad)
The current form is the cheapest at the call site; its only real costs are .data placement and one memset at exit.
yep, that's fair.
after looking at the generated code, the current approach avoids repeatedly constructing temporary stack ChainCode objects every time CreateMuSig2SyntheticXpub() runs. instead, the global ChainCode instance is constructed once, reused directly, and only cleaned up during program teardown
the generated code also makes it clear that the assignment itself is compiled down to efficient 128-bit SIMD, for x86-64 machines, moves (movdqa / movups) rather than a helper routine or repeated byte-wise copies
so yeah, i think the tradeoff made here makes sense
just a non-blocker:
out of curiosity for future cleanup: since MUSIG_CHAINCODE is a public constant that does not hold actual secret wallet data, do you think it is worth eventually introducing a non-cleansing wrapper type (like a raw uint256 array or a distinct PublicChainCode view) to completely bypass the default ~ChainCode() exit-scrub overhead, or is the single memset at program exit trivial enough that we should leave the ChainCode type abstraction exactly as it is ?