1
1
import Foundation
2
2
import CryptoSwift
3
3
import Scrypt
4
+ import bls_framework
4
5
5
6
public struct KeystoreETH2Factory {
6
7
@@ -11,7 +12,7 @@ public struct KeystoreETH2Factory {
11
12
///
12
13
/// - returns: The extracted private key.
13
14
///
14
- /// - throws: Some `KeystoreFactory .Error` if any step fails.
15
+ /// - throws: Some `KeystoreETH2Factory .Error` if any step fails.
15
16
public static func privateKey( from keystore: KeystoreETH2 , password: String ) throws -> Array < UInt8 > {
16
17
var forbiddenCharacterSet = CharacterSet ( )
17
18
forbiddenCharacterSet. insert ( charactersIn: Unicode . Scalar ( 0x00 ) ... Unicode . Scalar ( 0x1f ) )
@@ -63,6 +64,102 @@ public struct KeystoreETH2Factory {
63
64
return try aes. decrypt ( [ UInt8] ( ciphertextData) )
64
65
}
65
66
67
+ /// Creates a keystore for the given privateKey with the given password.
68
+ ///
69
+ /// - parameter privateKey: The private key to encrypt.
70
+ /// - parameter password: The password to use for the encryption.
71
+ /// - parameter kdf: The key derivation function to use.
72
+ /// - parameter cipher: The cipher to use for encryption.
73
+ /// - parameter checksum: The password checksum to use to detect bad passwords during decryption.
74
+ /// - parameter rounds: The number of rounds for the key derivation function to use. Defaults to a secure number.
75
+ ///
76
+ /// - returns: The KeystoreETH2 object with the encrypted private key.
77
+ ///
78
+ /// - throws: Some `KeystoreETH2Factory.Error` if any step fails.
79
+ public static func keystore(
80
+ from privateKey: [ UInt8 ] ,
81
+ password: String ,
82
+ kdf: KeystoreETH2 . KDFModule . KDFType ,
83
+ cipher: KeystoreETH2 . CipherModule . CipherType ,
84
+ checksum: KeystoreETH2 . ChecksumModule . ChecksumType ,
85
+ rounds: Int = 262144
86
+ ) throws -> KeystoreETH2 {
87
+ guard privateKey. count == 32 else {
88
+ throw Error . privateKeyMalformed
89
+ }
90
+
91
+ guard let iv = [ UInt8 ] . secureRandom ( count: 16 ) , let salt = [ UInt8 ] . secureRandom ( count: 32 ) else {
92
+ throw Error . bytesGenerationFailed
93
+ }
94
+
95
+ let password = password. decomposedStringWithCompatibilityMapping
96
+ . components ( separatedBy: . controlCharacters)
97
+ . filter ( { !$0. isEmpty } )
98
+ . joined ( separator: " " )
99
+
100
+ // Derive key
101
+ let kdfModule : KeystoreETH2 . KDFModule
102
+ switch kdf {
103
+ case . scrypt:
104
+ kdfModule = . init( function: kdf, params: . init( salt: salt. toHexString ( ) , dklen: 32 , n: rounds, r: 8 , p: 1 ) , message: " " )
105
+ case . pbkdf2:
106
+ kdfModule = . init( function: kdf, params: . init( salt: salt. toHexString ( ) , dklen: 32 , prf: " hmac-sha256 " , c: rounds) , message: " " )
107
+ }
108
+ let encryptionKey = try deriveKey ( password: password, kdf: kdfModule)
109
+ guard encryptionKey. count >= 32 else {
110
+ throw Error . kdfFailed
111
+ }
112
+
113
+ // Encrypt
114
+ let usableKey = encryptionKey [ 0 ..< 16 ]
115
+
116
+ guard cipher == . aes128Ctr else {
117
+ throw Error . cipherNotAvailable
118
+ }
119
+
120
+ let aes = try AES (
121
+ key: [ UInt8] ( usableKey) ,
122
+ blockMode: CTR ( iv: iv) ,
123
+ padding: . noPadding
124
+ )
125
+ let cipherMessage = try aes. encrypt ( privateKey)
126
+ let cipherModule = KeystoreETH2 . CipherModule ( function: cipher, params: . init( iv: iv. toHexString ( ) ) , message: cipherMessage. toHexString ( ) )
127
+
128
+ // Checksum
129
+ guard checksum == . sha256 else {
130
+ throw Error . checksumNotAvailable
131
+ }
132
+
133
+ let checksumGenerated = try generatePasswordChecksum ( decryptionKey: encryptionKey, cipher: cipherModule)
134
+ let checksumModule = KeystoreETH2 . ChecksumModule ( function: checksum, params: . init( ) , message: checksumGenerated. toHexString ( ) )
135
+
136
+ // BLS pubkey
137
+
138
+ try BLSInterface . blsInit ( )
139
+
140
+ var serializedPrivateKey = privateKey
141
+ var secretKey = blsSecretKey. init ( )
142
+ if blsSecretKeyDeserialize ( & secretKey, & serializedPrivateKey, numericCast ( serializedPrivateKey. count) ) <= 0 {
143
+ throw Error . privateKeyMalformed
144
+ }
145
+ var publicKey = blsPublicKey. init ( )
146
+ blsGetPublicKey ( & publicKey, & secretKey)
147
+ // Ethereum public key is 48 bytes
148
+ var publicKeyBytes = Data ( count: 48 ) . bytes
149
+ blsPublicKeySerialize ( & publicKeyBytes, 48 , & publicKey)
150
+
151
+ let ethereumPublicKey = Data ( publicKeyBytes)
152
+
153
+ return KeystoreETH2 (
154
+ crypto: . init( kdf: kdfModule, checksum: checksumModule, cipher: cipherModule) ,
155
+ description: " " ,
156
+ pubkey: ethereumPublicKey. toHexString ( ) ,
157
+ path: " " ,
158
+ uuid: UUID ( ) . uuidString,
159
+ version: 4
160
+ )
161
+ }
162
+
66
163
private static func deriveKey(
67
164
password: String ,
68
165
kdf: KeystoreETH2 . KDFModule
@@ -104,17 +201,25 @@ public struct KeystoreETH2Factory {
104
201
) . calculate ( ) )
105
202
}
106
203
107
- private static func passwordVerification (
204
+ private static func generatePasswordChecksum (
108
205
decryptionKey: Data ,
109
- cipher: KeystoreETH2 . CipherModule ,
110
- checksum: KeystoreETH2 . ChecksumModule
111
- ) throws -> Bool {
206
+ cipher: KeystoreETH2 . CipherModule
207
+ ) throws -> Data {
112
208
let dkSlice = decryptionKey [ 16 ..< 32 ]
113
209
var preImage = dkSlice
114
210
try preImage. append ( contentsOf: cipher. message. dataWithHexString ( ) )
115
211
116
212
let calculatedChecksum = preImage. sha256 ( )
117
213
214
+ return calculatedChecksum
215
+ }
216
+
217
+ private static func passwordVerification(
218
+ decryptionKey: Data ,
219
+ cipher: KeystoreETH2 . CipherModule ,
220
+ checksum: KeystoreETH2 . ChecksumModule
221
+ ) throws -> Bool {
222
+ let calculatedChecksum = try generatePasswordChecksum ( decryptionKey: decryptionKey, cipher: cipher)
118
223
return try calculatedChecksum == checksum. message. dataWithHexString ( )
119
224
}
120
225
@@ -141,6 +246,9 @@ public struct KeystoreETH2Factory {
141
246
/// The given cipher is not available
142
247
case cipherNotAvailable
143
248
249
+ /// The given checksum is not available
250
+ case checksumNotAvailable
251
+
144
252
/// Generating random bytes failed
145
253
case bytesGenerationFailed
146
254
0 commit comments