@muevoapp/js-ethereum-cryptography
Version:
All the cryptographic primitives used in Ethereum
330 lines (267 loc) • 8.93 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var assert = _interopDefault(require('assert'));
var safeBuffer = _interopDefault(require('safe-buffer'));
var bs58check = _interopDefault(require('bs58check'));
var reactNative = _interopDefault(require('react-native'));
var reactNativeHash = _interopDefault(require('react-native-hash'));
var cryptoShim = require("../shims/hdkey-crypto");
var secp256k1Shim = require("../shims/hdkey-secp256k1v3");
var Buffer = safeBuffer.Buffer;
var Platform = reactNative.Platform;
var CONSTANTS = reactNativeHash.CONSTANTS;
var MASTER_SECRET = Buffer.from('Bitcoin seed', 'utf8');
var HARDENED_OFFSET = 0x80000000;
var LEN = 78;
// Bitcoin hardcoded by default, can use package `coininfo` for others
var BITCOIN_VERSIONS = { private: 0x0488ade4, public: 0x0488b21e };
function HDKey(versions) {
this.versions = versions || BITCOIN_VERSIONS;
this.depth = 0;
this.index = 0;
this._privateKey = null;
this._publicKey = null;
this.chainCode = null;
this._fingerprint = 0;
this.parentFingerprint = 0;
}
Object.defineProperty(HDKey.prototype, 'fingerprint', {
get: function () {
return this._fingerprint;
},
});
Object.defineProperty(HDKey.prototype, 'identifier', {
get: function () {
return this._identifier;
},
});
Object.defineProperty(HDKey.prototype, 'pubKeyHash', {
get: function () {
return this.identifier;
},
});
Object.defineProperty(HDKey.prototype, 'privateKey', {
get: function () {
return this._privateKey;
},
set: function (value) {
assert.equal(value.length, 32, 'Private key must be 32 bytes.');
assert(secp256k1Shim.privateKeyVerify(value) === true, 'Invalid private key');
this._privateKey = value;
this._publicKey = Buffer.from(secp256k1Shim.publicKeyCreate(value, true));
this._identifier = hash160(this.publicKey);
this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0);
},
});
Object.defineProperty(HDKey.prototype, 'publicKey', {
get: function () {
return this._publicKey;
},
set: function (value) {
assert(
value.length === 33 || value.length === 65,
'Public key must be 33 or 65 bytes.'
);
assert(secp256k1Shim.publicKeyVerify(value) === true, 'Invalid public key');
this._publicKey = Buffer.from(secp256k1Shim.publicKeyConvert(value, true)); // force compressed point
this._identifier = hash160(this.publicKey);
this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0);
this._privateKey = null;
},
});
Object.defineProperty(HDKey.prototype, 'privateExtendedKey', {
get: function () {
if (this._privateKey)
return bs58check.encode(
serialize(
this,
this.versions.private,
Buffer.concat([Buffer.alloc(1, 0), this.privateKey])
)
);
else return null;
},
});
Object.defineProperty(HDKey.prototype, 'publicExtendedKey', {
get: function () {
return bs58check.encode(
serialize(this, this.versions.public, this.publicKey)
);
},
});
HDKey.prototype.derive = function (path) {
if (path === 'm' || path === 'M' || path === "m'" || path === "M'") {
return this;
}
var entries = path.split('/');
var hdkey = this;
entries.forEach(function (c, i) {
if (i === 0) {
assert(/^[mM]{1}/.test(c), 'Path must start with "m" or "M"');
return;
}
var hardened = c.length > 1 && c[c.length - 1] === "'";
var childIndex = parseInt(c, 10); // & (HARDENED_OFFSET - 1)
assert(childIndex < HARDENED_OFFSET, 'Invalid index');
if (hardened) childIndex += HARDENED_OFFSET;
hdkey = hdkey.deriveChild(childIndex);
});
return hdkey;
};
HDKey.prototype.deriveChild = function (index) {
// will be a promise..
var isHardened = index >= HARDENED_OFFSET;
var indexBuffer = Buffer.allocUnsafe(4);
indexBuffer.writeUInt32BE(index, 0);
var data;
if (isHardened) {
// Hardened child
assert(this.privateKey, 'Could not derive hardened child key');
var pk = this.privateKey;
var zb = Buffer.alloc(1, 0);
pk = Buffer.concat([zb, pk]);
// data = 0x00 || ser256(kpar) || ser32(index)
data = Buffer.concat([pk, indexBuffer]);
} else {
// Normal child
// data = serP(point(kpar)) || ser32(index)
// = serP(Kpar) || ser32(index)
data = Buffer.concat([this.publicKey, indexBuffer]);
}
var I;
if (Platform.OS === 'android') {
RNHash.generateHmac(
'message',
'secretKey',
CONSTANTS.HmacAlgorithms.HmacSHA512
)
.then((HMAC) => console.log(HMAC))
.catch((er) => console.log(er));
I = cryptoShim.createHmac('sha512', this.chainCode).update(data).digest();
}
if (Platform.OS === 'ios') {
I = cryptoShim.createHmac('sha512', this.chainCode).update(data).digest();
}
var IL = I.slice(0, 32);
var IR = I.slice(32);
var hd = new HDKey(this.versions);
// Private parent key -> private child key
if (this.privateKey) {
// ki = parse256(IL) + kpar (mod n)
try {
hd.privateKey = Buffer.from(
secp256k1Shim.privateKeyTweakAdd(Buffer.from(this.privateKey), IL)
);
// throw if IL >= n || (privateKey + IL) === 0
} catch (err) {
// In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i
return this.deriveChild(index + 1);
}
// Public parent key -> public child key
} else {
// Ki = point(parse256(IL)) + Kpar
// = G*IL + Kpar
try {
hd.publicKey = Buffer.from(
secp256k1Shim.publicKeyTweakAdd(Buffer.from(this.publicKey), IL, true)
);
// throw if IL >= n || (g**IL + publicKey) is infinity
} catch (err) {
// In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i
return this.deriveChild(index + 1);
}
}
hd.chainCode = IR;
hd.depth = this.depth + 1;
hd.parentFingerprint = this.fingerprint; // .readUInt32BE(0)
hd.index = index;
return hd;
};
HDKey.prototype.sign = function (hash) {
return Buffer.from(secp256k1Shim.ecdsaSign(hash, this.privateKey).signature);
};
HDKey.prototype.verify = function (hash, signature) {
return secp256k1Shim.ecdsaVerify(
Uint8Array.from(signature),
Uint8Array.from(hash),
Uint8Array.from(this.publicKey)
);
};
HDKey.prototype.wipePrivateData = function () {
if (this._privateKey)
cryptoShim.randomBytes(this._privateKey.length).copy(this._privateKey);
this._privateKey = null;
return this;
};
HDKey.prototype.toJSON = function () {
return {
xpriv: this.privateExtendedKey,
xpub: this.publicExtendedKey,
};
};
HDKey.fromMasterSeed = function (seedBuffer, versions) {
// will be a promise..
var I = cryptoShim
.createHmac('sha512', MASTER_SECRET)
.update(seedBuffer)
.digest();
var IL = I.slice(0, 32);
var IR = I.slice(32);
var hdkey = new HDKey(versions);
hdkey.chainCode = IR;
hdkey.privateKey = IL;
return hdkey;
};
HDKey.fromExtendedKey = function (base58key, versions) {
// => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
versions = versions || BITCOIN_VERSIONS;
var hdkey = new HDKey(versions);
var keyBuffer = bs58check.decode(base58key);
var version = keyBuffer.readUInt32BE(0);
assert(
version === versions.private || version === versions.public,
'Version mismatch: does not match private or public'
);
hdkey.depth = keyBuffer.readUInt8(4);
hdkey.parentFingerprint = keyBuffer.readUInt32BE(5);
hdkey.index = keyBuffer.readUInt32BE(9);
hdkey.chainCode = keyBuffer.slice(13, 45);
var key = keyBuffer.slice(45);
if (key.readUInt8(0) === 0) {
// private
assert(
version === versions.private,
'Version mismatch: version does not match private'
);
hdkey.privateKey = key.slice(1); // cut off first 0x0 byte
} else {
assert(
version === versions.public,
'Version mismatch: version does not match public'
);
hdkey.publicKey = key;
}
return hdkey;
};
HDKey.fromJSON = function (obj) {
return HDKey.fromExtendedKey(obj.xpriv);
};
function serialize(hdkey, version, key) {
// => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
var buffer = Buffer.allocUnsafe(LEN);
buffer.writeUInt32BE(version, 0);
buffer.writeUInt8(hdkey.depth, 4);
var fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000;
buffer.writeUInt32BE(fingerprint, 5);
buffer.writeUInt32BE(hdkey.index, 9);
hdkey.chainCode.copy(buffer, 13);
key.copy(buffer, 45);
return buffer;
}
function hash160(buf) {
var sha = cryptoShim.createHash('sha256').update(buf).digest();
return cryptoShim.createHash('ripemd160').update(sha).digest();
}
HDKey.HARDENED_OFFSET = HARDENED_OFFSET;
var hdkey = HDKey;
module.exports = hdkey;