// Package proquint renders identifiers as pronounceable quintuplets per // arXiv:0901.4016: alternating consonants and vowels, five letters per // sixteen bits, blocks joined by dashes. package proquint import ( "encoding/binary" "strings" ) const consonants = "bdfghjklmnprstvz" const vowels = "aiou" // Encode renders a 64-bit identifier as four proquint blocks. func Encode(id int64) string { var bytes [8]byte binary.BigEndian.PutUint64(bytes[:], uint64(id)) blocks := make([]string, 4) for i := range blocks { blocks[i] = block(binary.BigEndian.Uint16(bytes[i*2:])) } return strings.Join(blocks, "-") } func block(word uint16) string { return string([]byte{ consonants[word>>12], vowels[(word>>10)&3], consonants[(word>>6)&15], vowels[(word>>4)&3], consonants[word&15], }) } // Decode parses four proquint blocks into a 64-bit identifier. func Decode(text string) (int64, bool) { bytes, ok := PrefixBytes(text) if !ok || len(bytes) != 8 { return 0, false } return int64(binary.BigEndian.Uint64(bytes)), true } // PrefixBytes parses one to four proquint blocks into the leading bytes of // a 64-bit identifier, enabling unique-prefix resolution like git. func PrefixBytes(text string) ([]byte, bool) { if text == "" { return nil, false } parts := strings.Split(text, "-") if len(parts) > 4 { return nil, false } out := make([]byte, 0, len(parts)*2) for _, part := range parts { if len(part) != 5 { return nil, false } letters := []byte(part) consonant := func(index int) (uint16, bool) { value := strings.IndexByte(consonants, letters[index]) return uint16(value), value >= 0 } c1, ok := consonant(0) if !ok { return nil, false } v1 := strings.IndexByte(vowels, letters[1]) c2, ok := consonant(2) if !ok || v1 < 0 { return nil, false } v2 := strings.IndexByte(vowels, letters[3]) c3, ok := consonant(4) if !ok || v2 < 0 { return nil, false } word := c1<<12 | uint16(v1)<<10 | c2<<6 | uint16(v2)<<4 | c3 out = binary.BigEndian.AppendUint16(out, word) } return out, true }