Skip to content

Commit

Permalink
Internal refactoring TransactionBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier committed Nov 2, 2014
1 parent 0b0fee1 commit c02ce50
Showing 1 changed file with 150 additions and 126 deletions.
276 changes: 150 additions & 126 deletions NBitcoin/TransactionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Builder = System.Func<NBitcoin.TransactionBuildingContext, NBitcoin.Money>;
using Builder = System.Func<NBitcoin.TransactionBuilder.TransactionBuildingContext, NBitcoin.Money>;

namespace NBitcoin
{
Expand Down Expand Up @@ -147,102 +147,132 @@ public NotEnoughFundsException(string message, Exception inner)
}
}

class TransactionBuildingContext

public class TransactionBuilder
{
public TransactionBuildingContext(TransactionBuilder builder)
{
Builder = builder;
Transaction = new Transaction();
ChangeAmount = Money.Zero;
AdditionalFees = Money.Zero;
}
public TransactionBuilder.BuilderGroup Group
{
get;
set;
}
public TransactionBuilder Builder
internal class TransactionSigningContext
{
get;
set;
public TransactionSigningContext(TransactionBuilder builder, Transaction transaction)
{
Builder = builder;
Transaction = transaction;
}

public Transaction Transaction
{
get;
set;
}
public TransactionBuilder Builder
{
get;
set;
}

private readonly List<Key> _AdditionalKeys = new List<Key>();
public List<Key> AdditionalKeys
{
get
{
return _AdditionalKeys;
}
}
}
public Transaction Transaction
internal class TransactionBuildingContext
{
get;
set;
}
public TransactionBuildingContext(TransactionBuilder builder)
{
Builder = builder;
Transaction = new Transaction();
ChangeAmount = Money.Zero;
AdditionalFees = Money.Zero;
}
public TransactionBuilder.BuilderGroup Group
{
get;
set;
}
public TransactionBuilder Builder
{
get;
set;
}
public Transaction Transaction
{
get;
set;
}

public Money AdditionalFees
{
get;
set;
}
public Money AdditionalFees
{
get;
set;
}

ColorMarker _Marker;
ColorMarker _Marker;

public ColorMarker GetColorMarker(bool issuance)
{
if(_Marker == null)
_Marker = new ColorMarker();
if(!issuance)
EnsureMarkerInserted();
return _Marker;
}
public ColorMarker GetColorMarker(bool issuance)
{
if(_Marker == null)
_Marker = new ColorMarker();
if(!issuance)
EnsureMarkerInserted();
return _Marker;
}

private TxOut EnsureMarkerInserted()
{
var txout = Transaction.Outputs.FirstOrDefault(o => Script.IsNullOrEmpty(o.ScriptPubKey));
if(txout == null)
private TxOut EnsureMarkerInserted()
{
txout = Transaction.AddOutput(new TxOut());
txout.Value = Money.Zero;
var txout = Transaction.Outputs.FirstOrDefault(o => Script.IsNullOrEmpty(o.ScriptPubKey));
if(txout == null)
{
txout = Transaction.AddOutput(new TxOut());
txout.Value = Money.Zero;
}
return txout;
}
return txout;
}

public void Finish()
{
if(_Marker != null)
public void Finish()
{
var txout = EnsureMarkerInserted();
txout.ScriptPubKey = _Marker.GetScript();
if(_Marker != null)
{
var txout = EnsureMarkerInserted();
txout.ScriptPubKey = _Marker.GetScript();
}
}
}

public IssuanceCoin IssuanceCoin
{
get;
set;
}
public IssuanceCoin IssuanceCoin
{
get;
set;
}

public Money ChangeAmount
{
get;
set;
}
public Money ChangeAmount
{
get;
set;
}

public TransactionBuildingContext CreateMemento()
{
var memento = new TransactionBuildingContext(Builder);
memento.RestoreMemento(this);
return memento;
}
public TransactionBuildingContext CreateMemento()
{
var memento = new TransactionBuildingContext(Builder);
memento.RestoreMemento(this);
return memento;
}

public void RestoreMemento(TransactionBuildingContext memento)
{
_Marker = memento._Marker == null ? null : new ColorMarker(memento._Marker.GetScript());
Transaction = memento.Transaction.Clone();
AdditionalFees = memento.AdditionalFees;
}
public void RestoreMemento(TransactionBuildingContext memento)
{
_Marker = memento._Marker == null ? null : new ColorMarker(memento._Marker.GetScript());
Transaction = memento.Transaction.Clone();
AdditionalFees = memento.AdditionalFees;
}

public bool NonFinalSequenceSet
{
get;
set;
public bool NonFinalSequenceSet
{
get;
set;
}
}
}
public class TransactionBuilder
{

internal class BuilderGroup
{
TransactionBuilder _Parent;
Expand Down Expand Up @@ -620,13 +650,14 @@ public Transaction SignTransaction(Transaction transaction)
}
public void SignTransactionInPlace(Transaction transaction)
{
TransactionSigningContext ctx = new TransactionSigningContext(this, transaction);
for(int i = 0 ; i < transaction.Inputs.Count ; i++)
{
var txIn = transaction.Inputs[i];
var coin = FindCoin(txIn.PrevOut);
if(coin != null)
{
Sign(transaction, txIn, coin, i);
Sign(ctx, txIn, coin, i);
}
}
}
Expand Down Expand Up @@ -663,78 +694,67 @@ private ICoin FindCoin(OutPoint outPoint)
readonly static PayToPubkeyTemplate payToPubKey = new PayToPubkeyTemplate();
readonly static PayToMultiSigTemplate payToMultiSig = new PayToMultiSigTemplate();

private void Sign(Transaction tx, TxIn input, ICoin coin, int n)
private void Sign(TransactionSigningContext ctx, TxIn input, ICoin coin, int n)
{
if(coin is IColoredCoin)
coin = ((IColoredCoin)coin).Bearer;
List<Key> tempKeys = new List<Key>();

if(coin is StealthCoin)
{
var stealthCoin = (StealthCoin)coin;
var scanKey = FindKey(stealthCoin.Address.ScanPubKey);
var scanKey = FindKey(ctx, stealthCoin.Address.ScanPubKey);
if(scanKey == null)
throw new KeyNotFoundException("Scan key for decrypting StealthCoin not found");
var spendKeys = stealthCoin.Address.SpendPubKeys.Select(p => FindKey(p)).Where(p => p != null).ToArray();
tempKeys.AddRange(stealthCoin.Uncover(spendKeys, scanKey));
_Keys.AddRange(tempKeys);
var spendKeys = stealthCoin.Address.SpendPubKeys.Select(p => FindKey(ctx, p)).Where(p => p != null).ToArray();
ctx.AdditionalKeys.AddRange(stealthCoin.Uncover(spendKeys, scanKey));
}

try
{

if(payToScriptHash.CheckScriptPubKey(coin.ScriptPubKey))
if(payToScriptHash.CheckScriptPubKey(coin.ScriptPubKey))
{
var scriptCoin = coin as IScriptCoin;
if(scriptCoin == null)
{
var scriptCoin = coin as IScriptCoin;
if(scriptCoin == null)
{
var expectedId = payToScriptHash.ExtractScriptPubKeyParameters(coin.ScriptPubKey);
//Try to extract redeem from this transaction
var p2shParams = payToScriptHash.ExtractScriptSigParameters(input.ScriptSig);
if(p2shParams == null || p2shParams.RedeemScript.ID != expectedId)
throw new InvalidOperationException("A coin with a P2SH scriptPubKey was detected, however this coin is not a ScriptCoin, and no information about the redeem script was found in the input");
else
{
scriptCoin = new ScriptCoin(coin.Outpoint, ((Coin)coin).TxOut, p2shParams.RedeemScript);
}
}

var original = input.ScriptSig;
input.ScriptSig = CreateScriptSig(tx, input, coin, n, scriptCoin.Redeem);
if(original != input.ScriptSig)
var expectedId = payToScriptHash.ExtractScriptPubKeyParameters(coin.ScriptPubKey);
//Try to extract redeem from this transaction
var p2shParams = payToScriptHash.ExtractScriptSigParameters(input.ScriptSig);
if(p2shParams == null || p2shParams.RedeemScript.ID != expectedId)
throw new InvalidOperationException("A coin with a P2SH scriptPubKey was detected, however this coin is not a ScriptCoin, and no information about the redeem script was found in the input");
else
{
var ops = input.ScriptSig.ToOps().ToList();
ops.Add(Op.GetPushOp(scriptCoin.Redeem.ToRawScript(true)));
input.ScriptSig = new Script(ops.ToArray());
scriptCoin = new ScriptCoin(coin.Outpoint, ((Coin)coin).TxOut, p2shParams.RedeemScript);
}
}
else

var original = input.ScriptSig;
input.ScriptSig = CreateScriptSig(ctx, input, coin, n, scriptCoin.Redeem);
if(original != input.ScriptSig)
{
input.ScriptSig = CreateScriptSig(tx, input, coin, n, coin.ScriptPubKey);
var ops = input.ScriptSig.ToOps().ToList();
ops.Add(Op.GetPushOp(scriptCoin.Redeem.ToRawScript(true)));
input.ScriptSig = new Script(ops.ToArray());
}
}
finally
else
{
foreach(var tempKey in tempKeys)
{
_Keys.Remove(tempKey);
}
input.ScriptSig = CreateScriptSig(ctx, input, coin, n, coin.ScriptPubKey);
}

}


private Script CreateScriptSig(Transaction tx, TxIn input, ICoin coin, int n, Script scriptPubKey)
private Script CreateScriptSig(TransactionSigningContext ctx, TxIn input, ICoin coin, int n, Script scriptPubKey)
{
var originalScriptSig = input.ScriptSig;
input.ScriptSig = scriptPubKey;

var pubKeyHashParams = payToPubKeyHash.ExtractScriptPubKeyParameters(scriptPubKey);
if(pubKeyHashParams != null)
{
var key = FindKey(pubKeyHashParams);
var key = FindKey(ctx, pubKeyHashParams);
if(key == null)
return originalScriptSig;
var hash = input.ScriptSig.SignatureHash(tx, n, SigHash.All);
var hash = input.ScriptSig.SignatureHash(ctx.Transaction, n, SigHash.All);
var sig = key.Sign(hash);
return payToPubKeyHash.GenerateScriptSig(new TransactionSignature(sig, SigHash.All), key.PubKey);
}
Expand All @@ -757,7 +777,7 @@ private Script CreateScriptSig(Transaction tx, TxIn input, ICoin coin, int n, Sc
var keys =
multiSigParams
.PubKeys
.Select(p => FindKey(p))
.Select(p => FindKey(ctx, p))
.ToArray();

int sigCount = signatures.Where(s => s != TransactionSignature.Empty).Count();
Expand All @@ -772,7 +792,7 @@ private Script CreateScriptSig(Transaction tx, TxIn input, ICoin coin, int n, Sc
}
if(keys[i] != null)
{
var hash = input.ScriptSig.SignatureHash(tx, n, SigHash.All);
var hash = input.ScriptSig.SignatureHash(ctx.Transaction, n, SigHash.All);
var sig = keys[i].Sign(hash);
signatures[i] = new TransactionSignature(sig, SigHash.All);
sigCount++;
Expand All @@ -791,10 +811,10 @@ private Script CreateScriptSig(Transaction tx, TxIn input, ICoin coin, int n, Sc
var pubKeyParams = payToPubKey.ExtractScriptPubKeyParameters(scriptPubKey);
if(pubKeyParams != null)
{
var key = FindKey(pubKeyParams);
var key = FindKey(ctx, pubKeyParams);
if(key == null)
return originalScriptSig;
var hash = input.ScriptSig.SignatureHash(tx, n, SigHash.All);
var hash = input.ScriptSig.SignatureHash(ctx.Transaction, n, SigHash.All);
var sig = key.Sign(hash);
return payToPubKey.GenerateScriptSig(new TransactionSignature(sig, SigHash.All));
}
Expand All @@ -803,14 +823,18 @@ private Script CreateScriptSig(Transaction tx, TxIn input, ICoin coin, int n, Sc
}


private Key FindKey(TxDestination id)
private Key FindKey(TransactionSigningContext ctx, TxDestination id)
{
return _Keys.FirstOrDefault(k => k.PubKey.ID == id);
return _Keys
.Concat(ctx.AdditionalKeys)
.FirstOrDefault(k => k.PubKey.ID == id);
}

private Key FindKey(PubKey pubKeyParams)
private Key FindKey(TransactionSigningContext ctx, PubKey pubKey)
{
return _Keys.FirstOrDefault(k => k.PubKey == pubKeyParams);
return _Keys
.Concat(ctx.AdditionalKeys)
.FirstOrDefault(k => k.PubKey == pubKey);
}

public TransactionBuilder Then()
Expand Down

0 comments on commit c02ce50

Please sign in to comment.