main.mx raw

   1  // A variable declared from an untyped constant takes that constant's default
   2  // type: `acc := 0` is an int32 variable, not a value that is still untyped.
   3  //
   4  // Stage4 left it untyped, which made every later use "context-dependent": the
   5  // `byte(...)` in `out[j] = byte((acc >> bits) & 0x1f)` retyped the whole shift
   6  // chain to uint8, the emitter declared an i8 shift, and the shift ran on acc's
   7  // low byte sign-extended. The five-bit packing below answered 1e 00 02 where
   8  // the correct groups are 02 04 1a - bech32 payloads came out wrong, and the
   9  // web app's npub/nsec encoding with them. Legacy (go/types) types the variable
  10  // int32 and was right, so stage4's declaration typing is what had to change.
  11  package main
  12  
  13  // conv packs eight-bit groups into five-bit ones, the conversion bech32 is
  14  // built on.
  15  func conv(data []byte, out []byte) (n int32) {
  16  	j := 0
  17  	acc := 0 // untyped literal: the variable is int32
  18  	bits := 0
  19  	for _, b := range data {
  20  		acc = (acc << 8) | int32(b)
  21  		bits += 8
  22  		for bits >= 5 {
  23  			bits -= 5
  24  			out[j] = byte((acc >> bits) & 0x1f)
  25  			j++
  26  		}
  27  		acc &= (1 << uint32(bits)) - 1
  28  	}
  29  	if bits > 0 {
  30  		out[j] = byte((acc << (5 - bits)) & 0x1f)
  31  		j++
  32  	}
  33  	return int32(j)
  34  }
  35  
  36  func hexs(b []byte, n int32) (s string) {
  37  	for i := int32(0); i < n; i++ {
  38  		s = s | nd(b[i]>>4) | nd(b[i]&0xf)
  39  	}
  40  	return s
  41  }
  42  
  43  func nd(v byte) (s string) {
  44  	const d = "0123456789abcdef"
  45  	return string([]byte{d[v]})
  46  }
  47  
  48  func main() {
  49  	out := []byte{:8}
  50  	n := conv([]byte{0x90, 0xa8, 0x22, 0x0b, 0x53}, out)
  51  	println("groups " | hexs(out, n))
  52  }
  53