@@ -9,6 +9,9 @@ public enum MnemonicError: Swift.Error {
9
9
case seedDerivationFailed
10
10
case seedPhraseInvalid( String )
11
11
case error( Swift . Error )
12
+ case invalidWordCount
13
+ case invalidWordToShift
14
+ case invalidMnemonic
12
15
}
13
16
14
17
/**
@@ -181,4 +184,123 @@ public struct Mnemonic: Equatable, Codable {
181
184
182
185
return Mnemonic . isValidChecksum ( phrase: words, wordlist: vocabulary)
183
186
}
187
+
188
+ /**
189
+ Modifed from: https://github.com/pengpengliu/BIP39/blob/master/Sources/BIP39/Mnemonic.swift
190
+ Convert the current Mnemonic back into entropy
191
+ */
192
+ public func toEntropy( ignoreChecksum: Bool , wordlist: WordList = WordList . english) throws -> [ UInt8 ] {
193
+ let wordListWords = wordlist. words
194
+
195
+ let bits = try words. map { ( word) -> String in
196
+ guard let index = wordListWords. firstIndex ( of: word) else {
197
+ throw MnemonicError . invalidMnemonic
198
+ }
199
+
200
+ var str = String ( index, radix: 2 )
201
+ while str. count < 11 {
202
+ str = " 0 " + str
203
+ }
204
+ return str
205
+ } . joined ( separator: " " )
206
+
207
+ let dividerIndex = Int ( Double ( bits. count / 33 ) . rounded ( . down) * 32 )
208
+ let entropyBits = String ( bits. prefix ( dividerIndex) )
209
+ let checksumBits = String ( bits. suffix ( bits. count - dividerIndex) )
210
+
211
+ let regex = try ! NSRegularExpression ( pattern: " [01]{1,8} " , options: . caseInsensitive)
212
+ let entropyBytes = regex. matches ( in: entropyBits, options: [ ] , range: NSRange ( location: 0 , length: entropyBits. count) ) . map {
213
+ UInt8 ( strtoul ( String ( entropyBits [ Range ( $0. range, in: entropyBits) !] ) , nil , 2 ) )
214
+ }
215
+
216
+ if !ignoreChecksum && ( checksumBits != Mnemonic . deriveChecksumBits ( entropyBytes) ) {
217
+ throw MnemonicError . invalidMnemonic
218
+ }
219
+
220
+ return entropyBytes
221
+ }
222
+
223
+ /**
224
+ Take a `PrivateKey` from a TorusWallet and generate a custom "shifted checksum" mnemonic, so that we can recover wallets that previously had no seed words
225
+ */
226
+ public static func shiftedMnemonic( fromSpskPrivateKey pk: PrivateKey ) -> Mnemonic ? {
227
+ guard let entropy = Base58Check . decode ( string: pk. base58CheckRepresentation, prefix: Prefix . Keys. Secp256k1. secret) else {
228
+ return nil
229
+ }
230
+
231
+ let data = Data ( entropy)
232
+ guard let mnemonic = try ? Mnemonic ( entropy: data) else {
233
+ return nil
234
+ }
235
+
236
+ return try ? shiftChecksum ( mnemonic: mnemonic)
237
+ }
238
+
239
+ /**
240
+ Shift the checksum of of a `Mnemonic` so that it won't be accepted by tradtional improts
241
+ */
242
+ public static func shiftChecksum( mnemonic: Mnemonic , wordList: WordList = WordList . english) throws -> Mnemonic {
243
+ var mutableMnemonic = mnemonic
244
+ guard mutableMnemonic. words. count == 24 ,
245
+ let lastWord = mutableMnemonic. words. last,
246
+ let shiftedWord = try ? Mnemonic . getShiftedWord ( word: lastWord, wordList: wordList) else {
247
+ throw MnemonicError . invalidWordCount
248
+ }
249
+
250
+ var isValidMnemonic = ( mutableMnemonic. isValid ( ) ? 1 : 0 )
251
+ mutableMnemonic. phrase = mutableMnemonic. phrase. replacingOccurrences ( of: lastWord, with: shiftedWord)
252
+ isValidMnemonic += ( mutableMnemonic. isValid ( ) ? 1 : 0 )
253
+
254
+ if isValidMnemonic != 1 {
255
+ throw MnemonicError . invalidMnemonic
256
+ } else {
257
+ return mutableMnemonic
258
+ }
259
+ }
260
+
261
+ /**
262
+ Return a shifted word to replace the last word in a mnemonic
263
+ */
264
+ public static func getShiftedWord( word: String , wordList: WordList = WordList . english) throws -> String {
265
+ let words = wordList. words
266
+ guard let wordIndex = words. firstIndex ( of: word) else {
267
+ throw MnemonicError . invalidWordToShift
268
+ }
269
+
270
+ let checksumByte = wordIndex % 256
271
+ let newIndex = wordIndex - checksumByte + ( ( checksumByte + 128 ) % 256 )
272
+
273
+ if words. count > newIndex {
274
+ return words [ newIndex]
275
+ } else {
276
+ throw MnemonicError . invalidWordToShift
277
+ }
278
+ }
279
+
280
+ /**
281
+ Convert a mnemonic to a Base58 encoded private key string. Helpful when determining if a shifted mnemonic is valid
282
+ */
283
+ public static func mnemonicToSpsk( mnemonic: Mnemonic , wordList: WordList = WordList . english) -> String ? {
284
+ guard let bytes = try ? mnemonic. toEntropy ( ignoreChecksum: true , wordlist: wordList) else {
285
+ return nil
286
+ }
287
+
288
+ return Base58Check . encode ( message: bytes, prefix: Prefix . Keys. Secp256k1. secret)
289
+ }
290
+
291
+ /**
292
+ Convert a shifted Mnemoinc back to normal
293
+ */
294
+ public static func shiftedMnemonicToMnemonic( mnemonic: Mnemonic ) -> Mnemonic ? {
295
+ return try ? shiftChecksum ( mnemonic: mnemonic)
296
+ }
297
+
298
+ /**
299
+ Check if a supplied Spsk string is valid
300
+ */
301
+ public static func validSpsk( _ sk: String ) -> Bool {
302
+ let canDecode = Base58Check . decode ( string: sk, prefix: Prefix . Keys. Secp256k1. secret)
303
+
304
+ return sk. count == 54 && canDecode != nil
305
+ }
184
306
}
0 commit comments