Skip to content

Commit

Permalink
Can Script.CombineSignatures
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier committed Nov 20, 2014
1 parent da9a160 commit 5627da6
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 21 deletions.
152 changes: 144 additions & 8 deletions NBitcoin.Tests/script_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,15 @@ public void script_invalid()

private void AssertVerifyScript(Script scriptSig, Script scriptPubKey, ScriptVerify flags, int testIndex, string comment, bool expected)
{
var creditingTransaction = new Transaction();
creditingTransaction.AddInput(new TxIn()
{
ScriptSig = new Script(OpcodeType.OP_0, OpcodeType.OP_0)
});
creditingTransaction.AddOutput(Money.Zero, scriptPubKey);
var creditingTransaction = CreateCreditingTransaction(scriptPubKey);
var spendingTransaction = CreateSpendingTransaction(scriptSig, creditingTransaction);

var actual = Script.VerifyScript(scriptSig, scriptPubKey, spendingTransaction, 0, flags, SigHash.Undefined);
Assert.True(expected == actual, "Test : " + testIndex + " " + comment);
}

private static Transaction CreateSpendingTransaction(Script scriptSig, Transaction creditingTransaction)
{
var spendingTransaction = new Transaction();
spendingTransaction.AddInput(new TxIn(new OutPoint(creditingTransaction, 0))
{
Expand All @@ -276,9 +278,18 @@ private void AssertVerifyScript(Script scriptSig, Script scriptPubKey, ScriptVer
ScriptPubKey = new Script(),
Value = Money.Zero
});
return spendingTransaction;
}

var actual = Script.VerifyScript(scriptSig, scriptPubKey, spendingTransaction, 0, flags, SigHash.Undefined);
Assert.True(expected == actual, "Test : " + testIndex + " " + comment);
private static Transaction CreateCreditingTransaction(Script scriptPubKey)
{
var creditingTransaction = new Transaction();
creditingTransaction.AddInput(new TxIn()
{
ScriptSig = new Script(OpcodeType.OP_0, OpcodeType.OP_0)
});
creditingTransaction.AddOutput(Money.Zero, scriptPubKey);
return creditingTransaction;
}

private ScriptVerify ParseFlag(string flag)
Expand Down Expand Up @@ -476,6 +487,131 @@ public void script_CHECKMULTISIG23()
Assert.True(!Script.VerifyScript(badsig6, scriptPubKey23, txTo23, 0, flags, 0));
}


[Fact]
[Trait("Core", "Core")]
public void script_combineSigs()
{
Key[] keys = new[] { new Key(), new Key(), new Key() };
var txFrom = CreateCreditingTransaction(keys[0].PubKey.ID.CreateScriptPubKey());
var txTo = CreateSpendingTransaction(new Script(), txFrom);

Script scriptPubKey = txFrom.Outputs[0].ScriptPubKey;
Script scriptSig = txTo.Inputs[0].ScriptSig;

Script empty = new Script();
Script combined = Script.CombineSignatures(scriptPubKey, txTo, 0, empty, empty);
Assert.True(combined.ToRawScript().Length == 0);

// Single signature case:
SignSignature(keys, txFrom, txTo, 0); // changes scriptSig
scriptSig = txTo.Inputs[0].ScriptSig;
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty);
Assert.True(combined == scriptSig);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig);
Assert.True(combined == scriptSig);
Script scriptSigCopy = scriptSig.Clone();
// Signing again will give a different, valid signature:
SignSignature(keys, txFrom, txTo, 0);
scriptSig = txTo.Inputs[0].ScriptSig;

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig);
Assert.True(combined == scriptSigCopy || combined == scriptSig);


// P2SH, single-signature case:
Script pkSingle = PayToPubkeyTemplate.Instance.GenerateScriptPubKey(keys[0].PubKey);
scriptPubKey = pkSingle.ID.CreateScriptPubKey();
txFrom.Outputs[0].ScriptPubKey = scriptPubKey;
txTo.Inputs[0].PrevOut = new OutPoint(txFrom, 0);

SignSignature(keys, txFrom, txTo, 0, pkSingle);
scriptSig = txTo.Inputs[0].ScriptSig;

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty);
Assert.True(combined == scriptSig);

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig);
scriptSig = txTo.Inputs[0].ScriptSig;
Assert.True(combined == scriptSig);
scriptSigCopy = scriptSig.Clone();

SignSignature(keys, txFrom, txTo, 0);
scriptSig = txTo.Inputs[0].ScriptSig;

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig);
Assert.True(combined == scriptSigCopy || combined == scriptSig);
// dummy scriptSigCopy with placeholder, should always choose non-placeholder:
scriptSigCopy = new Script(OpcodeType.OP_0, Op.GetPushOp(pkSingle.ToRawScript()));
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig);
Assert.True(combined == scriptSig);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSig, scriptSigCopy);
Assert.True(combined == scriptSig);

// Hardest case: Multisig 2-of-3
scriptPubKey = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, keys.Select(k => k.PubKey).ToArray());
txFrom.Outputs[0].ScriptPubKey = scriptPubKey;
txTo.Inputs[0].PrevOut = new OutPoint(txFrom, 0);

SignSignature(keys, txFrom, txTo, 0);
scriptSig = txTo.Inputs[0].ScriptSig;

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty);
Assert.True(combined == scriptSig);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig);
Assert.True(combined == scriptSig);

// A couple of partially-signed versions:
uint256 hash1 = scriptPubKey.SignatureHash(txTo, 0, SigHash.All);
var sig1 = new TransactionSignature(keys[0].Sign(hash1), SigHash.All);

uint256 hash2 = scriptPubKey.SignatureHash(txTo, 0, SigHash.None);
var sig2 = new TransactionSignature(keys[1].Sign(hash2), SigHash.None);


uint256 hash3 = scriptPubKey.SignatureHash(txTo, 0, SigHash.Single);
var sig3 = new TransactionSignature(keys[2].Sign(hash3), SigHash.Single);


// Not fussy about order (or even existence) of placeholders or signatures:
Script partial1a = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig1.ToBytes()) + OpcodeType.OP_0;
Script partial1b = new Script() + OpcodeType.OP_0 + OpcodeType.OP_0 + Op.GetPushOp(sig1.ToBytes());
Script partial2a = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig2.ToBytes());
Script partial2b = new Script() + Op.GetPushOp(sig2.ToBytes()) + OpcodeType.OP_0;
Script partial3a = new Script() + Op.GetPushOp(sig3.ToBytes());
Script partial3b = new Script() + OpcodeType.OP_0 + OpcodeType.OP_0 + Op.GetPushOp(sig3.ToBytes());
Script partial3c = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig3.ToBytes()) + OpcodeType.OP_0;
Script complete12 = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig1.ToBytes()) + Op.GetPushOp(sig2.ToBytes());
Script complete13 = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig1.ToBytes()) + Op.GetPushOp(sig3.ToBytes());
Script complete23 = new Script() + OpcodeType.OP_0 + Op.GetPushOp(sig2.ToBytes()) + Op.GetPushOp(sig3.ToBytes());

combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial1b);
Assert.True(combined == partial1a);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial2a);
Assert.True(combined == complete12);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial1a);
Assert.True(combined == complete12);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial1b, partial2b);
Assert.True(combined == complete12);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial1b);
Assert.True(combined == complete13);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial3a);
Assert.True(combined == complete23);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial2b);
Assert.True(combined == complete23);
combined = Script.CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial3a);
Assert.True(combined == partial3c);
}

private void SignSignature(Key[] keys, Transaction txFrom, Transaction txTo, int n, params Script[] knownRedeems)
{
new TransactionBuilder()
.AddKeys(keys)
.AddKnownRedeems(knownRedeems)
.AddCoins(txFrom)
.SignTransactionInPlace(txTo);
}

[Fact]
[Trait("Core", "Core")]
public void script_PushData()
Expand Down
142 changes: 142 additions & 0 deletions NBitcoin/Script.cs
Original file line number Diff line number Diff line change
Expand Up @@ -778,5 +778,147 @@ public Script Clone()
{
return new Script(_Script);
}

public static Script CombineSignatures(Script scriptPubKey, Transaction transaction, int n, Script scriptSig1, Script scriptSig2)
{
ScriptEvaluationContext context = new ScriptEvaluationContext();
context.ScriptVerify = ScriptVerify.StrictEnc;
context.EvalScript(scriptSig1, transaction, n);

var stack1 = context.Stack.Reverse().ToArray();
context = new ScriptEvaluationContext();
context.ScriptVerify = ScriptVerify.StrictEnc;
context.EvalScript(scriptSig2, transaction, n);

var stack2 = context.Stack.Reverse().ToArray();

return CombineSignatures(scriptPubKey, transaction, n, stack1, stack2);



}

private static Script CombineSignatures(Script scriptPubKey, Transaction transaction, int n, byte[][] sigs1, byte[][] sigs2)
{
var template = StandardScripts.GetTemplateFromScriptPubKey(scriptPubKey);
if(template == null || template is TxNullDataTemplate)
return PushAll(Max(sigs1, sigs2));

if(template is PayToPubkeyTemplate || template is PayToPubkeyHashTemplate)
if(sigs1.Length == 0 || sigs1[0].Length == 0)
return PushAll(sigs2);
else
return PushAll(sigs1);

if(template is PayToScriptHashTemplate)
{
if(sigs1.Length == 0 || sigs1[0].Length == 0)
return PushAll(sigs2);
else if(sigs2.Length == 0 || sigs2[0].Length == 0)
return PushAll(sigs1);
else
{
var redeemBytes = sigs1[sigs1.Length - 1];
var redeem = new Script(redeemBytes);
sigs1 = sigs1.Take(sigs1.Length - 1).ToArray();
sigs2 = sigs2.Take(sigs1.Length - 1).ToArray();
Script result = CombineSignatures(scriptPubKey, transaction, n, sigs1, sigs2);
result += Op.GetPushOp(redeemBytes);
return result;
}
}

if(template is PayToMultiSigTemplate)
{
return CombineMultisig(scriptPubKey, transaction, n, sigs1, sigs2);
}

throw new NotSupportedException("An impossible thing happen !");
}

private static Script CombineMultisig(Script scriptPubKey, Transaction transaction, int n, byte[][] sigs1, byte[][] sigs2)
{
// Combine all the signatures we've got:
List<TransactionSignature> allsigs = new List<TransactionSignature>();
foreach(var v in sigs1)
{
try
{
allsigs.Add(new TransactionSignature(v));
}
catch(FormatException)
{
}
}


foreach(var v in sigs2)
{
try
{
allsigs.Add(new TransactionSignature(v));
}
catch(FormatException)
{
}
}

var multiSigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(scriptPubKey);
if(multiSigParams == null)
throw new InvalidOperationException("The scriptPubKey is not a valid multi sig");

Dictionary<PubKey, TransactionSignature> sigs = new Dictionary<PubKey, TransactionSignature>();

foreach(var sig in allsigs)
{
foreach(var pubkey in multiSigParams.PubKeys)
{
if(sigs.ContainsKey(pubkey))
continue; // Already got a sig for this pubkey

ScriptEvaluationContext eval = new ScriptEvaluationContext();
if(eval.CheckSig(sig.ToBytes(), pubkey.ToBytes(), scriptPubKey, transaction, n))
{
sigs.AddOrReplace(pubkey, sig);
}
}
}


// Now build a merged CScript:
int nSigsHave = 0;
Script result = new Script(OpcodeType.OP_0); // pop-one-too-many workaround
foreach(var pubkey in multiSigParams.PubKeys)
{
if(sigs.ContainsKey(pubkey))
{
result += Op.GetPushOp(sigs[pubkey].ToBytes());
nSigsHave++;
}
if(nSigsHave >= multiSigParams.SignatureCount)
break;
}

// Fill any missing with OP_0:
for(int i = nSigsHave ; i < multiSigParams.SignatureCount ; i++)
result += OpcodeType.OP_0;

return result;
}

private static Script PushAll(byte[][] stack)
{
Script s = new Script();
foreach(var push in stack)
{
s += Op.GetPushOp(push);
}
return s;
}

private static byte[][] Max(byte[][] scriptSig1, byte[][] scriptSig2)
{
return scriptSig1.Length >= scriptSig2.Length ? scriptSig1 : scriptSig2;
}
}
}
8 changes: 1 addition & 7 deletions NBitcoin/ScriptEvaluationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,7 @@ static T top<T>(Stack<T> stack, int i)
//stacktop(i) (altstack.at(altstack.size()+(i)))
}

private bool CheckSig(byte[] vchSig, byte[] vchPubKey, Script scriptCode, Transaction txTo, int nIn)
public bool CheckSig(byte[] vchSig, byte[] vchPubKey, Script scriptCode, Transaction txTo, int nIn)
{
//static CSignatureCache signatureCache;
if(!PubKey.IsValidSize(vchPubKey.Length))
Expand All @@ -1398,9 +1398,6 @@ private bool CheckSig(byte[] vchSig, byte[] vchPubKey, Script scriptCode, Transa

uint256 sighash = scriptCode.SignatureHash(txTo, nIn, scriptSig.SigHash);

//if (signatureCache.Get(sighash, vchSig, pubkey))
// return true;

if(!pubkey.Verify(sighash, scriptSig.Signature))
{
if((ScriptVerify & ScriptVerify.StrictEnc) != 0)
Expand All @@ -1421,9 +1418,6 @@ private bool CheckSig(byte[] vchSig, byte[] vchPubKey, Script scriptCode, Transa
}
}

//if (!(flags & SCRIPT_VERIFY_NOCACHE))
// signatureCache.Set(sighash, vchSig, pubkey);

return true;
}

Expand Down

0 comments on commit 5627da6

Please sign in to comment.