Skip to content

Commit

Permalink
move domain clasess to dshackle to fix integer limitation of transact…
Browse files Browse the repository at this point in the history
…ion V value
  • Loading branch information
a10zn8 committed Feb 14, 2024
1 parent aa4f569 commit 31ed51a
Show file tree
Hide file tree
Showing 87 changed files with 2,975 additions and 154 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
* Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
*
* 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 io.emeraldpay.dshackle.upstream.ethereum.domain;

import io.emeraldpay.etherjar.hex.Hex32;
import io.emeraldpay.etherjar.hex.HexData;
import org.bouncycastle.jcajce.provider.digest.Keccak;
import org.bouncycastle.util.encoders.Hex;

import java.util.Arrays;
import java.util.regex.Pattern;

/**
* Ethereum Wallet address
*/
public class Address extends HexData {

private final static byte[] HEX_BYTES = "0123456789abcdef".getBytes();

public static final int SIZE_BYTES = 20;
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;

private static final byte[] EMPTY_12BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

/**
* Use {@link Address#empty()}
*/
@Deprecated
public static final Address EMPTY = Address.from("0x0000000000000000000000000000000000000000");

private static final Pattern CASE_INSENSITIVE_PATTERN = Pattern.compile("0x(?i:[0-9a-f]{40})");
private static final Pattern CASE_SENSITIVE_PATTERN = Pattern.compile("0x(?:[0-9a-f]{40}|[0-9A-F]{40})");

private Address(byte[] bytes) {
super(bytes, SIZE_BYTES);
}

/**
* Create address from the provided value
*
* @param value 20 byte data
* @return address
*/
public static Address from(HexData value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
if (value.getSize() != SIZE_BYTES) {
throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES);
}
return new Address(value.getBytes());
}

public static Address from(byte[] value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
if (value.length != SIZE_BYTES) {
throw new IllegalArgumentException("Invalid input length: " + value.length + " != " + SIZE_BYTES);
}
return new Address(value);
}

public static Address from(String value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
if (value.length() != SIZE_HEX) {
throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX);
}
return new Address(HexData.from(value).getBytes());
}

public static Address empty() {
return new Address(new byte[SIZE_BYTES]);
}

/**
* Extract address from Hex32 (i.e. from method call parameters, logs, etc). The extract method verifies
* that the input in fact contains only address, i.e. input has zeroes for initial non-address bytes.
*
* @param value a Hex 32
* @return address
*/
public static Address extract(Hex32 value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
byte[] bytes = value.getBytes();
byte[] empty = new byte[Hex32.SIZE_BYTES - Address.SIZE_BYTES];
System.arraycopy(bytes, 0, empty, 0, empty.length);
if (!Arrays.equals(empty, EMPTY_12BYTES)) {
throw new IllegalArgumentException("Hex32 has non zero prefix for an Address");
}
byte[] address = new byte[Address.SIZE_BYTES];
System.arraycopy(bytes, Hex32.SIZE_BYTES - Address.SIZE_BYTES, address,0, address.length);
return new Address(address);
}

/**
* Validate address according to EIP 55.
*
* @param address a wallet address ('0x...')
* @return {@code true} if address correct or {@code false} otherwise
* @see <a href="https://github.com/ethereum/EIPs/issues/55">EIP 55</a>
*/
public static boolean isValidAddress(String address) {
return CASE_INSENSITIVE_PATTERN.matcher(address).matches()
&& (CASE_SENSITIVE_PATTERN.matcher(address).matches() || isValidChecksum(address));
}

public String toChecksumString() {
Keccak.Digest256 digest256 = new Keccak.Digest256();

byte[] hex = new byte[value.length * 2];
for(int i = 0, j = 0; i < value.length; i++){
hex[j++] = HEX_BYTES[(0xF0 & value[i]) >>> 4];
hex[j++] = HEX_BYTES[0x0F & value[i]];
}
digest256.update(hex);
String hash = Hex.toHexString(digest256.digest());

char[] plain = toHex().toCharArray();
char[] str = new char[hex.length + 2];
if (plain.length != str.length) {
throw new IllegalStateException("Hex representation has invalid length: " + plain.length + " != " + str.length);
}
str[0] = '0';
str[1] = 'x';
for (int i = 0; i < 40; i++) {
int dg = Character.digit(hash.charAt(i), 16);
if (dg > 7) {
str[i + 2] = Character.toUpperCase(plain[i + 2]);
} else {
str[i + 2] = plain[i + 2];
}
}
return new String(str);
}

@Override
public String toString() {
return toChecksumString();
}

/**
* Checks if the given string is an address with checksum (Keccak256).
*
* @param address a wallet address ('0x...')
* @return {@code true} if address with checksum
*/
static boolean isValidChecksum(String address) {
Keccak.Digest256 digest256 = new Keccak.Digest256();

digest256.update(
address.substring(2).toLowerCase().getBytes());

String hash = Hex.toHexString(digest256.digest());

for (int i = 0; i < 40; i++) {
char ch = address.charAt(i + 2);
int dg = Character.digit(hash.charAt(i), 16);

if ((dg > 7 && Character.toUpperCase(ch) != ch)
|| (dg <= 7 && Character.toLowerCase(ch) != ch))
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
* Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
*
* 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 io.emeraldpay.dshackle.upstream.ethereum.domain;

import io.emeraldpay.etherjar.hex.HexData;

public class BlockHash extends HexData {

public static final int SIZE_BYTES = 32;
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;

public BlockHash(byte[] value) {
super(value, SIZE_BYTES);
}

public static BlockHash from(byte[] value) {
if (value == null) {
throw new IllegalArgumentException("Null Hash");
}
if (value.length != SIZE_BYTES) {
throw new IllegalArgumentException("Invalid Block Hash length: " + value.length);
}
return new BlockHash(value);
}

public static BlockHash from(String value) {
if (value == null) {
throw new IllegalArgumentException("Null Hash");
}
if (value.length() != SIZE_HEX) {
throw new IllegalArgumentException("Invalid Block Hash length: " + value.length());
}
return new BlockHash(HexData.from(value).getBytes());
}

public static BlockHash empty() {
return new BlockHash(new byte[SIZE_BYTES]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.emeraldpay.dshackle.upstream.ethereum.domain;

import io.emeraldpay.etherjar.hex.HexData;
import org.bouncycastle.jcajce.provider.digest.Keccak;

import java.util.ArrayList;
import java.util.List;


public class Bloom extends HexData {

public static final int SIZE_BYTES = 256;
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;

public Bloom(byte[] value) {
super(value, SIZE_BYTES);
}

public static Bloom from(HexData value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
if (value.getSize() != SIZE_BYTES) {
throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES);
}
return new Bloom(value.getBytes());
}

public static Bloom from(String value) {
if (value == null) {
throw new IllegalArgumentException("Null input value");
}
if (value.length() != SIZE_HEX) {
throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX);
}
return new Bloom(HexData.from(value).getBytes());
}

public static Bloom empty() {
return new Bloom(new byte[SIZE_BYTES]);
}

public Bloom mergeWith(Bloom another) {
byte[] result = new byte[SIZE_BYTES];
for (int i = 0; i < SIZE_BYTES; i++) {
result[i] = (byte)(this.value[i] | another.value[i]);
}
return new Bloom(result);
}

public static Builder newBuilder() {
return new Builder();
}

public static int bytePosition(int bitpos) {
return bitpos >> 3;
}

public static int byteMask(int bytepos, int bitpos) {
int inbytepos = bitpos - (bytepos << 3);
return 1 << (inbytepos);
}

public static class Builder {
private final byte[] current = new byte[SIZE_BYTES];
private final Keccak.Digest256 keccak = new Keccak.Digest256();

public Builder add(HexData value) {
keccak.update(value.getBytes());
byte[] hash = keccak.digest();

for (int i = 0; i < 6; i+= 2) {
int high = hash[i] & 0x7;
int low = hash[i + 1] & 0xff;
int bitpos = (high << 8) + low;
int bytepos = bytePosition(bitpos);
int bytemask = byteMask(bytepos, bitpos);
current[SIZE_BYTES - bytepos - 1] |= bytemask & 0xff;
}
return this;
}

public Bloom build() {
return new Bloom(current);
}

public Filter buildFilter() {
List<Filter.Check> checks = new ArrayList<>();
for (int i = 0; i < SIZE_BYTES; i++) {
if (current[i] != 0) {
checks.add(new Filter.Check(i, current[i]));
}
}
return new Filter(checks.toArray(new Filter.Check[0]));
}
}

public static class Filter {
private final Check[] checks;

public Filter(Check[] checks) {
this.checks = checks;
}

public boolean isSet(Bloom bloom) {
for (Check check: checks) {
int actual = bloom.value[check.pos] & check.mask;
if (actual == 0) {
return false;
}
}
return true;
}

public static class Check {
private final int pos;
private final byte mask;

public Check(int pos, byte mask) {
if (pos >= SIZE_BYTES) {
throw new IllegalArgumentException("Out of filter boundaries position: " + pos);
}
if (mask == 0) {
throw new IllegalArgumentException("Mask cannot be empty");
}
this.pos = pos;
this.mask = mask;
}
}
}
}
Loading

0 comments on commit 31ed51a

Please sign in to comment.