diff --git a/dh/sidh/internal/p434/core.go b/dh/sidh/internal/p434/core.go index bdee4e5fa..81e43e291 100644 --- a/dh/sidh/internal/p434/core.go +++ b/dh/sidh/internal/p434/core.go @@ -4,6 +4,7 @@ package p434 import ( + crand "crypto/rand" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -260,7 +261,7 @@ func DeriveSecretA(ss, prv []byte, pub3Pt *[3]Fp2) { } // Establishing shared keys in in 3-torsion group -func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { +func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { var xP, xQ, xQmP ProjectivePoint var xR ProjectivePoint var phi isogeny3 @@ -274,6 +275,16 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { xP = ProjectivePoint{X: pub3Pt[0], Z: params.OneFp2} xQ = ProjectivePoint{X: pub3Pt[1], Z: params.OneFp2} xQmP = ProjectivePoint{X: pub3Pt[2], Z: params.OneFp2} + + //PUBLIC KEY VALIDATION + if err := PublicKeyValidation(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen); err != nil { + _, err_read := crand.Read(ss) + if err_read != nil { + panic("core: failed to generate random ss when public key verification fails") + } + return + } + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen, prv) // Traverse isogeny tree @@ -285,4 +296,6 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { Jinvariant(&cparam, &jInv) FromMontgomery(&jInv, &jInv) Fp2ToBytes(ss, &jInv, params.Bytelen) + + } diff --git a/dh/sidh/internal/p434/curve.go b/dh/sidh/internal/p434/curve.go index 260bc52af..3b0710eb5 100644 --- a/dh/sidh/internal/p434/curve.go +++ b/dh/sidh/internal/p434/curve.go @@ -4,8 +4,8 @@ package p434 import ( - "crypto/rand" - + "math" + "errors" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -128,6 +128,7 @@ func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *Curve mul(&cparams.C, &cparams.C, ¶ms.HalfFp2) } + // Combined coordinate doubling and differential addition. Takes projective points // P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. // Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE @@ -241,12 +242,7 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint R2 = *PmQ R0 = *Q - var blind ProjectivePoint - b := make([]byte, 4*params.Bytelen) - _, _ = rand.Read(b) - BytesToFp2(&blind.X, b[:2*params.Bytelen], params.Bytelen) - BytesToFp2(&blind.Z, b[2*params.Bytelen:], params.Bytelen) - + // Iterate over the bits of the scalar, bottom to top prevBit := uint8(0) for i := uint(0); i < nbits; i++ { @@ -254,7 +250,6 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint swap := prevBit ^ bit prevBit = bit cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) - cmov(&R1.X, &R1.Z, &blind.X, &blind.Z, isZero(&R1.X)|isZero(&R1.Z)) R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) } cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) @@ -365,3 +360,79 @@ func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) { mul(xq, xq, &t1) mul(zq, zq, &t0) } + +// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. +// CLN test: Check that P and Q are both of order 3^e3 and they generate the torsion E_A[3^e3] +// A countermeasure for remote timing attacks on SIKE; suggested by https://eprint.iacr.org/2022/054.pdf +// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. +// Input: The public key / ciphertext P, Q, PmQ. The projective coordinate A of the curve defined by (P, Q, PmQ) +// Outputs: Whether (P,Q,PmQ) follows the CLN test +func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint) error { + + + var PmQX, PmQZ Fp2 + FromMontgomery(&PmQX, &PmQ.X) + FromMontgomery(&PmQZ, &PmQ.Z) + + // PmQ is not point T or O + if((isZero(&PmQX)==1)||(isZero(&PmQZ)==1)){ + return errors.New("curve: PmQ is invalid") + } + + cparam := CalcCurveParamsEquiv3(cparams) + + // Compute e_3 = log3(2^(nbits+1)) + var e3 uint32 + e3_float := float64(int(nbits)+1)/math.Log2(3) + e3 = uint32(e3_float) + + // Verify that P and Q generate E_A[3^e_3] by checking: [3^(e_3-1)]P != [+-3^(e_3-1)]Q + var test_P, test_Q ProjectivePoint + test_P = *P + test_Q = *Q + + + Pow3k(&test_P, &cparam, e3-1) + Pow3k(&test_Q, &cparam, e3-1) + + + var PZ, QZ Fp2 + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + + // P, Q are not of full order 3^e_3 + if((isZero(&PZ)==1)||(isZero(&QZ)==1)){ + return errors.New("curve: ciphertext/public key are not of full order 3^e3") + } + + // PX/PZ = affine(PX) + // QX/QZ = affine(QX) + // If PX/PZ = QX/QZ, we have P=+-Q + var PXQZ_PZQX_fromMont, PXQZ_PZQX, PXQZ, PZQX Fp2 + mul(&PXQZ, &test_P.X, &test_Q.Z) + mul(&PZQX, &test_P.Z, &test_Q.X) + sub(&PXQZ_PZQX, &PXQZ, &PZQX) + FromMontgomery(&PXQZ_PZQX_fromMont, &PXQZ_PZQX) + + // [3^(e_3-1)]P == [+-3^(e_3-1)]Q + if(isZero(&PXQZ_PZQX_fromMont)==1){ + return errors.New("curve: ciphertext/public key are not linearly independent") + } + + // Check that Ord(P) = Ord(Q) = 3^(e_3) + Pow3k(&test_P, &cparam, 1) + Pow3k(&test_Q, &cparam, 1) + + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + // P, Q are not of correct order 3^e_3 + if((isZero(&PZ)==0)||(isZero(&QZ)==0)){ + return errors.New("curve: ciphertext/public key are not of correct order 3^e3") + } + return nil +} + + + diff --git a/dh/sidh/internal/p434/curve_test.go b/dh/sidh/internal/p434/curve_test.go index 01099021d..ce896ced5 100644 --- a/dh/sidh/internal/p434/curve_test.go +++ b/dh/sidh/internal/p434/curve_test.go @@ -6,8 +6,12 @@ package p434 import ( "bytes" "testing" - + "math" + "math/rand" + crand "crypto/rand" + "io" . "github.com/cloudflare/circl/dh/sidh/internal/common" + "time" ) func vartimeEqProjFp2(lhs, rhs *ProjectivePoint) bool { @@ -98,3 +102,297 @@ func BenchmarkThreePointLadder(b *testing.B) { ScalarMul3Pt(&curve, &threePointLadderInputs[0], &threePointLadderInputs[1], &threePointLadderInputs[2], uint(len(scalar3Pt)*8), scalar3Pt[:]) } } + +/* ------------------------------------------------------------------------- + Generate invalid public key points / ciphertext for test TestKEMInvalidPK + -------------------------------------------------------------------------*/ + +// Left-to-right Montgomery ladder, Algorithm 4 in Costello-Smith +// Input: ProjectivePoint P (xP, zP) +// Output: x([scalar]P), z([scalar]P) +func montgomeryLadder(cparams *ProjectiveCurveParameters, P *ProjectivePoint, scalar []uint8, random uint) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + coefEq := CalcCurveParamsEquiv4(cparams) // for xDbl + aPlus2Over4 := CalcAplus2Over4(cparams) // for xDblAdd + R0 = *P // RO <- P + R1 = *P + Pow2k(&R1, &coefEq, 1) // R1 <- [2]P + R2 = *P // R2 = R1-R0 = P + + prevBit := uint8(0) + for i := int(random); i >= 0; i-- { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, swap) + R0, R1 = xDbladd(&R0, &R1, &R2, &aPlus2Over4) + } + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, prevBit) + return R0 +} + +// P = P + T +// From paper https://eprint.iacr.org/2017/212.pdf +// The map tau_T: P->P+T is (X : Z) -> (Z : X) on Montgomery curves +func tauT(P *ProjectivePoint) { + P.X, P.Z = P.Z, P.X // magic! +} + +// Construct Invalid public key tuple (P,Q) such that P and Q are linearly dependent +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKNoneLinear(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + + // Make sure Q is of full order 3^e_3, + var test_Q ProjectivePoint + test_Q = Q + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&test_Q, &cparam_q, e3-1) + + var test_QZ Fp2 + FromMontgomery(&test_QZ, &test_Q.Z) + + // Q are not of full order 3^e_3 + for((isZero(&test_QZ)==1)){ + rand.Seed(time.Now().UnixNano()) + random_index = rand.Intn(int(params.B.SecretByteLen-1)*8) + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + test_Q = Q + Pow3k(&test_Q, &cparam_q, e3-1) + FromMontgomery(&test_QZ, &test_Q.Z) + } + + // invQz = 1/Q.Z + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect linearly dependent ciphertext to fail, index: %v scalar: %v ", random_index, secret) + } +} + +// Construct Invalid public key tuple (P,Q) such that Q = [k]P + T, where k is random and T is the point of order 2. +// Simulate HB and section 3.1.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKT(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + // Q = [k]P + T + tauT(&Q) + + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext involve point T to fail, index: %v scalar: %v ", random_index, secret) + } +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[2^e2] +// Simulate section 3.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKOrder2(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(params.A.SecretByteLen-1)*8) + random_index_q := rand.Intn(int(params.A.SecretByteLen-1)*8) + + P = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index_p)) + Q = montgomeryLadder(¶ms.InitCurve, &Q, secret, uint(random_index_q)) + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext in torsion E[2^e2] to fail, index_p: %v index_q: %v scalar: %v ", random_index_p, random_index_q, secret) + } + +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[3^e3] but not of full order 3^e3 +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKFullOrder(t *testing.T) { + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(e3)) + random_index_q := rand.Intn(int(e3)) + + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&P, &cparam_q, uint32(random_index_p)) + Pow3k(&Q, &cparam_q, uint32(random_index_q)) + + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext not of full order to fail, index_p: %v index_q: %v ", random_index_p, random_index_q) + } + +} + +// A trivial test case not covered by paper https://eprint.iacr.org/2022/054.pdf and HB +// Countermeasure in https://eprint.iacr.org/2022/054.pdf only cares about P and Q +// But if PmQ is point T or O, that can also lead to recovery of the first bit +func testInvalidPmQ(t *testing.T) { + + var zero Fp2 + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: zero, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect PmQ as T to fail\n") + } + +} + +// Test valid ciphertext +// Where P, Q are linearly independent points of correct order 3^e3 in E[3^e3] +func testValidPQ(t *testing.T) { + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify!=nil{ + t.Errorf("\nExpect correct ciphertext to not fail\n") + } + +} + + +/* ------------------------------------------------------------------------- + Public key / Ciphertext validation against attacks proposed in paper https://eprint.iacr.org/2022/054.pdf and HB + -------------------------------------------------------------------------*/ + +func TestInvalidPK(t *testing.T) { + + t.Run("InvalidPmQ", testInvalidPmQ) + t.Run("InvalidPKNoneLinear", testInvalidPKNoneLinear) + t.Run("InvalidPKT", testInvalidPKT) + t.Run("InvalidPKOrder2", testInvalidPKOrder2) + t.Run("InvalidPKFullOrder", testInvalidPKFullOrder) + t.Run("ValidPQ", testValidPQ) + +} diff --git a/dh/sidh/internal/p503/core.go b/dh/sidh/internal/p503/core.go index e405a29aa..a76dadf2c 100644 --- a/dh/sidh/internal/p503/core.go +++ b/dh/sidh/internal/p503/core.go @@ -4,6 +4,7 @@ package p503 import ( + crand "crypto/rand" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -260,7 +261,7 @@ func DeriveSecretA(ss, prv []byte, pub3Pt *[3]Fp2) { } // Establishing shared keys in in 3-torsion group -func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { +func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { var xP, xQ, xQmP ProjectivePoint var xR ProjectivePoint var phi isogeny3 @@ -274,6 +275,16 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { xP = ProjectivePoint{X: pub3Pt[0], Z: params.OneFp2} xQ = ProjectivePoint{X: pub3Pt[1], Z: params.OneFp2} xQmP = ProjectivePoint{X: pub3Pt[2], Z: params.OneFp2} + + //PUBLIC KEY VALIDATION + if err := PublicKeyValidation(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen); err != nil { + _, err_read := crand.Read(ss) + if err_read != nil { + panic("core: failed to generate random ss when public key verification fails") + } + return + } + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen, prv) // Traverse isogeny tree @@ -285,4 +296,6 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { Jinvariant(&cparam, &jInv) FromMontgomery(&jInv, &jInv) Fp2ToBytes(ss, &jInv, params.Bytelen) + + } diff --git a/dh/sidh/internal/p503/curve.go b/dh/sidh/internal/p503/curve.go index 5681e3782..d0b9e1703 100644 --- a/dh/sidh/internal/p503/curve.go +++ b/dh/sidh/internal/p503/curve.go @@ -4,8 +4,8 @@ package p503 import ( - "crypto/rand" - + "math" + "errors" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -128,6 +128,7 @@ func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *Curve mul(&cparams.C, &cparams.C, ¶ms.HalfFp2) } + // Combined coordinate doubling and differential addition. Takes projective points // P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. // Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE @@ -241,12 +242,7 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint R2 = *PmQ R0 = *Q - var blind ProjectivePoint - b := make([]byte, 4*params.Bytelen) - _, _ = rand.Read(b) - BytesToFp2(&blind.X, b[:2*params.Bytelen], params.Bytelen) - BytesToFp2(&blind.Z, b[2*params.Bytelen:], params.Bytelen) - + // Iterate over the bits of the scalar, bottom to top prevBit := uint8(0) for i := uint(0); i < nbits; i++ { @@ -254,7 +250,6 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint swap := prevBit ^ bit prevBit = bit cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) - cmov(&R1.X, &R1.Z, &blind.X, &blind.Z, isZero(&R1.X)|isZero(&R1.Z)) R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) } cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) @@ -365,3 +360,79 @@ func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) { mul(xq, xq, &t1) mul(zq, zq, &t0) } + +// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. +// CLN test: Check that P and Q are both of order 3^e3 and they generate the torsion E_A[3^e3] +// A countermeasure for remote timing attacks on SIKE; suggested by https://eprint.iacr.org/2022/054.pdf +// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. +// Input: The public key / ciphertext P, Q, PmQ. The projective coordinate A of the curve defined by (P, Q, PmQ) +// Outputs: Whether (P,Q,PmQ) follows the CLN test +func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint) error { + + + var PmQX, PmQZ Fp2 + FromMontgomery(&PmQX, &PmQ.X) + FromMontgomery(&PmQZ, &PmQ.Z) + + // PmQ is not point T or O + if((isZero(&PmQX)==1)||(isZero(&PmQZ)==1)){ + return errors.New("curve: PmQ is invalid") + } + + cparam := CalcCurveParamsEquiv3(cparams) + + // Compute e_3 = log3(2^(nbits+1)) + var e3 uint32 + e3_float := float64(int(nbits)+1)/math.Log2(3) + e3 = uint32(e3_float) + + // Verify that P and Q generate E_A[3^e_3] by checking: [3^(e_3-1)]P != [+-3^(e_3-1)]Q + var test_P, test_Q ProjectivePoint + test_P = *P + test_Q = *Q + + + Pow3k(&test_P, &cparam, e3-1) + Pow3k(&test_Q, &cparam, e3-1) + + + var PZ, QZ Fp2 + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + + // P, Q are not of full order 3^e_3 + if((isZero(&PZ)==1)||(isZero(&QZ)==1)){ + return errors.New("curve: ciphertext/public key are not of full order 3^e3") + } + + // PX/PZ = affine(PX) + // QX/QZ = affine(QX) + // If PX/PZ = QX/QZ, we have P=+-Q + var PXQZ_PZQX_fromMont, PXQZ_PZQX, PXQZ, PZQX Fp2 + mul(&PXQZ, &test_P.X, &test_Q.Z) + mul(&PZQX, &test_P.Z, &test_Q.X) + sub(&PXQZ_PZQX, &PXQZ, &PZQX) + FromMontgomery(&PXQZ_PZQX_fromMont, &PXQZ_PZQX) + + // [3^(e_3-1)]P == [+-3^(e_3-1)]Q + if(isZero(&PXQZ_PZQX_fromMont)==1){ + return errors.New("curve: ciphertext/public key are not linearly independent") + } + + // Check that Ord(P) = Ord(Q) = 3^(e_3) + Pow3k(&test_P, &cparam, 1) + Pow3k(&test_Q, &cparam, 1) + + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + // P, Q are not of correct order 3^e_3 + if((isZero(&PZ)==0)||(isZero(&QZ)==0)){ + return errors.New("curve: ciphertext/public key are not of correct order 3^e3") + } + return nil +} + + + diff --git a/dh/sidh/internal/p503/curve_test.go b/dh/sidh/internal/p503/curve_test.go index 94c29fe9b..4116ed39f 100644 --- a/dh/sidh/internal/p503/curve_test.go +++ b/dh/sidh/internal/p503/curve_test.go @@ -6,8 +6,12 @@ package p503 import ( "bytes" "testing" - + "math" + "math/rand" + crand "crypto/rand" + "io" . "github.com/cloudflare/circl/dh/sidh/internal/common" + "time" ) func vartimeEqProjFp2(lhs, rhs *ProjectivePoint) bool { @@ -98,3 +102,297 @@ func BenchmarkThreePointLadder(b *testing.B) { ScalarMul3Pt(&curve, &threePointLadderInputs[0], &threePointLadderInputs[1], &threePointLadderInputs[2], uint(len(scalar3Pt)*8), scalar3Pt[:]) } } + +/* ------------------------------------------------------------------------- + Generate invalid public key points / ciphertext for test TestKEMInvalidPK + -------------------------------------------------------------------------*/ + +// Left-to-right Montgomery ladder, Algorithm 4 in Costello-Smith +// Input: ProjectivePoint P (xP, zP) +// Output: x([scalar]P), z([scalar]P) +func montgomeryLadder(cparams *ProjectiveCurveParameters, P *ProjectivePoint, scalar []uint8, random uint) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + coefEq := CalcCurveParamsEquiv4(cparams) // for xDbl + aPlus2Over4 := CalcAplus2Over4(cparams) // for xDblAdd + R0 = *P // RO <- P + R1 = *P + Pow2k(&R1, &coefEq, 1) // R1 <- [2]P + R2 = *P // R2 = R1-R0 = P + + prevBit := uint8(0) + for i := int(random); i >= 0; i-- { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, swap) + R0, R1 = xDbladd(&R0, &R1, &R2, &aPlus2Over4) + } + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, prevBit) + return R0 +} + +// P = P + T +// From paper https://eprint.iacr.org/2017/212.pdf +// The map tau_T: P->P+T is (X : Z) -> (Z : X) on Montgomery curves +func tauT(P *ProjectivePoint) { + P.X, P.Z = P.Z, P.X // magic! +} + +// Construct Invalid public key tuple (P,Q) such that P and Q are linearly dependent +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKNoneLinear(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + + // Make sure Q is of full order 3^e_3, + var test_Q ProjectivePoint + test_Q = Q + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&test_Q, &cparam_q, e3-1) + + var test_QZ Fp2 + FromMontgomery(&test_QZ, &test_Q.Z) + + // Q are not of full order 3^e_3 + for((isZero(&test_QZ)==1)){ + rand.Seed(time.Now().UnixNano()) + random_index = rand.Intn(int(params.B.SecretByteLen-1)*8) + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + test_Q = Q + Pow3k(&test_Q, &cparam_q, e3-1) + FromMontgomery(&test_QZ, &test_Q.Z) + } + + // invQz = 1/Q.Z + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect linearly dependent ciphertext to fail, index: %v scalar: %v ", random_index, secret) + } +} + +// Construct Invalid public key tuple (P,Q) such that Q = [k]P + T, where k is random and T is the point of order 2. +// Simulate HB and section 3.1.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKT(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + // Q = [k]P + T + tauT(&Q) + + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext involve point T to fail, index: %v scalar: %v ", random_index, secret) + } +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[2^e2] +// Simulate section 3.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKOrder2(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(params.A.SecretByteLen-1)*8) + random_index_q := rand.Intn(int(params.A.SecretByteLen-1)*8) + + P = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index_p)) + Q = montgomeryLadder(¶ms.InitCurve, &Q, secret, uint(random_index_q)) + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext in torsion E[2^e2] to fail, index_p: %v index_q: %v scalar: %v ", random_index_p, random_index_q, secret) + } + +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[3^e3] but not of full order 3^e3 +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKFullOrder(t *testing.T) { + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(e3)) + random_index_q := rand.Intn(int(e3)) + + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&P, &cparam_q, uint32(random_index_p)) + Pow3k(&Q, &cparam_q, uint32(random_index_q)) + + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext not of full order to fail, index_p: %v index_q: %v ", random_index_p, random_index_q) + } + +} + +// A trivial test case not covered by paper https://eprint.iacr.org/2022/054.pdf and HB +// Countermeasure in https://eprint.iacr.org/2022/054.pdf only cares about P and Q +// But if PmQ is point T or O, that can also lead to recovery of the first bit +func testInvalidPmQ(t *testing.T) { + + var zero Fp2 + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: zero, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect PmQ as T to fail\n") + } + +} + +// Test valid ciphertext +// Where P, Q are linearly independent points of correct order 3^e3 in E[3^e3] +func testValidPQ(t *testing.T) { + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify!=nil{ + t.Errorf("\nExpect correct ciphertext to not fail\n") + } + +} + + +/* ------------------------------------------------------------------------- + Public key / Ciphertext validation against attacks proposed in paper https://eprint.iacr.org/2022/054.pdf and HB + -------------------------------------------------------------------------*/ + +func TestInvalidPK(t *testing.T) { + + t.Run("InvalidPmQ", testInvalidPmQ) + t.Run("InvalidPKNoneLinear", testInvalidPKNoneLinear) + t.Run("InvalidPKT", testInvalidPKT) + t.Run("InvalidPKOrder2", testInvalidPKOrder2) + t.Run("InvalidPKFullOrder", testInvalidPKFullOrder) + t.Run("ValidPQ", testValidPQ) + +} diff --git a/dh/sidh/internal/p751/core.go b/dh/sidh/internal/p751/core.go index 8917de970..b187d1fda 100644 --- a/dh/sidh/internal/p751/core.go +++ b/dh/sidh/internal/p751/core.go @@ -4,6 +4,7 @@ package p751 import ( + crand "crypto/rand" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -260,7 +261,7 @@ func DeriveSecretA(ss, prv []byte, pub3Pt *[3]Fp2) { } // Establishing shared keys in in 3-torsion group -func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { +func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { var xP, xQ, xQmP ProjectivePoint var xR ProjectivePoint var phi isogeny3 @@ -274,6 +275,16 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { xP = ProjectivePoint{X: pub3Pt[0], Z: params.OneFp2} xQ = ProjectivePoint{X: pub3Pt[1], Z: params.OneFp2} xQmP = ProjectivePoint{X: pub3Pt[2], Z: params.OneFp2} + + //PUBLIC KEY VALIDATION + if err := PublicKeyValidation(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen); err != nil { + _, err_read := crand.Read(ss) + if err_read != nil { + panic("core: failed to generate random ss when public key verification fails") + } + return + } + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen, prv) // Traverse isogeny tree @@ -285,4 +296,6 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { Jinvariant(&cparam, &jInv) FromMontgomery(&jInv, &jInv) Fp2ToBytes(ss, &jInv, params.Bytelen) + + } diff --git a/dh/sidh/internal/p751/curve.go b/dh/sidh/internal/p751/curve.go index 1ccdf8b31..cafa4155b 100644 --- a/dh/sidh/internal/p751/curve.go +++ b/dh/sidh/internal/p751/curve.go @@ -4,8 +4,8 @@ package p751 import ( - "crypto/rand" - + "math" + "errors" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -128,6 +128,7 @@ func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *Curve mul(&cparams.C, &cparams.C, ¶ms.HalfFp2) } + // Combined coordinate doubling and differential addition. Takes projective points // P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. // Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE @@ -241,12 +242,7 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint R2 = *PmQ R0 = *Q - var blind ProjectivePoint - b := make([]byte, 4*params.Bytelen) - _, _ = rand.Read(b) - BytesToFp2(&blind.X, b[:2*params.Bytelen], params.Bytelen) - BytesToFp2(&blind.Z, b[2*params.Bytelen:], params.Bytelen) - + // Iterate over the bits of the scalar, bottom to top prevBit := uint8(0) for i := uint(0); i < nbits; i++ { @@ -254,7 +250,6 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint swap := prevBit ^ bit prevBit = bit cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) - cmov(&R1.X, &R1.Z, &blind.X, &blind.Z, isZero(&R1.X)|isZero(&R1.Z)) R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) } cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) @@ -365,3 +360,79 @@ func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) { mul(xq, xq, &t1) mul(zq, zq, &t0) } + +// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. +// CLN test: Check that P and Q are both of order 3^e3 and they generate the torsion E_A[3^e3] +// A countermeasure for remote timing attacks on SIKE; suggested by https://eprint.iacr.org/2022/054.pdf +// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. +// Input: The public key / ciphertext P, Q, PmQ. The projective coordinate A of the curve defined by (P, Q, PmQ) +// Outputs: Whether (P,Q,PmQ) follows the CLN test +func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint) error { + + + var PmQX, PmQZ Fp2 + FromMontgomery(&PmQX, &PmQ.X) + FromMontgomery(&PmQZ, &PmQ.Z) + + // PmQ is not point T or O + if((isZero(&PmQX)==1)||(isZero(&PmQZ)==1)){ + return errors.New("curve: PmQ is invalid") + } + + cparam := CalcCurveParamsEquiv3(cparams) + + // Compute e_3 = log3(2^(nbits+1)) + var e3 uint32 + e3_float := float64(int(nbits)+1)/math.Log2(3) + e3 = uint32(e3_float) + + // Verify that P and Q generate E_A[3^e_3] by checking: [3^(e_3-1)]P != [+-3^(e_3-1)]Q + var test_P, test_Q ProjectivePoint + test_P = *P + test_Q = *Q + + + Pow3k(&test_P, &cparam, e3-1) + Pow3k(&test_Q, &cparam, e3-1) + + + var PZ, QZ Fp2 + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + + // P, Q are not of full order 3^e_3 + if((isZero(&PZ)==1)||(isZero(&QZ)==1)){ + return errors.New("curve: ciphertext/public key are not of full order 3^e3") + } + + // PX/PZ = affine(PX) + // QX/QZ = affine(QX) + // If PX/PZ = QX/QZ, we have P=+-Q + var PXQZ_PZQX_fromMont, PXQZ_PZQX, PXQZ, PZQX Fp2 + mul(&PXQZ, &test_P.X, &test_Q.Z) + mul(&PZQX, &test_P.Z, &test_Q.X) + sub(&PXQZ_PZQX, &PXQZ, &PZQX) + FromMontgomery(&PXQZ_PZQX_fromMont, &PXQZ_PZQX) + + // [3^(e_3-1)]P == [+-3^(e_3-1)]Q + if(isZero(&PXQZ_PZQX_fromMont)==1){ + return errors.New("curve: ciphertext/public key are not linearly independent") + } + + // Check that Ord(P) = Ord(Q) = 3^(e_3) + Pow3k(&test_P, &cparam, 1) + Pow3k(&test_Q, &cparam, 1) + + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + // P, Q are not of correct order 3^e_3 + if((isZero(&PZ)==0)||(isZero(&QZ)==0)){ + return errors.New("curve: ciphertext/public key are not of correct order 3^e3") + } + return nil +} + + + diff --git a/dh/sidh/internal/p751/curve_test.go b/dh/sidh/internal/p751/curve_test.go index 0bf0b8db5..c4fa6605a 100644 --- a/dh/sidh/internal/p751/curve_test.go +++ b/dh/sidh/internal/p751/curve_test.go @@ -6,8 +6,12 @@ package p751 import ( "bytes" "testing" - + "math" + "math/rand" + crand "crypto/rand" + "io" . "github.com/cloudflare/circl/dh/sidh/internal/common" + "time" ) func vartimeEqProjFp2(lhs, rhs *ProjectivePoint) bool { @@ -98,3 +102,297 @@ func BenchmarkThreePointLadder(b *testing.B) { ScalarMul3Pt(&curve, &threePointLadderInputs[0], &threePointLadderInputs[1], &threePointLadderInputs[2], uint(len(scalar3Pt)*8), scalar3Pt[:]) } } + +/* ------------------------------------------------------------------------- + Generate invalid public key points / ciphertext for test TestKEMInvalidPK + -------------------------------------------------------------------------*/ + +// Left-to-right Montgomery ladder, Algorithm 4 in Costello-Smith +// Input: ProjectivePoint P (xP, zP) +// Output: x([scalar]P), z([scalar]P) +func montgomeryLadder(cparams *ProjectiveCurveParameters, P *ProjectivePoint, scalar []uint8, random uint) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + coefEq := CalcCurveParamsEquiv4(cparams) // for xDbl + aPlus2Over4 := CalcAplus2Over4(cparams) // for xDblAdd + R0 = *P // RO <- P + R1 = *P + Pow2k(&R1, &coefEq, 1) // R1 <- [2]P + R2 = *P // R2 = R1-R0 = P + + prevBit := uint8(0) + for i := int(random); i >= 0; i-- { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, swap) + R0, R1 = xDbladd(&R0, &R1, &R2, &aPlus2Over4) + } + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, prevBit) + return R0 +} + +// P = P + T +// From paper https://eprint.iacr.org/2017/212.pdf +// The map tau_T: P->P+T is (X : Z) -> (Z : X) on Montgomery curves +func tauT(P *ProjectivePoint) { + P.X, P.Z = P.Z, P.X // magic! +} + +// Construct Invalid public key tuple (P,Q) such that P and Q are linearly dependent +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKNoneLinear(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + + // Make sure Q is of full order 3^e_3, + var test_Q ProjectivePoint + test_Q = Q + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&test_Q, &cparam_q, e3-1) + + var test_QZ Fp2 + FromMontgomery(&test_QZ, &test_Q.Z) + + // Q are not of full order 3^e_3 + for((isZero(&test_QZ)==1)){ + rand.Seed(time.Now().UnixNano()) + random_index = rand.Intn(int(params.B.SecretByteLen-1)*8) + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + test_Q = Q + Pow3k(&test_Q, &cparam_q, e3-1) + FromMontgomery(&test_QZ, &test_Q.Z) + } + + // invQz = 1/Q.Z + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect linearly dependent ciphertext to fail, index: %v scalar: %v ", random_index, secret) + } +} + +// Construct Invalid public key tuple (P,Q) such that Q = [k]P + T, where k is random and T is the point of order 2. +// Simulate HB and section 3.1.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKT(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + // Q = [k]P + T + tauT(&Q) + + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext involve point T to fail, index: %v scalar: %v ", random_index, secret) + } +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[2^e2] +// Simulate section 3.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKOrder2(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(params.A.SecretByteLen-1)*8) + random_index_q := rand.Intn(int(params.A.SecretByteLen-1)*8) + + P = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index_p)) + Q = montgomeryLadder(¶ms.InitCurve, &Q, secret, uint(random_index_q)) + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext in torsion E[2^e2] to fail, index_p: %v index_q: %v scalar: %v ", random_index_p, random_index_q, secret) + } + +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[3^e3] but not of full order 3^e3 +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKFullOrder(t *testing.T) { + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(e3)) + random_index_q := rand.Intn(int(e3)) + + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&P, &cparam_q, uint32(random_index_p)) + Pow3k(&Q, &cparam_q, uint32(random_index_q)) + + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext not of full order to fail, index_p: %v index_q: %v ", random_index_p, random_index_q) + } + +} + +// A trivial test case not covered by paper https://eprint.iacr.org/2022/054.pdf and HB +// Countermeasure in https://eprint.iacr.org/2022/054.pdf only cares about P and Q +// But if PmQ is point T or O, that can also lead to recovery of the first bit +func testInvalidPmQ(t *testing.T) { + + var zero Fp2 + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: zero, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect PmQ as T to fail\n") + } + +} + +// Test valid ciphertext +// Where P, Q are linearly independent points of correct order 3^e3 in E[3^e3] +func testValidPQ(t *testing.T) { + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify!=nil{ + t.Errorf("\nExpect correct ciphertext to not fail\n") + } + +} + + +/* ------------------------------------------------------------------------- + Public key / Ciphertext validation against attacks proposed in paper https://eprint.iacr.org/2022/054.pdf and HB + -------------------------------------------------------------------------*/ + +func TestInvalidPK(t *testing.T) { + + t.Run("InvalidPmQ", testInvalidPmQ) + t.Run("InvalidPKNoneLinear", testInvalidPKNoneLinear) + t.Run("InvalidPKT", testInvalidPKT) + t.Run("InvalidPKOrder2", testInvalidPKOrder2) + t.Run("InvalidPKFullOrder", testInvalidPKFullOrder) + t.Run("ValidPQ", testValidPQ) + +} diff --git a/dh/sidh/internal/templates/core.gotemp b/dh/sidh/internal/templates/core.gotemp index 6e0472de7..d23cbc54d 100644 --- a/dh/sidh/internal/templates/core.gotemp +++ b/dh/sidh/internal/templates/core.gotemp @@ -4,6 +4,7 @@ package {{ .PACKAGE}} import ( + crand "crypto/rand" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -260,7 +261,7 @@ func DeriveSecretA(ss, prv []byte, pub3Pt *[3]Fp2) { } // Establishing shared keys in in 3-torsion group -func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { +func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { var xP, xQ, xQmP ProjectivePoint var xR ProjectivePoint var phi isogeny3 @@ -274,6 +275,16 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { xP = ProjectivePoint{X: pub3Pt[0], Z: params.OneFp2} xQ = ProjectivePoint{X: pub3Pt[1], Z: params.OneFp2} xQmP = ProjectivePoint{X: pub3Pt[2], Z: params.OneFp2} + + //PUBLIC KEY VALIDATION + if err := PublicKeyValidation(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen); err != nil { + _, err_read := crand.Read(ss) + if err_read != nil { + panic("core: failed to generate random ss when public key verification fails") + } + return + } + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, params.B.SecretBitLen, prv) // Traverse isogeny tree @@ -285,4 +296,6 @@ func DeriveSecretB(ss, prv []byte, pub3Pt *[3]Fp2) { Jinvariant(&cparam, &jInv) FromMontgomery(&jInv, &jInv) Fp2ToBytes(ss, &jInv, params.Bytelen) + + } diff --git a/dh/sidh/internal/templates/curve.gotemp b/dh/sidh/internal/templates/curve.gotemp index 6161a8311..7647b0cc2 100644 --- a/dh/sidh/internal/templates/curve.gotemp +++ b/dh/sidh/internal/templates/curve.gotemp @@ -4,8 +4,8 @@ package {{ .PACKAGE}} import ( - "crypto/rand" - + "math" + "errors" . "github.com/cloudflare/circl/dh/sidh/internal/common" ) @@ -128,6 +128,7 @@ func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *Curve mul(&cparams.C, &cparams.C, ¶ms.HalfFp2) } + // Combined coordinate doubling and differential addition. Takes projective points // P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. // Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE @@ -241,12 +242,7 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint R2 = *PmQ R0 = *Q - var blind ProjectivePoint - b := make([]byte, 4*params.Bytelen) - _, _ = rand.Read(b) - BytesToFp2(&blind.X, b[:2*params.Bytelen], params.Bytelen) - BytesToFp2(&blind.Z, b[2*params.Bytelen:], params.Bytelen) - + // Iterate over the bits of the scalar, bottom to top prevBit := uint8(0) for i := uint(0); i < nbits; i++ { @@ -254,7 +250,6 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint swap := prevBit ^ bit prevBit = bit cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) - cmov(&R1.X, &R1.Z, &blind.X, &blind.Z, isZero(&R1.X)|isZero(&R1.Z)) R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) } cswap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) @@ -365,3 +360,79 @@ func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) { mul(xq, xq, &t1) mul(zq, zq, &t0) } + +// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. +// CLN test: Check that P and Q are both of order 3^e3 and they generate the torsion E_A[3^e3] +// A countermeasure for remote timing attacks on SIKE; suggested by https://eprint.iacr.org/2022/054.pdf +// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. +// Input: The public key / ciphertext P, Q, PmQ. The projective coordinate A of the curve defined by (P, Q, PmQ) +// Outputs: Whether (P,Q,PmQ) follows the CLN test +func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint) error { + + + var PmQX, PmQZ Fp2 + FromMontgomery(&PmQX, &PmQ.X) + FromMontgomery(&PmQZ, &PmQ.Z) + + // PmQ is not point T or O + if((isZero(&PmQX)==1)||(isZero(&PmQZ)==1)){ + return errors.New("curve: PmQ is invalid") + } + + cparam := CalcCurveParamsEquiv3(cparams) + + // Compute e_3 = log3(2^(nbits+1)) + var e3 uint32 + e3_float := float64(int(nbits)+1)/math.Log2(3) + e3 = uint32(e3_float) + + // Verify that P and Q generate E_A[3^e_3] by checking: [3^(e_3-1)]P != [+-3^(e_3-1)]Q + var test_P, test_Q ProjectivePoint + test_P = *P + test_Q = *Q + + + Pow3k(&test_P, &cparam, e3-1) + Pow3k(&test_Q, &cparam, e3-1) + + + var PZ, QZ Fp2 + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + + // P, Q are not of full order 3^e_3 + if((isZero(&PZ)==1)||(isZero(&QZ)==1)){ + return errors.New("curve: ciphertext/public key are not of full order 3^e3") + } + + // PX/PZ = affine(PX) + // QX/QZ = affine(QX) + // If PX/PZ = QX/QZ, we have P=+-Q + var PXQZ_PZQX_fromMont, PXQZ_PZQX, PXQZ, PZQX Fp2 + mul(&PXQZ, &test_P.X, &test_Q.Z) + mul(&PZQX, &test_P.Z, &test_Q.X) + sub(&PXQZ_PZQX, &PXQZ, &PZQX) + FromMontgomery(&PXQZ_PZQX_fromMont, &PXQZ_PZQX) + + // [3^(e_3-1)]P == [+-3^(e_3-1)]Q + if(isZero(&PXQZ_PZQX_fromMont)==1){ + return errors.New("curve: ciphertext/public key are not linearly independent") + } + + // Check that Ord(P) = Ord(Q) = 3^(e_3) + Pow3k(&test_P, &cparam, 1) + Pow3k(&test_Q, &cparam, 1) + + FromMontgomery(&PZ, &test_P.Z) + FromMontgomery(&QZ, &test_Q.Z) + + // P, Q are not of correct order 3^e_3 + if((isZero(&PZ)==0)||(isZero(&QZ)==0)){ + return errors.New("curve: ciphertext/public key are not of correct order 3^e3") + } + return nil +} + + + diff --git a/dh/sidh/internal/templates/curve_test.gotemp b/dh/sidh/internal/templates/curve_test.gotemp index 46bd5a2a5..956cb42ba 100644 --- a/dh/sidh/internal/templates/curve_test.gotemp +++ b/dh/sidh/internal/templates/curve_test.gotemp @@ -6,8 +6,12 @@ package {{ .PACKAGE}} import ( "bytes" "testing" - + "math" + "math/rand" + crand "crypto/rand" + "io" . "github.com/cloudflare/circl/dh/sidh/internal/common" + "time" ) func vartimeEqProjFp2(lhs, rhs *ProjectivePoint) bool { @@ -98,3 +102,297 @@ func BenchmarkThreePointLadder(b *testing.B) { ScalarMul3Pt(&curve, &threePointLadderInputs[0], &threePointLadderInputs[1], &threePointLadderInputs[2], uint(len(scalar3Pt)*8), scalar3Pt[:]) } } + +/* ------------------------------------------------------------------------- + Generate invalid public key points / ciphertext for test TestKEMInvalidPK + -------------------------------------------------------------------------*/ + +// Left-to-right Montgomery ladder, Algorithm 4 in Costello-Smith +// Input: ProjectivePoint P (xP, zP) +// Output: x([scalar]P), z([scalar]P) +func montgomeryLadder(cparams *ProjectiveCurveParameters, P *ProjectivePoint, scalar []uint8, random uint) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + coefEq := CalcCurveParamsEquiv4(cparams) // for xDbl + aPlus2Over4 := CalcAplus2Over4(cparams) // for xDblAdd + R0 = *P // RO <- P + R1 = *P + Pow2k(&R1, &coefEq, 1) // R1 <- [2]P + R2 = *P // R2 = R1-R0 = P + + prevBit := uint8(0) + for i := int(random); i >= 0; i-- { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, swap) + R0, R1 = xDbladd(&R0, &R1, &R2, &aPlus2Over4) + } + cswap(&R0.X, &R0.Z, &R1.X, &R1.Z, prevBit) + return R0 +} + +// P = P + T +// From paper https://eprint.iacr.org/2017/212.pdf +// The map tau_T: P->P+T is (X : Z) -> (Z : X) on Montgomery curves +func tauT(P *ProjectivePoint) { + P.X, P.Z = P.Z, P.X // magic! +} + +// Construct Invalid public key tuple (P,Q) such that P and Q are linearly dependent +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKNoneLinear(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + + // Make sure Q is of full order 3^e_3, + var test_Q ProjectivePoint + test_Q = Q + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&test_Q, &cparam_q, e3-1) + + var test_QZ Fp2 + FromMontgomery(&test_QZ, &test_Q.Z) + + // Q are not of full order 3^e_3 + for((isZero(&test_QZ)==1)){ + rand.Seed(time.Now().UnixNano()) + random_index = rand.Intn(int(params.B.SecretByteLen-1)*8) + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + test_Q = Q + Pow3k(&test_Q, &cparam_q, e3-1) + FromMontgomery(&test_QZ, &test_Q.Z) + } + + // invQz = 1/Q.Z + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect linearly dependent ciphertext to fail, index: %v scalar: %v ", random_index, secret) + } +} + +// Construct Invalid public key tuple (P,Q) such that Q = [k]P + T, where k is random and T is the point of order 2. +// Simulate HB and section 3.1.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKT(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + rand.Seed(time.Now().UnixNano()) + random_index := rand.Intn(int(params.B.SecretByteLen-1)*8) + + // Set P as a point of order 3^e3 + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + + // Set Q = [k]P, where k = secret[:random_index] + Q = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index)) + // Q = [k]P + T + tauT(&Q) + + var invQz Fp2 + invQz = Q.Z + inv(&invQz, &invQz) + + mul(&P.X, &P.X, &P.Z) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext involve point T to fail, index: %v scalar: %v ", random_index, secret) + } +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[2^e2] +// Simulate section 3.2 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKOrder2(t *testing.T) { + + // Generate random scalar as secret + secret := make([]byte, params.B.SecretByteLen) + _, err := io.ReadFull(crand.Reader, secret) + if err != nil{ + t.Error("Fail read random bytes") + } + + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(params.A.SecretByteLen-1)*8) + random_index_q := rand.Intn(int(params.A.SecretByteLen-1)*8) + + P = montgomeryLadder(¶ms.InitCurve, &P, secret, uint(random_index_p)) + Q = montgomeryLadder(¶ms.InitCurve, &Q, secret, uint(random_index_q)) + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext in torsion E[2^e2] to fail, index_p: %v index_q: %v scalar: %v ", random_index_p, random_index_q, secret) + } + +} + + +// Construct Invalid public key tuple (P,Q) such that P and Q are in E[3^e3] but not of full order 3^e3 +// Simulate section 3.1.1 of paper https://eprint.iacr.org/2022/054.pdf +// We only construct point P and Q because in the attacks the third point is P-Q by construction +// and the countermeasure does not test it +// Without loss of generality, we assume the curve is the starting curve +func testInvalidPKFullOrder(t *testing.T) { + + var P, Q ProjectivePoint + + P = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + Q = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + + var e3 uint32 + e3_float := float64(int(params.B.SecretBitLen)+1)/math.Log2(3) + e3 = uint32(e3_float) + + rand.Seed(time.Now().UnixNano()) + random_index_p := rand.Intn(int(e3)) + random_index_q := rand.Intn(int(e3)) + + cparam_q := CalcCurveParamsEquiv3(¶ms.InitCurve) + Pow3k(&P, &cparam_q, uint32(random_index_p)) + Pow3k(&Q, &cparam_q, uint32(random_index_q)) + + + var invQz, invPz Fp2 + invQz = Q.Z + invPz = P.Z + inv(&invQz, &invQz) + inv(&invPz, &invPz) + + mul(&P.X, &P.X, &invPz) + mul(&Q.X, &Q.X, &invQz) + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: P.X, Z: params.OneFp2} + xQ = ProjectivePoint{X: Q.X, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect ciphertext not of full order to fail, index_p: %v index_q: %v ", random_index_p, random_index_q) + } + +} + +// A trivial test case not covered by paper https://eprint.iacr.org/2022/054.pdf and HB +// Countermeasure in https://eprint.iacr.org/2022/054.pdf only cares about P and Q +// But if PmQ is point T or O, that can also lead to recovery of the first bit +func testInvalidPmQ(t *testing.T) { + + var zero Fp2 + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.A.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.A.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: zero, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify==nil{ + t.Errorf("\nExpect PmQ as T to fail\n") + } + +} + +// Test valid ciphertext +// Where P, Q are linearly independent points of correct order 3^e3 in E[3^e3] +func testValidPQ(t *testing.T) { + + var xP, xQ, xQmP ProjectivePoint + xP = ProjectivePoint{X: params.B.AffineP, Z: params.OneFp2} + xQ = ProjectivePoint{X: params.B.AffineQ, Z: params.OneFp2} + xQmP = ProjectivePoint{X: params.OneFp2, Z: params.OneFp2} + + error_verify := PublicKeyValidation(¶ms.InitCurve, &xP, &xQ, &xQmP, params.B.SecretBitLen) + if error_verify!=nil{ + t.Errorf("\nExpect correct ciphertext to not fail\n") + } + +} + + +/* ------------------------------------------------------------------------- + Public key / Ciphertext validation against attacks proposed in paper https://eprint.iacr.org/2022/054.pdf and HB + -------------------------------------------------------------------------*/ + +func TestInvalidPK(t *testing.T) { + + t.Run("InvalidPmQ", testInvalidPmQ) + t.Run("InvalidPKNoneLinear", testInvalidPKNoneLinear) + t.Run("InvalidPKT", testInvalidPKT) + t.Run("InvalidPKOrder2", testInvalidPKOrder2) + t.Run("InvalidPKFullOrder", testInvalidPKFullOrder) + t.Run("ValidPQ", testValidPQ) + +} diff --git a/dh/sidh/sidh_test.go b/dh/sidh/sidh_test.go index c0c2bd9c3..5fdd1da99 100644 --- a/dh/sidh/sidh_test.go +++ b/dh/sidh/sidh_test.go @@ -246,7 +246,6 @@ func testKeyAgreement(t *testing.T, v sidhVec) { dec[0] = ^dec[0] err = alicePublic.Import(dec) CheckNoErr(t, err, "import failed") - bobPrivate.DeriveSecret(s1, alicePublic) alicePrivate.DeriveSecret(s2, bobPublic) if bytes.Equal(s1[:], s2[:]) {