UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

149 lines (148 loc) 6.11 kB
import { bitLength, divUp, roundUp } from "../../types/bigint/module.f.js"; import { empty, length, listToVec, msb, repeat, unpack, vec, vec8 } from "../../types/bit_vec/module.f.js"; import { hmac } from "../hmac/module.f.js"; import { computeSync } from "../sha2/module.f.js"; // qlen to rlen const roundUp8 = roundUp(8n); const divUp8 = divUp(8n); export const all = (q) => { const qlen = bitLength(q); const bits2int = (b) => { const { length, uint } = unpack(b); const diff = length - qlen; return diff > 0n ? uint >> diff : uint; }; const int2octets = vec(roundUp8(qlen)); return { q, qlen, bits2int, int2octets, // since z2 < 2*q, we can use simple mod with `z1 < q ? z1 : z1 - q` bits2octets: b => int2octets(bits2int(b) % q), }; }; export const fromCurve = (c) => all(c.nf.p); const x01 = vec8(0x01n); const x00 = vec8(0x00n); const ltov = listToVec(msb); export const concat = (...x) => ltov(x); export const computeK = ({ q, bits2int, qlen, int2octets, bits2octets }) => hf => { // TODO: Look at https://www.rfc-editor.org/rfc/rfc6979#section-3.3 to reformulate // it using `HMAC_DRBG`. const hmacf = hmac(hf); // b. Set: // V = 0x01 0x01 0x01 ... 0x01 // such that the length of V, in bits, is equal to 8*ceil(hlen/8). // For instance, on an octet-based system, if H is SHA-256, then V // is set to a sequence of 32 octets of value 1. Note that in this // step and all subsequent steps, we use the same H function as the // one used in step 'a' to process the input message; this choice // will be discussed in more detail in Section 3.6. const rep = repeat(divUp8(hf.hashLength)); const v0 = rep(x01); // c. Set: // K = 0x00 0x00 0x00 ... 0x00 // such that the length of K, in bits, is equal to 8*ceil(hlen/8). const k0 = rep(x00); // return x => m => { let v = v0; let k = k0; // a. Process m through the hash function H, yielding: // h1 = H(m) // (h1 is a sequence of hlen bits). const h1 = computeSync(hf)([m]); // d. Set: // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1)) // where '||' denotes concatenation. const xh1 = concat(int2octets(x), bits2octets(h1)); k = hmacf(k)(concat(v, x00, xh1)); // e. Set: // V = HMAC_K(V) v = hmacf(k)(v); // f. Set: // K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1)) k = hmacf(k)(concat(v, x01, xh1)); // g. Set: // V = HMAC_K(V) v = hmacf(k)(v); // h. Apply the following algorithm until a proper value is for `k`: while (true) { // h. Apply the following algorithm until a proper value is for `k`: // 1. Set `T` to the empty sequence, so `tlen = 0`. let t = empty; // 2. while `tlen < qlen` do: // - `V = HMAC_K(V)` // - `T = T || V` // Possible optimizations: // - precompute number of iterations // - `qlen` can't be 0, so we can avoid the first check and // first concatenation. while (length(t) < qlen) { v = hmacf(k)(v); t = concat(t, v); } // 3. Compute `k = bits2int(T)`. If `k` is not in `[1, q-1]` or `kG = 0` then // - `K = HMAC_K(V || 0x00)` // - `V = HMAC_K(V)` // and loop (try to generate a new `T`, and so on). Return to step `1`. const result = bits2int(t); if (0n < result && result < q) { return result; } k = hmacf(k)(concat(v, x00)); v = hmacf(k)(v); } }; }; export const sign = (c) => (hf) => (x) => (m) => { // 2.4 Signature Generation const { nf: { p: q, div }, g } = c; const a = all(q); const { bits2int } = a; // The following steps are then applied: // // 1. H(m) is transformed into an integer modulo q using the bits2int // transform and an extra modular reduction: // // h = bits2int(H(m)) mod q // // As was noted in the description of bits2octets, the extra modular // reduction is no more than a conditional subtraction. const hm = computeSync(hf)([m]); const h = bits2int(hm) % q; // 2. A random value modulo q, dubbed k, is generated. That value // shall not be 0; hence, it lies in the [1, q-1] range. Most of // the remainder of this document will revolve around the process // used to generate k. In plain DSA or ECDSA, k should be selected // through a random selection that chooses a value among the q-1 // possible values with uniform probability. const k = computeK(a)(hf)(x)(m); // 3. A value r (modulo q) is computed from k and the key parameters: // // * For ECDSA: the point kG is computed; its X coordinate (a // member of the field over which E is defined) is converted to // an integer, which is reduced modulo q, yielding r. // // If r turns out to be zero, a new k should be selected and r // computed again (this is an utterly improbable occurrence). const rxy = c.mul(k)(g); // TODO: implement the loop. `computeK` should either // - accept a state (current `k`). // - accept a `is_valid` function. if (rxy === null) { throw 'rxy === null'; } const [r] = rxy; // 4. The value s (modulo q) is computed: // // s = (h+x*r)/k mod q // // The pair (r, s) is the signature. How a signature is to be // encoded is not covered by the DSA and ECDSA standards themselves; // a common way is to use a DER-encoded ASN.1 structure (a SEQUENCE // of two INTEGERs, for r and s, in that order). const s = div(h + x * r)(k); return [r, s]; };