Updated on 2026-08-14

This commit is contained in:
Tangem 2018-05-30 14:20:32 +03:00
parent 7ab858de77
commit ba7fa704be
18 changed files with 97 additions and 77 deletions

View file

@ -0,0 +1,493 @@
package com.tangem.util;
/**
* Created by Ilia on 29.09.2017.
*/
import android.util.Log;
import com.tangem.domain.wallet.Base58;
import com.tangem.domain.wallet.BitcoinException;
import com.tangem.domain.wallet.BitcoinOutputStream;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BTCUtils {
static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N
public static final long MIN_FEE_PER_KB = 10000;
public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1");
public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000;
public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L;
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
public static long calcMinimumFee(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
if (isZeroFeeAllowed(txLen, unspentOutputInfos, minOutput)) {
return 0;
}
return MIN_FEE_PER_KB * (1 + txLen / 1000);
}
public static boolean isZeroFeeAllowed(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
if (txLen < MAX_TX_LEN_FOR_NO_FEE && minOutput > MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE) {
long priority = 0;
for (UnspentOutputInfo output : unspentOutputInfos) {
if (output.confirmations > 0) {
priority += output.confirmations * output.value;
}
}
priority /= txLen;
if (priority > MIN_PRIORITY_FOR_NO_FEE) {
return true;
}
}
return false;
}
public static int getMaximumTxSize(Collection<UnspentOutputInfo> unspentOutputInfos, int outputsCount, boolean compressedPublicKey) throws BitcoinException {
if (unspentOutputInfos == null || unspentOutputInfos.isEmpty()) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No information about tx inputs provided");
}
int maxInputScriptLen = 73 + (compressedPublicKey ? 33 : 65);
return 9 + unspentOutputInfos.size() * (41 + maxInputScriptLen) + outputsCount * 33;
}
public static String publicKeyToAddress(byte[] publicKey) {
return publicKeyToAddress(false, publicKey);
}
public static String publicKeyToAddress(boolean testNet, byte[] publicKey) {
try {
byte[] hashedPublicKey = CryptoUtil.sha256ripemd160(publicKey);
byte[] addressBytes = new byte[1 + hashedPublicKey.length + 4];
addressBytes[0] = (byte) (testNet ? 111 : 0);
System.arraycopy(hashedPublicKey, 0, addressBytes, 1, hashedPublicKey.length);
MessageDigest digestSha = MessageDigest.getInstance("SHA-256");
digestSha.update(addressBytes, 0, addressBytes.length - 4);
byte[] check = digestSha.digest(digestSha.digest());
System.arraycopy(check, 0, addressBytes, hashedPublicKey.length + 1, 4);
return Base58.encodeBase58(addressBytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String toHex(byte[] bytes) {
if (bytes == null) {
return "";
}
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
unspentOutputs.get(currentInputPos).scriptForBuild = myScript;
int inputPos = currentInputPos;
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(body);
os.write(new byte[]{0x01, 0x00, 0x00, 0x00});
byte[] tx = os.toByteArray();
return tx;
}
public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, long amount, long change) throws BitcoinException, IOException {
int inputPos = -1;
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(body);
byte[] tx = os.toByteArray();
return tx;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = (byte)unspentOutputs.size();
forSign.write(inputCount); // input count
//hex str hash prev btc
for(int i = 0; i < inputCount; ++i)
{
UnspentOutputInfo outPut = unspentOutputs.get(i);
int outputIndex = outPut.outputIndex;
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash);
forSign.writeInt32(outputIndex); //output index in prev tx
if(inputPos ==-1 || i == inputPos)
{
// hex str 1976a914....88ac
forSign.write((byte)outPut.scriptForBuild.length);
forSign.write(outPut.scriptForBuild);
}
else
{
forSign.write(0x00);
}
//ffffffff
forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence
}
//02
byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte)sendScript.length);
forSign.write(sendScript);
if(change!=0){
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte)chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
//forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
return rawData;
}
int calculateSize(int inputCount, int outputCount)
{
int size = 0;
size += 4; // header
size += 1; //inputCount
//hex str hash prev btc
for(int i = 0; i < inputCount; ++i)
{
size += 32; //prevtx
size += 4; //outputIndex;
size += 1; //scriptLength
// size+=script;
size += 4; //ffffffff
}
size+=1; //outputCount
size+=8; //amount
size+=1;
// size+=script;
if(outputCount > 1)
{
size+=8;
size+=1;
//scriptLen;
}
size+=4;
return size;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = 1;
forSign.write(inputCount); // input count
//hex str hash prev btc
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash); //previos tx hash
//00000000
//byte indexOutput = outputIndex;
forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx
//forSign.write(0x00);
// hex str 1976a914....88ac
forSign.write((byte)script.length);
forSign.write(script);
//ffffffff
forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence
//02
byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte)sendScript.length);
forSign.write(sendScript);
if(change!=0){
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte)chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
//Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
return rawData;
}
public static ArrayList<byte[]> getPrevTX(String hex) throws BitcoinException {
byte[] rawTxByte = fromHex(hex);
Transaction baseTx = new Transaction(rawTxByte);
ArrayList<byte[]> prevHashes = new ArrayList<byte[]>();
for(int i =0; i < baseTx.inputs.length; ++i)
{
Transaction.Input input = baseTx.inputs[i];
prevHashes.add(input.outPoint.hash);
}
return prevHashes;
}
public static boolean isInput(String myAddress, String hex) throws BitcoinException {
byte[] rawTxByte = fromHex(hex);
Transaction baseTx = new Transaction(rawTxByte);
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
for(int i =0; i < baseTx.inputs.length; ++i)
{
Transaction.Input input = baseTx.inputs[i];
byte[] script = input.script.bytes;
// find outputs
if (Arrays.equals(myScript, script)){
return true;
}
}
return false;
}
public static ArrayList<UnspentOutputInfo> getOutputs(List<TangemCard.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
for(TangemCard.UnspentTransaction current: rawTxList)
{
byte[] rawTxByte = BTCUtils.fromHex(current.Raw);
if (rawTxByte == null)
{
continue;
}
Transaction baseTx = new Transaction(rawTxByte);
if(baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
throw new IllegalArgumentException("Unable to decode given transaction");
byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
String txHashForBuild = current.txID;
byte[] sign = null;
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
Transaction.Output output = baseTx.outputs[outputIndex];
// find outputs
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
}
}
}
return unspentOutputs;
}
public static byte[] fromHex(String s) {
if (s != null) {
try {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (!Character.isWhitespace(ch)) {
sb.append(ch);
}
}
s = sb.toString();
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hi = (Character.digit(s.charAt(i), 16) << 4);
int low = Character.digit(s.charAt(i + 1), 16);
if (hi >= 256 || low < 0 || low >= 16) {
return null;
}
data[i / 2] = (byte) (hi | low);
}
return data;
} catch (Exception ignored) {
}
}
return null;
}
public static byte[] reverse(byte[] bytes) {
byte[] result = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
result[i] = bytes[bytes.length - i - 1];
}
return result;
}
public static byte[] reverseInPlace(byte[] bytes) {
int len = bytes.length / 2;
for (int i = 0; i < len; i++) {
byte t = bytes[i];
bytes[i] = bytes[bytes.length - i - 1];
bytes[bytes.length - i - 1] = t;
}
return bytes;
}
public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException {
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes;
int indexOfOutputToSpend = -1;
for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) {
Transaction.Output output = tx.outputs[indexOfOutput];
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
indexOfOutputToSpend = indexOfOutput;
break;//only one input is supported for now
}
}
if (indexOfOutputToSpend == -1) {
throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress);
}
final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value;
if (spendableOutputValue < minAmount) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue);
}
return indexOfOutputToSpend;
}
public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException {
for (int i = 0; i < scripts.length; i++) {
Stack<byte[]> stack = new Stack<>();
spendTx.inputs[i].script.run(stack);//load signature+public key
scripts[i].run(i, spendTx, stack); //verify that this transaction able to spend that output
if (Transaction.Script.verifyFails(stack)) {
throw new Transaction.Script.ScriptInvalidException("Signature is invalid");
}
}
}
public static class FeeChangeAndSelectedOutputs {
public final long amountForRecipient, change, fee;
public final ArrayList<UnspentOutputInfo> outputsToSpend;
public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList<UnspentOutputInfo> outputsToSpend) {
this.fee = fee;
this.change = change;
this.amountForRecipient = amountForRecipient;
this.outputsToSpend = outputsToSpend;
}
}
public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List<UnspentOutputInfo> unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException {
long fee = 0;//calculated below
long change = 0;
long valueOfUnspentOutputs;
ArrayList<UnspentOutputInfo> outputsToSpend = new ArrayList<>();
if (amountToSend <= 0) {
//transfer all funds from these addresses to outputAddress
change = 0;
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
}
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, 1, isPublicKeyCompressed);
fee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, valueOfUnspentOutputs - MIN_FEE_PER_KB * (1 + txLen / 1000));
amountToSend = valueOfUnspentOutputs - fee - extraFee;
} else {
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
long updatedFee = MIN_FEE_PER_KB;
for (int i = 0; i < 3; i++) {
fee = updatedFee;
change = valueOfUnspentOutputs - fee - extraFee - amountToSend;
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, change > 0 ? 2 : 1, isPublicKeyCompressed);
updatedFee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, change > 0 ? Math.min(amountToSend, change) : amountToSend);
if (updatedFee == fee) {
break;
}
}
fee = updatedFee;
if (valueOfUnspentOutputs >= amountToSend + fee + extraFee) {
break;
}
}
}
if (amountToSend > valueOfUnspentOutputs - fee) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Not enough funds", valueOfUnspentOutputs - fee);
}
if (outputsToSpend.isEmpty()) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No outputs to spend");
}
if (fee + extraFee > MAX_ALLOWED_FEE) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_TOO_BIG, "Fee is too big", fee);
}
if (fee < 0 || extraFee < 0) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_LESS_THEN_ZERO, "Incorrect fee", fee);
}
if (change < 0) {
throw new BitcoinException(BitcoinException.ERR_CHANGE_IS_LESS_THEN_ZERO, "Incorrect change", change);
}
if (amountToSend < 0) {
throw new BitcoinException(BitcoinException.ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO, "Incorrect amount to send", amountToSend);
}
return new FeeChangeAndSelectedOutputs(fee + extraFee, change, amountToSend, outputsToSpend);
}
}

View file

@ -0,0 +1,270 @@
package com.tangem.util;
import android.util.Log;
import com.tangem.domain.wallet.ECDSASignatureETH;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.asn1.x9.X9IntegerConverter;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECAlgorithms;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import static org.bitcoinj.core.ECKey.CURVE;
import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER;
/**
* Created by Ilia on 15.02.2018.
*/
public class CryptoUtil {
public static boolean checkHashSign2(byte[] pub, byte[] hash, BigInteger r, BigInteger s)
{
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
return signer.verifySignature(hash, r, s);
}
public static boolean isCanonical(BigInteger s) {
return s.compareTo(HALF_CURVE_ORDER) <= 0;
}
public static BigInteger toCanonicalised(BigInteger s) {
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
if(!isCanonical(s)) {
BigInteger canon = CURVE.getN().subtract(s);
Log.e("TX_SIGN", "non Canonical S");
return canon;
}
return s;
}
private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
X9IntegerConverter x9 = new X9IntegerConverter();
byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
return CURVE.getCurve().decodePoint(compEnc);
}
public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignatureETH sig, byte[] messageHash) {
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
X9ECParameters params = SECNamedCurves.getByName("secp256k1");
ECDomainParameters CURVE2 = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
BigInteger n = CURVE2.getN(); // Curve order.
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = sig.r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
// specified in Section 2.3.7, where mlen = (log2 p)/8 or mlen = m/8.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
// conversion routine specified in Section 2.3.4. If this conversion routine outputs invalid, then
// do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
ECCurve.Fp curve = (ECCurve.Fp) CURVE2.getCurve();
BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime.
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
// So it's encoded in the recId.
ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
if (!R.multiply(n).isInfinity())
return null;
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
BigInteger e = new BigInteger(1, messageHash);
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
// ** is point multiplication and + is point addition (the EC group operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
// inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
BigInteger rInv = sig.r.modInverse(n);
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE2.getG(), eInvrInv, R, srInv);
return q.getEncoded(/* compressed */ false);
}
public static byte[] calcSign(byte[] priv, byte[] hash)
{
ECDSASigner signer = new ECDSASigner();
BigInteger d = new BigInteger(priv);
ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE);
signer.init(true, params);
BigInteger[] rs = signer.generateSignature(hash);
byte[] r = rs[0].toByteArray();
byte[] s = rs[1].toByteArray();
byte[] sign = new byte[64];
for(int i = 0; i < 32; ++i)
{
sign[i] = r[i];
sign[i+32] = s[i];
}
return sign;
}
public static boolean isEncodingCanonical(byte[] signature) {
// See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).
if (signature.length < 9 || signature.length > 73)
return false;
int hashType = (signature[signature.length-1] & 0xff) & ~0x80; // mask the byte to prevent sign-extension hurting us
if (hashType < 1 || hashType > 3)
return false;
// "wrong type" "wrong length marker"
if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3)
return false;
int lenR = signature[3] & 0xff;
if (5 + lenR >= signature.length || lenR == 0)
return false;
int lenS = signature[5+lenR] & 0xff;
if (lenR + lenS + 7 != signature.length || lenS == 0)
return false;
// R value type mismatch R value negative
if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80)
return false;
if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80)
return false; // R value excessively padded
// S value type mismatch S value negative
if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80)
return false;
if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
return false; // S value excessively padded
return true;
}
public static boolean VerifySign(byte[] tlvPublicKey, byte[] data, byte[] tlvSignature) throws IOException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
Signature signature = Signature.getInstance("SHA256withECDSA");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey);
ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
PublicKey publicKey = factory.generatePublic(keySpec);
signature.initVerify(publicKey);
signature.update(data);
ASN1EncodableVector v = new ASN1EncodableVector();
int size = tlvSignature.length / 2;
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, 0, size))));
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, size, size * 2))));
byte[] sigDer = new DERSequence(v).getEncoded();
return signature.verify(sigDer);
}
private static byte[] leaderZero(byte[] s)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (s[0] > 0x7f) {
baos.write((byte) 0x00);
}
for(int i = 0; i < 32; ++i)
baos.write(s[i]);
return baos.toByteArray();
}
public static boolean checkHashSign(byte[] pub, byte[] hash, byte[] sign)
{
byte[] rtmp = new byte[32];
byte[] stmp = new byte[32];
for(int i = 0; i< 32; ++i)
{
rtmp[i] = sign[i];
stmp[i] = sign[i+32];
}
leaderZero(rtmp);
byte[] r2 = leaderZero(rtmp);
byte[] s2 = leaderZero(stmp);
BigInteger r = new BigInteger(r2);
BigInteger s = new BigInteger(s2);
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
return signer.verifySignature(hash, r, s);
}
public static byte[] doubleSha256(byte[] bytes) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return sha256.digest(sha256.digest(bytes));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] sha256ripemd160(byte[] publicKey) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] sha256hash = sha256.digest(publicKey);
byte[] hashedPublicKey = Util.calculateRIPEMD160(sha256hash);
return hashedPublicKey;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -0,0 +1,132 @@
package com.tangem.util;
import com.tangem.domain.wallet.BitcoinOutputStream;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequenceGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
/**
* Created by Ilia on 15.02.2018.
*/
public class DerEncodingUtil {
public static byte[] PackInteger(byte[] s)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)0x02);
byte length = (byte)s.length;
if (s[0] > 0x7f) {
baos.write((byte)(length+1));
baos.write((byte) 0x00);
}
else {
baos.write((byte)length);
}
for(int i = 0; i < length; ++i)
baos.write(s[i]);
return baos.toByteArray();
}
public static byte[] packSignDer(BigInteger r, BigInteger s, byte[] pubKey) throws IOException
{
byte[] signDer = DerEncoding(r, s);
BitcoinOutputStream packKey = new BitcoinOutputStream();
packKey.write((byte)0x41);
packKey.write(pubKey);
byte[] keyArray = packKey.toByteArray();
BitcoinOutputStream result = new BitcoinOutputStream();
result.write((byte)(signDer.length+1));
result.write(signDer);
result.write((byte)0x1);
result.write(keyArray);
return result.toByteArray();
}
public static byte[] packSignDerBitcoinCash(BigInteger r, BigInteger s, byte[] pubKey) throws IOException
{
byte[] signDer = DerEncoding(r, s);
BitcoinOutputStream packKey = new BitcoinOutputStream();
packKey.write((byte)0x21); //compress key
packKey.write(pubKey);
byte[] keyArray = packKey.toByteArray();
BitcoinOutputStream result = new BitcoinOutputStream();
result.write((byte)(signDer.length+1));
result.write(signDer);
result.write((byte)0x41);
result.write(keyArray);
return result.toByteArray();
}
public static byte[] DerEncoding(BigInteger r, BigInteger s) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos.toByteArray();
}
public static byte[] DerEncoding(byte[] sign)
{
byte[] r = sign;
byte[] s = new byte[32];
for(int i =0; i < 32; ++i)
{
s[i] = sign[i+32];
}
byte[] newR = PackInteger(r);
byte[] newS = PackInteger(s);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)(newR.length+newS.length+2));
baos.write((byte)newR.length);
baos.write(newR, 0, newR.length);
baos.write((byte)newS.length);
baos.write(newS, 0, newS.length);
return baos.toByteArray();
}
public static byte[] DerEncodingBI(BigInteger[] sign)
{
byte[] r = sign[0].toByteArray();
byte[] s = sign[1].toByteArray();
byte[] newR = PackInteger(r);
byte[] newS = PackInteger(s);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)(newR.length+newS.length+2));
baos.write((byte)newR.length);
baos.write(newR, 0, newR.length);
baos.write((byte)newS.length);
baos.write(newS, 0, newS.length);
return baos.toByteArray();
}
}