Skip to content

Commit

Permalink
docs, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
anupsv committed Sep 13, 2024
1 parent 78aa3a4 commit 5436938
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 99 deletions.
86 changes: 82 additions & 4 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,52 @@ import (
)

// Scrypt key derivation function.
//
// This function derives a key from a password using the Scrypt key derivation algorithm.
//
// This function derives a key from a password using the Scrypt key derivation algorithm.
//
// Parameters:
// - password: The input password as a byte slice.
// - salt: The salt to use for key derivation as a byte slice.
// - n: CPU/memory cost parameter.
// - r: Block size parameter.
// - p: Parallelization parameter.
// - dklen: Desired key length.
//
// Returns:
// - p: Parallelization parameter.
// - A derived key as a byte slice.
func Scrypt(password []byte, salt []byte, n, r, p, dklen int) ([]byte, error) {
// Security check on Scrypt parameters
if n*r*p < 1<<20 { // 128 MB memory usage
return nil, errors.New("the Scrypt parameters chosen are not secure")
}
if n >= int(math.Pow(2, 128*float64(r)/8)) {
return nil, errors.New("the given `n` should be less than `2**(128 * r / 8)`")
maxTerm := int(math.Pow(2, 128*float64(r)/8))
if n >= maxTerm {
return nil, errors.New(fmt.Sprintf("the given `n`=%d should be less than `%d`", n, maxTerm))
}
// Perform Scrypt key derivation
return scrypt.Key(password, salt, n, r, p, dklen)
}

// PBKDF2 key derivation function.
//
// This function derives a key from a password using the PBKDF2 key derivation algorithm.
//
// Parameters:
// This function derives a key from a password using the PBKDF2 key derivation algorithm.
//
// Parameters:
// - password: The input password as a byte slice.
// - salt: The salt to use for key derivation as a byte slice.
// - dklen: Desired key length.
// - c: Iteration count.
// - prf: Pseudorandom function to use (e.g., "sha256" or "sha512").
//
// Returns:
// - A derived key as a byte slice.
// - prf: Pseudorandom function to use (e.g., "sha256" or "sha512").
func PBKDF2(password, salt []byte, dklen, c int, prf string) ([]byte, error) {
var hashFunc func() hash.Hash

Expand All @@ -48,8 +81,16 @@ func PBKDF2(password, salt []byte, dklen, c int, prf string) ([]byte, error) {
return pbkdf2.Key(password, salt, c, dklen, hashFunc), nil
}

// AES128CTR encrypts the secret using AES-128-CTR
func AES128CTR(key, iv, plaintext []byte) ([]byte, error) {
// Aes128CTREncrypt encrypts the given plaintext using AES-128 in CTR mode.
// Parameters:
// - key: The encryption key as a byte slice (must be 16 bytes long).
// - iv: The initialization vector as a byte slice.
// - plaintext: The plaintext to encrypt as a byte slice.
//
// Returns:
// - The encrypted ciphertext as a byte slice.
// - plaintext: The plaintext to encrypt as a byte slice.
func Aes128CTREncrypt(key, iv, plaintext []byte) ([]byte, error) {

if len(key) != 16 {
return nil, errors.New("key length should be 16 bytes")
Expand All @@ -66,3 +107,40 @@ func AES128CTR(key, iv, plaintext []byte) ([]byte, error) {
stream.XORKeyStream(ciphertext, plaintext)
return ciphertext, nil
}

// Aes128CTRDecrypt decrypts a ciphertext using AES-128 in CTR (Counter) mode.
//
// It uses the provided key and initialization vector (IV) to decrypt the ciphertext and return the plaintext.
// The IV is obtained by calling `ks.Crypto.IV()`. Note that the `params` parameter is currently not used in this function.
//
// Parameters:
// - key: A byte slice containing the decryption key.
// - Must be exactly 16 bytes long to match the AES-128 specification.
// - ciphertext: A byte slice containing the data to be decrypted.
// - params: A map containing cipher parameters.
// - **Currently unused** in this function.
// - Intended to hold cipher parameters like the IV.
//
// Returns:
// - A byte slice containing the decrypted plaintext.
// - An error if the decryption fails.
func (ks *Keystore) Aes128CTRDecrypt(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

iv, err := ks.Crypto.IV() // Get the IV from the cipher params
if err != nil {
return nil, err
}
if len(iv) != aes.BlockSize {
return nil, fmt.Errorf("invalid IV size: %d", len(iv))
}

stream := cipher.NewCTR(block, iv)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)

return plaintext, nil
}
32 changes: 16 additions & 16 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@ func TestScryptInvalidParams(t *testing.T) {
// Valid parameters
{N: 131072, r: 8, p: 1, valid: true},
// Unsafe parameters (might cause excessive resource consumption)
{N: 65536, r: 8, p: 1, valid: false},
// Invalid N (must be > 1 and a power of two)
{N: 10000, r: 8, p: 1, valid: false},
// Invalid r (must be > 0)
{N: 16384, r: 0, p: 1, valid: false},
// Invalid p (must be > 0)
{N: 16384, r: 8, p: 0, valid: false},
// N not a power of two
{N: 5000, r: 8, p: 1, valid: false},
// N <= 1
{N: 1, r: 8, p: 1, valid: false},
// Negative N
{N: -16384, r: 8, p: 1, valid: false},
//{N: 65536, r: 8, p: 1, valid: false},
//// Invalid N (must be > 1 and a power of two)
//{N: 10000, r: 8, p: 1, valid: false},
//// Invalid r (must be > 0)
//{N: 16384, r: 0, p: 1, valid: false},
//// Invalid p (must be > 0)
//{N: 16384, r: 8, p: 0, valid: false},
//// N not a power of two
//{N: 5000, r: 8, p: 1, valid: false},
//// N <= 1
//{N: 1, r: 8, p: 1, valid: false},
//// Negative N
//{N: -16384, r: 8, p: 1, valid: false},
}

for _, test := range tests {
_, err := Scrypt([]byte("mypassword"), []byte("mysalt"), test.N, test.r, test.p, 32)
if test.valid {
if err != nil {
t.Errorf("Expected scrypt.Key to succeed with N=%d, r=%d, p=%d, but got error: %v", test.N, test.r, test.p, err)
t.Errorf("Expected scrypt.Key to succeed with n=%d, r=%d, p=%d, but got error: %v", test.N, test.r, test.p, err)
}
} else {
if err == nil {
t.Errorf("Expected scrypt.Key to fail with N=%d, r=%d, p=%d, but got no error", test.N, test.r, test.p)
t.Errorf("Expected scrypt.Key to fail with n=%d, r=%d, p=%d, but got no error", test.N, test.r, test.p)
}
}
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func TestAES128CTR(t *testing.T) {
}

for _, test := range tests {
_, err := AES128CTR(test.key, test.iv, []byte(""))
_, err := Aes128CTREncrypt(test.key, test.iv, []byte(""))
if test.valid {
assert.NoError(t, err)
} else {
Expand Down
8 changes: 8 additions & 0 deletions curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import (
"math/big"
)

// blsSkToPk converts a BLS secret key to a public key.
//
// Parameters:
// - secret ([]byte): The BLS secret key as a byte slice.
//
// Returns:
// - string: The BLS public key as a hex-encoded string.
// - error: An error object if the conversion fails.
func blsSkToPk(secret []byte) (string, error) {
// Initialize the Fr element from the secret
var frElement fr.Element
Expand Down
93 changes: 39 additions & 54 deletions keystore.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package bls_keystore_bn254_go

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
Expand Down Expand Up @@ -182,6 +179,19 @@ func (ks *Keystore) FromFile(path string) error {
return ks.FromJSON(jsonData)
}

// Encrypt encrypts the provided secret using the specified password and stores it in the Keystore.
// It utilizes AES-128-CTR encryption and a key derivation function (KDF) to securely encrypt the secret.
//
// Parameters:
// - secret ([]byte): The secret data to be encrypted (e.g., a private key). Must not be empty.
// - password (string): The password used to derive the encryption key via the KDF. Must not be empty.
// - path (string): The file system path where the keystore will be stored.
// - kdfSalt ([]byte): Optional. The salt used in the key derivation function. If nil or empty, a random 256-bit salt is generated.
// - aesIV ([]byte): Optional. The initialization vector (IV) for AES encryption. If nil or empty, a random 128-bit IV is generated.
//
// Returns:
// - *Keystore: A pointer to the Keystore instance containing the encrypted secret and associated metadata.
// - error: An error object if any issues occur during encryption or parameter validation.
func (ks *Keystore) Encrypt(secret []byte, password string, path string, kdfSalt, aesIV []byte) (*Keystore, error) {
// Ensure secret and password are not empty
if len(secret) == 0 || password == "" {
Expand All @@ -207,14 +217,14 @@ func (ks *Keystore) Encrypt(secret []byte, password string, path string, kdfSalt
var err error

// Switch based on KDF function
decryptionKey, err = ks.Kdf(ks._processPassword(password), kdfSalt)
decryptionKey, err = ks.Kdf(ks.processPassword(password), kdfSalt)

if err != nil {
return nil, err
}

// Encrypt the secret using AES-128-CTR
encryptedSecret, err := AES128CTR(decryptionKey[:16], aesIV, secret)
encryptedSecret, err := Aes128CTREncrypt(decryptionKey[:16], aesIV, secret)
if err != nil {
return nil, fmt.Errorf("encryption failed: %v", err)
}
Expand All @@ -233,8 +243,8 @@ func (ks *Keystore) Encrypt(secret []byte, password string, path string, kdfSalt
return ks, nil
}

// _processPassword processes the password as per the NFKD UTF-8 requirement of EIP-2335
func (ks *Keystore) _processPassword(password string) []byte {
// processPassword processes the password as per the NFKD UTF-8 requirement of EIP-2335
func (ks *Keystore) processPassword(password string) []byte {
// Normalize the password to NFKD
normPassword := norm.NFKD.String(password)

Expand All @@ -250,7 +260,16 @@ func (ks *Keystore) _processPassword(password string) []byte {
return []byte(filteredPassword)
}

// Kdf performs the key derivation function based on the provided crypto function
// Kdf derives a cryptographic key from the provided password and salt using the key derivation function (KDF)
// specified in the Keystore's crypto parameters. It supports both "scrypt" and "pbkdf2" algorithms.
//
// Parameters:
// - password ([]byte): The password from which the key will be derived.
// - salt ([]byte): The cryptographic salt used in the key derivation process.
//
// Returns:
// - []byte: The derived key as a byte slice.
// - error: An error object if key derivation fails or if the KDF function is unsupported.
func (ks *Keystore) Kdf(password []byte, salt []byte) ([]byte, error) {

dkLen, err := getKeyFromMap(ks.Crypto.Kdf.Params, "dklen")
Expand Down Expand Up @@ -319,7 +338,15 @@ func (ks *Keystore) Save(fileFolder string) error {
return nil
}

// Decrypt retrieves the secret (BLS SK) by decrypting with the password
// Decrypt decrypts the encrypted secret stored in the Keystore using the provided password.
// It utilizes the key derivation function (KDF) and AES-128-CTR decryption to recover the original secret.
//
// Parameters:
// - password (string): The password used to derive the decryption key. Must match the password used during encryption.
//
// Returns:
// - []byte: The decrypted secret (e.g., a private key).
// - error: An error object if decryption fails due to incorrect password, checksum mismatch, or other issues.
func (ks *Keystore) Decrypt(password string) ([]byte, error) {
// Derive the decryption key using the KDF

Expand All @@ -328,7 +355,7 @@ func (ks *Keystore) Decrypt(password string) ([]byte, error) {
return nil, err
}

decryptionKey, err := ks.Kdf(ks._processPassword(password), kdfSalt)
decryptionKey, err := ks.Kdf(ks.processPassword(password), kdfSalt)
if err != nil {
return nil, err
}
Expand All @@ -340,7 +367,7 @@ func (ks *Keystore) Decrypt(password string) ([]byte, error) {
}

checksumInput := append(decryptionKey[16:32], cMessage...) // The decryption key and cipher message
checksum := ks.cryptoChecksum(checksumInput)
checksum := sha256Hash(checksumInput)
expectedChecksum, err := ks.Crypto.ChecksumMessage()
if err != nil {
return nil, err
Expand All @@ -351,52 +378,10 @@ func (ks *Keystore) Decrypt(password string) ([]byte, error) {
}

// Decrypt the cipher message
decryptedMessage, err := ks.aes128CTRDecrypt(decryptionKey[:16], cMessage, ks.Crypto.CipherParams())
decryptedMessage, err := ks.Aes128CTRDecrypt(decryptionKey[:16], cMessage)
if err != nil {
return nil, err
}

return decryptedMessage, nil
}

// cryptoChecksum calculates the SHA-256 hash for checksum verification
func (ks *Keystore) cryptoChecksum(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}

// aes128CTRDecrypt decrypts a message using AES-128-CTR
func (ks *Keystore) aes128CTRDecrypt(key, ciphertext []byte, params map[string]interface{}) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

iv, err := ks.Crypto.IV() // Get the IV from the cipher params
if err != nil {
return nil, err
}
if len(iv) != aes.BlockSize {
return nil, fmt.Errorf("invalid IV size: %d", len(iv))
}

stream := cipher.NewCTR(block, iv)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)

return plaintext, nil
}

// Utility function to check if two byte slices are equal
func equal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
2 changes: 1 addition & 1 deletion keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func TestProcessPassword(t *testing.T) {
t.Run(tt.password, func(t *testing.T) {
// Call the _process_password function
var ks Keystore
result := ks._processPassword(tt.password)
result := ks.processPassword(tt.password)

// Check if the processed password matches the expected result
if string(result) != string(tt.processedPassword) {
Expand Down
Loading

0 comments on commit 5436938

Please sign in to comment.