pkg/sdk/security/crypto/bech32/bech32.go (145 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // Copyright (c) 2017 Takatoshi Nakagawa // Copyright (c) 2019 Google LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // Package bech32 is a modified version of the reference implementation of BIP173. package bech32 import ( "fmt" "strings" ) var charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" var generator = []uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} func polymod(values []byte) uint32 { chk := uint32(1) for _, v := range values { top := chk >> 25 chk = (chk & 0x1ffffff) << 5 chk ^= uint32(v) for i := 0; i < 5; i++ { bit := top >> i & 1 if bit == 1 { chk ^= generator[i] } } } return chk } func hrpExpand(hrp string) []byte { h := []byte(strings.ToLower(hrp)) ret := []byte{} for _, c := range h { ret = append(ret, c>>5) } ret = append(ret, 0) for _, c := range h { ret = append(ret, c&31) } return ret } func verifyChecksum(hrp string, data []byte) bool { return polymod(append(hrpExpand(hrp), data...)) == 1 } func createChecksum(hrp string, data []byte) []byte { values := append(hrpExpand(hrp), data...) values = append(values, []byte{0, 0, 0, 0, 0, 0}...) mod := polymod(values) ^ 1 ret := make([]byte, 6) for p := range ret { shift := 5 * (5 - p) ret[p] = byte(mod>>shift) & 31 } return ret } func convertBits(data []byte, frombits, tobits byte, pad bool) ([]byte, error) { var ret []byte acc := uint32(0) bits := byte(0) maxv := byte(1<<tobits - 1) for idx, value := range data { if value>>frombits != 0 { return nil, fmt.Errorf("invalid data range: data[%d]=%d (frombits=%d)", idx, value, frombits) } acc = acc<<frombits | uint32(value) bits += frombits for bits >= tobits { bits -= tobits ret = append(ret, byte(acc>>bits)&maxv) } } switch { case pad: if bits > 0 { ret = append(ret, byte(acc<<(tobits-bits))&maxv) } case bits >= frombits: return nil, fmt.Errorf("illegal zero padding") case byte(acc<<(tobits-bits))&maxv != 0: return nil, fmt.Errorf("non-zero padding") } return ret, nil } // Encode encodes the HRP and a bytes slice to Bech32. If the HRP is uppercase, // the output will be uppercase. func Encode(hrp string, data []byte) (string, error) { values, err := convertBits(data, 8, 5, true) if err != nil { return "", err } if len(hrp)+len(values)+7 > 90 { return "", fmt.Errorf("too long: hrp length=%d, data length=%d", len(hrp), len(values)) } if len(hrp) < 1 { return "", fmt.Errorf("invalid HRP: %q", hrp) } for p, c := range hrp { if c < 33 || c > 126 { return "", fmt.Errorf("invalid HRP character: hrp[%d]=%d", p, c) } } if strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp { return "", fmt.Errorf("mixed case HRP: %q", hrp) } lower := strings.ToLower(hrp) == hrp hrp = strings.ToLower(hrp) var ret strings.Builder ret.WriteString(hrp) ret.WriteString("1") for _, p := range values { ret.WriteByte(charset[p]) } for _, p := range createChecksum(hrp, values) { ret.WriteByte(charset[p]) } if lower { return ret.String(), nil } return strings.ToUpper(ret.String()), nil } // Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase. func Decode(s string) (hrp string, data []byte, err error) { if len(s) > 90 { return "", nil, fmt.Errorf("too long: len=%d", len(s)) } if strings.ToLower(s) != s && strings.ToUpper(s) != s { return "", nil, fmt.Errorf("mixed case") } pos := strings.LastIndex(s, "1") if pos < 1 || pos+7 > len(s) { return "", nil, fmt.Errorf("separator '1' at invalid position: pos=%d, len=%d", pos, len(s)) } hrp = s[:pos] for p, c := range hrp { if c < 33 || c > 126 { return "", nil, fmt.Errorf("invalid character human-readable part: s[%d]=%d", p, c) } } s = strings.ToLower(s) for p, c := range s[pos+1:] { d := strings.IndexRune(charset, c) if d == -1 { return "", nil, fmt.Errorf("invalid character data part: s[%d]=%v", p, c) } data = append(data, byte(d)) } if !verifyChecksum(hrp, data) { return "", nil, fmt.Errorf("invalid checksum") } data, err = convertBits(data[:len(data)-6], 5, 8, false) if err != nil { return "", nil, err } return hrp, data, nil }