Initial commit

This commit is contained in:
ywlmac
2026-05-13 10:30:19 +08:00
commit a2a693885b
38 changed files with 5757 additions and 0 deletions
@@ -0,0 +1,134 @@
package mnemonics
import (
"crypto/rand"
"encoding/binary"
"fmt"
"math/big"
"strings"
"wallet_sdk/mnemonics/wordlists"
"github.com/tjfoc/gmsm/sm3"
)
var (
// Some bitwise operands for working with big.Ints.
last11BitsMask = big.NewInt(2047)
shift11BitsMask = big.NewInt(2048)
bigOne = big.NewInt(1)
bigTwo = big.NewInt(2)
wordList []string
)
func init() {
wordList = wordlists.English
}
func validateEntropyBitSize(bitSize int) error {
if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 {
return fmt.Errorf("invalid entropy length")
}
return nil
}
func computeChecksum(data []byte) []byte {
hasher := sm3.New()
_, _ = hasher.Write(data) // This error is guaranteed to be nil
return hasher.Sum(nil)
}
func addChecksum(data []byte) []byte {
// Get first byte of sha256
hash := computeChecksum(data)
firstChecksumByte := hash[0]
// len() is in bytes so we divide by 4
checksumBitLength := uint(len(data) / 4)
// For each bit of check sum we want we shift the data one the left
// and then set the (new) right most bit equal to checksum bit at that index
// staring from the left
dataBigInt := new(big.Int).SetBytes(data)
for i := uint(0); i < checksumBitLength; i++ {
// Bitshift 1 left
dataBigInt.Mul(dataBigInt, bigTwo)
// Set rightmost bit if leftmost checksum bit is set
if firstChecksumByte&(1<<(7-i)) > 0 {
dataBigInt.Or(dataBigInt, bigOne)
}
}
return dataBigInt.Bytes()
}
func padByteSlice(slice []byte, length int) []byte {
offset := length - len(slice)
if offset <= 0 {
return slice
}
newSlice := make([]byte, length)
copy(newSlice[offset:], slice)
return newSlice
}
func NewEntropy(bitSize int) ([]byte, error) {
if err := validateEntropyBitSize(bitSize); err != nil {
return nil, err
}
entropy := make([]byte, bitSize/8)
_, _ = rand.Read(entropy) // err is always nil
return entropy, nil
}
func NewMnemonic(entropy []byte) (string, error) {
// Compute some lengths for convenience.
entropyBitLength := len(entropy) * 8
checksumBitLength := entropyBitLength / 32
sentenceLength := (entropyBitLength + checksumBitLength) / 11
// Validate that the requested size is supported.
err := validateEntropyBitSize(entropyBitLength)
if err != nil {
return "", err
}
// Add checksum to entropy.
entropy = addChecksum(entropy)
// Break entropy up into sentenceLength chunks of 11 bits.
// For each word AND mask the rightmost 11 bits and find the word at that index.
// Then bitshift entropy 11 bits right and repeat.
// Add to the last empty slot so we can work with LSBs instead of MSB.
// Entropy as an int so we can bitmask without worrying about bytes slices.
entropyInt := new(big.Int).SetBytes(entropy)
// Slice to hold words in.
words := make([]string, sentenceLength)
// Throw away big.Int for AND masking.
word := big.NewInt(0)
for i := sentenceLength - 1; i >= 0; i-- {
// Get 11 right most bits and bitshift 11 to the right for next time.
word.And(entropyInt, last11BitsMask)
entropyInt.Div(entropyInt, shift11BitsMask)
// Get the bytes representing the 11 bits as a 2 byte slice.
wordBytes := padByteSlice(word.Bytes(), 2)
// Convert bytes to an index and add that word to the list.
words[i] = wordList[binary.BigEndian.Uint16(wordBytes)]
}
return strings.Join(words, " "), nil
}
File diff suppressed because it is too large Load Diff