package co.gem.round;
import co.gem.round.coinop.MultiWallet;
import co.gem.round.crypto.EncryptedMessage;
import co.gem.round.crypto.PassphraseBox;
import co.gem.round.patchboard.Client;
import co.gem.round.patchboard.Resource;
import com.google.gson.JsonObject;
import org.spongycastle.crypto.InvalidCipherTextException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;
/**
* Wallet is a gem wallet which is the HD Multi-Sig (2of3) wallet that is owned by a User. Wallets provide access to
* accounts in the wallet. As well as info such as the balance of all accounts. Gem wallets follow BIP32 and BIP44
* but without hardened nodes.
*
* @author Julian Vergel de Dios (julian@gem.co) on 12/18/14.
* @see co.gem.round.Account
*/
public class Wallet extends Base {
private Application app;
private EncryptedMessage encryptedSeed;
public Wallet(Resource resource, Round round, Application app) {
super(resource, round);
this.app = app;
}
public boolean hasApplication() {
return app != null;
}
public void unlock(String passphrase, UnlockedWalletCallback callback)
throws IOException, Client.UnexpectedStatusCodeException,
NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, InvalidAlgorithmParameterException, BadPaddingException, NoSuchPaddingException, InvalidKeyException, NoSuchProviderException, InvalidCipherTextException {
String decryptedSeed = null;
try {
decryptedSeed = PassphraseBox.decrypt(passphrase, this.getEncryptedSeed());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return;
} catch (InvalidKeySpecException e) {
e.printStackTrace();
return;
}
MultiWallet wallet = MultiWallet.importSeeds(decryptedSeed,
this.getBackupPublicSeed(), this.getCosignerPublicSeed());
wallet.purgeSeeds(); // Get rid of seeds, we have the keys after initialization.
callback.execute(wallet);
}
/**
* Gets default (first) account in a wallet
* @return default Account
* @throws Client.UnexpectedStatusCodeException
* @throws IOException
* @see co.gem.round.Account
*/
public Account defaultAccount() throws Client.UnexpectedStatusCodeException, IOException {
return accounts().get(0);
}
/**
* Gets account in a wallet with name matching accountName parameter.
* @param accountName Account name
* @return Account
* @throws Client.UnexpectedStatusCodeException
* @throws IOException
* @see co.gem.round.Account
*/
public Account account(String accountName) throws Client.UnexpectedStatusCodeException, IOException {
Map<String, String> query = new HashMap<>();
query.put("name", accountName);
Account account = new Account(resource.subresource("account_query", query), this.round);
account.fetch();
return account;
}
/**
* Getter for accounts in a wallet. Returns populated AccountCollection object. To
* retrieve reference without fetching accounts use 'accounts(false)'
* @return AccountCollection of Accounts
* @throws Client.UnexpectedStatusCodeException
* @throws IOException
* @see co.gem.round.AccountCollection
*/
public AccountCollection accounts() throws Client.UnexpectedStatusCodeException, IOException {
return accounts(true);
}
/**
* Getter for AccountCollection object in a wallet.
* @param fetch boolean used to determine whether to populate collection
* @return AccountCollection object
* @throws Client.UnexpectedStatusCodeException
* @throws IOException
* @see co.gem.round.AccountCollection
*/
public AccountCollection accounts(boolean fetch) throws Client.UnexpectedStatusCodeException, IOException {
AccountCollection accounts = new AccountCollection(resource.subresource("accounts"), this.round, this);
if (fetch)
accounts.fetch();
return accounts;
}
/**
* Getter for the elements of the encrypted primary seed.
* @return EncryptedMessage containing ciphertext, salt, iv, iterations for decryption purposes
*/
public EncryptedMessage getEncryptedSeed() {
if (this.encryptedSeed == null) {
JsonObject seedObject = getObject("primary_private_seed");
EncryptedMessage encryptedMessage = new EncryptedMessage();
encryptedMessage.ciphertext = seedObject.get("ciphertext").getAsString();
encryptedMessage.salt = seedObject.get("salt").getAsString();
encryptedMessage.iv = seedObject.get("iv").isJsonNull() ? null : seedObject.get("iv").getAsString();
encryptedMessage.iterations = Integer.parseInt(seedObject.get("iterations").getAsString());
this.encryptedSeed = encryptedMessage;
}
return this.encryptedSeed;
}
/**
* Getter for the backup pubSeed
* @return String
*/
public String getBackupPublicSeed() {
return getString("backup_public_seed");
}
/**
* Getter for Gem API's cosigner pubSeed
* @return String
*/
public String getCosignerPublicSeed() {
return getString("cosigner_public_seed");
}
/**
* Getter for Primary pubSeed
* @return String
*/
public String getPrimaryPublicSeed() {
return getString("primary_public_seed");
}
/**
* Sum of all of the account balances within a wallet. Balances are based off of the value of inputs with 1 or more
* confirmations.
* @return Long
*/
public Long balance() {
return getLong("balance");
}
public static class Wrapper {
private Wallet wallet;
private String backupPrivateSeed;
public Wrapper(Wallet wallet, String backupPrivateSeed) {
this.wallet = wallet;
this.backupPrivateSeed = backupPrivateSeed;
}
/**
* Getter for the wallet
* @return String
*/
public Wallet getWallet() {
return wallet;
}
/**
* Getter for the wallet's backup private seed
* @return String
*/
public String getBackupPrivateSeed() {
return backupPrivateSeed;
}
}
}