@roochnetwork/rooch-sdk
Version:
212 lines (211 loc) • 7.11 kB
JavaScript
import { bech32, bech32m } from "@scure/base";
import { bcs } from "../bcs/index.js";
import { EmptyBytes } from "../types/index.js";
import { blake2b, bytes, isHex, validateWitness } from "../utils/index.js";
import { ROOCH_ADDRESS_LENGTH } from "./address.js";
import { RoochAddress } from "./rooch.js";
import { MultiChainID } from "./types.js";
import { ThirdPartyAddress } from "./thirdparty-address.js";
import { Buffer } from "buffer";
import bs58check from "bs58check";
import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
var BitcoinNetowkType = /* @__PURE__ */ ((BitcoinNetowkType2) => {
BitcoinNetowkType2[BitcoinNetowkType2["Bitcoin"] = 0] = "Bitcoin";
BitcoinNetowkType2[BitcoinNetowkType2["Testnet"] = 1] = "Testnet";
BitcoinNetowkType2[BitcoinNetowkType2["Signet"] = 2] = "Signet";
BitcoinNetowkType2[BitcoinNetowkType2["Regtest"] = 3] = "Regtest";
return BitcoinNetowkType2;
})(BitcoinNetowkType || {});
var BitcoinAddressType = /* @__PURE__ */ ((BitcoinAddressType2) => {
BitcoinAddressType2[BitcoinAddressType2["pkh"] = 0] = "pkh";
BitcoinAddressType2[BitcoinAddressType2["sh"] = 1] = "sh";
BitcoinAddressType2[BitcoinAddressType2["witness"] = 2] = "witness";
return BitcoinAddressType2;
})(BitcoinAddressType || {});
const PUBKEY_ADDRESS_PREFIX_MAIN = 0;
const PUBKEY_ADDRESS_PREFIX_TEST = 111;
const SCRIPT_ADDRESS_PREFIX_MAIN = 5;
const SCRIPT_ADDRESS_PREFIX_TEST = 196;
class BitcoinNetwork {
constructor(network) {
this.network = network ?? 0 /* Bitcoin */;
}
static fromBech32Prefix(prefix) {
switch (prefix) {
case "bc":
return new BitcoinNetwork(0 /* Bitcoin */);
case "tb":
return new BitcoinNetwork(1 /* Testnet */);
case "bcrt":
return new BitcoinNetwork(3 /* Regtest */);
default:
return void 0;
}
}
bech32HRP() {
switch (this.network) {
case 0 /* Bitcoin */:
return "bc";
case 1 /* Testnet */:
return "tb";
case 2 /* Signet */:
return "tb";
case 3 /* Regtest */:
return "bcrt";
}
}
}
class BitcoinAddress extends ThirdPartyAddress {
constructor(input, network) {
super(input);
if (isHex(input)) {
this.bytes = bytes("hex", input.startsWith("0x") ? input.slice(2) : input);
let prefixed;
let version = this.bytes[1];
switch (this.bytes[0]) {
case 0 /* pkh */:
prefixed = new Uint8Array(22);
prefixed[0] = version;
prefixed[1] = this.getPubkeyAddressPrefix(network);
prefixed.set(this.bytes.slice(2));
this.rawAddress = bs58check.encode(prefixed);
break;
case 1 /* sh */:
prefixed = new Uint8Array(22);
prefixed[0] = version;
prefixed[1] = this.getScriptAddressPrefix(network);
prefixed.set(this.bytes.slice(2));
this.rawAddress = bs58check.encode(prefixed);
break;
case 2 /* witness */:
const hrp = new BitcoinNetwork(network).bech32HRP();
const words = bech32.toWords(Buffer.from(this.bytes.slice(2)));
words.unshift(version);
this.rawAddress = version === 0 ? bech32.encode(hrp, words, false) : bech32m.encode(hrp, words, false);
}
} else {
let info = this.decode();
this.bytes = this.wrapAddress(info.type, info.bytes, info.version);
}
}
static fromPublicKey(publicKey, network = 3 /* Regtest */) {
const tapTweak = (a, b) => {
const u2 = schnorr.utils;
const t2 = u2.taggedHash("TapTweak", a, b);
const tn = u2.bytesToNumberBE(t2);
if (tn >= secp256k1.CURVE.n)
throw new Error("tweak higher than curve order");
return tn;
};
const u = schnorr.utils;
const t = tapTweak(publicKey, EmptyBytes);
const P = u.lift_x(u.bytesToNumberBE(publicKey));
const Q = P.add(secp256k1.ProjectivePoint.fromPrivateKey(t));
const tweakedPubkey = u.pointToBytes(Q);
return new BitcoinAddress(
bech32m.encode(
new BitcoinNetwork(network).bech32HRP(),
[1].concat(bech32m.toWords(tweakedPubkey)),
false
)
);
}
getPubkeyAddressPrefix(network = 0 /* Bitcoin */) {
return network === 0 /* Bitcoin */ ? PUBKEY_ADDRESS_PREFIX_MAIN : PUBKEY_ADDRESS_PREFIX_TEST;
}
getScriptAddressPrefix(network = 0 /* Bitcoin */) {
return network === 0 /* Bitcoin */ ? SCRIPT_ADDRESS_PREFIX_MAIN : SCRIPT_ADDRESS_PREFIX_TEST;
}
toBytes() {
return bytes("utf8", this.rawAddress);
}
genMultiChainAddress() {
return bcs.MultiChainAddress.serialize({
multiChainId: MultiChainID.Bitcoin,
rawAddress: this.bytes
}).toBytes();
}
genRoochAddress() {
if (!this.roochAddress) {
this.roochAddress = new RoochAddress(blake2b(this.bytes, { dkLen: ROOCH_ADDRESS_LENGTH }));
}
return this.roochAddress;
}
decode() {
let input = this.rawAddress;
if (input.length < 14 || input.length > 74)
throw new Error("Invalid address length");
const bech32_network = (() => {
const sep = input.lastIndexOf("1");
const bech32Prefix = sep === -1 ? input : input.substring(0, sep);
return BitcoinNetwork.fromBech32Prefix(bech32Prefix);
})();
if (bech32_network !== void 0) {
let res;
try {
res = bech32.decode(input);
if (res.words[0] !== 0)
throw new Error(`bech32: wrong version=${res.words[0]}`);
} catch (_) {
res = bech32m.decode(input);
if (res.words[0] === 0)
throw new Error(`bech32m: wrong version=${res.words[0]}`);
}
const [version, ...program] = res.words;
const data2 = bech32.fromWords(program);
validateWitness(version, data2);
if (version === 0 && data2.length === 32)
return {
bytes: data2,
type: 2 /* witness */,
//wsh
version
};
else if (version === 0 && data2.length === 20)
return {
bytes: data2,
type: 2 /* witness */,
//wpkh
version
};
else if (version === 1 && data2.length === 32)
return {
bytes: data2,
type: 2 /* witness */,
//tr
version
};
else
throw new Error("Unknown witness program");
}
const data = bs58check.decode(input);
if (data.length !== 21)
throw new Error("Invalid base58 address");
if (data[0] === 0) {
return {
bytes: data.slice(1),
type: 0 /* pkh */
};
} else if (data[0] === 5) {
return { bytes: data.slice(1), type: 1 /* sh */ };
}
throw new Error(`Invalid address prefix=${data[0]}`);
}
wrapAddress(type, bytes2, version) {
const addr = new Uint8Array(bytes2.length + 1 + (version !== void 0 ? 1 : 0));
addr.set([type]);
if (version !== void 0) {
addr.set([version], 1);
addr.set(bytes2, 2);
} else {
addr.set(bytes2, 1);
}
return addr;
}
}
export {
BitcoinAddress,
BitcoinNetowkType,
BitcoinNetwork
};
//# sourceMappingURL=bitcoin.js.map