Skip to content
Merged
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
27 changes: 27 additions & 0 deletions NBitcoin.Tests/TaprootBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,33 @@ public void ScriptPathSpendUnitTest1()
var expectedSpk = "5120003cdb72825a12ea62f5834f3c47f9bf48d58d27f5ad1e6576ac613b093125f3";
Assert.Equal( expectedSpk, spk.ToHex());
}

[Fact]
[Trait("UnitTest", "UnitTest")]
public void ComputeTapTweak_DoesNotProduceAllZeroOutputKey()
{
// Regression test for .NET 10 ARM64 JIT span-aliasing bug.
// ComputeTapTweak previously reused the same Span<byte> for
// WriteToSpan serialization and GetHash output, which caused
// AddTweak to return an all-zero key on ARM64.
// BIP341 test vector: internal key with no script tree (key-path only).
var hex = Encoders.Hex;
var internalKey = TaprootInternalPubKey.Parse(
"d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d");
var fullPubKey = internalKey.GetTaprootFullPubKey();

var outputBytes = new byte[32];
fullPubKey.OutputKey.pubkey.WriteToSpan(outputBytes);

// Output key must not be all zeros
Assert.False(Array.TrueForAll(outputBytes, b => b == 0),
"TaprootFullPubKey output key is all zeros — ComputeTapTweak span aliasing bug");

// Expected output key for this internal key with null merkle root (BIP341)
Assert.Equal(
"53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
hex.EncodeData(outputBytes));
}
#endif
}
}
13 changes: 9 additions & 4 deletions NBitcoin/TaprootFullPubKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,19 @@ private TaprootFullPubKey(ECXOnlyPubKey outputKey, bool outputParity, TaprootInt

internal static void ComputeTapTweak(TaprootInternalPubKey internalKey, uint256? merkleRoot, Span<byte> tweak32)
{
// Use a separate buffer for serialization to avoid reusing tweak32
// as both scratch space and output. Reusing the same Span<byte> for
// WriteToSpan/ToBytes input and GetHash output triggers a .NET 10
// ARM64 JIT miscompilation due to span aliasing.
Span<byte> buf = stackalloc byte[32];
using Secp256k1.SHA256 sha = new Secp256k1.SHA256();
sha.InitializeTagged("TapTweak");
internalKey.pubkey.WriteToSpan(tweak32);
sha.Write(tweak32);
internalKey.pubkey.WriteToSpan(buf);
sha.Write(buf);
if (merkleRoot is uint256)
{
merkleRoot.ToBytes(tweak32);
sha.Write(tweak32);
merkleRoot.ToBytes(buf);
sha.Write(buf);
}
sha.GetHash(tweak32);
}
Expand Down
Loading