| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.tuweni.eth.precompiles |
| |
| import com.sun.jna.ptr.IntByReference |
| import org.apache.tuweni.bytes.Bytes |
| import org.apache.tuweni.bytes.Bytes32 |
| import org.apache.tuweni.bytes.MutableBytes |
| import org.apache.tuweni.crypto.Hash |
| import org.apache.tuweni.crypto.SECP256K1 |
| import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings |
| import java.math.BigInteger |
| import kotlin.experimental.and |
| |
| /** |
| * Contract returning a copy of the input. |
| */ |
| class IDPrecompiledContract : PrecompileContract { |
| override fun run(input: Bytes): Result = Result(gas = input.size().toLong() / 32 * 3 + 15, output = input.copy()) |
| } |
| |
| /** |
| * Contract recovering a hash from a signature. |
| */ |
| class ECRECPrecompiledContract : PrecompileContract { |
| override fun run(input: Bytes): Result { |
| val padded = if (input.size() < 128) |
| Bytes.wrap(Bytes.repeat(0, 128 - input.size()), input) |
| else |
| input |
| val h = Bytes32.wrap(padded.slice(0, 32)) |
| val v = padded.slice(32, 32) |
| val r = padded.slice(64, 32) |
| val s = padded.slice(96, 32) |
| if (v.numberOfLeadingZeroBytes() != 31) { |
| return Result(3000, Bytes.EMPTY) |
| } |
| try { |
| val signature = SECP256K1.Signature.create(v.get(31), r.toUnsignedBigInteger(), s.toUnsignedBigInteger()) |
| val pkey = SECP256K1.PublicKey.recoverFromHashAndSignature(h, signature) ?: return Result(3000, Bytes.EMPTY) |
| val hashed: Bytes32 = Hash.keccak256(pkey.bytes()) |
| return Result(3000, Bytes32.leftPad(hashed.slice(12))) |
| } catch (e: IllegalArgumentException) { |
| return Result(3000, Bytes.EMPTY) |
| } |
| } |
| } |
| |
| /** |
| * SHA-2-256 Hash precompile contract. |
| */ |
| class Sha256PrecompiledContract : PrecompileContract { |
| override fun run(input: Bytes): Result { |
| return Result(12 * input.size().toLong() / 32 + 60, Hash.sha2_256(input)) |
| } |
| } |
| |
| /** |
| * RIPEMD160 precompile contract. |
| */ |
| class RIPEMD160PrecompiledContract : PrecompileContract { |
| override fun run(input: Bytes): Result { |
| return Result(120 * input.size().toLong() / 32 + 600, Hash.digestUsingAlgorithm(input, "RIPEMD160")) |
| } |
| } |
| |
| class ModExpPrecompileContract : PrecompileContract { |
| companion object { |
| val BASE_OFFSET = BigInteger.valueOf(96) |
| |
| fun extractParameter(input: Bytes, offset: BigInteger, length: Int): BigInteger { |
| return if (BigInteger.valueOf(input.size().toLong()).compareTo(offset) <= 0) { |
| BigInteger.ZERO |
| } else { |
| val min = Math.max(0, Math.min(length, input.size() - offset.toInt() - length)) |
| var sliced = input.slice(offset.toInt(), min) |
| if (sliced.size() > 32) { |
| sliced = sliced.slice(sliced.size() - 32) |
| } |
| Bytes32.rightPad(sliced).toUnsignedBigInteger() |
| } |
| } |
| } |
| |
| override fun run(input: Bytes): Result { |
| val padded = if (input.size() < 96) |
| Bytes.wrap(Bytes.repeat(0, 96 - input.size()), input) |
| else |
| input |
| val baseLength = padded.slice(0, 32).toUnsignedBigInteger() |
| val exponentLength = padded.slice(32, 32).toUnsignedBigInteger() |
| val modulusLength = padded.slice(64, 32).toUnsignedBigInteger() |
| |
| if (baseLength == BigInteger.ZERO && modulusLength == BigInteger.ZERO) { |
| return Result(0, Bytes.EMPTY) |
| } |
| val exponentOffset = BASE_OFFSET.add(baseLength) |
| val modulusOffset = exponentOffset.add(exponentLength) |
| val base = extractParameter(input, BASE_OFFSET, baseLength.toInt()) |
| val exp = extractParameter(input, exponentOffset, exponentLength.toInt()) |
| val mod = extractParameter(input, modulusOffset, modulusLength.toInt()) |
| |
| val modExp = if (mod.compareTo(BigInteger.ZERO) == 0) { |
| MutableBytes.EMPTY |
| } else { |
| Bytes.wrap(base.modPow(exp, mod).toByteArray()).trimLeadingZeros() |
| } |
| var buffer = modulusLength.toInt() - modExp.size() |
| if (buffer < 0) { |
| buffer = 0 |
| } |
| |
| return Result(0, Bytes.wrap(Bytes.repeat(0, buffer), modExp)) |
| } |
| } |
| |
| class AltBN128PrecompiledContract(private val operation: Byte, val inputLen: Int, val baseCost: Long, val pairingGasCost: Long) : PrecompileContract { |
| |
| override fun run(input: Bytes): Result { |
| val parameters = input.size() / 192 |
| val gasCost = pairingGasCost * parameters + baseCost |
| val result = ByteArray(LibEthPairings.EIP196_PREALLOCATE_FOR_RESULT_BYTES) |
| val error = ByteArray(LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES) |
| |
| val o_len = IntByReference(LibEthPairings.EIP196_PREALLOCATE_FOR_RESULT_BYTES) |
| val err_len = IntByReference(LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES) |
| val inputSize = Math.min(inputLen, input.size()) |
| val errorNo: Int = LibEthPairings.eip196_perform_operation( |
| operation, |
| input.slice(0, inputSize).toArrayUnsafe(), |
| inputSize, |
| result, |
| o_len, |
| error, |
| err_len |
| ) |
| return if (errorNo == 0) { |
| Result(gasCost, Bytes.wrap(result, 0, o_len.getValue())) |
| } else { |
| Result(gasCost, Bytes.EMPTY) |
| } |
| } |
| } |
| |
| class Blake2BFPrecompileContract : PrecompileContract { |
| override fun run(input: Bytes): Result { |
| if (input.size() != 213) { |
| // Input is malformed, we can't read the number of rounds. |
| // Precompile can't be executed so we set its price to 0. |
| return Result(0, Bytes.EMPTY) |
| } |
| if (input.get(212).and(0xFE.toByte()) != 0x00.toByte()) { |
| // Input is malformed, F value can be only 0 or 1 |
| return Result(0, Bytes.EMPTY) |
| } |
| |
| val rounds = Bytes.wrap(Bytes.repeat(0, 4), input.slice(0, 4)) |
| |
| return Result(rounds.toLong(), Hash.digestUsingAlgorithm(input, "BLAKE2BF")) |
| } |
| } |