@ngraveio/ur-blockchain-commons
Version:
A JS implementation of Uniform Resources(UR) Registry specification from Blockchain Commons.
183 lines • 7.02 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeAddress = decodeAddress;
exports.encodeAddress = encodeAddress;
const base_1 = require("@scure/base");
const sha256_1 = require("@noble/hashes/sha256");
const sha3_1 = require("@noble/hashes/sha3");
/**
* Decode a Bitcoin or Ethereum address into its components.
* @param address - The address to decode.
* @param network - Optional network for Ethereum (default: 'mainnet').
* @returns The decoded address components.
*/
function decodeAddress(address, network) {
if (address.startsWith('1') || address.startsWith('3') || address.startsWith('m') || address.startsWith('2')) {
// Bitcoin P2PKH or P2SH (Base58Check)
const decoded = base_1.base58.decode(address);
const version = decoded[0];
const payload = decoded.slice(1, -4);
const checksum = decoded.slice(-4);
const calculatedChecksum = (0, sha256_1.sha256)((0, sha256_1.sha256)(decoded.slice(0, -4))).slice(0, 4);
if (!equalBytes(checksum, calculatedChecksum)) {
throw new Error('Invalid checksum');
}
let scriptType;
let addressNetwork;
switch (version) {
case 0x00:
scriptType = 'P2PKH';
addressNetwork = 'mainnet';
break;
case 0x6F:
scriptType = 'P2PKH';
addressNetwork = 'testnet';
break;
case 0x05:
scriptType = 'P2SH';
addressNetwork = 'mainnet';
break;
case 0xC4:
scriptType = 'P2SH';
addressNetwork = 'testnet';
break;
default:
throw new Error('Unknown version byte');
}
if (network !== undefined && network !== addressNetwork) {
throw new Error(`Address network mismatch: expected ${network}, got ${addressNetwork}`);
}
return {
type: 0, // BIP44 coin type for Bitcoin
scriptType,
network: addressNetwork,
payload,
checksum: toUInt32BE(checksum),
};
}
else if (/^(bc1|tb1)[a-z0-9]{25,}$/i.test(address)) {
// Bitcoin P2WPKH, P2WSH, or P2TR (Bech32 or Bech32m)
const isBech32m = address.startsWith('bc1p') || address.startsWith('tb1p');
const { prefix, words } = isBech32m ? base_1.bech32m.decode(address) : base_1.bech32.decode(address);
const version = words[0];
const payload = (isBech32m ? base_1.bech32m : base_1.bech32).fromWords(words.slice(1));
let scriptType;
let addressNetwork;
switch (prefix) {
case 'bc':
addressNetwork = 'mainnet';
break;
case 'tb':
addressNetwork = 'testnet';
break;
default:
throw new Error('Unknown HRP');
}
switch (payload.length) {
case 20:
scriptType = 'P2WPKH';
break;
case 32:
scriptType = version === 0x01 ? 'P2TR' : 'P2WSH';
break;
default:
throw new Error('Unknown witness program length');
}
if (network !== undefined && network !== addressNetwork) {
throw new Error(`Address network mismatch: expected ${network}, got ${addressNetwork}`);
}
return {
type: 0, // BIP44 coin type for Bitcoin
scriptType,
network: addressNetwork,
payload: new Uint8Array(payload),
};
}
else if (/^0x[a-fA-F0-9]{40}$/.test(address)) {
// Ethereum address (Hex)
const payload = address.slice(2).toLowerCase();
const payloadBuffer = new Uint8Array(payload.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
const checksummedAddress = toChecksumAddress(address);
return {
type: 60, // BIP44 coin type for Ethereum
network,
payload: payloadBuffer,
checksum: calculateEthereumChecksum(checksummedAddress),
};
}
else {
throw new Error(`Unsupported address format ${address}`);
}
}
/**
* Encode a Bitcoin or Ethereum address from its components.
* @param type - The BIP44 coin type.
* @param scriptType - The script type for Bitcoin addresses.
* @param network - The network type (mainnet or testnet).
* @param payload - The payload to encode.
* @returns The encoded address.
*/
function encodeAddress(type, scriptType, network, payload) {
if (type === 0) {
// Bitcoin address encoding
let version;
switch (scriptType) {
case 'P2PKH':
version = network === 'mainnet' ? 0x00 : 0x6F;
break;
case 'P2SH':
version = network === 'mainnet' ? 0x05 : 0xC4;
break;
case 'P2WPKH':
case 'P2WSH':
case 'P2TR': {
const prefix = network === 'mainnet' ? 'bc' : 'tb';
const words = (scriptType === 'P2TR' ? base_1.bech32m : base_1.bech32).toWords(payload);
words.unshift(scriptType === 'P2TR' ? 0x01 : 0x00);
return (scriptType === 'P2TR' ? base_1.bech32m : base_1.bech32).encode(prefix, words);
}
default:
throw new Error('Unknown script type');
}
const checksum = (0, sha256_1.sha256)((0, sha256_1.sha256)(new Uint8Array([version, ...payload]))).slice(0, 4);
return base_1.base58.encode(new Uint8Array([version, ...payload, ...checksum]));
}
else if (type === 60) {
// Ethereum address encoding
return toChecksumAddress(`0x${base_1.base16.encode(payload)}`);
}
else {
throw new Error(`Unsupported coin type: ${type}`);
}
}
/**
* Helper functions remain unchanged
*/
function calculateEthereumChecksum(address) {
const addressHash = (0, sha3_1.keccak_256)(address.slice(2).toLowerCase());
return parseInt(base_1.base16.encode(addressHash.slice(0, 4)), 16);
}
function toChecksumAddress(address) {
const addressHash = base_1.base16.encode((0, sha3_1.keccak_256)(address.slice(2).toLowerCase()));
let checksumAddress = '0x';
for (let i = 0; i < 40; i++) {
checksumAddress += parseInt(addressHash[i], 16) > 7 ? address[i + 2].toUpperCase() : address[i + 2].toLowerCase();
}
return checksumAddress;
}
function equalBytes(a, b) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
function toUInt32BE(bytes) {
if (bytes.length !== 4) {
throw new Error('Invalid byte length for UInt32BE conversion.');
}
return ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
}
//# sourceMappingURL=AddressHelpers.js.map