|
| 1 | +package org.bouncycastle.crypto.kems; |
| 2 | + |
| 3 | +import org.bouncycastle.crypto.EncapsulatedSecretGenerator; |
| 4 | +import org.bouncycastle.crypto.SecretWithEncapsulation; |
| 5 | +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; |
| 6 | +import org.bouncycastle.crypto.params.SAKKEPublicKey; |
| 7 | +import org.bouncycastle.math.ec.ECCurve; |
| 8 | +import org.bouncycastle.math.ec.ECFieldElement; |
| 9 | +import org.bouncycastle.math.ec.ECPoint; |
| 10 | +import org.bouncycastle.util.Arrays; |
| 11 | +import org.bouncycastle.util.BigIntegers; |
| 12 | + |
| 13 | +import java.math.BigInteger; |
| 14 | +import java.nio.ByteBuffer; |
| 15 | +import java.security.SecureRandom; |
| 16 | + |
| 17 | +public class SAKKEKEMSGenerator |
| 18 | + implements EncapsulatedSecretGenerator |
| 19 | +{ |
| 20 | + private final SAKKEPublicKey publicParams; |
| 21 | + private final SecureRandom random; |
| 22 | + |
| 23 | + public SAKKEKEMSGenerator(SAKKEPublicKey params, SecureRandom random) |
| 24 | + { |
| 25 | + this.publicParams = params; |
| 26 | + this.random = random; |
| 27 | + } |
| 28 | + |
| 29 | + @Override |
| 30 | + public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recipientKey) |
| 31 | + { |
| 32 | + // 1. Generate random SSV in range [0, 2^n - 1] |
| 33 | + BigInteger ssv = new BigInteger(publicParams.getN(), random); |
| 34 | + |
| 35 | + // 2. Compute r = HashToIntegerRange(SSV || b, q) |
| 36 | + BigInteger b = getRecipientId((SAKKEPublicKey)recipientKey); |
| 37 | + BigInteger r = SAKKEUtils.hashToIntegerRange(Arrays.concatenate(ssv.toByteArray(), b.toByteArray()), publicParams.getQ()); |
| 38 | + |
| 39 | + // 3. Compute R_(b,S) = [r]([b]P + Z_S) |
| 40 | + ECPoint bP = publicParams.getP().multiply(b); // [b]P |
| 41 | + ECPoint Z_S = publicParams.getZ(); // Z_S |
| 42 | + ECPoint R_bS = bP.add(Z_S).multiply(r); // [r]([b]P + Z_S) |
| 43 | + |
| 44 | + // 4. Compute H = SSV XOR HashToIntegerRange( g^r, 2^n ) |
| 45 | + BigInteger g_r = pairing(R_bS, publicParams.getP(), publicParams.getQ(), publicParams.getP().getCurve().getField().getCharacteristic()); |
| 46 | + BigInteger mask = SAKKEUtils.hashToIntegerRange(g_r.toByteArray(), BigInteger.ONE.shiftLeft(publicParams.getN())); // 2^n |
| 47 | + |
| 48 | + BigInteger H = ssv.xor(mask); |
| 49 | + |
| 50 | + // 5. Encode encapsulated data (R_bS, H) |
| 51 | + byte[] encapsulated = encodeData(R_bS, H); |
| 52 | + |
| 53 | + return new SecretWithEncapsulationImpl( |
| 54 | + BigIntegers.asUnsignedByteArray(publicParams.getN() / 8, ssv), // Output SSV as key material |
| 55 | + encapsulated |
| 56 | + ); |
| 57 | + } |
| 58 | + |
| 59 | + private BigInteger getRecipientId(SAKKEPublicKey pubKey) |
| 60 | + { |
| 61 | + byte[] hashedId = SAKKEUtils.hash(pubKey.getZ().getEncoded(false)); // Hash Z_S |
| 62 | + return new BigInteger(1, hashedId).mod(pubKey.getQ().subtract(BigInteger.ONE)).add(BigIntegers.TWO); |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Computes the Tate-Lichtenbaum pairing ⟨P, Q⟩ as per RFC 6508. |
| 67 | + * <p> |
| 68 | + * //* @param P First point (on E(F_p)). |
| 69 | + * |
| 70 | + * @param Q Second point (on E(F_p)). |
| 71 | + * @return Result of the pairing in the field F_p^2. |
| 72 | + */ |
| 73 | + public static BigInteger pairing(ECPoint R, ECPoint Q, BigInteger p, BigInteger q) |
| 74 | + { |
| 75 | + ECCurve curve = R.getCurve(); |
| 76 | + ECFieldElement i = curve.fromBigInteger(BigInteger.ONE.negate()); // i = -1 in F_p^2 |
| 77 | + |
| 78 | + ECPoint C = R; |
| 79 | + BigInteger c = p.add(BigInteger.ONE).divide(q); |
| 80 | + ECFieldElement v = curve.fromBigInteger(BigInteger.ONE); // v = 1 in F_p |
| 81 | + |
| 82 | + String qBits = q.subtract(BigInteger.ONE).toString(2); // Binary representation of q-1 |
| 83 | + |
| 84 | + for (int j = 1; j < qBits.length(); j++) |
| 85 | + { // Skip MSB |
| 86 | + // l = (3 * (C_x^2 - 1)) / (2 * C_y) |
| 87 | + ECFieldElement Cx = C.getAffineXCoord(); |
| 88 | + ECFieldElement Cy = C.getAffineYCoord(); |
| 89 | + ECFieldElement l = Cx.square().multiply(curve.fromBigInteger(ECFieldElement.THREE)).subtract(curve.fromBigInteger(BigInteger.ONE)) |
| 90 | + .divide(Cy.multiply(curve.fromBigInteger(BigIntegers.TWO))); |
| 91 | + |
| 92 | + // v = v^2 * (l * (Q_x + C_x) + (i * Q_y - C_y)) |
| 93 | + ECFieldElement Qx = Q.getAffineXCoord(); |
| 94 | + ECFieldElement Qy = Q.getAffineYCoord(); |
| 95 | + v = v.square().multiply(l.multiply(Qx.add(Cx)).add(i.multiply(Qy).subtract(Cy))); |
| 96 | + |
| 97 | + // Double the point |
| 98 | + C = C.twice(); |
| 99 | + |
| 100 | + // If the bit is 1, perform additional step |
| 101 | + if (qBits.charAt(j) == '1') |
| 102 | + { |
| 103 | + // l = (C_y - R_y) / (C_x - R_x) |
| 104 | + ECFieldElement Rx = R.getAffineXCoord(); |
| 105 | + ECFieldElement Ry = R.getAffineYCoord(); |
| 106 | + l = Cy.subtract(Ry).divide(Cx.subtract(Rx)); |
| 107 | + |
| 108 | + // v = v * (l * (Q_x + C_x) + (i * Q_y - C_y)) |
| 109 | + v = v.multiply(l.multiply(Qx.add(Cx)).add(i.multiply(Qy).subtract(Cy))); |
| 110 | + |
| 111 | + // C = C + R |
| 112 | + C = C.add(R); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + // Compute v^c |
| 117 | + v = curve.fromBigInteger(v.toBigInteger().pow(c.intValue())); |
| 118 | + |
| 119 | + // Convert to F_p representative |
| 120 | + return computeFpRepresentative(v, curve); |
| 121 | + } |
| 122 | + |
| 123 | + private static BigInteger computeFpRepresentative(ECFieldElement t, ECCurve curve) |
| 124 | + { |
| 125 | + // Characteristic of F_p |
| 126 | + BigInteger p = ((ECCurve.Fp) curve).getQ(); |
| 127 | + |
| 128 | + // Assume t = a + i * b in F_p² → extract a, b |
| 129 | + ECFieldElement a = t; // In F_p², a is the real part |
| 130 | + ECFieldElement b = t.multiply(curve.fromBigInteger(BigInteger.ONE.negate())); // Imaginary part |
| 131 | + |
| 132 | + // Compute b/a mod p |
| 133 | + return b.toBigInteger().multiply(a.toBigInteger().modInverse(p)).mod(p); |
| 134 | + } |
| 135 | + |
| 136 | + public static byte[] encodeData(ECPoint R_bS, BigInteger H) { |
| 137 | + // 1. Serialize EC Point (use compressed format for efficiency) |
| 138 | + byte[] R_bS_bytes = R_bS.getEncoded(true); |
| 139 | + |
| 140 | + // 2. Serialize H (convert to a fixed-length byte array) |
| 141 | + byte[] H_bytes = H.toByteArray(); |
| 142 | + |
| 143 | + // 3. Combine both into a single byte array |
| 144 | + ByteBuffer buffer = ByteBuffer.allocate(R_bS_bytes.length + H_bytes.length); |
| 145 | + buffer.put(R_bS_bytes); |
| 146 | + buffer.put(H_bytes); |
| 147 | + |
| 148 | + return buffer.array(); |
| 149 | + } |
| 150 | +} |
0 commit comments