2025-04-19 15:38:48 +08:00

162 lines
6.7 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hashPersonalMessage = exports.isValidSignature = exports.fromRpcSig = exports.toCompactSig = exports.toRpcSig = exports.ecrecover = exports.calculateSigRecovery = exports.ecsign = void 0;
const keccak_js_1 = require("ethereum-cryptography/keccak.js");
const secp256k1_js_1 = require("ethereum-cryptography/secp256k1.js");
const bytes_js_1 = require("./bytes.js");
const constants_js_1 = require("./constants.js");
const helpers_js_1 = require("./helpers.js");
/**
* Returns the ECDSA signature of a message hash.
*
* If `chainId` is provided assume an EIP-155-style signature and calculate the `v` value
* accordingly, otherwise return a "static" `v` just derived from the `recovery` bit
*/
function ecsign(msgHash, privateKey, chainId) {
const sig = secp256k1_js_1.secp256k1.sign(msgHash, privateKey);
const buf = sig.toCompactRawBytes();
const r = buf.slice(0, 32);
const s = buf.slice(32, 64);
const v = chainId === undefined
? BigInt(sig.recovery + 27)
: BigInt(sig.recovery + 35) + BigInt(chainId) * constants_js_1.BIGINT_2;
return { r, s, v };
}
exports.ecsign = ecsign;
function calculateSigRecovery(v, chainId) {
if (v === constants_js_1.BIGINT_0 || v === constants_js_1.BIGINT_1)
return v;
if (chainId === undefined) {
return v - constants_js_1.BIGINT_27;
}
return v - (chainId * constants_js_1.BIGINT_2 + BigInt(35));
}
exports.calculateSigRecovery = calculateSigRecovery;
function isValidSigRecovery(recovery) {
return recovery === constants_js_1.BIGINT_0 || recovery === constants_js_1.BIGINT_1;
}
/**
* ECDSA public key recovery from signature.
* NOTE: Accepts `v === 0 | v === 1` for EIP1559 transactions
* @returns Recovered public key
*/
const ecrecover = function (msgHash, v, r, s, chainId) {
const signature = (0, bytes_js_1.concatBytes)((0, bytes_js_1.setLengthLeft)(r, 32), (0, bytes_js_1.setLengthLeft)(s, 32));
const recovery = calculateSigRecovery(v, chainId);
if (!isValidSigRecovery(recovery)) {
throw new Error('Invalid signature v value');
}
const sig = secp256k1_js_1.secp256k1.Signature.fromCompact(signature).addRecoveryBit(Number(recovery));
const senderPubKey = sig.recoverPublicKey(msgHash);
return senderPubKey.toRawBytes(false).slice(1);
};
exports.ecrecover = ecrecover;
/**
* Convert signature parameters into the format of `eth_sign` RPC method.
* NOTE: Accepts `v === 0 | v === 1` for EIP1559 transactions
* @returns Signature
*/
const toRpcSig = function (v, r, s, chainId) {
const recovery = calculateSigRecovery(v, chainId);
if (!isValidSigRecovery(recovery)) {
throw new Error('Invalid signature v value');
}
// geth (and the RPC eth_sign method) uses the 65 byte format used by Bitcoin
return (0, bytes_js_1.bytesToHex)((0, bytes_js_1.concatBytes)((0, bytes_js_1.setLengthLeft)(r, 32), (0, bytes_js_1.setLengthLeft)(s, 32), (0, bytes_js_1.toBytes)(v)));
};
exports.toRpcSig = toRpcSig;
/**
* Convert signature parameters into the format of Compact Signature Representation (EIP-2098).
* NOTE: Accepts `v === 0 | v === 1` for EIP1559 transactions
* @returns Signature
*/
const toCompactSig = function (v, r, s, chainId) {
const recovery = calculateSigRecovery(v, chainId);
if (!isValidSigRecovery(recovery)) {
throw new Error('Invalid signature v value');
}
const ss = Uint8Array.from([...s]);
if ((v > BigInt(28) && v % constants_js_1.BIGINT_2 === constants_js_1.BIGINT_1) || v === constants_js_1.BIGINT_1 || v === BigInt(28)) {
ss[0] |= 0x80;
}
return (0, bytes_js_1.bytesToHex)((0, bytes_js_1.concatBytes)((0, bytes_js_1.setLengthLeft)(r, 32), (0, bytes_js_1.setLengthLeft)(ss, 32)));
};
exports.toCompactSig = toCompactSig;
/**
* Convert signature format of the `eth_sign` RPC method to signature parameters
*
* NOTE: For an extracted `v` value < 27 (see Geth bug https://github.com/ethereum/go-ethereum/issues/2053)
* `v + 27` is returned for the `v` value
* NOTE: After EIP1559, `v` could be `0` or `1` but this function assumes
* it's a signed message (EIP-191 or EIP-712) adding `27` at the end. Remove if needed.
*/
const fromRpcSig = function (sig) {
const bytes = (0, bytes_js_1.toBytes)(sig);
let r;
let s;
let v;
if (bytes.length >= 65) {
r = bytes.subarray(0, 32);
s = bytes.subarray(32, 64);
v = (0, bytes_js_1.bytesToBigInt)(bytes.subarray(64));
}
else if (bytes.length === 64) {
// Compact Signature Representation (https://eips.ethereum.org/EIPS/eip-2098)
r = bytes.subarray(0, 32);
s = bytes.subarray(32, 64);
v = BigInt((0, bytes_js_1.bytesToInt)(bytes.subarray(32, 33)) >> 7);
s[0] &= 0x7f;
}
else {
throw new Error('Invalid signature length');
}
// support both versions of `eth_sign` responses
if (v < 27) {
v = v + constants_js_1.BIGINT_27;
}
return {
v,
r,
s,
};
};
exports.fromRpcSig = fromRpcSig;
/**
* Validate a ECDSA signature.
* NOTE: Accepts `v === 0 | v === 1` for EIP1559 transactions
* @param homesteadOrLater Indicates whether this is being used on either the homestead hardfork or a later one
*/
const isValidSignature = function (v, r, s, homesteadOrLater = true, chainId) {
if (r.length !== 32 || s.length !== 32) {
return false;
}
if (!isValidSigRecovery(calculateSigRecovery(v, chainId))) {
return false;
}
const rBigInt = (0, bytes_js_1.bytesToBigInt)(r);
const sBigInt = (0, bytes_js_1.bytesToBigInt)(s);
if (rBigInt === constants_js_1.BIGINT_0 ||
rBigInt >= constants_js_1.SECP256K1_ORDER ||
sBigInt === constants_js_1.BIGINT_0 ||
sBigInt >= constants_js_1.SECP256K1_ORDER) {
return false;
}
if (homesteadOrLater && sBigInt >= constants_js_1.SECP256K1_ORDER_DIV_2) {
return false;
}
return true;
};
exports.isValidSignature = isValidSignature;
/**
* Returns the keccak-256 hash of `message`, prefixed with the header used by the `eth_sign` RPC call.
* The output of this function can be fed into `ecsign` to produce the same signature as the `eth_sign`
* call for a given `message`, or fed to `ecrecover` along with a signature to recover the public key
* used to produce the signature.
*/
const hashPersonalMessage = function (message) {
(0, helpers_js_1.assertIsBytes)(message);
const prefix = (0, bytes_js_1.utf8ToBytes)(`\u0019Ethereum Signed Message:\n${message.length}`);
return (0, keccak_js_1.keccak256)((0, bytes_js_1.concatBytes)(prefix, message));
};
exports.hashPersonalMessage = hashPersonalMessage;
//# sourceMappingURL=signature.js.map