/// <summary> /// Create the signing bag with matching scripts and private keys /// Then sign the inputs. /// </summary> public Transaction Sign(CoinParameters parameters, SignRawTransaction rawTransactions) { var transaction = this.Serializer.FromHex(rawTransactions.RawTransactionHex); var bag = new TransactionSigner.SignerBag { Items = new List <TransactionSigner.RedeemScript>() }; var keys = rawTransactions.PrivateKeys.Select(s => new BitcoinPrivateKey(parameters, s)).ToList(); // create a linked object between the pub key hash and its outpoint var inputs = rawTransactions.Inputs .Select(input => new { Input = input, PubKeyHash = new Script.Script(CryptoUtil.ConvertHex(input.ScriptPubKey)).TryGetPubKeyHash() }) .Where(p => p.PubKeyHash.IsNotNull()).ToList(); // compare private keys with redeem script pub key hash and add to the bag foreach (var key in keys) { // there should be at least one redeem script per private key var inserts = inputs.Where(f => f.PubKeyHash.SequenceEqual(key.Hash160)).ToList(); Thrower.If(inserts.None()).Throw <TransactionException>("Private key had no matching redeem script '{0}'".StringFormat(key.PublicKey.ToAddress(parameters).ToString())); inserts.ForEach(insert => bag.Add(insert.Input.TransactionId, insert.Input.Output, insert.Input.ScriptPubKey, key)); } this.Signer.SignInputs(transaction, bag); return(transaction); }
/// <summary> /// The resolve. /// </summary> public static bool PopulateCoinParameters(CoinParameters param) { // places to look for the network parameters // https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/chainparams.cpp // https://github.com/CrypticApplications/MTR-Update/blob/master/src/base58.h // example MTR => private key prefix is [128 + CBitcoinAddress::PUBKEY_ADDRESS] List <int> vals; if (Params.TryGetValue(param.CoinTag, out vals)) { param.PublicKeyAddressVersion = vals.ElementAt(0); param.ScriptAddressVersion = vals.ElementAt(1); param.ExtendedKeyIndex = vals.ElementAt(2); param.PrivateKeyVersion = vals.ElementAt(3); Thrower.If(param.PrivateKeyVersion == -1).Throw <AddressException>(); Thrower.If(param.ExtendedKeyIndex == -1).Throw <AddressException>(); Thrower.If(param.PublicKeyAddressVersion == -1).Throw <AddressException>(); return(true); } return(false); }
public void SignInputs(Transaction transaction, SignerBag bag) { foreach (var transactionInput in transaction.Inputs) { var redeem = bag.Find(transactionInput.Outpoint); Thrower.If(redeem.IsNull()).Throw <TransactionException>("redeem script was not found"); var redeemScript = new Script.Script(CryptoUtil.ConvertHex(redeem.ScriptPubKeyHex)); var signature = this.CalculateSignature(transaction, transactionInput.Outpoint, redeem.PrivateKey.Key, redeemScript, Transaction.SigHash.All); var signedScript = ScriptBuilder.CreateInputScript(signature, redeem.PrivateKey.Key); transactionInput.ScriptBytes = signedScript.GetProgram(); } }
/// <summary> /// Create a bitcoin address from an imported string. /// </summary> public static Address Create(CoinParameters coinParameters, string address) { var decoded = Base58Encoding.DecodeWithCheckSum(address); var version = decoded.First(); var hash = decoded.Skip(1).ToArray(); Thrower.If(hash.Length != Length).Throw <AddressException>("Invalid length? expected={0} found={1}", Length, hash.Length); Thrower.If( version != coinParameters.PublicKeyAddressVersion && version != coinParameters.ScriptAddressVersion) .Throw <AddressException>("Mismatched version number, trying to cross networks? expected={0},{1} found={2}", coinParameters.PublicKeyAddressVersion, coinParameters.ScriptAddressVersion, version); return(new Address { Hash160 = hash, CoinParameters = coinParameters, AddressVersion = version }); }
/// <summary> /// Returns the public key in this script. If a script contains two constants and nothing else, it is assumed to /// be a scriptSig (input) for a pay-to-address output and the second constant is returned (the first is the /// signature). If a script contains a constant and an ScriptOpCodes.OP_CHECKSIG opcode, the constant is returned as it is /// assumed to be a direct pay-to-key scriptPubKey (output) and the first constant is the public key. /// </summary> public byte[] GetPubKey() { Thrower.If(this.Chunks.Count() != 2).Throw <ScriptException>("Script not of right size, expecting 2 but got " + this.Chunks.Count()); ScriptChunk firstChunk = this.Chunks.ElementAt(0); ScriptChunk secondChunk = this.Chunks.ElementAt(1); if (firstChunk.Data != null && firstChunk.Data.Length > 2 && secondChunk.Data != null && secondChunk.Data.Length > 2) { // If we have two large constants assume the input to a pay-to-address output. return(secondChunk.Data); } if (secondChunk.EqualsOpCode(ScriptOpCodes.OP_CHECKSIG) && firstChunk.Data != null && firstChunk.Data.Length > 2) { // A large constant followed by an ScriptOpCodes.OP_CHECKSIG is the key. return(firstChunk.Data); } throw new ScriptException("Script did not match expected form: " + this); }
public TransactionSignature CalculateSignature(Transaction transaction, TransactionOutPoint outPoint, EcKey key, Script.Script redeemScript, Transaction.SigHash hashType) { // at the moment only signing all the outputs is supported Thrower.If(hashType != Transaction.SigHash.All).Throw <TransactionException>("Only SigHash type 'All' supported"); //// clone the transaction and clear all the inputs //// only the inputs for the equivalent output needs to be present for signing var signTx = transaction.Clone(); signTx.Inputs.ForEach(input => input.ScriptBytes = Enumerable.Empty <byte>().ToArray()); // set the redeem script and clear it of 'OP_CODESEPARATOR' var connectedScript = redeemScript.GetProgram(); var redeemConnectedScript = Script.Script.RemoveAllInstancesOfOp(connectedScript, ScriptOpCodes.OP_CODESEPARATOR); signTx.FindInput(outPoint).ScriptBytes = redeemConnectedScript; // serialize then hash the transaction to HEX and sign it. var trxHex = this.Serializer.ToHex(signTx, hashType); var hash = CryptoUtil.Sha256HashTwice(CryptoUtil.ConvertHex(trxHex)); return(new TransactionSignature(key.Sign(hash), hashType)); }
public ExtendedKeyPathBip44 AddChange(uint index) { Thrower.If(this.Items.Count() > 3).Throw <AddressException>("Change was already added"); this.AddChild(index); return(this); }