Compare commits

...

1 Commits

18 changed files with 181 additions and 29 deletions

View File

@ -3,6 +3,8 @@ package com.samourai.wallet.bip47.rpc.java;
import com.samourai.wallet.bip47.BIP47UtilGeneric;
import com.samourai.wallet.bip47.rpc.secretPoint.ISecretPoint;
import com.samourai.wallet.bip47.rpc.secretPoint.ISecretPointFactory;
import com.samourai.wallet.hd.HD_Address;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -27,6 +29,11 @@ public class Bip47UtilJava extends BIP47UtilGeneric {
InvalidKeyException {
return new SecretPointJava(dataPrv, dataPub);
}
@Override
public ISecretPoint newSecretPoint(HD_Address address, byte[] dataPub) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException {
return new SecretPointJava(address.getECKey().getPrivKeyBytes(), dataPub);
}
};
private Bip47UtilJava() {

View File

@ -2,6 +2,8 @@ package com.samourai.wallet.bip47.rpc.java;
import com.samourai.wallet.bip47.rpc.secretPoint.ISecretPoint;
import com.samourai.wallet.bip47.rpc.secretPoint.ISecretPointFactory;
import com.samourai.wallet.hd.HD_Address;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -17,11 +19,14 @@ public class SecretPointFactoryJava implements ISecretPointFactory {
return instance;
}
private SecretPointFactoryJava() {}
public SecretPointFactoryJava() {}
@Override
public ISecretPoint newSecretPoint(byte[] dataPrv, byte[] dataPub) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException {
return new SecretPointJava(dataPrv, dataPub);
}
public ISecretPoint newSecretPoint(HD_Address address, byte[] dataPub) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException {
return new SecretPointJava(address.getECKey().getPrivKeyBytes(), dataPub);
}
}

View File

@ -1,5 +1,7 @@
package com.samourai.wallet.bip47.rpc.secretPoint;
import com.samourai.wallet.hd.HD_Address;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -8,5 +10,5 @@ import java.security.spec.InvalidKeySpecException;
public interface ISecretPointFactory {
ISecretPoint newSecretPoint(byte[] dataPrv, byte[] dataPub) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException;
ISecretPoint newSecretPoint(HD_Address address, byte[] dataPub) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException;
}

View File

@ -22,7 +22,7 @@ public class BipWallet {
IIndexHandler indexChangeHandler,
AddressType addressType) {
this.account = account;
this.bipWallet = new HD_Wallet(addressType.getPurpose(), bip44w);
this.bipWallet = bip44w.getSeed() == null ? new HD_Wallet(bip44w.getParams(), bip44w.getAccounts()) : new HD_Wallet(addressType.getPurpose(), bip44w);
this.indexHandler = indexHandler;
this.indexChangeHandler = indexChangeHandler;
}

View File

@ -120,4 +120,8 @@ public class HD_Address {
public static String getPathFull(int purpose, int coinType, int account, int chain, int address) {
return "m/"+purpose+"'/"+coinType+"'/"+account+"'/"+chain+"/"+address;
}
public NetworkParameters getParams() {
return mParams;
}
}

View File

@ -10,10 +10,7 @@ import org.bitcoinj.crypto.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
public class HD_Wallet {
private byte[] mSeed = null;
@ -75,6 +72,21 @@ public class HD_Wallet {
}
}
public HD_Wallet(NetworkParameters params, Map<Integer, HD_Account> xpub) throws AddressFormatException {
mParams = params;
// initialize mAccounts and xpubs
mAccounts = new LinkedHashMap<>();
xpubs = new String[xpub.size()];
List<Integer> indexList = new ArrayList<>(xpub.keySet());
for(int i = 0; i < indexList.size(); i++) {
Integer accountIndex = indexList.get(i);
HD_Account account = new HD_Account(mParams, xpub.get(accountIndex).strXPUB, accountIndex);
mAccounts.put(accountIndex, account);
xpubs[i] = account.xpubstr();
}
}
private static DeterministicKey computeRootKey(int purpose, List<String> mWordList, String strPassphrase, NetworkParameters params) {
byte[] hd_seed = MnemonicCode.toSeed(mWordList, strPassphrase);
DeterministicKey mKey = HDKeyDerivation.createMasterPrivateKey(hd_seed);
@ -104,6 +116,10 @@ public class HD_Wallet {
return mParams;
}
public Map<Integer, HD_Account> getAccounts() {
return mAccounts;
}
public HD_Account getAccount(int accountIdx) {
HD_Account hdAccount = mAccounts.get(accountIdx);
if (hdAccount == null) {

View File

@ -0,0 +1,10 @@
package com.samourai.wallet.send.provider;
import org.bitcoinj.core.ECKey;
public abstract class AbstractUtxoKeyProvider implements UtxoKeyProvider {
@Override
public ECKey _getPrivKey(String utxoHash, int utxoIndex) throws Exception {
return getAddress(utxoHash, utxoIndex).getECKey();
}
}

View File

@ -1,5 +1,6 @@
package com.samourai.wallet.send.provider;
import com.samourai.wallet.hd.HD_Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.TransactionOutPoint;
@ -17,4 +18,9 @@ public class SimpleUtxoKeyProvider implements UtxoKeyProvider {
public ECKey _getPrivKey(String utxoHash, int utxoIndex) throws Exception {
return keys.get(utxoHash + ":" + utxoIndex);
}
@Override
public HD_Address getAddress(String utxoHash, int utxoIndex) throws Exception {
return null;
}
}

View File

@ -1,8 +1,10 @@
package com.samourai.wallet.send.provider;
import com.samourai.wallet.hd.HD_Address;
import org.bitcoinj.core.ECKey;
public interface UtxoKeyProvider {
ECKey _getPrivKey(String utxoHash, int utxoIndex) throws Exception;
HD_Address getAddress(String utxoHash, int utxoIndex) throws Exception;
}

View File

@ -0,0 +1,48 @@
package com.samourai.whirlpool.client.mix.handler;
import com.samourai.wallet.hd.HD_Address;
import com.samourai.wallet.segwit.SegwitAddress;
import com.samourai.wallet.send.SendFactoryGeneric;
import com.samourai.wallet.send.exceptions.SignTxException;
import com.samourai.wallet.send.provider.UtxoKeyProvider;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
public class DefaultSigningHandler implements SigningHandler {
@Override
public String signMessage(HD_Address hdAddress, String message) {
return hdAddress.getECKey().signMessage(message);
}
@Override
public Transaction signTx0Transaction(Transaction transaction, UtxoKeyProvider utxoKeyProvider) throws SignTxException {
byte[] signedTx = signTx0Transaction(transaction.bitcoinSerialize(), utxoKeyProvider);
if(signedTx != null) {
return new Transaction(transaction.getParams(), signedTx);
}
SendFactoryGeneric.getInstance().signTransaction(transaction, getUtxoKeyProvider(utxoKeyProvider));
return transaction;
}
protected byte[] signTx0Transaction(byte[] transactionBytes, UtxoKeyProvider utxoKeyProvider) throws SignTxException {
return null;
}
protected UtxoKeyProvider getUtxoKeyProvider(UtxoKeyProvider utxoKeyProvider) {
return utxoKeyProvider;
}
@Override
public byte[] signMixTransaction(byte[] transaction, int inputIndex, HD_Address hdAddress, long spendAmount) {
final SegwitAddress segwitAddress = new SegwitAddress(hdAddress.getECKey(), hdAddress.getParams());
final Script redeemScript = segwitAddress.segWitRedeemScript();
final Script scriptCode = redeemScript.scriptCode();
Transaction tx = new Transaction(hdAddress.getParams(), transaction);
TransactionSignature sig = tx.calculateWitnessSignature(inputIndex, hdAddress.getECKey(), scriptCode, Coin.valueOf(spendAmount), Transaction.SigHash.ALL, false);
return sig.encodeToBitcoin();
}
}

View File

@ -1,20 +1,21 @@
package com.samourai.whirlpool.client.mix.handler;
import com.samourai.wallet.segwit.SegwitAddress;
import com.samourai.wallet.hd.HD_Address;
import com.samourai.whirlpool.client.utils.ClientUtils;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
public class PremixHandler implements IPremixHandler {
private UtxoWithBalance utxo;
private ECKey utxoKey;
private HD_Address utxoAddress;
private String userPreHash;
private SigningHandler signingHandler;
public PremixHandler(UtxoWithBalance utxo, ECKey utxoKey, String userPreHash) {
public PremixHandler(UtxoWithBalance utxo, HD_Address utxoAddress, String userPreHash, SigningHandler signingHandler) {
this.utxo = utxo;
this.utxoKey = utxoKey;
this.utxoAddress = utxoAddress;
this.userPreHash = userPreHash;
this.signingHandler = signingHandler;
}
@Override
@ -27,19 +28,14 @@ public class PremixHandler implements IPremixHandler {
throws Exception {
// TODO SendFactoryGeneric.getInstance().signInput(utxoKey, params, tx, inputIndex);
long spendAmount = utxo.getBalance();
signInputSegwit(tx, inputIndex, utxoKey, spendAmount, params);
signInputSegwit(tx, inputIndex, utxoAddress.getECKey(), spendAmount, params);
}
// TODO
protected void signInputSegwit(
Transaction tx, int inputIdx, ECKey ecKey, long spendAmount, NetworkParameters params) {
final SegwitAddress segwitAddress = new SegwitAddress(ecKey, params);
final Script redeemScript = segwitAddress.segWitRedeemScript();
final Script scriptCode = redeemScript.scriptCode();
TransactionSignature sig =
tx.calculateWitnessSignature(
inputIdx, ecKey, scriptCode, Coin.valueOf(spendAmount), Transaction.SigHash.ALL, false);
byte[] sigBytes = signingHandler.signMixTransaction(tx.bitcoinSerialize(), inputIdx, utxoAddress, spendAmount);
TransactionSignature sig = TransactionSignature.decodeFromBitcoin(sigBytes, false, false);
final TransactionWitness witness = new TransactionWitness(2);
witness.setPush(0, sig.encodeToBitcoin());
witness.setPush(1, ecKey.getPubKey());
@ -48,7 +44,7 @@ public class PremixHandler implements IPremixHandler {
@Override
public String signMessage(String message) {
return utxoKey.signMessage(message);
return signingHandler.signMessage(utxoAddress, message);
}
@Override

View File

@ -0,0 +1,12 @@
package com.samourai.whirlpool.client.mix.handler;
import com.samourai.wallet.hd.HD_Address;
import com.samourai.wallet.send.exceptions.SignTxException;
import com.samourai.wallet.send.provider.UtxoKeyProvider;
import org.bitcoinj.core.Transaction;
public interface SigningHandler {
String signMessage(HD_Address hdAddress, String message);
Transaction signTx0Transaction(Transaction transaction, UtxoKeyProvider utxoKeyProvider) throws SignTxException;
byte[] signMixTransaction(byte[] transaction, int inputIndex, HD_Address hdAddress, long spendAmount);
}

View File

@ -36,14 +36,12 @@ public class Tx0Service {
private final Bech32UtilGeneric bech32Util = Bech32UtilGeneric.getInstance();
private final FormatsUtilGeneric formatsUtilGeneric = FormatsUtilGeneric.getInstance();
private final XorMask xorMask;
private final FeeUtil feeUtil = FeeUtil.getInstance();
private WhirlpoolWalletConfig config;
public Tx0Service(WhirlpoolWalletConfig config) {
this.config = config;
xorMask = XorMask.getInstance(config.getSecretPointFactory());
}
private int computeNbPremixMax(
@ -275,12 +273,19 @@ public class Tx0Service {
ECKey firstInputKey = utxoKeyProvider._getPrivKey(firstInput.tx_hash, firstInput.tx_output_n);
String feePaymentCode = tx0Data.getFeePaymentCode();
byte[] feePayload = tx0Data.getFeePayload();
byte[] feePayloadMasked =
XorMask xorMask = XorMask.getInstance(config.getSecretPointFactory());
byte[] feePayloadMasked = firstInputKey.hasPrivKey() ?
xorMask.mask(
feePayload,
feePaymentCode,
params,
firstInputKey.getPrivKeyBytes(),
firstInput.computeOutpoint(params)) :
xorMask.mask(
feePayload,
feePaymentCode,
params,
utxoKeyProvider.getAddress(firstInput.tx_hash, firstInput.tx_output_n),
firstInput.computeOutpoint(params));
if (log.isDebugEnabled()) {
log.debug("feePayloadHex=" + Hex.toHexString(feePayload));
@ -533,15 +538,15 @@ public class Tx0Service {
}
}
signTx0(tx, utxoKeyProvider);
tx = signTx0(tx, utxoKeyProvider);
tx.verify();
Tx0 tx0 = new Tx0(tx0Preview, tx, premixOutputs, changeOutputs);
return tx0;
}
protected void signTx0(Transaction tx, UtxoKeyProvider utxoKeyProvider) throws Exception {
SendFactoryGeneric.getInstance().signTransaction(tx, utxoKeyProvider);
protected Transaction signTx0(Transaction tx, UtxoKeyProvider utxoKeyProvider) throws Exception {
return config.getSigningHandler().signTx0Transaction(tx, utxoKeyProvider);
}
protected Collection<Tx0Data> fetchTx0Data(String partnerId) throws Exception {

View File

@ -51,7 +51,7 @@ public class PostmixIndexService {
}
HD_Address hdAddress = walletPostmix.getAddressAt(Chain.RECEIVE.getIndex(), postmixIndex);
String outputAddress = bech32Util.toBech32(hdAddress, config.getNetworkParameters());
String signature = hdAddress.getECKey().signMessage(outputAddress);
String signature = config.getSigningHandler().signMessage(hdAddress, outputAddress);
CheckOutputRequest checkOutputRequest = new CheckOutputRequest(outputAddress, signature);
return config.getServerApi().checkOutput(checkOutputRequest);
}

View File

@ -7,6 +7,8 @@ import com.samourai.wallet.bip47.rpc.java.SecretPointFactoryJava;
import com.samourai.wallet.bip47.rpc.secretPoint.ISecretPointFactory;
import com.samourai.wallet.util.FormatsUtilGeneric;
import com.samourai.whirlpool.client.exception.NotifiableException;
import com.samourai.whirlpool.client.mix.handler.DefaultSigningHandler;
import com.samourai.whirlpool.client.mix.handler.SigningHandler;
import com.samourai.whirlpool.client.tx0.ITx0ParamServiceConfig;
import com.samourai.whirlpool.client.utils.ClientUtils;
import com.samourai.whirlpool.client.wallet.beans.IndexRange;
@ -31,6 +33,7 @@ public class WhirlpoolWalletConfig extends WhirlpoolClientConfig implements ITx0
private DataSourceFactory dataSourceFactory;
private DataPersisterFactory dataPersisterFactory;
private SigningHandler signingHandler = new DefaultSigningHandler();
private boolean mobile;
private int maxClients;
@ -371,6 +374,14 @@ public class WhirlpoolWalletConfig extends WhirlpoolClientConfig implements ITx0
this.secretPointFactory = secretPointFactory;
}
public SigningHandler getSigningHandler() {
return signingHandler;
}
public void setSigningHandler(SigningHandler signingHandler) {
this.signingHandler = signingHandler;
}
public Map<String, String> getConfigInfo() {
Map<String, String> configInfo = new LinkedHashMap<String, String>();
configInfo.put("dataSourceFactory", dataSourceFactory.getClass().getSimpleName());

View File

@ -185,6 +185,16 @@ public abstract class BasicUtxoSupplier extends BasicSupplier<UtxoData>
return premixAddress.getECKey();
}
@Override
public HD_Address getAddress(String utxoHash, int utxoIndex) throws Exception {
WhirlpoolUtxo whirlpoolUtxo = findUtxo(utxoHash, utxoIndex);
if (whirlpoolUtxo == null) {
throw new Exception("Utxo not found: " + utxoHash + ":" + utxoIndex);
}
return getAddress(whirlpoolUtxo);
}
protected byte[] _getPrivKeyBytes(WhirlpoolUtxo whirlpoolUtxo) {
return null;
}

View File

@ -204,7 +204,7 @@ public class MixOrchestratorImpl extends MixOrchestrator {
String premix00Bech32 = Bech32UtilGeneric.getInstance().toBech32(premix00, params);
String userPreHash = ClientUtils.sha256Hash(premix00Bech32);
return new PremixHandler(utxoWithBalance, premixKey, userPreHash);
return new PremixHandler(utxoWithBalance, premixAddress, userPreHash, whirlpoolWallet.getConfig().getSigningHandler());
}
private IPostmixHandler computePostmixHandler(WhirlpoolUtxo whirlpoolUtxo) {

View File

@ -45,6 +45,24 @@ public class XorMask {
return dataMasked;
}
public byte[] mask(
byte[] dataToMask,
String paymentCodeOfSecretAccount,
NetworkParameters params,
HD_Address address,
TransactionOutPoint input0OutPoint)
throws Exception {
HD_Address notifAddressCli =
new PaymentCode(paymentCodeOfSecretAccount).notificationAddress(params);
ISecretPoint secretPointMask =
secretPointFactory.newSecretPoint(address, notifAddressCli.getPubKey());
byte[] dataMasked = PaymentCode.xorMask(dataToMask, secretPointMask, input0OutPoint);
if (dataMasked == null) {
throw new Exception("xorMask failed");
}
return dataMasked;
}
public byte[] unmask(
byte[] dataMasked,
BIP47Account secretAccount,