package info.frostfs.sdk; import info.frostfs.sdk.exceptions.ProcessFrostFSException; import info.frostfs.sdk.exceptions.ValidationFrostFSException; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.sec.SECObjectIdentifiers; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.math.ec.ECPoint; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import static info.frostfs.sdk.Helper.getRipemd160; import static info.frostfs.sdk.Helper.getSha256; import static info.frostfs.sdk.constants.ErrorConst.*; import static java.util.Objects.isNull; import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray; public class KeyExtension { public static final byte NEO_ADDRESS_VERSION = 0x35; private static final String CURVE_NAME = "secp256r1"; private static final String SECURITY_ALGORITHM = "EC"; private static final int PS_IN_HASH160 = 0x0C; private static final int DECODE_ADDRESS_LENGTH = 21; private static final int COMPRESSED_PUBLIC_KEY_LENGTH = 33; private static final int UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65; private static final int CHECK_SIG_DESCRIPTOR = ByteBuffer .wrap(getSha256("System.Crypto.CheckSig".getBytes(StandardCharsets.US_ASCII))) .order(ByteOrder.LITTLE_ENDIAN).getInt(); private KeyExtension() { } public static byte[] compress(byte[] publicKey) { checkInputValue(publicKey); if (publicKey.length != UNCOMPRESSED_PUBLIC_KEY_LENGTH) { throw new ValidationFrostFSException(String.format( UNCOMPRESSED_PUBLIC_KEY_WRONG_LENGTH_TEMPLATE, UNCOMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length )); } var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); var point = secp256R1.getCurve().decodePoint(publicKey); return point.getEncoded(true); } public static byte[] getPrivateKeyFromWIF(String wif) { if (StringUtils.isEmpty(wif)) { throw new ValidationFrostFSException(INPUT_PARAM_IS_MISSING); } var data = Base58.base58CheckDecode(wif); return Arrays.copyOfRange(data, 1, data.length - 1); } public static byte[] loadPublicKey(byte[] privateKey) { checkInputValue(privateKey); X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); ECDomainParameters domain = new ECDomainParameters( params.getCurve(), params.getG(), params.getN(), params.getH() ); ECPoint q = domain.getG().multiply(new BigInteger(1, privateKey)); ECPublicKeyParameters publicParams = new ECPublicKeyParameters(q, domain); return publicParams.getQ().getEncoded(true); } public static PrivateKey loadPrivateKey(byte[] privateKey) { checkInputValue(privateKey); X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); ECDomainParameters domain = new ECDomainParameters( params.getCurve(), params.getG(), params.getN(), params.getH() ); ECPrivateKeyParameters ecParams = new ECPrivateKeyParameters(fromUnsignedByteArray(privateKey), domain); ECParameterSpec ecParameterSpec = new ECNamedCurveSpec( CURVE_NAME, params.getCurve(), params.getG(), params.getN(), params.getH() ); ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(ecParams.getD(), ecParameterSpec); try { KeyFactory kf = KeyFactory.getInstance(SECURITY_ALGORITHM); return kf.generatePrivate(privateKeySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new ProcessFrostFSException(e); } } public static PublicKey getPublicKeyFromBytes(byte[] publicKey) { checkInputValue(publicKey); if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new ValidationFrostFSException(String.format( COMPRESSED_PUBLIC_KEY_WRONG_LENGTH_TEMPLATE, COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length )); } X9ECParameters secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); ECDomainParameters domain = new ECDomainParameters( secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH() ); var ecPoint = secp256R1.getCurve().decodePoint(publicKey); var publicParams = new ECPublicKeyParameters(ecPoint, domain); java.security.spec.ECPoint point = new java.security.spec.ECPoint( publicParams.getQ().getRawXCoord().toBigInteger(), publicParams.getQ().getRawYCoord().toBigInteger() ); ECParameterSpec ecParameterSpec = new ECNamedCurveSpec( CURVE_NAME, secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH(), secp256R1.getSeed() ); ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecParameterSpec); try { KeyFactory kf = KeyFactory.getInstance(SECURITY_ALGORITHM); return kf.generatePublic(publicKeySpec); } catch (Exception e) { throw new ProcessFrostFSException(e); } } public static byte[] getScriptHash(byte[] publicKey) { checkInputValue(publicKey); var script = createSignatureRedeemScript(publicKey); return getRipemd160(getSha256(script)); } public static String publicKeyToAddress(byte[] publicKey) { checkInputValue(publicKey); if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new ValidationFrostFSException(String.format( ENCODED_COMPRESSED_PUBLIC_KEY_WRONG_LENGTH_TEMPLATE, COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length )); } return toAddress(getScriptHash(publicKey)); } private static String toAddress(byte[] scriptHash) { checkInputValue(scriptHash); byte[] data = new byte[DECODE_ADDRESS_LENGTH]; data[0] = NEO_ADDRESS_VERSION; System.arraycopy(scriptHash, 0, data, 1, scriptHash.length); return Base58.base58CheckEncode(data); } private static byte[] getBytes(int value) { byte[] buffer = new byte[4]; for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) (value >> i * Byte.SIZE); } return buffer; } private static byte[] createSignatureRedeemScript(byte[] publicKey) { checkInputValue(publicKey); if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new ValidationFrostFSException(String.format( ENCODED_COMPRESSED_PUBLIC_KEY_WRONG_LENGTH_TEMPLATE, COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length )); } var script = new byte[]{PS_IN_HASH160, COMPRESSED_PUBLIC_KEY_LENGTH}; //PUSHDATA1 33 script = ArrayHelper.concat(script, publicKey); script = ArrayHelper.concat(script, new byte[]{UNCOMPRESSED_PUBLIC_KEY_LENGTH}); //SYSCALL script = ArrayHelper.concat(script, getBytes(CHECK_SIG_DESCRIPTOR)); //Neo_Crypto_CheckSig return script; } private static void checkInputValue(byte[] data) { if (isNull(data) || data.length == 0) { throw new ValidationFrostFSException(INPUT_PARAM_IS_MISSING); } } }