$prefix, 'type' => ( $version >> 3 ) & 0x0f, 'hash' => $hash, ); } /** True when `$addr` is a well-formed CashAddress with a valid checksum. */ public static function is_valid( $addr ) { return null !== self::decode( $addr ); } /** * Canonical form for storage and comparison: lowercase, prefixed, and * always the non-token p2pkh spelling. * * Two spellings of the same key must not become two accounts, so every * address is squashed to this before it is written to or compared against * the users table. * * @return string '' when the input is not an address. */ public static function normalize( $addr ) { $d = self::decode( $addr ); if ( null === $d ) { return ''; } $type = ( self::TYPE_P2SH === $d['type'] || self::TYPE_P2SH_TOKEN === $d['type'] ) ? self::TYPE_P2SH : self::TYPE_P2PKH; return self::encode( $d['prefix'], $d['hash'], $type ); } /** The address a compressed public key controls. */ public static function from_public_key( $pubkey_bin, $prefix = 'bitcoincash' ) { if ( 33 !== strlen( $pubkey_bin ) && 65 !== strlen( $pubkey_bin ) ) { return ''; } $hash = hash( 'ripemd160', hash( 'sha256', $pubkey_bin, true ), true ); return self::encode( $prefix, $hash, self::TYPE_P2PKH ); } /** A short, human-checkable form for UI: `bchtest:qr4…8ktm`. */ public static function shorten( $addr, $head = 8, $tail = 6 ) { $addr = (string) $addr; $pos = strpos( $addr, ':' ); $body = false === $pos ? $addr : substr( $addr, $pos + 1 ); $pre = false === $pos ? '' : substr( $addr, 0, $pos + 1 ); if ( strlen( $body ) <= $head + $tail + 1 ) { return $addr; } return $pre . substr( $body, 0, $head ) . '…' . substr( $body, -$tail ); } // --------------------------------------------------------------- checksum private static function expand_prefix( $prefix ) { $out = array(); $len = strlen( $prefix ); for ( $i = 0; $i < $len; $i++ ) { $out[] = ord( $prefix[ $i ] ) & 0x1f; } $out[] = 0; return $out; } private static function checksum( $prefix, $data ) { $mod = self::poly_mod( array_merge( self::expand_prefix( $prefix ), $data ) ); $out = array(); for ( $i = 0; $i < 8; $i++ ) { $out[] = ( $mod >> ( 5 * ( 7 - $i ) ) ) & 0x1f; } return $out; } /** * The BCH checksum polynomial over GF(32). * * Works on 40-bit accumulators, so this needs a 64-bit PHP build. 32-bit * PHP has been unsupported by WordPress-adjacent hosting for years; the * core plugin's requirements check says so explicitly rather than letting * addresses silently fail to validate here. */ private static function poly_mod( $values ) { $c = 1; foreach ( $values as $d ) { $c0 = ( $c >> 35 ) & 0xff; $c = ( ( $c & 0x07ffffffff ) << 5 ) ^ $d; if ( $c0 & 0x01 ) { $c ^= 0x98f2bc8e61; } if ( $c0 & 0x02 ) { $c ^= 0x79b76d99e2; } if ( $c0 & 0x04 ) { $c ^= 0xf33e5fb3c4; } if ( $c0 & 0x08 ) { $c ^= 0xae2eabe2a8; } if ( $c0 & 0x10 ) { $c ^= 0x1e4f43e470; } } return $c ^ 1; } // ------------------------------------------------------------- bit fiddling private static function bytes_to_array( $bin ) { return array_values( unpack( 'C*', $bin ) ); } private static function array_to_bytes( $arr ) { $out = ''; foreach ( $arr as $v ) { $out .= chr( $v & 0xff ); } return $out; } /** * Regroup a list of values from `$from` bits each to `$to` bits each. * * @param bool $pad Pad the tail (encoding) or require it to be zero (decoding). * @return array|null */ private static function convert_bits( $values, $from, $to, $pad ) { $acc = 0; $bits = 0; $out = array(); $max = ( 1 << $to ) - 1; foreach ( $values as $v ) { if ( $v < 0 || ( $v >> $from ) !== 0 ) { return null; } $acc = ( $acc << $from ) | $v; $bits += $from; while ( $bits >= $to ) { $bits -= $to; $out[] = ( $acc >> $bits ) & $max; } } if ( $pad ) { if ( $bits > 0 ) { $out[] = ( $acc << ( $to - $bits ) ) & $max; } } elseif ( $bits >= $from || ( ( $acc << ( $to - $bits ) ) & $max ) ) { return null; } return $out; } }