Commit 74dad036 authored by zeroleak's avatar zeroleak
Browse files

add DOJO pairing

parent baffb401
......@@ -172,7 +172,9 @@ Response:
"network": "test",
"serverUrl": "",
"serverName": "TESTNET",
"tor": true
"dojoUrl": "",
"tor": true,
"dojo": true
}
```
......@@ -208,10 +210,12 @@ Response:
Payload:
* pairingPayload: pairing payload from Samourai Wallet
* tor: enable Tor
* dojo: enable Dojo (use null to auto-detect from pairingPayload)
```
{
pairingPayload: "...",
tor: true
tor: true,
dojo: true
}
```
......
......@@ -75,6 +75,11 @@
<artifactId>jtorctl</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -14,6 +14,7 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.Fields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -38,11 +39,12 @@ public class JavaHttpClient implements IHttpClient {
}
@Override
public synchronized <T> T getJson(String urlStr, Class<T> responseType) throws HttpException {
public synchronized <T> T getJson(
String urlStr, Class<T> responseType, Map<String, String> headers) throws HttpException {
final boolean isRegOut = false;
try {
HttpClient httpClient = getHttpClient(isRegOut);
ContentResponse response = httpClient.GET(urlStr);
Request req = computeHttpRequest(isRegOut, urlStr, HttpMethod.GET, headers);
ContentResponse response = req.send();
T result = parseResponse(response, responseType);
return result;
......@@ -59,12 +61,12 @@ public class JavaHttpClient implements IHttpClient {
}
@Override
public synchronized <T> T postJsonOverTor(String urlStr, Class<T> responseType, Object bodyObj)
public synchronized <T> T postJsonOverTor(
String urlStr, Class<T> responseType, Map<String, String> headers, Object bodyObj)
throws HttpException {
final boolean isRegOut = true;
try {
HttpClient httpClient = getHttpClient(isRegOut);
Request request = httpClient.POST(urlStr);
Request request = computeHttpRequest(isRegOut, urlStr, HttpMethod.POST, headers);
String jsonBody = objectMapper.writeValueAsString(bodyObj);
request.content(
......@@ -88,12 +90,14 @@ public class JavaHttpClient implements IHttpClient {
@Override
public synchronized <T> T postUrlEncoded(
String urlStr, Class<T> responseType, Map<String, String> body) throws HttpException {
String urlStr, Class<T> responseType, Map<String, String> headers, Map<String, String> body)
throws HttpException {
final boolean isRegOut = false;
try {
HttpClient httpClient = getHttpClient(isRegOut);
Request request = httpClient.POST(urlStr);
Request request = computeHttpRequest(isRegOut, urlStr, HttpMethod.POST, headers);
if (log.isDebugEnabled()) {
log.debug("POST.body=" + body.keySet());
}
request.content(new FormContentProvider(computeBodyFields(body)));
ContentResponse response = request.send();
......@@ -121,6 +125,9 @@ public class JavaHttpClient implements IHttpClient {
private <T> T parseResponse(ContentResponse response, Class<T> responseType) throws Exception {
T result = null;
if (log.isTraceEnabled()) {
log.trace("response: " + response.getContentAsString());
}
if (responseType != null) {
result = objectMapper.readValue(response.getContent(), responseType);
}
......@@ -147,6 +154,24 @@ public class JavaHttpClient implements IHttpClient {
}
}
private Request computeHttpRequest(
boolean isRegOut, String url, HttpMethod method, Map<String, String> headers)
throws Exception {
if (log.isDebugEnabled()) {
String headersStr = headers != null ? " (" + headers.keySet() + ")" : "";
log.debug("+" + method + ": " + url + headersStr);
}
HttpClient httpClient = getHttpClient(isRegOut);
Request req = httpClient.newRequest(url);
req.method(method);
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
}
return req;
}
private HttpClient computeHttpClient(boolean isRegisterOutput) throws Exception {
HttpClient httpClient =
CliUtils.computeHttpClient(isRegisterOutput, torClientService, cliConfig.getCliProxy());
......
......@@ -7,6 +7,7 @@ import com.samourai.whirlpool.cli.api.protocol.rest.ApiCliInitResponse;
import com.samourai.whirlpool.cli.api.protocol.rest.ApiCliLoginRequest;
import com.samourai.whirlpool.cli.api.protocol.rest.ApiCliStateResponse;
import com.samourai.whirlpool.cli.beans.CliStatus;
import com.samourai.whirlpool.cli.beans.WhirlpoolPairingPayload;
import com.samourai.whirlpool.cli.config.CliConfig;
import com.samourai.whirlpool.cli.services.CliConfigService;
import com.samourai.whirlpool.cli.services.CliWalletService;
......@@ -35,7 +36,9 @@ public class CliController extends AbstractRestController {
cliWalletService.getCliState(),
cliConfig.getServer(),
cliConfig.computeServerUrl(),
cliConfig.getTor());
cliConfig.getDojo().getUrl(),
cliConfig.getTor(),
cliConfig.isDojoEnabled());
return response;
}
......@@ -53,7 +56,9 @@ public class CliController extends AbstractRestController {
// init
String pairingPayload = payload.pairingPayload;
boolean tor = payload.tor;
String apiKey = cliConfigService.initialize(pairingPayload, tor);
boolean dojo = payload.dojo;
WhirlpoolPairingPayload pairing = cliConfigService.parsePairingPayload(pairingPayload);
String apiKey = cliConfigService.initialize(pairing, tor, dojo);
ApiCliInitResponse response = new ApiCliInitResponse(apiKey);
return response;
......
package com.samourai.whirlpool.cli.api.protocol;
public class CliApi {
public static final String API_VERSION = "0.7";
public static final String API_VERSION = "0.8";
public static final String HEADER_API_VERSION = "apiVersion";
public static final String HEADER_API_KEY = "apiKey";
}
......@@ -3,6 +3,7 @@ package com.samourai.whirlpool.cli.api.protocol.beans;
import com.samourai.whirlpool.cli.beans.CliProxy;
import com.samourai.whirlpool.cli.config.CliConfig;
import com.samourai.whirlpool.cli.config.CliConfigFile;
import com.samourai.whirlpool.cli.services.CliConfigService;
import com.samourai.whirlpool.client.exception.NotifiableException;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolServer;
import java.lang.invoke.MethodHandles;
......@@ -17,6 +18,7 @@ public class ApiCliConfig {
private String server;
private String scode;
private Boolean tor;
private Boolean dojo;
private String proxy;
private ApiMixConfig mix;
......@@ -36,6 +38,7 @@ public class ApiCliConfig {
this.server = cliConfig.getServer().name();
this.scode = cliConfig.getScode();
this.tor = cliConfig.getTor();
this.dojo = cliConfig.getDojo().isEnabled();
this.proxy = cliConfig.getProxy();
this.mix = new ApiMixConfig(cliConfig.getMix());
}
......@@ -55,6 +58,10 @@ public class ApiCliConfig {
props.put(KEY_TOR, Boolean.toString(tor));
}
if (dojo != null) {
props.put(CliConfigService.KEY_DOJO_ENABLED, Boolean.toString(dojo));
}
if (proxy != null) {
if (!StringUtils.isEmpty(proxy) && !CliProxy.validate(proxy)) {
throw new NotifiableException("Invalid value for: proxy");
......@@ -91,6 +98,14 @@ public class ApiCliConfig {
this.tor = tor;
}
public Boolean getDojo() {
return dojo;
}
public void setDojo(Boolean dojo) {
this.dojo = dojo;
}
public String getProxy() {
return proxy;
}
......
......@@ -5,6 +5,7 @@ import javax.validation.constraints.NotEmpty;
public class ApiCliInitRequest {
@NotEmpty public String pairingPayload;
public boolean tor;
public boolean dojo;
public ApiCliInitRequest() {}
}
......@@ -13,10 +13,17 @@ public class ApiCliStateResponse {
private String network;
private String serverUrl;
private String serverName;
private String dojoUrl;
private boolean tor;
private boolean dojo;
public ApiCliStateResponse(
CliState cliState, WhirlpoolServer server, String serverUrl, boolean tor) {
CliState cliState,
WhirlpoolServer server,
String serverUrl,
String dojoUrl,
boolean tor,
boolean dojo) {
this.cliStatus = cliState.getCliStatus();
this.cliMessage = cliState.getCliMessage();
this.loggedIn = cliState.isLoggedIn();
......@@ -25,7 +32,9 @@ public class ApiCliStateResponse {
this.network = server.getParams().getPaymentProtocolId();
this.serverUrl = serverUrl;
this.serverName = server.name();
this.dojoUrl = dojoUrl;
this.tor = tor;
this.dojo = dojo;
}
public CliStatus getCliStatus() {
......@@ -56,7 +65,15 @@ public class ApiCliStateResponse {
return serverName;
}
public String getDojoUrl() {
return dojoUrl;
}
public boolean isTor() {
return tor;
}
public boolean isDojo() {
return dojo;
}
}
......@@ -18,8 +18,12 @@ public class WhirlpoolPairingPayload extends PairingPayload {
}
public WhirlpoolPairingPayload(
PairingVersion version, PairingNetwork network, String mnemonic, Boolean passphrase) {
super(PairingType.WHIRLPOOL_GUI, version, network, mnemonic, passphrase);
PairingVersion version,
PairingNetwork network,
String mnemonic,
Boolean passphrase,
PairingDojo dojo) {
super(PairingType.WHIRLPOOL_GUI, version, network, mnemonic, passphrase, dojo);
}
public static WhirlpoolPairingPayload parse(String pairingPayloadStr) throws NotifiableException {
......@@ -55,7 +59,8 @@ public class WhirlpoolPairingPayload extends PairingPayload {
throw new NotifiableException("Unsupported pairing.type");
}
if (!PairingVersion.V1_0_0.equals(getPairing().getVersion())
&& !PairingVersion.V2_0_0.equals(getPairing().getVersion())) {
&& !PairingVersion.V2_0_0.equals(getPairing().getVersion())
&& !PairingVersion.V3_0_0.equals(getPairing().getVersion())) {
throw new NotifiableException("Unsupported pairing.version");
}
if (getPairing().getPassphrase() == null) {
......
......@@ -40,14 +40,6 @@ public class CliConfig extends CliConfigFile {
return config;
}
public String computeBackendUrl() {
boolean isTestnet = FormatsUtilGeneric.getInstance().isTestNet(getServer().getParams());
BackendServer backendServer = BackendServer.get(isTestnet);
boolean useOnion = getTor() && getTorConfig().isOnionBackend();
String backendUrl = backendServer.getBackendUrl(useOnion);
return backendUrl;
}
public boolean isAutoAggregatePostmix() {
return autoAggregatePostmix;
}
......@@ -83,4 +75,36 @@ public class CliConfig extends CliConfigFile {
configInfo.put("cli/autoTx0PoolId", autoTx0PoolId != null ? autoTx0PoolId : "null");
return configInfo;
}
//
public String computeBackendUrl() {
if (getDojo().isEnabled()) {
// use dojo
return getDojo().getUrl();
}
// use Samourai backend
return computeBackendUrlSamourai();
}
public boolean isDojoEnabled() {
return getDojo() != null && getDojo().isEnabled();
}
public String computeBackendApiKey() {
if (isDojoEnabled()) {
// dojo: use apiKey
return getDojo().getApiKey();
}
// Samourai backend: no apiKey
return null;
}
private String computeBackendUrlSamourai() {
boolean isTestnet = FormatsUtilGeneric.getInstance().isTestNet(getServer().getParams());
BackendServer backendServer = BackendServer.get(isTestnet);
boolean useOnion = getTor() && getTorConfig().isOnionBackend();
String backendUrl = backendServer.getBackendUrl(useOnion);
return backendUrl;
}
}
......@@ -28,6 +28,7 @@ public abstract class CliConfigFile {
@NotEmpty private String pushtx;
@NotEmpty private boolean tor;
@NotEmpty private TorConfig torConfig;
@NotEmpty private DojoConfig dojo;
@NotEmpty private String apiKey;
@NotEmpty private String seed;
@NotEmpty private boolean seedAppendPassphrase;
......@@ -52,6 +53,7 @@ public abstract class CliConfigFile {
this.pushtx = copy.pushtx;
this.tor = copy.tor;
this.torConfig = new TorConfig(copy.torConfig);
this.dojo = new DojoConfig(copy.dojo);
this.apiKey = copy.apiKey;
this.seed = copy.seed;
this.seedAppendPassphrase = copy.seedAppendPassphrase;
......@@ -121,6 +123,14 @@ public abstract class CliConfigFile {
this.torConfig = torConfig;
}
public DojoConfig getDojo() {
return dojo;
}
public void setDojo(DojoConfig dojo) {
this.dojo = dojo;
}
public String getApiKey() {
return apiKey;
}
......@@ -319,6 +329,52 @@ public abstract class CliConfigFile {
}
}
public static class DojoConfig {
@NotEmpty private String url;
@NotEmpty private String apiKey;
@NotEmpty private boolean enabled;
public DojoConfig() {}
public DojoConfig(DojoConfig copy) {
this.url = copy.url;
this.apiKey = copy.apiKey;
this.enabled = copy.enabled;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Map<String, String> getConfigInfo() {
Map<String, String> configInfo = new HashMap<>();
configInfo.put("cli/dojo/url", url);
configInfo.put("cli/dojo/apiKey", ClientUtils.maskString(apiKey));
configInfo.put("cli/dojo/enabled", Boolean.toString(enabled));
return configInfo;
}
}
public String computeServerUrl() {
boolean useOnion = tor && torConfig.onionServer;
String serverUrl = server.getServerUrl(useOnion);
......@@ -358,6 +414,11 @@ public abstract class CliConfigFile {
configInfo.put("cli/pushtx", ClientUtils.maskString(pushtx));
configInfo.put("cli/tor", Boolean.toString(tor));
configInfo.putAll(torConfig.getConfigInfo());
if (dojo != null) {
configInfo.putAll(dojo.getConfigInfo());
} else {
configInfo.put("cli/dojo", "null");
}
configInfo.put("cli/apiKey", ClientUtils.maskString(apiKey));
configInfo.put("cli/seedEncrypted", ClientUtils.maskString(seed));
configInfo.put("cli/persistDelay", Integer.toString(persistDelay));
......
......@@ -77,6 +77,8 @@ public class CliStatusOrchestrator extends AbstractOrchestrator {
+ (whirlpoolWallet.isStarted() ? "STARTED" : "STOPPED")
+ (walletConfig.isAutoTx0() ? " +autoTx0=" + walletConfig.getAutoTx0PoolId() : "")
+ (walletConfig.isAutoMix() ? " +autoMix" : "")
+ (cliConfig.getTor() ? " +Tor" : "")
+ (cliConfig.isDojoEnabled() ? " +Dojo" : "")
+ ", "
+ mixState.getNbMixing()
+ " mixing, "
......
package com.samourai.whirlpool.cli.run;
import com.samourai.whirlpool.cli.ApplicationArgs;
import com.samourai.whirlpool.cli.beans.WhirlpoolPairingPayload;
import com.samourai.whirlpool.cli.services.CliConfigService;
import com.samourai.whirlpool.cli.services.CliWalletService;
import com.samourai.whirlpool.cli.utils.CliUtils;
......@@ -36,17 +37,26 @@ public class RunCliInit {
log.info("⣿ • Paste your pairing payload here:");
String pairingPayload = CliUtils.readUserInputRequired("Pairing payload?", false);
log.info("⣿ ");
WhirlpoolPairingPayload pairing = cliConfigService.parsePairingPayload(pairingPayload);
// Tor
log.info("⣿ • Enable Tor? (you can change this later)");
String torStr =
CliUtils.readUserInputRequired(
"Enable Tor? (y/n)", false, new String[] {"y", "n", "Y", "N"});
boolean tor = torStr.toLowerCase().equals("y");
log.info("⣿ ");
boolean tor;
if (pairing.getDojo() != null) {
// dojo => Tor enabled
log.info("⣿ Pairing with Dojo => Tor enabled.");
tor = true;
} else {
// samourai backend => Tor optional
log.info("⣿ • Enable Tor? (you can change this later)");
String torStr =
CliUtils.readUserInputRequired(
"Enable Tor? (y/n)", false, new String[] {"y", "n", "Y", "N"});
tor = torStr.toLowerCase().equals("y");
log.info("⣿ ");
}
// init
String apiKey = cliConfigService.initialize(pairingPayload, tor);
String apiKey = cliConfigService.initialize(pairing, tor, null);
log.info(CliUtils.LOG_SEPARATOR);
log.info("⣿ API KEY GENERATED");
......
package com.samourai.whirlpool.cli.services;
import com.samourai.wallet.api.pairing.PairingNetwork;
import com.samourai.wallet.api.pairing.PairingPayload;
import com.samourai.whirlpool.cli.api.protocol.beans.ApiCliConfig;
import com.samourai.whirlpool.cli.beans.CliStatus;
import com.samourai.whirlpool.cli.beans.WhirlpoolPairingPayload;
......@@ -35,6 +36,9 @@ public class CliConfigService {
private static final String KEY_APIKEY = "cli.apiKey";
private static final String KEY_SEED = "cli.seed";
private static final String KEY_SEED_APPEND_PASSPHRASE = "cli.seedAppendPassphrase";
private static final String KEY_DOJO_URL = "cli.dojo.url";
private static final String KEY_DOJO_APIKEY = "cli.dojo.apiKey";
public static final String KEY_DOJO_ENABLED = "cli.dojo.enabled";
private static final String KEY_VERSION = "cli.version";
private CliConfig cliConfig;
......@@ -74,27 +78,55 @@ public class CliConfigService {
return CliStatus.NOT_INITIALIZED.equals(cliStatus);
}
public String initialize(String pairingPayloadStr, boolean tor) throws NotifiableException {
public WhirlpoolPairingPayload parsePairingPayload(String pairingWalletPayload) throws Exception {
return WhirlpoolPairingPayload.parse(pairingWalletPayload);
}
public String initialize(WhirlpoolPairingPayload pairingWallet, boolean tor, Boolean dojo)
throws NotifiableException {
// parse payload
WhirlpoolPairingPayload pairingPayload = WhirlpoolPairingPayload.parse(pairingPayloadStr);
// use dojo?
String dojoUrl = null;
String dojoApiKey = null;
PairingPayload.PairingDojo pairingDojo = pairingWallet.getDojo();
if (pairingDojo != null) {
dojoUrl = pairingDojo.getUrl();
dojoApiKey = pairingDojo.getApikey();
if (dojo == null) {
dojo = true;
}
} else {
if (dojo == null) {
dojo = false;
}
if (dojo) {
throw new NotifiableException("Cannot enable DOJO: dojo pairing not found");
}
}
// initialize
String encryptedMnemonic = pairingPayload.getPairing().getMnemonic();
boolean appendPassphrase = pairingPayload.getPairing().getPassphrase();
PairingNetwork pairingNetwork = pairingPayload.getPairing().getNetwork();
PairingPayload.PairingValue pairing = pairingWallet.getPairing();
String encryptedMnemonic = pairing.getMnemonic();
boolean appendPassphrase = pairing.getPassphrase();
PairingNetwork pairingNetwork = pairing.getNetwork();
WhirlpoolServer whirlpoolServer =
PairingNetwork.MAINNET.equals(pairingNetwork)
? WhirlpoolServer.MAINNET
: WhirlpoolServer.TESTNET;
return initialize(encryptedMnemonic, appendPassphrase, whirlpoolServer, tor);
return initialize(
encryptedMnemonic, appendPassphrase, whirlpoolServer, tor, dojoUrl, dojoApiKey, dojo);
}
public synchronized String initialize(
String encryptedMnemonic,
boolean appendPassphrase,
WhirlpoolServer whirlpoolServer,
boolean tor)
boolean tor,
String dojoUrl,
String dojoApiKey,
boolean dojoEnabled)
throws NotifiableException {
if (log.isDebugEnabled()) {
log.debug(" • initialize");
......@@ -117,6 +149,13 @@ public class CliConfigService {
props.put(KEY_SEED_APPEND_PASSPHRASE, Boolean.toString(appendPassphrase));
props.put(ApiCliConfig.KEY_SERVER, whirlpoolServer.name());
props.put(ApiCliConfig.KEY_TOR, Boolean.toString(tor));
if (dojoUrl != null) {
props.put(KEY_DOJO_URL, dojoUrl);
}
if (dojoApiKey != null) {