chaingate
Version:
Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO
195 lines (194 loc) • 7.93 kB
JavaScript
;
/**
* Bitcoin Cash transaction signing and address derivation.
* @internal
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.publicKeyToHash160 = publicKeyToHash160;
exports.signBchTransaction = signBchTransaction;
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
const hmac_js_1 = require("@noble/hashes/hmac.js");
const legacy_js_1 = require("@noble/hashes/legacy.js");
const sha2_js_1 = require("@noble/hashes/sha2.js");
const btc = __importStar(require("@scure/btc-signer"));
const btc_signer_1 = require("@scure/btc-signer");
const encoding_1 = require("../../../utils/encoding");
const SIGHASH_BCH = 0x41;
const CURVE_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
const HALF_ORDER = CURVE_ORDER / 2n;
const G = secp256k1_js_1.secp256k1.Point.BASE;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function concat(...arrays) {
const len = arrays.reduce((a, b) => a + b.length, 0);
const result = new Uint8Array(len);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
function hmacSha256(data, key) {
return new Uint8Array((0, hmac_js_1.hmac)(sha2_js_1.sha256, key, data));
}
function bytesToBigInt(bytes) {
return BigInt('0x' + (0, encoding_1.bytesToHex)(bytes));
}
function modInverse(a, m) {
let [old_r, r] = [((a % m) + m) % m, m];
let [old_s, s] = [1n, 0n];
while (r !== 0n) {
const q = old_r / r;
[old_r, r] = [r, old_r - q * r];
[old_s, s] = [s, old_s - q * s];
}
return ((old_s % m) + m) % m;
}
// ---------------------------------------------------------------------------
// RFC 6979 deterministic k (bitcore-lib-cash compatible)
// ---------------------------------------------------------------------------
// BCH-compatible deterministic k generation (double-v-hash variant).
function getDeterministicK(hash, privkey) {
let v = new Uint8Array(32).fill(0x01);
let k = new Uint8Array(32).fill(0x00);
// Steps a-d of RFC 6979 §3.2
k = hmacSha256(concat(v, new Uint8Array([0x00]), privkey, hash), k);
v = hmacSha256(v, k);
k = hmacSha256(concat(v, new Uint8Array([0x01]), privkey, hash), k);
// Step h: generate — with double-v-hash quirk
v = hmacSha256(v, k);
v = hmacSha256(v, k); // second hash (bitcore quirk)
let T = bytesToBigInt(v);
// Retry until T is in the valid range (0, N)
while (T <= 0n || T >= CURVE_ORDER) {
k = hmacSha256(concat(v, new Uint8Array([0x00])), k);
v = hmacSha256(v, k);
v = hmacSha256(v, k); // second hash (bitcore quirk)
T = bytesToBigInt(v);
}
return T;
}
// ---------------------------------------------------------------------------
// ECDSA signing (bitcore-lib-cash compatible)
// ---------------------------------------------------------------------------
// BCH-compatible ECDSA signing.
function signEcdsaBch(hash, privateKey) {
const e = bytesToBigInt(hash);
const d = bytesToBigInt(privateKey);
const kVal = getDeterministicK(hash, privateKey);
const Q = G.multiply(kVal);
const r = Q.x % CURVE_ORDER;
const kInv = modInverse(kVal, CURVE_ORDER);
// s = k^-1 * (e + d*r) mod N
let s = (kInv * ((((e + ((d * r) % CURVE_ORDER)) % CURVE_ORDER) + CURVE_ORDER) % CURVE_ORDER)) %
CURVE_ORDER;
// BIP-62 low-S normalization
if (s > HALF_ORDER)
s = CURVE_ORDER - s;
const sig = new secp256k1_js_1.secp256k1.Signature(r, s);
return sig.toBytes('der');
}
// ---------------------------------------------------------------------------
// Address derivation
// ---------------------------------------------------------------------------
// Computes HASH160 of the input.
function hash160(data) {
return (0, legacy_js_1.ripemd160)((0, sha2_js_1.sha256)(data));
}
/**
* Derives the 20-byte public key hash from a compressed public key.
*
* @param publicKey - Compressed (33-byte) public key.
*/
function publicKeyToHash160(publicKey) {
return hash160(publicKey);
}
/**
* Signs a Bitcoin Cash transaction.
*
* @param inputs - UTXOs to spend.
* @param outputs - Destinations and amounts.
* @param privateKey - 32-byte private key.
* @param networkParams - Network parameters for address decoding.
* @returns Serialized raw transaction bytes.
*/
function signBchTransaction(inputs, outputs, privateKey, networkParams) {
const publicKey = secp256k1_js_1.secp256k1.getPublicKey(privateKey, true);
const pubKeyHash = hash160(publicKey);
// Build the transaction using @scure/btc-signer for structure.
const tx = new btc.Transaction({
allowLegacyWitnessUtxo: true,
});
// Add inputs.
for (const input of inputs) {
tx.addInput({
txid: (0, encoding_1.hexToBytes)(input.txid),
index: input.n,
witnessUtxo: {
script: input.script,
amount: input.amount,
},
sighashType: SIGHASH_BCH,
});
}
// Add outputs.
for (const output of outputs) {
tx.addOutput({
script: btc_signer_1.OutScript.encode(btc.Address(networkParams).decode(output.address)),
amount: output.amount,
});
}
// Sign each input using BIP-143 preimage with SIGHASH_FORKID.
const scriptCode = btc_signer_1.OutScript.encode({ type: 'pkh', hash: pubKeyHash });
for (let i = 0; i < inputs.length; i++) {
// preimageWitnessV0 computes the double-SHA256 digest of the BIP-143
// preimage. With hashType = 0x41, the FORKID flag is embedded in the
// 4-byte LE hashType field, which is exactly what BCH requires.
const digest = tx.preimageWitnessV0(i, scriptCode, SIGHASH_BCH, inputs[i].amount);
// ECDSA sign using bitcore-lib-cash compatible deterministic k.
const derSig = signEcdsaBch(digest, privateKey);
// scriptSig = <DER_sig || hashType_byte> <compressed_pubkey>
const sigWithHashType = new Uint8Array(derSig.length + 1);
sigWithHashType.set(derSig);
sigWithHashType[derSig.length] = SIGHASH_BCH;
const scriptSig = btc_signer_1.Script.encode([sigWithHashType, publicKey]);
tx.updateInput(i, { finalScriptSig: scriptSig }, true);
}
// Serialize the transaction (legacy format, no SegWit).
return (0, encoding_1.hexToBytes)(tx.hex);
}