sjcl.ecc.ecdsa.secretKey.prototype.sign = function(hash, paranoia, k_for_testing) { var R = this._curve.r, l = R.bitLength(); // k_for_testing should ONLY BE SPECIFIED FOR TESTING // specifying it will make the signature INSECURE var k; if (typeof k_for_testing === 'object' && k_for_testing.length > 0 && typeof k_for_testing[0] === 'number') { k = k_for_testing; } else if (typeof k_for_testing === 'string' && /^[0-9a-fA-F]+$/.test(k_for_testing)) { k = sjcl.bn.fromBits(sjcl.codec.hex.toBits(k_for_testing)); } else { // This is the only option that should be used in production k = sjcl.bn.random(R.sub(1), paranoia).add(1); } var r = this._curve.G.mult(k).x.mod(R); var s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)) .mul(k.inverseMod(R)).mod(R); return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); };
function bits2int(bits) { var blen = sjcl.bitArray.bitLength(bits); if (blen > qlen) { return sjcl.bn.fromBits(sjcl.bitArray.clamp(bits, qlen)); } return sjcl.bn.fromBits(bits); }
/** * Retrieve the r and s components of a signature * * @param {sjcl.ecc.curve} curve - curve * @param {bitArray} signature - signature * @returns {Object} Object with 'r' and 's' fields each as an sjcl.bn */ function getRandSFromSignature(curve, signature) { var r_length = curve.r.bitLength(); return { r: sjcl.bn.fromBits(sjcl.bitArray.bitSlice(signature, 0, r_length)), s: sjcl.bn.fromBits(sjcl.bitArray.bitSlice( signature, r_length, sjcl.bitArray.bitLength(signature))) }; }
it('0xff7fffffff', function () { var val = new sjcl.bn("ff7fffffff"); assert.strictEqual(val.testBit(32), 1); assert.strictEqual(val.testBit(31), 0); assert.strictEqual(val.testBit(30), 1); assert.strictEqual(val.testBit(24), 1); assert.strictEqual(val.testBit(23), 1); assert.strictEqual(val.testBit(22), 1); assert.strictEqual(val.testBit( 1), 1); assert.strictEqual(val.testBit( 0), 1); });
sjcl.ecc.ecdsa.publicKey.prototype.verify = function(hash, rs) { var w = sjcl.bitArray; var R = this._curve.r; var l = R.bitLength(); var r = sjcl.bn.fromBits(w.bitSlice(rs, 0, l)); var s = sjcl.bn.fromBits(w.bitSlice(rs, l, 2 * l)); var sInv = s.inverseMod(R); var hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R); var hA = r.mul(sInv).mod(R); var r2 = this._curve.G.mult2(hG, hA, this._point).x; if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) { throw (new sjcl.exception.corrupt('signature didn\'t check out')); } return true; };
/** * Get a random number based on the curve * * @param {sjcl.ecc.curve} curve * @param {number} paranoia * @returns {sjcl.bn} */ getRandomNumber(curve, paranoia) { if (!(curve instanceof sjcl.ecc.curve)) { throw new exception.InvalidCurve(curve); } paranoia = paranoia || 0; return sjcl.bn.random(curve.r, paranoia); }
it('0x1000000', function () { var val = new sjcl.bn("1000000"); assert.strictEqual(val.testBit(25), 0); assert.strictEqual(val.testBit(24), 1); assert.strictEqual(val.testBit(23), 0); assert.strictEqual(val.testBit( 1), 0); assert.strictEqual(val.testBit( 0), 0); });
.end(function(err, resp) { if (err || !resp) { return fn(new Error('Could not query PAKDF server ' + opts.host)); } var data = resp.body || resp.text ? JSON.parse(resp.text) : {}; if (data.result !== 'success') { return fn(new Error('Could not query PAKDF server '+opts.host)); } var iSignres = new sjcl.bn(String(data.signres)); var iRandomInv = iRandom.inverseMod(iModulus); var iSigned = iSignres.mulmod(iRandomInv, iModulus); var key = iSigned.toBits(); var result = { }; tokens.forEach(function(token) { result[token] = keyHash(key, token); }); fn(null, result); });
sjcl.ecc.ecdsa.secretKey.prototype.signWithRecoverablePublicKey = function( hash, paranoia, k_for_testing) { var self = this; // Convert hash to bits and determine encoding for output var hash_bits; if (typeof hash === 'object' && hash.length > 0 && typeof hash[0] === 'number') { hash_bits = hash; } else { throw new sjcl.exception.invalid('hash. Must be a bitArray'); } // Sign hash with standard, canonicalized method var standard_signature = self.sign(hash_bits, paranoia, k_for_testing); var canonical_signature = self.canonicalizeSignature(standard_signature); // Extract r and s signature components from canonical signature var r_and_s = getRandSFromSignature(self._curve, canonical_signature); // Rederive public key var public_key = self._curve.G.mult(sjcl.bn.fromBits(self.get())); // Determine recovery factor based on which possible value // returns the correct public key var recovery_factor = calculateRecoveryFactor(self._curve, r_and_s.r, r_and_s.s, hash_bits, public_key); // Prepend recovery_factor to signature and encode in DER // The value_to_prepend should be 4 bytes total var value_to_prepend = recovery_factor + 27; var final_signature_bits = sjcl.bitArray.concat([value_to_prepend], canonical_signature); // Return value in bits return final_signature_bits; };
it('0x0f set bit 4 => 0x1f', function () { var val = new sjcl.bn("0f"); val.setBitM(4); assert.strictEqual(val.toString(), '0x1f'); });
it('0x03', function () { var val = new sjcl.bn("03"); assert.strictEqual(val.testBit(0), 1); assert.strictEqual(val.testBit(1), 1); assert.strictEqual(val.testBit(2), 0); });
function(hash, hashObject) { var curve = this._curve var qlen = this._curveBitLength; /* Utility functions */ /* used to generate k and v */ function repeat(str, times) { return (new Array(times + 1)).join(str); } function bits2int(bits) { var blen = sjcl.bitArray.bitLength(bits); if (blen > qlen) { return sjcl.bn.fromBits(sjcl.bitArray.clamp(bits, qlen)); } return sjcl.bn.fromBits(bits); } function int2octets(integer) { var iModQ = integer.mulmod(new sjcl.bn(1), curve.r); var rlen = 8 * Math.ceil(qlen / 8); var ilen = iModQ.bitLength(); return sjcl.bitArray.concat( sjcl.codec.hex.toBits(repeat('0', Math.ceil((rlen - ilen) / 4))), iModQ.toBits() ); } function bits2octets(bits) { return int2octets(bits2int(bits).mulmod(new sjcl.bn(1), curve.r)); } function hmac() { var params = Array.prototype.slice.call(arguments); var key = params.shift(); var hmacK = new sjcl.misc.hmac(key, hashObject); var bits = params[0]; for (var i = 1; i < params.length; i++) { bits = sjcl.bitArray.concat(bits, params[i]); } return hmacK.encrypt(bits); } var hlen = sjcl.bitArray.bitLength(hash); var x = sjcl.bn.fromBits(this.get()); var k = sjcl.codec.hex.toBits(repeat('00', Math.ceil(hlen / 8))); var v = sjcl.codec.hex.toBits(repeat('01', Math.ceil(hlen / 8))); k = hmac( k, v, sjcl.codec.hex.toBits('00'), int2octets(x), bits2octets(hash) ); v = hmac(k, v); k = hmac( k, v, sjcl.codec.hex.toBits('01'), int2octets(x), bits2octets(hash) ); v = hmac(k, v); v = hmac(k, v); var T = sjcl.bn.fromBits(v); while ( T.bitLength() < qlen ) { v = hmac(k, v); T = sjcl.bn.fromBits(sjcl.bitArray.concat(T.toBits(), v)); } T = bits2int(T.toBits()); while (!(T.greaterEquals(1)) || (T.greaterEquals(curve.r))) { k = hmac( k, v, sjcl.codec.hex.toBits('00') ); v = hmac(k, v); T = sjcl.bn.fromBits(v); while ( T.bitLength() < qlen ) { v = hmac(k, v); T = sjcl.bn.fromBits(sjcl.bitArray.concat(T.toBits(), v)); } T = bits2int(T.toBits()); } return T; };
/** * Recover the public key from the signature. * * @param {sjcl.ecc.curve} curve * @param {sjcl.bn} r * @param {sjcl.bn} s * @param {bitArray} hash_bits * @param {Number, 0-3} recovery_factor * @returns {sjcl.point} Public key corresponding to signature */ function recoverPublicKeyPointFromSignature(curve, signature_r, signature_s, hash_bits, recovery_factor) { var field_order = curve.r; var field_modulus = curve.field.modulus; // Reduce the recovery_factor to the two bits used recovery_factor = recovery_factor & 3; // The less significant bit specifies whether the y coordinate // of the compressed point is even or not. var compressed_point_y_coord_is_even = recovery_factor & 1; // The more significant bit specifies whether we should use the // first or second candidate key. var use_second_candidate_key = recovery_factor >> 1; // Calculate (field_order + 1) / 4 if (!FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR) { FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR = field_modulus.add(1).div(4); } // In the paper they write "1. For j from 0 to h do the following..." // That is not necessary here because we are given the recovery_factor // step 1.1 Let x = r + jn // Here "j" is either 0 or 1 var x; if (use_second_candidate_key) { x = signature_r.add(field_order); } else { x = signature_r; } // step 1.2 and 1.3 convert x to an elliptic curve point // Following formula in section 2.3.4 Octet-String-to-Elliptic-Curve-Point // Conversion var alpha = x.mul(x).mul(x).add(curve.a.mul(x)).add(curve.b).mod( field_modulus); var beta = alpha.powermod(FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR, field_modulus); // If beta is even but y isn't or // if beta is odd and y is even // then subtract beta from the field_modulus var y; var beta_is_even = beta.mod(2).equals(0); if (beta_is_even && !compressed_point_y_coord_is_even || !beta_is_even && compressed_point_y_coord_is_even) { y = beta; } else { y = field_modulus.sub(beta); } // generated_point_R is the point generated from x and y var generated_point_R = new sjcl.ecc.point(curve, x, y); // step 1.4 check that R is valid and R x field_order !== infinity // TODO: add check for R x field_order === infinity if (!generated_point_R.isValidPoint()) { throw new sjcl.exception.corrupt( 'point R. Not a valid point on the curve. Cannot recover public key'); } // step 1.5 Compute e from M var message_e = sjcl.bn.fromBits(hash_bits); var message_e_neg = new sjcl.bn(0).sub(message_e).mod(field_order); // step 1.6 Compute Q = r^-1 (sR - eG) // console.log('r: ', signature_r); var signature_r_inv = signature_r.inverseMod(field_order); var public_key_point = generated_point_R.mult2(signature_s, message_e_neg, curve.G).mult(signature_r_inv); // Validate public key point if (!public_key_point.isValidPoint()) { throw new sjcl.exception.corrupt('public_key_point. Not a valid point' + ' on the curve. Cannot recover public key'); } // Verify that this public key matches the signature if (!verify_raw(curve, message_e, signature_r, signature_s, public_key_point)) { throw new sjcl.exception.corrupt('cannot recover public key'); } return public_key_point; }
Crypt.derive = function(opts, purpose, username, secret, fn) { var tokens; if (purpose === 'login') { tokens = ['id', 'crypt']; } else { tokens = ['unlock']; } var iExponent = new sjcl.bn(String(opts.exponent)); var iModulus = new sjcl.bn(String(opts.modulus)); var iAlpha = new sjcl.bn(String(opts.alpha)); var publicInfo = [ 'PAKDF_1_0_0', opts.host.length, opts.host, username.length, username, purpose.length, purpose ].join(':') + ':'; var publicSize = Math.ceil(Math.min((7 + iModulus.bitLength()) >>> 3, 256) / 8); var publicHash = fdh(publicInfo, publicSize); var publicHex = sjcl.codec.hex.fromBits(publicHash); var iPublic = new sjcl.bn(String(publicHex)).setBitM(0); var secretInfo = [ publicInfo, secret.length, secret ].join(':') + ':'; var secretSize = (7 + iModulus.bitLength()) >>> 3; var secretHash = fdh(secretInfo, secretSize); var secretHex = sjcl.codec.hex.fromBits(secretHash); var iSecret = new sjcl.bn(String(secretHex)).mod(iModulus); if (iSecret.jacobi(iModulus) !== 1) { iSecret = iSecret.mul(iAlpha).mod(iModulus); } var iRandom; for (;;) { iRandom = sjcl.bn.random(iModulus, 0); if (iRandom.jacobi(iModulus) === 1) { break; } } var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus); var iSignreq = iSecret.mulmod(iBlind, iModulus); var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits()); request.post(opts.url) .send({ info: publicInfo, signreq: signreq }) .end(function(err, resp) { if (err || !resp) { return fn(new Error('Could not query PAKDF server ' + opts.host)); } var data = resp.body || resp.text ? JSON.parse(resp.text) : {}; if (data.result !== 'success') { return fn(new Error('Could not query PAKDF server '+opts.host)); } var iSignres = new sjcl.bn(String(data.signres)); var iRandomInv = iRandom.inverseMod(iModulus); var iSigned = iSignres.mulmod(iRandomInv, iModulus); var key = iSigned.toBits(); var result = { }; tokens.forEach(function(token) { result[token] = keyHash(key, token); }); fn(null, result); }); };