Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Hello world !

  • Loading branch information...
commit b808d830dfd6553ed24404b1860b651427f17a0f 1 parent f5224f2
@speps authored
Showing with 338 additions and 1 deletion.
  1. +22 −0 LICENSE
  2. +4 −1 README.md
  3. +285 −0 hashids.go
  4. +27 −0 hashids_test.go
View
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Remi Gillig
+
+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.
View
5 README.md
@@ -1,4 +1,7 @@
go-hashids
==========
-Go (golang) implementation of http://www.hashids.org
+Go (golang) implementation of http://www.hashids.org
+under MIT License (same as the original implementations)
+
+Original implementations by Ivan Akimov at https://github.com/ivanakimov/hashids.php
View
285 hashids.go
@@ -0,0 +1,285 @@
+// Go implementation of http://www.hashids.org
+// go install
+// under MIT license
+// original implementations by Ivan Akimov at https://github.com/ivanakimov
+
+package hashids
+
+import (
+ "bytes"
+ "math"
+ "strconv"
+)
+
+const DefaultAlphabet string = "xcS4F6h89aUbidefI7jkyunopqrsgCYE5GHTKLMtARXz"
+
+var primes []int = []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43}
+var sepsIndices []int = []int{0, 4, 8, 12}
+
+type HashID struct {
+ Alphabet string
+ MinLength int
+ Salt string
+}
+
+func New() *HashID {
+ return &HashID{Alphabet: DefaultAlphabet}
+}
+
+func (h *HashID) Encrypt(numbers []int) string {
+ if len(numbers) == 0 {
+ panic("encrypting empty array of numbers makes no sense")
+ }
+ for _, n := range numbers {
+ if n < 0 {
+ panic("negative number not supported")
+ }
+ }
+ if len(h.Alphabet) < 4 {
+ panic("alphabet must contain at least 4 characters")
+ }
+
+ alphabetRunes := bytes.Runes([]byte(h.Alphabet))

It is possible to directly convert strings to []rune: []rune(h.Alphabet)

@speps Owner
speps added a note

Thanks I replaced wherever I could

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ saltRunes := bytes.Runes([]byte(h.Salt))
+
+ alphabetRunes, seps, guards := getSepsAndGuards(alphabetRunes)
+
+ alphabetRunes = consistentShuffle(alphabetRunes, saltRunes)
+
+ return string(encode(numbers, alphabetRunes, saltRunes, seps, guards, h.MinLength))
+}
+
+func encode(numbers []int, alphabetOriginal, salt, sepsOriginal, guards []rune, minLength int) []rune {
+ numbersRunes := make([]rune, 0)
+ for _, n := range numbers {
+ numbersRunes = append(numbersRunes, bytes.Runes([]byte(strconv.FormatInt(int64(n), 10)))...)
+ }
+ seps := consistentShuffle(sepsOriginal, numbersRunes)
+
+ alphabet := make([]rune, len(alphabetOriginal))
+ copy(alphabet, alphabetOriginal)
+
+ lotterySalt := new(bytes.Buffer)

This looks more convoluted than it should be: it looks simpler to write:

lotterySalt := make([]byte, 0, 2*len(numbers)) // pre-allocate a bit
for i, n := range numbers {
if i > 0 { lotterySalt = append(lotterySalt, '-') }
lotterySalt = strconv.AppendInt(lotterySalt, int64(n), 10)
}
...
bytes.Runes(lotterySalt)

@speps Owner
speps added a note

Thanks, I did not realize strconv could do this at first :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ for i, n := range numbers {
+ if i > 0 {
+ lotterySalt.WriteString("-")
+ }
+ s := strconv.FormatInt(int64(n), 10)
+ lotterySalt.WriteString(s)
+ }
+ for _, n := range numbers {
+ s := strconv.FormatInt(int64((n+1)*2), 10)
+ lotterySalt.WriteString("-")
+ lotterySalt.WriteString(s)
+ }
+ lottery := consistentShuffle(alphabet, bytes.Runes([]byte(lotterySalt.String())))
+ lotteryRune := lottery[0]
+
+ for i, r := range alphabet {
+ if r == lotteryRune {
+ alphabet = append([]rune{lotteryRune}, append(alphabet[:i], alphabet[i+1:]...)...)
+ break
+ }
+ }
+ saltL := append(bytes.Runes([]byte(strconv.FormatInt(int64(lotteryRune&12345), 10))), salt...)
+
+ result := make([]rune, 0, minLength)
+ result = append(result, lotteryRune)
+ for i, n := range numbers {
+ alphabet = consistentShuffle(alphabet, saltL)
+ hash := hash(n, alphabet)
+ result = append(result, hash...)
+ if (i + 1) < len(numbers) {
+ sepsIndex := (n + i) % len(seps)
+ result = append(result, seps[sepsIndex])
+ }
+ }
+
+ if len(result) < minLength {
+ guardIndex := 0
+ for i, n := range numbers {
+ guardIndex += (i + 1) * n
+ }
+
+ guardIndex %= len(guards)
+ guard := guards[guardIndex]
+
+ result = append([]rune{guard}, result...)
+ if len(result) < minLength {
+ guardIndex = (guardIndex + len(result)) % len(guards)
+ guard = guards[guardIndex]
+ result = append(result, guard)
+ }
+ }
+
+ for len(result) < minLength {
+ padArray := []int{int(alphabet[1]), int(alphabet[0])}
+ padLeft := encode(padArray, alphabet, salt, sepsOriginal, guards, 0)
+ padArrayRunes := append(bytes.Runes([]byte(strconv.FormatInt(int64(padArray[0]), 10))), bytes.Runes([]byte(strconv.FormatInt(int64(padArray[1]), 10)))...)
+ padRight := encode(padArray, alphabet, padArrayRunes, sepsOriginal, guards, 0)
+
+ result = append(padLeft, append(result, padRight...)...)
+ excess := len(result) - minLength
+ if excess > 0 {
+ result = result[excess/2 : excess/2+minLength]
+ }
+
+ alphabet = consistentShuffle(alphabet, append(salt, result...))
+ }
+
+ return result
+}
+
+func (h *HashID) Decrypt(hash string) []int {
+ alphabetRunes := bytes.Runes([]byte(h.Alphabet))
+ saltRunes := bytes.Runes([]byte(h.Salt))
+ hashRunes := bytes.Runes([]byte(hash))
+
+ alphabetRunes, seps, guards := getSepsAndGuards(alphabetRunes)
+
+ alphabetRunes = consistentShuffle(alphabetRunes, saltRunes)
+
+ return decode(hashRunes, alphabetRunes, saltRunes, seps, guards)
+}
+
+func decode(hash, alphabetOriginal, salt, seps, guards []rune) []int {
+ hashes := splitRunes(hash, guards)
+ hashIndex := 0
+ if len(hashes) == 2 || len(hashes) == 3 {
+ hashIndex = 1
+ } else {
+ panic("malformed hash input")
+ }
+
+ hashes = splitRunes(hashes[hashIndex], seps)
+ lotteryRune := hashes[0][0]
+ hashes[0] = hashes[0][1:]
+
+ alphabet := make([]rune, len(alphabetOriginal))
+ copy(alphabet, alphabetOriginal)
+ for i, r := range alphabet {
+ if r == lotteryRune {
+ alphabet = append([]rune{lotteryRune}, append(alphabet[:i], alphabet[i+1:]...)...)
+ break
+ }
+ }
+
+ saltL := append(bytes.Runes([]byte(strconv.FormatInt(int64(lotteryRune&12345), 10))), salt...)
+
+ result := make([]int, len(hashes))
+ for i, subHash := range hashes {
+ alphabet = consistentShuffle(alphabet, saltL)
+ result[i] = unhash(subHash, alphabet)
+ }
+
+ return result
+}
+
+func getSepsAndGuards(alphabet []rune) ([]rune, []rune, []rune) {
+ guards := make([]rune, 0, len(sepsIndices))
+ seps := make([]rune, 0, len(alphabet))
+ for _, prime := range primes {
+ index := prime - 1 - len(seps)
+ if index < len(alphabet) {
+ seps = append(seps, alphabet[index])
+ alphabet = append(alphabet[:index], alphabet[index+1:]...)
+ } else {
+ break
+ }
+ }
+ for _, index := range sepsIndices {
+ if index < len(seps) {
+ guards = append(guards, seps[index])
+ seps = append(seps[:index], seps[index+1:]...)
+ }
+ }
+ return alphabet, seps, guards
+}
+
+func splitRunes(input, seps []rune) [][]rune {
+ splitIndices := make([]int, 0)
+ for i, inputRune := range input {
+ for _, sepsRune := range seps {
+ if inputRune == sepsRune {
+ splitIndices = append(splitIndices, i)
+ }
+ }
+ }
+
+ result := make([][]rune, 0, len(splitIndices)+1)
+ inputLeft := input[:]
+ for _, splitIndex := range splitIndices {
+ splitIndex -= len(input) - len(inputLeft)
+ subInput := make([]rune, splitIndex)
+ copy(subInput, inputLeft[:splitIndex])
+ result = append(result, subInput)
+ inputLeft = inputLeft[splitIndex+1:]
+ }
+ result = append(result, inputLeft)
+
+ return result
+}
+
+func hash(input int, alphabet []rune) []rune {
+ result := make([]rune, 0)

for ; input > 0; input /= len(alphabet) {
r := alphabet[input%len(alphabet)]
result = append(result, r)
}
reversed := make([]rune, len(result))
for i, r := range result {
reversed[len(result)-1-i] = r
}
return reversed

@speps Owner
speps added a note

I knew it was not the most efficient way but it was like in the original implementation, I applied your suggestion, way more Go :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ for {
+ r := alphabet[input%len(alphabet)]
+ result = append([]rune{r}, result...)
+ input = input / len(alphabet)
+ if input == 0 {
+ break
+ }
+ }
+ return result
+}
+
+func unhash(input, alphabet []rune) int {
+ result := 0
+ for i, inputRune := range input {
+ alphabetPos := -1
+ for pos, alphabetRune := range alphabet {
+ if inputRune == alphabetRune {
+ alphabetPos = pos
+ break
+ }
+ }
+ if alphabetPos == -1 {
+ panic("should not happen, alphabet used for hash was different")
+ }
+
+ result += alphabetPos * int(math.Pow(float64(len(alphabet)), float64(len(input)-i-1)))
+ }
+ return result
+}
+
+func consistentShuffle(alphabet, salt []rune) []rune {
+ sortingArray := make([]int, len(salt))
+ for i, saltRune := range salt {
+ sortingArray[i] = int(saltRune)
+ }
+ for i, _ := range sortingArray {
+ add := true
+ for k, j := i, len(sortingArray)+i-1; k != j; k++ {
+ nextIndex := (k + 1) % len(sortingArray)
+ if add {
+ sortingArray[i] += sortingArray[nextIndex] + (k * i)
+ } else {
+ sortingArray[i] -= sortingArray[nextIndex]
+ }
+ add = !add
+ }
+ if sortingArray[i] < 0 {
+ sortingArray[i] = -sortingArray[i]
+ }
+ }
+
+ alphabetCopy := make([]rune, len(alphabet))
+ copy(alphabetCopy, alphabet)
+ result := make([]rune, 0, len(alphabet))
+ for i := 0; len(alphabetCopy) > 0; i++ {
+ pos := sortingArray[i%len(sortingArray)] % len(alphabetCopy)
+ result = append(result, alphabetCopy[pos])
+ alphabetCopy = append(alphabetCopy[:pos], alphabetCopy[pos+1:]...)
+ }
+ return result
+}
View
27 hashids_test.go
@@ -0,0 +1,27 @@
+package hashids
+
+import (
+ "testing"
+)
+
+func TestEncryptDecrypt(t *testing.T) {
+ hid := New()
+ hid.MinLength = 30
+ hid.Salt = "this is my salt"
+
+ numbers := []int{45, 434, 1313, 99}
+ hash := hid.Encrypt(numbers)
+ dec := hid.Decrypt(hash)
+
+ t.Logf("%v -> %v -> %v", numbers, hash, dec)
+
+ if len(numbers) != len(dec) {
+ t.Error("lengths do not match")
+ }
+
+ for i, n := range numbers {
+ if n != dec[i] {
+ t.Fail()
+ }
+ }
+}
@remyoudompheng

It is possible to directly convert strings to []rune: []rune(h.Alphabet)

@remyoudompheng

This looks more convoluted than it should be: it looks simpler to write:

lotterySalt := make([]byte, 0, 2*len(numbers)) // pre-allocate a bit
for i, n := range numbers {
if i > 0 { lotterySalt = append(lotterySalt, '-') }
lotterySalt = strconv.AppendInt(lotterySalt, int64(n), 10)
}
...
bytes.Runes(lotterySalt)

@remyoudompheng

for ; input > 0; input /= len(alphabet) {
r := alphabet[input%len(alphabet)]
result = append(result, r)
}
reversed := make([]rune, len(result))
for i, r := range result {
reversed[len(result)-1-i] = r
}
return reversed

@speps
Owner

Thanks, I did not realize strconv could do this at first :)

@speps
Owner

I knew it was not the most efficient way but it was like in the original implementation, I applied your suggestion, way more Go :)

@speps
Owner

Thanks I replaced wherever I could

Please sign in to comment.
Something went wrong with that request. Please try again.