| // Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <openssl/bn.h> |
| |
| #include <openssl/err.h> |
| |
| |
| int BN_sqrt(BIGNUM *out_sqrt, const BIGNUM *in, BN_CTX *ctx) { |
| BIGNUM *estimate, *tmp, *delta, *last_delta, *tmp2; |
| int ok = 0, last_delta_valid = 0; |
| |
| if (in->neg) { |
| OPENSSL_PUT_ERROR(BN, BN_R_NEGATIVE_NUMBER); |
| return 0; |
| } |
| if (BN_is_zero(in)) { |
| BN_zero(out_sqrt); |
| return 1; |
| } |
| |
| bssl::BN_CTXScope scope(ctx); |
| if (out_sqrt == in) { |
| estimate = BN_CTX_get(ctx); |
| } else { |
| estimate = out_sqrt; |
| } |
| tmp = BN_CTX_get(ctx); |
| last_delta = BN_CTX_get(ctx); |
| delta = BN_CTX_get(ctx); |
| if (estimate == NULL || tmp == NULL || last_delta == NULL || delta == NULL) { |
| goto err; |
| } |
| |
| // We estimate that the square root of an n-bit number is 2^{n/2}. |
| if (!BN_lshift(estimate, BN_value_one(), BN_num_bits(in)/2)) { |
| goto err; |
| } |
| |
| // This is Newton's method for finding a root of the equation |estimate|^2 - |
| // |in| = 0. |
| for (;;) { |
| // |estimate| = 1/2 * (|estimate| + |in|/|estimate|) |
| if (!BN_div(tmp, NULL, in, estimate, ctx) || |
| !BN_add(tmp, tmp, estimate) || |
| !BN_rshift1(estimate, tmp) || |
| // |tmp| = |estimate|^2 |
| !BN_sqr(tmp, estimate, ctx) || |
| // |delta| = |in| - |tmp| |
| !BN_sub(delta, in, tmp)) { |
| OPENSSL_PUT_ERROR(BN, ERR_R_BN_LIB); |
| goto err; |
| } |
| |
| delta->neg = 0; |
| // The difference between |in| and |estimate| squared is required to always |
| // decrease. This ensures that the loop always terminates, but I don't have |
| // a proof that it always finds the square root for a given square. |
| if (last_delta_valid && BN_cmp(delta, last_delta) >= 0) { |
| break; |
| } |
| |
| last_delta_valid = 1; |
| |
| tmp2 = last_delta; |
| last_delta = delta; |
| delta = tmp2; |
| } |
| |
| if (BN_cmp(tmp, in) != 0) { |
| OPENSSL_PUT_ERROR(BN, BN_R_NOT_A_SQUARE); |
| goto err; |
| } |
| |
| ok = 1; |
| |
| err: |
| if (ok && out_sqrt == in && !BN_copy(out_sqrt, estimate)) { |
| ok = 0; |
| } |
| return ok; |
| } |