private static string CalculateChecksum(string address) { ICurl curl = SpongeFactory.Create(SpongeFactory.Mode.KERL); curl.Reset(); curl.Absorb(Converter.ToTrits(address)); sbyte[] checksumTrits = new sbyte[Sponge.HASH_LENGTH]; curl.Squeeze(checksumTrits); string checksum = Converter.ToTrytes(checksumTrits); return(checksum.Substring(72, 9)); }
public void ShouldCreateValidHash1() { sbyte[] trits = Converter.ToTrits("GYOMKVTSNHVJNCNFBBAH9AAMXLPLLLROQY99QN9DLSJUHDPBLCFFAIQXZA9BKMBJCYSFHFPXAHDWZFEIZ"); Kerl kerl = (Kerl)SpongeFactory.Create(SpongeFactory.Mode.KERL); kerl.Reset(); kerl.Absorb(trits, 0, trits.Length); sbyte[] hashTrits = new sbyte[trits.Length]; kerl.Squeeze(hashTrits, 0, 243); string hash = Converter.ToTrytes(hashTrits); Assert.AreEqual(hash, "OXJCNFHUNAHWDLKKPELTBFUCVW9KLXKOGWERKTJXQMXTKFKNWNNXYD9DMJJABSEIONOSJTTEVKVDQEWTW"); }
public void ShouldCreateValidHash2() { sbyte[] trits = Converter.ToTrits("9MIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJFGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH"); Kerl kerl = (Kerl)SpongeFactory.Create(SpongeFactory.Mode.KERL); kerl.Reset(); kerl.Absorb(trits, 0, trits.Length); sbyte[] hashTrits = new sbyte[trits.Length * 2]; kerl.Squeeze(hashTrits, 0, 243 * 2); string hash = Converter.ToTrytes(hashTrits); Assert.AreEqual(hash, "G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJBVBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA"); }
public void ShouldCreateValidHash3() { sbyte[] trits = Converter.ToTrits( "G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJBVBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA"); Kerl kerl = (Kerl)SpongeFactory.Create(SpongeFactory.Mode.KERL); kerl.Reset(); kerl.Absorb(trits, 0, trits.Length); sbyte[] hashTrits = new sbyte[trits.Length]; kerl.Squeeze(hashTrits, 0, 243 * 2); string hash = Converter.ToTrytes(hashTrits); Assert.AreEqual(hash, "LUCKQVACOGBFYSPPVSSOXJEKNSQQRQKPZC9NXFSMQNRQCGGUL9OHVVKBDSKEQEBKXRNUJSRXYVHJTXBPDWQGNSCDCBAIRHAQCOWZEBSNHIJIGPZQITIBJQ9LNTDIBTCQ9EUWKHFLGFUVGGUWJONK9GBCDUIMAYMMQX"); }
private string GetFirstUnusedAddress(string seed, int securityLevel, int index, bool checksum) { while (true) { string newAddress = IotaApiUtils.NewAddress( seed, securityLevel, index, checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL)); if (!IsAddressSpent(newAddress, checksum)) { return(newAddress); } index++; } }
public void TestLongSeedKeyGeneration() { ICurl curl = SpongeFactory.Create(SpongeFactory.Mode.KERL); Signing signing = new Signing(curl); string seed = "EV9QRJFJZVFNLYUFXWKXMCRRPNAZYQVEYB9VEPUHQNXJCWKZFVUCTQJFCUAMXAHMMIUQUJDG9UGGQBPIY"; for (int i = Constants.MIN_SECURITY_LEVEL; i < Constants.MAX_SECURITY_LEVEL; i++) { sbyte[] key1 = signing.Key(Converter.ToTrits(seed), 0, i); Assert.AreEqual(Constants.KEY_LENGTH * i, key1.Length); sbyte[] key2 = signing.Key(Converter.ToTrits(seed + seed), 0, i); Assert.AreEqual(Constants.KEY_LENGTH * i, key2.Length); sbyte[] key3 = signing.Key(Converter.ToTrits(seed + seed + seed), 0, i); Assert.AreEqual(Constants.KEY_LENGTH * i, key3.Length); } }
private List <string> GetAddresses(string seed, int securityLevel, int index, bool checksum, int amount, bool addSpendAddresses) { List <string> addresses = new List <string>(); for (int i = index, numUnspentFound = 0; numUnspentFound < amount; i++) { string newAddress = IotaApiUtils.NewAddress(seed, securityLevel, i, checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL)); if (!IsAddressSpent(newAddress, checksum)) { addresses.Add(newAddress); numUnspentFound++; } else if (addSpendAddresses) { addresses.Add(newAddress); } } return(addresses); }
/// <summary> /// This does not mean that these addresses are safe to use (unspent) /// </summary> /// <param name="addressRequest"></param> /// <returns></returns> public GetNewAddressResponse GetAddressesUnchecked(AddressRequest addressRequest) { var stopwatch = Stopwatch.StartNew(); List <string> addresses = new List <string>(); for (int i = 0; i < addressRequest.Amount; i++) { addresses.Add( IotaApiUtils.NewAddress( addressRequest.Seed, addressRequest.SecurityLevel, i, addressRequest.Checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } stopwatch.Stop(); return(new GetNewAddressResponse { Addresses = addresses, Duration = stopwatch.ElapsedMilliseconds }); }
/// <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); }
private List <string> AddRemainder( string seed, int security, List <Input> inputs, Bundle bundle, string tag, long totalValue, string remainderAddress, List <string> signatureFragments) { var totalTransferValue = totalValue; foreach (var input in inputs) { var thisBalance = input.Balance; var toSubtract = 0 - thisBalance; var timestamp = TimeStamp.Now(); // Add input as bundle entry // use input.Security bundle.AddEntry(input.Security, input.Address, toSubtract, tag, timestamp); // If there is a remainder value // Add extra output to send remaining funds to if (thisBalance >= totalTransferValue) { var remainder = thisBalance - totalTransferValue; // If user has provided remainder address // Use it to send remaining funds to if (remainder > 0 && remainderAddress != null) { // Remainder bundle entry bundle.AddEntry(1, remainderAddress, remainder, tag, timestamp); // function for signing inputs return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } if (remainder > 0) { AddressRequest addressRequest = new AddressRequest(seed, security); var res = GenerateNewAddresses(addressRequest); // Remainder bundle entry bundle.AddEntry(1, res.Addresses[0], remainder, tag, timestamp); // function for signing inputs return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } // If there is no remainder, do not add transaction to bundle // simply sign and return return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } // If multiple inputs provided, subtract the totalTransferValue by // the inputs balance totalTransferValue -= thisBalance; } throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); }
/// <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="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)); } }
public static TransactionViewModel Validate(int[] trits, int minWeightMagnitude) { TransactionViewModel transactionViewModel = new TransactionViewModel(trits, Hash.Calculate(trits, 0, trits.Length, SpongeFactory.Create(SpongeFactory.Mode.CurlP81))); RunValidation(transactionViewModel, minWeightMagnitude); return(transactionViewModel); }
/// <summary> /// /// </summary> /// <param name="bundle"></param> /// <param name="curl"></param> /// <returns></returns> public static bool IsBundle(Bundle bundle, ICurl curl = null) { if (curl == null) { curl = SpongeFactory.Create(SpongeFactory.Mode.KERL); } long totalSum = 0; int lastIndex = bundle.Length - 1; for (int i = 0; i < bundle.Length; i++) { var tx = bundle.Transactions[i]; totalSum += tx.Value; if (tx.CurrentIndex != i) { throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); } if (tx.LastIndex != lastIndex) { throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); } sbyte[] txTrits = Converter.ToTrits(tx.ToTrytes().Substring(2187, 162)); curl.Absorb(txTrits); // continue if output or signature tx if (tx.Value >= 0) { continue; } // here we have an input transaction (negative value) List <string> fragments = new List <string> { tx.SignatureMessageFragment }; // find the subsequent txs containing the remaining signature // message fragments for this input transaction for (int j = i; j < bundle.Length - 1; j++) { Transaction tx2 = bundle.Transactions[j + 1]; // check if the tx is part of the input transaction if (tx.Address.Equals(tx2.Address, StringComparison.Ordinal) && tx2.Value == 0) { // append the signature message fragment fragments.Add(tx2.SignatureMessageFragment); } } bool valid = new Signing(curl.Clone()).ValidateSignatures(tx.Address, fragments.ToArray(), tx.Bundle); if (!valid) { throw new ArgumentException(Constants.INVALID_SIGNATURES_ERROR); } } // sum of all transaction must be 0 if (totalSum != 0) { throw new ArgumentException(Constants.INVALID_BUNDLE_SUM_ERROR); } sbyte[] bundleHashTrits = new sbyte[Sponge.HASH_LENGTH]; curl.Squeeze(bundleHashTrits, 0, Sponge.HASH_LENGTH); string bundleHash = Converter.ToTrytes(bundleHashTrits); if (!bundleHash.Equals(bundle.Transactions[0].Bundle, StringComparison.Ordinal)) { throw new ArgumentException(Constants.INVALID_BUNDLE_HASH_ERROR); } return(true); }