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