Skip to content

Commit

Permalink
Add TokenScript signature check API (#3339)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesSmartCell authored Nov 6, 2023
1 parent 20e7815 commit 03c4eba
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ SwapService provideSwapService()

@Singleton
@Provides
AlphaWalletService provideFeemasterService(OkHttpClient okHttpClient, TransactionRepositoryType transactionRepository, Gson gson)
AlphaWalletService provideFeemasterService(OkHttpClient okHttpClient, Gson gson)
{
return new AlphaWalletService(okHttpClient, transactionRepository, gson);
return new AlphaWalletService(okHttpClient, gson);
}

@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,42 +159,17 @@ public boolean isDebug()
public void determineSignatureType(XMLDsigDescriptor sigDescriptor)
{
boolean isDebug = isDebug();
if (sigDescriptor.result.equals("pass"))
{
if (isDebug) sigDescriptor.type = SigReturnType.DEBUG_SIGNATURE_PASS;
else sigDescriptor.type = SigReturnType.SIGNATURE_PASS;
}
else
{
setFailedIssuer(isDebug, sigDescriptor);
}
}

private void setFailedIssuer(boolean isDebug, XMLDsigDescriptor sigDescriptor)
{
String keyName;
if (isDebug)
{
sigDescriptor.keyName = context.getString(R.string.debug_script);
keyName = context.getString(R.string.debug_script);
}
else
{
sigDescriptor.keyName = context.getString(R.string.unsigned_script);
keyName = context.getString(R.string.unsigned_script);
}

if (sigDescriptor.subject != null && sigDescriptor.subject.contains("Invalid"))
{
if (isDebug)
sigDescriptor.type = SigReturnType.DEBUG_SIGNATURE_INVALID;
else
sigDescriptor.type = SigReturnType.SIGNATURE_INVALID;
}
else
{
if (isDebug)
sigDescriptor.type = SigReturnType.DEBUG_NO_SIGNATURE;
else
sigDescriptor.type = SigReturnType.NO_SIGNATURE;
}
sigDescriptor.setKeyDetails(isDebug, keyName);
}

public boolean fileChanged(String fileHash)
Expand Down
171 changes: 134 additions & 37 deletions app/src/main/java/com/alphawallet/app/service/AlphaWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,79 @@
import static com.alphawallet.app.entity.CryptoFunctions.sigFromByteArray;
import static com.alphawallet.token.tools.ParseMagicLink.currencyLink;
import static com.alphawallet.token.tools.ParseMagicLink.spawnable;
import static org.web3j.protocol.http.HttpService.JSON_MEDIA_TYPE;

import android.util.Base64;

import com.alphawallet.app.entity.CryptoFunctions;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.entity.tokens.Ticket;
import com.alphawallet.app.repository.EthereumNetworkRepository;
import com.alphawallet.app.repository.TransactionRepositoryType;
import com.alphawallet.app.util.Utils;
import com.alphawallet.token.entity.MagicLinkData;
import com.alphawallet.token.entity.XMLDsigDescriptor;
import com.alphawallet.token.tools.ParseMagicLink;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import org.json.JSONObject;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.Single;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;

public class AlphaWalletService
{
private final OkHttpClient httpClient;
private final TransactionRepositoryType transactionRepository;
private final Gson gson;
private ParseMagicLink parser;

private static final String API = "api/";
private static final String XML_VERIFIER_ENDPOINT = "https://aw.app/api/v1/verifyXMLDSig";
private static final String TSML_VERIFIER_ENDPOINT_STAGING = "https://doobtvjcpb8dc.cloudfront.net/tokenscript/validate";
private static final String TSML_VERIFIER_ENDPOINT = "https://api.smarttokenlabs.com/";
private static final String XML_VERIFIER_PASS = "pass";
private static final MediaType MEDIA_TYPE_TOKENSCRIPT
= MediaType.parse("text/xml; charset=UTF-8");

public AlphaWalletService(OkHttpClient httpClient,
TransactionRepositoryType transactionRepository,
Gson gson) {
this.httpClient = httpClient;
this.transactionRepository = transactionRepository;
this.gson = gson;
}

private void initParser()
private static class StatusElement
{
if (parser == null)
String type;
String status;
String statusText;
String signingKey;

public XMLDsigDescriptor getXMLDsigDescriptor()
{
parser = new ParseMagicLink(new CryptoFunctions(), EthereumNetworkRepository.extraChains());
XMLDsigDescriptor sig = new XMLDsigDescriptor();
sig.result = status;
sig.issuer = signingKey;
sig.certificateName = statusText;
sig.keyType = type;

return sig;
}
}

Expand All @@ -82,47 +98,89 @@ public Observable<Integer> handleFeemasterImport(String url, Wallet wallet, long

/**
* Use API to determine tokenscript validity
* @param tokenScriptFile
* @param scriptUri
* @param chainId
* @param address
* @return
*/
public XMLDsigDescriptor checkTokenScriptSignature(File tokenScriptFile)
public XMLDsigDescriptor checkTokenScriptSignature(String scriptUri, long chainId, String address)
{
XMLDsigDescriptor dsigDescriptor = new XMLDsigDescriptor();
dsigDescriptor.result = "fail";
try
{
RequestBody body = RequestBody.Companion.create(tokenScriptFile, MEDIA_TYPE_TOKENSCRIPT);
JSONObject jsonObject = new JSONObject();
jsonObject.put("sourceType", "scriptUri");
jsonObject.put("sourceId", chainId + "-" + Keys.toChecksumAddress(address));
jsonObject.put("sourceUrl", scriptUri);
RequestBody body = RequestBody.create(jsonObject.toString(), JSON_MEDIA_TYPE);

RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "tokenscript", body)
.build();
okhttp3.Response response = getTSValidationCheck(body);

Request request = new Request.Builder().url(XML_VERIFIER_ENDPOINT)
.post(requestBody)
.build();
if ((response.code() / 100) == 2)
{
String result = response.body().string();
JsonObject obj = gson.fromJson(result, JsonObject.class);
if (obj.has("error"))
return dsigDescriptor;

okhttp3.Response response = httpClient.newCall(request).execute();
JsonObject overview = obj.getAsJsonObject("overview");
if (overview != null)
{
JsonArray statuses = overview.getAsJsonArray("originStatuses");
if (statuses.size() == 0)
{
return dsigDescriptor;
}

StatusElement status1 = gson.fromJson(statuses.get(0), StatusElement.class);
return status1.getXMLDsigDescriptor();
}
}
}
catch (Exception e)
{
Timber.e(e);
}

return dsigDescriptor;
}

public XMLDsigDescriptor checkTokenScriptSignature(InputStream inputStream, long chainId, String address)
{
XMLDsigDescriptor dsigDescriptor = new XMLDsigDescriptor();
dsigDescriptor.result = "fail";
try
{
JSONObject jsonObject = new JSONObject();
jsonObject.put("sourceType", "scriptUri");
jsonObject.put("sourceId", chainId + "-" + Keys.toChecksumAddress(address));
jsonObject.put("sourceUrl", "");
jsonObject.put("base64Xml", streamToBase64(inputStream));
RequestBody body = RequestBody.create(jsonObject.toString(), JSON_MEDIA_TYPE);

String result = response.body().string();
JsonObject obj = gson.fromJson(result, JsonObject.class);
if (obj.has("error") || !obj.has("result")) return dsigDescriptor;
okhttp3.Response response = getTSValidationCheck(body);

String queryResult = obj.get("result").getAsString();
if (queryResult.equals(XML_VERIFIER_PASS))
if ((response.code() / 100) == 2)
{
dsigDescriptor = gson.fromJson(result, XMLDsigDescriptor.class);
//interpret subject to get the primary certifying body
String[] certifiers = dsigDescriptor.subject.split(",");
if (certifiers[0] != null && certifiers[0].length() > 3 && certifiers[0].startsWith("CN="))
String result = response.body().string();
JsonObject obj = gson.fromJson(result, JsonObject.class);
if (obj.has("error"))
return dsigDescriptor;

JsonObject overview = obj.getAsJsonObject("overview");
if (overview != null)
{
dsigDescriptor.certificateName = certifiers[0].substring(3);
JsonArray statuses = overview.getAsJsonArray("originStatuses");
if (statuses.size() == 0)
{
return dsigDescriptor;
}

StatusElement status1 = gson.fromJson(statuses.get(0), StatusElement.class);
return status1.getXMLDsigDescriptor();
}
}
else
{
dsigDescriptor.subject = obj.get("failureReason").getAsString();
}
}
catch (Exception e)
{
Expand All @@ -132,6 +190,45 @@ public XMLDsigDescriptor checkTokenScriptSignature(File tokenScriptFile)
return dsigDescriptor;
}

private String streamToBase64(InputStream inputStream) throws Exception
{
StringBuilder sb = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, StandardCharsets.UTF_8)))
{
int c;
while ((c = reader.read()) != -1)
{
sb.append((char) c);
}
}

byte[] base64Encoded = Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);

return new String(base64Encoded);
}

private Response getTSValidationCheck(RequestBody body) throws Exception
{
Request request = new Request.Builder().url(TSML_VERIFIER_ENDPOINT)
.post(body)
.build();

okhttp3.Response response = httpClient.newCall(request).execute();

if ((response.code() / 100) != 2)
{
//try staging endpoint
request = new Request.Builder().url(TSML_VERIFIER_ENDPOINT_STAGING)
.post(body)
.build();

response = httpClient.newCall(request).execute();
}

return response;
}

private Observable<Integer> sendFeemasterCurrencyTransaction(String url, long networkId, String address, MagicLinkData order)
{
return Observable.fromCallable(() -> {
Expand Down
Loading

0 comments on commit 03c4eba

Please sign in to comment.