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 }