external/boringssl: Sync to fa3aadcd40ec4fd27a6e9492ef099b3dcc6eb2af.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/7f7e5e231efec6e86d6c7d3fd1b759be1cece156..fa3aadcd40ec4fd27a6e9492ef099b3dcc6eb2af

Test: BoringSSL CTS Presubmits.
Change-Id: I5381241ee7b94e1076d04090a0bc468b7816a1a1
diff --git a/BORINGSSL_REVISION b/BORINGSSL_REVISION
index 9724cc9..21971f5 100644
--- a/BORINGSSL_REVISION
+++ b/BORINGSSL_REVISION
@@ -1 +1 @@
-7f7e5e231efec6e86d6c7d3fd1b759be1cece156
+fa3aadcd40ec4fd27a6e9492ef099b3dcc6eb2af
diff --git a/crypto_test_data.cc b/crypto_test_data.cc
index f76567a..18bb8f2 100644
--- a/crypto_test_data.cc
+++ b/crypto_test_data.cc
@@ -2679,7 +2679,7 @@
     "a0cdf2910c42c9f1954a4572d8e659733d5e26cbd35e3260be40017b2f5d38ec42315f5c0b056c596d\nR = 00d732ba8b3e9c9e0a495249e152e5bee69d94e9ff012d001b140d4b5d082aa9df77e10b65f115a594a50114722db42fa5fbe457c5bd05e7ac7ee510aa68fe7b1e7f\nS = 0134ac5e1ee339727df80c35ff5b2891596dd14d6cfd137bafd50ab98e2c1ab4008a0bd03552618d217912a9ec502a902f2353e757c3b5776309f7f2cfebf913e9cd\n\nCurve = P-521\nPrivate = 013c3852a6bc8825b45fd7da1754078913d77f4e586216a6eb08b6f03adce7464f5dbc2bea0eb7b12d103870ef045f53d67e3600d7eba07aac5db03f71b64db1cceb\nX = 00c97a4ebcbbe701c9f7be127e87079edf479b76d3c14bfbee693e1638e5bff8d4705ac0c14597529dbe13356ca85eb03a418edfe144ce6cbf3533016d4efc29dbd4\nY = 011c75b7a8894ef64109ac2dea972e7fd5f79b75dab1bf9441a5b8b86f1dc1324426fa6cf4e7b973b44e3d0576c52e5c9edf8ce2fc18cb3c28742d44419f044667f8\nDigest = d209f43006e29ada2b9fe840afdf5fe6b0abeeef5662acf3fbca7e6d1bf4538f7e860332ef6122020e70104b541c30c3c0581e2b1daa0d767271769d0f073133\nK = 01e25b86db041f21c2503d547e2b1b655f0b99d5b6c0e1cf2bdbd8a8c6a053f5d79d78c55b4ef75bff764a74edc920b35536e3c470b6f6b8fd53898f3bbc467539ef\nR = 01dce45ea592b34d016497882c48dc0c7afb1c8e0f81a051800d7ab8da9d237efd892207bc9401f1d30650f66af8d5349fc5b19727756270722d5a8adb0a49b72d0a\nS = 00b79ffcdc33e028b1ab894cb751ec792a69e3011b201a76f3b878655bc31efd1c0bf3b98aea2b14f262c19d142e008b98e890ebbf464d3b025764dd2f73c4251b1a\n\nCurve = P-521\nPrivate = 01654eaa1f6eec7159ee2d36fb24d15d6d33a128f36c52e2437f7d1b5a44ea4fa965c0a26d0066f92c8b82bd136491e929686c8bde61b7c704daab54ed1e1bdf6b77\nX = 01f269692c47a55242bb08731ff920f4915bfcecf4d4431a8b487c90d08565272c52ca90c47397f7604bc643982e34d05178e979c2cff7ea1b9eaec18d69ca7382de\nY = 00750bdd866fba3e92c29599c002ac6f9e2bf39af8521b7b133f70510e9918a94d3c279edec97ab75ecda95e3dd7861af84c543371c055dc74eeeff7061726818327\nDigest = c992314e8d282d10554b2e6e8769e8b10f85686cccafb30e7db62beaad080e0da6b5cf7cd1fc5614df56705fb1a841987cb950101e2f66d55f3a285fc75829ff\nK = 01b7519becd00d750459d63a72f13318b6ac61b8c8e7077cf9415c9b4b924f35514c9c28a0fae43d06e31c670a873716156aa7bc744577d62476e038b116576a9e53\nR = 0183bddb46c249e868ef231a1ebd85d0773bf8105a092ab7d884d677a1e9b7d6014d6358c09538a99d9dca8f36f163ac1827df420c3f9360cc66900a9737a7f756f3\nS = 00d05ee3e64bac4e56d9d8bd511c8a43941e953cba4e5d83c0553acb87091ff54f3aad4d69d9f15e520a2551cc14f2c86bb45513fef0295e381a7635486bd3917b50\n\nCurve = P-521\nPrivate = 01cba5d561bf18656991eba9a1dde8bde547885ea1f0abe7f2837e569ca52f53df5e64e4a547c4f26458b5d9626ed6d702e5ab1dd585cf36a0c84f768fac946cfd4c\nX = 012857c2244fa04db3b73db4847927db63cce2fa6cb22724466d3e20bc950a9250a15eafd99f236a801e5271e8f90d9e8a97f37c12f7da65bce8a2c93bcd25526205\nY = 00f394e37c17d5b8e35b488fa05a607dbc74264965043a1fb60e92edc212296ae72d7d6fe2e3457e67be853664e1da64f57e44bd259076b3bb2b06a2c604fea1be9d\nDigest = 6e14c91db5309a075fe69f6fe8ecd663a5ba7fab14770f96b05c22e1f631cde9e086c44335a25f63d5a43ddf57da899fcedbc4a3a4350ad2edd6f70c01bb051e\nK = 00e790238796fee7b5885dc0784c7041a4cc7ca4ba757d9f7906ad1fcbab5667e3734bc2309a48047442535ff89144b518f730ff55c0c67eeb4c880c2dfd2fb60d69\nR = 01d7ce382295a2a109064ea03f0ad8761dd60eefb9c207a20e3c5551e82ac6d2ee5922b3e9655a65ba6c359dcbf8fa843fbe87239a5c3e3eaecec0407d2fcdb687c2\nS = 0161963a6237b8955a8a756d8df5dbd303140bb90143b1da5f07b32f9cb64733dc6316080924733f1e2c81ade9d0be71b5b95b55666026a035a93ab3004d0bc0b19f\n\nCurve = P-521\nPrivate = 00972e7ff25adf8a032535e5b19463cfe306b90803bf27fabc6046ae0807d2312fbab85d1da61b80b2d5d48f4e5886f27fca050b84563aee1926ae6b2564cd756d63\nX = 01d7f1e9e610619daa9d2efa563610a371677fe8b58048fdc55a98a49970f6afa6649c516f9c72085ca3722aa595f45f2803402b01c832d28aac63d9941f1a25dfea\nY = 01571facce3fcfe733a8eef4e8305dfe99103a370f82b3f8d75085414f2592ad44969a2ef8196c8b9809f0eca2f7ddc71c47879e3f37a40b9fecf97992b97af29721\nDigest = 26b4f562053f7aed8b7268e95eff336ac80a448fae52329d2771b138c9c7f70de936ef54158446afa72b0a27c2a73ca45dfa38a2ba2bf323d31aba499651128f\nK = 00517f6e4002479dc89e8cbb55b7c426d128776ca82cf81be8c1da9557178783f40e3d047db7e77867f1af030a51de470ee3128c22e9c2d642d71e4904ab5a76edfa\nR = 01c3262a3a3fb74fa5124b71a6c7f7b7e6d56738eabaf7666b372b299b0c99ee8a16be3df88dd955de093fc8c049f76ee83a4138cee41e5fe94755d27a52ee44032f\nS = 0072fd88bb1684c4ca9531748dfce4c161037fcd6ae5c2803b7117fb60d3db5df7df380591aaf3073a3031306b76f062dcc547ded23f6690293c34a710e7e9a226c3\n\nCurve = P-521\nPrivate = 01f0ec8da29295394f2f072672db014861be33bfd9f91349dad5566ff396bea055e53b1d61c8c4e5c9f6e129ed75a49f91cce1d5530ad4e78c2b793a63195eb9f0da\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = ea13b25b80ec89ffa649a00ce85a494892f9fb7389df56eed084d670efb020c05508ac3f04872843c92a67ee5ea02e0445dad8495cd823ca16f5510d5863002b\nK = 00ac3b6d61ebda99e23301fa198d686a13c0832af594b289c9a55669ce6d62011384769013748b68465527a597ed6858a06a99d50493562b3a7dbcee975ad34657d8\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\n# The following tests exercise the bit-shifting case of ECDSA digest\n# truncation. The digests are larger than even SHA-512, but P-521 is the only\n# common prime-field curve. (This case typically comes up with curves over\n# GF(2^m).)\n\nCurve = P-521\nPrivate = 01f0ec8da29295394f2f072672db014861be33bfd9f91349dad5566ff396bea055e53b1d61c8c4e5c9f6e129ed75a49f91cce1d5530ad4e78c2b793a63195eb9f0da\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac31801580\nK = 00ac3b6d61ebda99e23301fa198d686a13c0832af594b289c9a55669ce6d62011384769013748b68465527a597ed6858a06a99d50493562b3a7dbcee975ad34657d8\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\nCurve = P-521\nPrivate = 01f0ec8da29295394f2f072672db014861be33bfd9f91349dad5566ff396bea055e53b1d61c8c4e5c9f6e129ed75a49f91cce1d5530ad4e78c2b793a63195eb9f0da\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac318015ff\nK = 00ac3b6d61ebda99e23301fa198d686a13c0832af594b289c9a55669ce6d62011384769013748b68465527a597ed6858a06a99d50493562b3a7dbcee975ad34657d8\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\nCurve = P-521\nPrivate = 01f0ec8da29295394f2f072672db014861be33bfd9f91349dad5566ff396bea055e53b1d61c8c4e5c9f6e129ed75a49f91cce1d5530ad4e78c2b793a63195eb9f0da\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac318015ffffffffffff\nK = 00ac3b6d61ebda99e23301fa198d686a13c0832af594b289c9a55669ce6d62011384769013748b68465527a597ed6858a06a99d50493562b3a7dbcee975ad34657d8\nR = ",
     "00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\n\n# The following tests use digests equal to the order and 2^n - 1, where n is\n# the number of bits in the order. This is to test the truncated digest not\n# being fully reduced.\n\nCurve = P-224\nPrivate = a80489eee3b15dedbc2d8ca4134f18b7d1a541fc212718f21a04692c\nX = bd283d0c18d90b69d9ee3e0f1e8e62f53822f5593fc94343666495b5\nY = b3177709b8dc4b62928f9dc561c2b4be42c7df52d4e90e7e885b4021\nDigest = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d\nK = 90fbb04276d112cbb6ecd2053e2a870f02350ac7e2881c89851a4640\nR = 7d0642a2cb98b56ff91837bd23e20bd90b60613b60eabfbc078cfbfa\nS = 0209a75bbd6c2310fa55fe2c0c3ddf35be53fef6e1cccf0537f3e7be\n\nCurve = P-224\nPrivate = 72a2e505634a669d492d28b1b43974cca3aac7b5eaffa1719a551d3e\nX = 42bafdd82b5bd766a727211e4af8bf46015705b878772b296791cca3\nY = f5db26e760f4b2ec586222d3cecb525fed32a841fa0ae547f5c435db\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nK = d6abc49b0e60f1e2e7a5736aa8e93a5de9777f4b9e6c96692fcb662b\nR = 42232b212356d9adbb5e43e96e23c376fa5d21c9ad6a50137d2e3bd2\nS = 020596ef40a9dbea4d6779ff02c9cb853b520093113a968a32309118\n\nCurve = P-256\nPrivate = fb801b1a1161c143578358dc6edf8357167c12636e5b588e171d8bffcca78d7a\nX = e57231383637c82c1ac801724cf7e03e67198f467a9beb60ac13cb582d13afa8\nY = 8f190e090155fcf63810b858bc88e259dc49afef8bdef6fd06d93dddb1991aed\nDigest = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551\nK = 3d1df8b364fc045d8c6517f7a4b99c91643a2bca351b3a74fe36268c97198c3e\nR = 05cc6037bb021f4910ea2e489fab2bae6bb6a2769a97f42ba5736994102b7f10\nS = 5db54832ceabf8bccdb8be99b1a49cecff8feee045cb697dec43118e2695b1da\n\nCurve = P-256\nPrivate = df1ae1f7a1043d03811c61695dba0350bbe58d36a670da66d58c69e5bc9ce1fd\nX = 6e0e2897b9a554ee287cdaf43bfbe25ca8404373971575a0e4b61c61aff5a2fe\nY = 23ea7823a411eb1b39f81bbde24c2cd6ac68be2c7eec3a0671c8676131b8905c\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nK = 6b6e0cf93ce4482a4c23821125186f39656ccc993e4f080ac8750c32927a515f\nR = 16831feeceab2fab1c575e073e944d73ce7e6f3e9b06312088f06159c530ff50\nS = 870cb824692638538b1569c6093fcb693c054e8e3b9a919e3bb26798910f66e9\n\nCurve = P-384\nPrivate = 2218a70d35d5a9eb16442eee8e74a8b992d9475edadd6b814ae6c8779b32df164553546bf3405bd5242b85092e2f0098\nX = f4a961c19f9cc4ebe4f43081110955f3cede085a08c1415d726e80b2eb774028c5fc96f092ba3ea7d1288dd57fe1db08\nY = 981398eed0895e09b3b582a0616f3024e51cca7b1ecc347dbf0d24a5f6a222b0c31912f8f5e427d4dde5c6c45212bb10\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973\nK = 118f1682e0dc4602fc6f142f98d48e36adf32566f34be311ca55ccbe00fec28e52d72857e02f139578316a5dbe1ed9b4\nR = 0b77eaff05bbd922dd80525d2ab301cc119318f5a920a12c71c4b5ff5bb77d25a538983df9bdd5984b0d159daf21f1a2\nS = 73af85ad03a34b6b3993082bf719018d25d1555717b2d2f2535d0601af06a71ad020eff8232d065ab9d7fc4cd0c0ee42\n\nCurve = P-384\nPrivate = fae6a843fcef48d15685766d189fe1f597cd85d4e07172c8e19589e1aa2e8e8c4b75731e9afccb7b585926934583829b\nX = 54dd8d7cbf2ccdf1a42f5bbc615a372803b094f6040e3c7b651a61bc6912432c836cf2410ab7d67f543236751d81066f\nY = 2219d6257b1c80bf327c96786f2b5d0b5a9b9bf7eee9c853bf66a3bf09520494cb1f7823e4c566d79a617b7e201ead96\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nK = b7db03d70db5cdcce3c708e55ad88eba80e90f6bb0be3713686bf298709a8b326619c1d47318f9af60039ff051f33a1e\nR = 9d923e199d98272e44b8fba382bf3c19660ecb4a9aae3513ff6802a73fef510c15c202807c3f9334b0bce7d6c6a80839\nS = 520784e6290d04d9b61993ee5ebc6fa8ff527fb0777c43cdefc7586701e60edb399005a5648ff852de80208232849fbd\n\nCurve = P-521\nPrivate = 015a5274c44e51b3cce4b1d657186871a851747e086934cb132559d83e07b3b2544c5d62b26385272101e20f963d2df6e029d6a6818cc4839c3f28a4c384dff4befa\nX = 00056cc489982829b728978193d047596325a91ee2e2c9110f7da605fd2d1b78424e87d85500f391fe9f54209c42e582ca3284484afc6edfe2acdc69c3591f6c47cf\nY = 010e91be6632da7afd03caedebdb572fd41cb1a7221e9c2d984016bac4693b3d10c5b1d76ba32b89f5fadd157df122be9cd85151977b99176998cfccbd3f9a03ba3f\nDigest = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409\nK = 001441599703e14eca10a787dd421c334bdd4c91ad33d05fe2929100a5eb343df47fb28236423323e769ad1cbaffc6e9ad01a06b2e401d647511ad2e920c2145262d\nR = 00bd5e59a9bc97de61588d143990ad7fd5405ac53aa8e6332a085a301138b23beaba126b41549db1167df47362a9de77c73b1bfaa14b31114644b4db8d35179f706a\nS = 000cbb560f68b7240e309301ed4e6dc20d329f7e2098bcae26a07dd364e6177bb408eb5d0b47a3fcf36def98b951af9a55a47d24d95cd66cc11973269694e2f6f8d1\n\nCurve = P-521\nPrivate = 00cfac6f8a1906241d873da27b4166e0d0bd76c511177835d0978117056db44750eb0648e6899f215e6c0dd6902c114a802ed5935df8c54290fbfe184ff8ccae444e\nX = 002aca58eeac43152b292f42a6a677d327386337409ba7de17acae1978e097f21e49d47f707c6ed6045c66551c93df9ef9bcc442db804e62fcac9f0574876d6d7fea\nY = 01862ed4f9d235afcc4e6b45e491da363104d4db7b97f12d869c40ab09a3c8c72519a9712ca733ddf046ad039842e8caed2425ecaf42d5171b3e236c11fee8699684\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nK = 00938d2f6550a46fb07b058e6287f428f0ff12aa6732a666d4a6cf2dd7cd8023ca76d0ce4e16b62830d0ff9e2fab9987261f3f3ffe0749ff70950d91b897d57007b2\nR = 00ec0b91fa4386a8acdc0e46dd9c1d1775abbe0da8ead424aa4ace58e284a5be00e2c1ef95b6f4d861615564e1e7305656567f95275ce63b534420eae77ec37492c2\nS = 01e1099fb389db498ab4cf23b4f06a74b9326878ae3c76ea13832e50702b30fe8303093a59cc9a0995f1dfc15e6f7dabca8a2acaf03ec005447d29fb429a252064ec\n",
 };
-static const size_t kLen43 = 150070;
+static const size_t kLen43 = 170210;
 
 static const char *kData43[] = {
     "# Tests from NIST CAVP 186-4 ECDSA2VS Test Vectors, Signature Verification Test\n# http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip\n#\n# NIST's files provide message and digest pairs. Since this is a low-level test,\n# the digests have been extracted. P-521 test vectors were fixed to have the\n# right number of leading zeros.\n\nCurve = P-224\nX = a100d410ce497e991070285c439cd361a1a9c6c973fd6f5e1ba9ec66\nY = 0a8c3a2f909f212c84441b8c0030529cbd731304d86f771d89d7cc29\nDigest = 6a02c84186eb132d8e91ab6fea2e066f6f8de1a5\nR = 1bfcaab01e47addd4733369320364ad208169ffb15e6aac33c2d7c06\nS = 07fb33465e7b7b373feda2ea35ab7cc9477156a1335ecad942f99627\n\nCurve = P-224\nX = a6cd3d14cd5eb188a9f59d9c32e93d890558de382f6fba5ff5c6e395\nY = 7a76734a0afead9e5e4aba65f1ae353d6445b1689b5ea402de5f9af9\nDigest = ab1205b7f9f591a013c70cb645435b38689644c1\nR = f0f670963c3d2a3281d639f850f3781c6402d99a1bf07cd9f35b2975\nS = 758e84920c1b744502cd787cdd64ec58364ccc6917258a2580097492\nInvalid =\n\nCurve = P-224\nX = f5fe7875a517207f1336ec2bb4fe5cc7eb80ee2b0f8ebeff4c56e620\nY = 0b7ac24ea9092d03b28904d89714b517be023235abc9cffa297cf4ad\nDigest = d62c7a42fcf3738276a6e0d27160328e9f27e5aa\nR = 88617e694e361d2cfef6b0658d444607fba030ad31fe8dead14db22e\nS = 5b0bf37c4a583dd75d99aec20943ea02617cecdbcd295d35ed01cc32\nInvalid =\n\nCurve = P-224\nX = 8a6a77179ffc0ff5d412cf859cc82aa19cd18e5224ab997e9c2e46b0\nY = 3d67c177ca7cc12c7b05a3bf55fb78549ef5400a566efe8ae3580c9f\nDigest = 0b5a025a4038b6f9f995001c0b8d7a660e6766c2\nR = 107b7442e6569ddde54b5da55a9dac9bd348079358047a19a3de0b91\nS = 92359be39353cb263946294fb728eecf1880f50a43637f391d3e7824\n\nCurve = P-224\nX = f9f23388d573562f29e7e7c9a98f27e7a1ff02d2d66e177c6506466f\nY = 4545937caf1878fbacc34ca38a0e5e1f6ad2b25ddd796d06c8d12351\nDigest = cf670c7589b91dac6a131fe2e863e86ee790ca75\nR = bc1db32e437c67439c27db1dc607e3c505210c984bf707a8e87abb70\nS = b760f4943a2397311e54e888a1ad379ad9c45d1fd09b5389ce1a00ee\nInvalid =\n\nCurve = P-224\nX = 8781e5a98950092570d685964e9ed27760fb7dcff8d3b6f3c8f77151\nY = 9207cef64b7c2ed181c57337001f45f1e800e0d1bc8adac296e454b5\nDigest = e5e5e8c8b3f7146c72ef86ca1b75f422181b5729\nR = 79826ae5b0297b9404829df0f02bbb7b8acb35459e13a4045c40f242\nS = 2a629dab19c9e5cd0a551a43851fe6d8409469f86cbcf6204b41e5b5\nInvalid =\n\nCurve = P-224\nX = 03c78c532b8767784fd45e75027abce3371181f8f54914811588cbb2\nY = 166c7b70e98fa11ac361d827557676ec07e553370a462b4fe502dedb\nDigest = 270606c9c9b136ffada9588f15f9786455369422\nR = ff18b493b166d832c9c25ee491525e4c188ff2b804e38b5964941c48\nS = bbf4291db484b4e4143c01a284c03543bbdaa2db1f1c571f1e5a5e2e\nInvalid =\n\nCurve = P-224\nX = 99fab11464484cee96d72dfcf0327d671787a2f6ee32f9b184c48fec\nY = fe8ec3d660cfa3f3e09e5cfc2c3298d4de2f464416deb5b4a27ac062\nDigest = 9c77c796ba619aedef68b2d30b4ba00c4972488b\nR = 714c48c143cb259408c04f77a38d6484e788cb268fc9789d5e871491\nS = 542793d5dbcabcebc83a809cca02b8e95189c93fa4e330d66d5a62ef\nInvalid =\n\nCurve = P-224\nX = 014e8e57388eba32ebdce80df60c481e5c7758374f90a92e0a82f1b9\nY = d1aa8418f992283c5b6bb0461f05dc9103050dc55e0265e1c99b935d\nDigest = 82b45d1fb3bb502c7c20ee1e2d63f2aaa9f492ab\nR = a159b83e80e656f54f614e8437821bd87f6f13264ac8eca1b3ddde29\nS = b77b7bc8cf374f012ee15f9f9224a46a560a5b689cfc92ca4fa03459\nInvalid =\n\nCurve = P-224\nX = e0b9e3cadca81311923d6d6adcfc326b62fac9c4b8d61c5f960c88fa\nY = be505338108f8d3f0ee80aefa304d51dd4a4035477934a98a6111403\nDigest = f4da99fee346e572906e6dc8083a3d0c2e09b773\nR = 8dba585dc3312056a7be61161c7af8ba8b538f0c125c80cf9af2682e\nS = 1b5b1adac4d66c7045f3f79c3aa154a0274c4a994ac7a093e2482eeb\nInvalid =\n\nCurve = P-224\nX = 29197e94a3617e62d9999c859640871a4537a073ca4f12a4c324dcad\nY = fe198969ac7cbe49df2c61c4cc6fa502c2207a7da10acdccec7b1cad\nDigest = 58fab970cb7c1f0dac21b7c7fd67d0ad169688a1\nR = 261670b09afaeee71c590c5658e3f57d859b18a887f70fdeb90e57ea\nS = d1d12c11cf7f4a9dd015ead4bd245793cb37ffee1f4cf109b7b68394\nInvalid =\n\nCurve = P-224\nX = 0fac352c1c444435e6aeb1d60f28ac773b0170ae902afb0944ef0a12\nY = ac3ca693a7c5347a074808b43edea94059e2b1d0571d935fde3f5841\nDigest = 4b69dbfac12f1b974566d8170d1672d0f5fc0506\nR = c33c7a4de313ff856d2f51cd9e3d173bd10668c296f0e6b208c036ef\nS = e562d30822b5cc69713a57ce8c70f83827add85a06c88109505ebf7a\nInvalid =\n\nCurve = P-224\nX = b0d4298e998b7d9d4509322a1ac974c6180956533debafd3d9e7f2fc\nY = 185a64ca840d4b6a2800e72433f26dd523f97daadc18d6d01533f0ad\nDigest = b84805c37e76e530729ddcb59a68ad69d40c82f9\nR = a5155ce53050cbfe84b67d62ce118c6004564087f2fe1cdf44e9c945\nS = b6894b050d77a3ff4d191ddc0c9fc7009a7472e31739949193d7cceb\nInvalid =\n\nCurve = P-224\nX = 59996a4a06658e553fc2993f0f55e3fc8ca2cb52d30f882a37729be4\nY = a5f68f26ea6608fd1f350d8da7c187c7e70f23363177a5aa41508fce\nDigest = ef0a69578d8a1dc930803a7ad2a92c3c19ab6513\nR = 704ef49e0a43c61ef5b325899acb9d12287883a849976c8b9c950634\nS = 73da6e3a26d5c512405fc09fcfdf650dd8da748e6c3dfc05032d7a9f\n\nCurve = P-224\nX = a0cfdfc5a096b0b23ba6748ebaad17e60228b204aebdc01057a7154b\nY = 9f6bd5369d21d88d7b5c3ce221af530fb9a8fb91e751cdb855ff32a6\nDigest = b05f0232e6d44151e249e7b75c7c9ab05c14d44b\nR = d68aa9048e84b8653b8ff3ab31bc73884c6ac7df1fd1bd3c38c16b0d\nS = 38ce58afe5fbc6af892e06a4ddd978c745d5ec700cab825c11dd8fd1\nInvalid =\n\nCurve = P-224\nX = f1eb36b3e1c96a18d87878d5fa8b79d77afce9d2ce40d26199f33482\nY = ae819af474f3efbd62401a407036505c5a2d60449274593865de3374\nDigest = 1dd27c95dd6fb3f080afebdf5a1ad906502e12ab8f64e5f38f67c386\nR = 003122e976bac378c06ec95fd73290b067e7ff022d23493c40663ec9\nS = b99eb4220146a282c7a34f98a9a4fa38ed3f48ca2c7983cde2d3235f\n\nCurve = P-224\nX = 3bdcc7c6112cde3c0522f1a4863f1d7b6727c5bff67598ba2f1bafc1\nY = 47acb6b254e0e8747e0039de471d0dda443cb09a592c678717d83200\nDigest = 19b39292f4e862ed3ee90c35e709587231191632dc8b35611dd24abe\nR = a5aab7768f549f8fe3c7e650154c865b71ea5089bd6303bfdfd19316\nS = ee4989c4b96bcc802464fe44b2adeb1b3506755a3f4fb3f9252bf21b\nInvalid =\n\nCurve = P-224\nX = 6d5bacf458cee3ded627d0ff14fd2aeb54fe1455d6daaf7bb43faeea\nY = caecc8d3967ca1c8889607e9ed975b8a335a17c0acbcfbfed721ee1c\nDigest = 328ab7d2a7c56d09cb72cedaacc23a6da46d5cf984dfdfd16af60964\nR = 80e7024bf30ecddf7a658785ae51cd6e5a23963c89ee96a82346d889\nS = 561252dc8d9280fc54da0046da494fa5e4b7aed213923e8b894a1ae3\nInvalid =\n\nCurve = P-224\nX = 7f9789c729355516588a5c75cb2cbcf85a14c35e14a5d03b4ef920d7\nY = 49e95c49e62dd20f02ed16594f35ebf3415ed50e6efdc0c548101a9d\nDigest = c5bb2d7ca9b37af1f4bb572ae6b6e69e8fcab9ac1cc5a6e1b6d1f8de\nR = 3c7b664413c2a0e4682a9d1c88243a96196fbd03f72cb873b9bee8b9\nS = 8f7f81ee9d3a2660ab1d666bac6cc434143ca9b04ff638ca7b4aa1ea\n\nCurve = P-224\nX = fd3efc7108edbe155adcd8686d8605e811fa79756c7e2dc8c1c04212\nY = 59edea73a4e5f91541fb4cabce539afffa85b6b0113289f049ce60a0\nDigest = 562d1a8fa642dd8bbb4f4801f2d9fc8cf3452be916c0ecd6c8ddc4fc\nR = 4907884b8b7d0eb9a7b24420f69c58e3a17314e101da0280c0ceb130\nS = f7629bed92e5c40f35d7731912fb45a3cee06eab3d409a62997f2282\nInvalid =\n\nCurve = P-224\nX = 8b3f3e31d9c8408a39997455ffe0240fe128a5f1be9b3a33a97b0910\nY = d74ac6ad8de2407887c335bd66f684454dee175a2af713bb334cb3fe\nDigest = b57ffce01c72221c6714e4a38c76746c45a8cc685f37c55a69f6773f\nR = d28ae763c22f50ae9ee9fbe5bab682fd8d820b99ab70677cc46624f7\nS = d9fa54d0300a6ac74936e7a47fbacadcbb4b25ae3a5b550aaf53991f\nInvalid =\n\nCurve = P-224\nX = f4fd02f3d224727e156a2cd7543483f3e35eb65219e32c7923f93ecf\nY = e7aa734828ef326259f98e0e8c3f30b62bd3295c6d1af2c429a087f6\nDigest = 8e70efc206d69d1bd1dce263a29a56030ad5602046bc61848899474d\nR = 9f57e28f69d2ebd96f6d98903156a4e795730e09fb67963771b0a851\nS = 8cfe716488479e04500c8eccdc86fdd54ff00258639f7177169e2030\nInvalid =\n\nCurve = P-224\nX = 0fdb8faf52d8f46229cca1e0f22e869a91bd56eb6dccc547151f9c68\nY = 96c8d1946528bdd2c14c3a0a9c17a088d3f0599752d095ba9de9ffa6\nDigest = db452771046d4b64ba673771b49df905881df9c4b6a1292a11f87515\nR = c53c0ce7d408278552a5fe5854c05641cbe93b1dc18eff1c68af53c1\nS = be7453a12693ce7812fe58746323882bc14eff972480b49431cb10b3\nInvalid =\n\nCurve = P-224\nX = 240431da69703b32ba2ae501d2458b355b66170725806b45996db195\nY = 13beb5198ee00abdcfb2cc5454416d4f7c795e97a14bd93cec3f0a56\nDigest = 3598d7d7b2cd9e482fd3bbebb9ae4549a4b452c81b89f3da6f6f2e85\nR = ad03bdf64e3450407a2a977e1985853d6ea41568c3a394d696de6739\nS = 7b55db9abf2045e2dc7ccfa2e8fb501883c494662d400590c74d100f\nInvalid =\n\nCurve = P-224\nX = 8c80c86f91b1e330f86f5177fdba839e625a27e8531f232efb10a484\nY = a24deab897",
@@ -2700,7 +2700,9 @@
     "be93cff4b96bd0e1f3e59\n\nCurve = P-521\nX = 00984cf3de2bbaf1b37ad4e9121a1294a0128d8a031ddfac7a8c5d7c9db83699de26c50012d42223d902cbd4be7e6fb611f4502ce8444d43d3eb0685aee07349d0c5\nY = 017165e8feaada26cc599ee394dfb5de7e2201004f755ebecb92ffda0a24be55aba88ab9b3c7a575884ffa7b78b631806f54e01ef875c5819fd2d52dd6369d649615\nDigest = a0f94fba76704fb2749e4cd454312e47f7606ece0b2013748096de2ff30626c3c7c7aaa855f33908ed60fc8943101625\nR = 0036c8554602661d9d8f4bfecbb099f01e9e314136e50c6d026de2297bbaf66213ea72fce13b73bb07e6e333523f19d3910983ea5842a1b634b3e3ec8157d270b496\nS = 0129b439d3ba2d66c89c34be2a674013128dccfcef33f5d3844c4465381453c361ce80e1b52b6a611749bc70933655caa56da2c5dd6b04defcd8baeb2d9be06f3caf\nInvalid =\n\nCurve = P-521\nX = 00f976d58a015d3015a14997fa3f59ca8d762a6541861be923d6110c9e742a0a2a77d59a6a9335c67f13a626d9545b27c072349c3d20b80c35b0a9490f3e6c5c1b3c\nY = 00425c22ac0755c58fe3497c1f1a9f537d5e26127d9b031359c2378fd4b13f83691a854444eac3fa346bb5a63bb9567c122945ce99d2aeb0bb1b956ad348f7c9c461\nDigest = dd84b1706091da5e5e27099894e439027b9f45c56e0f31ea0cc528dd587f13a45b9dc87aeb90bb2003e16f56c60b8ad8\nR = 01ca7346a2efe39e03e627ee9480a9b7c925a6677dc80932ffd67ca52b7e46acd2063402545d678d218ac579a64cf1fa4eff4f32f92d3fa4510eea22472dbd3daa72\nS = 00893d86a6502d5973f6c766413e7c7ecbc4583577c58672ef36a76c83755a0ab65af0e0af0ad0f3e6cb8f9ef67669132ce7e996d6122cbbe1dec710a7ba9c9d1ff9\nInvalid =\n\nCurve = P-521\nX = 0066ad5c073425bbbe3a1d97ce6e1a9f2c298392c5afb95c60eee1393f7cd5c9a12c283258b1a53f2ed4abd13ba1287f3a1b051a09cb0f337cb6cf616dffd16aacc2\nY = 009d2b2afc181bd82043b13b8222cd206b9264d73b229c71d9abcf74a478a7f7088bc8c7bb1e54882fee693340a3cf1aa56ccc2fb81d2675b19bba754dae0c2f00c3\nDigest = 89990b6ae2d21961eba4f7c9efd2e910ecf1c7809e1171d219236f2a8a38bceaefb553bbef7083114af5ea891fe44e89\nR = 004e6f08380c43f225169acb0e9f3ff61cdd2e9b713d149f63b5b6a4510d381409648fc1d442fa1bbbce2a8fe1ff7d1de0597f72d7681c79d3a876db6d3ef89ed192\nS = 011745ab4dec3542cbf37d10090d6038bd1ef9cce8216a4069b21e4a08075e7e8502ec97b99d3b18fd314d6ab6826bbbfaa2343ada1abc7c3b551c0b854dc45ffa75\nInvalid =\n\nCurve = P-521\nX = 0068801cdbb1e07f4b72218c52aa24bda872f1b2ab4e0c13b686cb8b10096ff88018e82196769359227192752a1c4c884f08cfa7f947ac428651f528bd41d1034073\nY = 01aeb335cb89ecae3cbc05681e2170870dcf40d486db4011c4d7bd84c58c6b3204161d9ca3516760b0c42466605077c96c0540939c635bf5d7d11e1407b6da30c094\nDigest = ad6637c97ce73a8476c08eab09a8e98f42ae6253517f9abed3b3527942075dac7132122d96978a68324ca4dc11193d47\nR = 01ce67a3509d59f8a0f171b86559f1d84589ff2693ff7d3ad3ae64b0e5af85db2fd99bfd7eda6e8f984a87f16767231cbd9026bed0a9a49d74ea5047201227c98f41\nS = 0032b0e4c043df8e81ff22c9bead36f704c992ec160d6be7764640200e1307002421b5d73154eccde012b463aeefd11138c5b9b705623c2c849736da23c122df06f9\n\nCurve = P-521\nX = 001dd34056fd2ff3009bca2d0bbfa70ea0fb678597d41dc545358263ce2cef9a2efc016622c12099c2a50257609d6a14f3c5ffac8a52661e4a34689a3aebdbe86163\nY = 017926740659acf72f7c7a147a3a320d501efadef8519bb289ebc33e348d6b9efd65fa516048101678548898619d311b8ef2a0d4a6f59f86810e9e6534176a24faf9\nDigest = 1cd3273e0dd337d53131614aaab0b6ffaba8d4c17863a1ddf1e7cf4965bc548628e7230f7331e1ae72b1ed9d1d2f8ae8\nR = 019043db42f44b957784a0e1f09d2e0a0dd548b865947f93b516f249ef1757402544ce5dc402cf8c1f180e9a3be01657258a1dfc14b25ef564805651763d6f609d43\nS = 01e0b45e00bde9c4e8dfe094f9bcd7af5a19b631db850a69bf0b6291fd3df6e26f4c712e3b5d4b7b8572f637874057d5652fa2bcd1977065a695d26a80669a23f0e9\nInvalid =\n\nCurve = P-521\nX = 016e5b4f4ff81c1b1e7956103c5cde951c56b37259fb8bf735b386e4d8b3d44063ef062d6e179f618a506ec8ad9773cfe99044748e2c8ae229a51bca6262aaefe2f5\nY = 000069bfdb9123885d8ce4ce67c63311055aa9a1a5150197717a853d0549bd17d2683e427fc90a0b78af5dc96465ea3f2862cf98e8f3ee2a07089e8837aa8d09d97f\nDigest = 9949e2a22eee8ae6aa35dae08f3c81a11e0e2c546ccc11428133c65c43d36686c40b17bfb6ecdb47f3279c01defec943\nR = 011550cb365daec01901b5a5cabe7930c10d79128c5e510d58b7593c88647eee811e6fa736b26351558cbe7f17d7c882bfd1ffa72ca3bf4bc1cf1c05f31f5e8bc057\nS = 00d6fc97ad14639a5157c92b39cfd1315d7e940a454f1289c8e95c8cbbce8731ad37180554e7a91565d86cffb3f5caf4ef883184d717e03eb776af714a32234e3f5f\nInvalid =\n\nCurve = P-521\nX = 00202896ccf6710cf780bef8908a2783b3c8d5b8356f1546a1b6b909b0d65ffd7999a16112d8d68c837597656e520a56c2f6578e322df6dd794d2c08bc5d8f9f4c37\nY = 00576152d30218c941e83080a502cdfbf9de7ca2c394969e779b76c359ffcb84902ff89e37125dea7dcdea0ba928ce2305c619b1906955e6be5ce40d087c5245eb45\nDigest = 90de70a32a54280bcf6acec4f4d2ff996855de0a224f538e2002106c06b695c8d9d143cfe0c90a1679a2fca7a15bf3ed\nR = 00bc6a7f5d77cb6ebb36a261e80d739f42b67ddc7a6496acc0ba7804d14b4850cf3fe4d8b56cdd8c019ef9f0d33aa26746018fbb4c69f4587b6da1adcf2feee2b438\nS = 00f09c6a94a8550a2781e70b4542096407fc07617f537cd27f1a1ddd15c599d5a9e3fa41da57094456277b44b89d40b26f2cc054fbe657788fa9d71659008d0d698c\n\nCurve = P-521\nX = 006ee95783b768c895e2af569bb84b0b1b00c8b72eec022df255892527987ffecdd81bd8afe267408a8912cce80982bad79c30610571a37d2a0e027e73ad23923b8d\nY = 01ca3f60a37b18bd8b08529da1e39f93d518ae3feead5d00e07150d80d641b20e887c62e8e910ca1c2f64cdcfa678c89b2e3012e3d9b96088ae31dd660dfe6369cb6\nDigest = 8a27d78796a750bf11f75bbed9fa9807633adb4d907125004f69d29b881ec79d14feac2f0e0ed5f113932563eb38c63f\nR = 006823e8f6514e42e79d50a112f0f320ecd53963729038ef0d66d5fb59e1c664fda493027678a02b139fcf290657fffd7a529f4f38ac73542f316e1b0b25b3b88cfd\nS = 01b3bf9e54b0f48bfcc7289d187e831d94d165949db3c660cb63106be1b933e10614e3673bb8078bd8b80ba052c63d566899e618ea31e2a37e0c9c10da111ad11560\nInvalid =\n\nCurve = P-521\nX = 01ba73e2af308df78d4f2a9e552c3b9fd35d35bf20126fdf751d8ad9917cc58d734fb9de27553cd07c02eabc077f16ad4532871a8aeb59bbec82e46ef1581e4abac0\nY = 00cf888c75582fb50bd0de724a9f4834ea127a1eea437b9a05935d1ec06815bace3464c230314b7f796423ba9fa983b2e6d1eb0260a32cf2f163a5ff46a9623ff149\nDigest = c9a34291213a5edc7474aee794f9de901be35159890bb660f9596efaf8ae7b02118457dfc3d8d2649cfd0bf5c7eea0eb\nR = 01df7e724658f1666aee8d5d75609e3f5215228ac32b978ea53434b7d154dd4edf661c688083d0937e43836c3611526c75f6f26b08f7844a95113ea4a6f1ab824a0b\nS = 019d40a7e03bd69ca568f70a066a4a57c0e6ab82dc8c2c8aa52b00c3ee4c327a87eeb7d837b0c4de68e25f7ac7cf6c0d8bbe0393b98dd61ac4961c7f8c70b40082e0\nInvalid =\n\nCurve = P-521\nX = 01419bc65174998ac21026f81e6807d8b42f0477396e7ff8a330e17c1d84bdc9b39b2a310767b46c41711f3f2fe503504350c86bf3d2b39473b64822ee32dec526e4\nY = 0184c968f6ad79bf0da00520e5339751cd9c50e41e7cd21ef37756bd0e36e23a8071e5f0240988b73acb3bb2b6002002e09bc7ef70ffcfc7cf42d6b7c65110f54ae0\nDigest = c1c0b91842d461d466e94b411c673069d3737c898435972eda2f6ba1118ce9db013d57d3970b137071446a1fa2477930\nR = 00d785b38c5283466f796988242aba08398ed2493aaabf959ed0e8b7b915cbb711d7694f94206db74641a518642d43c843ea7f43b8354a956a3695764021cc5d2774\nS = 012c20c6ab988ae911c7cdea0549de2e40e3e68c47cfe58fb777ebc204641bbb44f2c8b6a0196d330ea2ffa1d8cdc1dd9be353f1c657e43f7fe3c094898a569c45b6\nInvalid =\n\nCurve = P-521\nX = 010f3bb1c96a753d278ddf6435e7a79a53bc2855d26d9f8d5c1337b0fd7d70bccf204377a02a1cbe95cb63e21a9e8a3ce8ee7c8d4ade16ff4083dcacbc6c4b2a350e\nY = 01f98a0273c48fa78a91c0f8c1a43f59c7bccb74780fa38b08989d334f2ba0353a3619e6d4a1072e4e052720ed10e4f2c07e12d0c81a062fe912708dc51d4cdba97c\nDigest = 38688d539ee5c3792c29a505d8f8c01ad86efeb2fa3292e49cb921a76eca20dc536ae3feddf2e473dbe798926eb73fa9\nR = 014c4b9e23f51df21b4e02ed7611a8530466d1ed799b50b34b5fcac3bd1d63fa345925122414119cca76d22c167c18ad0fa8e1b47b53ab0f201bd4ca7ea25e011965\nS = 00ce91a050938119f80b5f584a9d9515c998212f6e122780f1607cebdb9b538dceb2d4039ab5e1b13736f4166e73d86c720516f20ad8f24e4b9fadd459c2988534ed\nInvalid =\n\nCurve = P-521\nX = 00819178ace7bf1e6e942fd6ed69193386f6c90cf65b42e9204d34ec96a0ce8fb92552ca57a7ba658422dc8b53bee150170362e6e74bdda24fb458271602aaa9b832\nY = 014af772624921f61b3d1275591ec2d68702fbf348382e9e552a9b6c110eebf6e93f20c8bff287d504fa08ae3628e611fc1262736916fa9edd87db1c78ed2426cab2\nDigest = dda4a591219b9762f682a9c9a626f172b9cb78ce191cf8acacf137ebbd3e28857e768a9e4f2407c990b192f07c5cf5ed\nR = 012c45d6ac0b5dbd9647211f770c3cca4411666aa39b6988a968bab345129237597b6c9b3bd788c5f9f39a38463a8afb159ad72f19e7e33e7f9ce8d67d611c3d9b46\nS = 01684000b3d7381aded85b18576832c4a89b4faeea0515454677e29e3f072097e786fef11f72f229b63defa1c2fd3c07090b34f9147647035854cf2950c12a8b16d8\nInvalid =\n\nCurve = P-521\nX = 011f8e50ed6905b029ce4b1",
     "6c8acb8ed9136b1c5adf6f11bfb5f3dd8bb1e208ca8329a0aff9bf286e3be90e4d61d5147bcaf2293f934862cca6aead51d6e0a083093\nY = 01963e84a2f06a9cb273a424ee5fa1ae5900fef348371cc91c99323f58bbcd8742a4495a4f7ef52677501a4d5d663658c1f6c8f6edef8b7880e6894ff9e52bb617da\nDigest = c55e6d3091b6eb8f48794749ae0c7e9394ca3cb7b083ad65177f8f8db938a76ed6d3c1286a3d51b333c74d1c0f8032b7\nR = 012fc3e0c18c4edbcda4f82b5136c893a6307c3f60affa15d0d99fc0e4a3576b7daefa363b3a362014d14f631c35619f6861bdff9a7b503825bf9f027fcb9a31fd8a\nS = 01a138d6b02fd2a7ba45f7f952b2f329ba6a8e25697379330dddd91d1d6e865d3df1541bc4717d3e09b10a57cf38dcef587ac31b4a8abedef43e4f6cdf6ec3f49eea\nInvalid =\n\nCurve = P-521\nX = 01efc81c1efc7a9bc36ed49a5ef6fa1ba641360fa5c0f96cc1e4a3f4d973c95e86935d979fc2101370777637ab210a56fc4173a50a758725d60e9f925f2066d2bc00\nY = 0108225fc94ab33c74aff785dcc68c45cfc3cbbdfa3481fd2a3f97308be671fb32fc8d268c129d97f140210def188dceecc9d712ac397793dbc39c5cac332671ec54\nDigest = 5fe56235e4684bd7419e321db508565d30cd351086ef67d943aa5b932f93efdce875be295920ce5210b7d3f092f401e6\nR = 00480c48a24e7a7ef832547d107769254fcdb4e7982d0e6abd16822837fd4f3b66d81e1d4a018606881abebd220ed8ca865d7e00499ac9651a98c65502baebf34a98\nS = 00ccd22d1b44a1701c99f662535aea9abff7e27f73628101f42708737db8b07effdc2b0b05d4ef233c5910b6261ae9d9c540115f27d2af766c0494c33d31bd56b3db\nInvalid =\n\nCurve = P-521\nX = 00a15c8040f94235b8b444f7a74ca293ed1b718449911eefbdb74332687850a644395394c690aa98e8064f6eca600fc3f659208c0f8a21a1e7113bed0c6e00e3176e\nY = 004bebea7037b731d175043dec3630b2ee85c680a81256921a89407c14507c10ac043deb5d474602211ad58cb569a8b805686bdac3ef7ff62a4d25b27200706b603d\nDigest = d27a626bc9154bfc85b03724329b8a06454d5dc005997bd565f64a80134c865e73a2e123d2b433927efcbdfa3eafa827\nR = 00c1a70919025aceb29dbabdfc2a43715192cc60fc3d1ceababb40f91e3110b2cdd8f6e9c1bafe7415a26fa4179f8fc261b143ddb094fe61117afb13adae9db8943d\nS = 00197d7f87aea8d6ccd2178614b147b290ec780c8075f8439137803c0e9a589e415d84fa23f5f31d61c1674f87142d4ba4f8473fc92d7715c281dcf3f1ee5c2f1390\n\nCurve = P-521\nX = 012a593f568ca2571e543e00066ecd3a3272a57e1c94fe311e5df96afc1b792e5862720fc730e62052bbf3e118d3a078f0144fc00c9d8baaaa8298ff63981d09d911\nY = 017cea5ae75a74100ee03cdf2468393eef55ddabfe8fd5718e88903eb9fd241e8cbf9c68ae16f4a1db26c6352afcb1894a9812da6d32cb862021c86cd8aa483afc26\nDigest = 7679eaaf0495725fa99c51a2dd0c35c8882b840e1c2340ba793013b1e2567471cba35c0dd6247cc2c2ca14f6556912a5687023fb2f0ee02114393bed4c598742\nR = 01aac7692baf3aa94a97907307010895efc1337cdd686f9ef2fd8404796a74701e55b03ceef41f3e6f50a0eeea11869c4789a3e8ab5b77324961d081e1a3377ccc91\nS = 0009c1e7d93d056b5a97759458d58c49134a45071854b8a6b8272f9fe7e78e1f3d8097e8a6e731f7ab4851eb26d5aa4fdadba6296dc7af835fe3d1b6dba4b031d5f3\nInvalid =\n\nCurve = P-521\nX = 01d6aef44370325a8a5882f4667c21172cdc8fa41d712562883ececff53883ac8ee276124e825088c79d6c9d96323cb7b8c0b7ea44d3f0026e2538f4b62d785bb1af\nY = 0027203959a6e944b91fe6306debe74dc5dde9831fd0ec27e8be2d0b56807d63151b15f6495b8632e919e1e6b015f5ae5f2b6fb8cf75b5f848f00cf4ee457cebed3a\nDigest = b99c410653ce928e365d3613331b5df067020e92f634696279d5cee80f1f4a82f7d976a059e318b36eb25314b56f8765a81070d0944f4c86e8407d9c3e2aa7da\nR = 004417ff74889dde6bb1820b5d13da5c81dcf9b0723ee89bb1ff0d3faa90d497685709f315b2cbe55481dee43ebb6d25b1501ae69494dd69e7bffb72f987d1573b93\nS = 00fd7aa027c665458c7ac11d54d4f32cb4a1e727b499ce27b08d3d647c636cc3222a4f0a6057732249ddc22574d7cb80c3769c3ea9de3d33db3edd8ea90cb3f8dc8a\nInvalid =\n\nCurve = P-521\nX = 0153eb2be05438e5c1effb41b413efc2843b927cbf19f0bc9cc14b693eee26394a0d8880dc946a06656bcd09871544a5f15c7a1fa68e00cdc728c7cfb9c448034867\nY = 0143ae8eecbce8fcf6b16e6159b2970a9ceb32c17c1d878c09317311b7519ed5ece3374e7929f338ddd0ec0522d81f2fa4fa47033ef0c0872dc049bb89233eef9bc1\nDigest = 97ff5a81fc88f7ddd3bc58154ffd2695912fe50ce7c63b62bd798fb673c6aa49f54bc7301fb7bddc6edc51b7e0d0b4dec9f80851fff02a33671ad9a406bbabe5\nR = 00dd633947446d0d51a96a0173c01125858abb2bece670af922a92dedcec067136c1fa92e5fa73d7116ac9c1a42b9cb642e4ac19310b049e48c53011ffc6e7461c36\nS = 00efbdc6a414bb8d663bb5cdb7c586bccfe7589049076f98cee82cdb5d203fddb2e0ffb77954959dfa5ed0de850e42a86f5a63c5a6592e9b9b8bd1b40557b9cd0cc0\n\nCurve = P-521\nX = 01184b27a48e223891cbd1f4a0255747d078f82768157e5adcc8e78355a2ff17d8363dfa39bcdb48e2fae759ea3bd6a8909ce1b2e7c20653915b7cd7b94d8f110349\nY = 003bd6e273ee4278743f1bb71ff7aefe1f2c52954d674c96f268f3985e69727f22adbe31e0dbe01da91e3e6d19baf8efa4dcb4d1cacd06a8efe1b617bd681839e6b9\nDigest = ee21776d7174103b7fb65f03fd5d78744d2706c6726ece81e3943cf90f60fad6d8978af6cae9bc059aee2412ef86d0600694447a10b9d21079b9ca77500634a9\nR = 004c1d88d03878f967133eb56714945d3c89c3200fad08bd2d3b930190246bf8d43e453643c94fdab9c646c5a11271c800d5df25c11927c000263e785251d62acd59\nS = 012e31766af5c605a1a67834702052e7e56bbd9e2381163a9bf16b579912a98bebabb70587da58bec621c1e779a8a21c193dda0785018fd58034f9a6ac3e297e3790\nInvalid =\n\nCurve = P-521\nX = 01d9020b8e6717254eebe619d46dd5a9dda7ba5491a7d1b6820fba888e236fafd71179200437f4d61284fb5a3dfbada66bac3e6909ccbeee03c2b93a8bebe41a73f4\nY = 0048a5f09174fda12704acdd8ed560695dec42864b6300a030768a0be7f09d25f82d7b126125e41417a145641937807ed8d1af7a53f5bc3fc3c57427d755dcce3e25\nDigest = cc4e8efb1e9061500bd2dcc5233c2bfa3d3bd89067c26cfee4fff4a5a7c9c9b15151aec1fa91e78b67cfe3efd966ce65681dd3daf36b887d844033a473be592d\nR = 0092df2dcb457fc7578eaacc98ffd73ade07d764e9553506f3dc958cdb3f65d37665528cb2f5f8bded0db0a57e6fa73bfad1aaf94718379d1655db4f32d4c505a785\nS = 010e0c31479c2b29dc2726fe9f75b397d9e37a17619e96bc631c62e9ece71f05b199804cc803940d43ddee41171dd7787668c7db05049dd5b63e4f63562aa700ca81\nInvalid =\n\nCurve = P-521\nX = 0007067d2cf7b7619b9fcff2c898246ae0950439b8bab92d809624970eda18456cb99953ce1ae45ee5d36ef02fcd5caa4d951de8581f0c21e572caad56d6dce60da3\nY = 01913c59007a309005f226b6a30122828d60b4d0390359e1977f88b5347dacf2056dd362648e8b1d6fc038a3bd3fde6f1140c740efa9075ab8b4a64b334c5cd43f09\nDigest = 996010910456dee59309f1631f30e3dbf7ac2da7d5d7f69223c8a18f491cb18f7e11d0ca09352b715354a071e6d392a8c1dc0751569bdfcf36c158c8b07a5ba6\nR = 012aa4a532c108aa3cfb1753f95ca626bb72bd96a423d727656d4ebdc3f406d6cc6c44d3718f9abae8a0b46be9b57f8fd3a540326b63d0d4a8a93165715920437787\nS = 001badaf38e16efd75915f4806f054d40abd2d11e402039bd48c832f66cbfd145e4dac93357d476b7e608d7b75a017374ae76eee86c505f2cc16eaa19075827ccd60\nInvalid =\n\nCurve = P-521\nX = 00365388d9589c18ae608124b4cf746ff488183a912e07d26b6e867c5defb552a5a0df5a16b6342014dd1b0b6760072bcd60045d6a9a514fc74d16047c2e8765636d\nY = 01a5319b26fd555f2a12e557418f6aa65a3461aeaea5c0c6d8698ceaa5495eed7a7d2fed0b76e77b5be11834f36e413d5288e47231c0eb0e9007d4b042bb7a1b6014\nDigest = f8e150be2f657c8266fadc9bdb04648fc5a51f3c3f7521022aaf58d24165f8af4ad66319d8aa2dab48fe8a2f773c8d0e6c8c4f732e0fdfbae4b91918530c1f91\nR = 01d9ef377063a592cf81e27815a2c20789ff9b60f7f125e618b52d90b35abdd41cd7f437cfad337953ab0314fe8e79a2f2d27fa08597d4b28313358f714a737321fb\nS = 00f01d4f150e0a174674a6a61a58a4ba781406024f6dd1b5252e04807b8a807a4ff8d52883eaa258286e506ef4b04ca890e6f81a79ed9a0cd5ed585094fea0bc5c43\n\nCurve = P-521\nX = 00fd0cac24aeb75ca50c50a72340256b43649050e0fa155f72342877bf49c3d57ac2b51b828385ee6aea94bae38587e63390f5ef4ac5540a9e6fc6f1c1e79b524693\nY = 0107b227bdd307efd7a8d4034f733d150c41601215e76eea2bac62ad2427dff52f75f46da3d5fe31bfaedf071d2a8bb5e3c82bf6c84ecdf89ca233c92d599d376309\nDigest = d0d8c24bc5b6f34bf35b08f25dc2d6ebcd36b565f96bee9c1b47030428f10c3ad2904de19247b29650690c08517404e8ca55f366ab176e5089a4c9c661f90eb2\nR = 01c00196aa5dcbc4c4404fa76504a5eacbc96aa66c3ba531a3a679f3fb675ce58f863e08b0d2bdeae74d96ad93a39a78ed4bb3749e26567d0ca5c48a71079925b617\nS = 00f1188eba4f0943f4003ddad6a54606c13af26014db2eb8e60534fad3dae8f07c021cea0990987f1e02dce03fe53360472c3dee3c305bb3ef4b0b53ea6625bf152a\nInvalid =\n\nCurve = P-521\nX = 0104a96beea09d88ea6789a9925880c8a9ece8d764be931675640c1bf847ac8e7a8b14f408ba6722c2bf6295db9132d6ad2fe287fa6e6855f7c58ed238148a896944\nY = 01b5e8e643fae552261427ea7d521f380adf605579462315c75e9203203ebdc9ee33dd7ba885b6cccccbd2327462988223c4b31485311c935a341ee87ba1ee820ce0\nDigest = e9ea3c8aeae3133be537da09b98c096b9a9eb287a02b3542efd30f0026ea9cb3f242b842b2cedbf02e70b44ff8a0b1bcf6f31956eaf6c0dd9a023bea36440068\nR = 00ba2c57827baae684d2c637590275c782a6db263a5358c8e1a08b5460ca3cf0f5ff8d4119a6b0d55fc68a75c793098e0a5622a0b4e2",
     "fcb0f17943440138d751797b\nS = 01594beb73b2ebb7c573ff07b5c43e722dc05979df0eef53587e9fe06a920f61d2efcc7671e6cb875df4e4d92cd4d37cc3eadcb9b6aee8f2097790ce24d6dcda8706\nInvalid =\n\nCurve = P-521\nX = 010d587aa82a4d8e690672c00e3fd71826d892862d14dc4fbad4935aaab86924dc7ee6f7fd3e2bbe86a8652589448494dab83d363d1d623cbae59f6c2670706a0576\nY = 01a9734c99b6ff21267050738937c30971d0f6fe07e29794748a5017ea1036c975c9a52e6d3739ca0e8d70e784529cc1a7437aac5d75c69121b69020a95356137f1d\nDigest = 8814a9dbef9e6d9b8322bdf8d471b207388bb7bf831d9fba8ad29da52d528d5d5108c01e4459f5ca13e26bf5da3c848195558828d7a00f53abb9fce47ef35091\nR = 0188dcb840dfc573a97117009226d58dbb930ba8ec848931786abc770611f3519c8ba73cceb5b489170805bcf04974672fe66c908ba379aca99fa67fec81a994c2d1\nS = 000b1a185512dc6a65e454ea2bdb8049ef8f012a53ae87b759fb5d9edba51ea32e254e80545a99eb4b7c58af96b7c433535fa3f009cc644b1c97666d88355af9fc19\n\nCurve = P-521\nX = 0182c957a62e2e27aa28acee2e2f7b1ed6aef81c68001d2648da47d2b621e8b8bd18d991cd1e3fb9afb84f639fbed1050584428cd2a1d50f877532ffdefdd4e6f7ba\nY = 005fadeef58cc0d79362b599e94636f9c70e3e5580c085b7ea52a5fd24fe4a892120b8f28ba53ec249c42d6d3b36268b8ca8464e54b72d37327d7504d9b7ce534d95\nDigest = e1838cf6ab5daf5ed28dc1b3365eb03466e01cc30f6fec9756c966cc7b89ef5ddb32754302a33b5aa309c871f98de082a21cf734ba8a368794d89b0cde1cfcf7\nR = 01e3a78e973fef6b6de8a0356401e89f435ae5f49c0173f073c4dbb9c91463e420f5265eade8305f11d30fa8d97e5b4c5ab33975f73385aea81fbdde2f7ddf7fdf16\nS = 00efeca10b5362e05a8f2e3df6661d0d536b32ca1e0a62515df2d94eb314aadb5eb40468483e24b16efe85c503d6c231ef860aabe674b72ed1ddd93853338e5e4e50\nInvalid =\n\nCurve = P-521\nX = 009911b41f9af525c874e05bfdf050331bf830296911bcb18eec16275027d63fa106c8989b07921c7e58b02711b5b5880cc4e6d9174e0d31060548cf643bf7ed4f0c\nY = 0184fc0fac3c2c80c69c1c0293f4e5e22fa08c267b1f36ac5ad6dfdf4da1754f7942f48cb56f56cba05e22b91508fe4db3703066e8f697aca56f974f3fe530c9640c\nDigest = 365868aac67d82cc0510bcfb012f9035f99b5841329344f1b45f0489463cfe22c2f3641f7d6c59a3703aa2804323db8fec4fb3804f521149e5f7d38c9e1e94f2\nR = 017b8a22fd8f73112310867909f234fad6aa82999c28ea5a2e74b4b4bc79b2f89008b4d361ef7e797c7656f7d9317eff3e5a4982799b8cc0db82618bd2aa3959f617\nS = 01edacc6d1c0004b2090d2025d615de1fd53a96e826a3930c7cafaf3c87f34b2583997534cfa127485600a7ae04e6af4a2e98c77fd04507195e520e80014aa982a3c\nInvalid =\n\nCurve = P-521\nX = 006da3b694e3123ef96b3fd2ab964f85a36110590720dc1724a5d50d3050498957211c6a1535032cf1f31240bfab967cc0cf3b442c35a1bfa3e72470df1863d2593a\nY = 017d0a5dc460c85d0365c7bdc2e9300e276b8aa97368af9972744f4422442afc601ecfe7903a33b0354c901c7b61f29d2d3c5610192cd188291c5651754b385b87a8\nDigest = 4e992e9e5403eb9822958f2737b70fa8096474a845a0f37244af744a6009e3b6e6e008faa7192fc01755bb785e03e4e3d2caef03eeadfe32a7fbc7e3bda49f5e\nR = 01f9cb1f4e2e65282a929acd8b685ab34da176f5c73bcb374fd1b09bc995385ce3902d6c5496b02916fd5a28f6f8bb662828a76aa0ad14b01bc24a63b328c7bb949b\nS = 001d6b3a2f34e3b7bf63d06b11ace172ca61ac5a911a4b408d766eb586c9ab820d42f555e546d892643e12a6752465427c213e3839e4f8cb3a7e4fd83642843e8544\nInvalid =\n\nCurve = P-521\nX = 00b7e03f0d623a0998add5360dfb0bfe836fcb0a46b0d6f697ba6b3766bd8698ac8c7af62f50511c6aa5e613f4a99fa28f70b220ba1cddb22482be74c969953ae6e5\nY = 00d4ee40ee4441dc85356760f87ba32e2e7c269a2e53a2e8425d5ff02f5e4fe8d65cefe20e162c3915d2eb9ad1354bd28595a86dbdc94a5d40c5b44b1e3aa3965455\nDigest = 8ebb37c7b60ba4622070391864a70b5e797dc2464151304b1d9614b77f0bcb92fce230f42cf98f9b2612f481c21f70564f5cbfc4e81e48e08ae27b466f717e02\nR = 01fcba4781de6506f7c3f26521f0e036b5225f651e69e115d6784b2176a666edf69d759627468400a73a136f599fb8db4643fcc16bdeeef6384a1875e1c81c36b962\nS = 00a21cfaa7e1ee0eff7efc3d7e936378500283b00687363070974483ad474c58c6b55b77f678d78e7cb44d9745f79394659bdd26b72663608384b5ae9cac1c888d13\nInvalid =\n\nCurve = P-521\nX = 001bb7c623fde41beec7ddfb96f65848c2f52b50b39576bf06de6ccf157b8ec49889528728480928236300447da7171f58c8f0e0ba8fd3e2cf378b88619aa6c1e0bc\nY = 01f8b20a1a7df319bf78c2cee03581a1ffe8ca5107fbfd40760fbd5ef5247e2df1092d5caf504a9ee653ded2995f0cdd841d6af29c9f720770056ebbc128705f68e6\nDigest = c18be2e3f935561d1ad1cacf6ae06e733a463c7e5063cbb0cfaf162a579522786755dff879d2bb0b63d4eea9120a2ed648d601a5cb2dee936dbada679bcc134b\nR = 0000db4c31f316912295c5b9506aabc24b0b2dc2b2358e6b023148889d9200bcf44762e88575e359b4868b2d93ba7bdb24800b09fc22eade0744b9832b71ee784e9c\nS = 018c84437fac7cd82099a2a4230084ac27ec7ea9c92e1c9d9a71290df9b37dc881f9ba59ed331c22dca4b2cbb837cd916e0a78398d2b7aaf8e88f113a942beac48c0\nInvalid =\n\n# The following tests exercise the bit-shifting case of ECDSA digest\n# truncation. The digests are larger than even SHA-512, but P-521 is the only\n# common prime-field curve. (This case typically comes up with curves over\n# GF(2^m).)\n\nCurve = P-521\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac31801580\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\nCurve = P-521\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac318015ff\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\nCurve = P-521\nX = 009ec1a3761fe3958073b9647f34202c5e8ca2428d056facc4f3fedc7077fa87f1d1eb30cc74f6e3ff3d3f82df2641cea1eb3ff1529e8a3866ae2055aacec0bf68c4\nY = 00bed0261b91f664c3ff53e337d8321cb988c3edc03b46754680097e5a8585245d80d0b7045c75a9c5be7f599d3b5eea08d828acb6294ae515a3df57a37f903ef62e\nDigest = 007509d92dc07644ffd324d006742d24a4497cfdb9c4efab7768426b3877d810602a84561f82439421e49533f72f50170222ed6c24ae6c11e50b7aa886ac318015ffffffffffff\nR = 00cef3f4babe6f9875e5db28c27d6a197d607c3641a90f10c2cc2cb302ba658aa151dc76c507488b99f4b3c8bb404fb5c852f959273f412cbdd5e713c5e3f0e67f94\nS = 00097ed9e005416fc944e26bcc3661a09b35c128fcccdc2742739c8a301a338dd77d9d13571612a3b9524a6164b09fe73643bbc31447ee31ef44a490843e4e7db23f\n\n\n# The following tests use digests equal to the order and 2^n - 1, where n is\n# the number of bits in the order. This is to test the truncated digest not\n# being fully reduced.\n\nCurve = P-224\nX = bd283d0c18d90b69d9ee3e0f1e8e62f53822f5593fc94343666495b5\nY = b3177709b8dc4b62928f9dc561c2b4be42c7df52d4e90e7e885b4021\nDigest = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d\nR = 7d0642a2cb98b56ff91837bd23e20bd90b60613b60eabfbc078cfbfa\nS = 0209a75bbd6c2310fa55fe2c0c3ddf35be53fef6e1cccf0537f3e7be\n\nCurve = P-224\nX = 42bafdd82b5bd766a727211e4af8bf46015705b878772b296791cca3\nY = f5db26e760f4b2ec586222d3cecb525fed32a841fa0ae547f5c435db\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nR = 42232b212356d9adbb5e43e96e23c376fa5d21c9ad6a50137d2e3bd2\nS = 020596ef40a9dbea4d6779ff02c9cb853b520093113a968a32309118\n\nCurve = P-256\nX = e57231383637c82c1ac801724cf7e03e67198f467a9beb60ac13cb582d13afa8\nY = 8f190e090155fcf63810b858bc88e259dc49afef8bdef6fd06d93dddb1991aed\nDigest = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551\nR = 05cc6037bb021f4910ea2e489fab2bae6bb6a2769a97f42ba5736994102b7f10\nS = 5db54832ceabf8bccdb8be99b1a49cecff8feee045cb697dec43118e2695b1da\n\nCurve = P-256\nX = 6e0e2897b9a554ee287cdaf43bfbe25ca8404373971575a0e4b61c61aff5a2fe\nY = 23ea7823a411eb1b39f81bbde24c2cd6ac68be2c7eec3a0671c8676131b8905c\nDigest = fffffffffffffffffffffffffffffffffffffffffffffffff",
-    "fffffffffffffff\nR = 16831feeceab2fab1c575e073e944d73ce7e6f3e9b06312088f06159c530ff50\nS = 870cb824692638538b1569c6093fcb693c054e8e3b9a919e3bb26798910f66e9\n\nCurve = P-384\nX = f4a961c19f9cc4ebe4f43081110955f3cede085a08c1415d726e80b2eb774028c5fc96f092ba3ea7d1288dd57fe1db08\nY = 981398eed0895e09b3b582a0616f3024e51cca7b1ecc347dbf0d24a5f6a222b0c31912f8f5e427d4dde5c6c45212bb10\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973\nR = 0b77eaff05bbd922dd80525d2ab301cc119318f5a920a12c71c4b5ff5bb77d25a538983df9bdd5984b0d159daf21f1a2\nS = 73af85ad03a34b6b3993082bf719018d25d1555717b2d2f2535d0601af06a71ad020eff8232d065ab9d7fc4cd0c0ee42\n\nCurve = P-384\nX = 54dd8d7cbf2ccdf1a42f5bbc615a372803b094f6040e3c7b651a61bc6912432c836cf2410ab7d67f543236751d81066f\nY = 2219d6257b1c80bf327c96786f2b5d0b5a9b9bf7eee9c853bf66a3bf09520494cb1f7823e4c566d79a617b7e201ead96\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nR = 9d923e199d98272e44b8fba382bf3c19660ecb4a9aae3513ff6802a73fef510c15c202807c3f9334b0bce7d6c6a80839\nS = 520784e6290d04d9b61993ee5ebc6fa8ff527fb0777c43cdefc7586701e60edb399005a5648ff852de80208232849fbd\n\nCurve = P-521\nX = 00056cc489982829b728978193d047596325a91ee2e2c9110f7da605fd2d1b78424e87d85500f391fe9f54209c42e582ca3284484afc6edfe2acdc69c3591f6c47cf\nY = 010e91be6632da7afd03caedebdb572fd41cb1a7221e9c2d984016bac4693b3d10c5b1d76ba32b89f5fadd157df122be9cd85151977b99176998cfccbd3f9a03ba3f\nDigest = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409\nR = 00bd5e59a9bc97de61588d143990ad7fd5405ac53aa8e6332a085a301138b23beaba126b41549db1167df47362a9de77c73b1bfaa14b31114644b4db8d35179f706a\nS = 000cbb560f68b7240e309301ed4e6dc20d329f7e2098bcae26a07dd364e6177bb408eb5d0b47a3fcf36def98b951af9a55a47d24d95cd66cc11973269694e2f6f8d1\n\nCurve = P-521\nX = 002aca58eeac43152b292f42a6a677d327386337409ba7de17acae1978e097f21e49d47f707c6ed6045c66551c93df9ef9bcc442db804e62fcac9f0574876d6d7fea\nY = 01862ed4f9d235afcc4e6b45e491da363104d4db7b97f12d869c40ab09a3c8c72519a9712ca733ddf046ad039842e8caed2425ecaf42d5171b3e236c11fee8699684\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nR = 00ec0b91fa4386a8acdc0e46dd9c1d1775abbe0da8ead424aa4ace58e284a5be00e2c1ef95b6f4d861615564e1e7305656567f95275ce63b534420eae77ec37492c2\nS = 01e1099fb389db498ab4cf23b4f06a74b9326878ae3c76ea13832e50702b30fe8303093a59cc9a0995f1dfc15e6f7dabca8a2acaf03ec005447d29fb429a252064ec\n",
+    "fffffffffffffff\nR = 16831feeceab2fab1c575e073e944d73ce7e6f3e9b06312088f06159c530ff50\nS = 870cb824692638538b1569c6093fcb693c054e8e3b9a919e3bb26798910f66e9\n\nCurve = P-384\nX = f4a961c19f9cc4ebe4f43081110955f3cede085a08c1415d726e80b2eb774028c5fc96f092ba3ea7d1288dd57fe1db08\nY = 981398eed0895e09b3b582a0616f3024e51cca7b1ecc347dbf0d24a5f6a222b0c31912f8f5e427d4dde5c6c45212bb10\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973\nR = 0b77eaff05bbd922dd80525d2ab301cc119318f5a920a12c71c4b5ff5bb77d25a538983df9bdd5984b0d159daf21f1a2\nS = 73af85ad03a34b6b3993082bf719018d25d1555717b2d2f2535d0601af06a71ad020eff8232d065ab9d7fc4cd0c0ee42\n\nCurve = P-384\nX = 54dd8d7cbf2ccdf1a42f5bbc615a372803b094f6040e3c7b651a61bc6912432c836cf2410ab7d67f543236751d81066f\nY = 2219d6257b1c80bf327c96786f2b5d0b5a9b9bf7eee9c853bf66a3bf09520494cb1f7823e4c566d79a617b7e201ead96\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nR = 9d923e199d98272e44b8fba382bf3c19660ecb4a9aae3513ff6802a73fef510c15c202807c3f9334b0bce7d6c6a80839\nS = 520784e6290d04d9b61993ee5ebc6fa8ff527fb0777c43cdefc7586701e60edb399005a5648ff852de80208232849fbd\n\nCurve = P-521\nX = 00056cc489982829b728978193d047596325a91ee2e2c9110f7da605fd2d1b78424e87d85500f391fe9f54209c42e582ca3284484afc6edfe2acdc69c3591f6c47cf\nY = 010e91be6632da7afd03caedebdb572fd41cb1a7221e9c2d984016bac4693b3d10c5b1d76ba32b89f5fadd157df122be9cd85151977b99176998cfccbd3f9a03ba3f\nDigest = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409\nR = 00bd5e59a9bc97de61588d143990ad7fd5405ac53aa8e6332a085a301138b23beaba126b41549db1167df47362a9de77c73b1bfaa14b31114644b4db8d35179f706a\nS = 000cbb560f68b7240e309301ed4e6dc20d329f7e2098bcae26a07dd364e6177bb408eb5d0b47a3fcf36def98b951af9a55a47d24d95cd66cc11973269694e2f6f8d1\n\nCurve = P-521\nX = 002aca58eeac43152b292f42a6a677d327386337409ba7de17acae1978e097f21e49d47f707c6ed6045c66551c93df9ef9bcc442db804e62fcac9f0574876d6d7fea\nY = 01862ed4f9d235afcc4e6b45e491da363104d4db7b97f12d869c40ab09a3c8c72519a9712ca733ddf046ad039842e8caed2425ecaf42d5171b3e236c11fee8699684\nDigest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nR = 00ec0b91fa4386a8acdc0e46dd9c1d1775abbe0da8ead424aa4ace58e284a5be00e2c1ef95b6f4d861615564e1e7305656567f95275ce63b534420eae77ec37492c2\nS = 01e1099fb389db498ab4cf23b4f06a74b9326878ae3c76ea13832e50702b30fe8303093a59cc9a0995f1dfc15e6f7dabca8a2acaf03ec005447d29fb429a252064ec\n\n\n# The following tests are intended to stress the final comparison in ECDSA.\n# ECDSA verification computes some curve point (x, y), picking the fully-reduced\n# representive of x mod p, and checking that x mod n is r. (n is the order of\n# the group and p defines the underlying prime field.)\n#\n# This makes the computation sensitive to values near n and p, and which of n or\n# p is larger. Additionally, there is an optimization that performs the\n# comparison mod p rather than n and compensates for the difference.\n#\n# These tests were generated by picking a target value of r and x, adjusting\n# both until x corresponded to a point on the curve, and then computing the\n# public key by solving for P in ECDSA's (x, y) = u1*G + u2*P. The digest is the\n# hash of \"hello, world\" with the suitably-sized SHA-2 hash, so the test vectors\n# are suitable for both message- and digest-based APIs.\n#\n# \"x\" in the comments refer to the x-coordinate of the computed point, not that\n# of the public key.\n\n# r = 3, x = 3 is valid.\nCurve = P-224\nX = f43eeb550591547d6a6479726b72be181d4ea26dea5516ae1c0b0ab3\nY = e127deeb94536c67793ac172ba31f3a6f81efbbf2ab3d7868d0cc9f9\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00000000000000000000000000000000000000000000000000000003\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\n\n# r = 3 + n, x = 3 is invalid. r must already be reduced.\nCurve = P-224\nX = f43eeb550591547d6a6479726b72be181d4ea26dea5516ae1c0b0ab3\nY = e127deeb94536c67793ac172ba31f3a6f81efbbf2ab3d7868d0cc9f9\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a40\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\nInvalid =\n\n# r = n-1, x = n-1 is the largest x without a reduction.\nCurve = P-224\nX = 32acb8d348f6ec350822227c4a90048733640317f7833dc9093a78f1\nY = dd45cab24ef90b8d6437f128437ea847036a8912322a6738dccceaa3\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3c\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\n\n# r = n-2, x = n-1 is incorrect.\nCurve = P-224\nX = 32acb8d348f6ec350822227c4a90048733640317f7833dc9093a78f1\nY = dd45cab24ef90b8d6437f128437ea847036a8912322a6738dccceaa3\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3b\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\nInvalid =\n\n# r = 3, x = n+3 is the smallest x with a reduction.\nCurve = P-224\nX = d7afcc97eefcf32becf100cf967588c68f9c149fa18344ac08e245b4\nY = 3b853f6c6d955587d9ac080c8f10bf355f9992a0103a27aa30dac7e8\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00000000000000000000000000000000000000000000000000000003\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\n\n# r = 4, x = n+3 is incorrect.\nCurve = P-224\nX = d7afcc97eefcf32becf100cf967588c68f9c149fa18344ac08e245b4\nY = 3b853f6c6d955587d9ac080c8f10bf355f9992a0103a27aa30dac7e8\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00000000000000000000000000000000000000000000000000000004\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\nInvalid =\n\n# r = p-3-n, x = p-3 is the largest valid x.\nCurve = P-224\nX = cdacee2255448c72d1558eb866b14831acef41ed348bd938cce655be\nY = d0b409693b64f3597468ae5535338052436158a6771c6318b68025de\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0000000000000000000000000000e95c1f470fc1ec22d6baa3a3d5c1\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\n\n# r = p-n+3, x = 3 is incorrect. r is too large to compare r+n with x.\nCurve = P-224\nX = ef9169ef146a19c9a7220c6f25f597e7345e25fa1267712b9a20e30d\nY = 454b19373a67ad81ca37ba8de9a96e881896df7160ba740f4c7373b9\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0000000000000000000000000000e95c1f470fc1ec22d6baa3a3d5c7\nS = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a\nInvalid =\n\n# r = 5, x = 5 is valid.\nCurve = P-256\nX = 264d796a0dab9b376d34eea6fe297dde1c7b73e53944bc96c8f1e8a6850bb6c9\nY = cf5308020eed460c649ddae61d4ef8bb79958113f106befaf4f18876d12a5e64\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0000000000000000000000000000000000000000000000000000000000000005\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\n\n# r = 5 + n, x = 5 is invalid. r must already be reduced.\nCurve = P-256\nX = 264d796a0dab9b376d34eea6fe297dde1c7b73e53944bc96c8f1e8a6850bb6c9\nY = cf5308020eed460c649ddae61d4ef8bb79958113f106befaf4f18876d12a5e64\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632556\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\nInvalid =\n\n# r = n-2, x = n-2 is the largest x without a reduction.\nCurve = P-256\nX = 50a50c01132bf79e42b31fb278f7317b29515e9e1c973a41266b69048826fb8e\nY = aac53e7df37b5eb25ce4ddb705fc7135c6b1e00a7f56e30744f62f258afa5537\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\n\n# r = n-3, x = n-2 is incorrect.\nCurve = P-256\nX = 50a50c01132bf79e42b31fb278f7317b29515e9e1c973a41266b69048826fb8e\nY = aac53e7df37b5eb25ce4ddb705fc7135c6b1e00a7f56e30744f62f258afa5537\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = ",
+    "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\nInvalid =\n\n# r = 3, x = n+3 is the smallest x with a reduction.\nCurve = P-256\nX = ce24c99032d52ac6ead23c0ae3ec68ef41e51a281fd457808c83136d7dcce90e\nY = 8f7a154b551e9f39c59279357aa491b2a62bdebc2bb78613883fc72936c057e0\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0000000000000000000000000000000000000000000000000000000000000003\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\n\n# r = 4, x = n+3 is incorrect.\nCurve = P-256\nX = ce24c99032d52ac6ead23c0ae3ec68ef41e51a281fd457808c83136d7dcce90e\nY = 8f7a154b551e9f39c59279357aa491b2a62bdebc2bb78613883fc72936c057e0\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0000000000000000000000000000000000000000000000000000000000000004\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\nInvalid =\n\n# r = p-3-n, x = p-3 is the largest valid x.\nCurve = P-256\nX = 768a0d300a595005a520130e50927d403395c8e1e40be997b48fc048410f7cdb\nY = 16f217d8e1c02bd887e5de388a17783b182e61b5d534152dc2c4be8d75fdd706\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 000000000000000000000000000000004319055358e8617b0c46353d039cdaab\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\n\n# r = p-n+5, x = 5 is incorrect. r is too large to compare r+n with x.\nCurve = P-256\nX = 0ec505bc19b14a43e05678cccf07a443d3e871a2e19b68a4da91859a0650f324\nY = 77300e4f64e9982d94dff5d294428bb37cc9be66117cae9c389d2d495f68b987\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 000000000000000000000000000000004319055358e8617b0c46353d039cdab3\nS = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e\nInvalid =\n\n# r = 2, x = 2 is valid.\nCurve = P-384\nX = 016d2db67561bc126ad6c344d6eeb2713a9e2892c649af0f015c6b7617f160c8a3b3a88add669d7155025073c5ac5b4f\nY = 43bf2ed0088af08645c80aa0a24a567a94ba2d794e9689d3ad4b185bc5d2dd008333e2dd2ebb5069a9b32251a3cac71e\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\n\n# r = 2 + n, x = 2 is invalid. r must already be reduced.\nCurve = P-384\nX = 016d2db67561bc126ad6c344d6eeb2713a9e2892c649af0f015c6b7617f160c8a3b3a88add669d7155025073c5ac5b4f\nY = 43bf2ed0088af08645c80aa0a24a567a94ba2d794e9689d3ad4b185bc5d2dd008333e2dd2ebb5069a9b32251a3cac71e\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52975\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\nInvalid =\n\n# r = n-1, x = n-1 is the largest x without a reduction.\nCurve = P-384\nX = b5b375264c09acf145ca91d12ab10a096092a41ec43f4d718e129ea1c12b2dea62c7785efc52f46f009fb1dba133e811\nY = bc0b2af172b4b3068d032a798080e76f4d56f72069519e3c19a43682a41794e52cb3ca139348d6bbc923e6a4f7945cb1\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52972\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\n\n# r = n-2, x = n-1 is incorrect.\nCurve = P-384\nX = b5b375264c09acf145ca91d12ab10a096092a41ec43f4d718e129ea1c12b2dea62c7785efc52f46f009fb1dba133e811\nY = bc0b2af172b4b3068d032a798080e76f4d56f72069519e3c19a43682a41794e52cb3ca139348d6bbc923e6a4f7945cb1\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52971\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\nInvalid =\n\n# r = 2, x = n+2 is the smallest x with a reduction.\nCurve = P-384\nX = 01b54a697305092bac2939fb906d7471b411c4eba8654169166a5da3810e1fc96795df921f7abbf519be4a027435176c\nY = a19012a3518773d508106d4153adee43c3c384fa62ce36a4addea08f593ec9c76b09a6b9c69d29bd7d47eb48e167dd2f\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\n\n# r = 3, x = n+2 is incorrect.\nCurve = P-384\nX = 01b54a697305092bac2939fb906d7471b411c4eba8654169166a5da3810e1fc96795df921f7abbf519be4a027435176c\nY = a19012a3518773d508106d4153adee43c3c384fa62ce36a4addea08f593ec9c76b09a6b9c69d29bd7d47eb48e167dd2f\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\nInvalid =\n\n# r = p-1-n, x = p-1 is the largest valid x.\nCurve = P-384\nX = c4fd8e68006b83f7b7b20b731ae405813aa05f6e57374589b36ae1cecd1d49cae1418c22f398188bcf4ef02e89fe7394\nY = dd1164b3707f59e05129fa228b8448031db159985f035d93470dc42b3ab4129f0760c46cf201d42e73a7e33ba7402ea6\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = 000000000000000000000000000000000000000000000000389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68b\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\n\n# r = p-n+2, x = 2 is incorrect. r is too large to compare r+n with x.\nCurve = P-384\nX = 4e5e4f1a6e97059a6cf2f4e8129e5c7c64cb84f9994a41ff5bf30b29c1bf5ba6898627c91a23c73e05cd1a43c8f908c0\nY = 06a0aed7f1e63a728f87dbd5360a67571a076ab0b4cde81b10d499959814ddb3a8c7854b0bbfa87cc272f90bca2a2254\nDigest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e\nR = 000000000000000000000000000000000000000000000000389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e\nS = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970\nInvalid =\n\n# r = 1, x = 1 is valid.\nCurve = P-521\nX = 00f07e0b593332d09ec4fd0bae93f648a3da04dd224faae3f64cc490ec8fce3a6fe53d1b2c9e326be076cafb921b7e3f8b2288db491819522d65472870668c3808c9\nY = 018e42509aca542a8de421589c38ba653e8cfd69322336217042a9dc0f67f6d7ae2cd4e385f480ffaf8981f715c7ca3765d9867dfd5a02947b0895f82eaf8b257e88\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\n\n# r = 1 + n, x = 1 is invalid. r must already be reduced.\nCurve = P-521\nX = 00f07e0b593332d09ec4fd0bae93f648a3da04dd224faae3f64cc490ec8fce3a6fe53d1b2c9e326be076cafb921b7e3f8b2288db491819522d65472870668c3808c9\nY = 018e42509aca542a8de421589c38ba653e8cfd69322336217042a9dc0f67f6d7ae2cd4e385f480ffaf8981f715c7ca3765d9867dfd5a02947b0895f82eaf8b257e88\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138640a\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\nInvalid =\n\n# r = n-2, x = n-2 is the largest x without a reduction.\nCurve = P-521\nX = 002a61afb982e49f030dd4e6ba0e495703abe0442b1283ee693fffc1b558f49f0a4cb4f138ea0604e667958495b86c61f358dce7e7f170da47372be3e4168408a260\nY = 01baa19e8929fc8e7208e854e706a3d7f21479d1f6922a6",
+    "5ae3490fd5f52ae6580513b1fdd5bee927d002a9608abbb925b6727bdc110a3145fc8622d1fa8154c82d8\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386407\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\n\n# r = n-3, x = n-2 is incorrect.\nCurve = P-521\nX = 002a61afb982e49f030dd4e6ba0e495703abe0442b1283ee693fffc1b558f49f0a4cb4f138ea0604e667958495b86c61f358dce7e7f170da47372be3e4168408a260\nY = 01baa19e8929fc8e7208e854e706a3d7f21479d1f6922a65ae3490fd5f52ae6580513b1fdd5bee927d002a9608abbb925b6727bdc110a3145fc8622d1fa8154c82d8\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\nInvalid =\n\n# r = 1, x = n+1 is the smallest x with a reduction.\nCurve = P-521\nX = 0049bbb2d3267a6eab2c59fac5b138b9e9c383db6637fcfe5d9f430e4c4c2ba0332340975448bd86c92a55c1a8288adf7f774096022419aa8c497499dafee7b93257\nY = 00bb52fd444ec497ce228135f2498d40fb84eb6f674df1245d3aaac3c75b55ff5fff8e90b6f0189a3132cb9fd8d6e74fda5866fe2b9fc7484c628fde97e0b00f2b67\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\n\n# r = 2, x = n+1 is incorrect.\nCurve = P-521\nX = 0049bbb2d3267a6eab2c59fac5b138b9e9c383db6637fcfe5d9f430e4c4c2ba0332340975448bd86c92a55c1a8288adf7f774096022419aa8c497499dafee7b93257\nY = 00bb52fd444ec497ce228135f2498d40fb84eb6f674df1245d3aaac3c75b55ff5fff8e90b6f0189a3132cb9fd8d6e74fda5866fe2b9fc7484c628fde97e0b00f2b67\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\nInvalid =\n\n# r = p-1-n, x = p-1 is the largest valid x.\nCurve = P-521\nX = 00f651d53d45bf6fd55a5f184e580d11259bc65200387dbc1bf7fb867d2d12a207d2962204ccf38e9d37d23ed95bd01ec576c457127766ecb8ad00342a476ea82078\nY = 0196caedf64fbaa9a12c16836e0564e36f733957375706edb5f32911991a994c2d6a1ea5db2ee764835a9d6aff379e195f722b48e8d2b60fc50de2a5160c77c3f06c\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 00000000000000000000000000000000000000000000000000000000000000000005ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf5\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\n\n# r = p-n+1, x = 1 is incorrect. r is too large to compare r+n with x.\nCurve = P-521\nX = 009eeb7f956230c3744ca5b683f413009363107aad18a027fa7af6ac07a699911e94143d3ef00c0062d4187c2ea74dc9322c05431a6b7fed51ee71b047ce3a0e967c\nY = 007d2c089a6720f7c7886ce8aa6aeb9b821adde0eb025ef63c62d37c32b2d6823c857ce7743b8181c35c8f34e6aeb4487dd693e01d69dfe883c07c25ebe89bdc4d56\nDigest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9\nR = 00000000000000000000000000000000000000000000000000000000000000000005ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf7\nS = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406\nInvalid =\n\n# Although we do not support secp160r1, all our built-in curves have p > n,\n# while n > p is reachable from custom curve logic. Moreover, p and n have\n# different word widths on 32-bit machines. We include some test vectors to\n# cover these cases.\n#\n# When n > p, the reduction mod n never occurs, but an optimized implementation,\n# working mod p, may incorrectly accept, e.g., r = p+4 instead of r = 4.\n\n# r = 4, x = 4 is valid.\nCurve = secp160r1\nX = 39891bd61138e775cd012518ff00f59ae01c4733\nY = 25026b77b1c44affb1592dcf711b4290e9404c9f\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 000000000000000000000000000000000000000004\nS = 0100000000000000000001f4c8f927aed3ca752254\n\n# r = 4 + n, x = 4 is invalid. r must already be reduced.\nCurve = secp160r1\nX = 39891bd61138e775cd012518ff00f59ae01c4733\nY = 25026b77b1c44affb1592dcf711b4290e9404c9f\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 0100000000000000000001f4c8f927aed3ca75225b\nS = 0100000000000000000001f4c8f927aed3ca752254\nInvalid =\n\n# r = p-3, x = p-3 are the largest valid values of x and r.\nCurve = secp160r1\nX = d88d902a0d8d942333c7b846a933d4794fcb5807\nY = d24c4f405689b86cd5c61fe104e6365d254d5222\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00ffffffffffffffffffffffffffffffff7ffffffc\nS = 0100000000000000000001f4c8f927aed3ca752254\n\n# r = p-4, x = p-3 is incorrect.\nCurve = secp160r1\nX = d88d902a0d8d942333c7b846a933d4794fcb5807\nY = d24c4f405689b86cd5c61fe104e6365d254d5222\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00ffffffffffffffffffffffffffffffff7ffffffb\nS = 0100000000000000000001f4c8f927aed3ca752254\nInvalid =\n\n# r = p+4, x = 4 is incorrect. They should be compared modulo the order, not p,\n# so r >= p is never valid.\nCurve = secp160r1\nX = d8add22064027856c162243ab09ea96642975297\nY = 8822a506712385ab3ebe5c61737c3bbb722b06b9\nDigest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b\nR = 00ffffffffffffffffffffffffffffffff80000003\nS = 0100000000000000000001f4c8f927aed3ca752254\nInvalid =\n",
 };
 static const size_t kLen44 = 9174;
 
diff --git a/eureka.mk b/eureka.mk
index 7b9f44d..41cd787 100644
--- a/eureka.mk
+++ b/eureka.mk
@@ -345,6 +345,7 @@
   linux-x86_64/crypto/fipsmodule/ghash-x86_64.S\
   linux-x86_64/crypto/fipsmodule/md5-x86_64.S\
   linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S\
+  linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S\
   linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S\
   linux-x86_64/crypto/fipsmodule/rsaz-avx2.S\
   linux-x86_64/crypto/fipsmodule/sha1-x86_64.S\
diff --git a/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S b/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S
new file mode 100644
index 0000000..64c62a1
--- /dev/null
+++ b/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S
@@ -0,0 +1,328 @@
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer) && !defined(OPENSSL_NO_ASM)
+#define OPENSSL_NO_ASM
+#endif
+#endif
+
+#if defined(__x86_64__) && !defined(OPENSSL_NO_ASM)
+#if defined(BORINGSSL_PREFIX)
+#include <boringssl_prefix_symbols_asm.h>
+#endif
+.text	
+
+.type	beeu_mod_inverse_vartime,@function
+.hidden	beeu_mod_inverse_vartime
+.globl	beeu_mod_inverse_vartime
+.hidden beeu_mod_inverse_vartime
+.align	32
+beeu_mod_inverse_vartime:
+.cfi_startproc	
+	pushq	%rbp
+.cfi_adjust_cfa_offset	8
+.cfi_offset	rbp,-16
+	movq	%rsp,%rbp
+.cfi_def_cfa_register	rbp
+
+	pushq	%r12
+.cfi_offset	r12,-24
+	pushq	%r13
+.cfi_offset	r13,-32
+	pushq	%r14
+.cfi_offset	r14,-40
+	pushq	%r15
+.cfi_offset	r15,-48
+	pushq	%rbx
+.cfi_offset	rbx,-56
+	pushq	%rsi
+.cfi_offset	rsi,-64
+
+	subq	$80,%rsp
+	movq	%rdi,0(%rsp)
+
+
+	movq	$1,%r8
+	xorq	%r9,%r9
+	xorq	%r10,%r10
+	xorq	%r11,%r11
+	xorq	%rdi,%rdi
+
+	xorq	%r12,%r12
+	xorq	%r13,%r13
+	xorq	%r14,%r14
+	xorq	%r15,%r15
+	xorq	%rbp,%rbp
+
+
+	vmovdqu	0(%rsi),%xmm0
+	vmovdqu	16(%rsi),%xmm1
+	vmovdqu	%xmm0,48(%rsp)
+	vmovdqu	%xmm1,64(%rsp)
+
+	vmovdqu	0(%rdx),%xmm0
+	vmovdqu	16(%rdx),%xmm1
+	vmovdqu	%xmm0,16(%rsp)
+	vmovdqu	%xmm1,32(%rsp)
+
+.Lbeeu_loop:
+	xorq	%rbx,%rbx
+	orq	48(%rsp),%rbx
+	orq	56(%rsp),%rbx
+	orq	64(%rsp),%rbx
+	orq	72(%rsp),%rbx
+	jz	.Lbeeu_loop_end
+
+
+
+
+
+
+
+
+
+
+	movq	$1,%rcx
+
+
+.Lbeeu_shift_loop_XB:
+	movq	%rcx,%rbx
+	andq	48(%rsp),%rbx
+	jnz	.Lbeeu_shift_loop_end_XB
+
+
+	movq	$1,%rbx
+	andq	%r8,%rbx
+	jz	.Lshift1_0
+	addq	0(%rdx),%r8
+	adcq	8(%rdx),%r9
+	adcq	16(%rdx),%r10
+	adcq	24(%rdx),%r11
+	adcq	$0,%rdi
+
+.Lshift1_0:
+	shrdq	$1,%r9,%r8
+	shrdq	$1,%r10,%r9
+	shrdq	$1,%r11,%r10
+	shrdq	$1,%rdi,%r11
+	shrq	$1,%rdi
+
+	shlq	$1,%rcx
+
+
+
+
+
+	cmpq	$0x8000000,%rcx
+	jne	.Lbeeu_shift_loop_XB
+
+.Lbeeu_shift_loop_end_XB:
+	bsfq	%rcx,%rcx
+	testq	%rcx,%rcx
+	jz	.Lbeeu_no_shift_XB
+
+
+
+	movq	8+48(%rsp),%rax
+	movq	16+48(%rsp),%rbx
+	movq	24+48(%rsp),%rsi
+
+	shrdq	%cl,%rax,0+48(%rsp)
+	shrdq	%cl,%rbx,8+48(%rsp)
+	shrdq	%cl,%rsi,16+48(%rsp)
+
+	shrq	%cl,%rsi
+	movq	%rsi,24+48(%rsp)
+
+
+.Lbeeu_no_shift_XB:
+
+	movq	$1,%rcx
+
+
+.Lbeeu_shift_loop_YA:
+	movq	%rcx,%rbx
+	andq	16(%rsp),%rbx
+	jnz	.Lbeeu_shift_loop_end_YA
+
+
+	movq	$1,%rbx
+	andq	%r12,%rbx
+	jz	.Lshift1_1
+	addq	0(%rdx),%r12
+	adcq	8(%rdx),%r13
+	adcq	16(%rdx),%r14
+	adcq	24(%rdx),%r15
+	adcq	$0,%rbp
+
+.Lshift1_1:
+	shrdq	$1,%r13,%r12
+	shrdq	$1,%r14,%r13
+	shrdq	$1,%r15,%r14
+	shrdq	$1,%rbp,%r15
+	shrq	$1,%rbp
+
+	shlq	$1,%rcx
+
+
+
+
+
+	cmpq	$0x8000000,%rcx
+	jne	.Lbeeu_shift_loop_YA
+
+.Lbeeu_shift_loop_end_YA:
+	bsfq	%rcx,%rcx
+	testq	%rcx,%rcx
+	jz	.Lbeeu_no_shift_YA
+
+
+
+	movq	8+16(%rsp),%rax
+	movq	16+16(%rsp),%rbx
+	movq	24+16(%rsp),%rsi
+
+	shrdq	%cl,%rax,0+16(%rsp)
+	shrdq	%cl,%rbx,8+16(%rsp)
+	shrdq	%cl,%rsi,16+16(%rsp)
+
+	shrq	%cl,%rsi
+	movq	%rsi,24+16(%rsp)
+
+
+.Lbeeu_no_shift_YA:
+
+	movq	48(%rsp),%rax
+	movq	56(%rsp),%rbx
+	movq	64(%rsp),%rsi
+	movq	72(%rsp),%rcx
+	subq	16(%rsp),%rax
+	sbbq	24(%rsp),%rbx
+	sbbq	32(%rsp),%rsi
+	sbbq	40(%rsp),%rcx
+	jnc	.Lbeeu_B_bigger_than_A
+
+
+	movq	16(%rsp),%rax
+	movq	24(%rsp),%rbx
+	movq	32(%rsp),%rsi
+	movq	40(%rsp),%rcx
+	subq	48(%rsp),%rax
+	sbbq	56(%rsp),%rbx
+	sbbq	64(%rsp),%rsi
+	sbbq	72(%rsp),%rcx
+	movq	%rax,16(%rsp)
+	movq	%rbx,24(%rsp)
+	movq	%rsi,32(%rsp)
+	movq	%rcx,40(%rsp)
+
+
+	addq	%r8,%r12
+	adcq	%r9,%r13
+	adcq	%r10,%r14
+	adcq	%r11,%r15
+	adcq	%rdi,%rbp
+	jmp	.Lbeeu_loop
+
+.Lbeeu_B_bigger_than_A:
+
+	movq	%rax,48(%rsp)
+	movq	%rbx,56(%rsp)
+	movq	%rsi,64(%rsp)
+	movq	%rcx,72(%rsp)
+
+
+	addq	%r12,%r8
+	adcq	%r13,%r9
+	adcq	%r14,%r10
+	adcq	%r15,%r11
+	adcq	%rbp,%rdi
+
+	jmp	.Lbeeu_loop
+
+.Lbeeu_loop_end:
+
+
+
+
+	movq	16(%rsp),%rbx
+	subq	$1,%rbx
+	orq	24(%rsp),%rbx
+	orq	32(%rsp),%rbx
+	orq	40(%rsp),%rbx
+
+	jnz	.Lbeeu_err
+
+
+
+
+	movq	0(%rdx),%r8
+	movq	8(%rdx),%r9
+	movq	16(%rdx),%r10
+	movq	24(%rdx),%r11
+	xorq	%rdi,%rdi
+
+.Lbeeu_reduction_loop:
+	movq	%r12,16(%rsp)
+	movq	%r13,24(%rsp)
+	movq	%r14,32(%rsp)
+	movq	%r15,40(%rsp)
+	movq	%rbp,48(%rsp)
+
+
+	subq	%r8,%r12
+	sbbq	%r9,%r13
+	sbbq	%r10,%r14
+	sbbq	%r11,%r15
+	sbbq	$0,%rbp
+
+
+	cmovcq	16(%rsp),%r12
+	cmovcq	24(%rsp),%r13
+	cmovcq	32(%rsp),%r14
+	cmovcq	40(%rsp),%r15
+	jnc	.Lbeeu_reduction_loop
+
+
+	subq	%r12,%r8
+	sbbq	%r13,%r9
+	sbbq	%r14,%r10
+	sbbq	%r15,%r11
+
+.Lbeeu_save:
+
+	movq	0(%rsp),%rdi
+
+	movq	%r8,0(%rdi)
+	movq	%r9,8(%rdi)
+	movq	%r10,16(%rdi)
+	movq	%r11,24(%rdi)
+
+
+	movq	$1,%rax
+	jmp	.Lbeeu_finish
+
+.Lbeeu_err:
+
+	xorq	%rax,%rax
+
+.Lbeeu_finish:
+	addq	$80,%rsp
+	popq	%rsi
+.cfi_restore	rsi
+	popq	%rbx
+.cfi_restore	rbx
+	popq	%r15
+.cfi_restore	r15
+	popq	%r14
+.cfi_restore	r14
+	popq	%r13
+.cfi_restore	r13
+	popq	%r12
+.cfi_restore	r12
+	popq	%rbp
+.cfi_restore	rbp
+.cfi_def_cfa	rsp, 8
+.cfi_endproc	
+	.byte	0xf3,0xc3
+
+.size	beeu_mod_inverse_vartime, .-beeu_mod_inverse_vartime
+#endif
diff --git a/mac-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S b/mac-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S
new file mode 100644
index 0000000..47fdb38
--- /dev/null
+++ b/mac-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S
@@ -0,0 +1,327 @@
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer) && !defined(OPENSSL_NO_ASM)
+#define OPENSSL_NO_ASM
+#endif
+#endif
+
+#if defined(__x86_64__) && !defined(OPENSSL_NO_ASM)
+#if defined(BORINGSSL_PREFIX)
+#include <boringssl_prefix_symbols_asm.h>
+#endif
+.text	
+
+
+.private_extern	_beeu_mod_inverse_vartime
+.globl	_beeu_mod_inverse_vartime
+.private_extern _beeu_mod_inverse_vartime
+.p2align	5
+_beeu_mod_inverse_vartime:
+
+	pushq	%rbp
+
+	movq	%rsp,%rbp
+
+
+	pushq	%r12
+
+	pushq	%r13
+
+	pushq	%r14
+
+	pushq	%r15
+
+	pushq	%rbx
+
+	pushq	%rsi
+
+
+	subq	$80,%rsp
+	movq	%rdi,0(%rsp)
+
+
+	movq	$1,%r8
+	xorq	%r9,%r9
+	xorq	%r10,%r10
+	xorq	%r11,%r11
+	xorq	%rdi,%rdi
+
+	xorq	%r12,%r12
+	xorq	%r13,%r13
+	xorq	%r14,%r14
+	xorq	%r15,%r15
+	xorq	%rbp,%rbp
+
+
+	vmovdqu	0(%rsi),%xmm0
+	vmovdqu	16(%rsi),%xmm1
+	vmovdqu	%xmm0,48(%rsp)
+	vmovdqu	%xmm1,64(%rsp)
+
+	vmovdqu	0(%rdx),%xmm0
+	vmovdqu	16(%rdx),%xmm1
+	vmovdqu	%xmm0,16(%rsp)
+	vmovdqu	%xmm1,32(%rsp)
+
+L$beeu_loop:
+	xorq	%rbx,%rbx
+	orq	48(%rsp),%rbx
+	orq	56(%rsp),%rbx
+	orq	64(%rsp),%rbx
+	orq	72(%rsp),%rbx
+	jz	L$beeu_loop_end
+
+
+
+
+
+
+
+
+
+
+	movq	$1,%rcx
+
+
+L$beeu_shift_loop_XB:
+	movq	%rcx,%rbx
+	andq	48(%rsp),%rbx
+	jnz	L$beeu_shift_loop_end_XB
+
+
+	movq	$1,%rbx
+	andq	%r8,%rbx
+	jz	L$shift1_0
+	addq	0(%rdx),%r8
+	adcq	8(%rdx),%r9
+	adcq	16(%rdx),%r10
+	adcq	24(%rdx),%r11
+	adcq	$0,%rdi
+
+L$shift1_0:
+	shrdq	$1,%r9,%r8
+	shrdq	$1,%r10,%r9
+	shrdq	$1,%r11,%r10
+	shrdq	$1,%rdi,%r11
+	shrq	$1,%rdi
+
+	shlq	$1,%rcx
+
+
+
+
+
+	cmpq	$0x8000000,%rcx
+	jne	L$beeu_shift_loop_XB
+
+L$beeu_shift_loop_end_XB:
+	bsfq	%rcx,%rcx
+	testq	%rcx,%rcx
+	jz	L$beeu_no_shift_XB
+
+
+
+	movq	8+48(%rsp),%rax
+	movq	16+48(%rsp),%rbx
+	movq	24+48(%rsp),%rsi
+
+	shrdq	%cl,%rax,0+48(%rsp)
+	shrdq	%cl,%rbx,8+48(%rsp)
+	shrdq	%cl,%rsi,16+48(%rsp)
+
+	shrq	%cl,%rsi
+	movq	%rsi,24+48(%rsp)
+
+
+L$beeu_no_shift_XB:
+
+	movq	$1,%rcx
+
+
+L$beeu_shift_loop_YA:
+	movq	%rcx,%rbx
+	andq	16(%rsp),%rbx
+	jnz	L$beeu_shift_loop_end_YA
+
+
+	movq	$1,%rbx
+	andq	%r12,%rbx
+	jz	L$shift1_1
+	addq	0(%rdx),%r12
+	adcq	8(%rdx),%r13
+	adcq	16(%rdx),%r14
+	adcq	24(%rdx),%r15
+	adcq	$0,%rbp
+
+L$shift1_1:
+	shrdq	$1,%r13,%r12
+	shrdq	$1,%r14,%r13
+	shrdq	$1,%r15,%r14
+	shrdq	$1,%rbp,%r15
+	shrq	$1,%rbp
+
+	shlq	$1,%rcx
+
+
+
+
+
+	cmpq	$0x8000000,%rcx
+	jne	L$beeu_shift_loop_YA
+
+L$beeu_shift_loop_end_YA:
+	bsfq	%rcx,%rcx
+	testq	%rcx,%rcx
+	jz	L$beeu_no_shift_YA
+
+
+
+	movq	8+16(%rsp),%rax
+	movq	16+16(%rsp),%rbx
+	movq	24+16(%rsp),%rsi
+
+	shrdq	%cl,%rax,0+16(%rsp)
+	shrdq	%cl,%rbx,8+16(%rsp)
+	shrdq	%cl,%rsi,16+16(%rsp)
+
+	shrq	%cl,%rsi
+	movq	%rsi,24+16(%rsp)
+
+
+L$beeu_no_shift_YA:
+
+	movq	48(%rsp),%rax
+	movq	56(%rsp),%rbx
+	movq	64(%rsp),%rsi
+	movq	72(%rsp),%rcx
+	subq	16(%rsp),%rax
+	sbbq	24(%rsp),%rbx
+	sbbq	32(%rsp),%rsi
+	sbbq	40(%rsp),%rcx
+	jnc	L$beeu_B_bigger_than_A
+
+
+	movq	16(%rsp),%rax
+	movq	24(%rsp),%rbx
+	movq	32(%rsp),%rsi
+	movq	40(%rsp),%rcx
+	subq	48(%rsp),%rax
+	sbbq	56(%rsp),%rbx
+	sbbq	64(%rsp),%rsi
+	sbbq	72(%rsp),%rcx
+	movq	%rax,16(%rsp)
+	movq	%rbx,24(%rsp)
+	movq	%rsi,32(%rsp)
+	movq	%rcx,40(%rsp)
+
+
+	addq	%r8,%r12
+	adcq	%r9,%r13
+	adcq	%r10,%r14
+	adcq	%r11,%r15
+	adcq	%rdi,%rbp
+	jmp	L$beeu_loop
+
+L$beeu_B_bigger_than_A:
+
+	movq	%rax,48(%rsp)
+	movq	%rbx,56(%rsp)
+	movq	%rsi,64(%rsp)
+	movq	%rcx,72(%rsp)
+
+
+	addq	%r12,%r8
+	adcq	%r13,%r9
+	adcq	%r14,%r10
+	adcq	%r15,%r11
+	adcq	%rbp,%rdi
+
+	jmp	L$beeu_loop
+
+L$beeu_loop_end:
+
+
+
+
+	movq	16(%rsp),%rbx
+	subq	$1,%rbx
+	orq	24(%rsp),%rbx
+	orq	32(%rsp),%rbx
+	orq	40(%rsp),%rbx
+
+	jnz	L$beeu_err
+
+
+
+
+	movq	0(%rdx),%r8
+	movq	8(%rdx),%r9
+	movq	16(%rdx),%r10
+	movq	24(%rdx),%r11
+	xorq	%rdi,%rdi
+
+L$beeu_reduction_loop:
+	movq	%r12,16(%rsp)
+	movq	%r13,24(%rsp)
+	movq	%r14,32(%rsp)
+	movq	%r15,40(%rsp)
+	movq	%rbp,48(%rsp)
+
+
+	subq	%r8,%r12
+	sbbq	%r9,%r13
+	sbbq	%r10,%r14
+	sbbq	%r11,%r15
+	sbbq	$0,%rbp
+
+
+	cmovcq	16(%rsp),%r12
+	cmovcq	24(%rsp),%r13
+	cmovcq	32(%rsp),%r14
+	cmovcq	40(%rsp),%r15
+	jnc	L$beeu_reduction_loop
+
+
+	subq	%r12,%r8
+	sbbq	%r13,%r9
+	sbbq	%r14,%r10
+	sbbq	%r15,%r11
+
+L$beeu_save:
+
+	movq	0(%rsp),%rdi
+
+	movq	%r8,0(%rdi)
+	movq	%r9,8(%rdi)
+	movq	%r10,16(%rdi)
+	movq	%r11,24(%rdi)
+
+
+	movq	$1,%rax
+	jmp	L$beeu_finish
+
+L$beeu_err:
+
+	xorq	%rax,%rax
+
+L$beeu_finish:
+	addq	$80,%rsp
+	popq	%rsi
+
+	popq	%rbx
+
+	popq	%r15
+
+	popq	%r14
+
+	popq	%r13
+
+	popq	%r12
+
+	popq	%rbp
+
+
+
+	.byte	0xf3,0xc3
+
+
+#endif
diff --git a/sources.bp b/sources.bp
index 15c6648..2a580c6 100644
--- a/sources.bp
+++ b/sources.bp
@@ -296,6 +296,7 @@
                 "linux-x86_64/crypto/fipsmodule/ghash-x86_64.S",
                 "linux-x86_64/crypto/fipsmodule/md5-x86_64.S",
                 "linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
+                "linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
                 "linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
                 "linux-x86_64/crypto/fipsmodule/rsaz-avx2.S",
                 "linux-x86_64/crypto/fipsmodule/sha1-x86_64.S",
diff --git a/sources.mk b/sources.mk
index 27f7930..b25a842 100644
--- a/sources.mk
+++ b/sources.mk
@@ -290,6 +290,7 @@
   linux-x86_64/crypto/fipsmodule/ghash-x86_64.S\
   linux-x86_64/crypto/fipsmodule/md5-x86_64.S\
   linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S\
+  linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S\
   linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S\
   linux-x86_64/crypto/fipsmodule/rsaz-avx2.S\
   linux-x86_64/crypto/fipsmodule/sha1-x86_64.S\
diff --git a/src/crypto/fipsmodule/CMakeLists.txt b/src/crypto/fipsmodule/CMakeLists.txt
index e6c8cc6..463febb 100644
--- a/src/crypto/fipsmodule/CMakeLists.txt
+++ b/src/crypto/fipsmodule/CMakeLists.txt
@@ -11,6 +11,7 @@
     ghash-x86_64.${ASM_EXT}
     md5-x86_64.${ASM_EXT}
     p256-x86_64-asm.${ASM_EXT}
+    p256_beeu-x86_64-asm.${ASM_EXT}
     rdrand-x86_64.${ASM_EXT}
     rsaz-avx2.${ASM_EXT}
     sha1-x86_64.${ASM_EXT}
@@ -100,6 +101,7 @@
 perlasm(md5-586.${ASM_EXT} md5/asm/md5-586.pl)
 perlasm(md5-x86_64.${ASM_EXT} md5/asm/md5-x86_64.pl)
 perlasm(p256-x86_64-asm.${ASM_EXT} ec/asm/p256-x86_64-asm.pl)
+perlasm(p256_beeu-x86_64-asm.${ASM_EXT} ec/asm/p256_beeu-x86_64-asm.pl)
 perlasm(rdrand-x86_64.${ASM_EXT} rand/asm/rdrand-x86_64.pl)
 perlasm(rsaz-avx2.${ASM_EXT} bn/asm/rsaz-avx2.pl)
 perlasm(sha1-586.${ASM_EXT} sha/asm/sha1-586.pl)
diff --git a/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl b/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl
new file mode 100644
index 0000000..12b9f5a
--- /dev/null
+++ b/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl
@@ -0,0 +1,405 @@
+# Copyright (c) 2018, Amazon Inc.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+#
+# Written by Nir Drucker, and Shay Gueron
+# AWS Cryptographic Algorithms Group
+# (ndrucker@amazon.com, gueron@amazon.com)
+# based on BN_mod_inverse_odd
+
+$flavour = shift;
+$output  = shift;
+if ($flavour =~ /\./) { $output = $flavour; undef $flavour; }
+
+$win64=0; $win64=1 if ($flavour =~ /[nm]asm|mingw64/ || $output =~ /\.asm$/);
+
+$0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
+( $xlate="${dir}x86_64-xlate.pl" and -f $xlate ) or
+( $xlate="${dir}../../../perlasm/x86_64-xlate.pl" and -f $xlate) or
+die "can't locate x86_64-xlate.pl";
+
+open OUT,"| \"$^X\" \"$xlate\" $flavour \"$output\"";
+*STDOUT=*OUT;
+
+#############################################################################
+# extern int beeu_mod_inverse_vartime(BN_ULONG out[P256_LIMBS],
+#                                     BN_ULONG a[P256_LIMBS],
+#                                     BN_ULONG n[P256_LIMBS]);
+#
+# (Binary Extended Euclidean Algorithm.
+#  See https://en.wikipedia.org/wiki/Binary_GCD_algorithm)
+#
+# Assumption 1: n is odd for the BEEU
+# Assumption 2: 1 < a < n < 2^256
+
+$out = "%rdi";
+$a = "%rsi";
+$n = "%rdx";
+
+# X/Y will hold the inverse parameter
+# Assumption: X,Y<2^(256)
+$x0 = "%r8";
+$x1 = "%r9";
+$x2 = "%r10";
+$x3 = "%r11";
+# borrow from out (out is needed only at the end)
+$x4 = "%rdi";
+$y0 = "%r12";
+$y1 = "%r13";
+$y2 = "%r14";
+$y3 = "%r15";
+$y4 = "%rbp";
+$shift = "%rcx";
+$t0 = "%rax";
+$t1 = "%rbx";
+$t2 = "%rsi";
+# borrow
+$t3 = "%rcx";
+
+$T0 = "%xmm0";
+$T1 = "%xmm1";
+
+# Offsets on the stack
+$out_rsp = 0;
+$shift_rsp = $out_rsp+0x8;
+$a_rsp0 = $shift_rsp+0x8;
+$a_rsp1 = $a_rsp0+0x8;
+$a_rsp2 = $a_rsp1+0x8;
+$a_rsp3 = $a_rsp2+0x8;
+$b_rsp0 = $a_rsp3+0x8;
+$b_rsp1 = $b_rsp0+0x8;
+$b_rsp2 = $b_rsp1+0x8;
+$b_rsp3 = $b_rsp2+0x8;
+
+# Borrow when a_rsp/b_rsp are no longer needed.
+$y_rsp0 = $a_rsp0;
+$y_rsp1 = $y_rsp0+0x8;
+$y_rsp2 = $y_rsp1+0x8;
+$y_rsp3 = $y_rsp2+0x8;
+$y_rsp4 = $y_rsp3+0x8;
+$last_rsp_offset = $b_rsp3+0x8;
+
+sub TEST_B_ZERO {
+  return <<___;
+    xorq $t1, $t1
+    or $b_rsp0(%rsp), $t1
+    or $b_rsp1(%rsp), $t1
+    or $b_rsp2(%rsp), $t1
+    or $b_rsp3(%rsp), $t1
+    jz .Lbeeu_loop_end
+___
+}
+
+$g_next_label = 0;
+
+sub SHIFT1 {
+  my ($var0, $var1, $var2, $var3, $var4) = @_;
+  my $label = ".Lshift1_${g_next_label}";
+  $g_next_label++;
+
+  return <<___;
+    # Ensure X is even and divide by two.
+    movq \$1, $t1
+    andq $var0, $t1
+    jz $label
+    add 0*8($n), $var0
+    adc 1*8($n), $var1
+    adc 2*8($n), $var2
+    adc 3*8($n), $var3
+    adc \$0, $var4
+
+$label:
+    shrdq \$1, $var1, $var0
+    shrdq \$1, $var2, $var1
+    shrdq \$1, $var3, $var2
+    shrdq \$1, $var4, $var3
+    shrq  \$1, $var4
+___
+}
+
+sub SHIFT256 {
+  my ($var) = @_;
+  return <<___;
+    # Copy shifted values.
+    # Remember not to override t3=rcx
+    movq 1*8+$var(%rsp), $t0
+    movq 2*8+$var(%rsp), $t1
+    movq 3*8+$var(%rsp), $t2
+
+    shrdq %cl, $t0, 0*8+$var(%rsp)
+    shrdq %cl, $t1, 1*8+$var(%rsp)
+    shrdq %cl, $t2, 2*8+$var(%rsp)
+
+    shrq  %cl, $t2
+    mov $t2, 3*8+$var(%rsp)
+___
+}
+
+$code.=<<___;
+.text
+
+.type beeu_mod_inverse_vartime,\@function
+.hidden beeu_mod_inverse_vartime
+.globl  beeu_mod_inverse_vartime
+.align 32
+beeu_mod_inverse_vartime:
+.cfi_startproc
+    push %rbp
+.cfi_push rbp
+    movq %rsp, %rbp
+.cfi_def_cfa_register rbp
+
+    push %r12
+.cfi_push r12
+    push %r13
+.cfi_push r13
+    push %r14
+.cfi_push r14
+    push %r15
+.cfi_push r15
+    push %rbx
+.cfi_push rbx
+    push %rsi
+.cfi_push rsi
+
+    sub \$$last_rsp_offset, %rsp
+    movq $out, $out_rsp(%rsp)
+
+    # X=1, Y=0
+    movq \$1, $x0
+    xorq $x1, $x1
+    xorq $x2, $x2
+    xorq $x3, $x3
+    xorq $x4, $x4
+
+    xorq $y0, $y0
+    xorq $y1, $y1
+    xorq $y2, $y2
+    xorq $y3, $y3
+    xorq $y4, $y4
+
+    # Copy a/n into B/A on the stack.
+    vmovdqu 0*8($a), $T0
+    vmovdqu 2*8($a), $T1
+    vmovdqu $T0, $b_rsp0(%rsp)
+    vmovdqu $T1, $b_rsp2(%rsp)
+
+    vmovdqu 0*8($n), $T0
+    vmovdqu 2*8($n), $T1
+    vmovdqu $T0, $a_rsp0(%rsp)
+    vmovdqu $T1, $a_rsp2(%rsp)
+
+.Lbeeu_loop:
+    ${\TEST_B_ZERO}
+
+    # 0 < B < |n|,
+    # 0 < A <= |n|,
+    # (1)      X*a  ==  B   (mod |n|),
+    # (2) (-1)*Y*a  ==  A   (mod |n|)
+
+    # Now divide B by the maximum possible power of two in the
+    # integers, and divide X by the same value mod |n|. When we're
+    # done, (1) still holds.
+    movq \$1, $shift
+
+    # Note that B > 0
+.Lbeeu_shift_loop_XB:
+    movq $shift, $t1
+    andq $b_rsp0(%rsp), $t1
+    jnz .Lbeeu_shift_loop_end_XB
+
+    ${\SHIFT1($x0, $x1, $x2, $x3, $x4)}
+    shl \$1, $shift
+
+    # Test wraparound of the shift parameter. The probability to have 32 zeroes
+    # in a row is small Therefore having the value below equal \$0x8000000 or
+    # \$0x8000 does not affect the performance. We choose 0x8000000 because it
+    # is the maximal immediate value possible.
+    cmp \$0x8000000, $shift
+    jne .Lbeeu_shift_loop_XB
+
+.Lbeeu_shift_loop_end_XB:
+    bsf $shift, $shift
+    test $shift, $shift
+    jz .Lbeeu_no_shift_XB
+
+    ${\SHIFT256($b_rsp0)}
+
+.Lbeeu_no_shift_XB:
+    # Same for A and Y.  Afterwards, (2) still holds.
+    movq \$1, $shift
+
+    # Note that A > 0
+.Lbeeu_shift_loop_YA:
+    movq $shift, $t1
+    andq $a_rsp0(%rsp), $t1
+    jnz .Lbeeu_shift_loop_end_YA
+
+    ${\SHIFT1($y0, $y1, $y2, $y3, $y4)}
+    shl \$1, $shift
+
+    # Test wraparound of the shift parameter. The probability to have 32 zeroes
+    # in a row is small therefore having the value below equal \$0x8000000 or
+    # \$0x8000 Does not affect the performance. We choose 0x8000000 because it
+    # is the maximal immediate value possible.
+    cmp \$0x8000000, $shift
+    jne .Lbeeu_shift_loop_YA
+
+.Lbeeu_shift_loop_end_YA:
+    bsf $shift, $shift
+    test $shift, $shift
+    jz .Lbeeu_no_shift_YA
+
+    ${\SHIFT256($a_rsp0)}
+
+.Lbeeu_no_shift_YA:
+    # T = B-A (A,B < 2^256)
+    mov $b_rsp0(%rsp), $t0
+    mov $b_rsp1(%rsp), $t1
+    mov $b_rsp2(%rsp), $t2
+    mov $b_rsp3(%rsp), $t3
+    sub $a_rsp0(%rsp), $t0
+    sbb $a_rsp1(%rsp), $t1
+    sbb $a_rsp2(%rsp), $t2
+    sbb $a_rsp3(%rsp), $t3  # borrow from shift
+    jnc .Lbeeu_B_bigger_than_A
+
+    # A = A - B
+    mov $a_rsp0(%rsp), $t0
+    mov $a_rsp1(%rsp), $t1
+    mov $a_rsp2(%rsp), $t2
+    mov $a_rsp3(%rsp), $t3
+    sub $b_rsp0(%rsp), $t0
+    sbb $b_rsp1(%rsp), $t1
+    sbb $b_rsp2(%rsp), $t2
+    sbb $b_rsp3(%rsp), $t3
+    mov $t0, $a_rsp0(%rsp)
+    mov $t1, $a_rsp1(%rsp)
+    mov $t2, $a_rsp2(%rsp)
+    mov $t3, $a_rsp3(%rsp)
+
+    # Y = Y + X
+    add $x0, $y0
+    adc $x1, $y1
+    adc $x2, $y2
+    adc $x3, $y3
+    adc $x4, $y4
+    jmp .Lbeeu_loop
+
+.Lbeeu_B_bigger_than_A:
+    # B = T = B - A
+    mov $t0, $b_rsp0(%rsp)
+    mov $t1, $b_rsp1(%rsp)
+    mov $t2, $b_rsp2(%rsp)
+    mov $t3, $b_rsp3(%rsp)
+
+    # X = Y + X
+    add $y0, $x0
+    adc $y1, $x1
+    adc $y2, $x2
+    adc $y3, $x3
+    adc $y4, $x4
+
+    jmp .Lbeeu_loop
+
+.Lbeeu_loop_end:
+    # The Euclid's algorithm loop ends when A == beeu(a,n);
+    # Therefore (-1)*Y*a == A (mod |n|), Y>0
+
+    # Verify that A = 1 ==> (-1)*Y*a = A = 1  (mod |n|)
+    mov $a_rsp0(%rsp), $t1
+    sub \$1, $t1
+    or $a_rsp1(%rsp), $t1
+    or $a_rsp2(%rsp), $t1
+    or $a_rsp3(%rsp), $t1
+    # If not, fail.
+    jnz .Lbeeu_err
+
+    # From this point on, we no longer need X
+    # Therefore we use it as a temporary storage.
+    # X = n
+    movq 0*8($n), $x0
+    movq 1*8($n), $x1
+    movq 2*8($n), $x2
+    movq 3*8($n), $x3
+    xorq $x4, $x4
+
+.Lbeeu_reduction_loop:
+    movq $y0, $y_rsp0(%rsp)
+    movq $y1, $y_rsp1(%rsp)
+    movq $y2, $y_rsp2(%rsp)
+    movq $y3, $y_rsp3(%rsp)
+    movq $y4, $y_rsp4(%rsp)
+
+    # If Y>n ==> Y=Y-n
+    sub $x0, $y0
+    sbb $x1, $y1
+    sbb $x2, $y2
+    sbb $x3, $y3
+    sbb \$0, $y4
+
+    # Choose old Y or new Y
+    cmovc $y_rsp0(%rsp), $y0
+    cmovc $y_rsp1(%rsp), $y1
+    cmovc $y_rsp2(%rsp), $y2
+    cmovc $y_rsp3(%rsp), $y3
+    jnc .Lbeeu_reduction_loop
+
+    # X = n - Y (n, Y < 2^256), (Cancel the (-1))
+    sub $y0, $x0
+    sbb $y1, $x1
+    sbb $y2, $x2
+    sbb $y3, $x3
+
+.Lbeeu_save:
+    # Save the inverse(<2^256) to out.
+    mov $out_rsp(%rsp), $out
+
+    movq $x0, 0*8($out)
+    movq $x1, 1*8($out)
+    movq $x2, 2*8($out)
+    movq $x3, 3*8($out)
+
+    # Return 1.
+    movq \$1, %rax
+    jmp .Lbeeu_finish
+
+.Lbeeu_err:
+    # Return 0.
+    xorq %rax, %rax
+
+.Lbeeu_finish:
+    add \$$last_rsp_offset, %rsp
+    pop %rsi
+.cfi_pop rsi
+    pop %rbx
+.cfi_pop rbx
+    pop %r15
+.cfi_pop r15
+    pop %r14
+.cfi_pop r14
+    pop %r13
+.cfi_pop r13
+    pop %r12
+.cfi_pop r12
+    pop %rbp
+.cfi_pop rbp
+.cfi_def_cfa rsp, 8
+.cfi_endproc
+    ret
+
+.size beeu_mod_inverse_vartime, .-beeu_mod_inverse_vartime
+___
+
+print $code;
+close STDOUT;
diff --git a/src/crypto/fipsmodule/ec/ec.c b/src/crypto/fipsmodule/ec/ec.c
index 908e35e..ba101fe 100644
--- a/src/crypto/fipsmodule/ec/ec.c
+++ b/src/crypto/fipsmodule/ec/ec.c
@@ -619,7 +619,7 @@
 int EC_GROUP_get_curve_name(const EC_GROUP *group) { return group->curve_name; }
 
 unsigned EC_GROUP_get_degree(const EC_GROUP *group) {
-  return ec_GFp_simple_group_get_degree(group);
+  return BN_num_bits(&group->field);
 }
 
 const char *EC_curve_nid2nist(int nid) {
@@ -743,7 +743,15 @@
     OPENSSL_PUT_ERROR(EC, EC_R_INCOMPATIBLE_OBJECTS);
     return 0;
   }
-  return group->meth->point_get_affine_coordinates(group, &point->raw, x, y);
+  EC_FELEM x_felem, y_felem;
+  if (!group->meth->point_get_affine_coordinates(group, &point->raw,
+                                                 x == NULL ? NULL : &x_felem,
+                                                 y == NULL ? NULL : &y_felem) ||
+      (x != NULL && !bn_set_words(x, x_felem.words, group->field.width)) ||
+      (y != NULL && !bn_set_words(y, y_felem.words, group->field.width))) {
+    return 0;
+  }
+  return 1;
 }
 
 int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *group, EC_POINT *point,
@@ -782,7 +790,7 @@
     OPENSSL_PUT_ERROR(EC, EC_R_INCOMPATIBLE_OBJECTS);
     return 0;
   }
-  ec_GFp_simple_add(group, &r->raw, &a->raw, &b->raw);
+  group->meth->add(group, &r->raw, &a->raw, &b->raw);
   return 1;
 }
 
@@ -793,7 +801,7 @@
     OPENSSL_PUT_ERROR(EC, EC_R_INCOMPATIBLE_OBJECTS);
     return 0;
   }
-  ec_GFp_simple_dbl(group, &r->raw, &a->raw);
+  group->meth->dbl(group, &r->raw, &a->raw);
   return 1;
 }
 
@@ -911,6 +919,43 @@
   return 1;
 }
 
+int ec_cmp_x_coordinate(int *out_result, const EC_GROUP *group,
+                        const EC_POINT *p, const BIGNUM *r, BN_CTX *ctx) {
+  return group->meth->cmp_x_coordinate(out_result, group, p, r, ctx);
+}
+
+int ec_field_element_to_scalar(const EC_GROUP *group, BIGNUM *r) {
+  // We must have p < 2×order, assuming p is not tiny (p >= 17). Thus rather we
+  // can reduce by performing at most one subtraction.
+  //
+  // Proof: We only work with prime order curves, so the number of points on
+  // the curve is the order. Thus Hasse's theorem gives:
+  //
+  //     |order - (p + 1)| <= 2×sqrt(p)
+  //         p + 1 - order <= 2×sqrt(p)
+  //     p + 1 - 2×sqrt(p) <= order
+  //       p + 1 - 2×(p/4)  < order       (p/4 > sqrt(p) for p >= 17)
+  //         p/2 < p/2 + 1  < order
+  //                     p  < 2×order
+  //
+  // Additionally, one can manually check this property for built-in curves. It
+  // is enforced for legacy custom curves in |EC_GROUP_set_generator|.
+  //
+  // TODO(davidben): Introduce |EC_FIELD_ELEMENT|, make this a function from
+  // |EC_FIELD_ELEMENT| to |EC_SCALAR|, and cut out the |BIGNUM|. Does this need
+  // to be constant-time for signing? |r| is the x-coordinate for kG, which is
+  // public unless k was rerolled because |s| was zero.
+  assert(!BN_is_negative(r));
+  assert(BN_cmp(r, &group->field) < 0);
+  if (BN_cmp(r, &group->order) >= 0 &&
+      !BN_sub(r, r, &group->order)) {
+    return 0;
+  }
+  assert(!BN_is_negative(r));
+  assert(BN_cmp(r, &group->order) < 0);
+  return 1;
+}
+
 void EC_GROUP_set_asn1_flag(EC_GROUP *group, int flag) {}
 
 const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group) {
diff --git a/src/crypto/fipsmodule/ec/ec_montgomery.c b/src/crypto/fipsmodule/ec/ec_montgomery.c
index 9eace95..4961a7c 100644
--- a/src/crypto/fipsmodule/ec/ec_montgomery.c
+++ b/src/crypto/fipsmodule/ec/ec_montgomery.c
@@ -182,7 +182,7 @@
 
 static int ec_GFp_mont_point_get_affine_coordinates(const EC_GROUP *group,
                                                     const EC_RAW_POINT *point,
-                                                    BIGNUM *x, BIGNUM *y) {
+                                                    EC_FELEM *x, EC_FELEM *y) {
   if (ec_GFp_simple_is_at_infinity(group, point)) {
     OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
     return 0;
@@ -201,35 +201,236 @@
   ec_GFp_mont_felem_from_montgomery(group, &z1, &z1);
 
   if (x != NULL) {
-    EC_FELEM tmp;
-    ec_GFp_mont_felem_mul(group, &tmp, &point->X, &z1);
-    if (!bn_set_words(x, tmp.words, group->field.width)) {
-      return 0;
-    }
+    ec_GFp_mont_felem_mul(group, x, &point->X, &z1);
   }
 
   if (y != NULL) {
-    EC_FELEM tmp;
     ec_GFp_mont_felem_mul(group, &z1, &z1, &z2);
-    ec_GFp_mont_felem_mul(group, &tmp, &point->Y, &z1);
-    if (!bn_set_words(y, tmp.words, group->field.width)) {
-      return 0;
-    }
+    ec_GFp_mont_felem_mul(group, y, &point->Y, &z1);
   }
 
   return 1;
 }
 
+void ec_GFp_mont_add(const EC_GROUP *group, EC_RAW_POINT *out,
+                     const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
+  if (a == b) {
+    ec_GFp_mont_dbl(group, out, a);
+    return;
+  }
+
+  // The method is taken from:
+  //   http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl
+  //
+  // Coq transcription and correctness proof:
+  // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L467>
+  // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L544>
+  EC_FELEM x_out, y_out, z_out;
+  BN_ULONG z1nz = ec_felem_non_zero_mask(group, &a->Z);
+  BN_ULONG z2nz = ec_felem_non_zero_mask(group, &b->Z);
+
+  // z1z1 = z1z1 = z1**2
+  EC_FELEM z1z1;
+  ec_GFp_mont_felem_sqr(group, &z1z1, &a->Z);
+
+  // z2z2 = z2**2
+  EC_FELEM z2z2;
+  ec_GFp_mont_felem_sqr(group, &z2z2, &b->Z);
+
+  // u1 = x1*z2z2
+  EC_FELEM u1;
+  ec_GFp_mont_felem_mul(group, &u1, &a->X, &z2z2);
+
+  // two_z1z2 = (z1 + z2)**2 - (z1z1 + z2z2) = 2z1z2
+  EC_FELEM two_z1z2;
+  ec_felem_add(group, &two_z1z2, &a->Z, &b->Z);
+  ec_GFp_mont_felem_sqr(group, &two_z1z2, &two_z1z2);
+  ec_felem_sub(group, &two_z1z2, &two_z1z2, &z1z1);
+  ec_felem_sub(group, &two_z1z2, &two_z1z2, &z2z2);
+
+  // s1 = y1 * z2**3
+  EC_FELEM s1;
+  ec_GFp_mont_felem_mul(group, &s1, &b->Z, &z2z2);
+  ec_GFp_mont_felem_mul(group, &s1, &s1, &a->Y);
+
+  // u2 = x2*z1z1
+  EC_FELEM u2;
+  ec_GFp_mont_felem_mul(group, &u2, &b->X, &z1z1);
+
+  // h = u2 - u1
+  EC_FELEM h;
+  ec_felem_sub(group, &h, &u2, &u1);
+
+  BN_ULONG xneq = ec_felem_non_zero_mask(group, &h);
+
+  // z_out = two_z1z2 * h
+  ec_GFp_mont_felem_mul(group, &z_out, &h, &two_z1z2);
+
+  // z1z1z1 = z1 * z1z1
+  EC_FELEM z1z1z1;
+  ec_GFp_mont_felem_mul(group, &z1z1z1, &a->Z, &z1z1);
+
+  // s2 = y2 * z1**3
+  EC_FELEM s2;
+  ec_GFp_mont_felem_mul(group, &s2, &b->Y, &z1z1z1);
+
+  // r = (s2 - s1)*2
+  EC_FELEM r;
+  ec_felem_sub(group, &r, &s2, &s1);
+  ec_felem_add(group, &r, &r, &r);
+
+  BN_ULONG yneq = ec_felem_non_zero_mask(group, &r);
+
+  // This case will never occur in the constant-time |ec_GFp_mont_mul|.
+  if (!xneq && !yneq && z1nz && z2nz) {
+    ec_GFp_mont_dbl(group, out, a);
+    return;
+  }
+
+  // I = (2h)**2
+  EC_FELEM i;
+  ec_felem_add(group, &i, &h, &h);
+  ec_GFp_mont_felem_sqr(group, &i, &i);
+
+  // J = h * I
+  EC_FELEM j;
+  ec_GFp_mont_felem_mul(group, &j, &h, &i);
+
+  // V = U1 * I
+  EC_FELEM v;
+  ec_GFp_mont_felem_mul(group, &v, &u1, &i);
+
+  // x_out = r**2 - J - 2V
+  ec_GFp_mont_felem_sqr(group, &x_out, &r);
+  ec_felem_sub(group, &x_out, &x_out, &j);
+  ec_felem_sub(group, &x_out, &x_out, &v);
+  ec_felem_sub(group, &x_out, &x_out, &v);
+
+  // y_out = r(V-x_out) - 2 * s1 * J
+  ec_felem_sub(group, &y_out, &v, &x_out);
+  ec_GFp_mont_felem_mul(group, &y_out, &y_out, &r);
+  EC_FELEM s1j;
+  ec_GFp_mont_felem_mul(group, &s1j, &s1, &j);
+  ec_felem_sub(group, &y_out, &y_out, &s1j);
+  ec_felem_sub(group, &y_out, &y_out, &s1j);
+
+  ec_felem_select(group, &x_out, z1nz, &x_out, &b->X);
+  ec_felem_select(group, &out->X, z2nz, &x_out, &a->X);
+  ec_felem_select(group, &y_out, z1nz, &y_out, &b->Y);
+  ec_felem_select(group, &out->Y, z2nz, &y_out, &a->Y);
+  ec_felem_select(group, &z_out, z1nz, &z_out, &b->Z);
+  ec_felem_select(group, &out->Z, z2nz, &z_out, &a->Z);
+}
+
+void ec_GFp_mont_dbl(const EC_GROUP *group, EC_RAW_POINT *r,
+                     const EC_RAW_POINT *a) {
+  if (group->a_is_minus3) {
+    // The method is taken from:
+    //   http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
+    //
+    // Coq transcription and correctness proof:
+    // <https://github.com/mit-plv/fiat-crypto/blob/79f8b5f39ed609339f0233098dee1a3c4e6b3080/src/Curves/Weierstrass/Jacobian.v#L93>
+    // <https://github.com/mit-plv/fiat-crypto/blob/79f8b5f39ed609339f0233098dee1a3c4e6b3080/src/Curves/Weierstrass/Jacobian.v#L201>
+    EC_FELEM delta, gamma, beta, ftmp, ftmp2, tmptmp, alpha, fourbeta;
+    // delta = z^2
+    ec_GFp_mont_felem_sqr(group, &delta, &a->Z);
+    // gamma = y^2
+    ec_GFp_mont_felem_sqr(group, &gamma, &a->Y);
+    // beta = x*gamma
+    ec_GFp_mont_felem_mul(group, &beta, &a->X, &gamma);
+
+    // alpha = 3*(x-delta)*(x+delta)
+    ec_felem_sub(group, &ftmp, &a->X, &delta);
+    ec_felem_add(group, &ftmp2, &a->X, &delta);
+
+    ec_felem_add(group, &tmptmp, &ftmp2, &ftmp2);
+    ec_felem_add(group, &ftmp2, &ftmp2, &tmptmp);
+    ec_GFp_mont_felem_mul(group, &alpha, &ftmp, &ftmp2);
+
+    // x' = alpha^2 - 8*beta
+    ec_GFp_mont_felem_sqr(group, &r->X, &alpha);
+    ec_felem_add(group, &fourbeta, &beta, &beta);
+    ec_felem_add(group, &fourbeta, &fourbeta, &fourbeta);
+    ec_felem_add(group, &tmptmp, &fourbeta, &fourbeta);
+    ec_felem_sub(group, &r->X, &r->X, &tmptmp);
+
+    // z' = (y + z)^2 - gamma - delta
+    ec_felem_add(group, &delta, &gamma, &delta);
+    ec_felem_add(group, &ftmp, &a->Y, &a->Z);
+    ec_GFp_mont_felem_sqr(group, &r->Z, &ftmp);
+    ec_felem_sub(group, &r->Z, &r->Z, &delta);
+
+    // y' = alpha*(4*beta - x') - 8*gamma^2
+    ec_felem_sub(group, &r->Y, &fourbeta, &r->X);
+    ec_felem_add(group, &gamma, &gamma, &gamma);
+    ec_GFp_mont_felem_sqr(group, &gamma, &gamma);
+    ec_GFp_mont_felem_mul(group, &r->Y, &alpha, &r->Y);
+    ec_felem_add(group, &gamma, &gamma, &gamma);
+    ec_felem_sub(group, &r->Y, &r->Y, &gamma);
+  } else {
+    // The method is taken from:
+    //   http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl
+    //
+    // Coq transcription and correctness proof:
+    // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L102>
+    // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L534>
+    EC_FELEM xx, yy, yyyy, zz;
+    ec_GFp_mont_felem_sqr(group, &xx, &a->X);
+    ec_GFp_mont_felem_sqr(group, &yy, &a->Y);
+    ec_GFp_mont_felem_sqr(group, &yyyy, &yy);
+    ec_GFp_mont_felem_sqr(group, &zz, &a->Z);
+
+    // s = 2*((x_in + yy)^2 - xx - yyyy)
+    EC_FELEM s;
+    ec_felem_add(group, &s, &a->X, &yy);
+    ec_GFp_mont_felem_sqr(group, &s, &s);
+    ec_felem_sub(group, &s, &s, &xx);
+    ec_felem_sub(group, &s, &s, &yyyy);
+    ec_felem_add(group, &s, &s, &s);
+
+    // m = 3*xx + a*zz^2
+    EC_FELEM m;
+    ec_GFp_mont_felem_sqr(group, &m, &zz);
+    ec_GFp_mont_felem_mul(group, &m, &group->a, &m);
+    ec_felem_add(group, &m, &m, &xx);
+    ec_felem_add(group, &m, &m, &xx);
+    ec_felem_add(group, &m, &m, &xx);
+
+    // x_out = m^2 - 2*s
+    ec_GFp_mont_felem_sqr(group, &r->X, &m);
+    ec_felem_sub(group, &r->X, &r->X, &s);
+    ec_felem_sub(group, &r->X, &r->X, &s);
+
+    // z_out = (y_in + z_in)^2 - yy - zz
+    ec_felem_add(group, &r->Z, &a->Y, &a->Z);
+    ec_GFp_mont_felem_sqr(group, &r->Z, &r->Z);
+    ec_felem_sub(group, &r->Z, &r->Z, &yy);
+    ec_felem_sub(group, &r->Z, &r->Z, &zz);
+
+    // y_out = m*(s-x_out) - 8*yyyy
+    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
+    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
+    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
+    ec_felem_sub(group, &r->Y, &s, &r->X);
+    ec_GFp_mont_felem_mul(group, &r->Y, &r->Y, &m);
+    ec_felem_sub(group, &r->Y, &r->Y, &yyyy);
+  }
+}
+
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_mont_method) {
   out->group_init = ec_GFp_mont_group_init;
   out->group_finish = ec_GFp_mont_group_finish;
   out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates = ec_GFp_mont_point_get_affine_coordinates;
-  out->mul = ec_GFp_simple_mul;
-  out->mul_public = ec_GFp_simple_mul_public;
+  out->add = ec_GFp_mont_add;
+  out->dbl = ec_GFp_mont_dbl;
+  out->mul = ec_GFp_mont_mul;
+  out->mul_public = ec_GFp_mont_mul_public;
   out->felem_mul = ec_GFp_mont_felem_mul;
   out->felem_sqr = ec_GFp_mont_felem_sqr;
   out->bignum_to_felem = ec_GFp_mont_bignum_to_felem;
   out->felem_to_bignum = ec_GFp_mont_felem_to_bignum;
   out->scalar_inv_montgomery = ec_simple_scalar_inv_montgomery;
+  out->scalar_inv_montgomery_vartime = ec_GFp_simple_mont_inv_mod_ord_vartime;
+  out->cmp_x_coordinate = ec_GFp_simple_cmp_x_coordinate;
 }
diff --git a/src/crypto/fipsmodule/ec/internal.h b/src/crypto/fipsmodule/ec/internal.h
index bb172b2..4afaef9 100644
--- a/src/crypto/fipsmodule/ec/internal.h
+++ b/src/crypto/fipsmodule/ec/internal.h
@@ -124,8 +124,21 @@
   void (*group_finish)(EC_GROUP *);
   int (*group_set_curve)(EC_GROUP *, const BIGNUM *p, const BIGNUM *a,
                          const BIGNUM *b, BN_CTX *);
-  int (*point_get_affine_coordinates)(const EC_GROUP *, const EC_RAW_POINT *,
-                                      BIGNUM *x, BIGNUM *y);
+
+  // point_get_affine_coordinates sets |*x| and |*y| to the affine coordinates
+  // of |p|. Either |x| or |y| may be NULL to omit it. It returns one on success
+  // and zero if |p| is the point at infinity.
+  //
+  // Note: unlike |EC_FELEM|s used as intermediate values internal to the
+  // |EC_METHOD|, |*x| and |*y| are not encoded in Montgomery form.
+  int (*point_get_affine_coordinates)(const EC_GROUP *, const EC_RAW_POINT *p,
+                                      EC_FELEM *x, EC_FELEM *y);
+
+  // add sets |r| to |a| + |b|.
+  void (*add)(const EC_GROUP *group, EC_RAW_POINT *r, const EC_RAW_POINT *a,
+              const EC_RAW_POINT *b);
+  // dbl sets |r| to |a| + |a|.
+  void (*dbl)(const EC_GROUP *group, EC_RAW_POINT *r, const EC_RAW_POINT *a);
 
   // Computes |r = g_scalar*generator + p_scalar*p| if |g_scalar| and |p_scalar|
   // are both non-null. Computes |r = g_scalar*generator| if |p_scalar| is null.
@@ -149,10 +162,9 @@
   // TODO(davidben): This constrains |EC_FELEM|'s internal representation, adds
   // many indirect calls in the middle of the generic code, and a bunch of
   // conversions. If p224-64.c were easily convertable to Montgomery form, we
-  // could say |EC_FELEM| is always in Montgomery form. If we exposed the
-  // internal add and double implementations in each of the curves, we could
-  // give |EC_POINT| an |EC_METHOD|-specific representation and |EC_FELEM| is
-  // purely a |EC_GFp_mont_method| type.
+  // could say |EC_FELEM| is always in Montgomery form. If we routed the rest of
+  // simple.c to |EC_METHOD|, we could give |EC_POINT| an |EC_METHOD|-specific
+  // representation and say |EC_FELEM| is purely a |EC_GFp_mont_method| type.
   void (*felem_mul)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a,
                     const EC_FELEM *b);
   void (*felem_sqr)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a);
@@ -162,11 +174,22 @@
   int (*felem_to_bignum)(const EC_GROUP *group, BIGNUM *out,
                          const EC_FELEM *in);
 
-  // scalar_inv_mont sets |out| to |in|^-1, where both input and output are in
-  // Montgomery form.
+  // scalar_inv_montgomery sets |out| to |in|^-1, where both input and output
+  // are in Montgomery form.
   void (*scalar_inv_montgomery)(const EC_GROUP *group, EC_SCALAR *out,
                                 const EC_SCALAR *in);
 
+  // scalar_inv_montgomery_vartime performs the same computation as
+  // |scalar_inv_montgomery|. It further assumes that the inputs are public so
+  // there is no concern about leaking their values through timing.
+  int (*scalar_inv_montgomery_vartime)(const EC_GROUP *group, EC_SCALAR *out,
+                                       const EC_SCALAR *in);
+
+  // cmp_x_coordinate compares the x (affine) coordinate of |p|, mod the group
+  // order, with |r|. On error it returns zero. Otherwise it sets |*out_result|
+  // to one iff the values match.
+  int (*cmp_x_coordinate)(int *out_result, const EC_GROUP *group,
+                          const EC_POINT *p, const BIGNUM *r, BN_CTX *ctx);
 } /* EC_METHOD */;
 
 const EC_METHOD *EC_GFp_mont_method(void);
@@ -272,6 +295,11 @@
 void ec_scalar_inv_montgomery(const EC_GROUP *group, EC_SCALAR *r,
                               const EC_SCALAR *a);
 
+// ec_scalar_inv_montgomery_vartime performs the same actions as
+// |ec_scalar_inv_montgomery|, but in variable time.
+int ec_scalar_inv_montgomery_vartime(const EC_GROUP *group, EC_SCALAR *r,
+                                     const EC_SCALAR *a);
+
 // ec_point_mul_scalar sets |r| to generator * |g_scalar| + |p| *
 // |p_scalar|. Unlike other functions which take |EC_SCALAR|, |g_scalar| and
 // |p_scalar| need not be fully reduced. They need only contain as many bits as
@@ -287,9 +315,19 @@
     const EC_GROUP *group, EC_POINT *r, const EC_SCALAR *g_scalar,
     const EC_POINT *p, const EC_SCALAR *p_scalar, BN_CTX *ctx);
 
-void ec_GFp_simple_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                       const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                       const EC_SCALAR *p_scalar);
+// ec_cmp_x_coordinate compares the x (affine) coordinate of |p| with |r|. It
+// returns zero on error. Otherwise it sets |*out_result| to one iff the values
+// match.
+int ec_cmp_x_coordinate(int *out_result, const EC_GROUP *group,
+                        const EC_POINT *p, const BIGNUM *r, BN_CTX *ctx);
+
+// ec_field_element_to_scalar reduces |r| modulo |group->order|. |r| must
+// previously have been reduced modulo |group->field|.
+int ec_field_element_to_scalar(const EC_GROUP *group, BIGNUM *r);
+
+void ec_GFp_mont_mul(const EC_GROUP *group, EC_RAW_POINT *r,
+                     const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
+                     const EC_SCALAR *p_scalar);
 
 // ec_compute_wNAF writes the modified width-(w+1) Non-Adjacent Form (wNAF) of
 // |scalar| to |out|. |out| must have room for |bits| + 1 elements, each of
@@ -302,9 +340,9 @@
 void ec_compute_wNAF(const EC_GROUP *group, int8_t *out,
                      const EC_SCALAR *scalar, size_t bits, int w);
 
-void ec_GFp_simple_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
-                              const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                              const EC_SCALAR *p_scalar);
+void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
+                            const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
+                            const EC_SCALAR *p_scalar);
 
 // method functions in simple.c
 int ec_GFp_simple_group_init(EC_GROUP *);
@@ -313,17 +351,15 @@
                                   const BIGNUM *b, BN_CTX *);
 int ec_GFp_simple_group_get_curve(const EC_GROUP *, BIGNUM *p, BIGNUM *a,
                                   BIGNUM *b);
-unsigned ec_GFp_simple_group_get_degree(const EC_GROUP *);
 void ec_GFp_simple_point_init(EC_RAW_POINT *);
 void ec_GFp_simple_point_copy(EC_RAW_POINT *, const EC_RAW_POINT *);
 void ec_GFp_simple_point_set_to_infinity(const EC_GROUP *, EC_RAW_POINT *);
 int ec_GFp_simple_point_set_affine_coordinates(const EC_GROUP *, EC_RAW_POINT *,
                                                const BIGNUM *x,
                                                const BIGNUM *y);
-void ec_GFp_simple_add(const EC_GROUP *, EC_RAW_POINT *r, const EC_RAW_POINT *a,
-                       const EC_RAW_POINT *b);
-void ec_GFp_simple_dbl(const EC_GROUP *, EC_RAW_POINT *r,
-                       const EC_RAW_POINT *a);
+void ec_GFp_mont_add(const EC_GROUP *, EC_RAW_POINT *r, const EC_RAW_POINT *a,
+                     const EC_RAW_POINT *b);
+void ec_GFp_mont_dbl(const EC_GROUP *, EC_RAW_POINT *r, const EC_RAW_POINT *a);
 void ec_GFp_simple_invert(const EC_GROUP *, EC_RAW_POINT *);
 int ec_GFp_simple_is_at_infinity(const EC_GROUP *, const EC_RAW_POINT *);
 int ec_GFp_simple_is_on_curve(const EC_GROUP *, const EC_RAW_POINT *);
@@ -332,6 +368,16 @@
 void ec_simple_scalar_inv_montgomery(const EC_GROUP *group, EC_SCALAR *r,
                                      const EC_SCALAR *a);
 
+int ec_GFp_simple_mont_inv_mod_ord_vartime(const EC_GROUP *group, EC_SCALAR *r,
+                                           const EC_SCALAR *a);
+
+// ec_GFp_simple_cmp_x_coordinate compares the x (affine) coordinate of |p|, mod
+// the group order, with |r|. It returns zero on error. Otherwise it sets
+// |*out_result| to one iff the values match.
+int ec_GFp_simple_cmp_x_coordinate(int *out_result, const EC_GROUP *group,
+                                   const EC_POINT *p, const BIGNUM *r,
+                                   BN_CTX *ctx);
+
 // method functions in montgomery.c
 int ec_GFp_mont_group_init(EC_GROUP *);
 int ec_GFp_mont_group_set_curve(EC_GROUP *, const BIGNUM *p, const BIGNUM *a,
diff --git a/src/crypto/fipsmodule/ec/p224-64.c b/src/crypto/fipsmodule/ec/p224-64.c
index 606108f..49d5328 100644
--- a/src/crypto/fipsmodule/ec/p224-64.c
+++ b/src/crypto/fipsmodule/ec/p224-64.c
@@ -203,13 +203,6 @@
   }
 }
 
-// From internal representation to OpenSSL BIGNUM
-static BIGNUM *p224_felem_to_BN(BIGNUM *out, const p224_felem in) {
-  p224_felem_bytearray b_out;
-  p224_felem_to_bin28(b_out, in);
-  return BN_le2bn(b_out, sizeof(b_out), out);
-}
-
 static void p224_generic_to_felem(p224_felem out, const EC_FELEM *in) {
   p224_bin28_to_felem(out, in->bytes);
 }
@@ -917,7 +910,7 @@
 
       if (!skip) {
         p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
-                  tmp[0], tmp[1], tmp[2]);
+                       tmp[0], tmp[1], tmp[2]);
       } else {
         OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
         skip = 0;
@@ -951,7 +944,7 @@
 
       if (!skip) {
         p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 0 /* mixed */,
-                  tmp[0], tmp[1], tmp[2]);
+                       tmp[0], tmp[1], tmp[2]);
       } else {
         OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
         skip = 0;
@@ -971,7 +964,8 @@
 // Takes the Jacobian coordinates (X, Y, Z) of a point and returns
 // (X', Y') = (X/Z^2, Y/Z^3)
 static int ec_GFp_nistp224_point_get_affine_coordinates(
-    const EC_GROUP *group, const EC_RAW_POINT *point, BIGNUM *x, BIGNUM *y) {
+    const EC_GROUP *group, const EC_RAW_POINT *point, EC_FELEM *x,
+    EC_FELEM *y) {
   if (ec_GFp_simple_is_at_infinity(group, point)) {
     OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
     return 0;
@@ -990,10 +984,7 @@
     p224_felem_mul(tmp, x_in, z1);
     p224_felem_reduce(x_in, tmp);
     p224_felem_contract(x_out, x_in);
-    if (!p224_felem_to_BN(x, x_out)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-      return 0;
-    }
+    p224_felem_to_generic(x, x_out);
   }
 
   if (y != NULL) {
@@ -1004,15 +995,39 @@
     p224_felem_mul(tmp, y_in, z1);
     p224_felem_reduce(y_in, tmp);
     p224_felem_contract(y_out, y_in);
-    if (!p224_felem_to_BN(y, y_out)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-      return 0;
-    }
+    p224_felem_to_generic(y, y_out);
   }
 
   return 1;
 }
 
+static void ec_GFp_nistp224_add(const EC_GROUP *group, EC_RAW_POINT *r,
+                                const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
+  p224_felem x1, y1, z1, x2, y2, z2;
+  p224_generic_to_felem(x1, &a->X);
+  p224_generic_to_felem(y1, &a->Y);
+  p224_generic_to_felem(z1, &a->Z);
+  p224_generic_to_felem(x2, &b->X);
+  p224_generic_to_felem(y2, &b->Y);
+  p224_generic_to_felem(z2, &b->Z);
+  p224_point_add(x1, y1, z1, x1, y1, z1, 0 /* both Jacobian */, x2, y2, z2);
+  p224_felem_to_generic(&r->X, x1);
+  p224_felem_to_generic(&r->Y, y1);
+  p224_felem_to_generic(&r->Z, z1);
+}
+
+static void ec_GFp_nistp224_dbl(const EC_GROUP *group, EC_RAW_POINT *r,
+                                const EC_RAW_POINT *a) {
+  p224_felem x, y, z;
+  p224_generic_to_felem(x, &a->X);
+  p224_generic_to_felem(y, &a->Y);
+  p224_generic_to_felem(z, &a->Z);
+  p224_point_double(x, y, z, x, y, z);
+  p224_felem_to_generic(&r->X, x);
+  p224_felem_to_generic(&r->Y, y);
+  p224_felem_to_generic(&r->Z, z);
+}
+
 static void ec_GFp_nistp224_points_mul(const EC_GROUP *group, EC_RAW_POINT *r,
                                        const EC_SCALAR *g_scalar,
                                        const EC_RAW_POINT *p,
@@ -1036,13 +1051,13 @@
     for (size_t j = 2; j <= 16; ++j) {
       if (j & 1) {
         p224_point_add(p_pre_comp[j][0], p_pre_comp[j][1], p_pre_comp[j][2],
-                  p_pre_comp[1][0], p_pre_comp[1][1], p_pre_comp[1][2],
-                  0, p_pre_comp[j - 1][0], p_pre_comp[j - 1][1],
-                  p_pre_comp[j - 1][2]);
+                       p_pre_comp[1][0], p_pre_comp[1][1], p_pre_comp[1][2], 0,
+                       p_pre_comp[j - 1][0], p_pre_comp[j - 1][1],
+                       p_pre_comp[j - 1][2]);
       } else {
-        p224_point_double(p_pre_comp[j][0], p_pre_comp[j][1],
-                     p_pre_comp[j][2], p_pre_comp[j / 2][0],
-                     p_pre_comp[j / 2][1], p_pre_comp[j / 2][2]);
+        p224_point_double(p_pre_comp[j][0], p_pre_comp[j][1], p_pre_comp[j][2],
+                          p_pre_comp[j / 2][0], p_pre_comp[j / 2][1],
+                          p_pre_comp[j / 2][2]);
       }
     }
   }
@@ -1100,6 +1115,8 @@
   out->group_set_curve = ec_GFp_simple_group_set_curve;
   out->point_get_affine_coordinates =
       ec_GFp_nistp224_point_get_affine_coordinates;
+  out->add = ec_GFp_nistp224_add;
+  out->dbl = ec_GFp_nistp224_dbl;
   out->mul = ec_GFp_nistp224_points_mul;
   out->mul_public = ec_GFp_nistp224_points_mul;
   out->felem_mul = ec_GFp_nistp224_felem_mul;
@@ -1107,6 +1124,8 @@
   out->bignum_to_felem = ec_GFp_nistp224_bignum_to_felem;
   out->felem_to_bignum = ec_GFp_nistp224_felem_to_bignum;
   out->scalar_inv_montgomery = ec_simple_scalar_inv_montgomery;
+  out->scalar_inv_montgomery_vartime = ec_GFp_simple_mont_inv_mod_ord_vartime;
+  out->cmp_x_coordinate = ec_GFp_simple_cmp_x_coordinate;
 };
 
 #endif  // BORINGSSL_HAS_UINT128 && !SMALL
diff --git a/src/crypto/fipsmodule/ec/p256-x86_64.c b/src/crypto/fipsmodule/ec/p256-x86_64.c
index a4f6515..e7f4909 100644
--- a/src/crypto/fipsmodule/ec/p256-x86_64.c
+++ b/src/crypto/fipsmodule/ec/p256-x86_64.c
@@ -44,6 +44,12 @@
     TOBN(0xffffffff, 0xffffffff), TOBN(0x00000000, 0xfffffffe),
 };
 
+// P256_ORDER is the order of the P-256 group, not in Montgomery form.
+static const BN_ULONG P256_ORDER[P256_LIMBS] = {
+    TOBN(0xf3b9cac2, 0xfc632551), TOBN(0xbce6faad, 0xa7179e84),
+    TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0x00000000),
+};
+
 // Precomputed tables for the default generator
 #include "p256-x86_64-table.h"
 
@@ -289,19 +295,63 @@
   ecp_nistz256_point_add(r, r, &h);
 }
 
+typedef union {
+  P256_POINT p;
+  P256_POINT_AFFINE a;
+} p256_point_union_t;
+
+static unsigned calc_first_wvalue(unsigned *index, const uint8_t p_str[33]) {
+  static const unsigned kWindowSize = 7;
+  static const unsigned kMask = (1 << (7 /* kWindowSize */ + 1)) - 1;
+  *index = kWindowSize;
+
+  unsigned wvalue = (p_str[0] << 1) & kMask;
+  return booth_recode_w7(wvalue);
+}
+
+static unsigned calc_wvalue(unsigned *index, const uint8_t p_str[33]) {
+  static const unsigned kWindowSize = 7;
+  static const unsigned kMask = (1 << (7 /* kWindowSize */ + 1)) - 1;
+
+  const unsigned off = (*index - 1) / 8;
+  unsigned wvalue = p_str[off] | p_str[off + 1] << 8;
+  wvalue = (wvalue >> ((*index - 1) % 8)) & kMask;
+  *index += kWindowSize;
+
+  return booth_recode_w7(wvalue);
+}
+
+static void mul_p_add_and_store(const EC_GROUP *group, EC_RAW_POINT *r,
+                                const EC_SCALAR *g_scalar,
+                                const EC_RAW_POINT *p_,
+                                const EC_SCALAR *p_scalar,
+                                p256_point_union_t *t, p256_point_union_t *p) {
+  const int p_is_infinity = g_scalar == NULL;
+  if (p_scalar != NULL) {
+    P256_POINT *out = &t->p;
+    if (p_is_infinity) {
+      out = &p->p;
+    }
+
+    ecp_nistz256_windowed_mul(group, out, p_, p_scalar);
+    if (!p_is_infinity) {
+      ecp_nistz256_point_add(&p->p, &p->p, out);
+    }
+  }
+
+  assert(group->field.width == P256_LIMBS);
+  OPENSSL_memcpy(r->X.words, p->p.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, p->p.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, p->p.Z, P256_LIMBS * sizeof(BN_ULONG));
+}
+
 static void ecp_nistz256_points_mul(const EC_GROUP *group, EC_RAW_POINT *r,
                                     const EC_SCALAR *g_scalar,
                                     const EC_RAW_POINT *p_,
                                     const EC_SCALAR *p_scalar) {
   assert((p_ != NULL) == (p_scalar != NULL));
 
-  static const unsigned kWindowSize = 7;
-  static const unsigned kMask = (1 << (7 /* kWindowSize */ + 1)) - 1;
-
-  alignas(32) union {
-    P256_POINT p;
-    P256_POINT_AFFINE a;
-  } t, p;
+  alignas(32) p256_point_union_t t, p;
 
   if (g_scalar != NULL) {
     uint8_t p_str[33];
@@ -309,10 +359,8 @@
     p_str[32] = 0;
 
     // First window
-    unsigned wvalue = (p_str[0] << 1) & kMask;
-    unsigned index = kWindowSize;
-
-    wvalue = booth_recode_w7(wvalue);
+    unsigned index = 0;
+    unsigned wvalue = calc_first_wvalue(&index, p_str);
 
     const PRECOMP256_ROW *const precomputed_table =
         (const PRECOMP256_ROW *)ecp_nistz256_precomputed;
@@ -328,12 +376,7 @@
     copy_conditional(p.p.Z, ONE, is_not_zero(wvalue >> 1));
 
     for (int i = 1; i < 37; i++) {
-      unsigned off = (index - 1) / 8;
-      wvalue = p_str[off] | p_str[off + 1] << 8;
-      wvalue = (wvalue >> ((index - 1) % 8)) & kMask;
-      index += kWindowSize;
-
-      wvalue = booth_recode_w7(wvalue);
+      wvalue = calc_wvalue(&index, p_str);
 
       ecp_nistz256_select_w7(&t.a, precomputed_table[i], wvalue >> 1);
 
@@ -344,28 +387,65 @@
     }
   }
 
-  const int p_is_infinity = g_scalar == NULL;
-  if (p_scalar != NULL) {
-    P256_POINT *out = &t.p;
-    if (p_is_infinity) {
-      out = &p.p;
-    }
+  mul_p_add_and_store(group, r, g_scalar, p_, p_scalar, &t, &p);
+}
 
-    ecp_nistz256_windowed_mul(group, out, p_, p_scalar);
-    if (!p_is_infinity) {
-      ecp_nistz256_point_add(&p.p, &p.p, out);
-    }
+static void ecp_nistz256_points_mul_public(const EC_GROUP *group,
+                                           EC_RAW_POINT *r,
+                                           const EC_SCALAR *g_scalar,
+                                           const EC_RAW_POINT *p_,
+                                           const EC_SCALAR *p_scalar) {
+  assert(p_ != NULL && p_scalar != NULL && g_scalar != NULL);
+
+  alignas(32) p256_point_union_t t, p;
+  uint8_t p_str[33];
+  OPENSSL_memcpy(p_str, g_scalar->bytes, 32);
+  p_str[32] = 0;
+
+  // First window
+  unsigned index = 0;
+  unsigned wvalue = calc_first_wvalue(&index, p_str);
+
+  const PRECOMP256_ROW *const precomputed_table =
+      (const PRECOMP256_ROW *)ecp_nistz256_precomputed;
+
+  // Convert |p| from affine to Jacobian coordinates. We set Z to zero if |p|
+  // is infinity and |ONE| otherwise. |p| was computed from the table, so it
+  // is infinity iff |wvalue >> 1| is zero.
+  if ((wvalue >> 1) != 0) {
+    OPENSSL_memcpy(&p.a, &precomputed_table[0][(wvalue >> 1) - 1], sizeof(p.a));
+    OPENSSL_memcpy(&p.p.Z, ONE, sizeof(p.p.Z));
+  } else {
+    OPENSSL_memset(&p.a, 0, sizeof(p.a));
+    OPENSSL_memset(p.p.Z, 0, sizeof(p.p.Z));
   }
 
-  assert(group->field.width == P256_LIMBS);
-  OPENSSL_memcpy(r->X.words, p.p.X, P256_LIMBS * sizeof(BN_ULONG));
-  OPENSSL_memcpy(r->Y.words, p.p.Y, P256_LIMBS * sizeof(BN_ULONG));
-  OPENSSL_memcpy(r->Z.words, p.p.Z, P256_LIMBS * sizeof(BN_ULONG));
+  if ((wvalue & 1) == 1) {
+    ecp_nistz256_neg(p.p.Y, p.p.Y);
+  }
+
+  for (int i = 1; i < 37; i++) {
+    wvalue = calc_wvalue(&index, p_str);
+
+    if ((wvalue >> 1) == 0) {
+      continue;
+    }
+
+    OPENSSL_memcpy(&t.a, &precomputed_table[i][(wvalue >> 1) - 1], sizeof(p.a));
+
+    if ((wvalue & 1) == 1) {
+      ecp_nistz256_neg(t.a.Y, t.a.Y);
+    }
+
+    ecp_nistz256_point_add_affine(&p.p, &p.p, &t.a);
+  }
+
+  mul_p_add_and_store(group, r, g_scalar, p_, p_scalar, &t, &p);
 }
 
 static int ecp_nistz256_get_affine(const EC_GROUP *group,
-                                   const EC_RAW_POINT *point, BIGNUM *x,
-                                   BIGNUM *y) {
+                                   const EC_RAW_POINT *point, EC_FELEM *x,
+                                   EC_FELEM *y) {
   if (ec_GFp_simple_is_at_infinity(group, point)) {
     OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
     return 0;
@@ -384,27 +464,44 @@
   ecp_nistz256_from_mont(z_inv2, z_inv2);
 
   if (x != NULL) {
-    BN_ULONG x_aff[P256_LIMBS];
-    ecp_nistz256_mul_mont(x_aff, z_inv2, point->X.words);
-    if (!bn_set_words(x, x_aff, P256_LIMBS)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
-      return 0;
-    }
+    ecp_nistz256_mul_mont(x->words, z_inv2, point->X.words);
   }
 
   if (y != NULL) {
-    BN_ULONG y_aff[P256_LIMBS];
     ecp_nistz256_mul_mont(z_inv3, z_inv3, z_inv2);
-    ecp_nistz256_mul_mont(y_aff, z_inv3, point->Y.words);
-    if (!bn_set_words(y, y_aff, P256_LIMBS)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
-      return 0;
-    }
+    ecp_nistz256_mul_mont(y->words, z_inv3, point->Y.words);
   }
 
   return 1;
 }
 
+static void ecp_nistz256_add(const EC_GROUP *group, EC_RAW_POINT *r,
+                             const EC_RAW_POINT *a_, const EC_RAW_POINT *b_) {
+  P256_POINT a, b;
+  OPENSSL_memcpy(a.X, a_->X.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(a.Y, a_->Y.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(a.Z, a_->Z.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(b.X, b_->X.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(b.Y, b_->Y.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(b.Z, b_->Z.words, P256_LIMBS * sizeof(BN_ULONG));
+  ecp_nistz256_point_add(&a, &a, &b);
+  OPENSSL_memcpy(r->X.words, a.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, a.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, a.Z, P256_LIMBS * sizeof(BN_ULONG));
+}
+
+static void ecp_nistz256_dbl(const EC_GROUP *group, EC_RAW_POINT *r,
+                             const EC_RAW_POINT *a_) {
+  P256_POINT a;
+  OPENSSL_memcpy(a.X, a_->X.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(a.Y, a_->Y.words, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(a.Z, a_->Z.words, P256_LIMBS * sizeof(BN_ULONG));
+  ecp_nistz256_point_double(&a, &a);
+  OPENSSL_memcpy(r->X.words, a.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, a.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, a.Z, P256_LIMBS * sizeof(BN_ULONG));
+}
+
 static void ecp_nistz256_inv_mod_ord(const EC_GROUP *group, EC_SCALAR *out,
                                      const EC_SCALAR *in) {
   // table[i] stores a power of |in| corresponding to the matching enum value.
@@ -486,18 +583,92 @@
   }
 }
 
+static int ecp_nistz256_mont_inv_mod_ord_vartime(const EC_GROUP *group,
+                                                 EC_SCALAR *out,
+                                                 const EC_SCALAR *in) {
+  if ((OPENSSL_ia32cap_P[1] & (1 << 28)) == 0) {
+    // No AVX support; fallback to generic code.
+    return ec_GFp_simple_mont_inv_mod_ord_vartime(group, out, in);
+  }
+
+  if (!beeu_mod_inverse_vartime(out->words, in->words, P256_ORDER)) {
+    return 0;
+  }
+
+  // The result should be returned in the Montgomery domain.
+  ec_scalar_to_montgomery(group, out, out);
+  return 1;
+}
+
+static int ecp_nistz256_cmp_x_coordinate(int *out_result, const EC_GROUP *group,
+                                         const EC_POINT *p, const BIGNUM *r,
+                                         BN_CTX *ctx) {
+  *out_result = 0;
+
+  if (ec_GFp_simple_is_at_infinity(group, &p->raw)) {
+    OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
+    return 0;
+  }
+
+  BN_ULONG r_words[P256_LIMBS];
+  if (!bn_copy_words(r_words, P256_LIMBS, r)) {
+    return 0;
+  }
+
+  // We wish to compare X/Z^2 with r. This is equivalent to comparing X with
+  // r*Z^2. Note that X and Z are represented in Montgomery form, while r is
+  // not.
+  BN_ULONG r_Z2[P256_LIMBS], Z2_mont[P256_LIMBS], X[P256_LIMBS];
+  ecp_nistz256_mul_mont(Z2_mont, p->raw.Z.words, p->raw.Z.words);
+  ecp_nistz256_mul_mont(r_Z2, r_words, Z2_mont);
+  ecp_nistz256_from_mont(X, p->raw.X.words);
+
+  if (OPENSSL_memcmp(r_Z2, X, sizeof(r_Z2)) == 0) {
+    *out_result = 1;
+    return 1;
+  }
+
+  // During signing the x coefficient is reduced modulo the group order.
+  // Therefore there is a small possibility, less than 1/2^128, that group_order
+  // < p.x < P. in that case we need not only to compare against |r| but also to
+  // compare against r+group_order.
+
+  // P_MINUS_ORDER is the difference between the field order (p) and the group
+  // order (N). This value is not in the Montgomery domain.
+  static const BN_ULONG P_MINUS_ORDER[P256_LIMBS] = {
+      TOBN(0x0c46353d, 0x039cdaae), TOBN(0x43190553, 0x58e8617b),
+      TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)};
+
+  if (bn_less_than_words(r_words, P_MINUS_ORDER, P256_LIMBS)) {
+    // We can add in-place, ignoring the carry, because: r + group_order < p <
+    // 2^256
+    bn_add_words(r_words, r_words, P256_ORDER, P256_LIMBS);
+    ecp_nistz256_mul_mont(r_Z2, r_words, Z2_mont);
+    if (OPENSSL_memcmp(r_Z2, X, sizeof(r_Z2)) == 0) {
+      *out_result = 1;
+      return 1;
+    }
+  }
+
+  return 1;
+}
+
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_nistz256_method) {
   out->group_init = ec_GFp_mont_group_init;
   out->group_finish = ec_GFp_mont_group_finish;
   out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates = ecp_nistz256_get_affine;
+  out->add = ecp_nistz256_add;
+  out->dbl = ecp_nistz256_dbl;
   out->mul = ecp_nistz256_points_mul;
-  out->mul_public = ecp_nistz256_points_mul;
+  out->mul_public = ecp_nistz256_points_mul_public;
   out->felem_mul = ec_GFp_mont_felem_mul;
   out->felem_sqr = ec_GFp_mont_felem_sqr;
   out->bignum_to_felem = ec_GFp_mont_bignum_to_felem;
   out->felem_to_bignum = ec_GFp_mont_felem_to_bignum;
   out->scalar_inv_montgomery = ecp_nistz256_inv_mod_ord;
+  out->scalar_inv_montgomery_vartime = ecp_nistz256_mont_inv_mod_ord_vartime;
+  out->cmp_x_coordinate = ecp_nistz256_cmp_x_coordinate;
 };
 
 #endif /* !defined(OPENSSL_NO_ASM) && defined(OPENSSL_X86_64) && \
diff --git a/src/crypto/fipsmodule/ec/p256-x86_64.h b/src/crypto/fipsmodule/ec/p256-x86_64.h
index 21b461c..9de3240 100644
--- a/src/crypto/fipsmodule/ec/p256-x86_64.h
+++ b/src/crypto/fipsmodule/ec/p256-x86_64.h
@@ -61,6 +61,16 @@
   ecp_nistz256_mul_mont(res, in, ONE);
 }
 
+// ecp_nistz256_to_mont sets |res| to |in|, converted to Montgomery domain
+// by multiplying with RR = 2^512 mod P precomputed for NIST P256 curve.
+static inline void ecp_nistz256_to_mont(BN_ULONG res[P256_LIMBS],
+                                        const BN_ULONG in[P256_LIMBS]) {
+  static const BN_ULONG RR[P256_LIMBS] = {
+      TOBN(0x00000000, 0x00000003), TOBN(0xfffffffb, 0xffffffff),
+      TOBN(0xffffffff, 0xfffffffe), TOBN(0x00000004, 0xfffffffd)};
+  ecp_nistz256_mul_mont(res, in, RR);
+}
+
 
 // P-256 scalar operations.
 //
@@ -79,6 +89,12 @@
 void ecp_nistz256_ord_sqr_mont(BN_ULONG res[P256_LIMBS],
                                const BN_ULONG a[P256_LIMBS], int rep);
 
+// beeu_mod_inverse_vartime sets out = a^-1 mod p using a Euclidean algorithm.
+// Assumption: 0 < a < p < 2^(256) and p is odd.
+int beeu_mod_inverse_vartime(BN_ULONG out[P256_LIMBS],
+                             const BN_ULONG a[P256_LIMBS],
+                             const BN_ULONG p[P256_LIMBS]);
+
 
 // P-256 point operations.
 //
diff --git a/src/crypto/fipsmodule/ec/p256-x86_64_test.cc b/src/crypto/fipsmodule/ec/p256-x86_64_test.cc
index 8ed1dd4..ab93dfb 100644
--- a/src/crypto/fipsmodule/ec/p256-x86_64_test.cc
+++ b/src/crypto/fipsmodule/ec/p256-x86_64_test.cc
@@ -24,8 +24,12 @@
 #include <gtest/gtest.h>
 
 #include <openssl/bn.h>
+#include <openssl/cpu.h>
+#include <openssl/ec.h>
 #include <openssl/mem.h>
+#include <openssl/nid.h>
 
+#include "internal.h"
 #include "../bn/internal.h"
 #include "../../internal.h"
 #include "../../test/file_test.h"
@@ -87,6 +91,64 @@
   }
 }
 
+TEST(P256_X86_64Test, BEEU) {
+  if ((OPENSSL_ia32cap_P[1] & (1 << 28)) == 0) {
+    // No AVX support; cannot run the BEEU code.
+    return;
+  }
+
+  bssl::UniquePtr<EC_GROUP> group(
+      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+  ASSERT_TRUE(group);
+
+  BN_ULONG order_words[P256_LIMBS];
+  ASSERT_TRUE(
+      bn_copy_words(order_words, P256_LIMBS, EC_GROUP_get0_order(group.get())));
+
+  BN_ULONG in[P256_LIMBS], out[P256_LIMBS];
+  EC_SCALAR in_scalar, out_scalar, result;
+  OPENSSL_memset(in, 0, sizeof(in));
+
+  // Trying to find the inverse of zero should fail.
+  ASSERT_FALSE(beeu_mod_inverse_vartime(out, in, order_words));
+
+  // kOneMont is 1, in Montgomery form.
+  static const BN_ULONG kOneMont[P256_LIMBS] = {
+      TOBN(0xc46353d, 0x039cdaaf), TOBN(0x43190552, 0x58e8617b),
+      0, 0xffffffff,
+  };
+
+  for (BN_ULONG i = 1; i < 2000; i++) {
+    SCOPED_TRACE(i);
+
+    in[0] = i;
+    if (i >= 1000) {
+      in[1] = i << 8;
+      in[2] = i << 32;
+      in[3] = i << 48;
+    } else {
+      in[1] = in[2] = in[3] = 0;
+    }
+
+    EXPECT_TRUE(bn_less_than_words(in, order_words, P256_LIMBS));
+    ASSERT_TRUE(beeu_mod_inverse_vartime(out, in, order_words));
+    EXPECT_TRUE(bn_less_than_words(out, order_words, P256_LIMBS));
+
+    // Calculate out*in and confirm that it equals one, modulo the order.
+    OPENSSL_memcpy(in_scalar.bytes, in, sizeof(in));
+    OPENSSL_memcpy(out_scalar.bytes, out, sizeof(out));
+    ec_scalar_to_montgomery(group.get(), &in_scalar, &in_scalar);
+    ec_scalar_to_montgomery(group.get(), &out_scalar, &out_scalar);
+    ec_scalar_mul_montgomery(group.get(), &result, &in_scalar, &out_scalar);
+
+    EXPECT_EQ(0, OPENSSL_memcmp(kOneMont, &result, sizeof(kOneMont)));
+
+    // Invert the result and expect to get back to the original value.
+    ASSERT_TRUE(beeu_mod_inverse_vartime(out, out, order_words));
+    EXPECT_EQ(0, OPENSSL_memcmp(in, out, sizeof(in)));
+  }
+}
+
 static bool GetFieldElement(FileTest *t, BN_ULONG out[P256_LIMBS],
                             const char *name) {
   std::vector<uint8_t> bytes;
diff --git a/src/crypto/fipsmodule/ec/scalar.c b/src/crypto/fipsmodule/ec/scalar.c
index 1bd6b02..88678a9 100644
--- a/src/crypto/fipsmodule/ec/scalar.c
+++ b/src/crypto/fipsmodule/ec/scalar.c
@@ -74,3 +74,8 @@
                               const EC_SCALAR *a) {
   group->meth->scalar_inv_montgomery(group, r, a);
 }
+
+int ec_scalar_inv_montgomery_vartime(const EC_GROUP *group, EC_SCALAR *r,
+                                     const EC_SCALAR *a) {
+  return group->meth->scalar_inv_montgomery_vartime(group, r, a);
+}
diff --git a/src/crypto/fipsmodule/ec/simple.c b/src/crypto/fipsmodule/ec/simple.c
index 5c63711..8b862ff 100644
--- a/src/crypto/fipsmodule/ec/simple.c
+++ b/src/crypto/fipsmodule/ec/simple.c
@@ -171,10 +171,6 @@
   return 1;
 }
 
-unsigned ec_GFp_simple_group_get_degree(const EC_GROUP *group) {
-  return BN_num_bits(&group->field);
-}
-
 void ec_GFp_simple_point_init(EC_RAW_POINT *point) {
   OPENSSL_memset(&point->X, 0, sizeof(EC_FELEM));
   OPENSSL_memset(&point->Y, 0, sizeof(EC_FELEM));
@@ -212,222 +208,6 @@
   return 1;
 }
 
-void ec_GFp_simple_add(const EC_GROUP *group, EC_RAW_POINT *out,
-                       const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
-  if (a == b) {
-    ec_GFp_simple_dbl(group, out, a);
-    return;
-  }
-
-
-  // The method is taken from:
-  //   http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl
-  //
-  // Coq transcription and correctness proof:
-  // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L467>
-  // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L544>
-  void (*const felem_mul)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a,
-                          const EC_FELEM *b) = group->meth->felem_mul;
-  void (*const felem_sqr)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a) =
-      group->meth->felem_sqr;
-
-  EC_FELEM x_out, y_out, z_out;
-  BN_ULONG z1nz = ec_felem_non_zero_mask(group, &a->Z);
-  BN_ULONG z2nz = ec_felem_non_zero_mask(group, &b->Z);
-
-  // z1z1 = z1z1 = z1**2
-  EC_FELEM z1z1;
-  felem_sqr(group, &z1z1, &a->Z);
-
-  // z2z2 = z2**2
-  EC_FELEM z2z2;
-  felem_sqr(group, &z2z2, &b->Z);
-
-  // u1 = x1*z2z2
-  EC_FELEM u1;
-  felem_mul(group, &u1, &a->X, &z2z2);
-
-  // two_z1z2 = (z1 + z2)**2 - (z1z1 + z2z2) = 2z1z2
-  EC_FELEM two_z1z2;
-  ec_felem_add(group, &two_z1z2, &a->Z, &b->Z);
-  felem_sqr(group, &two_z1z2, &two_z1z2);
-  ec_felem_sub(group, &two_z1z2, &two_z1z2, &z1z1);
-  ec_felem_sub(group, &two_z1z2, &two_z1z2, &z2z2);
-
-  // s1 = y1 * z2**3
-  EC_FELEM s1;
-  felem_mul(group, &s1, &b->Z, &z2z2);
-  felem_mul(group, &s1, &s1, &a->Y);
-
-  // u2 = x2*z1z1
-  EC_FELEM u2;
-  felem_mul(group, &u2, &b->X, &z1z1);
-
-  // h = u2 - u1
-  EC_FELEM h;
-  ec_felem_sub(group, &h, &u2, &u1);
-
-  BN_ULONG xneq = ec_felem_non_zero_mask(group, &h);
-
-  // z_out = two_z1z2 * h
-  felem_mul(group, &z_out, &h, &two_z1z2);
-
-  // z1z1z1 = z1 * z1z1
-  EC_FELEM z1z1z1;
-  felem_mul(group, &z1z1z1, &a->Z, &z1z1);
-
-  // s2 = y2 * z1**3
-  EC_FELEM s2;
-  felem_mul(group, &s2, &b->Y, &z1z1z1);
-
-  // r = (s2 - s1)*2
-  EC_FELEM r;
-  ec_felem_sub(group, &r, &s2, &s1);
-  ec_felem_add(group, &r, &r, &r);
-
-  BN_ULONG yneq = ec_felem_non_zero_mask(group, &r);
-
-  // This case will never occur in the constant-time |ec_GFp_simple_mul|.
-  if (!xneq && !yneq && z1nz && z2nz) {
-    ec_GFp_simple_dbl(group, out, a);
-    return;
-  }
-
-  // I = (2h)**2
-  EC_FELEM i;
-  ec_felem_add(group, &i, &h, &h);
-  felem_sqr(group, &i, &i);
-
-  // J = h * I
-  EC_FELEM j;
-  felem_mul(group, &j, &h, &i);
-
-  // V = U1 * I
-  EC_FELEM v;
-  felem_mul(group, &v, &u1, &i);
-
-  // x_out = r**2 - J - 2V
-  felem_sqr(group, &x_out, &r);
-  ec_felem_sub(group, &x_out, &x_out, &j);
-  ec_felem_sub(group, &x_out, &x_out, &v);
-  ec_felem_sub(group, &x_out, &x_out, &v);
-
-  // y_out = r(V-x_out) - 2 * s1 * J
-  ec_felem_sub(group, &y_out, &v, &x_out);
-  felem_mul(group, &y_out, &y_out, &r);
-  EC_FELEM s1j;
-  felem_mul(group, &s1j, &s1, &j);
-  ec_felem_sub(group, &y_out, &y_out, &s1j);
-  ec_felem_sub(group, &y_out, &y_out, &s1j);
-
-  ec_felem_select(group, &x_out, z1nz, &x_out, &b->X);
-  ec_felem_select(group, &out->X, z2nz, &x_out, &a->X);
-  ec_felem_select(group, &y_out, z1nz, &y_out, &b->Y);
-  ec_felem_select(group, &out->Y, z2nz, &y_out, &a->Y);
-  ec_felem_select(group, &z_out, z1nz, &z_out, &b->Z);
-  ec_felem_select(group, &out->Z, z2nz, &z_out, &a->Z);
-}
-
-void ec_GFp_simple_dbl(const EC_GROUP *group, EC_RAW_POINT *r,
-                       const EC_RAW_POINT *a) {
-  void (*const felem_mul)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a,
-                          const EC_FELEM *b) = group->meth->felem_mul;
-  void (*const felem_sqr)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a) =
-      group->meth->felem_sqr;
-
-  if (group->a_is_minus3) {
-    // The method is taken from:
-    //   http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
-    //
-    // Coq transcription and correctness proof:
-    // <https://github.com/mit-plv/fiat-crypto/blob/79f8b5f39ed609339f0233098dee1a3c4e6b3080/src/Curves/Weierstrass/Jacobian.v#L93>
-    // <https://github.com/mit-plv/fiat-crypto/blob/79f8b5f39ed609339f0233098dee1a3c4e6b3080/src/Curves/Weierstrass/Jacobian.v#L201>
-    EC_FELEM delta, gamma, beta, ftmp, ftmp2, tmptmp, alpha, fourbeta;
-    // delta = z^2
-    felem_sqr(group, &delta, &a->Z);
-    // gamma = y^2
-    felem_sqr(group, &gamma, &a->Y);
-    // beta = x*gamma
-    felem_mul(group, &beta, &a->X, &gamma);
-
-    // alpha = 3*(x-delta)*(x+delta)
-    ec_felem_sub(group, &ftmp, &a->X, &delta);
-    ec_felem_add(group, &ftmp2, &a->X, &delta);
-
-    ec_felem_add(group, &tmptmp, &ftmp2, &ftmp2);
-    ec_felem_add(group, &ftmp2, &ftmp2, &tmptmp);
-    felem_mul(group, &alpha, &ftmp, &ftmp2);
-
-    // x' = alpha^2 - 8*beta
-    felem_sqr(group, &r->X, &alpha);
-    ec_felem_add(group, &fourbeta, &beta, &beta);
-    ec_felem_add(group, &fourbeta, &fourbeta, &fourbeta);
-    ec_felem_add(group, &tmptmp, &fourbeta, &fourbeta);
-    ec_felem_sub(group, &r->X, &r->X, &tmptmp);
-
-    // z' = (y + z)^2 - gamma - delta
-    ec_felem_add(group, &delta, &gamma, &delta);
-    ec_felem_add(group, &ftmp, &a->Y, &a->Z);
-    felem_sqr(group, &r->Z, &ftmp);
-    ec_felem_sub(group, &r->Z, &r->Z, &delta);
-
-    // y' = alpha*(4*beta - x') - 8*gamma^2
-    ec_felem_sub(group, &r->Y, &fourbeta, &r->X);
-    ec_felem_add(group, &gamma, &gamma, &gamma);
-    felem_sqr(group, &gamma, &gamma);
-    felem_mul(group, &r->Y, &alpha, &r->Y);
-    ec_felem_add(group, &gamma, &gamma, &gamma);
-    ec_felem_sub(group, &r->Y, &r->Y, &gamma);
-  } else {
-    // The method is taken from:
-    //   http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl
-    //
-    // Coq transcription and correctness proof:
-    // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L102>
-    // <https://github.com/davidben/fiat-crypto/blob/c7b95f62b2a54b559522573310e9b487327d219a/src/Curves/Weierstrass/Jacobian.v#L534>
-    EC_FELEM xx, yy, yyyy, zz;
-    felem_sqr(group, &xx, &a->X);
-    felem_sqr(group, &yy, &a->Y);
-    felem_sqr(group, &yyyy, &yy);
-    felem_sqr(group, &zz, &a->Z);
-
-    // s = 2*((x_in + yy)^2 - xx - yyyy)
-    EC_FELEM s;
-    ec_felem_add(group, &s, &a->X, &yy);
-    felem_sqr(group, &s, &s);
-    ec_felem_sub(group, &s, &s, &xx);
-    ec_felem_sub(group, &s, &s, &yyyy);
-    ec_felem_add(group, &s, &s, &s);
-
-    // m = 3*xx + a*zz^2
-    EC_FELEM m;
-    felem_sqr(group, &m, &zz);
-    felem_mul(group, &m, &group->a, &m);
-    ec_felem_add(group, &m, &m, &xx);
-    ec_felem_add(group, &m, &m, &xx);
-    ec_felem_add(group, &m, &m, &xx);
-
-    // x_out = m^2 - 2*s
-    felem_sqr(group, &r->X, &m);
-    ec_felem_sub(group, &r->X, &r->X, &s);
-    ec_felem_sub(group, &r->X, &r->X, &s);
-
-    // z_out = (y_in + z_in)^2 - yy - zz
-    ec_felem_add(group, &r->Z, &a->Y, &a->Z);
-    felem_sqr(group, &r->Z, &r->Z);
-    ec_felem_sub(group, &r->Z, &r->Z, &yy);
-    ec_felem_sub(group, &r->Z, &r->Z, &zz);
-
-    // y_out = m*(s-x_out) - 8*yyyy
-    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
-    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
-    ec_felem_add(group, &yyyy, &yyyy, &yyyy);
-    ec_felem_sub(group, &r->Y, &s, &r->X);
-    felem_mul(group, &r->Y, &r->Y, &m);
-    ec_felem_sub(group, &r->Y, &r->Y, &yyyy);
-  }
-}
-
 void ec_GFp_simple_invert(const EC_GROUP *group, EC_RAW_POINT *point) {
   ec_felem_neg(group, &point->Y, &point->Y);
 }
@@ -570,3 +350,52 @@
   // The points are equal.
   return 0;
 }
+
+int ec_GFp_simple_mont_inv_mod_ord_vartime(const EC_GROUP *group,
+                                           EC_SCALAR *out,
+                                           const EC_SCALAR *in) {
+  // This implementation (in fact) runs in constant time,
+  // even though for this interface it is not mandatory.
+
+  // out = in^-1 in the Montgomery domain. This is
+  // |ec_scalar_to_montgomery| followed by |ec_scalar_inv_montgomery|, but
+  // |ec_scalar_inv_montgomery| followed by |ec_scalar_from_montgomery| is
+  // equivalent and slightly more efficient.
+  ec_scalar_inv_montgomery(group, out, in);
+  ec_scalar_from_montgomery(group, out, out);
+  return 1;
+}
+
+// Compares the x (affine) coordinate of the point p with x.
+// Return 1 on success 0 otherwise
+int ec_GFp_simple_cmp_x_coordinate(int *out_result, const EC_GROUP *group,
+                                   const EC_POINT *p, const BIGNUM *r,
+                                   BN_CTX *ctx) {
+  *out_result = 0;
+  int ret = 0;
+  BN_CTX_start(ctx);
+
+  BIGNUM *X = BN_CTX_get(ctx);
+  if (X == NULL) {
+    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+    goto out;
+  }
+
+  if (!EC_POINT_get_affine_coordinates_GFp(group, p, X, NULL, ctx)) {
+    OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
+    goto out;
+  }
+
+  if (!ec_field_element_to_scalar(group, X)) {
+    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+    goto out;
+  }
+
+  // The signature is correct iff |X| is equal to |r|.
+  *out_result = (BN_ucmp(X, r) == 0);
+  ret = 1;
+
+out:
+  BN_CTX_end(ctx);
+  return ret;
+}
diff --git a/src/crypto/fipsmodule/ec/simple_mul.c b/src/crypto/fipsmodule/ec/simple_mul.c
index 93ed0a8..e05f491 100644
--- a/src/crypto/fipsmodule/ec/simple_mul.c
+++ b/src/crypto/fipsmodule/ec/simple_mul.c
@@ -21,12 +21,12 @@
 #include "../../internal.h"
 
 
-static void ec_GFp_simple_mul_single(const EC_GROUP *group, EC_RAW_POINT *r,
-                                     const EC_RAW_POINT *p,
-                                     const EC_SCALAR *scalar) {
+static void ec_GFp_mont_mul_single(const EC_GROUP *group, EC_RAW_POINT *r,
+                                   const EC_RAW_POINT *p,
+                                   const EC_SCALAR *scalar) {
   // This is a generic implementation for uncommon curves that not do not
   // warrant a tuned one. It uses unsigned digits so that the doubling case in
-  // |ec_GFp_simple_add| is always unreachable, erring on safety and simplicity.
+  // |ec_GFp_mont_add| is always unreachable, erring on safety and simplicity.
 
   // Compute a table of the first 32 multiples of |p| (including infinity).
   EC_RAW_POINT precomp[32];
@@ -34,9 +34,9 @@
   ec_GFp_simple_point_copy(&precomp[1], p);
   for (size_t j = 2; j < OPENSSL_ARRAY_SIZE(precomp); j++) {
     if (j & 1) {
-      ec_GFp_simple_add(group, &precomp[j], &precomp[1], &precomp[j - 1]);
+      ec_GFp_mont_add(group, &precomp[j], &precomp[1], &precomp[j - 1]);
     } else {
-      ec_GFp_simple_dbl(group, &precomp[j], &precomp[j / 2]);
+      ec_GFp_mont_dbl(group, &precomp[j], &precomp[j / 2]);
     }
   }
 
@@ -45,7 +45,7 @@
   int r_is_at_infinity = 1;
   for (unsigned i = bits - 1; i < bits; i--) {
     if (!r_is_at_infinity) {
-      ec_GFp_simple_dbl(group, r, r);
+      ec_GFp_mont_dbl(group, r, r);
     }
     if (i % 5 == 0) {
       // Compute the next window value.
@@ -70,7 +70,7 @@
         ec_GFp_simple_point_copy(r, &tmp);
         r_is_at_infinity = 0;
       } else {
-        ec_GFp_simple_add(group, r, r, &tmp);
+        ec_GFp_mont_add(group, r, r, &tmp);
       }
     }
   }
@@ -79,21 +79,21 @@
   }
 }
 
-void ec_GFp_simple_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                       const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                       const EC_SCALAR *p_scalar) {
+void ec_GFp_mont_mul(const EC_GROUP *group, EC_RAW_POINT *r,
+                     const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
+                     const EC_SCALAR *p_scalar) {
   assert(g_scalar != NULL || p_scalar != NULL);
   if (p_scalar == NULL) {
-    ec_GFp_simple_mul_single(group, r, &group->generator->raw, g_scalar);
+    ec_GFp_mont_mul_single(group, r, &group->generator->raw, g_scalar);
   } else if (g_scalar == NULL) {
-    ec_GFp_simple_mul_single(group, r, p, p_scalar);
+    ec_GFp_mont_mul_single(group, r, p, p_scalar);
   } else {
     // Support constant-time two-point multiplication for compatibility.  This
     // does not actually come up in keygen, ECDH, or ECDSA, so we implement it
     // the naive way.
-    ec_GFp_simple_mul_single(group, r, &group->generator->raw, g_scalar);
+    ec_GFp_mont_mul_single(group, r, &group->generator->raw, g_scalar);
     EC_RAW_POINT tmp;
-    ec_GFp_simple_mul_single(group, &tmp, p, p_scalar);
-    ec_GFp_simple_add(group, r, r, &tmp);
+    ec_GFp_mont_mul_single(group, &tmp, p, p_scalar);
+    ec_GFp_mont_add(group, r, r, &tmp);
   }
 }
diff --git a/src/crypto/fipsmodule/ec/wnaf.c b/src/crypto/fipsmodule/ec/wnaf.c
index 145caa0..c0c2809 100644
--- a/src/crypto/fipsmodule/ec/wnaf.c
+++ b/src/crypto/fipsmodule/ec/wnaf.c
@@ -151,9 +151,9 @@
                             const EC_RAW_POINT *p, size_t len) {
   ec_GFp_simple_point_copy(&out[0], p);
   EC_RAW_POINT two_p;
-  ec_GFp_simple_dbl(group, &two_p, p);
+  ec_GFp_mont_dbl(group, &two_p, p);
   for (size_t i = 1; i < len; i++) {
-    ec_GFp_simple_add(group, &out[i], &out[i - 1], &two_p);
+    ec_GFp_mont_add(group, &out[i], &out[i - 1], &two_p);
   }
 }
 
@@ -168,15 +168,15 @@
   }
 }
 
-// EC_WNAF_WINDOW_BITS is the window size to use for |ec_GFp_simple_mul_public|.
+// EC_WNAF_WINDOW_BITS is the window size to use for |ec_GFp_mont_mul_public|.
 #define EC_WNAF_WINDOW_BITS 4
 
-// EC_WNAF_TABLE_SIZE is the table size to use for |ec_GFp_simple_mul_public|.
+// EC_WNAF_TABLE_SIZE is the table size to use for |ec_GFp_mont_mul_public|.
 #define EC_WNAF_TABLE_SIZE (1 << (EC_WNAF_WINDOW_BITS - 1))
 
-void ec_GFp_simple_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
-                              const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                              const EC_SCALAR *p_scalar) {
+void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
+                            const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
+                            const EC_SCALAR *p_scalar) {
   size_t bits = BN_num_bits(&group->order);
   size_t wNAF_len = bits + 1;
 
@@ -197,7 +197,7 @@
   int r_is_at_infinity = 1;
   for (size_t k = wNAF_len - 1; k < wNAF_len; k--) {
     if (!r_is_at_infinity) {
-      ec_GFp_simple_dbl(group, r, r);
+      ec_GFp_mont_dbl(group, r, r);
     }
 
     if (g_wNAF[k] != 0) {
@@ -206,7 +206,7 @@
         ec_GFp_simple_point_copy(r, &tmp);
         r_is_at_infinity = 0;
       } else {
-        ec_GFp_simple_add(group, r, r, &tmp);
+        ec_GFp_mont_add(group, r, r, &tmp);
       }
     }
 
@@ -216,7 +216,7 @@
         ec_GFp_simple_point_copy(r, &tmp);
         r_is_at_infinity = 0;
       } else {
-        ec_GFp_simple_add(group, r, r, &tmp);
+        ec_GFp_mont_add(group, r, r, &tmp);
       }
     }
   }
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa.c b/src/crypto/fipsmodule/ecdsa/ecdsa.c
index f3ce214..80371c3 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa.c
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa.c
@@ -98,40 +98,6 @@
                           order->width);
 }
 
-// field_element_to_scalar reduces |r| modulo |group->order|. |r| must
-// previously have been reduced modulo |group->field|.
-static int field_element_to_scalar(const EC_GROUP *group, BIGNUM *r) {
-  // We must have p < 2×order, assuming p is not tiny (p >= 17). Thus rather we
-  // can reduce by performing at most one subtraction.
-  //
-  // Proof: We only work with prime order curves, so the number of points on
-  // the curve is the order. Thus Hasse's theorem gives:
-  //
-  //     |order - (p + 1)| <= 2×sqrt(p)
-  //         p + 1 - order <= 2×sqrt(p)
-  //     p + 1 - 2×sqrt(p) <= order
-  //       p + 1 - 2×(p/4)  < order       (p/4 > sqrt(p) for p >= 17)
-  //         p/2 < p/2 + 1  < order
-  //                     p  < 2×order
-  //
-  // Additionally, one can manually check this property for built-in curves. It
-  // is enforced for legacy custom curves in |EC_GROUP_set_generator|.
-  //
-  // TODO(davidben): Introduce |EC_FIELD_ELEMENT|, make this a function from
-  // |EC_FIELD_ELEMENT| to |EC_SCALAR|, and cut out the |BIGNUM|. Does this need
-  // to be constant-time for signing? |r| is the x-coordinate for kG, which is
-  // public unless k was rerolled because |s| was zero.
-  assert(!BN_is_negative(r));
-  assert(BN_cmp(r, &group->field) < 0);
-  if (BN_cmp(r, &group->order) >= 0 &&
-      !BN_sub(r, r, &group->order)) {
-    return 0;
-  }
-  assert(!BN_is_negative(r));
-  assert(BN_cmp(r, &group->order) < 0);
-  return 1;
-}
-
 ECDSA_SIG *ECDSA_SIG_new(void) {
   ECDSA_SIG *sig = OPENSSL_malloc(sizeof(ECDSA_SIG));
   if (sig == NULL) {
@@ -193,12 +159,6 @@
   }
   int ret = 0;
   EC_POINT *point = NULL;
-  BN_CTX_start(ctx);
-  BIGNUM *X = BN_CTX_get(ctx);
-  if (X == NULL) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-    goto err;
-  }
 
   EC_SCALAR r, s, u1, u2, s_inv_mont, m;
   if (BN_is_zero(sig->r) ||
@@ -210,11 +170,7 @@
   }
 
   // s_inv_mont = s^-1 in the Montgomery domain. This is
-  // |ec_scalar_to_montgomery| followed by |ec_scalar_inv_montgomery|, but
-  // |ec_scalar_inv_montgomery| followed by |ec_scalar_from_montgomery| is
-  // equivalent and slightly more efficient.
-  ec_scalar_inv_montgomery(group, &s_inv_mont, &s);
-  ec_scalar_from_montgomery(group, &s_inv_mont, &s_inv_mont);
+  ec_scalar_inv_montgomery_vartime(group, &s_inv_mont, &s);
 
   // u1 = m * s^-1 mod order
   // u2 = r * s^-1 mod order
@@ -234,16 +190,14 @@
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
     goto err;
   }
-  if (!EC_POINT_get_affine_coordinates_GFp(group, point, X, NULL, ctx)) {
+
+  int match;
+  if (!ec_cmp_x_coordinate(&match, group, point, sig->r, ctx)) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
     goto err;
   }
-  if (!field_element_to_scalar(group, X)) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-    goto err;
-  }
-  // The signature is correct iff |X| is equal to |sig->r|.
-  if (BN_ucmp(X, sig->r) != 0) {
+
+  if (!match) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
     goto err;
   }
@@ -251,7 +205,6 @@
   ret = 1;
 
 err:
-  BN_CTX_end(ctx);
   BN_CTX_free(ctx);
   EC_POINT_free(point);
   return ret;
@@ -320,7 +273,7 @@
       goto err;
     }
 
-    if (!field_element_to_scalar(group, r)) {
+    if (!ec_field_element_to_scalar(group, r)) {
       goto err;
     }
   } while (BN_is_zero(r));
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc b/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
index 258c128..4c95df9 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
@@ -68,6 +68,45 @@
 #include "../../test/file_test.h"
 
 
+static bssl::UniquePtr<BIGNUM> HexToBIGNUM(const char *hex) {
+  BIGNUM *bn = nullptr;
+  BN_hex2bn(&bn, hex);
+  return bssl::UniquePtr<BIGNUM>(bn);
+}
+
+// Though we do not support secp160r1, it is reachable from the deprecated
+// custom curve APIs and has some unique properties (n is larger than p with the
+// difference crossing a word boundary on 32-bit), so test it explicitly.
+static bssl::UniquePtr<EC_GROUP> NewSecp160r1Group() {
+  static const char kP[] = "ffffffffffffffffffffffffffffffff7fffffff";
+  static const char kA[] = "ffffffffffffffffffffffffffffffff7ffffffc";
+  static const char kB[] = "1c97befc54bd7a8b65acf89f81d4d4adc565fa45";
+  static const char kX[] = "4a96b5688ef573284664698968c38bb913cbfc82";
+  static const char kY[] = "23a628553168947d59dcc912042351377ac5fb32";
+  static const char kN[] = "0100000000000000000001f4c8f927aed3ca752257";
+
+  bssl::UniquePtr<BIGNUM> p = HexToBIGNUM(kP), a = HexToBIGNUM(kA),
+                          b = HexToBIGNUM(kB), x = HexToBIGNUM(kX),
+                          y = HexToBIGNUM(kY), n = HexToBIGNUM(kN);
+  if (!p || !a || !b || !x || !y || !n) {
+    return nullptr;
+  }
+
+  bssl::UniquePtr<EC_GROUP> group(
+      EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), nullptr));
+  if (!group) {
+    return nullptr;
+  }
+  bssl::UniquePtr<EC_POINT> g(EC_POINT_new(group.get()));
+  if (!g ||
+      !EC_POINT_set_affine_coordinates_GFp(group.get(), g.get(), x.get(),
+                                           y.get(), nullptr) ||
+      !EC_GROUP_set_generator(group.get(), g.get(), n.get(), BN_value_one())) {
+    return nullptr;
+  }
+  return group;
+}
+
 enum API {
   kEncodedAPI,
   kRawAPI,
@@ -151,13 +190,18 @@
       { NID_X9_62_prime256v1, "secp256r1" },
       { NID_secp384r1, "secp384r1" },
       { NID_secp521r1, "secp521r1" },
+      { NID_secp160r1, "secp160r1" },
   };
 
   for (const auto &curve : kCurves) {
     SCOPED_TRACE(curve.name);
 
-    int nid = curve.nid;
-    bssl::UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(nid));
+    bssl::UniquePtr<EC_GROUP> group;
+    if (curve.nid == NID_secp160r1) {
+      group = NewSecp160r1Group();
+    } else {
+      group.reset(EC_GROUP_new_by_curve_name(curve.nid));
+    }
     ASSERT_TRUE(group);
     const BIGNUM *order = EC_GROUP_get0_order(group.get());
 
@@ -278,6 +322,9 @@
   if (curve_name == "P-521") {
     return bssl::UniquePtr<EC_GROUP>(EC_GROUP_new_by_curve_name(NID_secp521r1));
   }
+  if (curve_name == "secp160r1") {
+    return NewSecp160r1Group();
+  }
 
   ADD_FAILURE() << "Unknown curve: " << curve_name;
   return nullptr;
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa_verify_tests.txt b/src/crypto/fipsmodule/ecdsa/ecdsa_verify_tests.txt
index aa2fbd3..03975c8 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa_verify_tests.txt
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa_verify_tests.txt
@@ -2431,3 +2431,346 @@
 Digest = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 R = 00ec0b91fa4386a8acdc0e46dd9c1d1775abbe0da8ead424aa4ace58e284a5be00e2c1ef95b6f4d861615564e1e7305656567f95275ce63b534420eae77ec37492c2
 S = 01e1099fb389db498ab4cf23b4f06a74b9326878ae3c76ea13832e50702b30fe8303093a59cc9a0995f1dfc15e6f7dabca8a2acaf03ec005447d29fb429a252064ec
+
+
+# The following tests are intended to stress the final comparison in ECDSA.
+# ECDSA verification computes some curve point (x, y), picking the fully-reduced
+# representive of x mod p, and checking that x mod n is r. (n is the order of
+# the group and p defines the underlying prime field.)
+#
+# This makes the computation sensitive to values near n and p, and which of n or
+# p is larger. Additionally, there is an optimization that performs the
+# comparison mod p rather than n and compensates for the difference.
+#
+# These tests were generated by picking a target value of r and x, adjusting
+# both until x corresponded to a point on the curve, and then computing the
+# public key by solving for P in ECDSA's (x, y) = u1*G + u2*P. The digest is the
+# hash of "hello, world" with the suitably-sized SHA-2 hash, so the test vectors
+# are suitable for both message- and digest-based APIs.
+#
+# "x" in the comments refer to the x-coordinate of the computed point, not that
+# of the public key.
+
+# r = 3, x = 3 is valid.
+Curve = P-224
+X = f43eeb550591547d6a6479726b72be181d4ea26dea5516ae1c0b0ab3
+Y = e127deeb94536c67793ac172ba31f3a6f81efbbf2ab3d7868d0cc9f9
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00000000000000000000000000000000000000000000000000000003
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+
+# r = 3 + n, x = 3 is invalid. r must already be reduced.
+Curve = P-224
+X = f43eeb550591547d6a6479726b72be181d4ea26dea5516ae1c0b0ab3
+Y = e127deeb94536c67793ac172ba31f3a6f81efbbf2ab3d7868d0cc9f9
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a40
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+Invalid =
+
+# r = n-1, x = n-1 is the largest x without a reduction.
+Curve = P-224
+X = 32acb8d348f6ec350822227c4a90048733640317f7833dc9093a78f1
+Y = dd45cab24ef90b8d6437f128437ea847036a8912322a6738dccceaa3
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3c
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+
+# r = n-2, x = n-1 is incorrect.
+Curve = P-224
+X = 32acb8d348f6ec350822227c4a90048733640317f7833dc9093a78f1
+Y = dd45cab24ef90b8d6437f128437ea847036a8912322a6738dccceaa3
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3b
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+Invalid =
+
+# r = 3, x = n+3 is the smallest x with a reduction.
+Curve = P-224
+X = d7afcc97eefcf32becf100cf967588c68f9c149fa18344ac08e245b4
+Y = 3b853f6c6d955587d9ac080c8f10bf355f9992a0103a27aa30dac7e8
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00000000000000000000000000000000000000000000000000000003
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+
+# r = 4, x = n+3 is incorrect.
+Curve = P-224
+X = d7afcc97eefcf32becf100cf967588c68f9c149fa18344ac08e245b4
+Y = 3b853f6c6d955587d9ac080c8f10bf355f9992a0103a27aa30dac7e8
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00000000000000000000000000000000000000000000000000000004
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+Invalid =
+
+# r = p-3-n, x = p-3 is the largest valid x.
+Curve = P-224
+X = cdacee2255448c72d1558eb866b14831acef41ed348bd938cce655be
+Y = d0b409693b64f3597468ae5535338052436158a6771c6318b68025de
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0000000000000000000000000000e95c1f470fc1ec22d6baa3a3d5c1
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+
+# r = p-n+3, x = 3 is incorrect. r is too large to compare r+n with x.
+Curve = P-224
+X = ef9169ef146a19c9a7220c6f25f597e7345e25fa1267712b9a20e30d
+Y = 454b19373a67ad81ca37ba8de9a96e881896df7160ba740f4c7373b9
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0000000000000000000000000000e95c1f470fc1ec22d6baa3a3d5c7
+S = ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a
+Invalid =
+
+# r = 5, x = 5 is valid.
+Curve = P-256
+X = 264d796a0dab9b376d34eea6fe297dde1c7b73e53944bc96c8f1e8a6850bb6c9
+Y = cf5308020eed460c649ddae61d4ef8bb79958113f106befaf4f18876d12a5e64
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0000000000000000000000000000000000000000000000000000000000000005
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+
+# r = 5 + n, x = 5 is invalid. r must already be reduced.
+Curve = P-256
+X = 264d796a0dab9b376d34eea6fe297dde1c7b73e53944bc96c8f1e8a6850bb6c9
+Y = cf5308020eed460c649ddae61d4ef8bb79958113f106befaf4f18876d12a5e64
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632556
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+Invalid =
+
+# r = n-2, x = n-2 is the largest x without a reduction.
+Curve = P-256
+X = 50a50c01132bf79e42b31fb278f7317b29515e9e1c973a41266b69048826fb8e
+Y = aac53e7df37b5eb25ce4ddb705fc7135c6b1e00a7f56e30744f62f258afa5537
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+
+# r = n-3, x = n-2 is incorrect.
+Curve = P-256
+X = 50a50c01132bf79e42b31fb278f7317b29515e9e1c973a41266b69048826fb8e
+Y = aac53e7df37b5eb25ce4ddb705fc7135c6b1e00a7f56e30744f62f258afa5537
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+Invalid =
+
+# r = 3, x = n+3 is the smallest x with a reduction.
+Curve = P-256
+X = ce24c99032d52ac6ead23c0ae3ec68ef41e51a281fd457808c83136d7dcce90e
+Y = 8f7a154b551e9f39c59279357aa491b2a62bdebc2bb78613883fc72936c057e0
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0000000000000000000000000000000000000000000000000000000000000003
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+
+# r = 4, x = n+3 is incorrect.
+Curve = P-256
+X = ce24c99032d52ac6ead23c0ae3ec68ef41e51a281fd457808c83136d7dcce90e
+Y = 8f7a154b551e9f39c59279357aa491b2a62bdebc2bb78613883fc72936c057e0
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0000000000000000000000000000000000000000000000000000000000000004
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+Invalid =
+
+# r = p-3-n, x = p-3 is the largest valid x.
+Curve = P-256
+X = 768a0d300a595005a520130e50927d403395c8e1e40be997b48fc048410f7cdb
+Y = 16f217d8e1c02bd887e5de388a17783b182e61b5d534152dc2c4be8d75fdd706
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 000000000000000000000000000000004319055358e8617b0c46353d039cdaab
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+
+# r = p-n+5, x = 5 is incorrect. r is too large to compare r+n with x.
+Curve = P-256
+X = 0ec505bc19b14a43e05678cccf07a443d3e871a2e19b68a4da91859a0650f324
+Y = 77300e4f64e9982d94dff5d294428bb37cc9be66117cae9c389d2d495f68b987
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 000000000000000000000000000000004319055358e8617b0c46353d039cdab3
+S = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e
+Invalid =
+
+# r = 2, x = 2 is valid.
+Curve = P-384
+X = 016d2db67561bc126ad6c344d6eeb2713a9e2892c649af0f015c6b7617f160c8a3b3a88add669d7155025073c5ac5b4f
+Y = 43bf2ed0088af08645c80aa0a24a567a94ba2d794e9689d3ad4b185bc5d2dd008333e2dd2ebb5069a9b32251a3cac71e
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+
+# r = 2 + n, x = 2 is invalid. r must already be reduced.
+Curve = P-384
+X = 016d2db67561bc126ad6c344d6eeb2713a9e2892c649af0f015c6b7617f160c8a3b3a88add669d7155025073c5ac5b4f
+Y = 43bf2ed0088af08645c80aa0a24a567a94ba2d794e9689d3ad4b185bc5d2dd008333e2dd2ebb5069a9b32251a3cac71e
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52975
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+Invalid =
+
+# r = n-1, x = n-1 is the largest x without a reduction.
+Curve = P-384
+X = b5b375264c09acf145ca91d12ab10a096092a41ec43f4d718e129ea1c12b2dea62c7785efc52f46f009fb1dba133e811
+Y = bc0b2af172b4b3068d032a798080e76f4d56f72069519e3c19a43682a41794e52cb3ca139348d6bbc923e6a4f7945cb1
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52972
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+
+# r = n-2, x = n-1 is incorrect.
+Curve = P-384
+X = b5b375264c09acf145ca91d12ab10a096092a41ec43f4d718e129ea1c12b2dea62c7785efc52f46f009fb1dba133e811
+Y = bc0b2af172b4b3068d032a798080e76f4d56f72069519e3c19a43682a41794e52cb3ca139348d6bbc923e6a4f7945cb1
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52971
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+Invalid =
+
+# r = 2, x = n+2 is the smallest x with a reduction.
+Curve = P-384
+X = 01b54a697305092bac2939fb906d7471b411c4eba8654169166a5da3810e1fc96795df921f7abbf519be4a027435176c
+Y = a19012a3518773d508106d4153adee43c3c384fa62ce36a4addea08f593ec9c76b09a6b9c69d29bd7d47eb48e167dd2f
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+
+# r = 3, x = n+2 is incorrect.
+Curve = P-384
+X = 01b54a697305092bac2939fb906d7471b411c4eba8654169166a5da3810e1fc96795df921f7abbf519be4a027435176c
+Y = a19012a3518773d508106d4153adee43c3c384fa62ce36a4addea08f593ec9c76b09a6b9c69d29bd7d47eb48e167dd2f
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+Invalid =
+
+# r = p-1-n, x = p-1 is the largest valid x.
+Curve = P-384
+X = c4fd8e68006b83f7b7b20b731ae405813aa05f6e57374589b36ae1cecd1d49cae1418c22f398188bcf4ef02e89fe7394
+Y = dd1164b3707f59e05129fa228b8448031db159985f035d93470dc42b3ab4129f0760c46cf201d42e73a7e33ba7402ea6
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = 000000000000000000000000000000000000000000000000389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68b
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+
+# r = p-n+2, x = 2 is incorrect. r is too large to compare r+n with x.
+Curve = P-384
+X = 4e5e4f1a6e97059a6cf2f4e8129e5c7c64cb84f9994a41ff5bf30b29c1bf5ba6898627c91a23c73e05cd1a43c8f908c0
+Y = 06a0aed7f1e63a728f87dbd5360a67571a076ab0b4cde81b10d499959814ddb3a8c7854b0bbfa87cc272f90bca2a2254
+Digest = 1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e
+R = 000000000000000000000000000000000000000000000000389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e
+S = ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52970
+Invalid =
+
+# r = 1, x = 1 is valid.
+Curve = P-521
+X = 00f07e0b593332d09ec4fd0bae93f648a3da04dd224faae3f64cc490ec8fce3a6fe53d1b2c9e326be076cafb921b7e3f8b2288db491819522d65472870668c3808c9
+Y = 018e42509aca542a8de421589c38ba653e8cfd69322336217042a9dc0f67f6d7ae2cd4e385f480ffaf8981f715c7ca3765d9867dfd5a02947b0895f82eaf8b257e88
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+
+# r = 1 + n, x = 1 is invalid. r must already be reduced.
+Curve = P-521
+X = 00f07e0b593332d09ec4fd0bae93f648a3da04dd224faae3f64cc490ec8fce3a6fe53d1b2c9e326be076cafb921b7e3f8b2288db491819522d65472870668c3808c9
+Y = 018e42509aca542a8de421589c38ba653e8cfd69322336217042a9dc0f67f6d7ae2cd4e385f480ffaf8981f715c7ca3765d9867dfd5a02947b0895f82eaf8b257e88
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138640a
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+Invalid =
+
+# r = n-2, x = n-2 is the largest x without a reduction.
+Curve = P-521
+X = 002a61afb982e49f030dd4e6ba0e495703abe0442b1283ee693fffc1b558f49f0a4cb4f138ea0604e667958495b86c61f358dce7e7f170da47372be3e4168408a260
+Y = 01baa19e8929fc8e7208e854e706a3d7f21479d1f6922a65ae3490fd5f52ae6580513b1fdd5bee927d002a9608abbb925b6727bdc110a3145fc8622d1fa8154c82d8
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386407
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+
+# r = n-3, x = n-2 is incorrect.
+Curve = P-521
+X = 002a61afb982e49f030dd4e6ba0e495703abe0442b1283ee693fffc1b558f49f0a4cb4f138ea0604e667958495b86c61f358dce7e7f170da47372be3e4168408a260
+Y = 01baa19e8929fc8e7208e854e706a3d7f21479d1f6922a65ae3490fd5f52ae6580513b1fdd5bee927d002a9608abbb925b6727bdc110a3145fc8622d1fa8154c82d8
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+Invalid =
+
+# r = 1, x = n+1 is the smallest x with a reduction.
+Curve = P-521
+X = 0049bbb2d3267a6eab2c59fac5b138b9e9c383db6637fcfe5d9f430e4c4c2ba0332340975448bd86c92a55c1a8288adf7f774096022419aa8c497499dafee7b93257
+Y = 00bb52fd444ec497ce228135f2498d40fb84eb6f674df1245d3aaac3c75b55ff5fff8e90b6f0189a3132cb9fd8d6e74fda5866fe2b9fc7484c628fde97e0b00f2b67
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+
+# r = 2, x = n+1 is incorrect.
+Curve = P-521
+X = 0049bbb2d3267a6eab2c59fac5b138b9e9c383db6637fcfe5d9f430e4c4c2ba0332340975448bd86c92a55c1a8288adf7f774096022419aa8c497499dafee7b93257
+Y = 00bb52fd444ec497ce228135f2498d40fb84eb6f674df1245d3aaac3c75b55ff5fff8e90b6f0189a3132cb9fd8d6e74fda5866fe2b9fc7484c628fde97e0b00f2b67
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+Invalid =
+
+# r = p-1-n, x = p-1 is the largest valid x.
+Curve = P-521
+X = 00f651d53d45bf6fd55a5f184e580d11259bc65200387dbc1bf7fb867d2d12a207d2962204ccf38e9d37d23ed95bd01ec576c457127766ecb8ad00342a476ea82078
+Y = 0196caedf64fbaa9a12c16836e0564e36f733957375706edb5f32911991a994c2d6a1ea5db2ee764835a9d6aff379e195f722b48e8d2b60fc50de2a5160c77c3f06c
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 00000000000000000000000000000000000000000000000000000000000000000005ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf5
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+
+# r = p-n+1, x = 1 is incorrect. r is too large to compare r+n with x.
+Curve = P-521
+X = 009eeb7f956230c3744ca5b683f413009363107aad18a027fa7af6ac07a699911e94143d3ef00c0062d4187c2ea74dc9322c05431a6b7fed51ee71b047ce3a0e967c
+Y = 007d2c089a6720f7c7886ce8aa6aeb9b821adde0eb025ef63c62d37c32b2d6823c857ce7743b8181c35c8f34e6aeb4487dd693e01d69dfe883c07c25ebe89bdc4d56
+Digest = 8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
+R = 00000000000000000000000000000000000000000000000000000000000000000005ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf7
+S = 01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386406
+Invalid =
+
+# Although we do not support secp160r1, all our built-in curves have p > n,
+# while n > p is reachable from custom curve logic. Moreover, p and n have
+# different word widths on 32-bit machines. We include some test vectors to
+# cover these cases.
+#
+# When n > p, the reduction mod n never occurs, but an optimized implementation,
+# working mod p, may incorrectly accept, e.g., r = p+4 instead of r = 4.
+
+# r = 4, x = 4 is valid.
+Curve = secp160r1
+X = 39891bd61138e775cd012518ff00f59ae01c4733
+Y = 25026b77b1c44affb1592dcf711b4290e9404c9f
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 000000000000000000000000000000000000000004
+S = 0100000000000000000001f4c8f927aed3ca752254
+
+# r = 4 + n, x = 4 is invalid. r must already be reduced.
+Curve = secp160r1
+X = 39891bd61138e775cd012518ff00f59ae01c4733
+Y = 25026b77b1c44affb1592dcf711b4290e9404c9f
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 0100000000000000000001f4c8f927aed3ca75225b
+S = 0100000000000000000001f4c8f927aed3ca752254
+Invalid =
+
+# r = p-3, x = p-3 are the largest valid values of x and r.
+Curve = secp160r1
+X = d88d902a0d8d942333c7b846a933d4794fcb5807
+Y = d24c4f405689b86cd5c61fe104e6365d254d5222
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00ffffffffffffffffffffffffffffffff7ffffffc
+S = 0100000000000000000001f4c8f927aed3ca752254
+
+# r = p-4, x = p-3 is incorrect.
+Curve = secp160r1
+X = d88d902a0d8d942333c7b846a933d4794fcb5807
+Y = d24c4f405689b86cd5c61fe104e6365d254d5222
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00ffffffffffffffffffffffffffffffff7ffffffb
+S = 0100000000000000000001f4c8f927aed3ca752254
+Invalid =
+
+# r = p+4, x = 4 is incorrect. They should be compared modulo the order, not p,
+# so r >= p is never valid.
+Curve = secp160r1
+X = d8add22064027856c162243ab09ea96642975297
+Y = 8822a506712385ab3ebe5c61737c3bbb722b06b9
+Digest = 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
+R = 00ffffffffffffffffffffffffffffffff80000003
+S = 0100000000000000000001f4c8f927aed3ca752254
+Invalid =
diff --git a/src/include/openssl/ec.h b/src/include/openssl/ec.h
index 41a9c34..966393e 100644
--- a/src/include/openssl/ec.h
+++ b/src/include/openssl/ec.h
@@ -348,13 +348,15 @@
 // EC_POINT_clear_free calls |EC_POINT_free|.
 OPENSSL_EXPORT void EC_POINT_clear_free(EC_POINT *point);
 
-// Old code expects to get EC_KEY from ec.h.
-#include <openssl/ec_key.h>
-
 
 #if defined(__cplusplus)
 }  // extern C
+#endif
 
+// Old code expects to get EC_KEY from ec.h.
+#include <openssl/ec_key.h>
+
+#if defined(__cplusplus)
 extern "C++" {
 
 BSSL_NAMESPACE_BEGIN
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 0fa10b5..17153c2 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -1521,8 +1521,8 @@
 // TLS 1.3 was negotiated. Otherwise, it returns zero.
 OPENSSL_EXPORT int SSL_get_extms_support(const SSL *ssl);
 
-// SSL_get_current_cipher returns the cipher used in the current outgoing
-// connection state, or NULL if the null cipher is active.
+// SSL_get_current_cipher returns cipher suite used by |ssl|, or NULL if it has
+// not been negotiated yet.
 OPENSSL_EXPORT const SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl);
 
 // SSL_session_reused returns one if |ssl| performed an abbreviated handshake
diff --git a/src/ssl/handoff.cc b/src/ssl/handoff.cc
index a49d289..4cca981 100644
--- a/src/ssl/handoff.cc
+++ b/src/ssl/handoff.cc
@@ -37,6 +37,15 @@
       return false;
     }
   }
+  CBB curves;
+  if (!CBB_add_asn1(out, &curves, CBS_ASN1_OCTETSTRING)) {
+    return false;
+  }
+  for (const NamedGroup& g : NamedGroups()) {
+    if (!CBB_add_u16(&curves, g.group_id)) {
+      return false;
+    }
+  }
   return CBB_flush(out);
 }
 
@@ -120,7 +129,52 @@
   for (const SSL_CIPHER *unsupported_cipher : unsupported.get()) {
     ssl->config->cipher_list->Remove(unsupported_cipher);
   }
-  return sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) > 0;
+  if (sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) == 0) {
+    return false;
+  }
+
+  CBS curves;
+  if (!CBS_get_asn1(in, &curves, CBS_ASN1_OCTETSTRING)) {
+    return false;
+  }
+  Array<uint16_t> supported_curves;
+  if (!supported_curves.Init(CBS_len(&curves) / 2)) {
+    return false;
+  }
+  size_t idx = 0;
+  while (CBS_len(&curves)) {
+    uint16_t curve;
+    if (!CBS_get_u16(&curves, &curve)) {
+      return false;
+    }
+    supported_curves[idx++] = curve;
+  }
+  Span<const uint16_t> configured_curves =
+      tls1_get_grouplist(ssl->s3->hs.get());
+  Array<uint16_t> new_configured_curves;
+  if (!new_configured_curves.Init(configured_curves.size())) {
+    return false;
+  }
+  idx = 0;
+  for (uint16_t configured_curve : configured_curves) {
+    bool ok = false;
+    for (uint16_t supported_curve : supported_curves) {
+      if (supported_curve == configured_curve) {
+        ok = true;
+        break;
+      }
+    }
+    if (ok) {
+      new_configured_curves[idx++] = configured_curve;
+    }
+  }
+  if (idx == 0) {
+    return false;
+  }
+  new_configured_curves.Shrink(idx);
+  ssl->config->supported_group_list = std::move(new_configured_curves);
+
+  return true;
 }
 
 bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index 2e05ee0..fa86bda 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -1000,6 +1000,15 @@
   virtual bool Deserialize(CBS *in) { return false; }
 };
 
+struct NamedGroup {
+  int nid;
+  uint16_t group_id;
+  const char name[8], alias[11];
+};
+
+// NamedGroups returns all supported groups.
+Span<const NamedGroup> NamedGroups();
+
 // ssl_nid_to_group_id looks up the group corresponding to |nid|. On success, it
 // sets |*out_group_id| to the group ID and returns true. Otherwise, it returns
 // false.
@@ -1044,6 +1053,10 @@
 // handshake data that has not been consumed by |get_message|.
 bool tls_has_unprocessed_handshake_data(const SSL *ssl);
 
+// tls_append_handshake_data appends |data| to the handshake buffer. It returns
+// true on success and false on allocation failure.
+bool tls_append_handshake_data(SSL *ssl, Span<const uint8_t> data);
+
 // dtls_has_unprocessed_handshake_data behaves like
 // |tls_has_unprocessed_handshake_data| for DTLS.
 bool dtls_has_unprocessed_handshake_data(const SSL *ssl);
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc
index 55e9aaa..f835dc2 100644
--- a/src/ssl/s3_both.cc
+++ b/src/ssl/s3_both.cc
@@ -411,7 +411,7 @@
   OPENSSL_memcpy(random + (SSL3_RANDOM_SIZE - rand_len), CBS_data(&challenge),
                  rand_len);
 
-  // Write out an equivalent TLS ClientHello.
+  // Write out an equivalent TLS ClientHello directly to the handshake buffer.
   size_t max_v3_client_hello = SSL3_HM_HEADER_LENGTH + 2 /* version */ +
                                SSL3_RANDOM_SIZE + 1 /* session ID length */ +
                                2 /* cipher list length */ +
@@ -419,7 +419,11 @@
                                1 /* compression length */ + 1 /* compression */;
   ScopedCBB client_hello;
   CBB hello_body, cipher_suites;
-  if (!BUF_MEM_reserve(ssl->s3->hs_buf.get(), max_v3_client_hello) ||
+  if (!ssl->s3->hs_buf) {
+    ssl->s3->hs_buf.reset(BUF_MEM_new());
+  }
+  if (!ssl->s3->hs_buf ||
+      !BUF_MEM_reserve(ssl->s3->hs_buf.get(), max_v3_client_hello) ||
       !CBB_init_fixed(client_hello.get(), (uint8_t *)ssl->s3->hs_buf->data,
                       ssl->s3->hs_buf->max) ||
       !CBB_add_u8(client_hello.get(), SSL3_MT_CLIENT_HELLO) ||
@@ -539,18 +543,18 @@
   return ssl->s3->hs_buf && ssl->s3->hs_buf->length > msg_len;
 }
 
-ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed,
-                                      uint8_t *out_alert, Span<uint8_t> in) {
-  *out_consumed = 0;
+bool tls_append_handshake_data(SSL *ssl, Span<const uint8_t> data) {
   // Re-create the handshake buffer if needed.
   if (!ssl->s3->hs_buf) {
     ssl->s3->hs_buf.reset(BUF_MEM_new());
-    if (!ssl->s3->hs_buf) {
-      *out_alert = SSL_AD_INTERNAL_ERROR;
-      return ssl_open_record_error;
-    }
   }
+  return ssl->s3->hs_buf &&
+         BUF_MEM_append(ssl->s3->hs_buf.get(), data.data(), data.size());
+}
 
+ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed,
+                                      uint8_t *out_alert, Span<uint8_t> in) {
+  *out_consumed = 0;
   // Bypass the record layer for the first message to handle V2ClientHello.
   if (ssl->server && !ssl->s3->v2_hello_done) {
     // Ask for the first 5 bytes, the size of the TLS record header. This is
@@ -619,7 +623,7 @@
   }
 
   // Append the entire handshake record to the buffer.
-  if (!BUF_MEM_append(ssl->s3->hs_buf.get(), body.data(), body.size())) {
+  if (!tls_append_handshake_data(ssl, body)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return ssl_open_record_error;
   }
diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc
index e9b652e..f0ae8a2 100644
--- a/src/ssl/s3_pkt.cc
+++ b/src/ssl/s3_pkt.cc
@@ -318,11 +318,7 @@
       return ssl_open_record_error;
     }
 
-    if (!ssl->s3->hs_buf) {
-      ssl->s3->hs_buf.reset(BUF_MEM_new());
-    }
-    if (!ssl->s3->hs_buf ||
-        !BUF_MEM_append(ssl->s3->hs_buf.get(), body.data(), body.size())) {
+    if (!tls_append_handshake_data(ssl, body)) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       return ssl_open_record_error;
     }
diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc
index 8466eab..80b7d0a 100644
--- a/src/ssl/ssl_key_share.cc
+++ b/src/ssl/ssl_key_share.cc
@@ -211,11 +211,7 @@
   uint8_t private_key_[32];
 };
 
-CONSTEXPR_ARRAY struct {
-  int nid;
-  uint16_t group_id;
-  const char name[8], alias[11];
-} kNamedGroups[] = {
+CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = {
     {NID_secp224r1, SSL_CURVE_SECP224R1, "P-224", "secp224r1"},
     {NID_X9_62_prime256v1, SSL_CURVE_SECP256R1, "P-256", "prime256v1"},
     {NID_secp384r1, SSL_CURVE_SECP384R1, "P-384", "secp384r1"},
@@ -225,6 +221,10 @@
 
 }  // namespace
 
+Span<const NamedGroup> NamedGroups() {
+  return MakeConstSpan(kNamedGroups, OPENSSL_ARRAY_SIZE(kNamedGroups));
+}
+
 UniquePtr<SSLKeyShare> SSLKeyShare::Create(uint16_t group_id) {
   switch (group_id) {
     case SSL_CURVE_SECP224R1:
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index 5bd2442..8a88802 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -846,15 +846,7 @@
     return 0;
   }
 
-  // Re-create the handshake buffer if needed.
-  if (!ssl->s3->hs_buf) {
-    ssl->s3->hs_buf.reset(BUF_MEM_new());
-    if (!ssl->s3->hs_buf) {
-      return 0;
-    }
-  }
-
-  return BUF_MEM_append(ssl->s3->hs_buf.get(), data, len);
+  return tls_append_handshake_data(ssl, MakeConstSpan(data, len));
 }
 
 int SSL_do_handshake(SSL *ssl) {
@@ -2278,7 +2270,8 @@
 }
 
 const SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl) {
-  return ssl->s3->aead_write_ctx->cipher();
+  const SSL_SESSION *session = SSL_get_session(ssl);
+  return session == nullptr ? nullptr : session->cipher;
 }
 
 int SSL_session_reused(const SSL *ssl) {
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index f7b299a..f945898 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -4285,20 +4285,29 @@
   // handoff is a handoff message that has been artificially modified to pretend
   // that only cipher 0x0A is supported.  When it is applied to |server|, all
   // ciphers but that one should be removed.
+  //
+  // To make a new one of these, try sticking this in the |Handoff| test above:
+  //
+  // hexdump(stderr, "", handoff.data(), handoff.size());
+  // sed -e 's/\(..\)/0x\1, /g'
+  //
+  // and modify serialize_features() to emit only cipher 0x0A.
+
   uint8_t handoff[] = {
-      0x30, 0x81, 0x8e, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
-      0x00, 0x00, 0x7e, 0x03, 0x03, 0x77, 0x62, 0x00, 0x9a, 0x13, 0x48, 0x23,
-      0x46, 0x11, 0x6c, 0x0b, 0x1c, 0x91, 0x4e, 0xbc, 0x1c, 0xff, 0x54, 0xb9,
-      0xe6, 0x3f, 0xa8, 0x8d, 0x49, 0x37, 0x7a, 0x9e, 0xbf, 0x36, 0xd5, 0x08,
-      0x24, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+      0x30, 0x81, 0x9a, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
+      0x00, 0x00, 0x7e, 0x03, 0x03, 0x30, 0x8e, 0x8f, 0x79, 0xd2, 0x87, 0x39,
+      0xc2, 0x23, 0x23, 0x13, 0xca, 0x3c, 0x80, 0x44, 0xfd, 0x80, 0x83, 0x62,
+      0x3c, 0xcc, 0xf8, 0x76, 0xd3, 0x62, 0xbb, 0x54, 0xe3, 0xc4, 0x39, 0x24,
+      0xa5, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
       0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
       0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
-      0x00, 0x37, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
-      0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
-      0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06,
-      0x01, 0x02, 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x00,
-      0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x04, 0x02, 0x00,
-      0x0a,
+      0x00, 0x37, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00,
+      0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00,
+      0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
+      0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
+      0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x04, 0x02, 0x00,
+      0x0a, 0x04, 0x0a, 0x00, 0x15, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00,
+      0x1d,
   };
 
   EXPECT_EQ(20u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
@@ -4307,6 +4316,43 @@
   EXPECT_EQ(1u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
 }
 
+TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) {
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+  bssl::UniquePtr<SSL> server(SSL_new(server_ctx.get()));
+
+  // handoff is a handoff message that has been artificially modified to pretend
+  // that only one curve is supported.  When it is applied to |server|, all
+  // curves but that one should be removed.
+  //
+  // See |ApplyHandoffRemovesUnsupportedCiphers| for how to make a new one of
+  // these.
+  uint8_t handoff[] = {
+      0x30, 0x81, 0xc0, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
+      0x00, 0x00, 0x7e, 0x03, 0x03, 0x98, 0x30, 0xce, 0xd9, 0xb0, 0xdf, 0x5f,
+      0x82, 0x05, 0x4a, 0x43, 0x67, 0x7e, 0xdb, 0x6a, 0x4f, 0x21, 0x18, 0x4e,
+      0x0d, 0x94, 0x63, 0x18, 0x8b, 0x54, 0x89, 0xdb, 0x8b, 0x1d, 0x84, 0xbc,
+      0x09, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+      0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
+      0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+      0x00, 0x37, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00,
+      0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00,
+      0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
+      0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
+      0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x04, 0x30, 0x00,
+      0x02, 0x00, 0x0a, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x8c, 0x00, 0x8d, 0x00,
+      0x9c, 0x00, 0x9d, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x09, 0xc0,
+      0x0a, 0xc0, 0x13, 0xc0, 0x14, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x2f, 0xc0,
+      0x30, 0xc0, 0x35, 0xc0, 0x36, 0xcc, 0xa8, 0xcc, 0xa9, 0xcc, 0xac, 0x04,
+      0x02, 0x00, 0x17,
+  };
+
+  // The zero length means that the default list of groups is used.
+  EXPECT_EQ(0u, server->config->supported_group_list.size());
+  ASSERT_TRUE(
+      SSL_apply_handoff(server.get(), {handoff, OPENSSL_ARRAY_SIZE(handoff)}));
+  EXPECT_EQ(1u, server->config->supported_group_list.size());
+}
+
 TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
   // Configure the server to request client certificates.
   SSL_CTX_set_custom_verify(
@@ -4498,7 +4544,8 @@
 
   bool PeerSecretsMatch(ssl_encryption_level_t level) const {
     return levels_[level].write_secret == peer_->levels_[level].read_secret &&
-           levels_[level].read_secret == peer_->levels_[level].write_secret;
+           levels_[level].read_secret == peer_->levels_[level].write_secret &&
+           levels_[level].cipher == peer_->levels_[level].cipher;
   }
 
   bool HasSecrets(ssl_encryption_level_t level) const {
@@ -4508,11 +4555,18 @@
 
   bool SetEncryptionSecrets(ssl_encryption_level_t level,
                             const uint8_t *read_secret,
-                            const uint8_t *write_secret, size_t secret_len) {
+                            const uint8_t *write_secret, size_t secret_len,
+                            const SSL_CIPHER *cipher) {
     if (HasSecrets(level)) {
       ADD_FAILURE() << "duplicate keys configured";
       return false;
     }
+
+    if (cipher == nullptr) {
+      ADD_FAILURE() << "current cipher unavailable";
+      return false;
+    }
+
     if (level != ssl_encryption_early_data &&
         (read_secret == nullptr || write_secret == nullptr)) {
       ADD_FAILURE() << "key was unexpectedly null";
@@ -4525,6 +4579,7 @@
       levels_[level].write_secret.assign(write_secret,
                                          write_secret + secret_len);
     }
+    levels_[level].cipher = SSL_CIPHER_get_id(cipher);
     return true;
   }
 
@@ -4572,6 +4627,10 @@
       ADD_FAILURE() << "peer write key does not match read key";
       return false;
     }
+    if (peer_->levels_[level].cipher != levels_[level].cipher) {
+      ADD_FAILURE() << "peer cipher does not match";
+      return false;
+    }
     std::vector<uint8_t> *peer_data = &peer_->levels_[level].write_data;
     num = std::min(num, peer_data->size());
     out->assign(peer_data->begin(), peer_data->begin() + num);
@@ -4590,6 +4649,7 @@
     std::vector<uint8_t> write_data;
     std::vector<uint8_t> write_secret;
     std::vector<uint8_t> read_secret;
+    uint32_t cipher = 0;
   };
   Level levels_[kNumQUICLevels];
 };
@@ -4675,8 +4735,8 @@
                                           const uint8_t *read_key,
                                           const uint8_t *write_key,
                                           size_t key_len) {
-    return TransportFromSSL(ssl)->SetEncryptionSecrets(level, read_key,
-                                                       write_key, key_len);
+    return TransportFromSSL(ssl)->SetEncryptionSecrets(
+        level, read_key, write_key, key_len, SSL_get_current_cipher(ssl));
   }
 
   static int AddHandshakeDataCallback(SSL *ssl,
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index ef24ca0..52e6cf7 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -1638,10 +1638,5 @@
     }
   }
 
-  if (SSL_get_current_cipher(ssl.get()) != nullptr) {
-    fprintf(stderr, "non-null cipher before handshake\n");
-    return nullptr;
-  }
-
   return ssl;
 }
diff --git a/src/third_party/fiat/p256.c b/src/third_party/fiat/p256.c
index f42f8fe..c8e42a3 100644
--- a/src/third_party/fiat/p256.c
+++ b/src/third_party/fiat/p256.c
@@ -895,14 +895,6 @@
   fe_mul(x, x, kOne);
 }
 
-// BN_* compatability wrappers
-
-static BIGNUM *fe_to_BN(BIGNUM *out, const fe in) {
-  uint8_t tmp[NBYTES];
-  fe_tobytes(tmp, in);
-  return BN_le2bn(tmp, NBYTES, out);
-}
-
 static void fe_from_generic(fe out, const EC_FELEM *in) {
   fe_frombytes(out, in->bytes);
 }
@@ -1631,8 +1623,8 @@
 // Takes the Jacobian coordinates (X, Y, Z) of a point and returns (X', Y') =
 // (X/Z^2, Y/Z^3).
 static int ec_GFp_nistp256_point_get_affine_coordinates(
-    const EC_GROUP *group, const EC_RAW_POINT *point, BIGNUM *x_out,
-    BIGNUM *y_out) {
+    const EC_GROUP *group, const EC_RAW_POINT *point, EC_FELEM *x_out,
+    EC_FELEM *y_out) {
   if (ec_GFp_simple_is_at_infinity(group, point)) {
     OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
     return 0;
@@ -1652,10 +1644,7 @@
     fe x;
     fe_from_generic(x, &point->X);
     fe_mul(x, x, z1);
-    if (!fe_to_BN(x_out, x)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-      return 0;
-    }
+    fe_to_generic(x_out, x);
   }
 
   if (y_out != NULL) {
@@ -1663,15 +1652,39 @@
     fe_from_generic(y, &point->Y);
     fe_mul(z1, z1, z2);
     fe_mul(y, y, z1);
-    if (!fe_to_BN(y_out, y)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-      return 0;
-    }
+    fe_to_generic(y_out, y);
   }
 
   return 1;
 }
 
+static void ec_GFp_nistp256_add(const EC_GROUP *group, EC_RAW_POINT *r,
+                                const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
+  fe x1, y1, z1, x2, y2, z2;
+  fe_from_generic(x1, &a->X);
+  fe_from_generic(y1, &a->Y);
+  fe_from_generic(z1, &a->Z);
+  fe_from_generic(x2, &b->X);
+  fe_from_generic(y2, &b->Y);
+  fe_from_generic(z2, &b->Z);
+  point_add(x1, y1, z1, x1, y1, z1, 0 /* both Jacobian */, x2, y2, z2);
+  fe_to_generic(&r->X, x1);
+  fe_to_generic(&r->Y, y1);
+  fe_to_generic(&r->Z, z1);
+}
+
+static void ec_GFp_nistp256_dbl(const EC_GROUP *group, EC_RAW_POINT *r,
+                                const EC_RAW_POINT *a) {
+  fe x, y, z;
+  fe_from_generic(x, &a->X);
+  fe_from_generic(y, &a->Y);
+  fe_from_generic(z, &a->Z);
+  point_double(x, y, z, x, y, z);
+  fe_to_generic(&r->X, x);
+  fe_to_generic(&r->Y, y);
+  fe_to_generic(&r->Z, z);
+}
+
 static void ec_GFp_nistp256_points_mul(const EC_GROUP *group, EC_RAW_POINT *r,
                                        const EC_SCALAR *g_scalar,
                                        const EC_RAW_POINT *p,
@@ -1800,6 +1813,8 @@
   out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates =
     ec_GFp_nistp256_point_get_affine_coordinates;
+  out->add = ec_GFp_nistp256_add;
+  out->dbl = ec_GFp_nistp256_dbl;
   out->mul = ec_GFp_nistp256_points_mul;
   out->mul_public = ec_GFp_nistp256_point_mul_public;
   out->felem_mul = ec_GFp_mont_felem_mul;
@@ -1807,6 +1822,8 @@
   out->bignum_to_felem = ec_GFp_mont_bignum_to_felem;
   out->felem_to_bignum = ec_GFp_mont_felem_to_bignum;
   out->scalar_inv_montgomery = ec_simple_scalar_inv_montgomery;
+  out->scalar_inv_montgomery_vartime = ec_GFp_simple_mont_inv_mod_ord_vartime;
+  out->cmp_x_coordinate = ec_GFp_simple_cmp_x_coordinate;
 };
 
 #undef BORINGSSL_NISTP256_64BIT
diff --git a/win-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.asm b/win-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.asm
new file mode 100644
index 0000000..9023d8d
--- /dev/null
+++ b/win-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.asm
@@ -0,0 +1,338 @@
+default	rel
+%define XMMWORD
+%define YMMWORD
+%define ZMMWORD
+
+%ifdef BORINGSSL_PREFIX
+%include "boringssl_prefix_symbols_nasm.inc"
+%endif
+section	.text code align=64
+
+
+
+
+global	beeu_mod_inverse_vartime
+ALIGN	32
+beeu_mod_inverse_vartime:
+	mov	QWORD[8+rsp],rdi	;WIN64 prologue
+	mov	QWORD[16+rsp],rsi
+	mov	rax,rsp
+$L$SEH_begin_beeu_mod_inverse_vartime:
+	mov	rdi,rcx
+	mov	rsi,rdx
+	mov	rdx,r8
+	mov	rcx,r9
+	mov	r8,QWORD[40+rsp]
+	mov	r9,QWORD[48+rsp]
+
+
+
+	push	rbp
+
+	mov	rbp,rsp
+
+
+	push	r12
+
+	push	r13
+
+	push	r14
+
+	push	r15
+
+	push	rbx
+
+	push	rsi
+
+
+	sub	rsp,80
+	mov	QWORD[rsp],rdi
+
+
+	mov	r8,1
+	xor	r9,r9
+	xor	r10,r10
+	xor	r11,r11
+	xor	rdi,rdi
+
+	xor	r12,r12
+	xor	r13,r13
+	xor	r14,r14
+	xor	r15,r15
+	xor	rbp,rbp
+
+
+	vmovdqu	xmm0,XMMWORD[rsi]
+	vmovdqu	xmm1,XMMWORD[16+rsi]
+	vmovdqu	XMMWORD[48+rsp],xmm0
+	vmovdqu	XMMWORD[64+rsp],xmm1
+
+	vmovdqu	xmm0,XMMWORD[rdx]
+	vmovdqu	xmm1,XMMWORD[16+rdx]
+	vmovdqu	XMMWORD[16+rsp],xmm0
+	vmovdqu	XMMWORD[32+rsp],xmm1
+
+$L$beeu_loop:
+	xor	rbx,rbx
+	or	rbx,QWORD[48+rsp]
+	or	rbx,QWORD[56+rsp]
+	or	rbx,QWORD[64+rsp]
+	or	rbx,QWORD[72+rsp]
+	jz	NEAR $L$beeu_loop_end
+
+
+
+
+
+
+
+
+
+
+	mov	rcx,1
+
+
+$L$beeu_shift_loop_XB:
+	mov	rbx,rcx
+	and	rbx,QWORD[48+rsp]
+	jnz	NEAR $L$beeu_shift_loop_end_XB
+
+
+	mov	rbx,1
+	and	rbx,r8
+	jz	NEAR $L$shift1_0
+	add	r8,QWORD[rdx]
+	adc	r9,QWORD[8+rdx]
+	adc	r10,QWORD[16+rdx]
+	adc	r11,QWORD[24+rdx]
+	adc	rdi,0
+
+$L$shift1_0:
+	shrd	r8,r9,1
+	shrd	r9,r10,1
+	shrd	r10,r11,1
+	shrd	r11,rdi,1
+	shr	rdi,1
+
+	shl	rcx,1
+
+
+
+
+
+	cmp	rcx,0x8000000
+	jne	NEAR $L$beeu_shift_loop_XB
+
+$L$beeu_shift_loop_end_XB:
+	bsf	rcx,rcx
+	test	rcx,rcx
+	jz	NEAR $L$beeu_no_shift_XB
+
+
+
+	mov	rax,QWORD[((8+48))+rsp]
+	mov	rbx,QWORD[((16+48))+rsp]
+	mov	rsi,QWORD[((24+48))+rsp]
+
+	shrd	QWORD[((0+48))+rsp],rax,cl
+	shrd	QWORD[((8+48))+rsp],rbx,cl
+	shrd	QWORD[((16+48))+rsp],rsi,cl
+
+	shr	rsi,cl
+	mov	QWORD[((24+48))+rsp],rsi
+
+
+$L$beeu_no_shift_XB:
+
+	mov	rcx,1
+
+
+$L$beeu_shift_loop_YA:
+	mov	rbx,rcx
+	and	rbx,QWORD[16+rsp]
+	jnz	NEAR $L$beeu_shift_loop_end_YA
+
+
+	mov	rbx,1
+	and	rbx,r12
+	jz	NEAR $L$shift1_1
+	add	r12,QWORD[rdx]
+	adc	r13,QWORD[8+rdx]
+	adc	r14,QWORD[16+rdx]
+	adc	r15,QWORD[24+rdx]
+	adc	rbp,0
+
+$L$shift1_1:
+	shrd	r12,r13,1
+	shrd	r13,r14,1
+	shrd	r14,r15,1
+	shrd	r15,rbp,1
+	shr	rbp,1
+
+	shl	rcx,1
+
+
+
+
+
+	cmp	rcx,0x8000000
+	jne	NEAR $L$beeu_shift_loop_YA
+
+$L$beeu_shift_loop_end_YA:
+	bsf	rcx,rcx
+	test	rcx,rcx
+	jz	NEAR $L$beeu_no_shift_YA
+
+
+
+	mov	rax,QWORD[((8+16))+rsp]
+	mov	rbx,QWORD[((16+16))+rsp]
+	mov	rsi,QWORD[((24+16))+rsp]
+
+	shrd	QWORD[((0+16))+rsp],rax,cl
+	shrd	QWORD[((8+16))+rsp],rbx,cl
+	shrd	QWORD[((16+16))+rsp],rsi,cl
+
+	shr	rsi,cl
+	mov	QWORD[((24+16))+rsp],rsi
+
+
+$L$beeu_no_shift_YA:
+
+	mov	rax,QWORD[48+rsp]
+	mov	rbx,QWORD[56+rsp]
+	mov	rsi,QWORD[64+rsp]
+	mov	rcx,QWORD[72+rsp]
+	sub	rax,QWORD[16+rsp]
+	sbb	rbx,QWORD[24+rsp]
+	sbb	rsi,QWORD[32+rsp]
+	sbb	rcx,QWORD[40+rsp]
+	jnc	NEAR $L$beeu_B_bigger_than_A
+
+
+	mov	rax,QWORD[16+rsp]
+	mov	rbx,QWORD[24+rsp]
+	mov	rsi,QWORD[32+rsp]
+	mov	rcx,QWORD[40+rsp]
+	sub	rax,QWORD[48+rsp]
+	sbb	rbx,QWORD[56+rsp]
+	sbb	rsi,QWORD[64+rsp]
+	sbb	rcx,QWORD[72+rsp]
+	mov	QWORD[16+rsp],rax
+	mov	QWORD[24+rsp],rbx
+	mov	QWORD[32+rsp],rsi
+	mov	QWORD[40+rsp],rcx
+
+
+	add	r12,r8
+	adc	r13,r9
+	adc	r14,r10
+	adc	r15,r11
+	adc	rbp,rdi
+	jmp	NEAR $L$beeu_loop
+
+$L$beeu_B_bigger_than_A:
+
+	mov	QWORD[48+rsp],rax
+	mov	QWORD[56+rsp],rbx
+	mov	QWORD[64+rsp],rsi
+	mov	QWORD[72+rsp],rcx
+
+
+	add	r8,r12
+	adc	r9,r13
+	adc	r10,r14
+	adc	r11,r15
+	adc	rdi,rbp
+
+	jmp	NEAR $L$beeu_loop
+
+$L$beeu_loop_end:
+
+
+
+
+	mov	rbx,QWORD[16+rsp]
+	sub	rbx,1
+	or	rbx,QWORD[24+rsp]
+	or	rbx,QWORD[32+rsp]
+	or	rbx,QWORD[40+rsp]
+
+	jnz	NEAR $L$beeu_err
+
+
+
+
+	mov	r8,QWORD[rdx]
+	mov	r9,QWORD[8+rdx]
+	mov	r10,QWORD[16+rdx]
+	mov	r11,QWORD[24+rdx]
+	xor	rdi,rdi
+
+$L$beeu_reduction_loop:
+	mov	QWORD[16+rsp],r12
+	mov	QWORD[24+rsp],r13
+	mov	QWORD[32+rsp],r14
+	mov	QWORD[40+rsp],r15
+	mov	QWORD[48+rsp],rbp
+
+
+	sub	r12,r8
+	sbb	r13,r9
+	sbb	r14,r10
+	sbb	r15,r11
+	sbb	rbp,0
+
+
+	cmovc	r12,QWORD[16+rsp]
+	cmovc	r13,QWORD[24+rsp]
+	cmovc	r14,QWORD[32+rsp]
+	cmovc	r15,QWORD[40+rsp]
+	jnc	NEAR $L$beeu_reduction_loop
+
+
+	sub	r8,r12
+	sbb	r9,r13
+	sbb	r10,r14
+	sbb	r11,r15
+
+$L$beeu_save:
+
+	mov	rdi,QWORD[rsp]
+
+	mov	QWORD[rdi],r8
+	mov	QWORD[8+rdi],r9
+	mov	QWORD[16+rdi],r10
+	mov	QWORD[24+rdi],r11
+
+
+	mov	rax,1
+	jmp	NEAR $L$beeu_finish
+
+$L$beeu_err:
+
+	xor	rax,rax
+
+$L$beeu_finish:
+	add	rsp,80
+	pop	rsi
+
+	pop	rbx
+
+	pop	r15
+
+	pop	r14
+
+	pop	r13
+
+	pop	r12
+
+	pop	rbp
+
+
+
+	mov	rdi,QWORD[8+rsp]	;WIN64 epilogue
+	mov	rsi,QWORD[16+rsp]
+	DB	0F3h,0C3h		;repret
+
+$L$SEH_end_beeu_mod_inverse_vartime: