Craig Raw
3 years ago
24 changed files with 423 additions and 414 deletions
@ -1 +1 @@ |
|||
Subproject commit 7bb07ab39eafc0de54d3dc2e19a444d39f9a1fc3 |
|||
Subproject commit 956f59880e508127b62d62022e3e2618f659f4d2 |
@ -1,4 +1,4 @@ |
|||
package com.sparrowwallet.sparrow.soroban; |
|||
package com.sparrowwallet.sparrow.paynym; |
|||
|
|||
import com.sparrowwallet.drongo.address.P2WPKHAddress; |
|||
|
@ -1,4 +1,4 @@ |
|||
package com.sparrowwallet.sparrow.soroban; |
|||
package com.sparrowwallet.sparrow.paynym; |
|||
|
|||
import com.sparrowwallet.sparrow.AppServices; |
|||
import com.sparrowwallet.sparrow.EventManager; |
@ -0,0 +1,259 @@ |
|||
package com.sparrowwallet.sparrow.paynym; |
|||
|
|||
import com.google.common.net.HostAndPort; |
|||
import com.samourai.http.client.HttpUsage; |
|||
import com.samourai.http.client.IHttpClient; |
|||
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException; |
|||
import com.sparrowwallet.drongo.bip47.PaymentCode; |
|||
import com.sparrowwallet.drongo.crypto.ChildNumber; |
|||
import com.sparrowwallet.drongo.crypto.ECKey; |
|||
import com.sparrowwallet.drongo.protocol.ScriptType; |
|||
import com.sparrowwallet.drongo.wallet.Keystore; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.nightjar.http.JavaHttpClientService; |
|||
import io.reactivex.Observable; |
|||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler; |
|||
import io.reactivex.schedulers.Schedulers; |
|||
import java8.util.Optional; |
|||
import javafx.concurrent.Service; |
|||
import javafx.concurrent.Task; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
public class PayNymService { |
|||
private static final Logger log = LoggerFactory.getLogger(PayNymService.class); |
|||
|
|||
private final JavaHttpClientService httpClientService; |
|||
|
|||
public PayNymService(HostAndPort torProxy) { |
|||
this.httpClientService = new JavaHttpClientService(torProxy); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> createPayNym(Wallet wallet) { |
|||
return createPayNym(getPaymentCode(wallet)); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) { |
|||
if(paymentCode == null) { |
|||
throw new IllegalStateException("Payment code is null"); |
|||
} |
|||
|
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("code", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/create", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) { |
|||
if(paymentCode == null) { |
|||
throw new IllegalStateException("Payment code is null"); |
|||
} |
|||
|
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("code", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/token", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public void claimPayNym(Wallet wallet, Map<String, Object> createMap, boolean segwit) { |
|||
if(createMap.get("claimed") == Boolean.FALSE) { |
|||
getAuthToken(wallet, createMap).subscribe(authToken -> { |
|||
String signature = getSignature(wallet, authToken); |
|||
claimPayNym(authToken, signature).subscribe(claimMap -> { |
|||
log.debug("Claimed payment code " + claimMap.get("claimed")); |
|||
addPaymentCode(getPaymentCode(wallet), authToken, signature, segwit).subscribe(addMap -> { |
|||
log.debug("Added payment code " + addMap); |
|||
}); |
|||
}, error -> { |
|||
getAuthToken(wallet, new HashMap<>()).subscribe(newAuthToken -> { |
|||
String newSignature = getSignature(wallet, newAuthToken); |
|||
claimPayNym(newAuthToken, newSignature).subscribe(claimMap -> { |
|||
log.debug("Claimed payment code " + claimMap.get("claimed")); |
|||
addPaymentCode(getPaymentCode(wallet), newAuthToken, newSignature, segwit).subscribe(addMap -> { |
|||
log.debug("Added payment code " + addMap); |
|||
}); |
|||
}, newError -> { |
|||
log.error("Error claiming PayNym with new authToken", newError); |
|||
}); |
|||
}, newError -> { |
|||
log.error("Error retrieving new authToken", newError); |
|||
}); |
|||
}); |
|||
}, error -> { |
|||
log.error("Error retrieving authToken", error); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private Observable<Map<String, Object>> claimPayNym(String authToken, String signature) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("signature", signature); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/claim", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) { |
|||
String strPaymentCode; |
|||
try { |
|||
strPaymentCode = segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString(); |
|||
} catch(InvalidPaymentCodeException e) { |
|||
log.warn("Error creating segwit enabled payment code", e); |
|||
strPaymentCode = paymentCode.toString(); |
|||
} |
|||
|
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("nym", paymentCode.toString()); |
|||
body.put("code", strPaymentCode); |
|||
body.put("signature", signature); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/nym/add", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> followPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode, String authToken, String signature) { |
|||
return followPaymentCode(PaymentCode.fromString(paymentCode.toString()), authToken, signature); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("signature", signature); |
|||
body.put("target", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/follow", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> fetchPayNym(String nymIdentifier) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("nym", nymIdentifier); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/nym", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<PayNym> getPayNym(String nymIdentifier) { |
|||
return fetchPayNym(nymIdentifier).map(nymMap -> { |
|||
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes"); |
|||
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code"))); |
|||
|
|||
List<Map<String, Object>> followingMaps = (List<Map<String, Object>>)nymMap.get("following"); |
|||
List<PayNym> following = followingMaps.stream().map(followingMap -> { |
|||
return PayNym.fromString((String)followingMap.get("code"), (String)followingMap.get("nymId"), (String)followingMap.get("nymName"), (Boolean)followingMap.get("segwit"), Collections.emptyList(), Collections.emptyList()); |
|||
}).collect(Collectors.toList()); |
|||
|
|||
List<Map<String, Object>> followersMaps = (List<Map<String, Object>>)nymMap.get("followers"); |
|||
List<PayNym> followers = followersMaps.stream().map(followerMap -> { |
|||
return PayNym.fromString((String)followerMap.get("code"), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"), Collections.emptyList(), Collections.emptyList()); |
|||
}).collect(Collectors.toList()); |
|||
|
|||
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"), following, followers); |
|||
}); |
|||
} |
|||
|
|||
public Observable<String> getAuthToken(Wallet wallet, Map<String, Object> map) { |
|||
if(map.containsKey("token")) { |
|||
return Observable.just((String)map.get("token")); |
|||
} |
|||
|
|||
return updateToken(wallet).map(tokenMap -> (String)tokenMap.get("token")); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> updateToken(Wallet wallet) { |
|||
return updateToken(getPaymentCode(wallet)); |
|||
} |
|||
|
|||
public String getSignature(Wallet wallet, String authToken) { |
|||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); |
|||
Keystore keystore = masterWallet.getKeystores().get(0); |
|||
List<ChildNumber> derivation = keystore.getKeyDerivation().getDerivation(); |
|||
ChildNumber derivationStart = derivation.isEmpty() ? ChildNumber.ZERO_HARDENED : derivation.get(derivation.size() - 1); |
|||
ECKey notificationPrivKey = keystore.getBip47ExtendedPrivateKey().getKey(List.of(derivationStart, new ChildNumber(0))); |
|||
return notificationPrivKey.signMessage(authToken, ScriptType.P2PKH); |
|||
} |
|||
|
|||
private PaymentCode getPaymentCode(Wallet wallet) { |
|||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); |
|||
return masterWallet.getPaymentCode(); |
|||
} |
|||
|
|||
public HostAndPort getTorProxy() { |
|||
return httpClientService.getTorProxy(); |
|||
} |
|||
|
|||
public void setTorProxy(HostAndPort torProxy) { |
|||
//Ensure all http clients are shutdown first
|
|||
httpClientService.shutdown(); |
|||
httpClientService.setTorProxy(torProxy); |
|||
} |
|||
|
|||
public void shutdown() { |
|||
httpClientService.shutdown(); |
|||
} |
|||
|
|||
public static class ShutdownService extends Service<Boolean> { |
|||
private final PayNymService payNymService; |
|||
|
|||
public ShutdownService(PayNymService payNymService) { |
|||
this.payNymService = payNymService; |
|||
} |
|||
|
|||
@Override |
|||
protected Task<Boolean> createTask() { |
|||
return new Task<>() { |
|||
protected Boolean call() throws Exception { |
|||
payNymService.shutdown(); |
|||
return true; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -1,142 +0,0 @@ |
|||
package com.sparrowwallet.sparrow.soroban; |
|||
|
|||
import com.samourai.http.client.HttpUsage; |
|||
import com.samourai.http.client.IHttpClient; |
|||
import com.samourai.wallet.bip47.rpc.PaymentCode; |
|||
import com.sparrowwallet.nightjar.http.JavaHttpClientService; |
|||
import io.reactivex.Observable; |
|||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler; |
|||
import io.reactivex.schedulers.Schedulers; |
|||
import java8.util.Optional; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
public class PayNymService { |
|||
private final JavaHttpClientService httpClientService; |
|||
|
|||
public PayNymService(JavaHttpClientService httpClientService) { |
|||
this.httpClientService = httpClientService; |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) { |
|||
if(paymentCode == null) { |
|||
throw new IllegalStateException("Payment code is null"); |
|||
} |
|||
|
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("code", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/create", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) { |
|||
if(paymentCode == null) { |
|||
throw new IllegalStateException("Payment code is null"); |
|||
} |
|||
|
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("code", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/token", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> claimPayNym(String authToken, String signature) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("signature", signature); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/claim", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("nym", paymentCode.toString()); |
|||
body.put("code", segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString()); |
|||
body.put("signature", signature); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/nym/add", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
headers.put("auth-token", authToken); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("signature", signature); |
|||
body.put("target", paymentCode.toString()); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/follow", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<Map<String, Object>> fetchPayNym(String nymIdentifier) { |
|||
Map<String, String> headers = new HashMap<>(); |
|||
headers.put("content-type", "application/json"); |
|||
|
|||
HashMap<String, Object> body = new HashMap<>(); |
|||
body.put("nym", nymIdentifier); |
|||
|
|||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); |
|||
return httpClient.postJson("https://paynym.is/api/v1/nym", Map.class, headers, body) |
|||
.subscribeOn(Schedulers.io()) |
|||
.observeOn(JavaFxScheduler.platform()) |
|||
.map(Optional::get); |
|||
} |
|||
|
|||
public Observable<PayNym> getPayNym(String nymIdentifier) { |
|||
return fetchPayNym(nymIdentifier).map(nymMap -> { |
|||
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes"); |
|||
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code"))); |
|||
|
|||
List<Map<String, Object>> followingMaps = (List<Map<String, Object>>)nymMap.get("following"); |
|||
List<PayNym> following = followingMaps.stream().map(followingMap -> { |
|||
return new PayNym(new PaymentCode((String)followingMap.get("code")), (String)followingMap.get("nymId"), (String)followingMap.get("nymName"), (Boolean)followingMap.get("segwit"), Collections.emptyList(), Collections.emptyList()); |
|||
}).collect(Collectors.toList()); |
|||
|
|||
List<Map<String, Object>> followersMaps = (List<Map<String, Object>>)nymMap.get("followers"); |
|||
List<PayNym> followers = followersMaps.stream().map(followerMap -> { |
|||
return new PayNym(new PaymentCode((String)followerMap.get("code")), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"), Collections.emptyList(), Collections.emptyList()); |
|||
}).collect(Collectors.toList()); |
|||
|
|||
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"), following, followers); |
|||
}); |
|||
} |
|||
} |
Loading…
Reference in new issue