Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Milagro crypto-library with BLS12-381 implementation. #1236

Closed
wants to merge 12 commits into from
Closed
1 change: 1 addition & 0 deletions ethereumj-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ dependencies {
}

compile "org.xerial.snappy:snappy-java:1.1.4" // Snappy compression
compile "org.miracl.milagro.amcl:milagro-crypto-java:0.4.0" // BLS381 crypto implementation

// used to hide spring initialization logs messages in samples
optional "org.slf4j:jcl-over-slf4j:${slf4jVersion}"
Expand Down
23 changes: 22 additions & 1 deletion ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,37 @@ public static byte[] randomHash() {
* there is an agreement on using first 256-bits of BLAKE2B-512 digest
*/
public static byte[] blake2b(byte[] data) {
return blake2b(data, 32);
}

/**
* Blake2b cryptographic hash function
* 512-bytes version is used
* @param data Input
* @param nBytes Number of bytes for output, 64 not truncated, if less, truncates output
* @return hash
*/
private static byte[] blake2b(byte[] data, int nBytes) {
if (nBytes > 64 || nBytes < 0) throw new RuntimeException("nBytes should be in range of 512 bits (64 bytes)");
try {
MessageDigest digest = MessageDigest.getInstance("BLAKE2B-512");
digest.update(data);
return Arrays.copyOf(digest.digest(), 32);
return Arrays.copyOf(digest.digest(), nBytes);
} catch (NoSuchAlgorithmException e) {
LOG.error("Can't find such algorithm", e);
throw new RuntimeException(e);
}
}

/**
* Blake2b cryptographic hash function
* 512 bit result is truncated to 384 bits (48 bytes) output
* @return hash
*/
public static byte[] blake2b384(byte[] data) {
return blake2b(data, 48);
}

public static String shortHash(byte[] hash) {
return Hex.toHexString(hash).substring(0, 6);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.ethereum.sharding.crypto;

/**
* Common interface for BLS381-12 implementation
*/
public interface BLS381 {

/**
* Generates random private key
* Private key is correct scalar
*/
Scalar generateRandomPrivate();

/**
* Restores scalar from byte[]
*/
Scalar restoreScalar(byte[] value);

/**
* Restores point on elliptic curve #1
* @return ECP1
*/
P1 restoreECP1(byte[] value);

/**
* Restores point on elliptic curve #2
* @return ECP2
*/
P2 restoreECP2(byte[] value);

/**
* @return Generator of ECP1
*/
P1 generator();

// FIXME: REMOVE ME
/**
* Maps byte[] value to ECP2
* @return eligible mapping
*/
@Deprecated
P2 mapToECP2(byte[] value);

/**
* Pairing function
* @param point2 Point on ECP2
* @param point1 Point on ECP1
* @return element of FP12
*/
FP12 pair(P2 point2, P1 point1);

/**
* Represents scalar compliant with curve order
*/
interface Scalar {
byte[] asByteArray();
}

/**
* Represents point on ECP1 (elliptic curve #1)
*/
interface P1 {
P1 mul(Scalar value);

void add(P1 value);

byte[] asByteArray();
}

/**
* Represents point on ECP2 (elliptic curve #2)
*/
interface P2 {
P2 mul(Scalar value);

void add(P2 value);

byte[] asByteArray();
}

/**
* Represents element of FP12 extension field
*/
interface FP12 {
boolean equals(FP12 other);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.ethereum.sharding.crypto;

import org.ethereum.util.ByteUtil;

import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;

import static org.ethereum.sharding.crypto.BLS381.Scalar;
import static org.ethereum.sharding.crypto.BLS381.P1;
import static org.ethereum.sharding.crypto.BLS381.P2;
import static org.ethereum.sharding.crypto.BLS381.FP12;

/**
* This is an implementation of signature creation, verification and
* aggregation based on the BLS12-381 pairing-friendly elliptic curve.
*
* For curve parameters read original zkcrypto docs:
* https://github.com/zkcrypto/pairing/tree/master/src/bls12_381/
* Why this curve was chosen:
* https://z.cash/blog/new-snark-curve/
* Eth 2.0 specific implementation
* https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md
*/
public class BLS381Sign implements Sign {

public static int SCALAR_SIZE = 48;

public static int ECP_POINT_SIZE = 2 * SCALAR_SIZE + 1;

public static int ECP2_POINT_SIZE = 4 * SCALAR_SIZE;

public static byte[] DOMAIN = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

// Milagro implementation of BLS12-381 curve is used underneath.
private BLS381 bls381 = new MilagroBLS381();

/**
* Creates new random pair of Signature (Private) and
* Verification (Public) keys
*/
public KeyPair newKeyPair() {
Scalar sigKey = bls381.generateRandomPrivate();
P1 verKey = bls381.generator().mul(sigKey);

KeyPair res = new KeyPair();
res.sigKey = new BigInteger(sigKey.asByteArray());
res.verKey = new BigInteger(verKey.asByteArray());

return res;
}

/**
* Derives public key from private
*/
@Override
public BigInteger privToPub(BigInteger privKey) {
Scalar sigKey = bls381.restoreScalar(ByteUtil.bigIntegerToBytes(privKey, SCALAR_SIZE));
P1 verKey = bls381.generator().mul(sigKey);

return new BigInteger(verKey.asByteArray());
}

/**
* Signs the message using its hash
* @param msgHash Message hash, expects 384 bits
* @param domain Implementation specific byte value which "salts"
* the way msgHash is mapped to EC2
* @param privateKey Private key
* @return signature, point on G2 (bytes)
*/
@Override
public byte[] sign(byte[] msgHash, byte[] domain, BigInteger privateKey) {
P2 hashPointECP2 = mapToECP2(msgHash, domain);
P2 signature = hashPointECP2.mul(bls381.restoreScalar(ByteUtil.bigIntegerToBytes(privateKey, SCALAR_SIZE)));

return signature.asByteArray();
}

/**
* Verifies 384-bit hash and signature sig using Verification (Public)
* key verKey. Returns true if verification succeeded.
* @param signature Signature, G2 point
* @param msgHash Message hash
* @param domain Implementation specific byte value which "salts"
* the way msgHash is mapped to EC2
* @param publicKey Verification key, G1 point
* @return true if message is signature is done with the key
*/
@Override
public boolean verify(byte[] signature, byte[] msgHash, BigInteger publicKey, byte[] domain) {
// signature to ECP2, publicKey to ECP
byte[] verKeyBytes = ByteUtil.bigIntegerToBytes(publicKey, ECP_POINT_SIZE);
P2 sigPoint = bls381.restoreECP2(signature);
P1 publicKeyPoint = bls381.restoreECP1(verKeyBytes);

P1 generator = bls381.generator();
// TODO: domain-specific tests
P2 point = mapToECP2(msgHash, domain);

FP12 lhs = bls381.pair(sigPoint, generator);
FP12 rhs = bls381.pair(point, publicKeyPoint);

return lhs.equals(rhs);
}

private P2 mapToECP2(byte[] msgHash, byte[] domain) {
// TODO: implement me
// x1 = hash(bytes8(domain) + b'\x01' + m)
// x2 = hash(bytes8(domain) + b'\x02' + m)
// x_coord = FQ2([x1, x2]) # x1 + x2 * i
// while 1:
// x_cubed_plus_b2 = x_coord ** 3 + FQ2([4,4])
// y_coord = mod_sqrt(x_cubed_plus_b2)
// if y_coord is not None:
// break
// x_coord += FQ2([1, 0]) # Add one until we get a quadratic residue
// assert is_on_curve((x_coord, y_coord))
// return multiply((x_coord, y_coord), G2_cofactor)

// qmod = field_modulus ** 2 - 1
// eighth_roots_of_unity = [FQ2([1,1]) ** ((qmod * k) // 8) for k in range(8)]
//
// def mod_sqrt(val):
// candidate_sqrt = val ** ((qmod + 8) // 16)
// check = candidate_sqrt ** 2 / val
// if check in eighth_roots_of_unity[::2]:
// return candidate_sqrt / eighth_roots_of_unity[eighth_roots_of_unity.index(check) // 2]
// return None
return bls381.mapToECP2(msgHash);
}

/**
* Aggregates several signatures in one
*/
@Override
public byte[] aggSigns(List<byte[]> signatures) {
List<P2> sigs = signatures.stream()
.map((byte[] signature) -> bls381.restoreECP2(signature))
.collect(Collectors.toList());

P2 g2Agg = null;
for(P2 sig: sigs) {
if (g2Agg == null) {
g2Agg = sig;
} else {
g2Agg.add(sig);
}
}

return g2Agg.asByteArray();
}

/**
* Aggregates public keys
*/
@Override
public BigInteger aggPubs(List<BigInteger> verificationKeys) {
List<P1> verKeys = verificationKeys.stream()
.map((BigInteger b) -> bls381.restoreECP1(ByteUtil.bigIntegerToBytes(b, ECP_POINT_SIZE)))
.collect(Collectors.toList());

P1 g1Agg = null;

for(P1 ver: verKeys) {
if (g1Agg == null) {
g1Agg = ver;
} else {
g1Agg.add(ver);
}
}

return new BigInteger(g1Agg.asByteArray());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,55 @@
*/
package org.ethereum.sharding.crypto;

import org.ethereum.util.ByteUtil;
import org.ethereum.util.FastByteComparisons;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.List;

import static org.ethereum.crypto.HashUtil.blake2b;
import static org.ethereum.crypto.HashUtil.sha3;

/**
* Dummy signature implementation without real crypto underneath
*/
public class DummySign implements Sign {

SecureRandom random = new SecureRandom();

/**
* Sign the message
*/
@Override
public Signature sign(byte[] msg, BigInteger privateKey) {
public byte[] sign(byte[] msgHash, byte[] domain, BigInteger privateKey) {
byte[] rSource = sha3(privateKey.toByteArray());
byte[] sSource = sha3(msg, privateKey.toByteArray());
Signature res = new Signature();
res.r = new BigInteger(rSource);
res.s = new BigInteger(sSource);
byte[] sSource = sha3(msgHash, privateKey.toByteArray());

return res;
return ByteUtil.merge(rSource, sSource);
}

/**
* Verifies whether signature is made by signer with pubKey
*/
@Override
public boolean verify(Signature signature, byte[] msg, BigInteger pubKey) {
public boolean verify(byte[] signature, byte[] msgHash, BigInteger pubKey, byte[] domain) {
byte[] rSource = sha3(pubKey.toByteArray());
byte[] sSource = sha3(msg, pubKey.toByteArray());
Signature res = new Signature();
res.r = new BigInteger(rSource);
res.s = new BigInteger(sSource);
return res.equals(signature);
byte[] sSource = sha3(msgHash, pubKey.toByteArray());
byte[] res = ByteUtil.merge(rSource, sSource);

return FastByteComparisons.equal(res, signature);
}

@Override
public KeyPair newKeyPair() {
KeyPair res = new KeyPair();
byte[] sigKey = new byte[48];
random.nextBytes(sigKey);

res.sigKey = new BigInteger(sigKey);
res.verKey = privToPub(res.sigKey);

return res;
}

@Override
Expand All @@ -64,7 +77,7 @@ public BigInteger privToPub(BigInteger privKey) {
* Aggregates several signatures in one
*/
@Override
public Signature aggSigns(List<Signature> signatures) {
public byte[] aggSigns(List<byte[]> signatures) {
if (signatures.isEmpty())
throw new RuntimeException("Couldn't aggregate empty list");

Expand Down
Loading