/*! * schnorr.js - schnorr signatures for bcoin * Copyright (c) 2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ /** * This software is licensed under the MIT License. * * Copyright (c) 2017, Christopher Jeffrey (https://github.com/chjj) * * 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. */ import elliptic from "elliptic"; import { Buffer } from "buffer"; /* tslint:disable:no-unused-variable */ import hashjs from "hash.js"; import DRBG from "hmac-drbg"; import { BN } from "@zilliqa-js/util"; import { randomBytes } from "./random"; import { Signature } from "."; const ec = elliptic.ec; const secp256k1 = new ec("secp256k1"); const curve = secp256k1.curve; const PRIVKEY_SIZE_BYTES = 32; // Public key is a point (x, y) on the curve. // Each coordinate requires 32 bytes. // In its compressed form it suffices to store the x co-ordinate // and the sign for y. // Hence a total of 33 bytes. const PUBKEY_COMPRESSED_SIZE_BYTES = 33; // Personalization string used for HMAC-DRBG instantiation. const ALG = Buffer.from("Schnorr+SHA256 ", "ascii"); // The length in bytes of the string above. const ALG_LEN = 16; // The length in bytes of entropy inputs to HMAC-DRBG const ENT_LEN = 32; const HEX_ENC: "hex" = "hex"; /** * generatePrivateKey * * @returns {string} - the hex-encoded private key */ export const generatePrivateKey = (): string => { return secp256k1 .genKeyPair({ entropy: randomBytes(secp256k1.curve.n.byteLength()), entropyEnc: HEX_ENC, pers: "zilliqajs+secp256k1+SHA256", }) .getPrivate() .toString(16, PRIVKEY_SIZE_BYTES * 2); }; /** * Hash (r | M). * @param {Buffer} msg * @param {BN} r * * @returns {Buffer} */ export const hash = (q: BN, pubkey: Buffer, msg: Buffer) => { const sha256 = hashjs.sha256(); const totalLength = PUBKEY_COMPRESSED_SIZE_BYTES * 2 + msg.byteLength; // 33 q + 33 pubkey + variable msgLen const Q = q.toArrayLike(Buffer, "be", 33); const B = Buffer.allocUnsafe(totalLength); Q.copy(B, 0); pubkey.copy(B, 33); msg.copy(B, 66); return new BN(sha256.update(B).digest("hex"), 16); }; /** * sign * * @param {Buffer} msg * @param {Buffer} key * @param {Buffer} pubkey * * @returns {Signature} */ export const sign = ( msg: Buffer, privKey: Buffer, pubKey: Buffer ): Signature => { const prv = new BN(privKey); const drbg = getDRBG(msg); const len = curve.n.byteLength(); let sig; while (!sig) { const k = new BN(drbg.generate(len)); sig = trySign(msg, k, prv, pubKey); } return sig; }; /** * trySign * * @param {Buffer} msg - the message to sign over * @param {BN} k - output of the HMAC-DRBG * @param {BN} privateKey - the private key * @param {Buffer} pubKey - the public key * * @returns {Signature | null =>} */ export const trySign = ( msg: Buffer, k: BN, privKey: BN, pubKey: Buffer ): Signature | null => { if (privKey.isZero()) { throw new Error("Bad private key."); } if (privKey.gte(curve.n)) { throw new Error("Bad private key."); } // 1a. check that k is not 0 if (k.isZero()) { return null; } // 1b. check that k is < the order of the group if (k.gte(curve.n)) { return null; } // 2. Compute commitment Q = kG, where g is the base point const Q = curve.g.mul(k); // convert the commitment to octets first const compressedQ = new BN(Q.encodeCompressed()); // 3. Compute the challenge r = H(Q || pubKey || msg) // mod reduce the r value by the order of secp256k1, n const r = hash(compressedQ, pubKey, msg).umod(curve.n); const h = r.clone(); if (h.isZero()) { return null; } // 4. Compute s = k - r * prv // 4a. Compute r * prv let s = h.imul(privKey).umod(curve.n); // 4b. Compute s = k - r * prv mod n s = k.isub(s).umod(curve.n); if (s.isZero()) { return null; } return new Signature({ r, s }); }; /** * Verify signature. * * @param {Buffer} msg * @param {Buffer} signature * @param {Buffer} key * * @returns {boolean} * * 1. Check if r,s is in [1, ..., order-1] * 2. Compute Q = sG + r*kpub * 3. If Q = O (the neutral point), return 0; * 4. r' = H(Q, kpub, m) * 5. return r' == r */ export const verify = (msg: Buffer, signature: Signature, key: Buffer) => { const sig = new Signature(signature); if (sig.s.isZero() || sig.r.isZero()) { throw new Error("Invalid signature"); } if (sig.s.isNeg() || sig.r.isNeg()) { throw new Error("Invalid signature"); } if (sig.s.gte(curve.n) || sig.r.gte(curve.n)) { throw new Error("Invalid signature"); } const kpub = curve.decodePoint(key); if (!curve.validate(kpub)) { throw new Error("Invalid public key"); } const l = kpub.mul(sig.r); const r = curve.g.mul(sig.s); const Q = l.add(r); if (Q.isInfinity()) { throw new Error("Invalid intermediate point."); } const compressedQ = new BN(Q.encodeCompressed()); const r1 = hash(compressedQ, key, msg).umod(curve.n); if (r1.isZero()) { throw new Error("Invalid hash."); } return r1.eq(sig.r); }; export const toSignature = (serialised: string): Signature => { const r = serialised.slice(0, 64); const s = serialised.slice(64); return new Signature({ r, s }); }; /** * Instantiate an HMAC-DRBG. * * @param {Buffer} msg - used as nonce * * @returns {DRBG} */ const getDRBG = (msg: Buffer) => { const entropy = randomBytes(ENT_LEN); const pers = Buffer.allocUnsafe(ALG_LEN + ENT_LEN); Buffer.from(randomBytes(ENT_LEN)).copy(pers, 0); ALG.copy(pers, ENT_LEN); return new DRBG({ hash: hashjs.sha256, entropy, nonce: msg, pers, }); };