hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 8d214de3b694be056a32107df472cdf0057c2ff7
parent 19421dfe21b90f482c1b979feb3d329102cea52b
Author: Armin Preiml <apreiml@strohwolke.at>
Date:   Thu,  9 May 2024 13:31:02 +0200

crypto::ecdsa: implement sign and verify

Note that the verify function works also with ecdsa signatures
that aren't created according to RFC 6979. E.g. if 'k' is chosen
randomly.

Signed-off-by: Armin Preiml <apreiml@strohwolke.at>

Diffstat:
Acrypto/ecdsa/ecdsa.ha | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 309 insertions(+), 0 deletions(-)

diff --git a/crypto/ecdsa/ecdsa.ha b/crypto/ecdsa/ecdsa.ha @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> + +// Ported from BearSSL +// +// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org> +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crypto::ec; +use crypto::bigint; +use os; +use hash; +use crypto::bigint::{word}; +use io; + +def POINTSZ = 1 + ((ec::MAX_COORDBITSZ + bigint::WORD_BITSZ - 1) + / bigint::WORD_BITSZ); + +// Maximum signature size of curves supported by [[crypto::ec]]. +export def MAX_SIGSZ = ec::MAX_POINTSZ - 1; + +// Returns the size of a signature created/verifiable with given key. It is +// [[crypto::ec::pointsz]] - 1 for the NIST curves. +export fn sigsz(key: (*pubkey | *privkey)) size = { + let c = match (key) { + case let k: *pubkey => + yield k.curve; + case let k: *privkey => + yield k.curve; + }; + return c.pointsz - 1; +}; + +// Size of signature created with a P256 key. +export def P256_SIGSZ = 64; + +// Size of signature created with a P384 key. +export def P384_SIGSZ = 96; + +// Size of signature created with a P521 key. +export def P521_SIGSZ = 132; + +// Verifies the signature 'sig' with message 'hash' using the public key 'pub'. +// Returns 1 on success and 0 on error. Verification is done in constant time, +// but may return earlier if the signature format is not valid. +export fn verify(pub: *pubkey, hash: []u8, sig: []u8) u32 = { + // IMPORTANT: this code is fit only for curves with a prime + // order. This is needed so that modular reduction of the X + // coordinate of a point can be done with a simple subtraction. + assert(pub.curve == ec::p256 || pub.curve == ec::p384 + || pub.curve == ec::p521); + + let n: [POINTSZ]bigint::word = [0...]; + let r: [POINTSZ]bigint::word = [0...]; + let s: [POINTSZ]bigint::word = [0...]; + let t: [POINTSZ*2]bigint::word = [0...]; + let t1 = t[..POINTSZ]; + let t2 = t[POINTSZ..]; + + let tx: [(ec::MAX_COORDBITSZ + 7) >> 3]u8 = [0...]; + let ty: [(ec::MAX_COORDBITSZ + 7) >> 3]u8 = [0...]; + let eu: [ec::MAX_POINTSZ]u8 = [0...]; + + let q = pub.get_q(pub); + + // Signature length must be even. + if (len(sig) & 1 == 1) { + return 0; + }; + let rlen = len(sig) >> 1; + + let generator = pub.curve.generator(); + // Public key point must have the proper size for this curve. + if (len(q) != len(generator)) { + return 0; + }; + + // Get modulus; then decode the r and s values. They must be + // lower than the modulus, and s must not be null. + let order = pub.curve.order(); + const nlen = len(order); + + + bigint::encode(n, order); + let n0i = bigint::ninv(n[1]); + if (bigint::encodemod(r, sig[..rlen], n) == 0) { + return 0; + }; + if (bigint::encodemod(s, sig[rlen..2 * rlen], n) == 0) { + return 0; + }; + if (bigint::iszero(s) == 1) { + return 0; + }; + + // Invert s. We do that with a modular exponentiation; we use + // the fact that for all the curves we support, the least + // significant byte is not 0 or 1, so we can subtract 2 without + // any carry to process. + // We also want 1/s in Montgomery representation, which can be + // done by converting _from_ Montgomery representation before + // the inversion (because (1/s)*R = 1/(s/R)). + bigint::frommonty(s, n, n0i); + tx[..nlen] = order[..]; + tx[nlen - 1] -= 2; + bigint::modpow(s, tx[..nlen], n, n0i, t); + + + t1[..] = [0...]; + // Truncate the hash to the modulus length (in bits) and reduce + // it modulo the curve order. The modular reduction can be done + // with a subtraction since the truncation already reduced the + // value to the modulus bit length. + bits2int(t1, hash, n[0]); + bigint::sub(t1, n, bigint::sub(t1, n, 0) ^ 1); + + // Multiply the (truncated, reduced) hash value with 1/s, result in + // t2, encoded in ty. + bigint::montymul(t2, t1, s, n, n0i); + bigint::decode(ty[..nlen], t2); + + // Multiply r with 1/s, result in t1, encoded in tx. + bigint::montymul(t1, r, s, n, n0i); + bigint::decode(tx[..nlen], t1); + + // Compute the point x*Q + y*G. + let ulen = len(generator); + eu[..ulen] = q[..ulen]; + let res = pub.curve.muladd(eu[..ulen], [], tx[..nlen], ty[..nlen]); + + // Get the X coordinate, reduce modulo the curve order, and + // compare with the 'r' value. + // + // The modular reduction can be done with subtractions because + // we work with curves of prime order, so the curve order is + // close to the field order (Hasse's theorem). + bigint::zero(t1, n[0]); + bigint::encode(t1, eu[1..(ulen >> 1) + 1]); + t1[0] = n[0]; + bigint::sub(t1, n, bigint::sub(t1, n, 0) ^ 1); + res &= ~bigint::sub(t1, r, 1); + res &= bigint::iszero(t1); + return res; +}; + +def ORDER_LEN = (ec::MAX_COORDBITSZ + 7) >> 3; + +// Signs hashed message 'hash' with the private key and stores it into 'sig'. +// Returns the number of bytes written to sig on success or 0 on failure. +// +// The signature is done in a deterministic way according to RFC 6979, hence +// 'hashfn' and 'hashbuf' are required. 'hashfn' can be the same as the one that +// created 'hash', though it might not be. The overall security will be limited +// by the weaker of the two hash functions, according to the RFC. 'hashbuf' must +// be of size [[hash::sz]] of 'hashfn' * 2 + [[hash::bsz]] of 'hashfn'. +// +// For the size requirenment of 'sig' see [[sigsz]]. +export fn sign( + priv: *privkey, + hash: []u8, + hashfn: *hash::hash, + hashbuf: []u8, + sig: []u8 +) u32 = { + // IMPORTANT: this code is fit only for curves with a prime + // order. This is needed so that modular reduction of the X + // coordinate of a point can be done with a simple subtraction. + // We also rely on the last byte of the curve order to be distinct + // from 0 and 1. + assert(priv.curve == ec::p256 || priv.curve == ec::p384 + || priv.curve == ec::p521); + + let n: [POINTSZ]bigint::word = [0...]; + let r: [POINTSZ]bigint::word = [0...]; + let s: [POINTSZ]bigint::word = [0...]; + let x: [POINTSZ]bigint::word = [0...]; + let m: [POINTSZ]bigint::word = [0...]; + let k: [POINTSZ]bigint::word = [0...]; + let tmp: [POINTSZ * 2]bigint::word = [0...]; + + let tt: [ORDER_LEN << 1]u8 = [0...]; + let eu: [ec::MAX_POINTSZ]u8 = [0...]; + + // Get modulus. + let order = priv.curve.order(); + const nlen = len(order); + + bigint::encode(n, order); + const n0i = bigint::ninv(n[1]); + + // Get private key as an i31 integer. This also checks that the + // private key is well-defined (not zero, and less than the + // curve order). + if (bigint::encodemod(x, priv.get_x(priv), n) == 0) { + return 0; + }; + if (bigint::iszero(x) == 1) { + return 0; + }; + + // Truncate and reduce the hash value modulo the curve order. + bits2int(m, hash, n[0]); + bigint::sub(m, n, bigint::sub(m, n, 0) ^ 1); + + bigint::decode(tt[..nlen], x); + bigint::decode(tt[nlen..nlen * 2], m); + + // RFC 6979 generation of the "k" value. + // + // The process uses HMAC_DRBG (with the hash function used to + // process the message that is to be signed). The seed is the + // concatenation of the encodings of the private key and + // the hash value (after truncation and modular reduction). + hash::reset(hashfn); + let drbg = hmac_drbg(hashfn, tt[..nlen*2], hashbuf); + + for (true) { + hmac_drbg_generate(&drbg, eu[..nlen]); + bits2int(k, eu[..nlen], n[0]); + + if (bigint::iszero(k) == 1) continue; + if (bigint::sub(k, n, 0) > 0) { + break; + }; + }; + + // Compute k*G and extract the X coordinate, then reduce it + // modulo the curve order. Since we support only curves with + // prime order, that reduction is only a matter of computing + // a subtraction. + bigint::decode(tt[..nlen], k[..]); + + let ulen = priv.curve.mulgen(eu, tt[..nlen]); + + bigint::zero(r, n[0]); + bigint::encode(r, eu[1..(ulen >> 1) + 1]); + r[0] = n[0]; + bigint::sub(r, n, bigint::sub(r, n, 0) ^ 1); + + // Compute 1/k in double-Montgomery representation. We do so by + // first converting _from_ Montgomery representation (twice), + // then using a modular exponentiation. + bigint::frommonty(k, n, n0i); + bigint::frommonty(k, n, n0i); + tt[..nlen] = order[..nlen]; + tt[nlen - 1] -= 2; + + bigint::modpow(k, tt[..nlen], n, n0i, tmp); + + // Compute s = (m+xr)/k (mod n). + // The k[] array contains R^2/k (double-Montgomery representation); + // we thus can use direct Montgomery multiplications and conversions + // from Montgomery, avoiding any call to br_i31_to_monty() (which + // is slower). + let t1 = tmp[..POINTSZ]; + let t2 = tmp[POINTSZ..]; + bigint::frommonty(m, n, n0i); + bigint::montymul(t1, x, r, n, n0i); + let ctl = bigint::add(t1, m, 1); + ctl |= bigint::sub(t1, n, 0) ^ 1; + bigint::sub(t1, n, ctl); + bigint::montymul(s, t1, k, n, n0i); + + // Encode r and s in the signature. + bigint::decode(sig[..nlen], r); + bigint::decode(sig[nlen..nlen*2], s); + return (nlen << 1): u32; +}; + +// Decode some bytes as an i31 integer, with truncation (corresponding +// to the 'bits2int' operation in RFC 6979). The target ENCODED bit +// length is provided as last parameter. The resulting value will have +// this declared bit length, and consists the big-endian unsigned decoding +// of exactly that many bits in the source (capped at the source length). +fn bits2int(x: []bigint::word, src: []u8, ebitlen: bigint::word) void = { + let sc: i32 = 0; + let l: size = len(src); + + let bitlen: u32 = ebitlen - (ebitlen >> 5); + let hbitlen = len(src): u32 << 3; + if (hbitlen > bitlen) { + l = (bitlen + 7) >> 3; + sc = ((hbitlen - bitlen) & 7): i32; + }; + + bigint::zero(x, ebitlen); + bigint::encode(x, src[..l]); + bigint::rshift(x, sc: bigint::word); + x[0] = ebitlen; +};