multisigs [wip]

This commit is contained in:
AZ
2017-02-04 18:56:10 +01:00
parent 62bf75ed53
commit 4b4c4af7ae
3 changed files with 407 additions and 3 deletions
+172 -3
View File
@@ -225,7 +225,7 @@ public class IotaAPI extends IotaAPICore {
* @param {int} minWeightMagnitude
* @return
*/
public List<Transaction> sendTrytes(final String[] trytes, final int depth, final int minWeightMagnitude) {
public List<Transaction> sendTrytes(final String[] trytes, final int depth, final int minWeightMagnitude) throws InvalidTrytesException {
final GetTransactionsToApproveResponse txs = getTransactionsToApprove(depth);
// attach to tangle - do pow
@@ -662,7 +662,7 @@ public class IotaAPI extends IotaAPICore {
* @method replayBundle
* @returns {object} analyzed Transaction objects
**/
public ReplayBundleResponse replayBundle(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, ArgumentException, InvalidSignatureException {
public ReplayBundleResponse replayBundle(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, ArgumentException, InvalidSignatureException, InvalidTrytesException {
StopWatch stopWatch = new StopWatch();
List<String> bundleTrytes = new ArrayList<>();
@@ -706,7 +706,7 @@ public class IotaAPI extends IotaAPICore {
return getInclusionStates(hashes, latestMilestone);
}
public SendTransferResponse sendTransfer(String seed, int security, int depth, int minWeightMagnitude, final List<Transfer> transfers, Input[] inputs, String address) throws NotEnoughBalanceException, InvalidSecurityLevelException {
public SendTransferResponse sendTransfer(String seed, int security, int depth, int minWeightMagnitude, final List<Transfer> transfers, Input[] inputs, String address) throws NotEnoughBalanceException, InvalidSecurityLevelException, InvalidTrytesException {
if (security < 1 || security > 3) {
throw new InvalidSecurityLevelException();
@@ -781,6 +781,175 @@ public class IotaAPI extends IotaAPICore {
}
}
/**
* Prepares transfer by generating the bundle with the corresponding cosigner transactions
* Does not contain signatures
*
* @param {object} transfers
* @param {function} callback
* @param securitySum sum of security levels used by all co-signers
* @param inputAddress array of input addresses as well as the securitySum
* @param remainderAddress Has to be generated by the cosigners before initiating the transfer, can be null if fully spent
* @method initiateTransfer
* @returns {array} Array of transaction objects
**/
private GetTransferResponse initiateTransfer(int securitySum, final List<String> inputAddress, String remainderAddress, final List<Transfer> transfers) {
StopWatch sw = new StopWatch();
// If message or tag is not supplied, provide it
// Also remove the checksum of the address if it's there
for (Transfer transfer : transfers) {
transfer.setMessage(transfer.getMessage().isEmpty() ? StringUtils.rightPad(transfer.getMessage(), 2187, '9') : transfer.getMessage());
transfer.setTag(transfer.getTag().isEmpty() ? StringUtils.rightPad(transfer.getTag(), 27, '9') : transfer.getTag());
if (Checksum.isValidChecksum(transfer.getAddress())) {
transfer.setAddress(Checksum.removeChecksum(transfer.getAddress()));
}
}
// Input validation of transfers object
if (!InputValidator.isTransfersCollectionCorrect(transfers)) {
throw new IllegalStateException("Invalid Transfer");
}
// validate input address
for (String address : inputAddress) {
if (!InputValidator.isAddress(address))
throw new IllegalStateException("Invalid Address");
}
// validate remainder address
if (remainderAddress != null && !InputValidator.isAddress(remainderAddress)) {
throw new IllegalStateException("Invalid Address");
}
// Create a new bundle
final Bundle bundle = new Bundle();
int totalValue = 0;
final List<String> signatureFragments = new ArrayList<>();
String tag = "";
//
// Iterate over all transfers, get totalValue
// and prepare the signatureFragments, message and tag
for (final Transfer transfer : transfers) {
int signatureMessageLength = 1;
// If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction)
if (transfer.getMessage().length() > 2187) {
// Get total length, message / maxLength (2187 trytes)
signatureMessageLength += Math.floor(transfer.getMessage().length() / 2187);
String msgCopy = transfer.getMessage();
// While there is still a message, copy it
while (!msgCopy.isEmpty()) {
String fragment = StringUtils.substring(msgCopy, 0, 2187);
msgCopy = StringUtils.substring(msgCopy, 2187, msgCopy.length());
// Pad remainder of fragment
fragment = StringUtils.rightPad(fragment, 2187, '9');
signatureFragments.add(fragment);
}
} else {
// Else, get single fragment with 2187 of 9's trytes
String fragment = StringUtils.substring(transfer.getMessage(), 0, 2187);
fragment = StringUtils.rightPad(fragment, 2187, '9');
signatureFragments.add(fragment);
}
// get current timestamp in seconds
long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000);
// If no tag defined, get 27 tryte tag.
tag = transfer.getTag().isEmpty() ? StringUtils.rightPad(transfer.getTag(), 27, '9') : transfer.getTag();
// Pad for required 27 tryte length
tag = StringUtils.rightPad(tag, 27, '9');
// Add first entry to the bundle
bundle.addEntry(signatureMessageLength, transfer.getAddress(), transfer.getValue(), tag, timestamp);
// Sum up total value
totalValue += transfer.getValue();
}
// Get inputs if we are sending tokens
if (totalValue != 0) {
GetBalancesResponse balancesResponse = getBalances(100, inputAddress);
String[] balances = balancesResponse.getBalances();
long totalBalance = 0;
for (String balance : balances) {
long thisBalance = Integer.parseInt(balance);
totalBalance += thisBalance;
}
// get current timestamp in seconds
long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000);
if (totalBalance > 0) {
long toSubtract = 0 - totalBalance;
// Add input as bundle entry
// Only a single entry, signatures will be added later
// TODO: fill in the right address
bundle.addEntry(securitySum, "", 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.finalize(customCurl);
bundle.addTrytes(signatureFragments);
return GetTransferResponse.create(new Bundle[]{}, sw.getElapsedTimeMili());
} else {
throw new RuntimeException("Invalid value transfer: the transfer does not require a signature.");
}
}
public String findTailTransactionHash(String hash) throws ArgumentException {
GetTrytesResponse gtr = getTrytes(hash);
@@ -27,10 +27,24 @@ public class InputValidator {
return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$");
}
public static boolean isNinesTrytes(final String trytes, final int length) {
return trytes.matches("^[9]{" + (length == 0 ? "0," : length) + "}$");
}
public static boolean isValue(final String value) {
return NumberUtils.isNumber(value);
}
public static boolean isArrayOfTrytes(String[] trytes){
for (String tryte : trytes) {
// Check if correct 2673 trytes
if (!isTrytes(tryte, 2673)) {
return false;
}
}
return true;
}
public static boolean isArrayOfHashes(String[] hashes) {
if (hashes == null)
return false;
+221
View File
@@ -0,0 +1,221 @@
package jota.utils;
import jota.model.Bundle;
import jota.pow.ICurl;
import jota.pow.JCurl;
import java.util.Arrays;
/**
* Created by pinpong on 02.02.17.
*/
public class Multisig {
private ICurl curl;
private Signing signingInstance;
public Multisig(ICurl customCurl) {
this.curl = customCurl;
this.signingInstance = new Signing(curl);
}
public Multisig() {
this.curl = new JCurl();
}
/**
* Gets the digest value of a seed
*
* @param {string} seed
* @param {int} index
* @param {int} security
* @method getDigest
* @returns {string} digest trytes
**/
private String getDigest(String seed, int index, int security) {
int[] key = signingInstance.key(Converter.trits(seed), index, security);
return Converter.trytes(signingInstance.digests(key));
}
/**
* Initiates the generation of a new multisig address or adds the key digest to an existing one
*
* @param digestTrytes
* @param curlStateTrytes
* @method addAddressDigest
* @returns {String}
**/
private String addAddressDigest(String digestTrytes, String curlStateTrytes, ICurl customCurl) {
int[] digest = Converter.trits(digestTrytes);
// If curlStateTrytes is provided, convert into trits
// else use empty state and initiate the creation of a new address
int[] curlState = curlStateTrytes.isEmpty() ? Converter.trits(curlStateTrytes) : new int[243];
ICurl curl = customCurl == null ? new JCurl() : customCurl;
// initialize Curl with the provided state
curl.setState(curlState);
// absorb the key digest
curl.absorb(digest);
return Converter.trytes(curl.getState());
}
/**
* Gets the key value of a seed
*
* @param {string} seed
* @param {int} index
* @method getKey
* @returns {string} digest trytes
**/
private String getKey(String seed, int index, int security) {
return Converter.trytes(signingInstance.key(Converter.trits(seed), index, security));
}
/**
* Generates a new address
*
* @param {string} curlStateTrytes
* @param {string}
* @method finalizeAddress
* @returns {String} address
**/
private String finalizeAddress(String curlStateTrytes, ICurl customCurl) {
int[] curlState = Converter.trits(curlStateTrytes);
// initialize Curl with the provided state
curl.setState(curlState);
int[] addressTrits = new int[243];
curl.squeeze(addressTrits);
// Convert trits into trytes and return the address
return Converter.trytes(addressTrits);
}
/**
* Validates a generated multisig address
*
* @param multisigAddress
* @param digests
* @method validateAddress
* @returns {bool}
**/
private boolean validateAddress(String multisigAddress, int[][] digests, ICurl customCurl) {
// initialize Curl with the provided state
curl.reset();
for (int[] keyDigest : digests) {
curl.absorb(keyDigest);
}
int[] addressTrits = new int[243];
curl.squeeze(addressTrits);
// Convert trits into trytes and return the address
return Converter.trytes(addressTrits).equals(multisigAddress);
}
/**
* Adds the cosigner signatures to the corresponding bundle transaction
*
* @param {array} bundleToSign
* @param {int} cosignerIndex
* @param {string} inputAddress
* @param {string} key
* @param {function} callback
* @method addSignature
* @returns {array} trytes Returns bundle trytes
**/
private void addSignature(Bundle[] bundleToSign, String inputAddress, String keyTrytes) {
// Get the security used for the private key
// 1 security level = 2187 trytes
int security = (keyTrytes.length() / 2187);
// convert private key trytes into trits
int[] key = Converter.trits(keyTrytes);
// First get the total number of already signed transactions
// use that for the bundle hash calculation as well as knowing
// where to add the signature
int numSignedTxs = 0;
for (Bundle bundle : bundleToSign)
for (int i = 0; i < bundle.getTransactions().size(); i++) {
if (!bundle.getTransactions().get(i).getAddress().equals(inputAddress)) {
// If transaction is already signed, increase counter
if (!InputValidator.isNinesTrytes(bundle.getTransactions().get(i).getSignatureFragments(), bundle.getTransactions().get(i).getSignatureFragments().length())) {
numSignedTxs++;
}
// Else sign the transactions
else {
String bundleHash = bundle.getTransactions().get(i).getBundle();
// First 6561 trits for the firstFragment
int[] firstFragment = Arrays.copyOfRange(key, 0, 6561);
// Get the normalized bundle hash
int[][] normalizedBundleFragments = new int[3][27];
int[] normalizedBundleHash = bundle.normalizedBundle(bundleHash);
// Split hash into 3 fragments
for (int k = 0; k < 3; k++) {
normalizedBundleFragments[k] = Arrays.copyOfRange(normalizedBundleHash, (k * 27), (k + 1) * 27);
}
// First bundle fragment uses 27 trytes
int[] firstBundleFragment = normalizedBundleFragments[numSignedTxs % 3];
// Calculate the new signatureFragment with the first bundle fragment
int[] firstSignedFragment = signingInstance.signatureFragment(firstBundleFragment, firstFragment);
// Convert signature to trytes and assign the new signatureFragment
bundle.getTransactions().get(i).setSignatureFragments(Converter.trytes(firstSignedFragment));
for (int j = 1; j < security; j++) {
// Next 6561 trits for the firstFragment
int[] nextFragment = Arrays.copyOfRange(key, 6561 * j, (j + 1) * 6561);
// Use the next 27 trytes
int[] nextBundleFragment = normalizedBundleFragments[(numSignedTxs + j) % 3];
// Calculate the new signatureFragment with the first bundle fragment
int[] nextSignedFragment = signingInstance.signatureFragment(nextBundleFragment, nextFragment);
// Convert signature to trytes and add new bundle entry at i + j position
// Assign the signature fragment
bundle.getTransactions().get(i + j).setSignatureFragments(Converter.trytes(nextSignedFragment));
}
break;
}
}
}
return;
}
}