package info.FrostFS.sdk; 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 org.bouncycastle.util.BigIntegers.fromUnsignedByteArray; public class KeyExtension { public static final byte NEO_ADDRESS_VERSION = 0x35; 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(); public static byte[] compress(byte[] publicKey) { if (publicKey.length != UNCOMPRESSED_PUBLIC_KEY_LENGTH) { throw new IllegalArgumentException( String.format("Compress argument isn't uncompressed public key. Expected length=%s, actual=%s", 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) { var data = Base58.base58CheckDecode(wif); return Arrays.copyOfRange(data, 1, data.length - 1); } public static byte[] loadPublicKey(byte[] 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) { 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( "secp256r1", params.getCurve(), params.getG(), params.getN(), params.getH() ); ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(ecParams.getD(), ecParameterSpec); try { KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePrivate(privateKeySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } } public static PublicKey getPublicKeyFromBytes(byte[] publicKey) { if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new IllegalArgumentException( String.format("Decompress argument isn't compressed public key. Expected length=%s, actual=%s", 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( "secp256r1", secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH(), secp256R1.getSeed() ); ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecParameterSpec); try { KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePublic(publicKeySpec); } catch (Exception e) { throw new RuntimeException(e); } } public static byte[] getScriptHash(byte[] publicKey) { var script = createSignatureRedeemScript(publicKey); return getRIPEMD160(getSha256(script)); } public static String publicKeyToAddress(byte[] publicKey) { if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new IllegalArgumentException( String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s", COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) ); } return toAddress(getScriptHash(publicKey), NEO_ADDRESS_VERSION); } private static String toAddress(byte[] scriptHash, byte version) { byte[] data = new byte[21]; data[0] = 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 * 8); } return buffer; } private static byte[] createSignatureRedeemScript(byte[] publicKey) { if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { throw new IllegalArgumentException( String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s", COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) ); } var script = new byte[]{0x0c, COMPRESSED_PUBLIC_KEY_LENGTH}; //PUSHDATA1 33 script = ArrayHelper.concat(script, publicKey); script = ArrayHelper.concat(script, new byte[]{0x41}); //SYSCALL script = ArrayHelper.concat(script, getBytes(CHECK_SIG_DESCRIPTOR)); //Neo_Crypto_CheckSig return script; } }