Skip to content

Commit

Permalink
Merge pull request #115 from bianjieai/sv/evmTx
Browse files Browse the repository at this point in the history
Sv/evm tx
  • Loading branch information
prolenking authored Nov 17, 2022
2 parents 9391d10 + 054e4fb commit 4e0aaf4
Show file tree
Hide file tree
Showing 20 changed files with 1,875 additions and 17 deletions.
35 changes: 35 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,33 @@
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.tuweni</groupId>
<artifactId>tuweni-bytes</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tuweni</groupId>
<artifactId>tuweni-units</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>io.grpc</groupId>
Expand All @@ -87,6 +114,10 @@
<artifactId>animal-sniffer-annotations</artifactId>
<groupId>org.codehaus.mojo</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down Expand Up @@ -114,6 +145,10 @@
<artifactId>animal-sniffer-annotations</artifactId>
<groupId>org.codehaus.mojo</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
Expand Down
192 changes: 192 additions & 0 deletions src/main/java/irita/sdk/crypto/eth/AbstractRLPOutput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package irita.sdk.crypto.eth;

/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;

import static com.google.common.base.Preconditions.checkState;

abstract class AbstractRLPOutput implements RLPOutput {
/*
* The algorithm implemented works as follows:
*
* Values written to the output are accumulated in the 'values' list. When a list is started, it
* is indicated by adding a specific marker in that list (LIST_MARKER).
* While this is gathered, we also incrementally compute the size of the payload of every list of
* that output. Those sizes are stored in 'payloadSizes': when all the output has been added,
* payloadSizes[i] will contain the size of the (encoded) payload of the ith list in 'values'
* (that is, the list that starts at the ith LIST_MARKER in 'values').
*
* With that information gathered, encoded() can write its output in a single walk of 'values':
* values can encoded directly, and every time we read a list marker, we use the corresponding
* payload size to write the proper prefix and continue.
*
* The main remaining aspect is how the values of 'payloadSizes' are computed. Computing the size
* of a list without nesting inside is easy: simply add the encoded size of any newly added value
* to the running size. The difficulty is with nesting: when we start a new list, we need to
* track both the sizes of the previous list and the new one. To deal with that, we use the small
* stack 'parentListStack': it stores the index in 'payloadSizes' of every currently "open" lists.
* In other words, payloadSises[parentListStack[stackSize - 1]] corresponds to the size of the
* current list, the one to which newly added value are currently written (until the next call
* to 'endList()' that is, while payloadSises[parentListStack[stackSize - 2]] would be the size
* of the parent list, ....
*
* Note that when a new value is added, we add its size only the currently running list. We should
* add that size to that of any parent list as well, but we do so indirectly when a list is
* finished: when 'endList()' is called, we add the size of the full list we just finished (and
* whose size we have now have completely) to its parent size.
*
* Side-note: this class internally and informally use "element" to refer to a non list items.
*/

private static final Bytes LIST_MARKER = Bytes.wrap(new byte[0]);

private final List<Bytes> values = new ArrayList<>();
// For every value i in values, rlpEncoded.get(i) will be true only if the value stored is an
// already encoded item.
private final BitSet rlpEncoded = new BitSet();

// First element is the total size of everything (the encoding may be a single non-list item, so
// this handle that case more easily; we need that value to size out final output). Following
// elements holds the size of the payload of the ith list in 'values'.
private int[] payloadSizes = new int[8];
private int listsCount = 1; // number of lists current in 'values' + 1.

private int[] parentListStack = new int[4];
private int stackSize = 1;

private int currentList() {
return parentListStack[stackSize - 1];
}

@Override
public void writeBytes(final Bytes v) {
checkState(
stackSize > 1 || values.isEmpty(), "Terminated RLP output, cannot add more elements");
values.add(v);
payloadSizes[currentList()] += RLPEncodingHelpers.elementSize(v);
}

public void writeRaw(final Bytes v) {
checkState(
stackSize > 1 || values.isEmpty(), "Terminated RLP output, cannot add more elements");
values.add(v);
// Mark that last value added as already encoded.
rlpEncoded.set(values.size() - 1);
payloadSizes[currentList()] += v.size();
}

@Override
public void startList() {
values.add(LIST_MARKER);
++listsCount; // we'll add a new element to payloadSizes
++stackSize; // and to the list stack.

// Resize our lists if necessary.
if (listsCount > payloadSizes.length) {
payloadSizes = Arrays.copyOf(payloadSizes, (payloadSizes.length * 3) / 2);
}
if (stackSize > parentListStack.length) {
parentListStack = Arrays.copyOf(parentListStack, (parentListStack.length * 3) / 2);
}

// The new current list size is store in the slot we just made room for by incrementing
// listsCount
parentListStack[stackSize - 1] = listsCount - 1;
}

@Override
public void endList() {
checkState(stackSize > 1, "LeaveList() called with no prior matching startList()");

final int current = currentList();
final int finishedListSize = RLPEncodingHelpers.listSize(payloadSizes[current]);
--stackSize;

// We just finished an item of our parent list, add it to that parent list size now.
final int newCurrent = currentList();
payloadSizes[newCurrent] += finishedListSize;
}

/**
* Computes the final encoded data size.
*
* @return The size of the RLP-encoded data written to this output.
* @throws IllegalStateException if some opened list haven't been closed (the output is not valid
* as is).
*/
public int encodedSize() {
checkState(stackSize == 1, "A list has been entered (startList()) but not left (endList())");
return payloadSizes[0];
}

/**
* Write the rlp encoded value to the provided {@link MutableBytes}
*
* @param mutableBytes the value to which the rlp-data will be written
*/
public void writeEncoded(final MutableBytes mutableBytes) {
// Special case where we encode only a single non-list item (note that listsCount is initially
// set to 1, so listsCount == 1 really mean no list explicitly added to the output).
if (listsCount == 1) {
// writeBytes make sure we cannot have more than 1 value without a list
assert values.size() == 1;
final Bytes value = values.get(0);

final int finalOffset;
// Single non-list value.
if (rlpEncoded.get(0)) {
value.copyTo(mutableBytes, 0);
finalOffset = value.size();
} else {
finalOffset = RLPEncodingHelpers.writeElement(value, mutableBytes, 0);
}
checkState(
finalOffset == mutableBytes.size(),
"Expected single element RLP encode to be of size %s but was of size %s.",
mutableBytes.size(),
finalOffset);
return;
}

int offset = 0;
int listIdx = 0;
for (int i = 0; i < values.size(); i++) {
final Bytes value = values.get(i);
if (value == LIST_MARKER) {
final int payloadSize = payloadSizes[++listIdx];
offset = RLPEncodingHelpers.writeListHeader(payloadSize, mutableBytes, offset);
} else if (rlpEncoded.get(i)) {
value.copyTo(mutableBytes, offset);
offset += value.size();
} else {
offset = RLPEncodingHelpers.writeElement(value, mutableBytes, offset);
}
}

checkState(
offset == mutableBytes.size(),
"Expected RLP encoding to be of size %s but was of size %s.",
mutableBytes.size(),
offset);
}
}
111 changes: 111 additions & 0 deletions src/main/java/irita/sdk/crypto/eth/AbstractSECP256.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package irita.sdk.crypto.eth;


import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Optional;

public abstract class AbstractSECP256 implements SignatureAlgorithm {

protected static final int PRIVATE_KEY_BYTE_LENGTH = 32;
protected static final int PUBLIC_KEY_BYTE_LENGTH = 64;
protected static final int SIGNATURE_BYTE_LENGTH = 65;

public static final String PROVIDER = "BC";

protected final ECDomainParameters curve;
protected final BigInteger halfCurveOrder;

protected final KeyPairGenerator keyPairGenerator;
protected final BigInteger curveOrder;

final BigInteger prime;

protected AbstractSECP256(final String curveName, final BigInteger prime) {
this.prime = prime;
Security.addProvider(new BouncyCastleProvider());

final X9ECParameters params = SECNamedCurves.getByName(curveName);
curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
curveOrder = curve.getN();
halfCurveOrder = curveOrder.shiftRight(1);
try {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
} catch (final Exception e) {
throw new RuntimeException(e);
}
final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(curveName);
try {
keyPairGenerator.initialize(ecGenParameterSpec, SecureRandomProvider.createSecureRandom());
} catch (final InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}

// Decompress a compressed public key (x co-ord and low-bit of y-coord).
protected ECPoint decompressKey(final BigInteger xBN, final boolean yBit) {
final X9IntegerConverter x9 = new X9IntegerConverter();
final byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(curve.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
// TODO: Find a better way to handle an invalid point compression here.
// Currently ECCurve#decodePoint throws an IllegalArgumentException.
return curve.getCurve().decodePoint(compEnc);
}

protected BigInteger recoverFromSignature(
final int recId, final BigInteger r, final BigInteger s, final Bytes32 dataHash) {
assert (recId >= 0);
assert (r.signum() >= 0);
assert (s.signum() >= 0);
assert (dataHash != null);

final BigInteger n = curve.getN(); // Curve order.
final BigInteger i = BigInteger.valueOf((long) recId / 2);
final BigInteger x = r.add(i.multiply(n));
if (x.compareTo(prime) >= 0) {
return null;
}
final ECPoint R = decompressKey(x, (recId & 1) == 1);
if (!R.multiply(n).isInfinity()) {
return null;
}
final BigInteger e = dataHash.toUnsignedBigInteger();
final BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
final BigInteger rInv = r.modInverse(n);
final BigInteger srInv = rInv.multiply(s).mod(n);
final BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
final ECPoint q = ECAlgorithms.sumOfTwoMultiplies(curve.getG(), eInvrInv, R, srInv);

if (q.isInfinity()) {
return null;
}

final byte[] qBytes = q.getEncoded(false);
// We remove the prefix
return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
}

@Override
public Optional<SECPPublicKey> recoverPublicKeyFromSignature(
final Bytes32 dataHash, final SECPSignature signature) {
final BigInteger publicKeyBI =
recoverFromSignature(signature.getRecId(), signature.getR(), signature.getS(), dataHash);
return Optional.of(SECPPublicKey.create(publicKeyBI, ALGORITHM));
}

}


31 changes: 31 additions & 0 deletions src/main/java/irita/sdk/crypto/eth/Address.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package irita.sdk.crypto.eth;

import com.fasterxml.jackson.annotation.JsonCreator;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.DelegatingBytes;

public class Address extends DelegatingBytes {

public static final int SIZE = 20;

public Address(final Bytes bytes) {
super(bytes);
}

public static Address wrap(final Bytes value) {
return new Address(value);
}


public static Address extract(final Bytes32 hash) {
return wrap(hash.slice(12, 20));
}


@JsonCreator
public static Address fromHexString(final String str) {
if (str == null) return null;
return wrap(Bytes.fromHexStringLenient(str, SIZE));
}
}
Loading

0 comments on commit 4e0aaf4

Please sign in to comment.