github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/basex.go (about)

     1  // Copyright 2020 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"math/big"
    10  	"strconv"
    11  )
    12  
    13  const invalidBaseIndex = 0xFF
    14  
    15  var Base30 = NewBaseX("abcdefghjkmnpqrsuvwxyz23456789")
    16  var Base58 = NewBaseX("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
    17  
    18  type BaseXEncoder struct {
    19  	size        int
    20  	base        *big.Int
    21  	alphabet    string
    22  	alphabetMap [256]byte
    23  }
    24  
    25  func NewBaseX(alphabet string) *BaseXEncoder {
    26  	enc := &BaseXEncoder{
    27  		size:     len(alphabet),
    28  		base:     big.NewInt(int64(len(alphabet))),
    29  		alphabet: alphabet,
    30  	}
    31  	if enc.size > 255 {
    32  		panic("unsupported BaseX alphabet size, got " + strconv.Itoa(enc.size))
    33  	}
    34  	for i := range enc.alphabetMap {
    35  		enc.alphabetMap[i] = invalidBaseIndex
    36  	}
    37  	for i, c := range alphabet {
    38  		enc.alphabetMap[c] = uint8(i)
    39  	}
    40  	return enc
    41  }
    42  
    43  func reverseBuf(buf []byte) {
    44  	tot := len(buf)
    45  	mid := tot / 2
    46  	for i := 0; i < mid; i++ {
    47  		buf[i], buf[tot-i-1] = buf[tot-i-1], buf[i]
    48  	}
    49  }
    50  
    51  func (b *BaseXEncoder) EncodeToString(input []byte) string {
    52  	num := new(big.Int).SetBytes(input)
    53  	buf := make([]byte, 0, len(input))
    54  	rem := new(big.Int)
    55  	quo := new(big.Int)
    56  
    57  	for num.Sign() != 0 {
    58  		num, rem = quo.QuoRem(num, b.base, rem)
    59  		c := b.alphabet[rem.Uint64()]
    60  		buf = append(buf, c)
    61  	}
    62  
    63  	// Pad leading zeros...
    64  	for _, c := range input {
    65  		if c == 0x0 {
    66  			buf = append(buf, b.alphabet[0])
    67  		} else {
    68  			// Stop adding padding after the first nonzero byte.
    69  			break
    70  		}
    71  	}
    72  	reverseBuf(buf)
    73  
    74  	return string(buf)
    75  }
    76  
    77  func (b *BaseXEncoder) DecodeString(inp string) (outp []byte, err error) {
    78  	place := big.NewInt(1)
    79  	buf := []byte(inp)
    80  	padlen := 0
    81  
    82  	// Advance to first non-pad byte
    83  	for ; padlen < len(buf); padlen++ {
    84  		if buf[padlen] != b.alphabet[0] {
    85  			break
    86  		}
    87  	}
    88  	buf = buf[padlen:]
    89  	reverseBuf(buf)
    90  
    91  	tmp := new(big.Int)
    92  	res := big.NewInt(0)
    93  
    94  	for i, c := range buf {
    95  		charIndex := b.alphabetMap[c]
    96  		if charIndex == invalidBaseIndex {
    97  			err = fmt.Errorf("Bad character '%c' found at pos %d", c, i)
    98  			return
    99  		}
   100  
   101  		tmp.Mul(place, big.NewInt(int64(charIndex)))
   102  		res.Add(res, tmp)
   103  
   104  		if i != len(buf)-1 {
   105  			place.Mul(place, b.base)
   106  		}
   107  	}
   108  	buf = res.Bytes()
   109  	pad := bytes.Repeat([]byte{0}, padlen)
   110  	outp = make([]byte, len(pad)+len(buf))
   111  	copy(outp, pad)
   112  	copy(outp[len(pad):], buf)
   113  
   114  	return
   115  }