/
keys.go
229 lines (214 loc) · 7.47 KB
/
keys.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/*
* This file is part of OpenMonero's Go library monero.go
*
* Copyright (c) 2023 OpenMonero
* All Rights Reserved.
* The code is distributed under MIT license, see LICENSE file for details.
* Generated by OpenMonero on 03-07-2023.
*
*/
package monero
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"github.com/paxosglobal/moneroutil"
"golang.org/x/crypto/sha3"
"hash/crc32"
"math/big"
"strings"
"unsafe"
)
// Generates and returns a random word from the mnemonic word list, which is a list of 1626 words
func randomWord(wordList []string) string {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(wordList))))
return wordList[num.Int64()]
}
// Generates and returns the index of the checksum word (25th word) in the mnemonic
// The checksum is calculated by taking the first prefixLength letters of each word in the mnemonic
// and calculating the CRC32 checksum of the resulting string. The index of the checksum
// word is the checksum modulo the number of words in the mnemonic
func getChecksumIndex(mnemonics []string, prefixLength int) int {
trimmedWords := ""
for _, word := range mnemonics {
trimmedWords += word[:prefixLength]
}
checksum := crc32.ChecksumIEEE([]byte(trimmedWords))
index := int(checksum) % len(mnemonics)
return index
}
// GenerateMnemonicSeed : Generates and returns a 25 word mnemonic
// Returns error if there has been an error
func GenerateMnemonicSeed(language string) (string, error) {
wordList := wordSets[language].words
prefixLen := wordSets[language].prefixLen
if wordList == nil {
return "", errors.New("invalid language")
}
// Continue if language is supported
var mnemonic []string
for i := 0; i < 24; i++ {
mnemonic = append(mnemonic, randomWord(wordList))
}
checksumIndex := getChecksumIndex(mnemonic, prefixLen)
mnemonic = append(mnemonic, mnemonic[checksumIndex])
return strings.Join(mnemonic, " "), nil
}
// Implements swapEndian4Byte that is used in DeriveHexSeedFromMnemonicSeed
func swapEndian4Byte(str string) (string, error) {
if len(str) != 8 {
return "", errors.New("invalid input length: " + string(rune(len(str))))
}
return str[6:8] + str[4:6] + str[2:4] + str[0:2], nil
}
// Returns index of given word in the given array
// Returns -1 if not found in the array
func findIndex(array []string, word string) int {
for i, val := range array {
if val == word {
return i
}
}
return -1
}
// DeriveHexSeedFromMnemonicSeed : Derives and returns hex seed from given mnemonic
// Returns error if there has been an error
func DeriveHexSeedFromMnemonicSeed(mnemonic string, language string) (string, error) {
wordset := wordSets[language]
if wordset.words == nil {
return "", errors.New("invalid language")
}
out := ""
n := len(wordset.words)
wordsList := strings.Split(mnemonic, " ")
checksumWord := ""
if (wordset.prefixLen == 0 && len(wordsList)%3 != 0) ||
(wordset.prefixLen > 0 && len(wordsList)%3 == 2) {
return "", errors.New("you've entered too few words, please try again")
}
if wordset.prefixLen > 0 && len(wordsList)%3 == 0 {
return "", errors.New("you seem to be missing the last word in your private key, please try again")
}
if wordset.prefixLen > 0 {
checksumWord = wordsList[len(wordsList)-1]
wordsList = wordsList[:len(wordsList)-1]
}
for i := 0; i < len(wordsList); i += 3 {
var w1, w2, w3 int
if wordset.prefixLen == 0 {
w1 = findIndex(wordset.words, wordsList[i])
w2 = findIndex(wordset.words, wordsList[i+1])
w3 = findIndex(wordset.words, wordsList[i+2])
} else {
w1 = findIndex(wordset.truncWords, wordsList[i][:wordset.prefixLen])
w2 = findIndex(wordset.truncWords, wordsList[i+1][:wordset.prefixLen])
w3 = findIndex(wordset.truncWords, wordsList[i+2][:wordset.prefixLen])
}
if w1 == -1 || w2 == -1 || w3 == -1 {
return "", errors.New("invalid word in mnemonic")
}
x := w1 + n*((n-w1+w2)%n) + n*n*((n-w2+w3)%n)
if x%n != w1 {
return "", errors.New("something went wrong when decoding your private key, please try again")
}
swapped, err := swapEndian4Byte(fmt.Sprintf("%08x", x))
if err != nil {
return "", err
}
out += swapped
}
if wordset.prefixLen > 0 {
index := getChecksumIndex(wordsList, wordset.prefixLen)
expectedChecksumWord := wordsList[index]
if expectedChecksumWord[:wordset.prefixLen] != checksumWord[:wordset.prefixLen] {
return "", errors.New("your private key could not be verified, please try again")
}
}
return out, nil
}
// Returns keccak256 value of given bytes
func keccak256B(bytes []byte) [32]byte {
hash := sha3.NewLegacyKeccak256()
hash.Write(bytes)
var out [32]byte
copy(out[:], hash.Sum(nil))
return out
}
// Returns keccak256 value of given bytes
func keccak256D(data ...[]byte) *[32]byte {
h := sha3.NewLegacyKeccak256()
for _, v := range data {
h.Write(v)
}
sum := h.Sum(nil)
sum32 := (*[32]byte)(unsafe.Pointer(&sum[0]))
return sum32
}
// DerivePrivateKeysFromHexSeed : Calculates and returns private spend key and private view key from given hexadecimal seed
// Returns error if there has been an error
func DerivePrivateKeysFromHexSeed(hexSeed string) (string /* Private Spend Key */, string /* Private View Key */, error) {
hexBytes, err := hex.DecodeString(hexSeed)
if err != nil {
return "", "", err
}
bytesSeed := new([32]byte)
copy(bytesSeed[:], hexBytes[:32])
moneroutil.ScReduce32((*moneroutil.Key)(bytesSeed))
spendKey := bytesSeed
privateViewKeyBytes32 := keccak256B(spendKey[:])
privateViewKey := privateViewKeyBytes32
moneroutil.ScReduce32((*moneroutil.Key)(privateViewKey[:]))
return fmt.Sprintf("%x", spendKey)[1:], fmt.Sprintf("%x", privateViewKey), nil
}
// DerivePrivVKFromPrivSK : Calculates and returns private view key from given private spend key
// Returns error if there has been an error
func DerivePrivVKFromPrivSK(privateSpendKey string) (string, error) {
hexBytes, err := hex.DecodeString(privateSpendKey)
if err != nil {
return "", err
}
bytesSeed := new([32]byte)
copy(bytesSeed[:], hexBytes[:32])
privateViewKeyBytes32 := keccak256B(bytesSeed[:])
privateViewKey := privateViewKeyBytes32
moneroutil.ScReduce32((*moneroutil.Key)(privateViewKey[:]))
return fmt.Sprintf("%x", privateViewKey), nil
}
// DerivePublicKeyFromPrivateKey : Calculates and returns public key from given private key
// Returns error if there has been an error
func DerivePublicKeyFromPrivateKey(privateKey string) (string, error) {
pub := new([32]byte)
priv, err := hex.DecodeString(privateKey)
if err != nil {
return "", err
}
p := new(moneroutil.ExtendedGroupElement)
moneroutil.GeScalarMultBase(p, (*moneroutil.Key)(priv))
p.ToBytes((*moneroutil.Key)(pub))
return fmt.Sprintf("%x", pub)[1:], nil
}
// DeriveAddressFromPubKeys : Calculates and returns address from given public spend key and public view key and network
// Network can only be "moneromainnet" or "monerotestnet"
// Returns error if there has been an error
func DeriveAddressFromPubKeys(pubSpendKey string, pubViewKey string, network string) (string, error) {
var givenNetwork []byte
if network == "moneromainnet" {
givenNetwork = []byte{0x12}
} else if network == "monerotestnet" {
givenNetwork = []byte{0x35}
} else {
return "", errors.New("invalid network")
}
pubSpendKeyBytes, err := hex.DecodeString(pubSpendKey)
if err != nil {
return "", err
}
pubViewKeyBytes, err := hex.DecodeString(pubViewKey)
if err != nil {
return "", err
}
hash := keccak256D(givenNetwork, pubSpendKeyBytes[:], pubViewKeyBytes[:])
address := moneroutil.EncodeMoneroBase58(givenNetwork, pubSpendKeyBytes[:], pubViewKeyBytes[:], hash[:4])
return address, nil
}