encap_decap_bits.tentative.https.any.js (5828B)
1 // META: title=WebCryptoAPI: ML-KEM encapsulateBits() and decapsulateBits() tests 2 // META: script=ml_kem_vectors.js 3 // META: script=../util/helpers.js 4 // META: timeout=long 5 6 function define_bits_tests() { 7 var subtle = self.crypto.subtle; 8 var variants = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; 9 10 variants.forEach(function (algorithmName) { 11 // Test encapsulateBits operation 12 promise_test(async function (test) { 13 // Generate a key pair for testing 14 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 15 'encapsulateBits', 16 'decapsulateBits', 17 ]); 18 19 // Test encapsulateBits 20 var encapsulatedBits = await subtle.encapsulateBits( 21 { name: algorithmName }, 22 keyPair.publicKey 23 ); 24 25 assert_true( 26 encapsulatedBits instanceof Object, 27 'encapsulateBits should return an object' 28 ); 29 assert_true( 30 encapsulatedBits.hasOwnProperty('sharedKey'), 31 'Result should have sharedKey property' 32 ); 33 assert_true( 34 encapsulatedBits.hasOwnProperty('ciphertext'), 35 'Result should have ciphertext property' 36 ); 37 assert_true( 38 encapsulatedBits.sharedKey instanceof ArrayBuffer, 39 'sharedKey should be ArrayBuffer' 40 ); 41 assert_true( 42 encapsulatedBits.ciphertext instanceof ArrayBuffer, 43 'ciphertext should be ArrayBuffer' 44 ); 45 46 // Verify sharedKey length (should be 32 bytes for all ML-KEM variants) 47 assert_equals( 48 encapsulatedBits.sharedKey.byteLength, 49 32, 50 'Shared key should be 32 bytes' 51 ); 52 53 // Verify ciphertext length based on algorithm variant 54 var expectedCiphertextLength; 55 switch (algorithmName) { 56 case 'ML-KEM-512': 57 expectedCiphertextLength = 768; 58 break; 59 case 'ML-KEM-768': 60 expectedCiphertextLength = 1088; 61 break; 62 case 'ML-KEM-1024': 63 expectedCiphertextLength = 1568; 64 break; 65 } 66 assert_equals( 67 encapsulatedBits.ciphertext.byteLength, 68 expectedCiphertextLength, 69 'Ciphertext should be ' + 70 expectedCiphertextLength + 71 ' bytes for ' + 72 algorithmName 73 ); 74 }, algorithmName + ' encapsulateBits basic functionality'); 75 76 // Test decapsulateBits operation 77 promise_test(async function (test) { 78 // Generate a key pair for testing 79 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 80 'encapsulateBits', 81 'decapsulateBits', 82 ]); 83 84 // First encapsulate to get ciphertext 85 var encapsulatedBits = await subtle.encapsulateBits( 86 { name: algorithmName }, 87 keyPair.publicKey 88 ); 89 90 // Then decapsulate using the private key 91 var decapsulatedBits = await subtle.decapsulateBits( 92 { name: algorithmName }, 93 keyPair.privateKey, 94 encapsulatedBits.ciphertext 95 ); 96 97 assert_true( 98 decapsulatedBits instanceof ArrayBuffer, 99 'decapsulateBits should return ArrayBuffer' 100 ); 101 assert_equals( 102 decapsulatedBits.byteLength, 103 32, 104 'Decapsulated bits should be 32 bytes' 105 ); 106 107 // The decapsulated shared secret should match the original 108 assert_true( 109 equalBuffers(decapsulatedBits, encapsulatedBits.sharedKey), 110 'Decapsulated shared secret should match original' 111 ); 112 }, algorithmName + ' decapsulateBits basic functionality'); 113 114 // Test round-trip compatibility 115 promise_test(async function (test) { 116 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 117 'encapsulateBits', 118 'decapsulateBits', 119 ]); 120 121 var encapsulatedBits = await subtle.encapsulateBits( 122 { name: algorithmName }, 123 keyPair.publicKey 124 ); 125 126 var decapsulatedBits = await subtle.decapsulateBits( 127 { name: algorithmName }, 128 keyPair.privateKey, 129 encapsulatedBits.ciphertext 130 ); 131 132 assert_true( 133 equalBuffers(encapsulatedBits.sharedKey, decapsulatedBits), 134 'Encapsulated and decapsulated shared secrets should match' 135 ); 136 }, algorithmName + 137 ' encapsulateBits/decapsulateBits round-trip compatibility'); 138 139 // Test vector-based decapsulation 140 promise_test(async function (test) { 141 var vectors = ml_kem_vectors[algorithmName]; 142 143 // Import the private key from the vector's privateSeed 144 var privateKey = await subtle.importKey( 145 'raw-seed', 146 vectors.privateSeed, 147 { name: algorithmName }, 148 false, 149 ['decapsulateBits'] 150 ); 151 152 // Decapsulate the sample ciphertext from the vectors 153 var decapsulatedBits = await subtle.decapsulateBits( 154 { name: algorithmName }, 155 privateKey, 156 vectors.sampleCiphertext 157 ); 158 159 assert_true( 160 decapsulatedBits instanceof ArrayBuffer, 161 'decapsulateBits should return ArrayBuffer' 162 ); 163 assert_equals( 164 decapsulatedBits.byteLength, 165 32, 166 'Decapsulated bits should be 32 bytes' 167 ); 168 169 // The decapsulated shared secret should match the expected value from vectors 170 assert_true( 171 equalBuffers(decapsulatedBits, vectors.expectedSharedSecret), 172 "Decapsulated shared secret should match vector's expectedSharedSecret" 173 ); 174 }, algorithmName + ' vector-based sampleCiphertext decapsulation'); 175 }); 176 } 177 178 // Helper function to compare two ArrayBuffers 179 function equalBuffers(a, b) { 180 if (a.byteLength !== b.byteLength) { 181 return false; 182 } 183 var aBytes = new Uint8Array(a); 184 var bBytes = new Uint8Array(b); 185 for (var i = 0; i < a.byteLength; i++) { 186 if (aBytes[i] !== bBytes[i]) { 187 return false; 188 } 189 } 190 return true; 191 } 192 193 define_bits_tests();