Coverage Summary for Class: GetMultisigScriptHash (co.rsk.pcc.bto)

Class Class, % Method, % Line, %
GetMultisigScriptHash 0% (0/1) 0% (0/7) 0% (0/40)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2019 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18  19 package co.rsk.pcc.bto; 20  21 import co.rsk.bitcoinj.core.BtcECKey; 22 import co.rsk.bitcoinj.script.Script; 23 import co.rsk.bitcoinj.script.ScriptBuilder; 24 import co.rsk.pcc.ExecutionEnvironment; 25 import co.rsk.pcc.NativeMethod; 26 import co.rsk.pcc.exception.NativeContractIllegalArgumentException; 27 import org.ethereum.core.CallTransaction; 28 import org.ethereum.util.ByteUtil; 29  30 import java.math.BigInteger; 31 import java.util.ArrayList; 32 import java.util.List; 33  34 /** 35  * This implements the "getMultisigScriptHash" method 36  * that belongs to the HDWalletUtils native contract. 37  * 38  * @author Ariel Mendelzon 39  */ 40 public class GetMultisigScriptHash extends NativeMethod { 41  private final CallTransaction.Function function = CallTransaction.Function.fromSignature( 42  "getMultisigScriptHash", 43  new String[]{"int256", "bytes[]"}, 44  new String[]{"bytes"} 45  ); 46  47  private final static int COMPRESSED_PUBLIC_KEY_LENGTH = 33; 48  private final static int UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65; 49  50  private final static long BASE_COST = 20_000L; 51  private final static long COST_PER_EXTRA_KEY = 700L; 52  53  private final static int MINIMUM_REQUIRED_KEYS = 2; 54  55  // Enforced by the 520-byte size limit of the redeem script 56  // (see https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#520byte_limitation_on_serialized_script_size) 57  private final static int MAXIMUM_ALLOWED_KEYS = 15; 58  59  private final static String REQUIRED_SIGNATURE_NULL_OR_ZERO = "Minimum required signatures must be present and greater than zero"; 60  private final static String PUBLIC_KEYS_NULL_OR_ONE = String.format("At least %d public keys are required", MINIMUM_REQUIRED_KEYS); 61  private final static String INVALID_REQUIRED_SIGNATURE_AND_PUBLIC_KEYS_PAIR = "Given public keys (%d) are less than the minimum required signatures (%d)"; 62  63  64  public GetMultisigScriptHash(ExecutionEnvironment executionEnvironment) { 65  super(executionEnvironment); 66  } 67  68  @Override 69  public CallTransaction.Function getFunction() { 70  return function; 71  } 72  73  @Override 74  public Object execute(Object[] arguments) throws NativeContractIllegalArgumentException { 75  if (arguments == null || arguments[0] == null) { 76  throw new NativeContractIllegalArgumentException(REQUIRED_SIGNATURE_NULL_OR_ZERO); 77  } 78  int minimumSignatures = ((BigInteger) arguments[0]).intValueExact(); 79  Object[] publicKeys = (Object[]) arguments[1]; 80  81  if (minimumSignatures <= 0) { 82  throw new NativeContractIllegalArgumentException(REQUIRED_SIGNATURE_NULL_OR_ZERO); 83  } 84  85  if (publicKeys == null || publicKeys.length < MINIMUM_REQUIRED_KEYS) { 86  throw new NativeContractIllegalArgumentException(PUBLIC_KEYS_NULL_OR_ONE); 87  } 88  89  if (publicKeys.length < minimumSignatures) { 90  throw new NativeContractIllegalArgumentException(String.format( 91  INVALID_REQUIRED_SIGNATURE_AND_PUBLIC_KEYS_PAIR, 92  publicKeys.length, minimumSignatures 93  )); 94  } 95  96  if (publicKeys.length > MAXIMUM_ALLOWED_KEYS) { 97  throw new NativeContractIllegalArgumentException(String.format( 98  "Given public keys (%d) are more than the maximum allowed signatures (%d)", 99  publicKeys.length, MAXIMUM_ALLOWED_KEYS 100  )); 101  } 102  103  List<BtcECKey> btcPublicKeys = new ArrayList<>(); 104  for (Object o: publicKeys) { 105  byte[] publicKey = (byte[]) o; 106  if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH && publicKey.length != UNCOMPRESSED_PUBLIC_KEY_LENGTH) { 107  throw new NativeContractIllegalArgumentException(String.format( 108  "Invalid public key length: %d", publicKey.length 109  )); 110  } 111  112  // Avoid extra work by not recalculating compressed keys on already compressed keys 113  try { 114  BtcECKey btcPublicKey = BtcECKey.fromPublicOnly(publicKey); 115  if (publicKey.length == UNCOMPRESSED_PUBLIC_KEY_LENGTH) { 116  btcPublicKey = BtcECKey.fromPublicOnly(btcPublicKey.getPubKeyPoint().getEncoded(true)); 117  } 118  btcPublicKeys.add(btcPublicKey); 119  } catch (IllegalArgumentException e) { 120  throw new NativeContractIllegalArgumentException(String.format( 121  "Invalid public key format: %s", ByteUtil.toHexString(publicKey) 122  ), e); 123  } 124  } 125  126  Script multisigScript = ScriptBuilder.createP2SHOutputScript(minimumSignatures, btcPublicKeys); 127  128  return multisigScript.getPubKeyHash(); 129  } 130  131  @Override 132  public long getGas(Object[] parsedArguments, byte[] originalData) { 133  Object[] keys = ((Object[]) parsedArguments[1]); 134  135  if (keys == null || keys.length < 2) { 136  return BASE_COST; 137  } 138  139  // Base cost is the cost for 2 keys (the minimum). 140  // Then a fee is payed per additional key. 141  return BASE_COST + (keys.length - 2) * COST_PER_EXTRA_KEY; 142  } 143  144  @Override 145  public boolean isEnabled() { 146  return true; 147  } 148  149  @Override 150  public boolean onlyAllowsLocalCalls() { 151  return false; 152  } 153 }