Commit 75dbe7e7 authored by zeroleak's avatar zeroleak
Browse files

Merge branch 'dsk' into develop

# Conflicts:
#	pom.xml
parents 46da3462 01f49379
package com.samourai.wallet.client;
import com.samourai.wallet.api.backend.beans.UnspentOutput;
import com.samourai.wallet.client.indexHandler.IIndexHandler;
import com.samourai.wallet.hd.*;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolAccount;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BipWallet {
private static final Logger log = LoggerFactory.getLogger(BipWallet.class);
private WhirlpoolAccount account;
private HD_Wallet bipWallet;
private IIndexHandler indexHandler;
private IIndexHandler indexChangeHandler;
public BipWallet(
HD_Wallet bip44w,
WhirlpoolAccount account,
IIndexHandler indexHandler,
IIndexHandler indexChangeHandler,
AddressType addressType) {
this.account = account;
this.bipWallet = new HD_Wallet(addressType.getPurpose(), bip44w);
this.indexHandler = indexHandler;
this.indexChangeHandler = indexChangeHandler;
}
public String getPub(AddressType addressType) {
HD_Account hdAccount = bipWallet.getAccount(account.getAccountIndex());
switch (addressType) {
case LEGACY:
return hdAccount.xpubstr();
case SEGWIT_COMPAT:
return hdAccount.ypubstr();
case SEGWIT_NATIVE:
return hdAccount.zpubstr();
}
log.error("Unknown addressType: " + addressType);
return null;
}
public HD_Address getNextAddress() {
return getNextAddress(true);
}
public HD_Address getNextAddress(boolean increment) {
int nextAddressIndex = increment ? indexHandler.getAndIncrement() : indexHandler.get();
return getAddressAt(Chain.RECEIVE.getIndex(), nextAddressIndex);
}
public HD_Address getNextChangeAddress() {
return getNextChangeAddress(true);
}
public HD_Address getNextChangeAddress(boolean increment) {
int nextAddressIndex =
increment ? indexChangeHandler.getAndIncrement() : indexChangeHandler.get();
return getAddressAt(Chain.CHANGE.getIndex(), nextAddressIndex);
}
public HD_Address getAddressAt(int chainIndex, int addressIndex) {
return bipWallet.getAddressAt(account.getAccountIndex(), chainIndex, addressIndex);
}
public HD_Address getAddressAt(UnspentOutput utxo) {
return getAddressAt(utxo.computePathChainIndex(), utxo.computePathAddressIndex());
}
public WhirlpoolAccount getAccount() {
return account;
}
public IIndexHandler getIndexHandler() {
return indexHandler;
}
public IIndexHandler getIndexChangeHandler() {
return indexChangeHandler;
}
}
package com.samourai.http.client;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.samourai.wallet.api.backend.beans.HttpException;
import com.samourai.wallet.util.JSONUtils;
import io.reactivex.Observable;
import java8.util.Optional;
import org.slf4j.Logger;
......@@ -14,11 +14,7 @@ import java.util.concurrent.Callable;
public abstract class JacksonHttpClient implements IHttpClient {
private static final Logger log = LoggerFactory.getLogger(JacksonHttpClient.class);
private ObjectMapper objectMapper;
public JacksonHttpClient() {
this.objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
protected abstract String requestJsonGet(String urlStr, Map<String, String> headers, boolean async)
......@@ -68,7 +64,7 @@ public abstract class JacksonHttpClient implements IHttpClient {
@Override
public T call() throws Exception {
try {
String jsonBody = objectMapper.writeValueAsString(bodyObj);
String jsonBody = getObjectMapper().writeValueAsString(bodyObj);
String responseContent = requestJsonPost(urlStr, headers, jsonBody);
T result = parseJson(responseContent, responseType);
return result;
......@@ -119,7 +115,7 @@ public abstract class JacksonHttpClient implements IHttpClient {
if (String.class.equals(responseType)) {
result = (T) responseContent;
} else {
result = objectMapper.readValue(responseContent, responseType);
result = getObjectMapper().readValue(responseContent, responseType);
}
return result;
}
......@@ -142,6 +138,6 @@ public abstract class JacksonHttpClient implements IHttpClient {
}
protected ObjectMapper getObjectMapper() {
return objectMapper;
return JSONUtils.getInstance().getObjectMapper();
}
}
......@@ -9,4 +9,8 @@ public class SamouraiWalletConst {
// hard limit for acceptable fees 0.005
public static final long MAX_ACCEPTABLE_FEES = 500000;
public static final BigInteger RBF_SEQUENCE_VAL_WITH_NLOCKTIME = BigInteger.valueOf(0xffffffffL - 1L);
public static final BigInteger RBF_SEQUENCE_VAL = BigInteger.valueOf(0xffffffffL - 2L);
public static final BigInteger NLOCKTIME_SEQUENCE_VAL = BigInteger.valueOf(0xffffffffL - 3L);
}
package com.samourai.wallet.api.backend;
import com.samourai.wallet.api.backend.beans.*;
import com.samourai.wallet.util.oauth.OAuthApi;
import com.samourai.wallet.util.oauth.OAuthManager;
import java8.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class BackendApi implements OAuthApi {
public class BackendApi {
private Logger log = LoggerFactory.getLogger(BackendApi.class);
private static final String URL_UNSPENT = "/unspent?active=";
private static final String URL_MULTIADDR = "/multiaddr?active=";
private static final String URL_WALLET = "/wallet?active=";
private static final String URL_TXS = "/txs?active=";
private static final String URL_TX = "/tx/";
private static final String URL_INIT_BIP84 = "/xpub";
private static final String URL_MINER_FEES = "/fees";
private static final String URL_PUSHTX = "/pushtx/";
private static final String URL_GET_AUTH_LOGIN = "/auth/login";
private static final String URL_GET_AUTH_REFRESH = "/auth/refresh";
private static final String ZPUB_SEPARATOR = "%7C";
private IBackendClient httpClient;
private String urlBackend;
private Optional<OAuthManager> oAuthManager;
private OAuthManager oAuthManager; // may be null
public BackendApi(IBackendClient httpClient, String urlBackend, Optional<OAuthManager> oAuthManager) {
public BackendApi(IBackendClient httpClient, String urlBackend) {
this(httpClient, urlBackend, null);
}
public BackendApi(IBackendClient httpClient, String urlBackend, OAuthManager oAuthManager) {
this.httpClient = httpClient;
this.urlBackend = urlBackend;
if (oAuthManager == null) {
oAuthManager = Optional.empty();
}
this.oAuthManager = oAuthManager;
if (log.isDebugEnabled()) {
String oAuthStr = oAuthManager.isPresent() ? "yes" : "no";
String oAuthStr = oAuthManager != null ? "yes" : "no";
log.debug("urlBackend=" + urlBackend + ", oAuth=" + oAuthStr);
}
}
......@@ -47,16 +45,10 @@ public class BackendApi implements OAuthApi {
return zpubStr;
}
/**
* @deprecated use fetchWallet()
*/
public List<UnspentOutput> fetchUtxos(String zpub) throws Exception {
return fetchUtxos(new String[]{zpub});
}
/**
* @deprecated use fetchWallet()
*/
public List<UnspentOutput> fetchUtxos(String[] zpubs) throws Exception {
String zpubStr = computeZpubStr(zpubs);
String url = computeAuthUrl(urlBackend + URL_UNSPENT + zpubStr);
......@@ -73,9 +65,6 @@ public class BackendApi implements OAuthApi {
return unspentOutputs;
}
/**
* @deprecated use fetchWallet()
*/
public Map<String,MultiAddrResponse.Address> fetchAddresses(String[] zpubs) throws Exception {
String zpubStr = computeZpubStr(zpubs);
String url = computeAuthUrl(urlBackend + URL_MULTIADDR + zpubStr);
......@@ -93,9 +82,6 @@ public class BackendApi implements OAuthApi {
return addressesByZpub;
}
/**
* @deprecated use fetchWallet()
*/
public MultiAddrResponse.Address fetchAddress(String zpub) throws Exception {
Collection<MultiAddrResponse.Address> addresses = fetchAddresses(new String[]{zpub}).values();
if (addresses.size() != 1) {
......@@ -115,9 +101,6 @@ public class BackendApi implements OAuthApi {
return address;
}
/**
* @deprecated use fetchWallet()
*/
public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception {
String zpubStr = computeZpubStr(zpubs);
......@@ -129,6 +112,15 @@ public class BackendApi implements OAuthApi {
return httpClient.getJson(url, TxsResponse.class, headers);
}
public TxDetail fetchTx(String txid, boolean fees) throws Exception {
String url = computeAuthUrl(urlBackend + URL_TX + txid + (fees ? "?fees=1" : ""));
if (log.isDebugEnabled()) {
log.debug("fetchTx: "+txid);
}
Map<String,String> headers = computeHeaders();
return httpClient.getJson(url, TxDetail.class, headers);
}
public WalletResponse fetchWallet(String zpub) throws Exception {
return fetchWallet(new String[]{zpub});
}
......@@ -243,9 +235,9 @@ public class BackendApi implements OAuthApi {
protected Map<String,String> computeHeaders() throws Exception {
Map<String,String> headers = new HashMap<String, String>();
if (oAuthManager.isPresent()) {
if (oAuthManager != null) {
// add auth token
headers.put("Authorization", "Bearer " + oAuthManager.get().getOAuthAccessToken(this));
headers.put("Authorization", "Bearer " + oAuthManager.getOAuthAccessToken());
}
return headers;
}
......@@ -263,39 +255,4 @@ public class BackendApi implements OAuthApi {
return urlBackend;
}
// OAuthAPI
@Override
public RefreshTokenResponse.Authorization oAuthAuthenticate(String apiKey) throws Exception {
String url = getUrlBackend() + URL_GET_AUTH_LOGIN;
if (log.isDebugEnabled()) {
log.debug("tokenAuthenticate");
}
Map<String, String> postBody = new HashMap<String, String>();
postBody.put("apikey", apiKey);
RefreshTokenResponse response =
getHttpClient().postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
if (response.authorizations == null|| StringUtils.isEmpty(response.authorizations.access_token)) {
throw new Exception("Authorization refused. Invalid apiKey?");
}
return response.authorizations;
}
@Override
public String oAuthRefresh(String refreshTokenStr) throws Exception {
String url = getUrlBackend() + URL_GET_AUTH_REFRESH;
if (log.isDebugEnabled()) {
log.debug("tokenRefresh");
}
Map<String, String> postBody = new HashMap<String, String>();
postBody.put("rt", refreshTokenStr);
RefreshTokenResponse response =
getHttpClient().postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
if (response.authorizations == null || StringUtils.isEmpty(response.authorizations.access_token)) {
throw new Exception("Authorization refused. Invalid apiKey?");
}
return response.authorizations.access_token;
}
}
package com.samourai.wallet.api.backend;
import com.samourai.wallet.api.backend.beans.RefreshTokenResponse;
import com.samourai.wallet.util.oauth.OAuthApi;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class BackendOAuthApi implements OAuthApi {
private Logger log = LoggerFactory.getLogger(BackendApi.class);
private static final String URL_GET_AUTH_LOGIN = "/auth/login";
private static final String URL_GET_AUTH_REFRESH = "/auth/refresh";
private IBackendClient httpClient;
private String urlBackend;
public BackendOAuthApi(IBackendClient httpClient, String urlBackend) {
this.httpClient = httpClient;
this.urlBackend = urlBackend;
}
@Override
public RefreshTokenResponse.Authorization oAuthAuthenticate(String apiKey) throws Exception {
String url = urlBackend + URL_GET_AUTH_LOGIN;
if (log.isDebugEnabled()) {
log.debug("tokenAuthenticate");
}
Map<String, String> postBody = new HashMap<String, String>();
postBody.put("apikey", apiKey);
RefreshTokenResponse response =
httpClient.postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
if (response.authorizations == null|| StringUtils.isEmpty(response.authorizations.access_token)) {
throw new Exception("Authorization refused. Invalid apiKey?");
}
return response.authorizations;
}
@Override
public String oAuthRefresh(String refreshTokenStr) throws Exception {
String url = urlBackend + URL_GET_AUTH_REFRESH;
if (log.isDebugEnabled()) {
log.debug("tokenRefresh");
}
Map<String, String> postBody = new HashMap<String, String>();
postBody.put("rt", refreshTokenStr);
RefreshTokenResponse response =
httpClient.postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
if (response.authorizations == null || StringUtils.isEmpty(response.authorizations.access_token)) {
throw new Exception("Authorization refused. Invalid apiKey?");
}
return response.authorizations.access_token;
}
}
......@@ -13,4 +13,16 @@ public class MinerFee {
int fee = feesResponse.get(feeTarget.getValue());
return fee;
}
public Map<String, Integer> _getMap() {
return feesResponse;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MinerFee minerFee = (MinerFee) o;
return feesResponse.equals(minerFee.feesResponse);
}
}
package com.samourai.wallet.api.backend;
/*
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Looper;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.samourai.wallet.MainActivity2;
import com.samourai.wallet.SamouraiWallet;
import com.samourai.wallet.api.APIFactory;
import com.samourai.wallet.bip47.BIP47Meta;
import com.samourai.wallet.bip47.BIP47Util;
import com.samourai.wallet.bip47.rpc.PaymentCode;
import com.samourai.wallet.tor.TorManager;
import com.samourai.wallet.util.AppUtil;
import com.samourai.wallet.util.MonetaryUtil;
import com.samourai.wallet.util.NotificationsFactory;
import com.samourai.wallet.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import static com.samourai.wallet.util.LogUtil.debug;
import static com.samourai.wallet.util.LogUtil.info;
public class WebSocketHandler {
private WebSocket mConnection = null;
private String[] addrs = null;
private static List<String> seenHashes = new ArrayList<String>();
private static Context context = null;
public WebSocketHandler(Context ctx, String[] addrs) {
this.context = ctx;
this.addrs = addrs;
}
public void send(String message) {
try {
if (mConnection != null && mConnection.isOpen()) {
info("WebSocketHandler", "Websocket subscribe:" + message);
mConnection.sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void subscribe() {
send("{\"op\":\"blocks_sub\"}");
for(int i = 0; i < addrs.length; i++) {
if(addrs[i] != null && addrs[i].length() > 0) {
send("{\"op\":\"addr_sub\", \"addr\":\""+ addrs[i] + "\"}");
// info("WebSocketHandler", "{\"op\":\"addr_sub\",\"addr\":\"" + addrs[i] + "\"}");
}
}
}
public boolean isConnected() {
return mConnection != null && mConnection.isOpen();
}
public void stop() {
if(mConnection != null && mConnection.isOpen()) {
mConnection.disconnect();
}
}
public void start() {
try {
stop();
connect();
}
catch (IOException | com.neovisionaries.ws.client.WebSocketException e) {
e.printStackTrace();
}
}
private void connect() throws IOException, WebSocketException
{
new ConnectionTask().execute();
}
private void updateBalance(final String rbfHash, final String blkHash) {
new Thread() {
public void run() {
Looper.prepare();
// Intent intent = new Intent("com.samourai.wallet.BalanceFragment.REFRESH");
// intent.putExtra("rbf", rbfHash);
// intent.putExtra("notifTx", true);
// intent.putExtra("fetch", true);
// intent.putExtra("hash", blkHash);
// LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Looper.loop();
}
}.start();
}
private void updateReceive(final String address) {
new Thread() {
public void run() {
Looper.prepare();
Intent intent = new Intent("com.samourai.wallet.ReceiveFragment.REFRESH");
intent.putExtra("received_on", address);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Looper.loop();
}
}.start();
}
private class ConnectionTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... args) {
if(AppUtil.getInstance(context).isOfflineMode() || TorManager.INSTANCE.isRequired()) {
return null;
}
try {
mConnection = new WebSocketFactory()
.createSocket(SamouraiWallet.getInstance().isTestNet() ? "wss://api.samourai.io/test/v2/inv" : "wss://api.samourai.io/v2/inv")
.addListener(new WebSocketAdapter() {
public void onTextMessage(WebSocket websocket, String message) {
debug("WebSocket", message);
try {
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(message);
} catch (JSONException je) {
// info("WebSocketHandler", "JSONException:" + je.getMessage());
jsonObject = null;
}
if (jsonObject == null) {
// info("WebSocketHandler", "jsonObject is null");
return;
}
// info("WebSocketHandler", jsonObject.toString());
String op = (String) jsonObject.get("op");
if(op.equals("block") && jsonObject.has("x")) {
JSONObject objX = (JSONObject) jsonObject.get("x");
String hash = null;
if (objX.has("hash")) {
hash = objX.getString("hash");
if(seenHashes.contains(hash)){
return;
}
else {
seenHashes.add(hash);
}
}
updateBalance(null, hash);
return;
}
if (op.equals("utx") && jsonObject.has("x")) {
JSONObject objX = (JSONObject) jsonObject.get("x");
long value = 0L;
long total_value = 0L;
long ts = 0L;
String in_addr = null;
String out_addr = null;
String hash = null;
boolean isRBF = false;
if (objX.has("time")) {
ts = objX.getLong("time");
}
if (objX.has("hash")) {
hash = objX.getString("hash");
}