Skip to content
This repository has been archived by the owner on Aug 19, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3 from MoonfishApp/main
Browse files Browse the repository at this point in the history
Add encryption conform EIP1024
  • Loading branch information
Foboz authored Aug 19, 2022
2 parents a0ff7de + 8a6fdc5 commit 21c7187
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 19 deletions.
125 changes: 106 additions & 19 deletions Sources/MEWwalletTweetNacl/TweetNacl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,40 +64,77 @@ public enum TweetNaclError: LocalizedError {
}
}

// Based on https://github.com/dchest/tweetnacl-js
public class TweetNacl {
public static func keyPair(fromSecretKey sk: Data) throws -> (publicKey: Data, secretKey: Data) {
guard sk.count == Constants.SecretKeyLength else { throw TweetNaclError.invalidSecretKey }

var pk = [UInt8](repeating: 0, count: Constants.SecretKeyLength)
var sk = [UInt8](sk)


// MARK: - Keys

/// Creates key pair on curve25519 as specified in EIP1024. Pass nil to create a new key pair or an Ethereum private key
/// to create a key pair linked to an Ethereum key pair.
/// Based on nacl.box.keyPair.fromSecretKey and nacl.box.keyPair
/// - Parameter fromSecretKey: Ethereum private key
/// - Returns: curve25519 key pair
public static func keyPair(fromSecretKey: Data? = nil) throws -> (publicKey: Data, secretKey: Data) {
var sk: [UInt8]
if let fromSecretKey = fromSecretKey {
guard fromSecretKey.count == Constants.SecretKeyLength else { throw TweetNaclError.invalidSecretKey }
sk = [UInt8](fromSecretKey)
} else {
sk = [UInt8](repeating: 0, count: Constants.SecretKeyLength)
let status = SecRandomCopyBytes(kSecRandomDefault, Constants.SecretKeyLength, &sk)
guard status == errSecSuccess else {
throw TweetNaclError.tweetNacl("Secure random bytes error")
}
}

var pk = [UInt8](repeating: 0, count: Constants.PublicKeyLength)
let result = crypto_scalarmult_curve25519_tweet_base(&pk, &sk)

guard result == 0 else { throw TweetNaclError.tweetNacl("[TweetNacl.keyPair] Internal error code: \(result)") }

return (Data(pk), Data(sk))
}

public static func open(message: Data, nonce: Data, publicKey: Data, secretKey: Data) throws -> Data {
let k = try before(publicKey: publicKey, secretKey: secretKey)
return try open(box: message, nonce: nonce, key: k)
}

public static func before(publicKey: Data, secretKey: Data) throws -> Data {

/// Pre-calculate shared secret key
/// Based on nacl.box.before
/// - Parameters:
/// - publicKey: public key
/// - secretKey: private key
/// - Returns: shared key
internal static func before(publicKey: Data, secretKey: Data) throws -> Data {
guard publicKey.count == Constants.PublicKeyLength else { throw TweetNaclError.invalidPublicKey }
guard secretKey.count == Constants.SecretKeyLength else { throw TweetNaclError.invalidSecretKey }

var publicKey = [UInt8](publicKey)
var secretKey = [UInt8](secretKey)

var k = [UInt8](repeating: 0, count: Constants.BeforeNMLength)

let result = crypto_box_curve25519xsalsa20poly1305_tweet_beforenm(&k, &publicKey, &secretKey)
guard result == 0 else { throw TweetNaclError.tweetNacl("[TweetNacl.before] Internal error code: \(result)") }

return Data(k)
}

// MARK: - Decryption

/// Decrypts encrypted message
/// Based on nacl.box.open
/// - Parameters:
/// - message: secret box
/// - nonce: unique nonce
/// - publicKey: public curve25519 key provided by sender
/// - secretKey: receiver's private curve25519 key to decrypt message
/// - Returns: clear text
public static func open(message: Data, nonce: Data, publicKey: Data, secretKey: Data) throws -> Data {
let k = try before(publicKey: publicKey, secretKey: secretKey)
return try open(box: message, nonce: nonce, key: k)
}

/// Decrypts encrypted message
/// Based on nacl.secretbox.open
/// - Parameters:
/// - box: secret box
/// - nonce: unique nonce
/// - key: private key
/// - Returns: clear text of message
public static func open(box: Data, nonce: Data, key: Data) throws -> Data {
guard key.count == Constants.SecretBox.keyLength else { throw TweetNaclError.invalidKey }
guard nonce.count == Constants.SecretBox.nonceLength else { throw TweetNaclError.invalidNonce }
Expand All @@ -115,4 +152,54 @@ public class TweetNacl {

return Data(m[Constants.SecretBox.zeroLength..<c.count])
}

// MARK: - Encryption

/// Encrypts message, creates secretbox
/// Based on nacl.box
/// - Parameters:
/// - message: Clear text message
/// - theirPublicKey: Recipient's public key
/// - mySecretKey: Sender's private key
/// - nonce: nonce (pass nil for random nonce)
/// - Returns: secret box
public static func box(message: Data, recipientPublicKey: Data, senderSecretKey: Data, nonce: Data? = nil) throws -> (box: Data, nonce: Data) {
let k = try before(publicKey: recipientPublicKey, secretKey: senderSecretKey)
let nonce = try nonce ?? randomNonce()
let box = try secretbox(message: message, nonce: nonce, key: k)
return (box: box, nonce: nonce)
}

/// Encrypts message, creates secretbox
/// Based on nacl.secretbox
/// - Parameters:
/// - message: Clear text message
/// - nonce: Unique nonce
/// - key: Shared secret key
/// - Returns: secret box
private static func secretbox(message: Data, nonce: Data, key: Data) throws -> Data {
guard key.count == Constants.SecretBox.keyLength else { throw TweetNaclError.invalidKey }
guard nonce.count == Constants.SecretBox.nonceLength else { throw TweetNaclError.invalidNonce }

var mData = Data(count: Constants.SecretBox.zeroLength + message.count)
mData.replaceSubrange(Constants.SecretBox.zeroLength ..< mData.count, with: message)
var m = [UInt8](mData)
var c = [UInt8](repeating: 0, count: m.count)
var nonce = [UInt8](nonce)
var key = [UInt8](key)

let result = crypto_secretbox_xsalsa20poly1305_tweet(&c, &m, UInt64(m.count), &nonce, &key)
guard result == 0 else { throw TweetNaclError.tweetNacl("[TweetNacl.secretbox] Internal error code: \(result)") }

return Data(c[Constants.SecretBox.boxZeroLength..<c.count])
}

private static func randomNonce() throws -> Data {
var nonce = [UInt8](repeating: 0, count: Constants.SecretBox.nonceLength)
let status = SecRandomCopyBytes(kSecRandomDefault, Constants.SecretBox.nonceLength, &nonce)
guard status == errSecSuccess else {
throw TweetNaclError.tweetNacl("Secure random bytes error")
}
return Data(nonce)
}
}
54 changes: 54 additions & 0 deletions Tests/MEWwalletTweetNaclTests/MEWwalletTweetNaclTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,58 @@ final class MEWwalletTweetNaclTests: XCTestCase {
XCTFail((error as? LocalizedError)?.failureReason ?? error.localizedDescription)
}
}

func testEncode() throws {
let receiverKeys = try TweetNacl.keyPair(fromSecretKey: privateKey)
let ephemKeys = try TweetNacl.keyPair()
let message = "My name is Satoshi Buterin".data(using: .utf8)!
let nonceData = Data(base64Encoded: self.nonce)!

XCTAssertEqual(String(data: receiverKeys.publicKey.base64EncodedData(), encoding: .utf8), "C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U=")
XCTAssertEqual(String(data: nonceData.base64EncodedData(), encoding: .utf8), "1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej")

// encrypt
let secretbox = try TweetNacl.box(message: message, recipientPublicKey: receiverKeys.publicKey, senderSecretKey: ephemKeys.secretKey, nonce: nonceData)
let secretboxString = String(data: secretbox.box.base64EncodedData(), encoding: .utf8)!
let expectedCiphertext = "f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy"
XCTAssertEqual(secretboxString.count, expectedCiphertext.count)

// decrypt
let decrypted = try TweetNacl.open(message: secretbox.box, nonce: nonceData, publicKey: ephemKeys.publicKey, secretKey: receiverKeys.secretKey)
guard let decryptedMessage = String(data: decrypted, encoding: .utf8) else {
XCTFail("Can't create a string")
return
}
XCTAssertEqual(decryptedMessage, String(data: message, encoding: .utf8))
}

func testEncodeRandom() throws {
let message = "My name is Satoshi Buterin".data(using: .utf8)!
let receiverKeys = try TweetNacl.keyPair(fromSecretKey: privateKey)
let senderKeys = try TweetNacl.keyPair()
let encoded = try TweetNacl.box(message: message, recipientPublicKey: receiverKeys.publicKey, senderSecretKey: senderKeys.secretKey)

// decrypt
let decrypted = try TweetNacl.open(message: encoded.box, nonce: encoded.nonce, publicKey: senderKeys.publicKey, secretKey: receiverKeys.secretKey)
guard let decryptedMessage = String(data: decrypted, encoding: .utf8) else {
XCTFail("Can't create a string")
return
}
XCTAssertEqual(decryptedMessage, String(data: message, encoding: .utf8))
}

func testDecodeBySender() throws {
let message = "My name is Satoshi Buterin".data(using: .utf8)!
let receiverKeys = try TweetNacl.keyPair(fromSecretKey: privateKey)
let senderKeys = try TweetNacl.keyPair()
let (box, nonce) = try TweetNacl.box(message: message, recipientPublicKey: receiverKeys.publicKey, senderSecretKey: senderKeys.secretKey)

// decrypt by sender
let decrypted = try TweetNacl.open(message: box, nonce: nonce, publicKey: receiverKeys.publicKey, secretKey: senderKeys.secretKey)
guard let decryptedMessage = String(data: decrypted, encoding: .utf8) else {
XCTFail("Can't create a string")
return
}
XCTAssertEqual(decryptedMessage, String(data: message, encoding: .utf8))
}
}

0 comments on commit 21c7187

Please sign in to comment.