package com.greenaddress.greenapi;
import com.blockstream.libwally.Wally;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.script.Script;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SWWallet extends ISigningWallet {
private final DeterministicKey mRootKey;
public SWWallet(final String mnemonic) {
final byte[] seed = CryptoHelper.mnemonic_to_seed(mnemonic);
mRootKey = HDKey.createMasterKeyFromSeed(seed);
}
public SWWallet(final DeterministicKey key) {
mRootKey = key;
}
@Override
public boolean requiresPrevoutRawTxs() { return false; }
private SWWallet derive(final Integer childNumber) {
return new SWWallet(HDKey.deriveChildKey(mRootKey, childNumber));
}
@Override
public DeterministicKey getSubAccountPublicKey(final int subAccount) {
return getMyKey(subAccount).mRootKey;
}
@Override
public List<byte[]> signTransaction(final PreparedTransaction ptx) {
return signTransaction(ptx.mDecoded, ptx, ptx.mPrevOutputs);
}
@Override
public List<byte[]> signTransaction(final Transaction tx, final PreparedTransaction ptx, final List<Output> prevOuts) {
final List<TransactionInput> txInputs = tx.getInputs();
final List<byte[]> sigs = new ArrayList<>(txInputs.size());
for (int i = 0; i < txInputs.size(); ++i) {
final Output prevOut = prevOuts.get(i);
final Script script = new Script(Wally.hex_to_bytes(prevOut.script));
final Sha256Hash hash;
if (prevOut.scriptType.equals(GATx.P2SH_P2WSH_FORTIFIED_OUT))
hash = tx.hashForSignatureWitness(i, script.getProgram(), Coin.valueOf(prevOut.value), Transaction.SigHash.ALL, false);
else
hash = tx.hashForSignature(i, script.getProgram(), Transaction.SigHash.ALL, false);
final SWWallet key = getMyKey(prevOut.subAccount).derive(prevOut.branch).derive(prevOut.pointer);
final ECKey eckey = ECKey.fromPrivate(key.mRootKey.getPrivKey());
sigs.add(getTxSignature(eckey.sign(Sha256Hash.wrap(hash.getBytes()))));
}
return sigs;
}
@Override
public Object[] getChallengeArguments() {
final Address addr = new Address(Network.NETWORK, mRootKey.getIdentifier());
return new Object[]{ "login.get_challenge", addr.toString() };
}
@Override
public String[] signChallenge(final String challengeString, final String[] challengePath) {
// Generate a path for the challenge. This is really a nonce so we aren't
// tricked into signing the same challenge (and thus revealing our key)
// by a compromised server.
final byte[] path = CryptoHelper.randomBytes(8);
// Return the path to the caller for them to pass in the server RPC call
challengePath[0] = Wally.hex_from_bytes(path);
// Derive the private key for signing the challenge from the path
DeterministicKey key = mRootKey;
for (int i = 0; i < path.length / 2; ++i) {
final int step = u8(path[i * 2]) * 256 + u8(path[i * 2 + 1]);
key = HDKey.deriveChildKey(key, step);
}
// Get rid of initial 0 byte if challenge > 2^31
// FIXME: The server should not send us challenges that we have to munge!
byte[] challenge = new BigInteger(challengeString).toByteArray();
if (challenge.length == 33 && challenge[0] == 0)
challenge = Arrays.copyOfRange(challenge, 1, 33);
// Compute and return the challenge signatures
final ECKey.ECDSASignature sig;
sig = ECKey.fromPrivate(key.getPrivKey()).sign(Sha256Hash.wrap(challenge));
return new String[]{ sig.r.toString(), sig.s.toString() };
}
public DeterministicKey getMasterKey() {
return mRootKey;
}
private SWWallet getMyKey(final int subAccount) {
SWWallet parent = this;
if (subAccount != 0)
parent = parent.derive(ISigningWallet.HARDENED | 3)
.derive(ISigningWallet.HARDENED | subAccount);
return parent;
}
public byte[] getLocalEncryptionPassword() {
final byte[] pubkey = this.derive(PASSWORD_PATH).mRootKey.getPubKey();
return CryptoHelper.pbkdf2_hmac_sha512(pubkey, PASSWORD_SALT);
}
private int u8(final int i) { return i < 0 ? 256 + i : i; }
}