Skip to content

Fix ComputeTapTweak span aliasing causing all-zero output key on .NET 10 ARM64#1300

Merged
NicolasDorier merged 1 commit into
MetacoSA:masterfrom
dangershony:fix/taptweak-span-aliasing
May 11, 2026
Merged

Fix ComputeTapTweak span aliasing causing all-zero output key on .NET 10 ARM64#1300
NicolasDorier merged 1 commit into
MetacoSA:masterfrom
dangershony:fix/taptweak-span-aliasing

Conversation

@dangershony
Copy link
Copy Markdown
Contributor

Summary

TaprootFullPubKey.ComputeTapTweak reuses the same Span<byte> tweak32 for:

  1. WriteToSpan (serializing the internal pubkey)
  2. SHA256.Write (feeding the hash)
  3. merkleRoot.ToBytes (serializing the merkle root)
  4. SHA256.GetHash (writing the hash output)

On .NET 10 ARM64 (Android), the JIT miscompiles this span aliasing pattern. The result is that AddTweak receives a corrupted tweak value and returns an all-zero ECXOnlyPubKey, producing invalid P2TR output scripts.

Root Cause

The .NET 10 ARM64 JIT appears to optimize away or reorder memory operations when the same Span<byte> is used as both input and output across multiple method calls within the same scope. This is a JIT codegen bug — the C# code is semantically correct (each use of tweak32 is sequential and non-overlapping), but the generated native code does not preserve the correct memory ordering.

Fix

Use a separate stackalloc byte[32] buffer (buf) for the serialization writes (WriteToSpan, ToBytes), keeping tweak32 exclusively for the final GetHash output. This eliminates the aliasing pattern that triggers the JIT bug.

Impact

  • Affected: Any .NET 10 ARM64 consumer calling GetTaprootFullPubKey() or TaprootFullPubKey.Create() — produces all-zero output keys, leading to funds sent to unspendable addresses
  • Not affected: x64, .NET 8/9, or any platform where the JIT correctly handles the aliasing

Testing

  • Added regression test ComputeTapTweak_DoesNotProduceAllZeroOutputKey using BIP341 test vector
  • All 6 existing taproot tests continue to pass
  • Verified fix produces correct output on ARM64 Android device

Related

  • Discovered while upgrading Angor to .NET 10 + Avalonia 12 for Android
  • Transaction with bug: 160f198ba0f7dacbc097d6a61703b3121af38b7362d17b063cd9152f74012598 (signet, funds sent to all-zero P2TR)

….NET 10 ARM64

ComputeTapTweak reused the same Span<byte> tweak32 for WriteToSpan
serialization (input), SHA256.Write (hash input), and GetHash (output).
On .NET 10 ARM64 (Android), the JIT miscompiles this aliasing pattern,
causing AddTweak to receive a corrupted tweak and return an all-zero
ECXOnlyPubKey.

Fix: use a separate stackalloc byte[32] buffer for serialization,
keeping tweak32 exclusively for the GetHash output.

Add regression test that verifies a known BIP341 test vector produces
the expected non-zero output key.
@dangershony
Copy link
Copy Markdown
Contributor Author

dangershony commented May 10, 2026

Hi @NicolasDorier
The message above and code changes where discovered and fixed by Claude Opus 4.6 when we had errors running it on an Android device.
This issue has been preventing us form upgrading NBitcoin both on Avalonia for Android deployments and for Blazor (C# WASM).

@NicolasDorier
Copy link
Copy Markdown
Collaborator

wtf, this is the second time in dotnet I see something like this.

@NicolasDorier
Copy link
Copy Markdown
Collaborator

@dangershony can you also report this to dotnet team? to me it seems like a dotnet bug, and I suspect I am using the same pattern at other places.

@NicolasDorier NicolasDorier merged commit 9ab4a23 into MetacoSA:master May 11, 2026
5 of 6 checks passed
@NicolasDorier
Copy link
Copy Markdown
Collaborator

bumped 10.0.4

@NicolasDorier
Copy link
Copy Markdown
Collaborator

NicolasDorier commented May 11, 2026

For record, this bug seems similar to me to dotnet/runtime#122237
Sort of bug only happening on Android for reason...

It would be nice if you could make a small repro for them and open an issue.

@dangershony
Copy link
Copy Markdown
Contributor Author

Thanks mate issue opened

dotnet/runtime#128038

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.

2 participants