11import { TransferableOutput } from '@flarenetwork/flarejs' ;
2+ import { bech32 } from 'bech32' ;
3+ import bs58 from 'bs58' ;
24import {
35 BaseUtils ,
46 Entry ,
@@ -13,7 +15,6 @@ import { ecc } from '@bitgo/secp256k1';
1315import { createHash } from 'crypto' ;
1416import { DeprecatedOutput , DeprecatedTx , Output } from './iface' ;
1517import {
16- DECODED_BLOCK_ID_LENGTH ,
1718 SHORT_PUB_KEY_LENGTH ,
1819 COMPRESSED_PUBLIC_KEY_LENGTH ,
1920 UNCOMPRESSED_PUBLIC_KEY_LENGTH ,
@@ -30,6 +31,7 @@ import {
3031 PADSTART_CHAR ,
3132 HEX_RADIX ,
3233 STRING_TYPE ,
34+ DECODED_BLOCK_ID_LENGTH ,
3335} from './constants' ;
3436
3537// Regex utility functions for hex validation
@@ -44,6 +46,13 @@ export const createFlexibleHexRegex = (requirePrefix = false): RegExp => {
4446} ;
4547
4648export class Utils implements BaseUtils {
49+ public addressToString = ( hrp : string , prefix : string , address : Buffer ) : string => {
50+ // Convert the address bytes to 5-bit words for bech32 encoding
51+ const words = bech32 . toWords ( address ) ;
52+ // Create the full bech32 address with format: P-{hrp}1{bech32_encoded_address}
53+ return `${ prefix } -${ bech32 . encode ( hrp , words ) } ` ;
54+ } ;
55+
4756 public includeIn ( walletAddresses : string [ ] , otxoOutputAddresses : string [ ] ) : boolean {
4857 return walletAddresses . map ( ( a ) => otxoOutputAddresses . includes ( a ) ) . reduce ( ( a , b ) => a && b , true ) ;
4958 }
@@ -71,23 +80,6 @@ export class Utils implements BaseUtils {
7180 return ADDRESS_REGEX . test ( address ) ;
7281 }
7382
74- /**
75- * Checks if it is a valid blockId with length 66 including 0x
76- *
77- * @param {string } hash - blockId to be validated
78- * @returns {boolean } - the validation result
79- */
80- /** @inheritdoc */
81- isValidBlockId ( hash : string ) : boolean {
82- // FlareJS equivalent - check if it's a valid CB58 hash with correct length
83- try {
84- const decoded = Buffer . from ( hash ) ; // FlareJS should provide CB58 utilities
85- return decoded . length === DECODED_BLOCK_ID_LENGTH ;
86- } catch {
87- return false ;
88- }
89- }
90-
9183 /**
9284 * Checks if the string is a valid protocol public key or
9385 * extended public key.
@@ -101,8 +93,7 @@ export class Utils implements BaseUtils {
10193 let pubBuf : Buffer ;
10294 if ( pub . length === SHORT_PUB_KEY_LENGTH ) {
10395 try {
104- // For FlareJS, we'll need to implement CB58 decode functionality
105- pubBuf = Buffer . from ( pub , HEX_ENCODING ) ; // Temporary placeholder
96+ pubBuf = this . cb58Decode ( pub ) ;
10697 } catch {
10798 return false ;
10899 }
@@ -135,9 +126,27 @@ export class Utils implements BaseUtils {
135126 }
136127 }
137128
138- public parseAddress = ( pub : string ) : Buffer => {
139- // FlareJS equivalent for address parsing
140- return Buffer . from ( pub , HEX_ENCODING ) ; // Simplified implementation
129+ public parseAddress = ( address : string ) : Buffer => {
130+ return this . stringToAddress ( address ) ;
131+ } ;
132+
133+ public stringToAddress = ( address : string , hrp ?: string ) : Buffer => {
134+ const parts = address . trim ( ) . split ( '-' ) ;
135+ if ( parts . length < 2 ) {
136+ throw new Error ( 'Error - Valid address should include -' ) ;
137+ }
138+
139+ const split = parts [ 1 ] . lastIndexOf ( '1' ) ;
140+ if ( split < 0 ) {
141+ throw new Error ( 'Error - Valid address must include separator (1)' ) ;
142+ }
143+
144+ const humanReadablePart = parts [ 1 ] . slice ( 0 , split ) ;
145+ if ( humanReadablePart !== 'flare' && humanReadablePart !== 'costwo' ) {
146+ throw new Error ( 'Error - Invalid HRP' ) ;
147+ }
148+
149+ return Buffer . from ( bech32 . fromWords ( bech32 . decode ( parts [ 1 ] ) . words ) ) ;
141150 } ;
142151
143152 /**
@@ -263,7 +272,12 @@ export class Utils implements BaseUtils {
263272
264273 /** @inheritdoc */
265274 isValidTransactionId ( txId : string ) : boolean {
266- throw new NotImplementedError ( 'isValidTransactionId not implemented' ) ;
275+ return this . isValidId ( txId ) ;
276+ }
277+
278+ /** @inheritdoc */
279+ isValidBlockId ( blockId : string ) : boolean {
280+ return this . isValidId ( blockId ) ;
267281 }
268282
269283 /**
@@ -323,7 +337,16 @@ export class Utils implements BaseUtils {
323337 */
324338 verifySignature ( network : FlareNetwork , message : Buffer , signature : Buffer , publicKey : Buffer ) : boolean {
325339 try {
326- return ecc . verify ( message , publicKey , signature ) ;
340+ // Hash the message first - must match the hash used in signing
341+ const messageHash = createHash ( 'sha256' ) . update ( message ) . digest ( ) ;
342+
343+ // Extract the actual signature without recovery parameter
344+ if ( signature . length !== 65 ) {
345+ throw new Error ( 'Invalid signature length - expected 65 bytes (64 bytes signature + 1 byte recovery)' ) ;
346+ }
347+ const sigOnly = signature . slice ( 0 , 64 ) ;
348+
349+ return ecc . verify ( messageHash , publicKey , sigOnly ) ;
327350 } catch ( error ) {
328351 return false ;
329352 }
@@ -510,34 +533,6 @@ export class Utils implements BaseUtils {
510533 return parseInt ( outputidx . toString ( HEX_ENCODING ) , HEX_RADIX ) . toString ( ) ;
511534 }
512535
513- /**
514- * CB58 decode function - simple Base58 decode implementation
515- * @param {string } data - CB58 encoded string
516- * @returns {Buffer } decoded buffer
517- */
518- cb58Decode ( data : string ) : Buffer {
519- // For now, use a simple hex decode as placeholder
520- // In a full implementation, this would be proper CB58 decoding
521- try {
522- return Buffer . from ( data , HEX_ENCODING ) ;
523- } catch {
524- // Fallback to buffer from string
525- return Buffer . from ( data ) ;
526- }
527- }
528-
529- /**
530- * Convert address buffer to bech32 string
531- * @param {string } hrp - Human readable part
532- * @param {string } chainid - Chain identifier
533- * @param {Buffer } addressBuffer - Address buffer
534- * @returns {string } Address string
535- */
536- addressToString ( hrp : string , chainid : string , addressBuffer : Buffer ) : string {
537- // Simple implementation - in practice this would use bech32 encoding
538- return `${ chainid } -${ addressBuffer . toString ( HEX_ENCODING ) } ` ;
539- }
540-
541536 /**
542537 * Convert string to bytes for FlareJS memo
543538 * Follows FlareJS utils.stringToBytes pattern
@@ -600,6 +595,65 @@ export class Utils implements BaseUtils {
600595 validateMemoSize ( memoBytes : Uint8Array , maxSize = 4096 ) : boolean {
601596 return memoBytes . length <= maxSize ;
602597 }
598+
599+ /**
600+ * Adds a checksum to a Buffer and returns the concatenated result
601+ */
602+ private addChecksum ( buff : Buffer ) : Buffer {
603+ const hashSlice = createHash ( 'sha256' ) . update ( buff ) . digest ( ) . slice ( 28 ) ;
604+ return Buffer . concat ( [ buff , hashSlice ] ) ;
605+ }
606+
607+ /**
608+ * Validates a checksum on a Buffer and returns true if valid, false if not
609+ */
610+ private validateChecksum ( buff : Buffer ) : boolean {
611+ const hashSlice = buff . slice ( buff . length - 4 ) ;
612+ const calculatedHashSlice = createHash ( 'sha256' )
613+ . update ( buff . slice ( 0 , buff . length - 4 ) )
614+ . digest ( )
615+ . slice ( 28 ) ;
616+ return hashSlice . toString ( 'hex' ) === calculatedHashSlice . toString ( 'hex' ) ;
617+ }
618+
619+ /**
620+ * Encodes a Buffer as a base58 string with checksum
621+ */
622+ public cb58Encode ( bytes : Buffer ) : string {
623+ const withChecksum = this . addChecksum ( bytes ) ;
624+ return bs58 . encode ( withChecksum ) ;
625+ }
626+
627+ /**
628+ * Decodes a base58 string with checksum to a Buffer
629+ */
630+ public cb58Decode ( str : string ) : Buffer {
631+ const decoded = bs58 . decode ( str ) ;
632+ if ( ! this . validateChecksum ( Buffer . from ( decoded ) ) ) {
633+ throw new Error ( 'Invalid checksum' ) ;
634+ }
635+ return Buffer . from ( decoded . slice ( 0 , decoded . length - 4 ) ) ;
636+ }
637+
638+ /**
639+ * Checks if a string is a valid CB58 (base58 with checksum) format
640+ */
641+ private isCB58 ( str : string ) : boolean {
642+ try {
643+ this . cb58Decode ( str ) ;
644+ return true ;
645+ } catch {
646+ return false ;
647+ }
648+ }
649+
650+ isValidId ( id : string ) : boolean {
651+ try {
652+ return this . isCB58 ( id ) && this . cb58Decode ( id ) . length === DECODED_BLOCK_ID_LENGTH ;
653+ } catch {
654+ return false ;
655+ }
656+ }
603657}
604658
605659const utils = new Utils ( ) ;
0 commit comments