From 36879c078d35d18d680957ff983551b8334c1499 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Fri, 1 Mar 2024 10:22:00 +0900 Subject: [PATCH] Make taproot a bit easier --- NBitcoin.Tests/TaprootBuilderTests.cs | 57 ++++++++-------- NBitcoin.Tests/transaction_tests.cs | 3 + NBitcoin/Script.cs | 27 +------- NBitcoin/Scripting/OutputDescriptor.cs | 20 +++--- NBitcoin/TapScript.cs | 95 ++++++++++++++++++++++++++ NBitcoin/TaprootSpendInfo.cs | 55 +++++++-------- 6 files changed, 162 insertions(+), 95 deletions(-) create mode 100644 NBitcoin/TapScript.cs diff --git a/NBitcoin.Tests/TaprootBuilderTests.cs b/NBitcoin.Tests/TaprootBuilderTests.cs index 4a647f66c..fd5282b8b 100644 --- a/NBitcoin.Tests/TaprootBuilderTests.cs +++ b/NBitcoin.Tests/TaprootBuilderTests.cs @@ -28,11 +28,11 @@ public void TestVectorsCore() var scriptWeights = new[] { - (10u, Script.FromHex("51")), - (20u, Script.FromHex("52")), - (20u, Script.FromHex("53")), - (30u, Script.FromHex("54")), - (19u, Script.FromHex("55")), + (10u, Script.FromHex("51").ToTapScript(TapLeafVersion.C0)), + (20u, Script.FromHex("52").ToTapScript(TapLeafVersion.C0)), + (20u, Script.FromHex("53").ToTapScript(TapLeafVersion.C0)), + (30u, Script.FromHex("54").ToTapScript(TapLeafVersion.C0)), + (19u, Script.FromHex("55").ToTapScript(TapLeafVersion.C0)), }; var treeInfo = TaprootSpendInfo.WithHuffmanTree(internalKey, scriptWeights); @@ -52,7 +52,7 @@ public void TestVectorsCore() Assert.True( treeInfo .ScriptToMerkleProofMap! - .TryGetValue((Script.FromHex(script), (byte)TaprootConstants.TAPROOT_LEAF_TAPSCRIPT), out var scriptSet) + .TryGetValue(Script.FromHex(script).ToTapScript(TapLeafVersion.C0), out var scriptSet) ); var actualLength = scriptSet[0]; Assert.Equal(expectedLength, actualLength.Count); @@ -62,7 +62,7 @@ public void TestVectorsCore() foreach (var (_, script) in scriptWeights) { - var ctrlBlock = treeInfo.GetControlBlock(script, (byte)TaprootConstants.TAPROOT_LEAF_TAPSCRIPT); + var ctrlBlock = treeInfo.GetControlBlock(script); Assert.True(ctrlBlock.VerifyTaprootCommitment(outputKey, script)); } } @@ -82,11 +82,11 @@ public void TaptreeBuilderTests() // / \ / \ // A B C / \ // D E - var a = Script.FromHex("51"); - var b = Script.FromHex("52"); - var c = Script.FromHex("53"); - var d = Script.FromHex("54"); - var e = Script.FromHex("55"); + var a = Script.FromHex("51").ToTapScript(TapLeafVersion.C0); + var b = Script.FromHex("52").ToTapScript(TapLeafVersion.C0); + var c = Script.FromHex("53").ToTapScript(TapLeafVersion.C0); + var d = Script.FromHex("54").ToTapScript(TapLeafVersion.C0); + var e = Script.FromHex("55").ToTapScript(TapLeafVersion.C0); builder .AddLeaf(2, a) .AddLeaf(2, b) @@ -100,12 +100,12 @@ public void TaptreeBuilderTests() var outputKey = treeInfo.OutputPubKey; foreach (var script in new[] { a, b, c, d, e }) { - var ctrlBlock = treeInfo.GetControlBlock(script, (byte)TaprootConstants.TAPROOT_LEAF_TAPSCRIPT); + var ctrlBlock = treeInfo.GetControlBlock(script); Assert.True(ctrlBlock.VerifyTaprootCommitment(outputKey, script)); } } - private TaprootBuilder ProcessScriptTrees(JToken v, TaprootBuilder builder, List<(Script, byte)> leaves, + private TaprootBuilder ProcessScriptTrees(JToken v, TaprootBuilder builder, List leaves, uint depth) { @@ -119,10 +119,10 @@ private TaprootBuilder ProcessScriptTrees(JToken v, TaprootBuilder builder, List } else { - var script = Script.FromHex(v["script"].ToString()); + var script = Script.FromHex(v["script"].ToString()).ToTapScript(TapLeafVersion.C0); var ver = (byte)(ulong)v["leafVersion"]; - leaves.Add((script, ver)); - builder = builder.AddLeaf(depth, script, ver); + leaves.Add(script); + builder = builder.AddLeaf(depth, script); } return builder; } @@ -133,14 +133,13 @@ public void CanHandleMultipleProofForSameScript() { var internalKey = TaprootInternalPubKey.Parse("93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51"); var data = new byte[] { 0x01 }; - var sc = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(data)); - var version = (byte)TaprootConstants.TAPROOT_LEAF_TAPSCRIPT; - var nodeInfoA = TaprootNodeInfo.NewLeafWithVersion(sc, version); - var nodeInfoB = TaprootNodeInfo.NewLeafWithVersion(sc, version); + var sc = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(data)).ToTapScript(TapLeafVersion.C0); + var nodeInfoA = TaprootNodeInfo.NewLeaf(sc); + var nodeInfoB = TaprootNodeInfo.NewLeaf(sc); var rootNode = nodeInfoA + nodeInfoB; var info = TaprootSpendInfo.FromNodeInfo(internalKey, rootNode); Assert.Single(info.ScriptToMerkleProofMap); - Assert.True(info.ScriptToMerkleProofMap.TryGetValue((sc, version), out var proofs)); + Assert.True(info.ScriptToMerkleProofMap.TryGetValue(sc, out var proofs)); Assert.Equal(2, proofs.Count); } @@ -168,16 +167,16 @@ public void BIP341Tests() var leafHashes = arr["intermediary"]!["leafHashes"]!.ToArray(); var ctrlBlocks = arr["expected"]!["scriptPathControlBlocks"]!.ToArray(); var builder = new TaprootBuilder(); - var leaves = new List<(Script, byte)>(); + var leaves = new List(); builder = ProcessScriptTrees(scriptTree, builder, leaves, 0); var spendInfo = builder.Finalize(internalKey); - foreach (var (i, (script, version)) in leaves.Select((v, i) => (i, v))) + foreach (var (i, script) in leaves.Select((v, i) => (i, v))) { var expectedLeafHash = new uint256(Encoders.Hex.DecodeData(leafHashes[i].ToString())); var expectedControlBlockStr = ctrlBlocks[i].ToString(); var expectedControlBlock = ControlBlock.FromHex(expectedControlBlockStr); - var leafHash = script.TaprootLeafHash(version); - var ctrlBlock = spendInfo.GetControlBlock(script, version); + var leafHash = script.LeafHash; + var ctrlBlock = spendInfo.GetControlBlock(script); Assert.Equal(expectedLeafHash, leafHash); var ctrlStr = Encoders.Hex.EncodeData(ctrlBlock.ToBytes()); _testOutputHelper.WriteLine($"Control block str {ctrlStr}. expected {expectedControlBlockStr}"); @@ -210,9 +209,9 @@ public void ScriptPathSpendUnitTest1() { var builder = new TaprootBuilder(); builder - .AddLeaf(1, Script.FromHex("210203a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5ac")) - .AddLeaf(2, Script.FromHex("2102a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac")) - .AddLeaf(2, Script.FromHex("210203a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5ac")); + .AddLeaf(1, Script.FromHex("210203a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5ac").ToTapScript(TapLeafVersion.C0)) + .AddLeaf(2, Script.FromHex("2102a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac").ToTapScript(TapLeafVersion.C0)) + .AddLeaf(2, Script.FromHex("210203a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5ac").ToTapScript(TapLeafVersion.C0)); var internalKey = TaprootInternalPubKey.Parse("03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5"); var info = builder.Finalize(internalKey); diff --git a/NBitcoin.Tests/transaction_tests.cs b/NBitcoin.Tests/transaction_tests.cs index ce727887b..e1cc35dec 100644 --- a/NBitcoin.Tests/transaction_tests.cs +++ b/NBitcoin.Tests/transaction_tests.cs @@ -3129,6 +3129,9 @@ public void CanParseTransaction() [Fact] public void Play() { + var script = new Key().GetScriptPubKey(ScriptPubKeyType.Legacy); + var tapSript = script.ToTapScript(TapLeafVersion.C0); + var aa = PSBT.Parse("cHNidP8BAHEBAAAAAa5DWRuSCbbha7kDIp/LMMEZCYyyX4S6cBp7zWulUa/MAQAAAAD/////AhEoKgQAAAAAFgAU7nHAKjqvWjNf/8RQqlA77gFfVZcALTEBAAAAABYAFJetm1OALTie8TP5CY3/moUsteKlAAAAAAABAR8ljFsFAAAAABYAFO5xwCo6r1ozX//EUKpQO+4BX1WXIgIC1S6EeEs43Kpiqww0O0noYaUxYubyjtkZJIDCLyZBbx1HMEQCIG2DB/kiJIemnd1io2FH5YfmYbaYoUs0Yx5rujhTrYYJAiAM5uVbmbELCKssXXeVjKeD7hggtghj2OZcTIezwgfoTAEiBgLVLoR4SzjcqmKrDDQ7SehhpTFi5vKO2RkkgMIvJkFvHRgDOcj3VAAAgAEAAIAAAACAAQAAAAEAAAAAIgIC1S6EeEs43Kpiqww0O0noYaUxYubyjtkZJIDCLyZBbx0YAznI91QAAIABAACAAAAAgAEAAAABAAAAAAA=", Altcoins.Groestlcoin.Instance.Testnet); aa.AssertSanity(); var aaew = aa.CheckSanity(); diff --git a/NBitcoin/Script.cs b/NBitcoin/Script.cs index c13a5a5c2..617d5c8ff 100644 --- a/NBitcoin/Script.cs +++ b/NBitcoin/Script.cs @@ -520,29 +520,6 @@ public int Length } } -#if HAS_SPAN - private uint256? _leafHash; - public uint256 TaprootV1LeafHash - { - get - { - if (_leafHash is not null) - return _leafHash; - _leafHash = TaprootLeafHash((byte)TaprootConstants.TAPROOT_LEAF_TAPSCRIPT); - return _leafHash; - } - } - - internal uint256 TaprootLeafHash(byte version) - { - var hash = new HashStream { SingleSHA256 = true }; - hash.InitializeTagged("TapLeaf"); - hash.WriteByte(version); - var bs = new BitcoinStream(hash, true); - bs.ReadWrite(this); - return hash.GetHash(); - } -#endif /// /// Extract the ScriptCode delimited by the codeSeparatorIndex th OP_CODESEPARATOR. /// @@ -905,7 +882,9 @@ public byte[] ToBytes(bool @unsafe) } public WitScript ToWitScript() => new WitScript(this); - +#if HAS_SPAN + public TapScript ToTapScript(TapLeafVersion version) => new TapScript(this, version); +#endif public byte[] ToCompressedBytes() { var compressor = new ScriptCompressor(this); diff --git a/NBitcoin/Scripting/OutputDescriptor.cs b/NBitcoin/Scripting/OutputDescriptor.cs index e2621d115..cac3ad94a 100644 --- a/NBitcoin/Scripting/OutputDescriptor.cs +++ b/NBitcoin/Scripting/OutputDescriptor.cs @@ -89,13 +89,13 @@ internal class TreeNode internal bool Done; } - public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey outputKey, [MaybeNullWhen(false)]out List> result) + public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey outputKey, [MaybeNullWhen(false)]out List> result) { result = null; if (!outputKey.CheckTapTweak(info.InternalPubKey, info.MerkleRoot, info.OutputPubKey.OutputKeyParity)) throw new ArgumentException($"TapTweak mismatch (outputKey: {outputKey}, internalKey: {info.InternalPubKey}, merkleRoot: {info.MerkleRoot})"); - result = new List>(); + result = new List>(); if (info.IsKeyPathOnlySpend) return true; @@ -106,7 +106,7 @@ public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey ou { foreach (var control in kv.Value) { - var (script, leafVersion) = kv.Key; + var script = kv.Key; var controlB = control.Select(h => h.ToBytes()).SelectMany(x => x).ToArray(); // Skip script records with invalid control block size. if (controlB.Length % TaprootConstants.TAPROOT_CONTROL_NODE_SIZE != 0) @@ -115,7 +115,7 @@ public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey ou continue; } - var leafHash = new TaprootScriptLeaf(script, leafVersion).LeafHash; + var leafHash = new TaprootScriptLeaf(script).LeafHash; TreeNode node = root; var levels = controlB.Length/ TaprootConstants.TAPROOT_CONTROL_NODE_SIZE; @@ -163,7 +163,7 @@ public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey ou if (node.Sub?.Item1 is not null) return false; node.Explored = true; node.Inner = false; - node.Leaf = new TaprootScriptLeaf(script, leafVersion); + node.Leaf = new TaprootScriptLeaf(script); node.Hash = leafHash; } } @@ -195,7 +195,7 @@ public static bool InferTaprootTree(this TaprootSpendInfo info, TaprootPubKey ou } else if (!node.Inner) { - result.Add(new Tuple((int)stack.Count - 1, node.Leaf!.Script, node.Leaf.Version)); + result.Add(new Tuple((int)stack.Count - 1, node.Leaf!.Script)); node.Done = true; stack.Pop(); } @@ -544,7 +544,7 @@ internal bool TryGetSpendInfo(ISigningRepository repo, [MaybeNullWhen(false)] ou foreach (var s in scripts) { - builder.AddLeaf((uint)depth, s); + builder.AddLeaf((uint)depth, s.ToTapScript(TapLeafVersion.C0)); } } } @@ -748,7 +748,7 @@ private bool TryExpand( return false; foreach (var s in subScripts) { - builder.AddLeaf((uint)depth, s); + builder.AddLeaf((uint)depth, s.ToTapScript(TapLeafVersion.C0)); } } } @@ -1018,10 +1018,10 @@ private static OutputDescriptor InferFromScript(Script sc, ISigningRepository re bool ok = true; var subScripts = new List(); var depths = new List(); - foreach (var (depth, script, leafVersion) in tree) + foreach (var (depth, script) in tree) { OutputDescriptor? subdesc = - (leafVersion == TaprootConstants.TAPROOT_LEAF_TAPSCRIPT) + (script.Version == TapLeafVersion.C0) ? InferFromScript(script, repo, network, ScriptContext.P2TR) : null; if (subdesc is null) diff --git a/NBitcoin/TapScript.cs b/NBitcoin/TapScript.cs new file mode 100644 index 000000000..b363c7937 --- /dev/null +++ b/NBitcoin/TapScript.cs @@ -0,0 +1,95 @@ +#nullable enable +#if HAS_SPAN +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NBitcoin.Crypto; + +namespace NBitcoin +{ + public enum TapLeafVersion : byte + { + C0 = 0xC0, + } + public class TapScript + { + public Script Script { get; } + public TapLeafVersion Version { get; } + uint256? _LeafHash; + public uint256 LeafHash => _LeafHash ??= ComputeLeafHash(Script, Version); + + internal static uint256 ComputeLeafHash(Script script, TapLeafVersion version) + { + var hash = new HashStream { SingleSHA256 = true }; + hash.InitializeTagged("TapLeaf"); + hash.WriteByte((byte)version); + var bs = new BitcoinStream(hash, true); + bs.ReadWrite(script); + return hash.GetHash(); + } + public TapScript(Script script, TapLeafVersion version) + { + //Lazy + if (script is null) + throw new ArgumentNullException(nameof(script)); + this.Script = script; + this.Version = version; + } + + public TapScript(TapScript script) + { + if (script is null) + throw new ArgumentNullException(nameof(script)); + Script = script.Script; + Version = script.Version; + _LeafHash = script._LeafHash; + } + + [return: NotNullIfNotNull("script")] + public static implicit operator Script?(TapScript? script) + { + if (script is null) + return null; + return script.Script; + } + public override string ToString() + { + return $"{(byte)Version:X2}: {Script}"; + } + + + public override bool Equals(object? obj) + { + TapScript? item = obj as TapScript; + if (item is null) + return false; + if (this._LeafHash is not null && item._LeafHash is not null) + return _LeafHash.Equals(item._LeafHash); + return Script == item.Script && Version == item.Version; + } + public static bool operator ==(TapScript a, TapScript b) + { + if (System.Object.ReferenceEquals(a, b)) + return true; + if (a is null || b is null) + return false; + if (a._LeafHash is not null && b._LeafHash is not null) + return a._LeafHash == b._LeafHash; + return a.Script == b.Script && a.Version == b.Version; + } + + public static bool operator !=(TapScript a, TapScript b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return HashCode.Combine(Script, Version); + } + } +} +#endif diff --git a/NBitcoin/TaprootSpendInfo.cs b/NBitcoin/TaprootSpendInfo.cs index 3dc6a744c..599769a58 100644 --- a/NBitcoin/TaprootSpendInfo.cs +++ b/NBitcoin/TaprootSpendInfo.cs @@ -12,7 +12,6 @@ #endif using TaprootMerkleBranch = System.Collections.Generic.List; using static NBitcoin.TaprootConstants; -using LeafVersion = System.Byte; namespace NBitcoin { @@ -104,17 +103,15 @@ static class EnumerableExtensions public class TaprootScriptLeaf { - internal Script Script { get; } - internal LeafVersion Version { get; } + internal TapScript Script { get; } internal TaprootMerkleBranch MerkleBranch { get; } = new (); - public TaprootScriptLeaf(Script script, LeafVersion version) + public TaprootScriptLeaf(TapScript script) { Script = script ?? throw new ArgumentNullException(nameof(script)); - Version = version; } public uint Depth => (uint)this.MerkleBranch.Count; - public uint256 LeafHash => Script.TaprootLeafHash(Version); + public uint256 LeafHash => Script.LeafHash; } /// @@ -137,13 +134,9 @@ internal TaprootNodeInfo(uint256 hash, List leaves, bool hasH HasHiddenNodes = hasHiddenNodes; } - public static TaprootNodeInfo NewLeafWithVersion(Script sc, LeafVersion leafVersion) + public static TaprootNodeInfo NewLeaf(TapScript script) { - var leaf = new TaprootScriptLeaf - ( - script: sc, - version: leafVersion - ); + var leaf = new TaprootScriptLeaf(script); return new TaprootNodeInfo ( @@ -288,9 +281,9 @@ public byte[] ToBytes() /// Full verification must also execute the script with witness data. /// /// - public bool VerifyTaprootCommitment(TaprootFullPubKey outputKey, Script script) + public bool VerifyTaprootCommitment(TaprootFullPubKey outputKey, TapScript script) { - var merkleRoot = ScriptEvaluationContext.ComputeTaprootMerkleRoot(this.ToBytes(), script.TaprootV1LeafHash); + var merkleRoot = ScriptEvaluationContext.ComputeTaprootMerkleRoot(this.ToBytes(), script.LeafHash); return outputKey.CheckTapTweak(InternalPubKey, merkleRoot, OutputParityIsOdd); } @@ -330,7 +323,7 @@ public class TaprootSpendInfo /// In all cases, keeping one should be enough for spending funds, but we keep all of the paths /// so that a full tree can be constructed again from spending data if required. /// - internal ConcurrentDictionary<(Script, LeafVersion), List> ScriptToMerkleProofMap { get; } + internal ConcurrentDictionary> ScriptToMerkleProofMap { get; } public uint256? MerkleRoot { get; } public bool IsKeyPathOnlySpend => this.ScriptToMerkleProofMap.IsEmpty; @@ -338,14 +331,14 @@ public class TaprootSpendInfo public TaprootSpendInfo( TaprootInternalPubKey internalPubKey, TaprootFullPubKey outputPubKey, - Dictionary<(Script, LeafVersion), List> scriptToMerkleProofMap, + Dictionary> scriptToMerkleProofMap, uint256? merkleRoot ) { if (internalPubKey is null) throw new ArgumentNullException(nameof(internalPubKey)); if (outputPubKey == null) throw new ArgumentNullException(nameof(outputPubKey)); if (scriptToMerkleProofMap == null) throw new ArgumentNullException(nameof(scriptToMerkleProofMap)); - var map = new ConcurrentDictionary<(Script, LeafVersion), List>(); + var map = new ConcurrentDictionary>(); foreach (var kv in scriptToMerkleProofMap) { map.AddOrReplace(kv.Key, kv.Value); @@ -360,7 +353,7 @@ public static TaprootSpendInfo CreateKeySpend(TaprootInternalPubKey internalPubK { var outputKey = internalPubKey.GetTaprootFullPubKey(merkleRoot); return new TaprootSpendInfo(internalPubKey, outputKey, - new Dictionary<(Script, LeafVersion), List>(), merkleRoot); + new Dictionary>(), merkleRoot); } @@ -370,7 +363,7 @@ public static TaprootSpendInfo FromNodeInfo(TaprootInternalPubKey internalPubKey var info = TaprootSpendInfo.CreateKeySpend(internalPubKey, rootHash); foreach (var leaves in node.Leaves) { - var k = (leaves.Script, leaves.Version); + var k = leaves.Script; var v = leaves.MerkleBranch; if (info.ScriptToMerkleProofMap.TryGetValue(k, out var set)) set.Add(v); @@ -380,7 +373,7 @@ public static TaprootSpendInfo FromNodeInfo(TaprootInternalPubKey internalPubKey return info; } - public static TaprootSpendInfo WithHuffmanTree(TaprootInternalPubKey internalPubKey, params (UInt32, Script)[] scriptWeights) => + public static TaprootSpendInfo WithHuffmanTree(TaprootInternalPubKey internalPubKey, params (UInt32, TapScript)[] scriptWeights) => TaprootBuilder.WithHuffmanTree(scriptWeights).Finalize(internalPubKey); /// @@ -390,10 +383,10 @@ public static TaprootSpendInfo WithHuffmanTree(TaprootInternalPubKey internalPub /// If there are multiple control blocks possible, gets the shortest one. /// If the script is not contained in the `TaprootSpendInfo` false. /// - public bool TryGetControlBlock(Script script, LeafVersion version, [MaybeNullWhen(false)] out ControlBlock controlBlock) + public bool TryGetControlBlock(TapScript script, [MaybeNullWhen(false)] out ControlBlock controlBlock) { controlBlock = null; - if (!this.ScriptToMerkleProofMap.TryGetValue((script, version), out var merkleBranchSet)) + if (!this.ScriptToMerkleProofMap.TryGetValue(script, out var merkleBranchSet)) return false; // choose the smallest merkle proof. @@ -403,16 +396,16 @@ public bool TryGetControlBlock(Script script, LeafVersion version, [MaybeNullWhe outputParityIsOdd: this.OutputPubKey.OutputKeyParity, internalPubKey: InternalPubKey, - leafVersion: version, + leafVersion: (byte)script.Version, merkleBranch: smallest! ); return true; } - public ControlBlock GetControlBlock(Script script, LeafVersion version) + public ControlBlock GetControlBlock(TapScript script) { - if (!TryGetControlBlock(script, version, out var ctrl)) - throw new InvalidDataException($"Failed to get control block for script: {script}, version: {version}"); + if (!TryGetControlBlock(script, out var ctrl)) + throw new InvalidDataException($"Failed to get control block for script: {script}"); return ctrl; } @@ -454,7 +447,7 @@ internal List Branch /// not happen unless you are dealing with billions of branches with weights close to 2^32. /// /// - public static TaprootBuilder WithHuffmanTree(params (UInt32, Script)[] scriptWeights) + public static TaprootBuilder WithHuffmanTree(params (UInt32, TapScript)[] scriptWeights) { if (scriptWeights == null) throw new ArgumentNullException(nameof(scriptWeights)); if (scriptWeights.Length == 0) throw new ArgumentException("Scripts has 0 length.", nameof(scriptWeights)); @@ -462,7 +455,7 @@ public static TaprootBuilder WithHuffmanTree(params (UInt32, Script)[] scriptWei foreach (var (p, leaf) in scriptWeights) { - var nodeInfo = TaprootNodeInfo.NewLeafWithVersion(leaf, (byte)TAPROOT_LEAF_TAPSCRIPT); + var nodeInfo = TaprootNodeInfo.NewLeaf(leaf); nodeWeights.Enqueue((p, nodeInfo), p); } @@ -495,11 +488,9 @@ public static TaprootBuilder WithHuffmanTree(params (UInt32, Script)[] scriptWei /// /// /// - public TaprootBuilder AddLeaf(uint depth, Script script, LeafVersion version) => - Insert(TaprootNodeInfo.NewLeafWithVersion(script, version), depth); + public TaprootBuilder AddLeaf(uint depth, TapScript script) => + Insert(TaprootNodeInfo.NewLeaf(script), depth); - public TaprootBuilder AddLeaf(uint depth, Script script) => - AddLeaf(depth, script, (byte)TAPROOT_LEAF_TAPSCRIPT); /// /// Adds a hidden/omitted node at `depth` to the builder. Errors if the leaves are not provided in DFS walk order. /// The depth of the root node is 0.