/*
* Copyright 2014 Christopher Mann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.uni_bonn.bit;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import com.google.common.collect.Lists;
import com.google.common.math.DoubleMath;
import de.uni_bonn.bit.wallet_protocol.EncryptedSignatureWithProof;
import de.uni_bonn.bit.wallet_protocol.EphemeralPublicValueWithProof;
import de.uni_bonn.bit.wallet_protocol.EphemeralValueShare;
import de.uni_bonn.bit.wallet_protocol.SignatureParts;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.pqc.math.linearalgebra.IntegerFunctions;
import java.math.BigInteger;
import java.util.List;
import static de.uni_bonn.bit.BitcoinECMathHelper.convertPointToPubKEy;
import static de.uni_bonn.bit.BitcoinECMathHelper.convertPrivKeyToBigInt;
import static de.uni_bonn.bit.BitcoinECMathHelper.convertPubKeyToPoint;
import static de.uni_bonn.bit.BitcoinECMathHelper.convertBigIntToPrivKey;
/**
* This class contains the implementations of two basic attacks on the two-party ECDSA signature protocol. The attacks
* must not succeed if the protocol is correctly implemented.
*/
public class ProtocolAttackTest {
ECKey desktopKeyShare = convertBigIntToPrivKey(new BigInteger("41"));
ECKey phoneKeyShare = convertBigIntToPrivKey(new BigInteger("42"));
ECKey commonPublicKey;
BCParameters desktopBCParameters;
BCParameters phoneBCParameters;
@Before
public void setUp() {
commonPublicKey = convertPointToPubKEy(
convertPubKeyToPoint(desktopKeyShare).multiply(convertPrivKeyToBigInt(phoneKeyShare)));
desktopBCParameters = BCParameters.generateBCParameters();
phoneBCParameters = BCParameters.generateBCParameters2();
}
/**
* This test tries to trick the phone into singing something completely different then it thinks by sending
* incorrectly constructed alphaDesktop and beta to the phone. This attack should not succeed as the zero-knowledge
* proof Pi_A proofs to the phone that alphaDesktop and beta are correct.
*/
@Test(expected = ProtocolException.class)
public void attackOnPhoneSignerTest(){
BigInteger nEC = ECKey.CURVE.getN();
byte[] benignHash = Sha256Hash.create("---Benign Message---".getBytes()).getBytes();
final BigInteger benignHashInt = new BigInteger(1, benignHash);
Sha256Hash maliciousHash = Sha256Hash.create("---Malicious Message---".getBytes());
final BigInteger maliciousHashInt = new BigInteger(1, maliciousHash.getBytes());
ECPoint QDesktop = convertPubKeyToPoint(desktopKeyShare);
ECPoint QPhone = convertPubKeyToPoint(phoneKeyShare);
PaillierKeyPair pkpDesktop = PaillierKeyPair.generatePaillierKeyPair();
PaillierKeyPair pkpPhone = PaillierKeyPair.generatePaillierKeyPair();
DesktopSigner desktopSigner = new DesktopSigner(desktopKeyShare, convertPointToPubKEy(QPhone), pkpDesktop, pkpPhone.clearPrivateKey(),
desktopBCParameters, phoneBCParameters.clearPrivate()){
@Override
public SignatureParts computeSignatureParts() {
kDesktop = IntegerFunctions.randomize(nEC);
zDesktop = kDesktop.modInverse(nEC);
r1 = pkpDesktop.generateRandomizer();
alphaDesktop = pkpDesktop.encrypt(zDesktop.multiply(benignHashInt.modInverse(nEC)).multiply(maliciousHashInt).mod(nEC), r1);
r2 = pkpDesktop.generateRandomizer();
beta = pkpDesktop.encrypt(privateKey.multiply(zDesktop).mod(nEC), r2);
return new SignatureParts(alphaDesktop, beta);
}
@Override
public ECKey.ECDSASignature decryptEncryptedSignature(EncryptedSignatureWithProof encryptedSignature, byte[] hash) {
BigInteger r = R.normalize().getAffineXCoord().toBigInteger().mod(nEC);
BigInteger s = pkpDesktop.decrypt(encryptedSignature.getSigma()).mod(nEC);
return new ECKey.ECDSASignature(r, s);
}
};
PhoneSigner phoneSigner = new PhoneSigner(phoneKeyShare, convertPointToPubKEy(QDesktop), pkpDesktop.clearPrivateKey(), pkpPhone,
desktopBCParameters.clearPrivate(), phoneBCParameters);
//Step 1
SignatureParts signatureParts = desktopSigner.computeSignatureParts();
//Step 2
EphemeralValueShare ephemeralValueShare = phoneSigner.generateEphemeralValueShare(signatureParts);
//Step 3
EphemeralPublicValueWithProof ephemeralPublicValueWithProof = desktopSigner.computeEphemeralPublicValue(ephemeralValueShare);
//Step 4
EncryptedSignatureWithProof encryptedSignatureWithProof = phoneSigner.computeEncryptedSignature(ephemeralPublicValueWithProof, benignHash);
//Step 4
ECKey.ECDSASignature ecdsaSignature = desktopSigner.decryptEncryptedSignature(encryptedSignatureWithProof, maliciousHash.getBytes());
Assert.assertFalse("Attack successful! The phone signer has been tricked into creating a signature for an unknown hash value.",
commonPublicKey.verify(maliciousHash, ecdsaSignature));
}
/**
* This test tries to find out the length of the phone's private by analyzing the encrypted signature sigma.
* This attack should not succeed as the phone adds some additional randomization to the encrypted signature.
* The randomization should prevent such an information leak.
*/
@Test
public void attackOnPhoneSignerTest2(){
BigInteger nEC = ECKey.CURVE.getN();
byte[] hash = Sha256Hash.create("---Benign Message---".getBytes()).getBytes();
ECKey phoneKeySmall = convertBigIntToPrivKey(new BigInteger("1"));
ECPoint QPhoneSmall = convertPubKeyToPoint(phoneKeySmall);
ECKey phoneKeyLarge = convertBigIntToPrivKey(nEC.subtract(BigInteger.ONE));
ECPoint QPhoneLarge = convertPubKeyToPoint(phoneKeyLarge);
ECPoint QDesktop = convertPubKeyToPoint(desktopKeyShare);
PaillierKeyPair pkpDesktop = PaillierKeyPair.generatePaillierKeyPair();
PaillierKeyPair pkpPhone = PaillierKeyPair.generatePaillierKeyPair();
//Phone uses a small key
List<Integer> bitLengthsSmall = Lists.newArrayList(10);
for(int i = 1; i < 10; i++){
DesktopSigner desktopSigner = new DesktopSigner(desktopKeyShare, convertPointToPubKEy(QPhoneSmall),
pkpDesktop, pkpPhone.clearPrivateKey(), desktopBCParameters, phoneBCParameters.clearPrivate());
PhoneSigner phoneSigner = new PhoneSigner(phoneKeySmall, convertPointToPubKEy(QDesktop), pkpDesktop.clearPrivateKey(), pkpPhone,
desktopBCParameters.clearPrivate(), phoneBCParameters);
//Step 1
SignatureParts signatureParts = desktopSigner.computeSignatureParts();
//Step 2
EphemeralValueShare ephemeralValueShare = phoneSigner.generateEphemeralValueShare(signatureParts);
//Step 3
EphemeralPublicValueWithProof ephemeralPublicValueWithProof = desktopSigner.computeEphemeralPublicValue(ephemeralValueShare);
//Step 4
EncryptedSignatureWithProof encryptedSignatureWithProof = phoneSigner.computeEncryptedSignature(ephemeralPublicValueWithProof, hash);
bitLengthsSmall.add(pkpDesktop.decrypt(encryptedSignatureWithProof.getSigma()).bitLength());
}
//Phone uses a large key
List<Integer> bitLengthsLarge = Lists.newArrayList(10);
for(int i = 1; i < 10; i++){
DesktopSigner desktopSigner = new DesktopSigner(desktopKeyShare, convertPointToPubKEy(QPhoneLarge),
pkpDesktop, pkpPhone.clearPrivateKey(), desktopBCParameters, phoneBCParameters.clearPrivate());
PhoneSigner phoneSigner = new PhoneSigner(phoneKeyLarge, convertPointToPubKEy(QDesktop), pkpDesktop.clearPrivateKey(), pkpPhone,
desktopBCParameters.clearPrivate(), phoneBCParameters);
//Step 1
SignatureParts signatureParts = desktopSigner.computeSignatureParts();
//Step 2
EphemeralValueShare ephemeralValueShare = phoneSigner.generateEphemeralValueShare(signatureParts);
//Step 3
EphemeralPublicValueWithProof ephemeralPublicValueWithProof = desktopSigner.computeEphemeralPublicValue(ephemeralValueShare);
//Step 4
EncryptedSignatureWithProof encryptedSignatureWithProof = phoneSigner.computeEncryptedSignature(ephemeralPublicValueWithProof, hash);
bitLengthsLarge.add(pkpDesktop.decrypt(encryptedSignatureWithProof.getSigma()).bitLength());
}
double smallMean = DoubleMath.mean(bitLengthsSmall);
double largeMean = DoubleMath.mean(bitLengthsLarge);
System.out.printf("smallKey: %4.2f <-> largeKey: %4.2f", smallMean, largeMean);
Assert.assertTrue("Attack successful! Desktop signer can see the length of the phone signer's key.",
Math.abs(smallMean - largeMean) < 3);
}
}