Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 105 additions & 91 deletions browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ var ec = new EC("secp256k1");
var browserCrypto = global.crypto || global.msCrypto || {};
var subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;

var nodeCrypto = require('crypto');
var nodeCrypto = require("crypto");

const EC_GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex');
const EC_GROUP_ORDER = Buffer.from(
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
"hex"
);
const ZERO32 = Buffer.alloc(32, 0);

function assert(condition, message) {
Expand All @@ -17,17 +20,17 @@ function assert(condition, message) {
}
}

function isScalar (x) {
function isScalar(x) {
return Buffer.isBuffer(x) && x.length === 32;
}

function isValidPrivateKey(privateKey) {
if (!isScalar(privateKey))
{
if (!isScalar(privateKey)) {
return false;
}
return privateKey.compare(ZERO32) > 0 && // > 0
privateKey.compare(EC_GROUP_ORDER) < 0; // < G
return (
privateKey.compare(ZERO32) > 0 && privateKey.compare(EC_GROUP_ORDER) < 0 // > 0
); // < G
}

// Compare two buffers in constant time to prevent timing attacks.
Expand All @@ -37,7 +40,7 @@ function equalConstTime(b1, b2) {
}
var res = 0;
for (var i = 0; i < b1.length; i++) {
res |= b1[i] ^ b2[i]; // jshint ignore:line
res |= b1[i] ^ b2[i]; // jshint ignore:line
}
return res === 0;
}
Expand All @@ -47,7 +50,7 @@ not, since the functions are different and does
not convert using browserify */
function randomBytes(size) {
var arr = new Uint8Array(size);
if (typeof window === 'undefined') {
if (typeof window === "undefined") {
return Buffer.from(nodeCrypto.randomBytes(size));
} else {
browserCrypto.getRandomValues(arr);
Expand All @@ -57,7 +60,7 @@ function randomBytes(size) {

function sha512(msg) {
return new Promise(function(resolve) {
var hash = nodeCrypto.createHash('sha512');
var hash = nodeCrypto.createHash("sha512");
var result = hash.update(msg).digest();
resolve(new Uint8Array(result));
});
Expand All @@ -67,24 +70,24 @@ function getAes(op) {
return function(iv, key, data) {
return new Promise(function(resolve) {
if (subtle) {
var importAlgorithm = {name: "AES-CBC"};
var importAlgorithm = { name: "AES-CBC" };
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
return keyp.then(function(cryptoKey) {
var encAlgorithm = {name: "AES-CBC", iv: iv};
return subtle[op](encAlgorithm, cryptoKey, data);
}).then(function(result) {
resolve(Buffer.from(new Uint8Array(result)));
});
return keyp
.then(function(cryptoKey) {
var encAlgorithm = { name: "AES-CBC", iv: iv };
return subtle[op](encAlgorithm, cryptoKey, data);
})
.then(function(result) {
resolve(Buffer.from(new Uint8Array(result)));
});
} else {
if (op === 'encrypt') {
var cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
cipher.update(data);
resolve(cipher.final());
}
else if (op === 'decrypt') {
var decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
decipher.update(data);
resolve(decipher.final());
if (op === "encrypt") {
var cipher = nodeCrypto.createCipheriv("aes-256-cbc", key, iv);

resolve(Buffer.concat([cipher.update(data), cipher.final()]));
} else if (op === "decrypt") {
var decipher = nodeCrypto.createDecipheriv("aes-256-cbc", key, iv);
resolve(Buffer.concat([decipher.update(data), decipher.final()]));
}
}
});
Expand All @@ -96,7 +99,7 @@ var aesCbcDecrypt = getAes("decrypt");

function hmacSha256Sign(key, msg) {
return new Promise(function(resolve) {
var hmac = nodeCrypto.createHmac('sha256', Buffer.from(key));
var hmac = nodeCrypto.createHmac("sha256", Buffer.from(key));
hmac.update(msg);
var result = hmac.digest();
resolve(result);
Expand All @@ -105,45 +108,48 @@ function hmacSha256Sign(key, msg) {

function hmacSha256Verify(key, msg, sig) {
return new Promise(function(resolve) {
var hmac = nodeCrypto.createHmac('sha256', Buffer.from(key));
var hmac = nodeCrypto.createHmac("sha256", Buffer.from(key));
hmac.update(msg);
var expectedSig = hmac.digest();
resolve(equalConstTime(expectedSig, sig));
});
}

/**
* Generate a new valid private key. Will use the window.crypto or window.msCrypto as source
* depending on your browser.
* @return {Buffer} A 32-byte private key.
* @function
*/
exports.generatePrivate = function () {
* Generate a new valid private key. Will use the window.crypto or window.msCrypto as source
* depending on your browser.
* @return {Buffer} A 32-byte private key.
* @function
*/
exports.generatePrivate = function() {
var privateKey = randomBytes(32);
while (!isValidPrivateKey(privateKey)) {
privateKey = randomBytes(32);
}
return privateKey;
};

var getPublic = exports.getPublic = function(privateKey) {
var getPublic = (exports.getPublic = function(privateKey) {
// This function has sync API so we throw an error immediately.
assert(privateKey.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKey), "Bad private key");
// XXX(Kagami): `elliptic.utils.encode` returns array for every
// encoding except `hex`.
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("arr"));
};
});

/**
* Get compressed version of public key.
*/
var getPublicCompressed = exports.getPublicCompressed = function(privateKey) { // jshint ignore:line
exports.getPublicCompressed = function(privateKey) {
// jshint ignore:line
assert(privateKey.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKey), "Bad private key");
// See https://github.com/wanderer/secp256k1-node/issues/46
let compressed = true;
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "arr"));
return Buffer.from(
ec.keyFromPrivate(privateKey).getPublic(compressed, "arr")
);
};

// NOTE(Kagami): We don't use promise shim in Browser implementation
Expand All @@ -157,19 +163,20 @@ exports.sign = function(privateKey, msg) {
assert(isValidPrivateKey(privateKey), "Bad private key");
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
resolve(Buffer.from(ec.sign(msg, privateKey, {canonical: true}).toDER()));
resolve(Buffer.from(ec.sign(msg, privateKey, { canonical: true }).toDER()));
});
};

exports.verify = function(publicKey, msg, sig) {
return new Promise(function(resolve, reject) {
assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key");
if (publicKey.length === 65)
{
assert(
publicKey.length === 65 || publicKey.length === 33,
"Bad public key"
);
if (publicKey.length === 65) {
assert(publicKey[0] === 4, "Bad public key");
}
if (publicKey.length === 33)
{
if (publicKey.length === 33) {
assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key");
}
assert(msg.length > 0, "Message should not be empty");
Expand All @@ -182,27 +189,28 @@ exports.verify = function(publicKey, msg, sig) {
});
};

var derive = exports.derive = function(privateKeyA, publicKeyB) {
var derive = (exports.derive = function(privateKeyA, publicKeyB) {
return new Promise(function(resolve) {
assert(Buffer.isBuffer(privateKeyA), "Bad private key");
assert(Buffer.isBuffer(publicKeyB), "Bad public key");
assert(privateKeyA.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKeyA), "Bad private key");
assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key");
if (publicKeyB.length === 65)
{
assert(
publicKeyB.length === 65 || publicKeyB.length === 33,
"Bad public key"
);
if (publicKeyB.length === 65) {
assert(publicKeyB[0] === 4, "Bad public key");
}
if (publicKeyB.length === 33)
{
if (publicKeyB.length === 33) {
assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key");
}
var keyA = ec.keyFromPrivate(privateKeyA);
var keyB = ec.keyFromPublic(publicKeyB);
var Px = keyA.derive(keyB.getPublic()); // BN instance
var Px = keyA.derive(keyB.getPublic()); // BN instance
resolve(Buffer.from(Px.toArray()));
});
};
});

exports.encrypt = function(publicKeyTo, msg, opts) {
opts = opts || {};
Expand All @@ -211,52 +219,58 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
return new Promise(function(resolve) {
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
// There is a very unlikely possibility that it is not a valid key
while(!isValidPrivateKey(ephemPrivateKey))
{
while (!isValidPrivateKey(ephemPrivateKey)) {
ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
}
ephemPublicKey = getPublic(ephemPrivateKey);
resolve(derive(ephemPrivateKey, publicKeyTo));
}).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
iv = opts.iv || randomBytes(16);
var encryptionKey = hash.slice(0, 32);
macKey = hash.slice(32);
return aesCbcEncrypt(iv, encryptionKey, msg);
}).then(function(data) {
ciphertext = data;
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
return hmacSha256Sign(macKey, dataToMac);
}).then(function(mac) {
return {
iv: iv,
ephemPublicKey: ephemPublicKey,
ciphertext: ciphertext,
mac: mac,
};
});
})
.then(function(Px) {
return sha512(Px);
})
.then(function(hash) {
iv = opts.iv || randomBytes(16);
var encryptionKey = hash.slice(0, 32);
macKey = hash.slice(32);
return aesCbcEncrypt(iv, encryptionKey, msg);
})
.then(function(data) {
ciphertext = data;
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
return hmacSha256Sign(macKey, dataToMac);
})
.then(function(mac) {
return {
iv: iv,
ephemPublicKey: ephemPublicKey,
ciphertext: ciphertext,
mac: mac
};
});
};

exports.decrypt = function(privateKey, opts) {
// Tmp variable to save context from flat promises;
var encryptionKey;
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
encryptionKey = hash.slice(0, 32);
var macKey = hash.slice(32);
var dataToMac = Buffer.concat([
opts.iv,
opts.ephemPublicKey,
opts.ciphertext
]);
return hmacSha256Verify(macKey, dataToMac, opts.mac);
}).then(function(macGood) {
assert(macGood, "Bad MAC");
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
}).then(function(msg) {
return Buffer.from(new Uint8Array(msg));
});
return derive(privateKey, opts.ephemPublicKey)
.then(function(Px) {
return sha512(Px);
})
.then(function(hash) {
encryptionKey = hash.slice(0, 32);
var macKey = hash.slice(32);
var dataToMac = Buffer.concat([
opts.iv,
opts.ephemPublicKey,
opts.ciphertext
]);
return hmacSha256Verify(macKey, dataToMac, opts.mac);
})
.then(function(macGood) {
assert(macGood, "Bad MAC");
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
})
.then(function(msg) {
return Buffer.from(new Uint8Array(msg));
});
};