@@ -419,6 +419,13 @@ public virtual string EncodeData(ReadOnlySpan<byte> data, Bech32EncodingType enc
419419 {
420420 if ( encodingType == null )
421421 throw new ArgumentNullException ( nameof ( encodingType ) ) ;
422+ #if HAS_SPAN
423+ if ( SquashBytes )
424+ data = ByteSquasher ( data , 8 , 5 ) . AsSpan ( ) ;
425+ #else
426+ data = ByteSquasher ( data , 8 , 5 ) ;
427+ #endif
428+
422429#if HAS_SPAN
423430 Span < byte > combined = _Hrp . Length + 1 + data . Length + 6 is int v && v > 256 ? new byte [ v ] :
424431 stackalloc byte [ v ] ;
@@ -434,6 +441,7 @@ public virtual string EncodeData(ReadOnlySpan<byte> data, Bech32EncodingType enc
434441 combinedOffset += _Hrp . Length ;
435442 combined [ combinedOffset ] = 49 ;
436443 combinedOffset ++ ;
444+
437445#if HAS_SPAN
438446 data . CopyTo ( combined . Slice ( combinedOffset ) ) ;
439447#else
@@ -446,9 +454,11 @@ public virtual string EncodeData(ReadOnlySpan<byte> data, Bech32EncodingType enc
446454#else
447455 var checkSum = CreateChecksum ( data , offset , count , encodingType ) ;
448456#endif
457+
449458#if HAS_SPAN
450459 checkSum . CopyTo ( combined . Slice ( combinedOffset , 6 ) ) ;
451460 combinedOffset += 6 ;
461+
452462 for ( int i = 0 ; i < data . Length + 6 ; i ++ )
453463#else
454464 Array . Copy ( checkSum , 0 , combined , combinedOffset , 6 ) ;
@@ -486,6 +496,8 @@ public byte[] DecodeDataRaw(string encoded, out Bech32EncodingType encodingType)
486496 return DecodeDataCore ( encoded , out encodingType ) ;
487497 }
488498 public bool StrictLength { get ; set ; } = true ;
499+ public bool SquashBytes { get ; set ; } = false ;
500+
489501 protected virtual byte [ ] DecodeDataCore ( string encoded , out Bech32EncodingType encodingType )
490502 {
491503 if ( encoded == null )
@@ -543,11 +555,60 @@ protected virtual byte[] DecodeDataCore(string encoded, out Bech32EncodingType e
543555 throw new Bech32FormatException ( $ "Error in Bech32 string at { String . Join ( "," , error ) } ", error ) ;
544556 }
545557#if HAS_SPAN
546- return data . Slice ( 0 , data . Length - 6 ) . ToArray ( ) ;
558+ var arr = data . Slice ( 0 , data . Length - 6 ) . ToArray ( ) ;
547559#else
548- return data . Take ( data . Length - 6 ) . ToArray ( ) ;
560+ var arr = data . Take ( data . Length - 6 ) . ToArray ( ) ;
549561#endif
562+ if ( SquashBytes )
563+ {
564+ arr = ByteSquasher ( arr , 5 , 8 ) ;
565+ if ( arr is null )
566+ throw new FormatException ( "Invalid squashed bech32" ) ;
567+ }
568+ return arr ;
569+ }
570+ #if HAS_SPAN
571+ private static byte [ ] ByteSquasher ( ReadOnlySpan < byte > input , int inputWidth , int outputWidth )
572+ #else
573+ private static byte [ ] ByteSquasher ( byte [ ] input , int inputWidth , int outputWidth )
574+ #endif
575+ {
576+ var bitstash = 0 ;
577+ var accumulator = 0 ;
578+ var output = new List < byte > ( ) ;
579+ var maxOutputValue = ( 1 << outputWidth ) - 1 ;
580+
581+ for ( var i = 0 ; i < input . Length ; i ++ )
582+ {
583+ var c = input [ i ] ;
584+ if ( c >> inputWidth != 0 )
585+ {
586+ return null ;
587+ }
588+
589+ accumulator = ( accumulator << inputWidth ) | c ;
590+ bitstash += inputWidth ;
591+ while ( bitstash >= outputWidth )
592+ {
593+ bitstash -= outputWidth ;
594+ output . Add ( ( byte ) ( ( accumulator >> bitstash ) & maxOutputValue ) ) ;
595+ }
596+ }
597+
598+ // pad if going from 8 to 5
599+ if ( inputWidth == 8 && outputWidth == 5 )
600+ {
601+ if ( bitstash != 0 ) output . Add ( ( byte ) ( ( accumulator << ( outputWidth - bitstash ) ) & maxOutputValue ) ) ;
602+ }
603+ else if ( bitstash >= inputWidth || ( ( accumulator << ( outputWidth - bitstash ) ) & maxOutputValue ) != 0 )
604+ {
605+ // no pad from 5 to 8 allowed
606+ return null ;
607+ }
608+
609+ return output . ToArray ( ) ;
550610 }
611+
551612#if HAS_SPAN
552613 // We don't use this one, but old version of NBitcoin.Altcoins does, we prefer not causing problems if there is a mismatch of
553614 // assembly between NBitcoin.Altcoins and NBitcoin.
0 commit comments