mirror of https://github.com/hyperledger/besu
[PRIV] Add Private transaction abstraction (#592)
* Implement PrivateTransaction * Add PrivateTransaction Test * Fix PrivateTransaction comment - add params for private transaction specific fields * Fix PrivateTransaction signature calculation Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
efc8653da2
commit
8136b0fcb2
@ -0,0 +1,631 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.privacy; |
||||
|
||||
import static com.google.common.base.Preconditions.checkState; |
||||
import static tech.pegasys.pantheon.crypto.Hash.keccak256; |
||||
|
||||
import tech.pegasys.pantheon.crypto.SECP256K1; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPException; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; |
||||
import tech.pegasys.pantheon.util.bytes.Bytes32; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValues; |
||||
import tech.pegasys.pantheon.util.uint.UInt256; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
import java.util.Optional; |
||||
import java.util.OptionalInt; |
||||
|
||||
/** An operation submitted by an external actor to be applied to the system. */ |
||||
public class PrivateTransaction { |
||||
|
||||
// Used for transactions that are not tied to a specific chain
|
||||
// (e.g. does not have a chain id associated with it).
|
||||
private static final int REPLAY_UNPROTECTED_V_BASE = 27; |
||||
|
||||
private static final int REPLAY_PROTECTED_V_BASE = 35; |
||||
|
||||
// The v signature parameter starts at 36 because 1 is the first valid chainId so:
|
||||
// chainId > 1 implies that 2 * chainId + V_BASE > 36.
|
||||
private static final int REPLAY_PROTECTED_V_MIN = 36; |
||||
|
||||
private final long nonce; |
||||
|
||||
private final Wei gasPrice; |
||||
|
||||
private final long gasLimit; |
||||
|
||||
private final Optional<Address> to; |
||||
|
||||
private final Wei value; |
||||
|
||||
private final SECP256K1.Signature signature; |
||||
|
||||
private final BytesValue payload; |
||||
|
||||
private final OptionalInt chainId; |
||||
|
||||
private final BytesValue privateFrom; |
||||
|
||||
private final List<BytesValue> privateFor; |
||||
|
||||
private final BytesValue restriction; |
||||
|
||||
// Caches a "hash" of a portion of the transaction used for sender recovery.
|
||||
// Note that this hash does not include the transaction signature so it does not
|
||||
// fully identify the transaction (use the result of the {@code hash()} for that).
|
||||
// It is only used to compute said signature and recover the sender from it.
|
||||
protected volatile Bytes32 hashNoSignature; |
||||
|
||||
// Caches the transaction sender.
|
||||
protected volatile Address sender; |
||||
|
||||
// Caches the hash used to uniquely identify the transaction.
|
||||
protected volatile Hash hash; |
||||
|
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
public static PrivateTransaction readFrom(final RLPInput input) throws RLPException { |
||||
input.enterList(); |
||||
|
||||
final Builder builder = |
||||
builder() |
||||
.nonce(input.readLongScalar()) |
||||
.gasPrice(input.readUInt256Scalar(Wei::wrap)) |
||||
.gasLimit(input.readLongScalar()) |
||||
.to(input.readBytesValue(v -> v.size() == 0 ? null : Address.wrap(v))) |
||||
.value(input.readUInt256Scalar(Wei::wrap)) |
||||
.payload(input.readBytesValue()); |
||||
|
||||
final int v = input.readIntScalar(); |
||||
final byte recId; |
||||
int chainId = -1; |
||||
if (v == REPLAY_UNPROTECTED_V_BASE || v == REPLAY_UNPROTECTED_V_BASE + 1) { |
||||
recId = (byte) (v - REPLAY_UNPROTECTED_V_BASE); |
||||
} else if (v > REPLAY_PROTECTED_V_MIN) { |
||||
chainId = (v - REPLAY_PROTECTED_V_BASE) / 2; |
||||
recId = (byte) (v - (2 * chainId + REPLAY_PROTECTED_V_BASE)); |
||||
} else { |
||||
throw new RuntimeException( |
||||
String.format("An unsupported encoded `v` value of %s was found", v)); |
||||
} |
||||
final BigInteger r = BytesValues.asUnsignedBigInteger(input.readUInt256Scalar().getBytes()); |
||||
final BigInteger s = BytesValues.asUnsignedBigInteger(input.readUInt256Scalar().getBytes()); |
||||
final SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId); |
||||
final BytesValue privateFrom = input.readBytesValue(); |
||||
final List<BytesValue> privateFor = input.readList(RLPInput::readBytesValue); |
||||
final BytesValue restriction = input.readBytesValue(); |
||||
|
||||
input.leaveList(); |
||||
|
||||
return builder |
||||
.chainId(chainId) |
||||
.signature(signature) |
||||
.privateFrom(privateFrom) |
||||
.privateFor(privateFor) |
||||
.restriction(restriction) |
||||
.build(); |
||||
} |
||||
|
||||
/** |
||||
* Instantiates a transaction instance. |
||||
* |
||||
* @param nonce the nonce |
||||
* @param gasPrice the gas price |
||||
* @param gasLimit the gas limit |
||||
* @param to the transaction recipient |
||||
* @param value the value being transferred to the recipient |
||||
* @param signature the signature |
||||
* @param payload the payload |
||||
* @param sender the transaction sender |
||||
* @param chainId the chain id to apply the transaction to |
||||
* <p>The {@code to} will be an {@code Optional.empty()} for a contract creation transaction; |
||||
* otherwise it should contain an address. |
||||
* <p>The {@code chainId} must be greater than 0 to be applied to a specific chain; otherwise |
||||
* it will default to any chain. |
||||
* @param privateFrom The public key of the sender of this private transaction |
||||
* @param privateFor An array of the public keys of the intended recipients of this private |
||||
* transaction |
||||
* @param restriction the restriction of this private transaction |
||||
*/ |
||||
protected PrivateTransaction( |
||||
final long nonce, |
||||
final Wei gasPrice, |
||||
final long gasLimit, |
||||
final Optional<Address> to, |
||||
final Wei value, |
||||
final SECP256K1.Signature signature, |
||||
final BytesValue payload, |
||||
final Address sender, |
||||
final int chainId, |
||||
final BytesValue privateFrom, |
||||
final List<BytesValue> privateFor, |
||||
final BytesValue restriction) { |
||||
this.nonce = nonce; |
||||
this.gasPrice = gasPrice; |
||||
this.gasLimit = gasLimit; |
||||
this.to = to; |
||||
this.value = value; |
||||
this.signature = signature; |
||||
this.payload = payload; |
||||
this.sender = sender; |
||||
this.chainId = chainId > 0 ? OptionalInt.of(chainId) : OptionalInt.empty(); |
||||
this.privateFrom = privateFrom; |
||||
this.privateFor = privateFor; |
||||
this.restriction = restriction; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction nonce. |
||||
* |
||||
* @return the transaction nonce |
||||
*/ |
||||
public long getNonce() { |
||||
return nonce; |
||||
} |
||||
|
||||
/** |
||||
* Return the transaction gas price. |
||||
* |
||||
* @return the transaction gas price |
||||
*/ |
||||
public Wei getGasPrice() { |
||||
return gasPrice; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction gas limit. |
||||
* |
||||
* @return the transaction gas limit |
||||
*/ |
||||
public long getGasLimit() { |
||||
return gasLimit; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction recipient. |
||||
* |
||||
* <p>The {@code Optional<Address>} will be {@code Optional.empty()} if the transaction is a |
||||
* contract creation; otherwise it will contain the message call transaction recipient. |
||||
* |
||||
* @return the transaction recipient if a message call; otherwise {@code Optional.empty()} |
||||
*/ |
||||
public Optional<Address> getTo() { |
||||
return to; |
||||
} |
||||
|
||||
/** |
||||
* Returns the value transferred in the transaction. |
||||
* |
||||
* @return the value transferred in the transaction |
||||
*/ |
||||
public Wei getValue() { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Returns the signature used to sign the transaction. |
||||
* |
||||
* @return the signature used to sign the transaction |
||||
*/ |
||||
public SECP256K1.Signature getSignature() { |
||||
return signature; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction payload. |
||||
* |
||||
* @return the transaction payload |
||||
*/ |
||||
public BytesValue getPayload() { |
||||
return payload; |
||||
} |
||||
|
||||
/** |
||||
* Return the transaction chain id (if it exists) |
||||
* |
||||
* <p>The {@code OptionalInt} will be {@code OptionalInt.empty()} if the transaction is not tied |
||||
* to a specific chain. |
||||
* |
||||
* @return the transaction chain id if it exists; otherwise {@code OptionalInt.empty()} |
||||
*/ |
||||
public OptionalInt getChainId() { |
||||
return chainId; |
||||
} |
||||
|
||||
/** |
||||
* Returns the enclave public key of the sender. |
||||
* |
||||
* @return the enclave public key of the sender. |
||||
*/ |
||||
public BytesValue getPrivateFrom() { |
||||
return privateFrom; |
||||
} |
||||
|
||||
/** |
||||
* Returns the enclave public keys of the receivers. |
||||
* |
||||
* @return the enclave public keys of the receivers |
||||
*/ |
||||
public List<BytesValue> getPrivateFor() { |
||||
return privateFor; |
||||
} |
||||
|
||||
/** |
||||
* Returns the restriction of this private transaction. |
||||
* |
||||
* @return the restriction |
||||
*/ |
||||
public BytesValue getRestriction() { |
||||
return restriction; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction sender. |
||||
* |
||||
* @return the transaction sender |
||||
*/ |
||||
public Address getSender() { |
||||
if (sender == null) { |
||||
final SECP256K1.PublicKey publicKey = |
||||
SECP256K1.PublicKey.recoverFromSignature(getOrComputeSenderRecoveryHash(), signature) |
||||
.orElseThrow( |
||||
() -> |
||||
new IllegalStateException( |
||||
"Cannot recover public key from " + "signature for " + this)); |
||||
sender = Address.extract(Hash.hash(publicKey.getEncodedBytes())); |
||||
} |
||||
return sender; |
||||
} |
||||
|
||||
private Bytes32 getOrComputeSenderRecoveryHash() { |
||||
if (hashNoSignature == null) { |
||||
hashNoSignature = |
||||
computeSenderRecoveryHash( |
||||
nonce, |
||||
gasPrice, |
||||
gasLimit, |
||||
to.orElse(null), |
||||
value, |
||||
payload, |
||||
chainId, |
||||
privateFrom, |
||||
privateFor, |
||||
restriction); |
||||
} |
||||
return hashNoSignature; |
||||
} |
||||
|
||||
/** |
||||
* Writes the transaction to RLP |
||||
* |
||||
* @param out the output to write the transaction to |
||||
*/ |
||||
public void writeTo(final RLPOutput out) { |
||||
out.startList(); |
||||
|
||||
out.writeLongScalar(getNonce()); |
||||
out.writeUInt256Scalar(getGasPrice()); |
||||
out.writeLongScalar(getGasLimit()); |
||||
out.writeBytesValue(getTo().isPresent() ? getTo().get() : BytesValue.EMPTY); |
||||
out.writeUInt256Scalar(getValue()); |
||||
out.writeBytesValue(getPayload()); |
||||
writeSignature(out); |
||||
out.writeBytesValue(getPrivateFrom()); |
||||
out.writeList(getPrivateFor(), (bv, rlpO) -> rlpO.writeBytesValue(bv)); |
||||
out.writeBytesValue(getRestriction()); |
||||
|
||||
out.endList(); |
||||
} |
||||
|
||||
private void writeSignature(final RLPOutput out) { |
||||
out.writeIntScalar(getV()); |
||||
out.writeBigIntegerScalar(getSignature().getR()); |
||||
out.writeBigIntegerScalar(getSignature().getS()); |
||||
} |
||||
|
||||
public BigInteger getR() { |
||||
return signature.getR(); |
||||
} |
||||
|
||||
public BigInteger getS() { |
||||
return signature.getS(); |
||||
} |
||||
|
||||
public int getV() { |
||||
final int v; |
||||
if (!chainId.isPresent()) { |
||||
v = signature.getRecId() + REPLAY_UNPROTECTED_V_BASE; |
||||
} else { |
||||
v = (getSignature().getRecId() + REPLAY_PROTECTED_V_BASE + 2 * chainId.getAsInt()); |
||||
} |
||||
return v; |
||||
} |
||||
|
||||
/** |
||||
* Returns the transaction hash. |
||||
* |
||||
* @return the transaction hash |
||||
*/ |
||||
public Hash hash() { |
||||
if (hash == null) { |
||||
final BytesValue rlp = RLP.encode(this::writeTo); |
||||
hash = Hash.hash(rlp); |
||||
} |
||||
return hash; |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the transaction is a contract creation |
||||
* |
||||
* @return {@code true} if this is a contract-creation transaction; otherwise {@code false} |
||||
*/ |
||||
public boolean isContractCreation() { |
||||
return !getTo().isPresent(); |
||||
} |
||||
|
||||
/** |
||||
* Calculates the up-front cost for the gas the transaction can use. |
||||
* |
||||
* @return the up-front cost for the gas the transaction can use. |
||||
*/ |
||||
public Wei getUpfrontGasCost() { |
||||
return Wei.of(getGasLimit()).times(getGasPrice()); |
||||
} |
||||
|
||||
/** |
||||
* Calculates the up-front cost for the transaction. |
||||
* |
||||
* <p>The up-front cost is paid by the sender account before the transaction is executed. The |
||||
* sender must have the amount in its account balance to execute and some of this amount may be |
||||
* refunded after the transaction has executed. |
||||
* |
||||
* @return the up-front gas cost for the transaction |
||||
*/ |
||||
public Wei getUpfrontCost() { |
||||
return getUpfrontGasCost().plus(getValue()); |
||||
} |
||||
|
||||
private static Bytes32 computeSenderRecoveryHash( |
||||
final long nonce, |
||||
final Wei gasPrice, |
||||
final long gasLimit, |
||||
final Address to, |
||||
final Wei value, |
||||
final BytesValue payload, |
||||
final OptionalInt chainId, |
||||
final BytesValue privateFrom, |
||||
final List<BytesValue> privateFor, |
||||
final BytesValue restriction) { |
||||
return keccak256( |
||||
RLP.encode( |
||||
out -> { |
||||
out.startList(); |
||||
out.writeLongScalar(nonce); |
||||
out.writeUInt256Scalar(gasPrice); |
||||
out.writeLongScalar(gasLimit); |
||||
out.writeBytesValue(to == null ? BytesValue.EMPTY : to); |
||||
out.writeUInt256Scalar(value); |
||||
out.writeBytesValue(payload); |
||||
if (chainId.isPresent()) { |
||||
out.writeIntScalar(chainId.getAsInt()); |
||||
out.writeUInt256Scalar(UInt256.ZERO); |
||||
out.writeUInt256Scalar(UInt256.ZERO); |
||||
} |
||||
out.writeBytesValue(privateFrom); |
||||
out.writeList(privateFor, (bv, rlpO) -> rlpO.writeBytesValue(bv)); |
||||
out.writeBytesValue(restriction); |
||||
out.endList(); |
||||
})); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object other) { |
||||
if (!(other instanceof PrivateTransaction)) { |
||||
return false; |
||||
} |
||||
final PrivateTransaction that = (PrivateTransaction) other; |
||||
return this.chainId.equals(that.chainId) |
||||
&& this.gasLimit == that.gasLimit |
||||
&& this.gasPrice.equals(that.gasPrice) |
||||
&& this.nonce == that.nonce |
||||
&& this.payload.equals(that.payload) |
||||
&& this.signature.equals(that.signature) |
||||
&& this.to.equals(that.to) |
||||
&& this.value.equals(that.value) |
||||
&& this.privateFor.equals(that.privateFor) |
||||
&& this.privateFrom.equals(that.privateFrom) |
||||
&& this.restriction.equals(that.restriction); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash( |
||||
nonce, |
||||
gasPrice, |
||||
gasLimit, |
||||
to, |
||||
value, |
||||
payload, |
||||
signature, |
||||
chainId, |
||||
privateFor, |
||||
privateFrom, |
||||
restriction); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
final StringBuilder sb = new StringBuilder(); |
||||
sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append("{"); |
||||
sb.append("nonce=").append(getNonce()).append(", "); |
||||
sb.append("gasPrice=").append(getGasPrice()).append(", "); |
||||
sb.append("gasLimit=").append(getGasLimit()).append(", "); |
||||
if (getTo().isPresent()) sb.append("to=").append(getTo().get()).append(", "); |
||||
sb.append("value=").append(getValue()).append(", "); |
||||
sb.append("sig=").append(getSignature()).append(", "); |
||||
if (chainId.isPresent()) sb.append("chainId=").append(getChainId().getAsInt()).append(", "); |
||||
sb.append("payload=").append(getPayload()); |
||||
sb.append("privateFrom=").append(getPrivateFrom()); |
||||
sb.append("privateFor=").append(Arrays.toString(getPrivateFor().toArray())); |
||||
sb.append("restriction=").append(getRestriction()); |
||||
return sb.append("}").toString(); |
||||
} |
||||
|
||||
public Optional<Address> contractAddress() { |
||||
if (isContractCreation()) { |
||||
return Optional.of(Address.contractAddress(getSender(), getNonce())); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
public static class Builder { |
||||
|
||||
protected long nonce = -1L; |
||||
|
||||
protected Wei gasPrice; |
||||
|
||||
protected long gasLimit = -1L; |
||||
|
||||
protected Address to; |
||||
|
||||
protected Wei value; |
||||
|
||||
protected SECP256K1.Signature signature; |
||||
|
||||
protected BytesValue payload; |
||||
|
||||
protected Address sender; |
||||
|
||||
protected int chainId = -1; |
||||
|
||||
protected BytesValue privateFrom; |
||||
|
||||
protected List<BytesValue> privateFor; |
||||
|
||||
protected BytesValue restriction; |
||||
|
||||
public Builder chainId(final int chainId) { |
||||
this.chainId = chainId; |
||||
return this; |
||||
} |
||||
|
||||
public Builder gasPrice(final Wei gasPrice) { |
||||
this.gasPrice = gasPrice; |
||||
return this; |
||||
} |
||||
|
||||
public Builder gasLimit(final long gasLimit) { |
||||
this.gasLimit = gasLimit; |
||||
return this; |
||||
} |
||||
|
||||
public Builder nonce(final long nonce) { |
||||
this.nonce = nonce; |
||||
return this; |
||||
} |
||||
|
||||
public Builder value(final Wei value) { |
||||
this.value = value; |
||||
return this; |
||||
} |
||||
|
||||
public Builder to(final Address to) { |
||||
this.to = to; |
||||
return this; |
||||
} |
||||
|
||||
public Builder payload(final BytesValue payload) { |
||||
this.payload = payload; |
||||
return this; |
||||
} |
||||
|
||||
public Builder sender(final Address sender) { |
||||
this.sender = sender; |
||||
return this; |
||||
} |
||||
|
||||
public Builder signature(final SECP256K1.Signature signature) { |
||||
this.signature = signature; |
||||
return this; |
||||
} |
||||
|
||||
public Builder privateFrom(final BytesValue privateFrom) { |
||||
this.privateFrom = privateFrom; |
||||
return this; |
||||
} |
||||
|
||||
public Builder privateFor(final List<BytesValue> privateFor) { |
||||
this.privateFor = privateFor; |
||||
return this; |
||||
} |
||||
|
||||
public Builder restriction(final BytesValue restriction) { |
||||
this.restriction = restriction; |
||||
return this; |
||||
} |
||||
|
||||
public PrivateTransaction build() { |
||||
return new PrivateTransaction( |
||||
nonce, |
||||
gasPrice, |
||||
gasLimit, |
||||
Optional.ofNullable(to), |
||||
value, |
||||
signature, |
||||
payload, |
||||
sender, |
||||
chainId, |
||||
privateFrom, |
||||
privateFor, |
||||
restriction); |
||||
} |
||||
|
||||
public PrivateTransaction signAndBuild(final SECP256K1.KeyPair keys) { |
||||
checkState( |
||||
signature == null, "The transaction signature has already been provided to this builder"); |
||||
signature(computeSignature(keys)); |
||||
sender(Address.extract(Hash.hash(keys.getPublicKey().getEncodedBytes()))); |
||||
return build(); |
||||
} |
||||
|
||||
protected SECP256K1.Signature computeSignature(final SECP256K1.KeyPair keys) { |
||||
final OptionalInt optionalChainId = |
||||
chainId > 0 ? OptionalInt.of(chainId) : OptionalInt.empty(); |
||||
final Bytes32 hash = |
||||
computeSenderRecoveryHash( |
||||
nonce, |
||||
gasPrice, |
||||
gasLimit, |
||||
to, |
||||
value, |
||||
payload, |
||||
optionalChainId, |
||||
privateFrom, |
||||
privateFor, |
||||
restriction); |
||||
return SECP256K1.sign(hash, keys); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.privacy; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
import tech.pegasys.pantheon.crypto.SECP256K1; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; |
||||
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPException; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Optional; |
||||
|
||||
import com.google.common.collect.Lists; |
||||
import org.junit.Test; |
||||
|
||||
public class PrivateTransactionTest { |
||||
|
||||
private static final String INVALID_RLP = |
||||
"0xf87f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" |
||||
+ "a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
||||
+ "fffffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff75" |
||||
+ "9b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43" |
||||
+ "601b4ab949f53faa07bd2c804"; |
||||
|
||||
private static final String VALID_PRIVATE_TRANSACTION_RLP = |
||||
"0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" |
||||
+ "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
||||
+ "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" |
||||
+ "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" |
||||
+ "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" |
||||
+ "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" |
||||
+ "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" |
||||
+ "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" |
||||
+ "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" |
||||
+ "64"; |
||||
|
||||
private static final PrivateTransaction VALID_PRIVATE_TRANSACTION = |
||||
new PrivateTransaction( |
||||
0L, |
||||
Wei.of(1), |
||||
21000L, |
||||
Optional.of( |
||||
Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))), |
||||
Wei.of( |
||||
new BigInteger( |
||||
"115792089237316195423570985008687907853269984665640564039457584007913129639935")), |
||||
SECP256K1.Signature.create( |
||||
new BigInteger( |
||||
"32886959230931919120748662916110619501838190146643992583529828535682419954515"), |
||||
new BigInteger( |
||||
"14473701025599600909210599917245952381483216609124029382871721729679842002948"), |
||||
Byte.valueOf("0")), |
||||
BytesValue.fromHexString("0x"), |
||||
Address.wrap(BytesValue.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")), |
||||
0, |
||||
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)), |
||||
Lists.newArrayList( |
||||
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)), |
||||
BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))), |
||||
BytesValue.wrap("restricted".getBytes(UTF_8))); |
||||
|
||||
@Test |
||||
public void testWriteTo() { |
||||
BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); |
||||
VALID_PRIVATE_TRANSACTION.writeTo(bvrlpo); |
||||
assertEquals(VALID_PRIVATE_TRANSACTION_RLP, bvrlpo.encoded().toString()); |
||||
} |
||||
|
||||
@Test |
||||
public void testReadFrom() { |
||||
PrivateTransaction p = |
||||
PrivateTransaction.readFrom( |
||||
new BytesValueRLPInput(BytesValue.fromHexString(VALID_PRIVATE_TRANSACTION_RLP), false)); |
||||
|
||||
assertEquals(VALID_PRIVATE_TRANSACTION, p); |
||||
} |
||||
|
||||
@Test(expected = RLPException.class) |
||||
public void testReadFromInvalid() { |
||||
PrivateTransaction.readFrom( |
||||
new BytesValueRLPInput(BytesValue.fromHexString(INVALID_RLP), false)); |
||||
} |
||||
} |
Loading…
Reference in new issue