From 3cf0b9d48e8ede73f093facf1c905612204bb352 Mon Sep 17 00:00:00 2001 From: Ivaylo Kirilov Date: Mon, 15 Jul 2019 03:08:46 +0100 Subject: [PATCH] [PAN-2852] privacy group id consistent (#1667) --- .../acceptance/dsl/privacy/PrivacyNode.java | 3 ++- ...t.java => EnclaveErrorAcceptanceTest.java} | 10 +++---- .../web3j/privacy/EventEmitterHarness.java | 15 ++++------- .../tests/web3j/privacy/PrivacyGroup.java | 7 +++-- .../privacy/PrivacyPrecompiledContract.java | 11 +++----- .../privacy/PrivateTransactionHandler.java | 26 +++++++++---------- .../privacy/EeaGetPrivateTransaction.java | 9 +++---- .../privacy/EeaGetTransactionReceipt.java | 8 +++--- .../privacy/EeaGetTransactionCountTest.java | 9 ++++--- .../pantheon/util/bytes/BytesValues.java | 13 ++++++++++ 10 files changed, 56 insertions(+), 55 deletions(-) rename acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/{PrivateTxEnclaveErrorAcceptanceTest.java => EnclaveErrorAcceptanceTest.java} (91%) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java index e9091a4e6f..a66233cd04 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java @@ -30,6 +30,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaGetTransactionCountTransaction; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; import java.io.IOException; import java.util.Arrays; @@ -113,7 +114,7 @@ public void testOrionConnection(final PrivacyNode... otherNodes) { public long nextNonce(final BytesValue privacyGroupId) { return execute( new EeaGetTransactionCountTransaction( - getAddress().toString(), privacyGroupId.toString())) + getAddress().toString(), BytesValues.asBase64String(privacyGroupId))) .longValue(); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateTxEnclaveErrorAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EnclaveErrorAcceptanceTest.java similarity index 91% rename from acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateTxEnclaveErrorAcceptanceTest.java rename to acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EnclaveErrorAcceptanceTest.java index f07621770b..470a6b4094 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateTxEnclaveErrorAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EnclaveErrorAcceptanceTest.java @@ -12,7 +12,6 @@ */ package tech.pegasys.pantheon.tests.web3j.privacy; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static tech.pegasys.pantheon.tests.web3j.privacy.PrivacyGroup.generatePrivacyGroup; @@ -23,6 +22,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder.TransactionType; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; import java.util.Base64; @@ -32,7 +32,7 @@ import org.junit.Before; import org.junit.Test; -public class PrivateTxEnclaveErrorAcceptanceTest extends PrivacyAcceptanceTestBase { +public class EnclaveErrorAcceptanceTest extends PrivacyAcceptanceTestBase { protected static final String CONTRACT_NAME = "Event Emitter"; private EventEmitterHarness eventEmitterHarness; @@ -54,7 +54,8 @@ public void setUp() throws Exception { privateTransactionVerifier, eea); wrongPublicKey = - BytesValue.wrap(Base64.getEncoder().encode(Box.KeyPair.random().publicKey().bytesArray())); + BytesValues.fromBase64( + Base64.getEncoder().encode(Box.KeyPair.random().publicKey().bytesArray())); } @Test @@ -91,8 +92,7 @@ public void enclaveNoPeerUrlError() { .nonce(privacyNet.getNode("Alice").nextNonce(privacyGroup)) .from(privacyNet.getNode("Alice").getAddress()) .privateFrom( - BytesValue.wrap( - privacyNet.getEnclave("Alice").getPublicKeys().get(0).getBytes(UTF_8))) + BytesValues.fromBase64(privacyNet.getEnclave("Alice").getPublicKeys().get(0))) .privateFor(Lists.newArrayList(wrongPublicKey)) .keyPair(privacyNet.getNode("Alice").keyPair()) .build(TransactionType.CREATE_CONTRACT); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EventEmitterHarness.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EventEmitterHarness.java index d1fa3f3cb7..07715f8e58 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EventEmitterHarness.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/EventEmitterHarness.java @@ -12,7 +12,6 @@ */ package tech.pegasys.pantheon.tests.web3j.privacy; -import static java.nio.charset.StandardCharsets.UTF_8; import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor; import static tech.pegasys.pantheon.tests.web3j.privacy.PrivacyGroup.generatePrivacyGroup; @@ -24,6 +23,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.privacy.PrivateTransactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; import java.util.Arrays; import java.util.HashMap; @@ -118,8 +118,7 @@ public void store( .from(privacyNet.getNode(sender).getAddress()) .to(Address.fromHexString(contractAddress)) .privateFrom( - BytesValue.wrap( - privacyNet.getEnclave(sender).getPublicKeys().get(0).getBytes(UTF_8))) + BytesValues.fromBase64(privacyNet.getEnclave(sender).getPublicKeys().get(0))) .privateFor(convertNamesToOrionPublicKeys(receivers)) .keyPair(privacyNet.getNode(sender).keyPair()) .build(PrivateTransactionBuilder.TransactionType.STORE); @@ -159,8 +158,7 @@ public void get( .from(privacyNet.getNode(sender).getAddress()) .to(Address.fromHexString(contractAddress)) .privateFrom( - BytesValue.wrap( - privacyNet.getEnclave(sender).getPublicKeys().get(0).getBytes(UTF_8))) + BytesValues.fromBase64(privacyNet.getEnclave(sender).getPublicKeys().get(0))) .privateFor(convertNamesToOrionPublicKeys(receivers)) .keyPair(privacyNet.getNode(sender).keyPair()) .build(PrivateTransactionBuilder.TransactionType.GET); @@ -190,8 +188,7 @@ private void deploy( .from(privacyNet.getNode(sender).getAddress()) .to(null) .privateFrom( - BytesValue.wrap( - privacyNet.getEnclave(sender).getPublicKeys().get(0).getBytes(UTF_8))) + BytesValues.fromBase64(privacyNet.getEnclave(sender).getPublicKeys().get(0))) .privateFor(convertNamesToOrionPublicKeys(receivers)) .keyPair(privacyNet.getNode(sender).keyPair()) .build(PrivateTransactionBuilder.TransactionType.CREATE_CONTRACT); @@ -229,9 +226,7 @@ private void waitForTransactionToBeMined(final String transactionHash) { private List convertNamesToOrionPublicKeys(final String... toNodeNames) { return Arrays.stream(toNodeNames) - .map( - name -> - BytesValue.wrap(privacyNet.getEnclave(name).getPublicKeys().get(0).getBytes(UTF_8))) + .map(name -> BytesValues.fromBase64(privacyNet.getEnclave(name).getPublicKeys().get(0))) .collect(Collectors.toList()); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyGroup.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyGroup.java index e06f58453f..17b0238e36 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyGroup.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyGroup.java @@ -14,6 +14,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.privacy.PrivacyNet; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; import java.util.ArrayList; import java.util.Arrays; @@ -27,7 +28,6 @@ import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; import org.web3j.rlp.RlpType; -import org.web3j.utils.Numeric; public class PrivacyGroup { public static BytesValue generatePrivacyGroup( @@ -47,8 +47,7 @@ public static BytesValue generatePrivacyGroup( .sorted(Comparator.comparing(Arrays::hashCode)) .map(RlpString::create) .collect(Collectors.toList()); - return BytesValue.fromHexString( - Numeric.toHexString( - Base64.getEncoder().encode(Hash.sha3(RlpEncoder.encode(new RlpList(rlpList)))))); + return BytesValues.fromBase64( + Base64.getEncoder().encode(Hash.sha3(RlpEncoder.encode(new RlpList(rlpList))))); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index bbe6d4e9ba..b3f9cd7f6a 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -39,10 +39,8 @@ import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; -import java.util.Base64; - -import com.google.common.base.Charsets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -107,15 +105,14 @@ public BytesValue compute(final BytesValue input, final MessageFrame messageFram } final BytesValueRLPInput bytesValueRLPInput = - new BytesValueRLPInput( - BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + new BytesValueRLPInput(BytesValues.fromBase64(receiveResponse.getPayload()), false); final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); final WorldUpdater publicWorldState = messageFrame.getWorldState(); - final BytesValue privacyGroupId = - BytesValue.wrap(receiveResponse.getPrivacyGroupId().getBytes(Charsets.UTF_8)); + final BytesValue privacyGroupId = BytesValues.fromBase64(receiveResponse.getPrivacyGroupId()); + // get the last world state root hash - or create a new one final Hash lastRootHash = privateStateStorage.getPrivateAccountState(privacyGroupId).orElse(EMPTY_ROOT_HASH); diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java index 2a97c157c9..119c6c4a09 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java @@ -35,7 +35,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValues; -import java.util.Base64; import java.util.List; import java.util.stream.Collectors; @@ -98,18 +97,18 @@ public String sendToOrion(final PrivateTransaction privateTransaction) throws Ex public String getPrivacyGroup(final String key, final PrivateTransaction privateTransaction) throws Exception { if (privateTransaction.getPrivacyGroupId().isPresent()) { - return privateTransaction.getPrivacyGroupId().get().toString(); + return BytesValues.asBase64String(privateTransaction.getPrivacyGroupId().get()); } final ReceiveRequest receiveRequest = - new ReceiveRequest(key, BytesValues.asString(privateTransaction.getPrivateFrom().get())); + new ReceiveRequest( + key, BytesValues.asBase64String(privateTransaction.getPrivateFrom().get())); LOG.debug( "Getting privacy group for {}", - BytesValues.asString(privateTransaction.getPrivateFrom().get())); + BytesValues.asBase64String(privateTransaction.getPrivateFrom().get())); final ReceiveResponse receiveResponse; try { receiveResponse = enclave.receive(receiveRequest); - return BytesValue.wrap(receiveResponse.getPrivacyGroupId().getBytes(Charsets.UTF_8)) - .toString(); + return receiveResponse.getPrivacyGroupId(); } catch (Exception e) { LOG.error("Failed to retrieve private transaction in enclave", e); throw e; @@ -159,33 +158,34 @@ public ValidationResult validatePrivateTransaction( private SendRequest createSendRequest(final PrivateTransaction privateTransaction) { final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); privateTransaction.writeTo(bvrlp); + final String payload = BytesValues.asBase64String(bvrlp.encoded()); if (privateTransaction.getPrivacyGroupId().isPresent()) { return new SendRequestPantheon( - Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()), + payload, enclavePublicKey, - BytesValues.asString(privateTransaction.getPrivacyGroupId().get())); + BytesValues.asBase64String(privateTransaction.getPrivacyGroupId().get())); } else { final List privateFor = privateTransaction.getPrivateFor().get().stream() - .map(BytesValues::asString) + .map(BytesValues::asBase64String) .collect(Collectors.toList()); // FIXME: orion should accept empty privateFor if (privateFor.isEmpty()) { - privateFor.add(BytesValues.asString(privateTransaction.getPrivateFrom().get())); + privateFor.add(BytesValues.asBase64String(privateTransaction.getPrivateFrom().get())); } return new SendRequestLegacy( - Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()), - BytesValues.asString(privateTransaction.getPrivateFrom().get()), + payload, + BytesValues.asBase64String(privateTransaction.getPrivateFrom().get()), privateFor); } } public long getSenderNonce(final Address sender, final String privacyGroupId) { return privateStateStorage - .getPrivateAccountState(BytesValue.fromHexString(privacyGroupId)) + .getPrivateAccountState(BytesValues.fromBase64(privacyGroupId)) .map( lastRootHash -> privateWorldStateArchive diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetPrivateTransaction.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetPrivateTransaction.java index 60ff8b67f6..c2bb1a9e49 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetPrivateTransaction.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetPrivateTransaction.java @@ -26,9 +26,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; -import tech.pegasys.pantheon.util.bytes.BytesValue; - -import java.util.Base64; +import tech.pegasys.pantheon.util.bytes.BytesValues; import org.apache.logging.log4j.Logger; @@ -56,7 +54,7 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequest request) { - LOG.trace("Executing {}", RpcMethod.EEA_GET_TRANSACTION_RECEIPT.getMethodName()); + LOG.trace("Executing {}", RpcMethod.EEA_GET_PRIVATE_TRANSACTION.getMethodName()); final String enclaveKey = parameters.required(request.getParams(), 0, String.class); try { ReceiveResponse receiveResponse = @@ -64,8 +62,7 @@ public JsonRpcResponse response(final JsonRpcRequest request) { LOG.trace("Received transaction information from Enclave"); final BytesValueRLPInput bytesValueRLPInput = - new BytesValueRLPInput( - BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + new BytesValueRLPInput(BytesValues.fromBase64(receiveResponse.getPayload()), false); final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); return new JsonRpcSuccessResponse(request.getId(), privateTransaction); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java index 753154b913..375cfb19ad 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java @@ -39,13 +39,12 @@ import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; -import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; -import com.google.common.base.Charsets; import org.apache.logging.log4j.Logger; public class EeaGetTransactionReceipt implements JsonRpcMethod { @@ -98,8 +97,7 @@ public JsonRpcResponse response(final JsonRpcRequest request) { LOG.trace("Received transaction information from Enclave"); final BytesValueRLPInput bytesValueRLPInput = - new BytesValueRLPInput( - BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + new BytesValueRLPInput(BytesValues.fromBase64(receiveResponse.getPayload()), false); privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); privacyGroupId = receiveResponse.getPrivacyGroupId(); @@ -114,7 +112,7 @@ public JsonRpcResponse response(final JsonRpcRequest request) { ? Address.privateContractAddress( privateTransaction.getSender(), privateTransaction.getNonce(), - BytesValue.wrap(privacyGroupId.getBytes(Charsets.UTF_8))) + BytesValues.fromBase64(privacyGroupId)) .toString() : null; diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java index 67412af431..d00fa09f40 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java @@ -23,13 +23,15 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.bytes.BytesValues; import org.junit.Test; public class EeaGetTransactionCountTest { private final JsonRpcParameter parameters = new JsonRpcParameter(); - private final BytesValue privacyGroupId = BytesValue.wrap("0x123".getBytes(UTF_8)); + private final String privacyGroupId = + BytesValues.asBase64String(BytesValue.wrap("0x123".getBytes(UTF_8))); private final Address senderAddress = Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57"); @@ -39,13 +41,12 @@ public class EeaGetTransactionCountTest { public void verifyTransactionCount() { final PrivateTransactionHandler privateTransactionHandler = mock(PrivateTransactionHandler.class); - when(privateTransactionHandler.getSenderNonce(senderAddress, privacyGroupId.toString())) - .thenReturn(NONCE); + when(privateTransactionHandler.getSenderNonce(senderAddress, privacyGroupId)).thenReturn(NONCE); final EeaGetTransactionCount eeaGetTransactionCount = new EeaGetTransactionCount(parameters, privateTransactionHandler); - final Object[] params = new Object[] {senderAddress, privacyGroupId.toString()}; + final Object[] params = new Object[] {senderAddress, privacyGroupId}; final JsonRpcRequest request = new JsonRpcRequest("1", "eea_getTransactionCount", params); final JsonRpcSuccessResponse response = diff --git a/util/src/main/java/tech/pegasys/pantheon/util/bytes/BytesValues.java b/util/src/main/java/tech/pegasys/pantheon/util/bytes/BytesValues.java index 5e3b53d244..a51c332302 100644 --- a/util/src/main/java/tech/pegasys/pantheon/util/bytes/BytesValues.java +++ b/util/src/main/java/tech/pegasys/pantheon/util/bytes/BytesValues.java @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.util.Base64; /** Static utility methods to work with {@link BytesValue} and {@link MutableBytesValue}. */ public abstract class BytesValues { @@ -254,6 +255,18 @@ public static BigInteger asSignedBigInteger(final BytesValue bytes) { return new BigInteger(bytes.getArrayUnsafe()); } + public static String asBase64String(final BytesValue bytesValue) { + return Base64.getEncoder().encodeToString(bytesValue.extractArray()); + } + + public static BytesValue fromBase64(final byte[] bytes) { + return BytesValue.wrap(Base64.getDecoder().decode(bytes)); + } + + public static BytesValue fromBase64(final String str) { + return BytesValue.wrap(Base64.getDecoder().decode(str)); + } + // In Java9, this could be moved to BytesValue and made private static BytesValue fromHexString(final String str, final int destSize, final boolean lenient) { return BytesValue.wrap(fromRawHexString(str, destSize, lenient));