From 6794746d66390cd3131aa0342d3cf8d96b175bdf Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 21 Oct 2020 22:25:17 +0900 Subject: [PATCH] Add Coin.IsMalleable, rename HashVersion.Witness to WitnessV0 --- NBitcoin.Altcoins/Elements/ElementsTransaction.cs | 4 ++-- NBitcoin.Altcoins/ForkIdTransaction.cs | 8 ++++---- NBitcoin.Tests/transaction_tests.cs | 8 ++++---- NBitcoin/BIP174/PSBTInput.cs | 4 ++-- NBitcoin/Coin.cs | 14 +++++++++----- NBitcoin/Policy/StandardTransactionPolicy.cs | 2 +- NBitcoin/Script.cs | 6 ++++-- NBitcoin/ScriptEvaluationContext.cs | 4 ++-- NBitcoin/Transaction.cs | 8 ++++---- NBitcoin/TransactionBuilder.cs | 8 ++++---- 10 files changed, 36 insertions(+), 30 deletions(-) diff --git a/NBitcoin.Altcoins/Elements/ElementsTransaction.cs b/NBitcoin.Altcoins/Elements/ElementsTransaction.cs index 8c78f2f219..787fee7330 100644 --- a/NBitcoin.Altcoins/Elements/ElementsTransaction.cs +++ b/NBitcoin.Altcoins/Elements/ElementsTransaction.cs @@ -743,7 +743,7 @@ internal void ReadWrite(BitcoinStream stream) public override uint256 GetSignatureHash(Script scriptCode, int nIn, SigHash nHashType, TxOut spentOutput, HashVersion sigversion, PrecomputedTransactionData precomputedTransactionData) { - if (sigversion == HashVersion.Witness) + if (sigversion == HashVersion.WitnessV0) { var spentOutputElem = spentOutput as ElementsTxOut; if (spentOutputElem == null) @@ -905,7 +905,7 @@ private static void WriteScriptCode(BitcoinStream stream, Script scriptCode) private uint256 GetIssuanceHash() { - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); for (int i = 0; i < this.Inputs.Count; i++) { if (Inputs[i] is ElementsTxIn elemInput && elemInput.HasAssetIssuance) diff --git a/NBitcoin.Altcoins/ForkIdTransaction.cs b/NBitcoin.Altcoins/ForkIdTransaction.cs index ed4c5bf222..eb689f589a 100644 --- a/NBitcoin.Altcoins/ForkIdTransaction.cs +++ b/NBitcoin.Altcoins/ForkIdTransaction.cs @@ -45,7 +45,7 @@ public override uint256 GetSignatureHash(Script scriptCode, int nIn, SigHash nHa if(UsesForkId(nHashType)) nForkHashType |= ForkId << 8; - if((SupportSegwit && sigversion == HashVersion.Witness) || UsesForkId(nHashType)) + if((SupportSegwit && sigversion == HashVersion.WitnessV0) || UsesForkId(nHashType)) { if (spentOutput?.Value == null || spentOutput.Value == TxOut.NullMoney) throw new ArgumentException("The output being signed with the amount must be provided", nameof(spentOutput)); @@ -191,7 +191,7 @@ private static uint256 GetHash(BitcoinStream stream) internal override uint256 GetHashOutputs() { uint256 hashOutputs; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach(var txout in Outputs) { ss.ReadWrite(txout); @@ -203,7 +203,7 @@ internal override uint256 GetHashOutputs() internal override uint256 GetHashSequence() { uint256 hashSequence; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach(var input in Inputs) { ss.ReadWrite(input.Sequence); @@ -215,7 +215,7 @@ internal override uint256 GetHashSequence() internal override uint256 GetHashPrevouts() { uint256 hashPrevouts; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach(var input in Inputs) { ss.ReadWrite(input.PrevOut); diff --git a/NBitcoin.Tests/transaction_tests.cs b/NBitcoin.Tests/transaction_tests.cs index 8ea43f459a..f4138330d6 100644 --- a/NBitcoin.Tests/transaction_tests.cs +++ b/NBitcoin.Tests/transaction_tests.cs @@ -3251,7 +3251,7 @@ public void bip143Test() { Transaction tx = Transaction.Parse("0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000", Network); var output = tx.Outputs.CreateNewTxOut(Money.Satoshis(0x23c34600L), new Script(Encoders.Hex.DecodeData("76a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac"))); - var h = tx.GetSignatureHash(output.ScriptPubKey, 1, SigHash.All, output, HashVersion.Witness); + var h = tx.GetSignatureHash(output.ScriptPubKey, 1, SigHash.All, output, HashVersion.WitnessV0); Assert.Equal(new uint256(Encoders.Hex.DecodeData("c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670"), true), h); } [Fact] @@ -3490,21 +3490,21 @@ public void CheckScriptCoinIsCoherent() scriptCoin = new ScriptCoin(c, key.PubKey.WitHash.ScriptPubKey); Assert.True(scriptCoin.RedeemType == RedeemType.P2SH); Assert.True(scriptCoin.IsP2SH); - Assert.True(scriptCoin.GetHashVersion() == HashVersion.Witness); + Assert.True(scriptCoin.GetHashVersion() == HashVersion.WitnessV0); //P2WSH c.ScriptPubKey = key.PubKey.ScriptPubKey.WitHash.ScriptPubKey; scriptCoin = new ScriptCoin(c, key.PubKey.ScriptPubKey); Assert.True(scriptCoin.RedeemType == RedeemType.WitnessV0); Assert.True(!scriptCoin.IsP2SH); - Assert.True(scriptCoin.GetHashVersion() == HashVersion.Witness); + Assert.True(scriptCoin.GetHashVersion() == HashVersion.WitnessV0); //P2SH(P2WSH) c.ScriptPubKey = key.PubKey.ScriptPubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey; scriptCoin = new ScriptCoin(c, key.PubKey.ScriptPubKey); Assert.True(scriptCoin.RedeemType == RedeemType.WitnessV0); Assert.True(scriptCoin.IsP2SH); - Assert.True(scriptCoin.GetHashVersion() == HashVersion.Witness); + Assert.True(scriptCoin.GetHashVersion() == HashVersion.WitnessV0); Assert.Throws(() => new ScriptCoin(c, key.PubKey.ScriptPubKey.WitHash.ScriptPubKey)); diff --git a/NBitcoin/BIP174/PSBTInput.cs b/NBitcoin/BIP174/PSBTInput.cs index 2f0ffeb63c..62a09da48d 100644 --- a/NBitcoin/BIP174/PSBTInput.cs +++ b/NBitcoin/BIP174/PSBTInput.cs @@ -306,7 +306,7 @@ public void UpdateFromCoin(ICoin coin) } } if (Parent.Network.Consensus.NeverNeedPreviousTxForSigning || - coin.GetHashVersion() == HashVersion.Witness || witness_script != null) + !coin.IsMalleable || witness_script != null) { witness_utxo = coin.TxOut; non_witness_utxo = null; @@ -934,7 +934,7 @@ public bool TrySlimUTXO() return false; if (Parent.Network.Consensus.NeverNeedPreviousTxForSigning || - coin.GetHashVersion() == HashVersion.Witness) + !coin.IsMalleable) { if (WitnessUtxo == null) { diff --git a/NBitcoin/Coin.cs b/NBitcoin/Coin.cs index 574296a550..869c318693 100644 --- a/NBitcoin/Coin.cs +++ b/NBitcoin/Coin.cs @@ -44,6 +44,10 @@ bool CanGetScriptCode get; } HashVersion GetHashVersion(); + bool IsMalleable + { + get; + } } public class IssuanceCoin : IColoredCoin @@ -62,7 +66,7 @@ public IssuanceCoin(OutPoint outpoint, TxOut txout) Bearer = new Coin(outpoint, txout); } - + public bool IsMalleable => Bearer.IsMalleable; public AssetId AssetId { get @@ -235,7 +239,7 @@ public AssetId AssetId return Amount.Id; } } - + public bool IsMalleable => TxOut.ScriptPubKey.IsMalleable; public AssetMoney Amount { get; @@ -444,7 +448,7 @@ public virtual Script GetScriptCode() return key.AsKeyId().ScriptPubKey; return ScriptPubKey; } - + public virtual bool IsMalleable => GetHashVersion() != HashVersion.WitnessV0; public virtual bool CanGetScriptCode { get @@ -456,7 +460,7 @@ public virtual bool CanGetScriptCode public virtual HashVersion GetHashVersion() { if (PayToWitTemplate.Instance.CheckScriptPubKey(ScriptPubKey)) - return HashVersion.Witness; + return HashVersion.WitnessV0; return HashVersion.Original; } @@ -769,7 +773,7 @@ public override HashVersion GetHashVersion() var isWitness = PayToWitTemplate.Instance.CheckScriptPubKey(ScriptPubKey) || PayToWitTemplate.Instance.CheckScriptPubKey(Redeem) || RedeemType == NBitcoin.RedeemType.WitnessV0; - return isWitness ? HashVersion.Witness : HashVersion.Original; + return isWitness ? HashVersion.WitnessV0 : HashVersion.Original; } /// diff --git a/NBitcoin/Policy/StandardTransactionPolicy.cs b/NBitcoin/Policy/StandardTransactionPolicy.cs index b8d9356232..2e6173d88d 100644 --- a/NBitcoin/Policy/StandardTransactionPolicy.cs +++ b/NBitcoin/Policy/StandardTransactionPolicy.cs @@ -111,7 +111,7 @@ public TransactionPolicyError[] Check(Transaction transaction, ICoin[] spentCoin foreach (var input in transaction.Inputs.AsIndexedInputs()) { var coin = spentCoins.FirstOrDefault(s => s.Outpoint == input.PrevOut); - if (coin != null && coin.GetHashVersion() != HashVersion.Witness) + if (coin != null && coin.IsMalleable) errors.Add(new InputPolicyError("Malleable input detected", input)); } } diff --git a/NBitcoin/Script.cs b/NBitcoin/Script.cs index db6aed06b9..58eadc7cc4 100644 --- a/NBitcoin/Script.cs +++ b/NBitcoin/Script.cs @@ -335,7 +335,9 @@ public enum OpcodeType : byte public enum HashVersion { Original = 0, - Witness = 1 + [Obsolete("Use HashVersion.WitnessV0 instead")] + Witness = 1, + WitnessV0 = 1 } public enum ScriptType @@ -1095,7 +1097,7 @@ public static ScriptSigs CombineSignatures(Script scriptPubKey, TransactionCheck { scriptSig1 = input1.WitSig.ToScript(); scriptSig2 = input2.WitSig.ToScript(); - hashVersion = HashVersion.Witness; + hashVersion = HashVersion.WitnessV0; } var context = new ScriptEvaluationContext(); diff --git a/NBitcoin/ScriptEvaluationContext.cs b/NBitcoin/ScriptEvaluationContext.cs index 70270a0e0b..125054e321 100644 --- a/NBitcoin/ScriptEvaluationContext.cs +++ b/NBitcoin/ScriptEvaluationContext.cs @@ -855,7 +855,7 @@ bool EvalScript(Script s, TransactionChecker checker, int hashversion) var vch = _stack.Top(-1); - if (hashversion == (int)HashVersion.Witness && (ScriptVerify & ScriptVerify.MinimalIf) != 0) + if (hashversion == (int)HashVersion.WitnessV0 && (ScriptVerify & ScriptVerify.MinimalIf) != 0) { if (vch.Length > 1) return SetError(ScriptError.MinimalIf); @@ -1655,7 +1655,7 @@ private bool CheckPubKeyEncoding(byte[] vchPubKey, int sigversion) Error = ScriptError.PubKeyType; return false; } - if ((ScriptVerify & ScriptVerify.WitnessPubkeyType) != 0 && sigversion == (int)HashVersion.Witness && !IsCompressedPubKey(vchPubKey)) + if ((ScriptVerify & ScriptVerify.WitnessPubkeyType) != 0 && sigversion == (int)HashVersion.WitnessV0 && !IsCompressedPubKey(vchPubKey)) { return SetError(ScriptError.WitnessPubkeyType); } diff --git a/NBitcoin/Transaction.cs b/NBitcoin/Transaction.cs index efd5520d1d..c22d4cf578 100644 --- a/NBitcoin/Transaction.cs +++ b/NBitcoin/Transaction.cs @@ -1988,7 +1988,7 @@ public TransactionCheckResult Check() public virtual uint256 GetSignatureHash(Script scriptCode, int nIn, SigHash nHashType, TxOut spentOutput, HashVersion sigversion, PrecomputedTransactionData precomputedTransactionData) { - if (sigversion == HashVersion.Witness) + if (sigversion == HashVersion.WitnessV0) { if (spentOutput?.Value == null || spentOutput.Value == TxOut.NullMoney) throw new ArgumentException("The output being signed with the amount must be provided", nameof(spentOutput)); @@ -2146,7 +2146,7 @@ private static uint256 GetHash(BitcoinStream stream) internal virtual uint256 GetHashOutputs() { uint256 hashOutputs; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach (var txout in Outputs) { txout.ReadWrite(ss); @@ -2158,7 +2158,7 @@ internal virtual uint256 GetHashOutputs() internal virtual uint256 GetHashSequence() { uint256 hashSequence; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach (var input in Inputs) { ss.ReadWrite((uint)input.Sequence); @@ -2170,7 +2170,7 @@ internal virtual uint256 GetHashSequence() internal virtual uint256 GetHashPrevouts() { uint256 hashPrevouts; - BitcoinStream ss = CreateHashWriter(HashVersion.Witness); + BitcoinStream ss = CreateHashWriter(HashVersion.WitnessV0); foreach (var input in Inputs) { ss.ReadWrite(input.PrevOut); diff --git a/NBitcoin/TransactionBuilder.cs b/NBitcoin/TransactionBuilder.cs index f51b5062b1..8f3f52d699 100644 --- a/NBitcoin/TransactionBuilder.cs +++ b/NBitcoin/TransactionBuilder.cs @@ -2194,7 +2194,7 @@ public void EstimateSizes(Transaction tx, out int witSize, out int baseSize) var coin = FindSignableCoin(txin) ?? FindCoin(txin.PrevOut); if (coin == null) throw CoinNotFound(txin); - if (coin.GetHashVersion() == HashVersion.Witness) + if (!coin.IsMalleable) hasWitness = true; else nonWitnessCount++; @@ -2263,7 +2263,7 @@ private void EstimateScriptSigSize(ICoin coin, ref int witSize, ref int baseSize if (scriptSigSize == -1) scriptSigSize += coin.TxOut.ScriptPubKey.Length; //Using heurestic to approximate size of unknown scriptPubKey - if (coin.GetHashVersion() == HashVersion.Witness) + if (!coin.IsMalleable) { baseSize += new Protocol.VarInt((ulong)p2shPushRedeemSize).GetSerializedSize(); witSize += scriptSigSize + new Protocol.VarInt((ulong)(scriptSigSize + segwitPushRedeemSize)).GetSerializedSize(); @@ -2341,7 +2341,7 @@ private void Sign(TransactionSigningContext ctx, ICoin coin, IndexedTxIn txIn) ScriptCoin? scriptCoin = coin as ScriptCoin; Script? signatures = null; - if (coin.GetHashVersion() == HashVersion.Witness) + if (!coin.IsMalleable) { signatures = txIn.WitScript; if (scriptCoin != null) @@ -2362,7 +2362,7 @@ private void Sign(TransactionSigningContext ctx, ICoin coin, IndexedTxIn txIn) signatures = CombineScriptSigs(coin, scriptSig, signatures); - if (coin.GetHashVersion() == HashVersion.Witness) + if (!coin.IsMalleable) { txIn.WitScript = signatures; if (scriptCoin != null)