import { Immutable } from '../format/format' ; import { decodeBech32 , encodeBech32 , isBech32CharacterSet , regroupBits , } from './bech32' ; export enum CashAddressNetworkPrefix { mainnet = 'bitcoincash' , testnet = 'bchtest' , regtest = 'bchreg' } export const cashAddressBitToSize = { 0 : 160 , 1 : 192 , 2 : 224 , 3 : 256 , 4 : 320 , 5 : 384 , 6 : 448 , 7 : 512 , } as const ; export const cashAddressSizeToBit = { 160 : 0 , 192 : 1 , 224 : 2 , 256 : 3 , 320 : 4 , 384 : 5 , 448 : 6 , 512 : 7 , } as const ; /**
 * The CashAddress specification standardizes the format of the version byte:
 * - Most significant bit: reserved, must be `0`
 * - next 4 bits: Address Type
 * - 3 least significant bits: Hash Size
 *
 * Only two Address Type values are currently standardized:
 * - 0 (`0b0000`): P2PKH
 * - 1 (`0b0001`): P2SH
 *
 * While both P2PKH and P2SH addresses always use 160 bit hashes, the
 * CashAddress specification standardizes other sizes for future use (or use by
 * other systems), see `CashAddressSizeBit`.
 *
 * With these constraints, only two version byte values are currently standard.
 */ export enum CashAddressVersionByte { /**
     * Pay to Public Key Hash (P2PKH): `0b00000000`
     *
     * - Most significant bit: `0` (reserved)
     * - Address Type bits: `0000` (P2PKH)
     * - Size bits: `000` (160 bits)
     */ P2PKH = 0b00000000 , /**
     * Pay to Script Hash (P2SH): `0b00001000`
     *
     * - Most significant bit: `0` (reserved)
     * - Address Type bits: `0001` (P2SH)
     * - Size bits: `000` (160 bits)
     */ P2SH = 0b00001000 } /**
 * The address types currently defined in the CashAddress specification. See
 * also: `CashAddressVersionByte`.
 */ export enum CashAddressType { /**
     * Pay to Public Key Hash (P2PKH)
     */ P2PKH = 0 , /**
     * Pay to Script Hash (P2SH)
     */ P2SH = 1 } const cashAddressTypeBitShift = 3 ; export type CashAddressAvailableTypes = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 ; export type CashAddressAvailableSizesInBits = keyof typeof cashAddressSizeToBit ; export type CashAddressAvailableSizes = keyof typeof cashAddressBitToSize ; param type param bitLength export const encodeCashAddressVersionByte = ( type , bitLength ) => ( type << cashAddressTypeBitShift ) | cashAddressSizeToBit [ bitLength ] ; const cashAddressReservedBitMask = 0b10000000 ; const cashAddressTypeBits = 0b1111 ; const cashAddressSizeBits = 0b111 ; const empty = 0 ; export enum CashAddressVersionByteDecodingError { reservedBitSet = 'Reserved bit is set.' } param version export const decodeCashAddressVersionByte = ( version ) => ( version & cashAddressReservedBitMask ) !== empty ? CashAddressVersionByteDecodingError . reservedBitSet : { bitLength : cashAddressBitToSize [ ( version & cashAddressSizeBits ) as keyof typeof cashAddressBitToSize ] , type : ( version >>> cashAddressTypeBitShift ) & cashAddressTypeBits , } ; /**
 * In ASCII, each pair of upper and lower case characters share the same 5 least
 * significant bits.
 */ const asciiCaseInsensitiveBits = 0b11111 ; param prefix export const maskCashAddressPrefix = ( prefix ) => { const result = [ ] ; for ( let i = 0 ; i < prefix . length ; i ++ ) { result . push ( prefix . charCodeAt ( i ) & asciiCaseInsensitiveBits ) ; } return result ; } ; const bech32GeneratorMostSignificantByte = [ 0x98 , 0x79 , 0xf3 , 0xae , 0x1e ] ; const bech32GeneratorRemainingBytes = [ 0xf2bc8e61 , 0xb76d99e2 , 0x3e5fb3c4 , 0x2eabe2a8 , 0x4f43e470 ] ; remarks privateRemarks param v export const cashAddressPolynomialModulo = ( v ) => { let mostSignificantByte = 0 ; let lowerBytes = 1 ; let c = 0 ; for ( let j = 0 ; j < v . length ; j ++ ) { c = mostSignificantByte >>> 3 ; mostSignificantByte &= 0x07 ; mostSignificantByte <<= 5 ; mostSignificantByte |= lowerBytes >>> 27 ; lowerBytes &= 0x07ffffff ; lowerBytes <<= 5 ; lowerBytes ^= v [ j ] ; for ( let i = 0 ; i < bech32GeneratorMostSignificantByte . length ; ++ i ) { if ( c & ( 1 << i ) ) { mostSignificantByte ^= bech32GeneratorMostSignificantByte [ i ] ; lowerBytes ^= bech32GeneratorRemainingBytes [ i ] ; } } } lowerBytes ^= 1 ; if ( lowerBytes < 0 ) { lowerBytes ^= 1 << 31 ; lowerBytes += ( 1 << 30 ) * 2 ; } return mostSignificantByte * ( 1 << 30 ) * 4 + lowerBytes ; } ; const base32WordLength = 5 ; const base256WordLength = 8 ; param checksum export const cashAddressChecksumToUint5Array = ( checksum ) => { const result = [ ] ; for ( let i = 0 ; i < base256WordLength ; ++ i ) { result . push ( checksum & 31 ) ; checksum /= 32 ; } return result . reverse ( ) ; } ; const payloadSeparator = 0 ; param prefix param version param hash export const encodeCashAddressFormat = < Prefix extends string = CashAddressNetworkPrefix , Version extends number = CashAddressVersionByte > ( prefix , version , hash ) => { const checksum40BitPlaceholder = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ; const payloadContents = regroupBits ( { bin : Uint8Array . from ( [ version , ... hash ] ) , resultWordLength : base32WordLength , sourceWordLength : base256WordLength , } ) as number [ ] ; const checksumContents = [ ... maskCashAddressPrefix ( prefix ) , payloadSeparator , ... payloadContents , ... checksum40BitPlaceholder , ] ; const checksum = cashAddressPolynomialModulo ( checksumContents ) ; const payload = [ ... payloadContents , ... cashAddressChecksumToUint5Array ( checksum ) , ] ; return `${ prefix }:${ encodeBech32 ( payload ) }` ; } ; export enum CashAddressEncodingError { unsupportedHashLength = 'CashAddress encoding error: a hash of this length can not be encoded as a valid CashAddress.' } const isValidBitLength = ( bitLength ) : bitLength is CashAddressAvailableSizesInBits => ( cashAddressSizeToBit [ bitLength as CashAddressAvailableSizesInBits ] as CashAddressAvailableSizes | undefined ) !== undefined ; param prefix param type param hash export const encodeCashAddress = < Prefix extends string = CashAddressNetworkPrefix , Type extends CashAddressAvailableTypes = CashAddressType > ( prefix , type , hash ) => { const bitLength = hash . length * base256WordLength ; if ( ! isValidBitLength ( bitLength ) ) { return CashAddressEncodingError . unsupportedHashLength ; } return encodeCashAddressFormat ( prefix , encodeCashAddressVersionByte ( type , bitLength ) , hash ) ; } ; export enum CashAddressDecodingError { improperPadding = 'CashAddress decoding error: the payload is improperly padded.' , invalidCharacters = 'CashAddress decoding error: the payload contains non-bech32 characters.' , invalidChecksum = 'CashAddress decoding error: invalid checksum – please review the address for errors.' , invalidFormat = 'CashAddress decoding error: CashAddresses should be of the form "prefix:payload".' , mismatchedHashLength = 'CashAddress decoding error: mismatched hash length for specified address version.' , reservedByte = 'CashAddress decoding error: unknown CashAddress version, reserved byte set.' } param address export const decodeCashAddressFormat = ( address ) => { const parts = address . toLowerCase ( ) . split ( ':' ) ; if ( parts . length !== 2 || parts [ 0 ] === '' || parts [ 1 ] === '' ) { return CashAddressDecodingError . invalidFormat ; } const [ prefix , payload ] = parts ; if ( ! isBech32CharacterSet ( payload ) ) { return CashAddressDecodingError . invalidCharacters ; } const decodedPayload = decodeBech32 ( payload ) ; const polynomial = [ ... maskCashAddressPrefix ( prefix ) , payloadSeparator , ... decodedPayload , ] ; if ( cashAddressPolynomialModulo ( polynomial ) !== 0 ) { return CashAddressDecodingError . invalidChecksum ; } const checksum40BitPlaceholderLength = 8 ; const payloadContents = regroupBits ( { allowPadding : false , bin : decodedPayload . slice ( 0 , - checksum40BitPlaceholderLength ) , resultWordLength : base256WordLength , sourceWordLength : base32WordLength , } ) ; if ( typeof payloadContents === 'string' ) { return CashAddressDecodingError . improperPadding ; } const [ version , ... hashContents ] = payloadContents ; const hash = Uint8Array . from ( hashContents ) ; return { hash , prefix , version } ; } ; param address export const decodeCashAddress = ( address ) => { const decoded = decodeCashAddressFormat ( address ) ; if ( typeof decoded === 'string' ) { return decoded ; } const info = decodeCashAddressVersionByte ( decoded . version ) ; if ( info === CashAddressVersionByteDecodingError . reservedBitSet ) { return CashAddressDecodingError . reservedByte ; } if ( decoded . hash . length * base256WordLength !== info . bitLength ) { return CashAddressDecodingError . mismatchedHashLength ; } return { hash : decoded . hash , prefix : decoded . prefix , type : info . type , } ; } ; param address param possiblePrefixes export const decodeCashAddressFormatWithoutPrefix = ( address , possiblePrefixes = [ CashAddressNetworkPrefix . mainnet , CashAddressNetworkPrefix . testnet , CashAddressNetworkPrefix . regtest , ] ) => { for ( const prefix of possiblePrefixes ) { const attempt = decodeCashAddressFormat ( `${ prefix }:${ address }` ) ; if ( attempt !== CashAddressDecodingError . invalidChecksum ) { return attempt ; } } return CashAddressDecodingError . invalidChecksum ; } ; const asciiLowerCaseStart = 96 ; remarks param polynomial export const cashAddressPolynomialToCashAddress = ( polynomial ) => { const separatorPosition = polynomial . indexOf ( 0 ) ; const prefix = polynomial . slice ( 0 , separatorPosition ) . map ( ( integer ) => String . fromCharCode ( asciiLowerCaseStart + integer ) ) . join ( '' ) ; const contents = encodeBech32 ( polynomial . slice ( separatorPosition + 1 ) ) ; return `${ prefix }:${ contents }` ; } ; export enum CashAddressCorrectionError { tooManyErrors = 'This address has more than 2 errors and cannot be corrected.' } const finiteFieldOrder = 32 ; privateRemarks param address export const attemptCashAddressFormatErrorCorrection = ( address ) => { const parts = address . toLowerCase ( ) . split ( ':' ) ; if ( parts . length !== 2 || parts [ 0 ] === '' || parts [ 1 ] === '' ) { return CashAddressDecodingError . invalidFormat ; } const [ prefix , payload ] = parts ; if ( ! isBech32CharacterSet ( payload ) ) { return CashAddressDecodingError . invalidCharacters ; } const decodedPayload = decodeBech32 ( payload ) ; const polynomial = [ ... maskCashAddressPrefix ( prefix ) , 0 , ... decodedPayload ] ; const originalChecksum = cashAddressPolynomialModulo ( polynomial ) ; if ( originalChecksum === 0 ) { return { address : cashAddressPolynomialToCashAddress ( polynomial ) , corrections : [ ] , } ; } const syndromes = { } ; for ( let term = 0 ; term < polynomial . length ; term ++ ) { for ( let errorVector = 1 ; errorVector < finiteFieldOrder ; errorVector ++ ) { polynomial [ term ] ^= errorVector ; const correct = cashAddressPolynomialModulo ( polynomial ) ; if ( correct === 0 ) { return { address : cashAddressPolynomialToCashAddress ( polynomial ) , corrections : [ term ] , } ; } const s0 = ( BigInt ( correct ) ^ BigInt ( originalChecksum ) ) . toString ( ) ; syndromes [ s0 ] = term * finiteFieldOrder + errorVector ; polynomial [ term ] ^= errorVector ; } } for ( const [ s0 , pe ] of Object . entries ( syndromes ) ) { const s1Location = ( BigInt ( s0 ) ^ BigInt ( originalChecksum ) ) . toString ( ) ; const s1 = syndromes [ s1Location ] as number | undefined ; if ( s1 !== undefined ) { const correctionIndex1 = Math . trunc ( pe / finiteFieldOrder ) ; const correctionIndex2 = Math . trunc ( s1 / finiteFieldOrder ) ; polynomial [ correctionIndex1 ] ^= pe % finiteFieldOrder ; polynomial [ correctionIndex2 ] ^= s1 % finiteFieldOrder ; return { address : cashAddressPolynomialToCashAddress ( polynomial ) , corrections : [ correctionIndex1 , correctionIndex2 ] . sort ( ( a , b ) => a - b ) , } ; } } return CashAddressCorrectionError . tooManyErrors ; } ;