@@ -0,0 +1,3 @@
|
||||
# XO Primitives
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@xo/primitives",
|
||||
"version": "0.0.1",
|
||||
"description": "XO Stack Primitives",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build-old": "rollup -c"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
import {
|
||||
Base58AddressFormatVersion,
|
||||
addressContentsToLockingBytecode,
|
||||
lockingBytecodeToAddressContents,
|
||||
isHex,
|
||||
binToHex,
|
||||
hexToBin,
|
||||
decodeBase58Address,
|
||||
decodeCashAddress,
|
||||
disassembleBytecodeBCH,
|
||||
encodeBase58AddressFormat,
|
||||
encodeCashAddress,
|
||||
sha256,
|
||||
ripemd160,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
import type { KnownAddressTypeContents } from '@bitauth/libauth';
|
||||
|
||||
export type AddressType = 'P2PK' | 'P2PKH' | 'P2SH20' | 'P2SH32';
|
||||
|
||||
/**
|
||||
* Address Entity.
|
||||
*/
|
||||
export class Address {
|
||||
public readonly addressContents: KnownAddressTypeContents;
|
||||
|
||||
constructor(addressContents: KnownAddressTypeContents) {
|
||||
this.addressContents = addressContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a CashAddr into an Address Entity.
|
||||
*
|
||||
* @param address {string} The CashAddr to decode.
|
||||
*
|
||||
* @throws {Error} If address cannot be decoded.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*
|
||||
* @example
|
||||
* const address = Address.fromCashAddr('bitcoincash:qpeqv9k9j3l7fepp9du24f9pr5jdu7c2mvn4wwldav')
|
||||
*/
|
||||
public static fromCashAddr(cashAddr: string): Address {
|
||||
// CashAddr
|
||||
const decodeResult = decodeCashAddress(cashAddr);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Define the encode type in advance.
|
||||
let encodeType: AddressType;
|
||||
|
||||
// Determine the encoding to use based on the Decoding Result's Type.
|
||||
switch (decodeResult.type) {
|
||||
case 'p2pkh':
|
||||
encodeType = 'P2PKH';
|
||||
break;
|
||||
case 'p2sh':
|
||||
encodeType = decodeResult.payload.length === 32 ? 'P2SH32' : 'P2SH20';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported address type: ${decodeResult.type}`);
|
||||
}
|
||||
|
||||
// Return new instance of address.
|
||||
return new Address({ payload: decodeResult.payload, type: encodeType });
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Legacy Address into an Address Entity.
|
||||
*
|
||||
* @param address {string} The Legacy (Base58) Address to decode.
|
||||
*
|
||||
* @throws {Error} If address cannot be decoded.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromLegacy(legacyAddress: string): Address {
|
||||
// Decode the Legacy (Base58) Address.
|
||||
const decodeResult = decodeBase58Address(legacyAddress);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Define the encode type in advance.
|
||||
let encodeType: AddressType;
|
||||
|
||||
// Determine the encoding to use based on the Decoding Result's Type.
|
||||
switch (decodeResult.version) {
|
||||
case Base58AddressFormatVersion.p2pkh:
|
||||
case Base58AddressFormatVersion.p2pkhTestnet:
|
||||
encodeType = 'P2PKH';
|
||||
break;
|
||||
case Base58AddressFormatVersion.p2sh20:
|
||||
case Base58AddressFormatVersion.p2sh20Testnet:
|
||||
encodeType = 'P2SH20';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported address type: ${decodeResult.version}`);
|
||||
}
|
||||
|
||||
return new Address({ payload: decodeResult.payload, type: encodeType });
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Cash Address or Legacy Address.
|
||||
*
|
||||
* @param address {string} The address to attempt to decode.
|
||||
*
|
||||
* @throws {Error} If address cannot be decoded.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromCashAddrOrLegacy(address: string): Address {
|
||||
// Attempt to decode as a Cash Address.
|
||||
try {
|
||||
return Address.fromCashAddr(address);
|
||||
} catch (error) {
|
||||
// And if that fails, attempt to decode as a Base58 Address.
|
||||
return Address.fromLegacy(address);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate an address from Contract Bytecode.
|
||||
*
|
||||
* @param contractBytes {Uint8Array} The contract bytecode
|
||||
*
|
||||
* @throws {Error} If address cannot be instantiated from Contract Bytecode.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromRedeemScript(
|
||||
contractBytes: Uint8Array,
|
||||
type: 'P2SH20' | 'P2SH32' = 'P2SH32'
|
||||
): Address {
|
||||
// Do an initial SHA256 on the Contract Bytes.
|
||||
let payload = sha256.hash(contractBytes);
|
||||
|
||||
if (type === 'P2SH20') {
|
||||
// RIPEMD160 the SHA256 hash.
|
||||
payload = ripemd160.hash(payload);
|
||||
} else if (type === 'P2SH32') {
|
||||
// SHA256 Hash the SHA256 hash a seconr time.
|
||||
payload = sha256.hash(payload);
|
||||
}
|
||||
|
||||
// Return a new instance of address.
|
||||
return new Address({ payload, type });
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate an address from Contract Bytecode as a hex string.
|
||||
*
|
||||
* @param contractBytes {Uint8Array} The contract bytecode
|
||||
*
|
||||
* @throws {Error} If address cannot be instantiated from Contract Bytecode.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromRedeemScriptHex(
|
||||
hex: string,
|
||||
type: 'P2SH20' | 'P2SH32' = 'P2SH32'
|
||||
): Address {
|
||||
if (!isHex(hex)) {
|
||||
throw new Error('Invalid hex');
|
||||
}
|
||||
|
||||
const contractBytecode = hexToBin(hex);
|
||||
|
||||
// Return a new instance of address.
|
||||
return Address.fromRedeemScript(contractBytecode, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to instantiate Address from Locking Bytecode.
|
||||
*
|
||||
* @param lockScript {Uint8Array} The lockscript bytes.
|
||||
*
|
||||
* @throws {Error} If lock script cannot be decoded into an address.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromLockscriptBytes(lockScript: Uint8Array): Address {
|
||||
const addressContents = lockingBytecodeToAddressContents(lockScript);
|
||||
|
||||
// If the type is not recognized, throw an error...
|
||||
if (addressContents.type === 'unknown') {
|
||||
throw new Error(`Invalid type (${addressContents.type})`);
|
||||
}
|
||||
|
||||
return new Address(addressContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to instantiate Address from Locking Bytecode as a hex string.
|
||||
*
|
||||
* @param hex {Uint8Array} The lockscript bytes as hex.
|
||||
*
|
||||
* @throws {Error} If lock script cannot be decoded into an address.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromLockscriptHex(hex: string): Address {
|
||||
if (!isHex(hex)) {
|
||||
throw new Error('Invalid hex');
|
||||
}
|
||||
|
||||
const lockScriptBytes = hexToBin(hex);
|
||||
|
||||
return Address.fromLockscriptBytes(lockScriptBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate an address from a Ripemd160 hash.
|
||||
*
|
||||
* @param hash160 {Uint8Array} The hash to use.
|
||||
* @param type {AddressType} The type of the address to instantiate (e.g. P2PKH).
|
||||
*
|
||||
* @throws {Error} If hash160 is not 20 bytes.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromHash160(
|
||||
hash160: Uint8Array,
|
||||
type: AddressType = 'P2PKH'
|
||||
): Address {
|
||||
// If the Hash160 bytes is not 20 in length, it is invalid...
|
||||
if (hash160.length !== 20) {
|
||||
throw new Error(
|
||||
`Invalid hash160 length: Expected 20 (Received ${hash160.length})`
|
||||
);
|
||||
}
|
||||
|
||||
return new Address({ payload: hash160, type });
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate an address from a Ripemd160 hash (as hex).
|
||||
*
|
||||
* @param hash160Hex {string} The hash to use (as hex).
|
||||
* @param type {AddressType} The type of the address to instantiate (e.g. P2PKH).
|
||||
*
|
||||
* @throws {Error} If hash160 is not 20 bytes.
|
||||
*
|
||||
* @returns {Address} Instance of Address.
|
||||
*/
|
||||
public static fromHash160Hex(
|
||||
hash160Hex: string,
|
||||
type: AddressType = 'P2PKH'
|
||||
): Address {
|
||||
if (!isHex(hash160Hex)) {
|
||||
throw new Error('Invalid hex');
|
||||
}
|
||||
|
||||
const hash160Bytes = hexToBin(hash160Hex);
|
||||
|
||||
return Address.fromHash160(hash160Bytes, type);
|
||||
}
|
||||
|
||||
public static isValid(address: string): boolean {
|
||||
try {
|
||||
Address.fromCashAddrOrLegacy(address);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static isValidLegacy(address: string): boolean {
|
||||
try {
|
||||
Address.fromLegacy(address);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static isValidCashAddr(address: string): boolean {
|
||||
try {
|
||||
Address.fromCashAddr(address);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of address (P2PKH, P2SH).
|
||||
*
|
||||
* @returns {AddressType} The type of Address.
|
||||
*/
|
||||
public type(): AddressType {
|
||||
return this.addressContents.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this address' raw Hash160 (Ripemd160) hash as a Uint8Array.
|
||||
*
|
||||
* @returns {Uint8Array} The Hash160 of this address.
|
||||
*/
|
||||
public toHash160Bytes(): Uint8Array {
|
||||
return this.addressContents.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this address' raw Hash160 (Ripemd16) hash as a hex string.
|
||||
*
|
||||
* @returns {Uint8Array} The Hash160 of this address.
|
||||
*/
|
||||
public toHash160Hex(): string {
|
||||
return binToHex(this.addressContents.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this address to CashAddr format.
|
||||
*
|
||||
* @param networkPrefix {'bitcoincash' | 'bchtest' | 'bchreg'} The Cash Address Network prefix.
|
||||
*
|
||||
* @throws {Error} If address cannot be encoded as a CashAddr.
|
||||
*
|
||||
* @returns {string} The address in CashAddr format.
|
||||
*/
|
||||
public toCashAddr(
|
||||
networkPrefix: 'bitcoincash' | 'bchtest' | 'bchreg' = 'bitcoincash',
|
||||
supportTokens = false
|
||||
): string {
|
||||
// Define the encode type in advance.
|
||||
let encodeType: 'p2pkh' | 'p2sh' | 'p2pkhWithTokens' | 'p2shWithTokens';
|
||||
|
||||
// Determine the encoding to use based on the Address Content's Type.
|
||||
switch (this.addressContents.type) {
|
||||
case 'P2PKH':
|
||||
encodeType = supportTokens ? 'p2pkhWithTokens' : 'p2pkh';
|
||||
break;
|
||||
case 'P2SH20':
|
||||
encodeType = supportTokens ? 'p2shWithTokens' : 'p2sh';
|
||||
break;
|
||||
case 'P2SH32':
|
||||
encodeType = supportTokens ? 'p2shWithTokens' : 'p2sh';
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported address type: ${this.addressContents.type}`
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: This function will throw if the payload is invalid.
|
||||
return encodeCashAddress({
|
||||
prefix: networkPrefix,
|
||||
type: encodeType,
|
||||
payload: this.addressContents.payload,
|
||||
}).address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this address to Legacy (Base58) Address format.
|
||||
*
|
||||
* @throws {Error} If address cannot be encoded as a Legacy Address (Base58).
|
||||
*
|
||||
* @returns {string} The address in Legacy Address format.
|
||||
*/
|
||||
public toLegacy(): string {
|
||||
// Define the encode type in advance.
|
||||
let encodeType: Base58AddressFormatVersion;
|
||||
|
||||
// Determine the encoding to use based on the Address Content's Type.
|
||||
switch (this.addressContents.type) {
|
||||
case 'P2PKH':
|
||||
encodeType = Base58AddressFormatVersion.p2pkh;
|
||||
break;
|
||||
case 'P2SH20':
|
||||
encodeType = Base58AddressFormatVersion.p2sh20;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported address type: ${this.addressContents.type}`
|
||||
);
|
||||
}
|
||||
|
||||
// Encode the address in Base58 format.
|
||||
return encodeBase58AddressFormat(encodeType, this.addressContents.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding lockscript for this address as a Uint8Array.
|
||||
*
|
||||
* @returns {Uint8Array} The lockscript as a Uint8Array.
|
||||
*/
|
||||
public toLockscriptBytes(): Uint8Array {
|
||||
return addressContentsToLockingBytecode(this.addressContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding lockscript for this address as a hex string.
|
||||
*
|
||||
* @returns {Uint8Array} The lockscript as a hex string.
|
||||
*/
|
||||
public toLockscriptHex(): string {
|
||||
return binToHex(this.toLockscriptBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding lockscript for this address as ASM.
|
||||
*
|
||||
* @returns {Uint8Array} The lockscript as ASN.
|
||||
*/
|
||||
public toLockscriptAsm(): string {
|
||||
const lockscriptBytes = this.toLockscriptBytes();
|
||||
|
||||
const lockscriptAsm = disassembleBytecodeBCH(lockscriptBytes);
|
||||
|
||||
return lockscriptAsm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
isHex,
|
||||
base64ToBin,
|
||||
binToBase64,
|
||||
isBase64,
|
||||
binToUtf8,
|
||||
utf8ToBin,
|
||||
bigIntToBinUint64LE,
|
||||
bigIntToBinUintLE,
|
||||
binToBigIntUintLE,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Bytes Primitive.
|
||||
*/
|
||||
export class Bytes {
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Instantiate Bytes from a Uint8Array.
|
||||
*
|
||||
* @param bytes {Uint8Array} The bytes to use.
|
||||
*/
|
||||
constructor(bytes: Uint8Array) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates bytes from a Base64 string.
|
||||
*
|
||||
* @param utf8 {string} The Base64 string.
|
||||
*
|
||||
* @throws {Error} If Base64 is invalid.
|
||||
*
|
||||
* @returns {Bytes} Instance of Bytes.
|
||||
*/
|
||||
public static fromBase64(base64: string): Bytes {
|
||||
// If the Base64 is invalid, throw an error.
|
||||
if (!isBase64(base64)) {
|
||||
throw new Error('Invalid base64 string');
|
||||
}
|
||||
|
||||
return new Bytes(base64ToBin(base64));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates bytes from a BigInt (LE encoded).
|
||||
*
|
||||
* @param number {number} The bigint number.
|
||||
*
|
||||
* @throws {Error} If number is invalid.
|
||||
*
|
||||
* @returns {Bytes} Instance of Bytes.
|
||||
*/
|
||||
public static fromBigInt(number: bigint): Bytes {
|
||||
return new Bytes(bigIntToBinUintLE(number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates bytes from a BigInt (64 Bit LE encoded).
|
||||
*
|
||||
* @param number {number} The bigint number.
|
||||
*
|
||||
* @throws {Error} If number is invalid.
|
||||
*
|
||||
* @returns {Bytes} Instance of Bytes.
|
||||
*/
|
||||
public static fromBigInt64(number: bigint): Bytes {
|
||||
return new Bytes(bigIntToBinUint64LE(number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates bytes from a hex string.
|
||||
*
|
||||
* @param hex {string} The hex string.
|
||||
*
|
||||
* @throws {Error} If hex is invalid.
|
||||
*
|
||||
* @returns {Bytes} Instance of Bytes.
|
||||
*/
|
||||
public static fromHex(hex: string): Bytes {
|
||||
// If the hex is invalid, throw an error.
|
||||
if (!isHex(hex)) {
|
||||
throw new Error('Invalid hexidecimal string');
|
||||
}
|
||||
|
||||
return new Bytes(hexToBin(hex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates bytes from a UTF8 string.
|
||||
*
|
||||
* @param utf8 {string} The UTF8 string.
|
||||
*
|
||||
* @returns {Bytes} Instance of Bytes.
|
||||
*/
|
||||
public static fromUtf8(utf8: string): Bytes {
|
||||
return new Bytes(utf8ToBin(utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes as a Base64 string.
|
||||
*
|
||||
* @returns {string} Bytes as a Base64 string.
|
||||
*/
|
||||
public toBase64(): string {
|
||||
return binToBase64(this.bytes);
|
||||
}
|
||||
|
||||
public toBigInt(): bigint {
|
||||
return binToBigIntUintLE(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes as a hex string.
|
||||
*
|
||||
* @returns {string} Bytes as a hex string.
|
||||
*/
|
||||
public toHex(): string {
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes as a UTF8 string
|
||||
*
|
||||
* @returns {string} Bytes as a UTF8 string.
|
||||
*/
|
||||
public toUtf8(): string {
|
||||
return binToUtf8(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes as a Uint8Array
|
||||
*
|
||||
* @returns {Uint8Array} Bytes as a Uint8Array.
|
||||
*/
|
||||
public toBytes(): Uint8Array {
|
||||
return this.bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
ripemd160,
|
||||
sha256,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Crypto Module.
|
||||
*/
|
||||
export class Crypto
|
||||
{
|
||||
/**
|
||||
* SHA256 hash the given input
|
||||
*
|
||||
* @param input {Uint8Array} The input to hash.
|
||||
*
|
||||
* @returns {Uint8Array} The SHA256 Hash of the given input.
|
||||
*/
|
||||
public static sha256(input: Uint8Array): Uint8Array
|
||||
{
|
||||
return sha256.hash(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* RIPEMD160 hash the given input
|
||||
*
|
||||
* @param input {Uint8Array} The input to hash.
|
||||
*
|
||||
* @returns {Uint8Array} The RIPEMD160 Hash of the given input.
|
||||
*/
|
||||
public static ripemd160(input: Uint8Array): Uint8Array
|
||||
{
|
||||
return ripemd160.hash(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Double SHA256 hash the given input
|
||||
*
|
||||
* @param input {Uint8Array} The input to hash.
|
||||
*
|
||||
* @returns {Uint8Array} The Double SHA256 Hash of the given input.
|
||||
*/
|
||||
public static hash256(input: Uint8Array): Uint8Array
|
||||
{
|
||||
return sha256.hash(sha256.hash(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* RIPEMD160 and then SHA256 hash the given input
|
||||
*
|
||||
* @param input {Uint16Array} The input to hash.
|
||||
*
|
||||
* @returns {Uint8Array} The RIPEMD160+SHA256 Hash of the given input.
|
||||
*/
|
||||
public static hash160(input: Uint8Array): Uint8Array
|
||||
{
|
||||
return ripemd160.hash(sha256.hash(input));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { HDPublicNode } from './hd-public-node.js';
|
||||
import { PrivateKey } from './private-key.js';
|
||||
|
||||
import {
|
||||
encodeHdPrivateKey,
|
||||
decodeHdPrivateKey,
|
||||
deriveHdPath,
|
||||
deriveHdPublicNode,
|
||||
deriveHdPrivateNodeFromSeed,
|
||||
generateRandomBytes,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
import type {
|
||||
DecodedHdKey,
|
||||
HdKeyNetwork,
|
||||
HdPrivateNodeValid,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Hierarchically Deterministic Private Node Entity.
|
||||
*/
|
||||
export class HDPrivateNode {
|
||||
// The HD Private Node.
|
||||
public readonly node: HdPrivateNodeValid;
|
||||
|
||||
/**
|
||||
* Construct a new HD Private Node.
|
||||
*
|
||||
* @param node {HdPrivateNodeValid} The HD Private Node.
|
||||
*/
|
||||
constructor(node: HdPrivateNodeValid) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HD Private Node from the given XPriv Key.
|
||||
*
|
||||
* @param xpriv {string} The XPriv Key.
|
||||
*
|
||||
* @throws {Error} If HD Private Node cannot be created.
|
||||
*
|
||||
* @returns {HDPrivateNode} The created HD Private Node.
|
||||
*/
|
||||
public static fromXPriv(xpriv: string): HDPrivateNode {
|
||||
// Attempt to decode the XPriv Key.
|
||||
const decodeResult = decodeHdPrivateKey(xpriv);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Return a new HD Private Node from the given XPriv.
|
||||
return new HDPrivateNode(decodeResult.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HD Private Node from a raw seed.
|
||||
*
|
||||
* @param seed {Uint8Array} The raw seed bytes.
|
||||
*
|
||||
* @throws {Error} If HD Private Node cannot be created.
|
||||
*
|
||||
* @returns {HDPrivateNode} The created HD Private Node.
|
||||
*/
|
||||
public static fromSeed(seed: Uint8Array): HDPrivateNode {
|
||||
// If the seed is not 32 bytes, throw an error.
|
||||
/*if (seed.length !== 32) {
|
||||
throw new Error(
|
||||
`Failed to derive Private Node from seed: Seed must be 32 bytes (${seed.length} given)`
|
||||
);
|
||||
}*/
|
||||
|
||||
// Attempt to derive HD Private Node from the given seed.
|
||||
const deriveResult = deriveHdPrivateNodeFromSeed(seed);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof deriveResult === 'string') {
|
||||
throw new Error(deriveResult);
|
||||
}
|
||||
|
||||
// Return a new HD Private Node from the given seed.
|
||||
return new HDPrivateNode(deriveResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random HD Private Node.
|
||||
*
|
||||
* @throws {Error} If HD Private Node cannot be created.
|
||||
*
|
||||
* @returns {HDPrivateNode} The created HD Private Node.
|
||||
*/
|
||||
public static generateRandom(): HDPrivateNode {
|
||||
// Attempt to derive HD Private Node from randomly generated bytes.
|
||||
const deriveResult = deriveHdPrivateNodeFromSeed(generateRandomBytes(32));
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof deriveResult === 'string') {
|
||||
throw new Error(deriveResult);
|
||||
}
|
||||
|
||||
// Return the new HD Private Node from the random bytes.
|
||||
return new HDPrivateNode(deriveResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XPriv string for this node.
|
||||
*
|
||||
* @returns {string} The XPriv string for this node.
|
||||
*/
|
||||
public toString(): string {
|
||||
// Return the Mainnet XPriv.
|
||||
return this.toXPriv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Private Key entity for this node.
|
||||
*
|
||||
* @returns {PrivateKey} The Private Key Entity for this node.
|
||||
*/
|
||||
public privateKey(): PrivateKey {
|
||||
// Return a Private Key Entity from the private key of our node.
|
||||
return new PrivateKey(this.node.privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a HD Private Node from the given BIP32 path.
|
||||
*
|
||||
* @param path {string} The BIP32 Path (e.g. m/44'/145'/0'/0/1).
|
||||
*
|
||||
* @throws {Error} If HD Private Node cannot be derived.
|
||||
*
|
||||
* @returns {HDPrivateNode} The derived HD Private Node
|
||||
*/
|
||||
public derivePath(path: string): HDPrivateNode {
|
||||
// Attempt to derive the given path.
|
||||
const derivePathResult = deriveHdPath(this.node, path);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof derivePathResult === 'string') {
|
||||
throw new Error(derivePathResult);
|
||||
}
|
||||
|
||||
// Return a new HD Private Node Entity derived from the given path.
|
||||
return new HDPrivateNode(derivePathResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a HD Public Node from this Private Node.
|
||||
*
|
||||
* @returns {HDPublicNode} The derived HD Public Node
|
||||
*/
|
||||
public deriveHDPublicNode(): HDPublicNode {
|
||||
// Derive a HD Public Node from this HD Private Node.
|
||||
const publicNode = deriveHdPublicNode(this.node);
|
||||
|
||||
// Return a new HD Public Node entity from our derived Public Node.
|
||||
return new HDPublicNode(publicNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this node to an XPriv Key for export.
|
||||
*
|
||||
* @param network {HdKeyNetwork} The network to encode for.
|
||||
*
|
||||
* @returns {string} The XPriv Key.
|
||||
*/
|
||||
public toXPriv(network: HdKeyNetwork = 'mainnet'): string {
|
||||
// Create our node info structure.
|
||||
const nodeInfo: DecodedHdKey<HdPrivateNodeValid> = {
|
||||
network: network,
|
||||
node: this.node,
|
||||
};
|
||||
|
||||
// Encode the XPriv from our node info.
|
||||
return encodeHdPrivateKey(nodeInfo).hdPrivateKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { PublicKey } from './public-key.js';
|
||||
|
||||
import {
|
||||
encodeHdPublicKey,
|
||||
decodeHdPublicKey,
|
||||
deriveHdPath,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
import type {
|
||||
DecodedHdKey,
|
||||
HdKeyNetwork,
|
||||
HdPublicNodeValid,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Hierarchically Deterministic Public Node Entity.
|
||||
*/
|
||||
export class HDPublicNode {
|
||||
// The HD Public Node.
|
||||
public readonly node: HdPublicNodeValid;
|
||||
|
||||
/**
|
||||
* Construct a new HD Public Node.
|
||||
*
|
||||
* @param node {HdPublicNode} The HD Public Node.
|
||||
*/
|
||||
constructor(node: HdPublicNodeValid) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HD Public Node from the given XPriv Key.
|
||||
*
|
||||
* @param xpub {string} The XPriv Key.
|
||||
*
|
||||
* @throws {Error} If HD Public Node cannot be created.
|
||||
*
|
||||
* @returns {HDPrivateNode} The created HD Public Node.
|
||||
*/
|
||||
public static fromXPub(xpub: string): HDPublicNode {
|
||||
// Attempt to decode the XPub Key.
|
||||
const decodeResult = decodeHdPublicKey(xpub);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Return a new HD Public Node from the given XPub.
|
||||
return new HDPublicNode(decodeResult.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XPub string for this node.
|
||||
*
|
||||
* @returns {string} The XPub string for this node.
|
||||
*/
|
||||
public toString(): string {
|
||||
// Return the Mainnet XPub.
|
||||
return this.toXPub();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Public Key Entity for this node.
|
||||
*
|
||||
* @returns {PublicKey} The Public Key Entity for this node.
|
||||
*/
|
||||
public publicKey(): PublicKey {
|
||||
// Return a Public Key Entity from the public key of our node.
|
||||
return new PublicKey(this.node.publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a HD Public Node from the given BIP32 path.
|
||||
*
|
||||
* @param path {string} The BIP32 Path (e.g. m/44'/145'/0'/0/1).
|
||||
*
|
||||
* @throws {Error} If HD Public Node cannot be derived.
|
||||
*
|
||||
* @returns {HDPrivateNode} The derived HD Public Node
|
||||
*/
|
||||
public derivePath(path: string): HDPublicNode {
|
||||
// Attempt to derive the given path.
|
||||
const derivePathResult = deriveHdPath(this.node, path);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof derivePathResult === 'string') {
|
||||
throw new Error(derivePathResult);
|
||||
}
|
||||
|
||||
// Return a new HD Public Node Entity derived from the given path.
|
||||
return new HDPublicNode(derivePathResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this node to an XPub Key for export.
|
||||
*
|
||||
* @param network {HdKeyNetwork} The network to encode for.
|
||||
*
|
||||
* @returns {string} The XPub Key.
|
||||
*/
|
||||
public toXPub(network: HdKeyNetwork = 'mainnet'): string {
|
||||
// Create our node info structure.
|
||||
const nodeInfo = {
|
||||
network: network,
|
||||
node: this.node,
|
||||
} as DecodedHdKey<HdPublicNodeValid>;
|
||||
|
||||
// Encode the XPub from our node info.
|
||||
return encodeHdPublicKey(nodeInfo).hdPublicKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export * from './address.js';
|
||||
export * from './bytes.js';
|
||||
export * from './crypto.js';
|
||||
export * from './hd-private-node.js';
|
||||
export * from './hd-public-node.js';
|
||||
export * from './private-key.js';
|
||||
export * from './public-key.js';
|
||||
export * from './satoshis.js';
|
||||
export * from './script.js';
|
||||
export * from './transaction.js';
|
||||
@@ -0,0 +1,310 @@
|
||||
import { PublicKey } from './public-key.js';
|
||||
|
||||
import {
|
||||
binToHex,
|
||||
decodePrivateKeyWif,
|
||||
encodePrivateKeyWif,
|
||||
generatePrivateKey,
|
||||
hexToBin,
|
||||
isHex,
|
||||
binToUtf8,
|
||||
utf8ToBin,
|
||||
bigIntToCompactUint,
|
||||
secp256k1,
|
||||
sha256,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
import type {
|
||||
RecoverableSignature,
|
||||
WalletImportFormatType,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Private Key entity.
|
||||
*/
|
||||
export class PrivateKey {
|
||||
// The raw bytes representing the Private Key.
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Construct a new Private Key.
|
||||
*
|
||||
* @throws {Error} If Private Key cannot be constructed.
|
||||
*
|
||||
* @param bytes {Uint8Array} The raw bytes (32) for the Private Key.
|
||||
*/
|
||||
constructor(bytes: Uint8Array) {
|
||||
// Ensure that the Private Key is valid.
|
||||
if (!secp256k1.validatePrivateKey(bytes)) {
|
||||
throw new Error(
|
||||
`Private Key is invalid: Must be >= 0x01 and <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140.`
|
||||
);
|
||||
}
|
||||
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Private Key Entity from the given bytes (as hexadecimal string).
|
||||
*
|
||||
* @param privateKeyHex {string} Raw Private Key bytes encoded as hexadecimal string.
|
||||
*
|
||||
* @throws {Error} If Private Key could not be created.
|
||||
*
|
||||
* @returns {PrivateKey} The created Private Key Entity.
|
||||
*/
|
||||
public static fromHex(privateKeyHex: string): PrivateKey {
|
||||
// Ensure that the given string is hex.
|
||||
if (!isHex(privateKeyHex)) {
|
||||
throw new Error(
|
||||
`The given string (${privateKeyHex}) is not valid hexadecimal.`
|
||||
);
|
||||
}
|
||||
|
||||
// Convert the hex to binary (Uint8Array).
|
||||
const privateKeyBin = hexToBin(privateKeyHex);
|
||||
|
||||
// Create a Private Key Entity from the binary.
|
||||
return new PrivateKey(privateKeyBin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Private Key from a given WIF (Note that the network information from the WIF will be lost).
|
||||
*
|
||||
* @throws {Error} If Private Key cannot be created.
|
||||
*
|
||||
* @returns {PrivateKey} The Private Key
|
||||
*/
|
||||
public static fromWIF(
|
||||
wif: string,
|
||||
removePrefixes = ['bitcoincash:', 'wif:']
|
||||
): PrivateKey {
|
||||
// Remove the prefixes from the WIF (if they exist).
|
||||
const wifWithoutPrefixes = removePrefixes.reduce((result, prefix) => {
|
||||
return result.startsWith(prefix) ? result.slice(prefix.length) : result;
|
||||
}, wif);
|
||||
|
||||
// Attempt to decode the WIF.
|
||||
const decodeResult = decodePrivateKeyWif(wifWithoutPrefixes);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Return a new Private Key entity.
|
||||
return new PrivateKey(decodeResult.privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random Private Key.
|
||||
*
|
||||
* @returns {PrivateKey} The randomly generated Private Key
|
||||
*/
|
||||
public static generateRandom(): PrivateKey {
|
||||
const bytes = generatePrivateKey();
|
||||
|
||||
return new PrivateKey(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the public key from this private key.
|
||||
*
|
||||
* @throws {Error} If Public Key cannot be derived.
|
||||
*
|
||||
* @returns {PublicKey} The derived Public Key.
|
||||
*/
|
||||
public derivePublicKey(): PublicKey {
|
||||
// Attempt to derive the public key.
|
||||
const deriveResult = secp256k1.derivePublicKeyCompressed(this.bytes);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof deriveResult === 'string') {
|
||||
throw new Error(deriveResult);
|
||||
}
|
||||
|
||||
// Return the derived Public Key.
|
||||
return new PublicKey(deriveResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message hash with this Private Key using a Recoverable Compact Signature.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
*
|
||||
* @throws {Error} If message hash cannot be signed.
|
||||
*
|
||||
* @returns {Uint8Array} The generated signature.
|
||||
*/
|
||||
public signMessageHashRecoverableCompact(
|
||||
messageHash: Uint8Array
|
||||
): RecoverableSignature {
|
||||
// Attempt to sign the message.
|
||||
const signResult = secp256k1.signMessageHashRecoverableCompact(
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof signResult === 'string') {
|
||||
throw new Error(signResult);
|
||||
}
|
||||
|
||||
// Return the signature.
|
||||
return signResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message hash with this Private Key using a Compact Signature.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
*
|
||||
* @throws {Error} If message hash cannot be signed.
|
||||
*
|
||||
* @returns {Uint8Array} The generated signature.
|
||||
*/
|
||||
public signMessageHashCompact(messageHash: Uint8Array): Uint8Array {
|
||||
// Attempt to sign the message.
|
||||
const signResult = secp256k1.signMessageHashCompact(
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof signResult === 'string') {
|
||||
throw new Error(signResult);
|
||||
}
|
||||
|
||||
// Return the signature.
|
||||
return signResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message hash with this Private Key using a Schnorr Signature.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
*
|
||||
* @throws {Error} If message hash cannot be signed.
|
||||
*
|
||||
* @returns {Uint8Array} The generated signature.
|
||||
*/
|
||||
public signMessageHashSchnorr(messageHash: Uint8Array): Uint8Array {
|
||||
// Attempt to sign the message.
|
||||
const signResult = secp256k1.signMessageHashSchnorr(
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof signResult === 'string') {
|
||||
throw new Error(signResult);
|
||||
}
|
||||
|
||||
// Return the signature.
|
||||
return signResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message hash with this Private Key using a DER Signature.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
*
|
||||
* @throws {Error} If message hash cannot be signed.
|
||||
*
|
||||
* @returns {Uint8Array} The generated signature.
|
||||
*/
|
||||
public signMessageHashDER(messageHash: Uint8Array): Uint8Array {
|
||||
const signResult = secp256k1.signMessageHashDER(this.bytes, messageHash);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof signResult === 'string') {
|
||||
throw new Error(signResult);
|
||||
}
|
||||
|
||||
// Return the signature.
|
||||
return signResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message hash with this Private Key using an Electron Cash Signature.
|
||||
*
|
||||
* This applies the Bitcoin and Electron Magic Bytes.
|
||||
*
|
||||
* NOTE: Electron can verify this message successfully, but generates them differently internally.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
*
|
||||
* @throws {Error} If message hash cannot be signed.
|
||||
*
|
||||
* @returns {Uint8Array} The generated signature.
|
||||
*/
|
||||
public signMessageElectron(message: string): Uint8Array {
|
||||
// Compile the message.
|
||||
const magicBytes = utf8ToBin('\x18Bitcoin Signed Message:\n');
|
||||
const messageBytes = utf8ToBin(message);
|
||||
const messageLengthBytes = bigIntToCompactUint(BigInt(messageBytes.length)); // hexToBin(messageBytes.length.toString(16)); //
|
||||
const messageFinal = new Uint8Array([
|
||||
...magicBytes,
|
||||
...messageLengthBytes,
|
||||
...messageBytes,
|
||||
]);
|
||||
|
||||
// Hash the message.
|
||||
// NOTE: That we hash256 (2 x sha256) the message.
|
||||
const messageHash = sha256.hash(sha256.hash(messageFinal));
|
||||
|
||||
// Use Recoverable Compact Signature.
|
||||
const signature = this.signMessageHashRecoverableCompact(messageHash);
|
||||
|
||||
// Apply Electon encoding.
|
||||
const electronEncoding = new Uint8Array([
|
||||
...[31 + signature.recoveryId],
|
||||
...signature.signature,
|
||||
]);
|
||||
|
||||
// Return the signature.
|
||||
return electronEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the raw bytes of this Private Key to hexadecimal encoding.
|
||||
*
|
||||
* @returns {string} The raw bytes of the Private Key as a hexadecimal string.
|
||||
*/
|
||||
public toHex(): string {
|
||||
// Return the hexadecimal encoded bytes of this Private Key.
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this Private Key to a raw 32 byte representation.
|
||||
*
|
||||
* @returns {Uint8Array} The raw Private Key as a Uint8Array.
|
||||
*/
|
||||
public toBytes(): Uint8Array {
|
||||
// Return the raw bytes for this Private Key.
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this Private Key to a WIF format.
|
||||
*
|
||||
* @param type {WalletImportFormatType} The type of WIF to create.
|
||||
*
|
||||
* @returns {string} The Private Key in WIF format.
|
||||
*/
|
||||
public toWif(type: WalletImportFormatType = 'mainnet'): string {
|
||||
// Return the encoded Private Key WIF.
|
||||
return encodePrivateKeyWif(this.bytes, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Private Key as a mainnet WIF.
|
||||
*
|
||||
* @returns {string} The Private Key as a mainnet WIF.
|
||||
*/
|
||||
public toString(): string {
|
||||
// Return the encoded Mainnet Private Key WIF.
|
||||
return this.toWif();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import { Address } from './address.js';
|
||||
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
isHex,
|
||||
secp256k1,
|
||||
sha256,
|
||||
ripemd160,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Public Key Entity.
|
||||
*/
|
||||
export class PublicKey {
|
||||
// The raw bytes representing the Public Key.
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Construct a new Public Key.
|
||||
*
|
||||
* @param bytes {Uint8Array} The raw bytes (33) for the Public Key.
|
||||
*/
|
||||
constructor(bytes: Uint8Array) {
|
||||
// Ensure that exactly 33 Bytes are given.
|
||||
if (!secp256k1.validatePublicKey(bytes)) {
|
||||
throw new Error(`Invalid Public Key.`);
|
||||
}
|
||||
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Public Key entity from the given hexadecimal string.
|
||||
*
|
||||
* @param publicKeyHex {string} Hexadecimal encoding of the Public Key bytes.
|
||||
*
|
||||
* @throws {Error} If Public Key cannot be created.
|
||||
*
|
||||
* @returns {PublicKey} The created Public Key entity.
|
||||
*/
|
||||
public static fromHex(publicKeyHex: string): PublicKey {
|
||||
// Ensure that the given string is hex.
|
||||
if (!isHex(publicKeyHex)) {
|
||||
throw new Error(
|
||||
`The given string (${publicKeyHex}) is not valid hexadecimal.`
|
||||
);
|
||||
}
|
||||
|
||||
// Convert the hex to binary (Uint8Array).
|
||||
const publicKey = hexToBin(publicKeyHex);
|
||||
|
||||
// Create a Public Key Entity from the binary.
|
||||
return new PublicKey(publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the Hash160 for this Public Key.
|
||||
*
|
||||
* @returns {Uint8Array} The Hash160 of the Public Key.
|
||||
*/
|
||||
public deriveHash160(): Uint8Array {
|
||||
// SHA256 Hash the public key.
|
||||
const sha256Hash = sha256.hash(this.bytes);
|
||||
|
||||
// RIPEMD160 the SHA256 hash.
|
||||
const ripemd160Hash = ripemd160.hash(sha256Hash);
|
||||
|
||||
// And then return it.
|
||||
return ripemd160Hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the Hash160 as Hex for this Public Key.
|
||||
*
|
||||
* @returns {string} The Hash160 of the Public Key as hex.
|
||||
*/
|
||||
public deriveHash160Hex(): string {
|
||||
const hash160Bytes = this.deriveHash160();
|
||||
|
||||
return binToHex(hash160Bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the Address for this Public Key.
|
||||
*
|
||||
* @returns {Address} The Address for this Public Key.
|
||||
*/
|
||||
public deriveAddress(): Address {
|
||||
// Get the RIPEMD160 Hash of the Public Key.
|
||||
const ripemd160Hash = this.deriveHash160();
|
||||
|
||||
// Return an instance of address
|
||||
return Address.fromHash160(ripemd160Hash, 'P2PKH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a compact message hash was signed with the corresponding Private Key.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
* @param signature {Uint8Array) The signature of the message.
|
||||
*
|
||||
* @returns {boolean} True if signature and message hash verifies, false otherwise..
|
||||
*/
|
||||
public verifyMessageHashCompact(
|
||||
messageHash: Uint8Array,
|
||||
signature: Uint8Array
|
||||
): boolean {
|
||||
const validity = secp256k1.verifySignatureCompact(
|
||||
signature,
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
return validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a DER message hash was signed with the corresponding Private Key.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
* @param signature {Uint8Array) The signature of the message.
|
||||
*
|
||||
* @returns {boolean} True if signature and message hash verifies, false otherwise..
|
||||
*/
|
||||
public verifyMessageHashDER(
|
||||
messageHash: Uint8Array,
|
||||
signature: Uint8Array
|
||||
): boolean {
|
||||
const validity = secp256k1.verifySignatureDER(
|
||||
signature,
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
return validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a Schnorr message hash was signed with the corresponding Private Key.
|
||||
*
|
||||
* @param messageHash {Uint8Array} The hash of the message.
|
||||
* @param signature {Uint8Array) The signature of the message.
|
||||
*
|
||||
* @returns {boolean} True if signature and message hash verifies, false otherwise..
|
||||
*/
|
||||
public verifyMessageHashSchnorr(
|
||||
messageHash: Uint8Array,
|
||||
signature: Uint8Array
|
||||
): boolean {
|
||||
const validity = secp256k1.verifySignatureSchnorr(
|
||||
signature,
|
||||
this.bytes,
|
||||
messageHash
|
||||
);
|
||||
|
||||
return validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the raw bytes of this Public Key to a hexadecimal representation.
|
||||
*
|
||||
* @returns {string} The raw bytes Public Key encoded as Hexadecimal.
|
||||
*/
|
||||
public toHex(): string {
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw bytes of this Public Key.
|
||||
*
|
||||
* @returns {Uint8Array} The raw bytes of the Public Key.
|
||||
*/
|
||||
public toBytes(): Uint8Array {
|
||||
// Return the raw bytes for this Public Key.
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hexadecimal representation of the raw bytes of the Public Key.
|
||||
*
|
||||
* @returns {string} The raw bytes of the Public Key as hexadecimal.
|
||||
*/
|
||||
public toString(): string {
|
||||
// Return the raw Public Key byes encoded as a hexadecimal string.
|
||||
return this.toHex();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Satoshis Primitive
|
||||
*/
|
||||
export class Satoshis {
|
||||
public readonly satoshis: bigint;
|
||||
|
||||
/**
|
||||
* Construct a new instance of Satoshis.
|
||||
*
|
||||
* This is the same as using fromSatoshis().
|
||||
*
|
||||
* @param satoshis {number} The satoshi amount.
|
||||
*/
|
||||
constructor(satoshis: bigint | number) {
|
||||
// Set the satoshi amount.
|
||||
this.satoshis = BigInt(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates from a Satoshi Amount.
|
||||
*
|
||||
* @param satoshis {number} The Satoshi amount.
|
||||
*
|
||||
* @returns {Satoshis} New instance of Satoshis.
|
||||
*/
|
||||
public static fromSats(satoshis: bigint | number): Satoshis {
|
||||
return new Satoshis(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates from a Satoshi Amount.
|
||||
*
|
||||
* @param satoshis {number} The Satoshi amount.
|
||||
*
|
||||
* @deprecated Use fromSats() instead.
|
||||
*
|
||||
* @returns {Satoshis} New instance of Satoshis.
|
||||
*/
|
||||
public static fromSatoshis(satoshis: bigint | number): Satoshis {
|
||||
return new Satoshis(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates from a Bits Amount (100 satoshis = 1 bit).
|
||||
*
|
||||
* @param bits {number} The Bits amount.
|
||||
*
|
||||
* @returns {Satoshis} New instance of Satoshis.
|
||||
*/
|
||||
public static fromBits(bits: number): Satoshis {
|
||||
const satoshis = Satoshis.roundToDigits(bits * 100, 0);
|
||||
|
||||
return new Satoshis(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates from a mBCH Amount (100,000 satoshis = 1 mBCH).
|
||||
*
|
||||
* @param mBCH {number} The mBCH amount.
|
||||
*
|
||||
* @returns {Satoshis} New instance of Satoshis.
|
||||
*/
|
||||
public static fromMBCH(mBCH: number): Satoshis {
|
||||
const satoshis = Satoshis.roundToDigits(mBCH * 100_000, 0);
|
||||
|
||||
return new Satoshis(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates from a BCH Amount (100,000,000 satoshis = 1 BCH).
|
||||
*
|
||||
* @param bch {number} The BCH amount.
|
||||
*
|
||||
* @returns {Satoshis} New instance of Satoshis.
|
||||
*/
|
||||
public static fromBCH(bch: number): Satoshis {
|
||||
const satoshis = Satoshis.roundToDigits(bch * 100_000_000, 0);
|
||||
|
||||
return new Satoshis(satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a number to the given number of digits.
|
||||
*
|
||||
* @param numberToRound {number} The number to round.
|
||||
* @param digits {number} The number of digits to round to.
|
||||
*
|
||||
* @returns {Number} The rounded number.
|
||||
*/
|
||||
public static roundToDigits(numberToRound: number, digits: number): number {
|
||||
// Set the options of the Number Format object.
|
||||
const options: Intl.NumberFormatOptions = {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
useGrouping: false,
|
||||
numberingSystem: 'latn',
|
||||
};
|
||||
|
||||
// Create an instance of number format using above options.
|
||||
// NOTE: We force the locale to en-GB so that the number is formatted correctly (e.g. with a decimal, not a comma).
|
||||
const numberFormat = new Intl.NumberFormat('en-GB', options);
|
||||
|
||||
// Format the number.
|
||||
const formattedAmount = numberFormat.format(numberToRound);
|
||||
|
||||
// Return the formatted number.
|
||||
return Number(formattedAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to Satoshis (as a BigInt).
|
||||
*
|
||||
* @returns {bigint} The satoshi amount.
|
||||
*/
|
||||
public toBigInt(): bigint {
|
||||
return this.satoshis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to Satoshis.
|
||||
*
|
||||
* @returns {number} The satoshi amount.
|
||||
*/
|
||||
public toSats(): number {
|
||||
return Number(this.satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to Satoshis.
|
||||
*
|
||||
* @deprecated Use toSats() instead.
|
||||
*
|
||||
* @returns {number} The satoshi amount.
|
||||
*/
|
||||
public toSatoshis(): number {
|
||||
return Number(this.satoshis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to Bits (100 satoshis = 1 bit).
|
||||
*
|
||||
* @returns {number} The bit amount.
|
||||
*/
|
||||
public toBits(): number {
|
||||
return Satoshis.roundToDigits(Number(this.satoshis) / 100, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to mBCH (100,000 satoshis = 1 mBCH).
|
||||
*
|
||||
* @returns {number} The mBCH amount.
|
||||
*/
|
||||
public toMBCH(): number {
|
||||
return Satoshis.roundToDigits(Number(this.satoshis) / 100_000, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to BCH (100,000,000 satoshis = 1 BCH).
|
||||
*
|
||||
* @returns {number} The BCH amount.
|
||||
*/
|
||||
public toBCH(): number {
|
||||
return Satoshis.roundToDigits(Number(this.satoshis) / 100_000_000, 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
assembleBytecodeBCH,
|
||||
disassembleBytecodeBCH,
|
||||
base58AddressToLockingBytecode,
|
||||
cashAddressToLockingBytecode,
|
||||
binToHex,
|
||||
hexToBin,
|
||||
isHex,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Script Entity.
|
||||
*/
|
||||
export class Script {
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Construct a new Script
|
||||
*
|
||||
* @param bytes {Uint8Array} The raw bytes of the Script.
|
||||
*/
|
||||
constructor(bytes: Uint8Array) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Script Entity from the given Assembly String.
|
||||
*
|
||||
* @param asm {string} String of assembly code.
|
||||
*
|
||||
* @throws {Error} If script cannot be decoded.
|
||||
*
|
||||
* @returns {Script} The created Script Entity.
|
||||
*/
|
||||
public static fromASM(asm: string): Script {
|
||||
// Attempt to convert the given ASM to bytecode.
|
||||
const assembleResult = assembleBytecodeBCH(asm);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof assembleResult === 'string') {
|
||||
throw new Error(assembleResult);
|
||||
}
|
||||
|
||||
// If the success flag is not marked, this indicates an error...
|
||||
if (!assembleResult.success) {
|
||||
throw new Error('Could not assemble bytecode from assembly.');
|
||||
}
|
||||
|
||||
// Return new Script Entity from the assembled bytecode.
|
||||
return new Script(assembleResult.bytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Script Entity from the given bytes (as hexadecimal string).
|
||||
*
|
||||
* @param hex {string} Raw script bytes encoded as hexadecimal.
|
||||
*
|
||||
* @throws {Error} If script cannot be decoded.
|
||||
*
|
||||
* @returns {Script} The created Script Entity.
|
||||
*/
|
||||
public static fromHex(hex: string): Script {
|
||||
// Ensure that the given string is hex.
|
||||
if (!isHex(hex)) {
|
||||
throw new Error(`The given string (${hex}) is not valid hexadecimal.`);
|
||||
}
|
||||
|
||||
// Conver the given hex to binary (Uint8Array).
|
||||
const scriptBin = hexToBin(hex);
|
||||
|
||||
// Return new Script entity constructed from the binary.
|
||||
return new Script(scriptBin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Script Entity from the given CashAddr.
|
||||
*
|
||||
* @param cashAddr {string} The Address in CashAddr format.
|
||||
*
|
||||
* @throws {Error} If address cannot be decoded.
|
||||
*
|
||||
* @returns {Script} The created Script Entity.
|
||||
*/
|
||||
public static fromCashAddr(cashAddr: string): Script {
|
||||
const decodeResult = cashAddressToLockingBytecode(cashAddr);
|
||||
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
return new Script(decodeResult.bytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Script Entity from the given Legacy Address.
|
||||
*
|
||||
* @param legacyAddress {string} The Address in Legacy format.
|
||||
*
|
||||
* @throws {Error} If address cannot be decoded.
|
||||
*
|
||||
* @returns {Script} The created Script Entity.
|
||||
*/
|
||||
public static fromLegacyAddress(legacyAddress: string): Script {
|
||||
const decodeResult = base58AddressToLockingBytecode(legacyAddress);
|
||||
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
return new Script(decodeResult.bytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this script to a human-readable string of assembly.
|
||||
*
|
||||
* @returns {string} Human-readable assembly.
|
||||
*/
|
||||
public toASM(joinWith = '\n'): string {
|
||||
const disassembledBytecode = disassembleBytecodeBCH(this.bytes);
|
||||
|
||||
if (joinWith) {
|
||||
return disassembledBytecode.split(' ').join(joinWith);
|
||||
}
|
||||
|
||||
return disassembledBytecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw bytecode for this script.
|
||||
*
|
||||
* @returns {Uint8Array} The compiled bytes.
|
||||
*/
|
||||
public toBytes(): Uint8Array {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw bytecode for this script as a hex string.
|
||||
*
|
||||
* @returns {string} The compiled bytes as a hex string.
|
||||
*/
|
||||
public toHex(): string {
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
encodeTransaction,
|
||||
decodeTransaction,
|
||||
isHex,
|
||||
} from '@bitauth/libauth';
|
||||
import type { TransactionBCH } from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Transaction Entity.
|
||||
*/
|
||||
export class Transaction {
|
||||
constructor(public transaction: TransactionBCH) {}
|
||||
|
||||
/**
|
||||
* Create a Transaction Entity from the given byte data.
|
||||
*
|
||||
* @param bytes {Uint8Array} The raw byte data of the transaction.
|
||||
*
|
||||
* @throws {Error} If transaction data cannot be decoded.
|
||||
*
|
||||
* @returns {Transaction} The created Transaction Entity.
|
||||
*/
|
||||
public static fromBytes(bytes: Uint8Array): Transaction {
|
||||
// Attempt to decode the raw transaction bytes.
|
||||
const decodeResult = decodeTransaction(bytes);
|
||||
|
||||
// If a string is returned, this indicates an error...
|
||||
if (typeof decodeResult === 'string') {
|
||||
throw new Error(decodeResult);
|
||||
}
|
||||
|
||||
// Return a new instance of the Transaction Entity created from the decode result.
|
||||
return new Transaction(decodeResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Transaction Entity from the given byte data encoded as hexadecimal string.
|
||||
*
|
||||
* @param hex {string} The raw byte data (encoded as hexadecimal string) of the transaction.
|
||||
*
|
||||
* @throws {Error} If transaction cannot be decoded.
|
||||
*
|
||||
* @returns {Transaction} The created Transaction Entity.
|
||||
*/
|
||||
public static fromHex(hex: string): Transaction {
|
||||
// Ensure that the given string is hex.
|
||||
if (!isHex(hex)) {
|
||||
throw new Error('The given string is not valid hexadecimal.');
|
||||
}
|
||||
|
||||
// Convert our hex to binary.
|
||||
const bytes = hexToBin(hex);
|
||||
|
||||
// Return the transaction constructed from our bytes.
|
||||
return Transaction.fromBytes(bytes);
|
||||
}
|
||||
|
||||
public toBytes(): Uint8Array {
|
||||
const bytes = encodeTransaction(this.transaction);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public toHex(): string {
|
||||
const bytes = this.toBytes();
|
||||
|
||||
return binToHex(bytes);
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.transaction.version;
|
||||
}
|
||||
|
||||
public getInputs() {
|
||||
return this.transaction.inputs;
|
||||
}
|
||||
|
||||
public getOutputs() {
|
||||
return this.transaction.outputs;
|
||||
}
|
||||
|
||||
public getLocktime() {
|
||||
return this.transaction.locktime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user