-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #115 from bianjieai/sv/evmTx
Sv/evm tx
- Loading branch information
Showing
20 changed files
with
1,875 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
src/main/java/irita/sdk/crypto/eth/AbstractRLPOutput.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
111
src/main/java/irita/sdk/crypto/eth/AbstractSECP256.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
Oops, something went wrong.