Property | Value |
';
@@ -163,7 +151,7 @@
html += '';
}
- if (p == "generator") {
+ if (p == "generatorAddress") {
html += addressAsLink(blockData[p]);
} else {
html += blockData[p];
@@ -200,12 +188,11 @@
var ourHeight = blockData.height;
var blockTimestamp = new Date(blockData.timestamp).toUTCString();
- var blockGeneratorAddress = publicKeyToAddress(base64ToArray(blockData.generatorPublicKey)); // currently base64 but likely to be base58 in the future
var ourRow = document.createElement('TR');
ourRow.innerHTML = ' | ' + ourHeight + ' | ' +
'' + blockTimestamp + ' | ' +
- '' + addressAsLink(blockGeneratorAddress) + ' | ' +
+ '' + addressAsLink(blockData.generatorAddress) + ' | ' +
'' + blockData.generatingBalance + ' | ' +
'' + blockData.transactionCount + ' | ' +
'' + blockData.totalFees + ' QORA | ';
diff --git a/pom.xml b/pom.xml
index dcbdfc8a..8418f9b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,7 +80,8 @@
deepLinking: true,
tagsSorter: "alpha",
- operationsSorter: "alpha",
+ operationsSorter:
+ "alpha",
@@ -108,6 +109,34 @@
+
+
+ com.github.bohnman
+ package-info-maven-plugin
+ 1.0.1
+
+
+
+ data.**
+ ${project.basedir}/src/data/package-info.java
+
+
+ api.models**
+ ${project.basedir}/src/data/package-info.java
+
+
+ ${project.basedir}/src
+ ${project.build.directory}/generated-sources/package-info
+
+
+
+
+ generate
+
+
+
+
org.apache.maven.plugins
maven-jar-plugin
@@ -128,7 +157,8 @@
false
-
+
org.webjars:swagger-ui
@@ -175,6 +205,12 @@
+
+
+ com.github.bohnman
+ package-info-maven-plugin
+ 1.0.1
+
org.hsqldb
diff --git a/src/api/AddressesResource.java b/src/api/AddressesResource.java
index 7fdc768f..f3dd6454 100644
--- a/src/api/AddressesResource.java
+++ b/src/api/AddressesResource.java
@@ -11,7 +11,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.math.BigDecimal;
-import java.util.Base64;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -31,6 +30,7 @@ import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
import transform.Transformer;
+import utils.Base58;
@Path("addresses")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
@@ -48,7 +48,7 @@ public class AddressesResource {
@Path("/lastreference/{address}")
@Operation(
summary = "Fetch reference for next transaction to be created by address",
- description = "Returns the base64-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.",
+ description = "Returns the base58-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.",
extensions = {
@Extension(name = "translation", properties = {
@ExtensionProperty(name="path", value="GET lastreference:address"),
@@ -60,7 +60,7 @@ public class AddressesResource {
},
responses = {
@ApiResponse(
- description = "the base64-encoded transaction signature or \"false\"",
+ description = "the base58-encoded transaction signature or \"false\"",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
extensions = {
@Extension(name = "translation", properties = {
@@ -87,7 +87,7 @@ public class AddressesResource {
if(lastReference == null || lastReference.length == 0) {
return "false";
} else {
- return Base64.getEncoder().encodeToString(lastReference);
+ return Base58.encode(lastReference);
}
}
@@ -95,7 +95,7 @@ public class AddressesResource {
@Path("/lastreference/{address}/unconfirmed")
@Operation(
summary = "Fetch reference for next transaction to be created by address, considering unconfirmed transactions",
- description = "Returns the base64-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.",
+ description = "Returns the base58-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.",
extensions = {
@Extension(name = "translation", properties = {
@ExtensionProperty(name="path", value="GET lastreference:address:unconfirmed"),
@@ -107,7 +107,7 @@ public class AddressesResource {
},
responses = {
@ApiResponse(
- description = "the base64-encoded transaction signature",
+ description = "the base58-encoded transaction signature",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
extensions = {
@Extension(name = "translation", properties = {
@@ -134,7 +134,7 @@ public class AddressesResource {
if(lastReference == null || lastReference.length == 0) {
return "false";
} else {
- return Base64.getEncoder().encodeToString(lastReference);
+ return Base58.encode(lastReference);
}
}
@@ -356,7 +356,7 @@ public class AddressesResource {
@Path("/publickey/{address}")
@Operation(
summary = "Get public key of address",
- description = "Returns the base64-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
+ description = "Returns the base58-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
extensions = {
@Extension(name = "translation", properties = {
@ExtensionProperty(name="path", value="GET publickey:address"),
@@ -392,7 +392,7 @@ public class AddressesResource {
if (publicKey == null)
return "false";
- return Base64.getEncoder().encodeToString(publicKey);
+ return Base58.encode(publicKey);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
@@ -404,7 +404,7 @@ public class AddressesResource {
@Path("/convert/{publickey}")
@Operation(
summary = "Convert public key into address",
- description = "Returns account address based on supplied public key. Expects base64-encoded, 32-byte public key.",
+ description = "Returns account address based on supplied public key. Expects base58-encoded, 32-byte public key.",
extensions = {
@Extension(name = "translation", properties = {
@ExtensionProperty(name="path", value="GET publickey:address"),
@@ -426,21 +426,21 @@ public class AddressesResource {
)
}
)
- public String fromPublicKey(@PathParam("publickey") String publicKey) {
+ public String fromPublicKey(@PathParam("publickey") String publicKey58) {
// Decode public key
- byte[] publicKeyBytes;
+ byte[] publicKey;
try {
- publicKeyBytes = Base64.getDecoder().decode(publicKey);
+ publicKey = Base58.decode(publicKey58);
} catch (NumberFormatException e) {
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY, e);
}
// Correct size for public key?
- if (publicKeyBytes.length != Transformer.PUBLIC_KEY_LENGTH)
+ if (publicKey.length != Transformer.PUBLIC_KEY_LENGTH)
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY);
try (final Repository repository = RepositoryManager.getRepository()) {
- return Crypto.toAddress(publicKeyBytes);
+ return Crypto.toAddress(publicKey);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
diff --git a/src/api/ApiDefinition.java b/src/api/ApiDefinition.java
index b91d1b63..fbd8e3a2 100644
--- a/src/api/ApiDefinition.java
+++ b/src/api/ApiDefinition.java
@@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.tags.Tag;
@OpenAPIDefinition(
- info = @Info( title = "Qora API", description = "NOTE: byte-arrays are encoded in Base64" ),
+ info = @Info( title = "Qora API", description = "NOTE: byte-arrays are encoded in Base58" ),
tags = {
@Tag(name = "Addresses"),
@Tag(name = "Admin"),
diff --git a/src/api/AssetsResource.java b/src/api/AssetsResource.java
index 1e0a2552..b5202646 100644
--- a/src/api/AssetsResource.java
+++ b/src/api/AssetsResource.java
@@ -11,9 +11,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
+import utils.Base58;
import java.util.ArrayList;
-import java.util.Base64;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -196,11 +196,11 @@ public class AssetsResource {
)
}
)
- public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId64) {
+ public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) {
// Decode orderID
byte[] orderId;
try {
- orderId = Base64.getDecoder().decode(orderId64);
+ orderId = Base58.decode(orderId58);
} catch (NumberFormatException e) {
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ORDER_ID, e);
}
diff --git a/src/api/Base58TypeAdapter.java b/src/api/Base58TypeAdapter.java
new file mode 100644
index 00000000..4a55565b
--- /dev/null
+++ b/src/api/Base58TypeAdapter.java
@@ -0,0 +1,25 @@
+package api;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+import org.bitcoinj.core.Base58;
+
+public class Base58TypeAdapter extends XmlAdapter {
+
+ @Override
+ public byte[] unmarshal(String input) throws Exception {
+ if (input == null)
+ return null;
+
+ return Base58.decode(input);
+ }
+
+ @Override
+ public String marshal(byte[] input) throws Exception {
+ if (input == null)
+ return null;
+
+ return Base58.encode(input);
+ }
+
+}
diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java
index 450adb1e..cae882e5 100644
--- a/src/api/BlocksResource.java
+++ b/src/api/BlocksResource.java
@@ -2,7 +2,6 @@ package api;
import data.block.BlockData;
import data.transaction.TransactionData;
-import globalization.Translator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
@@ -14,7 +13,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
@@ -33,63 +31,96 @@ import qora.block.Block;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
+import utils.Base58;
@Path("blocks")
-@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
-@Extension(name = "translation", properties = { @ExtensionProperty(name = "path", value = "/Api/BlocksResource") })
-@Tag(name = "Blocks")
+@Produces({
+ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
+})
+@Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "/Api/BlocksResource"
+ )
+ }
+)
+@Tag(
+ name = "Blocks"
+)
public class BlocksResource {
@Context
HttpServletRequest request;
- private ApiErrorFactory apiErrorFactory;
-
- public BlocksResource() {
- this(new ApiErrorFactory(Translator.getInstance()));
- }
-
- public BlocksResource(ApiErrorFactory apiErrorFactory) {
- this.apiErrorFactory = apiErrorFactory;
- }
-
@GET
@Path("/signature/{signature}")
@Operation(
- summary = "Fetch block using base64 signature",
+ summary = "Fetch block using base58 signature",
description = "Returns the block that matches the given signature",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET signature"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BlockWithTransactions getBlock(@PathParam("signature") String signature,
- @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
+ public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter(
+ ref = "includeTransactions"
+ ) @QueryParam("includeTransactions") boolean includeTransactions) {
// Decode signature
- byte[] signatureBytes;
+ byte[] signature;
try {
- signatureBytes = Base64.getDecoder().decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
+ BlockData blockData = repository.getBlockRepository().fromSignature(signature);
return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -100,25 +131,48 @@ public class BlocksResource {
description = "Returns the genesis block",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET first"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET first"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
+ public BlockWithTransactions getFirstBlock(@Parameter(
+ ref = "includeTransactions"
+ ) @QueryParam("includeTransactions") boolean includeTransactions) {
try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().fromHeight(1);
return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -129,73 +183,126 @@ public class BlocksResource {
description = "Returns the last valid block",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET last"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET last"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
+ public BlockWithTransactions getLastBlock(@Parameter(
+ ref = "includeTransactions"
+ ) @QueryParam("includeTransactions") boolean includeTransactions) {
try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().getLastBlock();
return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@GET
@Path("/child/{signature}")
@Operation(
- summary = "Fetch child block using base64 signature of parent block",
+ summary = "Fetch child block using base58 signature of parent block",
description = "Returns the child block of the block that matches the given signature",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET child:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET child:signature"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BlockWithTransactions getChild(@PathParam("signature") String signature,
- @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
+ public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter(
+ ref = "includeTransactions"
+ ) @QueryParam("includeTransactions") boolean includeTransactions) {
// Decode signature
- byte[] signatureBytes;
+ byte[] signature;
try {
- signatureBytes = Base64.getDecoder().decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
+ BlockData blockData = repository.getBlockRepository().fromSignature(signature);
// Check block exists
if (blockData == null)
- throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
- BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes);
+ BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
// Checking child exists is handled by packageBlockData()
return packageBlockData(repository, childBlockData, includeTransactions);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -206,16 +313,38 @@ public class BlocksResource {
description = "Calculates the generating balance of the block that will follow the last block",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET generatingbalance"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the generating balance",
- content = @Content(schema = @Schema(implementation = BigDecimal.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the generating balance",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ implementation = BigDecimal.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
public BigDecimal getGeneratingBalance() {
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -225,7 +354,7 @@ public class BlocksResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -234,44 +363,73 @@ public class BlocksResource {
@Operation(
summary = "Generating balance of block after specific block",
description = "Calculates the generating balance of the block that will follow the block that matches the signature",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET generatingbalance:signature"),
- @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BigDecimal.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET generatingbalance:signature"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ implementation = BigDecimal.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BigDecimal getGeneratingBalance(@PathParam("signature") String signature) {
+ public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) {
// Decode signature
- byte[] signatureBytes;
+ byte[] signature;
try {
- signatureBytes = Base64.getDecoder().decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
+ BlockData blockData = repository.getBlockRepository().fromSignature(signature);
// Check block exists
if (blockData == null)
- throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
Block block = new Block(repository, blockData);
return block.calcNextBlockGeneratingBalance();
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -282,16 +440,39 @@ public class BlocksResource {
description = "Calculates the time it should take for the network to generate the next block",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET time"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET time"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the time in seconds", // in seconds?
- content = @Content(schema = @Schema(implementation = long.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the time in seconds", // in
+ // seconds?
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "number"
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
public long getTimePerBlock() {
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -300,7 +481,7 @@ public class BlocksResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -311,18 +492,40 @@ public class BlocksResource {
description = "Calculates the time it should take for the network to generate blocks based on specified generating balance",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET time:generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET time:generatingbalance"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the time", // in seconds?
- content = @Content(schema = @Schema(implementation = long.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the time", // in seconds?
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "number"
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) {
+ public long getTimePerBlock(@PathParam("generatingbalance") BigDecimal generatingbalance) {
return Block.calcForgingDelay(generatingbalance);
}
@@ -333,16 +536,38 @@ public class BlocksResource {
description = "Returns the block height of the last block.",
extensions = @Extension(
name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET height"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
),
- responses =
- { @ApiResponse(
- description = "the height",
- content = @Content(schema = @Schema(implementation = int.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ responses = {
+ @ApiResponse(
+ description = "the height",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "number"
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
public int getHeight() {
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -350,7 +575,7 @@ public class BlocksResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -359,42 +584,72 @@ public class BlocksResource {
@Operation(
summary = "Height of specific block",
description = "Returns the block height of the block that matches the given signature",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET height:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "the height",
- content = @Content(schema = @Schema(implementation = int.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET height:signature"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "the height",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "number"
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public int getHeight(@PathParam("signature") String signature) {
+ public int getHeight(@PathParam("signature") String signature58) {
// Decode signature
- byte[] signatureBytes;
+ byte[] signature;
try {
- signatureBytes = Base64.getDecoder().decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
+ BlockData blockData = repository.getBlockRepository().fromSignature(signature);
// Check block exists
if (blockData == null)
- throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
return blockData.getHeight();
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -403,30 +658,60 @@ public class BlocksResource {
@Operation(
summary = "Fetch block using block height",
description = "Returns the block with given height",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "the block",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET byheight:height"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "the block",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public BlockWithTransactions getByHeight(@PathParam("height") int height,
- @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
+ public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter(
+ ref = "includeTransactions"
+ ) @QueryParam("includeTransactions") boolean includeTransactions) {
try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().fromHeight(height);
return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -435,22 +720,53 @@ public class BlocksResource {
@Operation(
summary = "Fetch blocks starting with given height",
description = "Returns blocks starting with given height.",
- extensions =
- { @Extension(
- name = "translation",
- properties =
- { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
- ), @Extension(properties =
- { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
- responses =
- { @ApiResponse(
- description = "blocks",
- content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
- extensions =
- { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
- ) }
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "path",
+ value = "GET byheight:height"
+ ), @ExtensionProperty(
+ name = "description.key",
+ value = "operation:description"
+ )
+ }
+ ), @Extension(
+ properties = {
+ @ExtensionProperty(
+ name = "apiErrors",
+ value = "[\"BLOCK_NO_EXISTS\"]",
+ parseValue = true
+ ),
+ }
+ )
+ },
+ responses = {
+ @ApiResponse(
+ description = "blocks",
+ content = @Content(
+ schema = @Schema(
+ implementation = BlockWithTransactions.class
+ )
+ ),
+ extensions = {
+ @Extension(
+ name = "translation",
+ properties = {
+ @ExtensionProperty(
+ name = "description.key",
+ value = "success_response:description"
+ )
+ }
+ )
+ }
+ )
+ }
)
- public List getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) {
+ public List getBlockRange(@PathParam("height") int height, @Parameter(
+ ref = "count"
+ ) @QueryParam("count") int count) {
boolean includeTransactions = false;
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -469,13 +785,13 @@ public class BlocksResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException {
if (blockData == null)
- throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
List transactions = null;
if (includeTransactions) {
diff --git a/src/api/models/TransactionClassExtractor.java b/src/api/TransactionClassExtractor.java
similarity index 95%
rename from src/api/models/TransactionClassExtractor.java
rename to src/api/TransactionClassExtractor.java
index 250caf3b..3c58343e 100644
--- a/src/api/models/TransactionClassExtractor.java
+++ b/src/api/TransactionClassExtractor.java
@@ -1,4 +1,4 @@
-package api.models;
+package api;
import org.eclipse.persistence.descriptors.ClassExtractor;
import org.eclipse.persistence.sessions.Record;
diff --git a/src/api/TransactionsResource.java b/src/api/TransactionsResource.java
index 39505032..06370c3c 100644
--- a/src/api/TransactionsResource.java
+++ b/src/api/TransactionsResource.java
@@ -1,6 +1,5 @@
package api;
-import globalization.Translator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
@@ -13,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import qora.transaction.Transaction.TransactionType;
import java.util.ArrayList;
-import java.util.Base64;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -45,26 +43,16 @@ public class TransactionsResource {
@Context
HttpServletRequest request;
- private ApiErrorFactory apiErrorFactory;
-
- public TransactionsResource() {
- this(new ApiErrorFactory(Translator.getInstance()));
- }
-
- public TransactionsResource(ApiErrorFactory apiErrorFactory) {
- this.apiErrorFactory = apiErrorFactory;
- }
-
@GET
@Path("/signature/{signature}")
@Operation(
summary = "Fetch transaction using transaction signature",
description = "Returns transaction",
extensions = {
- @Extension(properties = {
- @ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"TRANSACTION_NO_EXISTS\"]", parseValue = true),
- })
- },
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"TRANSACTION_NO_EXISTS\"]", parseValue = true),
+ })
+ },
responses = {
@ApiResponse(
description = "a transaction",
@@ -77,25 +65,24 @@ public class TransactionsResource {
)
}
)
- public TransactionData getTransactions(@PathParam("signature") String signature) {
- // Decode signature
- byte[] signatureBytes;
+ public TransactionData getTransactions(@PathParam("signature") String signature58) {
+ byte[] signature;
try {
- signatureBytes = Base64.getDecoder().decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- TransactionData transactionData = repository.getTransactionRepository().fromSignature(signatureBytes);
+ TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
if (transactionData == null)
- throw this.apiErrorFactory.createError(ApiError.TRANSACTION_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.TRANSACTION_NO_EXISTS);
return transactionData;
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -127,21 +114,20 @@ public class TransactionsResource {
)
}
)
- public List getBlockTransactions(@PathParam("signature") String signature, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
- // decode signature
- byte[] signatureBytes;
+ public List getBlockTransactions(@PathParam("signature") String signature58, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
+ byte[] signature;
try {
- signatureBytes = Base58.decode(signature);
+ signature = Base58.decode(signature58);
} catch (NumberFormatException e) {
- throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
- List transactions = repository.getBlockRepository().getTransactionsFromSignature(signatureBytes);
+ List transactions = repository.getBlockRepository().getTransactionsFromSignature(signature);
// check if block exists
if(transactions == null)
- throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
+ throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
// Pagination would take effect here (or as part of the repository access)
int fromIndex = Integer.min(offset, transactions.size());
@@ -152,7 +138,7 @@ public class TransactionsResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -179,7 +165,7 @@ public class TransactionsResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@@ -211,13 +197,13 @@ public class TransactionsResource {
public List searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
@QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
- throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
TransactionType txType = null;
if (txTypeNum != null) {
txType = TransactionType.valueOf(txTypeNum);
if (txType == null)
- throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA);
+ throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
}
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -237,7 +223,7 @@ public class TransactionsResource {
} catch (ApiException e) {
throw e;
} catch (DataException e) {
- throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
+ throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
diff --git a/src/api/UtilsResource.java b/src/api/UtilsResource.java
index 86602bc1..c935a64c 100644
--- a/src/api/UtilsResource.java
+++ b/src/api/UtilsResource.java
@@ -3,24 +3,36 @@ package api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
+import qora.crypto.Crypto;
import utils.Base58;
+import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Base64;
+import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import com.google.common.primitives.Bytes;
+import com.google.common.primitives.Longs;
+
@Path("/utils")
-@Produces({MediaType.TEXT_PLAIN})
-@Tag(name = "Utilities")
+@Produces({
+ MediaType.TEXT_PLAIN
+})
+@Tag(
+ name = "Utilities"
+)
public class UtilsResource {
@Context
@@ -33,7 +45,11 @@ public class UtilsResource {
responses = {
@ApiResponse(
description = "base58 data",
- content = @Content(schema = @Schema(implementation = String.class))
+ content = @Content(
+ schema = @Schema(
+ implementation = String.class
+ )
+ )
)
}
)
@@ -52,7 +68,11 @@ public class UtilsResource {
responses = {
@ApiResponse(
description = "base64 data",
- content = @Content(schema = @Schema(implementation = String.class))
+ content = @Content(
+ schema = @Schema(
+ implementation = String.class
+ )
+ )
)
}
)
@@ -70,15 +90,103 @@ public class UtilsResource {
summary = "Generate random 32-byte seed",
responses = {
@ApiResponse(
- description = "base64 data",
- content = @Content(schema = @Schema(implementation = String.class))
+ description = "base58 data",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "string"
+ )
+ )
)
}
)
public String seed() {
byte[] seed = new byte[32];
new SecureRandom().nextBytes(seed);
- return Base64.getEncoder().encodeToString(seed);
+ return Base58.encode(seed);
+ }
+
+ @GET
+ @Path("/seedPhrase")
+ @Operation(
+ summary = "Generate random 12-word BIP39 seed phrase",
+ responses = {
+ @ApiResponse(
+ description = "seed phrase",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "string"
+ )
+ )
+ )
+ }
+ )
+ public String seedPhrase() {
+ /*
+ * BIP39 word lists have 2048 entries so can be represented by 11 bits.
+ * UUID (128bits) and another 4 bits gives 132 bits.
+ * 132 bits, divided by 11, gives 12 words.
+ */
+ final int WORD_MASK = 2048 - 1;
+
+ UUID uuid = UUID.randomUUID();
+
+ System.out.println("UUID: " + uuid.toString());
+
+ byte[] uuidMSB = Longs.toByteArray(uuid.getMostSignificantBits());
+ byte[] uuidLSB = Longs.toByteArray(uuid.getLeastSignificantBits());
+ byte[] message = Bytes.concat(uuidMSB, uuidLSB);
+
+ // Use SHA256 to generate more bits
+ byte[] hash = Crypto.digest(message);
+
+ // Append last 4 bits from hash to end. (Actually 8 bits but we only use 4).
+ message = Bytes.concat(message, new byte[] {
+ hash[hash.length - 1]
+ });
+
+ BigInteger wordBits = new BigInteger(message);
+
+ String[] phraseWords = new String[12];
+ for (int i = phraseWords.length; i >= 0; --i) {
+ int wordListIndex = wordBits.intValue() & WORD_MASK;
+ wordBits = wordBits.shiftRight(11);
+ // phraseWords[i] = wordList.get(wordListIndex);
+ }
+
+ return String.join(" ", phraseWords);
+ }
+
+ @POST
+ @Path("/privateKey")
+ @Operation(
+ summary = "Calculate private key from 12-word BIP39 seed phrase",
+ description = "Returns the base58-encoded private key, or \"false\" if phrase is invalid.",
+ requestBody = @RequestBody(
+ required = true,
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "string"
+ )
+ )
+ ),
+ responses = {
+ @ApiResponse(
+ description = "the private key",
+ content = @Content(
+ mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(
+ type = "string"
+ )
+ )
+ )
+ }
+ )
+ public String getPublicKey(String seedPhrase) {
+ // TODO: convert BIP39 seed phrase to private key
+ return seedPhrase;
}
}
diff --git a/src/data/block/BlockData.java b/src/data/block/BlockData.java
index 4b794426..15a40c0b 100644
--- a/src/data/block/BlockData.java
+++ b/src/data/block/BlockData.java
@@ -2,11 +2,14 @@ package data.block;
import com.google.common.primitives.Bytes;
+import qora.crypto.Crypto;
+
import java.io.Serializable;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
// All properties to be converted to JSON via JAX-RS
@XmlAccessorType(XmlAccessType.FIELD)
@@ -14,6 +17,8 @@ public class BlockData implements Serializable {
private static final long serialVersionUID = -7678329659124664620L;
+ // Properties
+
private byte[] signature;
private int version;
private byte[] reference;
@@ -28,6 +33,8 @@ public class BlockData implements Serializable {
private int atCount;
private BigDecimal atFees;
+ // Constructors
+
// necessary for JAX-RS serialization
protected BlockData() {
}
@@ -53,6 +60,8 @@ public class BlockData implements Serializable {
this.signature = null;
}
+ // Getters/setters
+
public byte[] getSignature() {
return this.signature;
}
@@ -141,4 +150,11 @@ public class BlockData implements Serializable {
this.atFees = atFees;
}
+ // JAXB special
+
+ @XmlElement(name = "generatorAddress")
+ protected String getGeneratorAddress() {
+ return Crypto.toAddress(this.generatorPublicKey);
+ }
+
}
diff --git a/src/data/package-info.java b/src/data/package-info.java
new file mode 100644
index 00000000..27ea8739
--- /dev/null
+++ b/src/data/package-info.java
@@ -0,0 +1,6 @@
+// This file (data/package-info.java) is used as a template!
+
+@XmlJavaTypeAdapter(type = byte[].class, value = api.Base58TypeAdapter.class)
+package data;
+
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
diff --git a/src/data/transaction/TransactionData.java b/src/data/transaction/TransactionData.java
index 0eb423e3..2b0b2cc4 100644
--- a/src/data/transaction/TransactionData.java
+++ b/src/data/transaction/TransactionData.java
@@ -6,11 +6,14 @@ import java.util.Arrays;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlTransient;
import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
-import api.models.TransactionClassExtractor;
+import api.TransactionClassExtractor;
+import qora.crypto.Crypto;
import qora.transaction.Transaction.TransactionType;
/*
@@ -32,6 +35,7 @@ public abstract class TransactionData {
// Properties shared with all transaction types
protected TransactionType type;
+ @XmlTransient // represented in transaction-specific properties
protected byte[] creatorPublicKey;
protected long timestamp;
protected byte[] reference;
@@ -87,6 +91,13 @@ public abstract class TransactionData {
this.signature = signature;
}
+ // JAXB special
+
+ @XmlElement(name = "creatorAddress")
+ protected String getCreatorAddress() {
+ return Crypto.toAddress(this.creatorPublicKey);
+ }
+
// Comparison
@Override