/// <summary> /// /// </summary> /// <param name="seed"></param> /// <param name="security"></param> /// <param name="depth"></param> /// <param name="minWeightMagnitude"></param> /// <param name="transfers"></param> /// <param name="inputs"></param> /// <param name="remainderAddress"></param> /// <param name="validateInputs"></param> /// <param name="validateInputAddresses"></param> /// <param name="tips"></param> /// <returns></returns> public SendTransferResponse SendTransfer( string seed, int security, int depth, int minWeightMagnitude, Transfer[] transfers, Input[] inputs, string remainderAddress, bool validateInputs, bool validateInputAddresses, Transaction[] tips) { Stopwatch stopwatch = Stopwatch.StartNew(); var trytes = PrepareTransfers(seed, security, transfers, remainderAddress, inputs, tips, validateInputs); if (validateInputAddresses) { ValidateTransfersAddresses(seed, security, trytes); } string reference = tips != null && tips.Length > 0 ? tips[0].CurlHash() : null; var transactions = SendTrytes(trytes.ToArray(), depth, minWeightMagnitude, reference); var successful = new bool[transactions.Length]; for (var i = 0; i < transactions.Length; i++) { var response = IotaClient.FindTransactionsByBundles(transactions[i].Bundle); successful[i] = response.Hashes.Count != 0; } stopwatch.Stop(); return(new SendTransferResponse(transactions, successful, stopwatch.ElapsedMilliseconds)); }
private GetBalancesAndFormatResponse GetBalanceAndFormat( string[] addresses, string[] tips, long threshold, int start, int security, Stopwatch stopwatch = null) { if (stopwatch == null) { stopwatch = Stopwatch.StartNew(); } if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } var getBalancesResponse = IotaClient.GetBalances(100, addresses.ToList(), tips.ToList()); var balances = getBalancesResponse.Balances; // If threshold defined, keep track of whether reached or not // else set default to true var inputList = new List <Input>(); long totalBalance = 0; var thresholdReached = threshold == 0; for (var i = 0; i < addresses.Length; i++) { if (balances[i] > 0) { inputList.Add(new Input { Address = addresses[i], Balance = balances[i], KeyIndex = start + i, Security = security }); totalBalance += balances[i]; if (totalBalance >= threshold) { thresholdReached = true; break; } } } stopwatch.Stop(); if (thresholdReached) { return(new GetBalancesAndFormatResponse(inputList, totalBalance, stopwatch.ElapsedMilliseconds)); } throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); }
private ReplayBundleResponse ReplayBundle(Bundle bundle, int depth, int minWeightMagnitude, string reference, Stopwatch stopWatch) { List <string> bundleTrytes = new List <string>(); foreach (var trx in bundle.Transactions) { bundleTrytes.Add(trx.ToTrytes()); } bundleTrytes.Reverse(); var trxs = SendTrytes(bundleTrytes.ToArray(), depth, minWeightMagnitude, reference); bool[] successful = new bool[trxs.Length]; for (int i = 0; i < trxs.Length; i++) { var response = IotaClient.FindTransactionsByBundles(trxs[i].Bundle); successful[i] = response.Hashes.Count != 0; } stopWatch.Stop(); return(new ReplayBundleResponse(new Bundle(trxs.ToList(), trxs.Length), successful, stopWatch.ElapsedMilliseconds)); }
private void ValidateTransfersAddresses(string seed, int security, List <string> trytes) { HashSet <string> addresses = new HashSet <string>(); List <Transaction> inputTransactions = new List <Transaction>(); List <string> inputAddresses = new List <string>(); foreach (var trx in trytes) { addresses.Add(new Transaction(trx).Address); inputTransactions.Add(new Transaction(trx)); } var hashes = IotaClient.FindTransactionsByAddresses(addresses.ToArray()).Hashes; List <Transaction> transactions = FindTransactionObjectsByHashes(hashes.ToArray()); // Get addresses until first unspent var addressRequest = new AddressRequest(seed, security) { Amount = 0, Checksum = true }; var gna = GenerateNewAddresses(addressRequest); // Get inputs for this seed, until we fund an unused address var gbr = GetInputs(seed, security, 0, 0, 0); foreach (var input in gbr.Inputs) { inputAddresses.Add(input.Address); } //check if send to input foreach (var trx in inputTransactions) { if (trx.Value > 0 && inputAddresses.Contains(trx.Address)) { throw new ArgumentException("Send to inputs!"); } } foreach (var trx in transactions) { //check if destination address is already in use if (trx.Value < 0 && !inputAddresses.Contains(trx.Address)) { throw new ArgumentException("Sending to a used address."); } //check if key reuse if (trx.Value < 0 && gna.Addresses.Contains(trx.Address)) { throw new ArgumentException("Private key reuse detect!"); } } }
private Bundle TraverseBundle(string trunkTx, string bundleHash, Bundle bundle) { var gtr = IotaClient.GetTrytes(trunkTx); if (gtr != null) { if (gtr.Trytes.Count == 0) { throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); } Transaction trx = new Transaction(gtr.Trytes[0]); if (trx.Bundle == null) { throw new ArgumentException(Constants.INVALID_TRYTES_INPUT_ERROR); } // If first transaction to search is not a tail, return error if (bundleHash == null && trx.CurrentIndex != 0) { throw new ArgumentException(Constants.INVALID_TAIL_HASH_INPUT_ERROR); } // If no bundle hash, define it if (bundleHash == null) { bundleHash = trx.Bundle; } // If different bundle hash, return with bundle if (!bundleHash.Equals(trx.Bundle, StringComparison.Ordinal)) { bundle.Length = bundle.Transactions.Count; return(bundle); } // If only one bundle element, return if (trx.LastIndex == 0 && trx.CurrentIndex == 0) { return(new Bundle(new List <Transaction> { trx }, 1)); } // Define new trunkTransaction for search trunkTx = trx.TrunkTransaction; // Add transaction object to bundle bundle.Transactions.Add(trx); // Continue traversing with new trunkTx return(TraverseBundle(trunkTx, bundleHash, bundle)); } throw new ArgumentException(Constants.GET_TRYTES_RESPONSE_ERROR); }
/// <summary> /// /// </summary> /// <param name="bundles"></param> /// <returns></returns> public List <Transaction> FindTransactionObjectsByBundle(params string[] bundles) { var ftr = IotaClient.FindTransactionsByBundles(bundles); if (ftr?.Hashes == null) { return(new List <Transaction>()); } // get the transaction objects of the transactions return(FindTransactionObjectsByHashes(ftr.Hashes.ToArray())); }
/// <summary> /// /// </summary> /// <param name="addresses"></param> /// <returns></returns> public List <Transaction> FindTransactionObjectsByAddresses(string[] addresses) { var ftr = IotaClient.FindTransactionsByAddresses(addresses); if (ftr?.Hashes == null) { return(new List <Transaction>()); } // get the transaction objects of the transactions return(FindTransactionObjectsByHashes(ftr.Hashes.ToArray())); }
private bool IsAddressSpent(string newAddress, bool checksum) { string address = checksum ? newAddress : newAddress.AddChecksum(); var response = IotaClient.FindTransactionsByAddresses(address); if (response.Hashes.Count == 0) { var spentFromResponse = IotaClient.WereAddressesSpentFrom(address); return(spentFromResponse.States[0]); } return(true); }
/// <summary> /// /// </summary> /// <param name="hashes"></param> /// <returns></returns> public List <Transaction> FindTransactionObjectsByHashes(string[] hashes) { if (!InputValidator.IsArrayOfHashes(hashes)) { throw new IllegalStateException("Not an Array of Hashes: " + hashes); } var trytesResponse = IotaClient.GetTrytes(hashes); var trxs = new List <Transaction>(); foreach (var tryte in trytesResponse.Trytes) { trxs.Add(new Transaction(tryte)); } return(trxs); }
/// <summary> /// /// </summary> /// <param name="trytes"></param> /// <param name="depth"></param> /// <param name="minWeightMagnitude"></param> /// <param name="reference"></param> /// <returns></returns> public Transaction[] SendTrytes(string[] trytes, int depth, int minWeightMagnitude, string reference) { var transactionsToApproveResponse = IotaClient.GetTransactionsToApprove(depth, reference); // attach to tangle - do pow AttachToTangleResponse attachToTangleResponse; if (LocalPoW == null) { attachToTangleResponse = IotaClient.AttachToTangle(transactionsToApproveResponse.TrunkTransaction, transactionsToApproveResponse.BranchTransaction, minWeightMagnitude, trytes); } else { attachToTangleResponse = LocalPoW.AttachToTangle(transactionsToApproveResponse.TrunkTransaction, transactionsToApproveResponse.BranchTransaction, minWeightMagnitude, trytes); } try { BroadcastAndStore(attachToTangleResponse.Trytes.ToArray()); } catch (System.Exception) { return(Array.Empty <Transaction>()); } var trx = new List <Transaction>(); foreach (var tx in attachToTangleResponse.Trytes) { trx.Add(new Transaction(tx)); } return(trx.ToArray()); }
/// <summary> /// /// </summary> /// <param name="seed"></param> /// <param name="security"></param> /// <param name="transfers"></param> /// <param name="remainder"></param> /// <param name="inputs"></param> /// <param name="tips"></param> /// <param name="validateInputs"></param> /// <returns></returns> public List <string> PrepareTransfers( string seed, int security, Transfer[] transfers, string remainder, Input[] inputs, Transaction[] tips, bool validateInputs) { // validate seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } if (remainder != null && !InputValidator.IsAddress(remainder)) { throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); } // Input validation of transfers object if (!InputValidator.IsValidTransfersCollection(transfers.ToList())) { throw new ArgumentException(Constants.INVALID_TRANSFERS_INPUT_ERROR); } if (inputs != null && !InputValidator.IsValidInputsCollection(inputs)) { throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); } // Create a new bundle var bundle = new Bundle(); var signatureFragments = new List <string>(); long totalValue = 0; var tag = ""; // // Iterate over all transfers, get totalValue // and prepare the signatureFragments, message and tag // foreach (var transfer in transfers) { // remove the checksum of the address if provided transfer.Address = transfer.Address.RemoveChecksum(); var signatureMessageLength = 1; // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) if (transfer.Message.Length > Constants.MESSAGE_LENGTH) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor((double)transfer.Message.Length / Constants.MESSAGE_LENGTH); var msgCopy = transfer.Message; // While there is still a message, copy it while (!string.IsNullOrEmpty(msgCopy)) { var fragment = msgCopy.Substring(0, 2187 > msgCopy.Length ? msgCopy.Length : 2187); msgCopy = msgCopy.Substring(fragment.Length, msgCopy.Length - fragment.Length); // Pad remainder of fragment if (fragment.Length < 2187) { fragment = fragment.PadRight(2187, '9'); } signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes var fragment = transfer.Message.PadRight(Constants.MESSAGE_LENGTH, '9'); signatureFragments.Add(fragment); } // get current timestamp in seconds var timestamp = (long)Math.Floor((double)TimeStamp.Now() / 1000); // If no tag defined, get 27 tryte tag. tag = string.IsNullOrEmpty(transfer.Tag) ? "999999999999999999999999999" : transfer.Tag; // Pad for required 27 tryte length if (tag.Length < Constants.TAG_LENGTH) { tag = tag.PadRight(Constants.TAG_LENGTH, '9'); } // Add first entries to the bundle // Slice the address in case the user provided a checksummed one bundle.AddEntry(signatureMessageLength, transfer.Address, transfer.Value, tag, timestamp); // Sum up total value totalValue += transfer.Value; } // Get inputs if we are sending tokens if (totalValue != 0) { // validate seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } // Case 1: user provided inputs // Validate the inputs by calling getBalances if (inputs != null && inputs.Length > 0) { if (!validateInputs) { return(AddRemainder(seed, security, inputs.ToList(), bundle, tag, totalValue, remainder, signatureFragments)); } // Get list if addresses of the provided inputs var inputAddresses = new List <string>(); foreach (var input in inputs) { inputAddresses.Add(input.Address); } List <string> tipHashes = null; if (tips != null) { tipHashes = new List <string>(); foreach (var tx in tips) { tipHashes.Add(tx.CurlHash()); } } var balances = IotaClient.GetBalances(100, inputAddresses, tipHashes); var confirmedInputs = new List <Input>(); long totalBalance = 0; for (var i = 0; i < balances.Balances.Count; i++) { var thisBalance = balances.Balances[i]; totalBalance += thisBalance; // If input has balance, add it to confirmedInputs if (thisBalance > 0) { var inputEl = inputs[i]; inputEl.Balance = thisBalance; confirmedInputs.Add(inputEl); // if we've already reached the intended input value, break out of loop if (totalBalance >= totalValue) { Log.Info("Total balance already reached "); break; } } } // Return not enough balance error if (totalValue > totalBalance) { throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } return(AddRemainder(seed, security, confirmedInputs, bundle, tag, totalValue, remainder, signatureFragments)); } // Case 2: Get inputs deterministically // // If no inputs provided, derive the addresses from the seed and // confirm that the inputs exceed the threshold var inputList = GetInputs(seed, security, 0, 0, (int)totalValue).Inputs; return(AddRemainder(seed, security, inputList, bundle, tag, totalValue, remainder, signatureFragments)); } // If no input required, don't sign and simply finalize the bundle bundle.FinalizeBundle(SpongeFactory.Create(SpongeFactory.Mode.KERL)); bundle.AddTrytes(signatureFragments); var bundleTrytes = new List <string>(); bundle.Transactions.ForEach(tx => bundleTrytes.Add(tx.ToTrytes())); bundleTrytes.Reverse(); return(bundleTrytes); }
/// <summary> /// /// </summary> /// <param name="securitySum"></param> /// <param name="inputAddress"></param> /// <param name="remainderAddress"></param> /// <param name="transfers"></param> /// <param name="tips"></param> /// <param name="testMode"></param> /// <returns></returns> public List <Transaction> InitiateTransfer( int securitySum, string inputAddress, string remainderAddress, List <Transfer> transfers, List <Transaction> tips, bool testMode) { // validate input address if (!InputValidator.IsAddress(inputAddress)) { throw new ArgumentException("Invalid addresses provided."); } // validate remainder address if (remainderAddress != null && !InputValidator.IsAddress(remainderAddress)) { throw new ArgumentException("Invalid addresses provided."); } // Input validation of transfers object if (!InputValidator.IsValidTransfersCollection(transfers)) { throw new ArgumentException("Invalid transfers provided."); } // Create a new bundle Bundle bundle = new Bundle(); long totalValue = 0; List <String> signatureFragments = new List <string>(); String tag = ""; // // Iterate over all transfers, get totalValue // and prepare the signatureFragments, message and tag foreach (Transfer transfer in transfers) { // remove the checksum of the address if provided if (transfer.Address.IsValidChecksum()) { transfer.Address = transfer.Address.RemoveChecksum(); } int signatureMessageLength = 1; // If message longer than 2187 trytes, increase signatureMessageLength (add next transaction) if (transfer.Message.Length > Constants.MESSAGE_LENGTH) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor((double)transfer.Message.Length / Constants.MESSAGE_LENGTH); String msgCopy = transfer.Message; // While there is still a message, copy it while (!string.IsNullOrEmpty(msgCopy)) { string fragment = msgCopy.Substring(0, Constants.MESSAGE_LENGTH); msgCopy = msgCopy.Substring(Constants.MESSAGE_LENGTH, msgCopy.Length - Constants.MESSAGE_LENGTH); // Pad remainder of fragment fragment = fragment.PadRight(Constants.MESSAGE_LENGTH, '9'); signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes String fragment = transfer.Message; if (transfer.Message.Length < Constants.MESSAGE_LENGTH) { fragment = fragment.PadRight(Constants.MESSAGE_LENGTH, '9'); } signatureFragments.Add(fragment); } tag = transfer.Tag; // pad for required 27 tryte length if (transfer.Tag.Length < Constants.TAG_LENGTH) { tag = tag.PadRight(Constants.TAG_LENGTH, '9'); } // get current timestamp in seconds long timestamp = (long)Math.Floor(GetCurrentTimestampInSeconds()); // Add first entry to the bundle bundle.AddEntry(signatureMessageLength, transfer.Address, transfer.Value, tag, timestamp); // Sum up total value totalValue += transfer.Value; } // Get inputs if we are sending tokens if (totalValue != 0) { List <string> tipHashes = null; if (tips != null) { tipHashes = new List <string>(); foreach (var tx in tips) { tipHashes.Add(tx.CurlHash()); } } GetBalancesResponse balancesResponse = IotaClient.GetBalances(100, new List <string> { inputAddress }, tipHashes); var balances = balancesResponse.Balances; long totalBalance = 0; foreach (var balance in balances) { totalBalance += balance; } // get current timestamp in seconds long timestamp = (long)Math.Floor(GetCurrentTimestampInSeconds()); // bypass the balance checks during unit testing //TODO remove this ugliness if (testMode) { totalBalance += 1000; } if (totalBalance > 0) { long toSubtract = 0 - totalBalance; // Add input as bundle entry // Only a single entry, signatures will be added later bundle.AddEntry(securitySum, inputAddress, toSubtract, tag, timestamp); } // Return not enough balance error if (totalValue > totalBalance) { throw new IllegalStateException("Not enough balance."); } // If there is a remainder value // Add extra output to send remaining funds to if (totalBalance > totalValue) { long remainder = totalBalance - totalValue; // Remainder bundle entry if necessary if (remainderAddress == null) { throw new IllegalStateException("No remainder address defined."); } bundle.AddEntry(1, remainderAddress, remainder, tag, timestamp); } bundle.FinalizeBundle(SpongeFactory.Create(SpongeFactory.Mode.CURLP81)); bundle.AddTrytes(signatureFragments); return(bundle.Transactions); } else { throw new System.Exception("Invalid value transfer: the transfer does not require a signature."); } }
/// <summary> /// /// </summary> /// <param name="trytes"></param> public void BroadcastAndStore(params string[] trytes) { IotaClient.BroadcastTransactions(trytes); IotaClient.StoreTransactions(trytes); }
/// <summary> /// /// </summary> /// <param name="seed"></param> /// <param name="security"></param> /// <param name="start"></param> /// <param name="end"></param> /// <param name="threshold"></param> /// <param name="tips"></param> /// <returns></returns> public GetBalancesAndFormatResponse GetInputs(string seed, int security, int start, int end, long threshold, params string[] tips) { // validate the seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } seed = InputValidator.PadSeedIfNecessary(seed); if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } // If start value bigger than end, return error if (start > end) { throw new ArgumentException("start must be smaller than end", nameof(start)); } // or if difference between end and start is bigger than 500 keys if (end - start > 500) { throw new ArgumentException("total number of keys exceeded 500"); } Stopwatch stopwatch = Stopwatch.StartNew(); // Case 1: start and end // // If start and end is defined by the user, simply iterate through the keys // and call getBalances if (end != 0) { var allAddresses = new string[end - start]; for (var i = start; i < end; i++) { var address = IotaApiUtils.NewAddress(seed, security, i, true, SpongeFactory.Create(SpongeFactory.Mode.KERL)); allAddresses[i] = address; } return(GetBalanceAndFormat(allAddresses, tips, threshold, start, security, stopwatch)); } { // Case 2: iterate till threshold // // Either start from index: 0 or start (if defined) until threshold is reached. List <Input> allInputs = new List <Input>(); bool thresholdReached = true; long currentTotal = 0; for (int i = start; thresholdReached; i++) { string address = IotaApiUtils.NewAddress(seed, security, i, true, SpongeFactory.Create(SpongeFactory.Mode.KERL)); // Received input, this epoch or previous GetBalancesResponse response = IotaClient.GetBalances(100, new List <string>() { address }, tips.ToList()); var balance = response.Balances[0]; if (balance > 0) { // Is it already spent from? WereAddressesSpentFromResponse wasSpent = IotaClient.WereAddressesSpentFrom(address); if (wasSpent.States.Length > 0 && !wasSpent.States[0]) { // We can use this! allInputs.Add(new Input { Address = address, Balance = balance, KeyIndex = i, Security = security }); currentTotal += balance; if (threshold != 0 && threshold <= currentTotal) { // Stop because we found threshold thresholdReached = false; } } } else { // Check if there was any activity at all FindTransactionsResponse tx = IotaClient.FindTransactionsByAddresses(address); if (tx.Hashes.Count == 0 || i - start > 500) { // Stop because we reached our limit or no activity thresholdReached = false; } } } stopwatch.Stop(); return(new GetBalancesAndFormatResponse(allInputs, currentTotal, stopwatch.ElapsedMilliseconds)); } }
/// <summary> /// /// </summary> /// <param name="hashes"></param> /// <returns></returns> public GetInclusionStatesResponse GetLatestInclusion(string[] hashes) { string[] latestMilestone = { IotaClient.GetNodeInfo().LatestSolidSubtangleMilestone }; return(IotaClient.GetInclusionStates(hashes, latestMilestone)); }