@toruslabs/metadata-helpers
Version:
Helper methods for metadata
143 lines (139 loc) • 5.6 kB
JavaScript
;
var modular_js = require('@noble/curves/abstract/modular.js');
var ed25519_js = require('@noble/curves/ed25519.js');
var secp256k1_js = require('@noble/curves/secp256k1.js');
var utils_js = require('@noble/curves/utils.js');
var sha2_js = require('@noble/hashes/sha2.js');
var sha3_js = require('@noble/hashes/sha3.js');
var base = require('@scure/base');
var hex = require('./hex.js');
var number = require('./number.js');
const getSecp256k1 = () => secp256k1_js.secp256k1;
const getEd25519 = () => ed25519_js.ed25519;
const getKeyCurve = keyType => {
if (keyType === "secp256k1") return getSecp256k1();
if (keyType === "ed25519") return getEd25519();
throw new Error(`Invalid keyType: ${keyType}`);
};
function derivePubKey(ecCurve, sk) {
return ecCurve.Point.BASE.multiply(sk).toAffine();
}
function generatePrivateKey(ecCurveOrKeyType) {
const ec = typeof ecCurveOrKeyType === "string" ? getKeyCurve(ecCurveOrKeyType) : ecCurveOrKeyType;
return ec.utils.randomSecretKey();
}
/**
* Returns keccak256 hash as hex string (torus.js–compatible name). By default 0x-prefixed.
* Use keccak256Bytes when you need raw bytes.
*
* @param a - Input bytes.
* @param options - Optional. Set `{ prefixed: false }` to omit "0x" prefix.
*/
function keccak256(a, options) {
const hash = utils_js.bytesToHex(sha3_js.keccak_256(a));
return (options === null || options === void 0 ? void 0 : options.prefixed) === false ? hash : `0x${hash}`;
}
/** Returns keccak256 hash as raw bytes. */
function keccak256Bytes(a) {
return sha3_js.keccak_256(a);
}
function adjustScalarBytes(bytes) {
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
// set the three least significant bits of the first byte
bytes[0] &= 248; // 0b1111_1000
// and the most significant bit of the last to zero,
bytes[31] &= 127; // 0b0111_1111
// set the second most significant bit of the last byte to 1
bytes[31] |= 64; // 0b0100_0000
return bytes;
}
/** Ed25519 extended public key derivation per RFC 8032 5.1.5 */
function getEd25519ExtendedPublicKey(keyBytes) {
const ed25519Curve = getKeyCurve("ed25519");
const N = ed25519Curve.Point.CURVE().n;
if (keyBytes.length !== 32) {
throw new Error("Invalid seed for ed25519 key derivation");
}
const hashed = sha2_js.sha512(keyBytes);
if (hashed.length !== 64) {
throw new Error("Invalid hash length for ed25519 seed");
}
const head = utils_js.bytesToNumberLE(adjustScalarBytes(new Uint8Array(hashed.slice(0, 32))));
const scalar = modular_js.mod(head, N);
const point = derivePubKey(ed25519Curve, scalar);
return {
scalar,
point
};
}
function encodeEd25519Point(point) {
const ed25519Curve = getKeyCurve("ed25519");
return ed25519Curve.Point.fromAffine(point).toBytes();
}
/** Derives a secp256k1 key pair from an Ed25519 scalar via keccak hash. */
const getSecpKeyFromEd25519 = ed25519Scalar => {
const secp = getSecp256k1();
const N = secp.Point.CURVE().n;
const keyHash = sha3_js.keccak_256(utils_js.numberToBytesBE(ed25519Scalar, 32));
const secpScalar = modular_js.mod(utils_js.bytesToNumberBE(keyHash), N);
const point = derivePubKey(secp, secpScalar);
return {
scalar: secpScalar,
point
};
};
/** Encodes a secp256k1 affine point as 64-byte uncompressed key (without 04 prefix). */
function getSecp256k1PublicKeyFromAffinePoint(point) {
const uncompressed = getSecp256k1().Point.fromAffine(point).toBytes(false);
return uncompressed.slice(1);
}
function generateAddressFromPoint(keyType, point) {
if (keyType === "secp256k1") {
const publicKey = getSecp256k1PublicKeyFromAffinePoint(point);
const evmAddressLower = `0x${keccak256(publicKey).slice(64 - 38)}`;
return hex.getChecksumAddress(evmAddressLower);
} else if (keyType === "ed25519") {
const publicKey = encodeEd25519Point(point);
return base.base58.encode(publicKey);
}
throw new Error(`Invalid keyType: ${keyType}`);
}
function generateAddressFromPrivKey(keyType, privateKey) {
const ecCurve = getKeyCurve(keyType);
const point = derivePubKey(ecCurve, privateKey);
return generateAddressFromPoint(keyType, point);
}
function generateAddressFromPubKey(keyType, publicKeyX, publicKeyY) {
return generateAddressFromPoint(keyType, {
x: publicKeyX,
y: publicKeyY
});
}
/**
* Derive postbox key: privKey - nonce (mod n).
* Inputs accept hex with or without 0x prefix.
* By default returns raw hex (no prefix) for backward compat.
*
* @param options - Optional. Set `{ prefixed: true }` to add "0x" prefix.
*/
function getPostboxKeyFrom1OutOf1(ecCurve, privKey, nonce, options) {
const privKeyBI = number.toBigIntBE(privKey);
const nonceBI = number.toBigIntBE(nonce);
const n = ecCurve.Point.CURVE().n;
const result = modular_js.mod(privKeyBI - nonceBI, n);
return number.bigintToHex(result, 64, options);
}
exports.derivePubKey = derivePubKey;
exports.encodeEd25519Point = encodeEd25519Point;
exports.generateAddressFromPrivKey = generateAddressFromPrivKey;
exports.generateAddressFromPubKey = generateAddressFromPubKey;
exports.generatePrivateKey = generatePrivateKey;
exports.getEd25519 = getEd25519;
exports.getEd25519ExtendedPublicKey = getEd25519ExtendedPublicKey;
exports.getKeyCurve = getKeyCurve;
exports.getPostboxKeyFrom1OutOf1 = getPostboxKeyFrom1OutOf1;
exports.getSecp256k1 = getSecp256k1;
exports.getSecp256k1PublicKeyFromAffinePoint = getSecp256k1PublicKeyFromAffinePoint;
exports.getSecpKeyFromEd25519 = getSecpKeyFromEd25519;
exports.keccak256 = keccak256;
exports.keccak256Bytes = keccak256Bytes;