Skip to content

Commit

Permalink
Add SignTaprootScriptSpend (#1230)
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier authored Oct 12, 2024
1 parent 0bc86c8 commit db86ab3
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 10 deletions.
116 changes: 116 additions & 0 deletions NBitcoin.Tests/TaprootAddressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,122 @@ public void CanSignUsingTaproot()
}
}

[Fact]
public void CanSignUsingTapscriptAndKeySpend()
{
var ctx = Context.Instance;
var ecPrivateKeysHex = new[] {
"527b33ce0c67ec2cc12ba7bb2e48dda66884a5c4b6d110be894a10802b21b3d6",
"54082c2ee51166cfa4fd8c3076ee30043808b3cca351e3288360af81d3ef9f8c",
"cba536615bbe1ae2fdf8100104829db61c8cf2a7f0bd9a225cbf09e79d83096c"
};

var ecPrivateKeys = new ECPrivKey[ecPrivateKeysHex.Length];
for (int i = 0; i < ecPrivateKeysHex.Length; i++)
{
byte[] privateKeyBytes = Encoders.Hex.DecodeData(ecPrivateKeysHex[i]);
ecPrivateKeys[i] = ctx.CreateECPrivKey(privateKeyBytes);
}

var privateKeys = new Key[ecPrivateKeysHex.Length];
for (int i = 0; i < ecPrivateKeysHex.Length; i++)
{
byte[] privateKeyBytes = Encoders.Hex.DecodeData(ecPrivateKeysHex[i]);
privateKeys[i] = new Key(privateKeyBytes);
}

var peers = ecPrivateKeys.Length;
TaprootPubKey taprootPubKey = null;



// XOnly pubKey
var ecPubKeys = ecPrivateKeys.Select(c => c.CreateXOnlyPubKey()).ToArray();
// pubKeys (compressd)
//var ecPubKeys = ecPrivateKeys.Select(c => c.CreatePubKey()).ToArray();


var howManyScripts = 3;
var Scripts = new TapScript[howManyScripts];
Scripts[0] = new Script(Op.GetPushOp(ecPubKeys[1].ToBytes()), OpcodeType.OP_CHECKSIG, Op.GetPushOp(ecPubKeys[2].ToBytes()), OpcodeType.OP_CHECKSIGADD, OpcodeType.OP_2, OpcodeType.OP_NUMEQUAL).ToTapScript(TapLeafVersion.C0);
Scripts[1] = new Script(Op.GetPushOp(ecPubKeys[0].ToBytes()), OpcodeType.OP_CHECKSIG, Op.GetPushOp(ecPubKeys[1].ToBytes()), OpcodeType.OP_CHECKSIGADD, OpcodeType.OP_2, OpcodeType.OP_NUMEQUAL).ToTapScript(TapLeafVersion.C0);
Scripts[2] = new Script(Op.GetPushOp(ecPubKeys[0].ToBytes()), OpcodeType.OP_CHECKSIG, Op.GetPushOp(ecPubKeys[2].ToBytes()), OpcodeType.OP_CHECKSIGADD, OpcodeType.OP_2, OpcodeType.OP_NUMEQUAL).ToTapScript(TapLeafVersion.C0);



var scriptWeightsList = new List<(UInt32, TapScript)>
{
(30u, Scripts[0]),
(30u, Scripts[1]),
(30u, Scripts[2])
};

var scriptWeights = scriptWeightsList.ToArray();

var keySpend = new Key(Encoders.Hex.DecodeData("c0655fae21a8b7fae19cfeac6135ded8090920f9640a148b0fd5ff9c15c6e948"));
var KeySpendinternalPubKey = keySpend.PubKey.TaprootInternalKey;
var treeInfo = TaprootSpendInfo.WithHuffmanTree(KeySpendinternalPubKey, scriptWeights);
taprootPubKey = treeInfo.OutputPubKey.OutputKey;
using (var nodeBuilder = NodeBuilder.Create(NodeDownloadData.Bitcoin.v25_0, Network.RegTest))
{
var rpc = nodeBuilder.CreateNode().CreateRPCClient();
nodeBuilder.StartAll();
rpc.Generate(nodeBuilder.Network.Consensus.CoinbaseMaturity + 1);

var addr = taprootPubKey.GetAddress(Network.RegTest);

foreach (var useKeySpend in new[] { false, true })
{
var txid = rpc.SendToAddress(addr, Money.Coins(1.0m));

var tx = rpc.GetRawTransaction(txid);
var spentOutput = tx.Outputs.AsIndexedOutputs().First(o => o.TxOut.ScriptPubKey == addr.ScriptPubKey);

var spender = nodeBuilder.Network.CreateTransaction();
spender.Inputs.Add(new OutPoint(tx, spentOutput.N));

var dest = rpc.GetNewAddress();
spender.Outputs.Add(Money.Coins(0.7m), dest);
spender.Outputs.Add(Money.Coins(0.2999000m), addr);


var sighash = TaprootSigHash.All | TaprootSigHash.AnyoneCanPay;

var spentOutputsIn = new[] { spentOutput.TxOut };


TaprootExecutionData extectionData;

// ADDRESS PATH
if (useKeySpend)
extectionData = new TaprootExecutionData(0) { SigHash = sighash };
else
extectionData = new TaprootExecutionData(0, Scripts[2].LeafHash) { SigHash = sighash };

var hash = spender.GetSignatureHashTaproot(spentOutputsIn, extectionData);

if (useKeySpend)
{
var sig = keySpend.SignTaprootKeySpend(hash, treeInfo.MerkleRoot, sighash);
spender.Inputs[0].WitScript = new WitScript(Op.GetPushOp(sig.ToBytes()));
}
else
{
// use this signatures if XOnly pubKey
var sig11 = privateKeys[0].SignTaprootScriptSpend(hash, sighash);
var sig22 = privateKeys[2].SignTaprootScriptSpend(hash, sighash);
spender.Inputs[0].WitScript = new WitScript(Op.GetPushOp(sig22.ToBytes()), Op.GetPushOp(sig11.ToBytes()), Op.GetPushOp(Scripts[2].Script.ToBytes()), Op.GetPushOp(treeInfo.GetControlBlock(Scripts[2]).ToBytes()));

}

var validator = spender.CreateValidator(new[] { spentOutput.TxOut });
var result = validator.ValidateInput(0);
Assert.Null(result.Error);
rpc.SendRawTransaction(spender);
}
}
}

[Fact]
[Trait("UnitTest", "UnitTest")]
public void CanGenerateTaprootPubKey()
Expand Down
9 changes: 0 additions & 9 deletions NBitcoin.Tests/transaction_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
using Xunit.Abstractions;
using Encoders = NBitcoin.DataEncoders.Encoders;
using static NBitcoin.Tests.Helpers.PrimitiveUtils;
using Newtonsoft.Json.Schema;
using Xunit.Sdk;
using NBitcoin.Scripting;
using NBitcoin.RPC;

namespace NBitcoin.Tests
{
Expand Down Expand Up @@ -3129,11 +3125,6 @@ public void CanParseTransaction()
[Fact]
public void Play()
{
var aa = PSBT.Parse("cHNidP8BAHEBAAAAAa5DWRuSCbbha7kDIp/LMMEZCYyyX4S6cBp7zWulUa/MAQAAAAD/////AhEoKgQAAAAAFgAU7nHAKjqvWjNf/8RQqlA77gFfVZcALTEBAAAAABYAFJetm1OALTie8TP5CY3/moUsteKlAAAAAAABAR8ljFsFAAAAABYAFO5xwCo6r1ozX//EUKpQO+4BX1WXIgIC1S6EeEs43Kpiqww0O0noYaUxYubyjtkZJIDCLyZBbx1HMEQCIG2DB/kiJIemnd1io2FH5YfmYbaYoUs0Yx5rujhTrYYJAiAM5uVbmbELCKssXXeVjKeD7hggtghj2OZcTIezwgfoTAEiBgLVLoR4SzjcqmKrDDQ7SehhpTFi5vKO2RkkgMIvJkFvHRgDOcj3VAAAgAEAAIAAAACAAQAAAAEAAAAAIgIC1S6EeEs43Kpiqww0O0noYaUxYubyjtkZJIDCLyZBbx0YAznI91QAAIABAACAAAAAgAEAAAABAAAAAAA=", Altcoins.Groestlcoin.Instance.Testnet);
aa.AssertSanity();
var aaew = aa.CheckSanity();
aa.Finalize();
var tx = aa.ExtractTransaction();
}

[Fact]
Expand Down
18 changes: 18 additions & 0 deletions NBitcoin/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,24 @@ public ECDSASignature Sign(uint256 hash)
return _ECKey.Sign(hash, true);
}
#if HAS_SPAN
public TaprootSignature SignTaprootScriptSpend(uint256 hash, TaprootSigHash sigHash = TaprootSigHash.Default)
{
return SignTaprootScriptSpend(hash, null, sigHash);
}
public TaprootSignature SignTaprootScriptSpend(uint256 hash, uint256? merkleRoot, TaprootSigHash sigHash)
{
return SignTaprootScriptSpend(hash, merkleRoot, null, sigHash);
}
public TaprootSignature SignTaprootScriptSpend(uint256 hash, uint256? merkleRoot, uint256? aux, TaprootSigHash sigHash)
{
if (hash == null)
throw new ArgumentNullException(nameof(hash));
AssertNotDisposed();
Span<byte> buf = stackalloc byte[32];
hash.ToBytes(buf);
var sig = aux?.ToBytes() is byte[] auxbytes ? _ECKey.SignBIP340(buf, auxbytes) : _ECKey.SignBIP340(buf);
return new TaprootSignature(new SchnorrSignature(sig), sigHash);
}
public TaprootSignature SignTaprootKeySpend(uint256 hash, TaprootSigHash sigHash = TaprootSigHash.Default)
{
return SignTaprootKeySpend(hash, null, sigHash);
Expand Down
2 changes: 1 addition & 1 deletion NBitcoin/NBitcoin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">7.0.39</Version>
<Version Condition=" '$(Version)' == '' ">7.0.40</Version>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
Expand Down

0 comments on commit db86ab3

Please sign in to comment.