Merge branch 'locked-memory-manager' into zcashconsensus-fixes-base
This commit is contained in:
commit
3aec16a84b
|
@ -239,7 +239,7 @@ BITCOIN_CORE_H = \
|
|||
support/allocators/zeroafterfree.h \
|
||||
support/cleanse.h \
|
||||
support/events.h \
|
||||
support/pagelocker.h \
|
||||
support/lockedpool.h \
|
||||
sync.h \
|
||||
threadsafety.h \
|
||||
timedata.h \
|
||||
|
@ -377,6 +377,8 @@ libbitcoin_wallet_a_SOURCES = \
|
|||
crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_CONFIG_INCLUDES)
|
||||
crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
crypto_libbitcoin_crypto_a_SOURCES = \
|
||||
crypto/aes.cpp \
|
||||
crypto/aes.h \
|
||||
crypto/common.h \
|
||||
crypto/equihash.cpp \
|
||||
crypto/equihash.h \
|
||||
|
@ -450,7 +452,7 @@ libbitcoin_common_a_SOURCES = \
|
|||
libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
libbitcoin_util_a_SOURCES = \
|
||||
support/pagelocker.cpp \
|
||||
support/lockedpool.cpp \
|
||||
chainparamsbase.cpp \
|
||||
clientversion.cpp \
|
||||
compat/glibc_sanity.cpp \
|
||||
|
@ -616,11 +618,17 @@ libzcashconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
|||
endif
|
||||
#
|
||||
|
||||
CTAES_DIST = crypto/ctaes/bench.c
|
||||
CTAES_DIST += crypto/ctaes/ctaes.c
|
||||
CTAES_DIST += crypto/ctaes/ctaes.h
|
||||
CTAES_DIST += crypto/ctaes/README.md
|
||||
CTAES_DIST += crypto/ctaes/test.c
|
||||
|
||||
CLEANFILES = leveldb/libleveldb.a leveldb/libmemenv.a *.gcda *.gcno */*.gcno wallet/*/*.gcno
|
||||
|
||||
DISTCLEANFILES = obj/build.h
|
||||
|
||||
EXTRA_DIST = leveldb rust
|
||||
EXTRA_DIST = leveldb $(CTAES_DIST) rust
|
||||
|
||||
clean-local:
|
||||
rm -f $(top_srcdir)/.cargo/config $(top_srcdir)/.cargo/.configured-for-*
|
||||
|
|
|
@ -13,6 +13,7 @@ bench_bench_bitcoin_SOURCES = \
|
|||
bench/verification.cpp \
|
||||
bench/crypto_hash.cpp \
|
||||
bench/base58.cpp \
|
||||
bench/lockedpool.cpp \
|
||||
bench/perf.cpp \
|
||||
bench/perf.h \
|
||||
bench/prevector_destructor.cpp
|
||||
|
|
|
@ -61,20 +61,35 @@ endif
|
|||
zcash_gtest_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
zcash_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
|
||||
zcash_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
||||
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1)
|
||||
if ENABLE_ZMQ
|
||||
zcash_gtest_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
|
||||
endif
|
||||
if ENABLE_WALLET
|
||||
zcash_gtest_LDADD += $(LIBBITCOIN_WALLET)
|
||||
endif
|
||||
zcash_gtest_LDADD = \
|
||||
-lgtest -lgmock \
|
||||
$(LIBBITCOIN_SERVER) \
|
||||
$(LIBBITCOIN_CLI) \
|
||||
$(LIBBITCOIN_WALLET) \
|
||||
$(LIBBITCOIN_COMMON) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
$(LIBBITCOIN_ZMQ) \
|
||||
$(LIBBITCOIN_PROTON) \
|
||||
$(LIBBITCOIN_CRYPTO) \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBLEVELDB) \
|
||||
$(LIBMEMENV) \
|
||||
$(LIBSECP256K1)
|
||||
|
||||
zcash_gtest_LDADD += $(LIBZCASH_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(LIBZCASH) $(LIBRUSTZCASH) $(LIBZCASH_LIBS)
|
||||
|
||||
if ENABLE_PROTON
|
||||
zcash_gtest_LDADD += $(LIBBITCOIN_PROTON) $(PROTON_LIBS)
|
||||
endif
|
||||
zcash_gtest_LDADD += \
|
||||
$(LIBZCASH_CONSENSUS) \
|
||||
$(BOOST_LIBS) \
|
||||
$(BOOST_UNIT_TEST_FRAMEWORK_LIB) \
|
||||
$(BDB_LIBS) \
|
||||
$(SSL_LIBS) \
|
||||
$(CRYPTO_LIBS) \
|
||||
$(EVENT_PTHREADS_LIBS) \
|
||||
$(EVENT_LIBS) \
|
||||
$(ZMQ_LIBS) \
|
||||
$(PROTON_LIBS) \
|
||||
$(LIBZCASH) \
|
||||
$(LIBRUSTZCASH) \
|
||||
$(LIBZCASH_LIBS)
|
||||
|
||||
|
||||
zcash_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
||||
|
|
|
@ -114,6 +114,7 @@ BITCOIN_TESTS += \
|
|||
wallet/test/wallet_test_fixture.h \
|
||||
wallet/test/accounting_tests.cpp \
|
||||
wallet/test/wallet_tests.cpp \
|
||||
wallet/test/crypto_tests.cpp \
|
||||
wallet/test/rpc_wallet_tests.cpp
|
||||
endif
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "bench.h"
|
||||
|
||||
#include "support/lockedpool.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#define ASIZE 2048
|
||||
#define BITER 5000
|
||||
#define MSIZE 2048
|
||||
|
||||
static void LockedPool(benchmark::State& state)
|
||||
{
|
||||
void *synth_base = reinterpret_cast<void*>(0x08000000);
|
||||
const size_t synth_size = 1024*1024;
|
||||
Arena b(synth_base, synth_size, 16);
|
||||
|
||||
std::vector<void*> addr;
|
||||
for (int x=0; x<ASIZE; ++x)
|
||||
addr.push_back(0);
|
||||
uint32_t s = 0x12345678;
|
||||
while (state.KeepRunning()) {
|
||||
for (int x=0; x<BITER; ++x) {
|
||||
int idx = s & (addr.size()-1);
|
||||
if (s & 0x80000000) {
|
||||
b.free(addr[idx]);
|
||||
addr[idx] = 0;
|
||||
} else if(!addr[idx]) {
|
||||
addr[idx] = b.alloc((s >> 16) & (MSIZE-1));
|
||||
}
|
||||
bool lsb = s & 1;
|
||||
s >>= 1;
|
||||
if (lsb)
|
||||
s ^= 0xf00f00f0; // LFSR period 0xf7ffffe0
|
||||
}
|
||||
}
|
||||
for (void *ptr: addr)
|
||||
b.free(ptr);
|
||||
addr.clear();
|
||||
}
|
||||
|
||||
BENCHMARK(LockedPool);
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "aes.h"
|
||||
#include "crypto/common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" {
|
||||
#include "crypto/ctaes/ctaes.c"
|
||||
}
|
||||
|
||||
AES128Encrypt::AES128Encrypt(const unsigned char key[16])
|
||||
{
|
||||
AES128_init(&ctx, key);
|
||||
}
|
||||
|
||||
AES128Encrypt::~AES128Encrypt()
|
||||
{
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
}
|
||||
|
||||
void AES128Encrypt::Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const
|
||||
{
|
||||
AES128_encrypt(&ctx, 1, ciphertext, plaintext);
|
||||
}
|
||||
|
||||
AES128Decrypt::AES128Decrypt(const unsigned char key[16])
|
||||
{
|
||||
AES128_init(&ctx, key);
|
||||
}
|
||||
|
||||
AES128Decrypt::~AES128Decrypt()
|
||||
{
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
}
|
||||
|
||||
void AES128Decrypt::Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const
|
||||
{
|
||||
AES128_decrypt(&ctx, 1, plaintext, ciphertext);
|
||||
}
|
||||
|
||||
AES256Encrypt::AES256Encrypt(const unsigned char key[32])
|
||||
{
|
||||
AES256_init(&ctx, key);
|
||||
}
|
||||
|
||||
AES256Encrypt::~AES256Encrypt()
|
||||
{
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
}
|
||||
|
||||
void AES256Encrypt::Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const
|
||||
{
|
||||
AES256_encrypt(&ctx, 1, ciphertext, plaintext);
|
||||
}
|
||||
|
||||
AES256Decrypt::AES256Decrypt(const unsigned char key[32])
|
||||
{
|
||||
AES256_init(&ctx, key);
|
||||
}
|
||||
|
||||
AES256Decrypt::~AES256Decrypt()
|
||||
{
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
}
|
||||
|
||||
void AES256Decrypt::Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const
|
||||
{
|
||||
AES256_decrypt(&ctx, 1, plaintext, ciphertext);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
static int CBCEncrypt(const T& enc, const unsigned char iv[AES_BLOCKSIZE], const unsigned char* data, int size, bool pad, unsigned char* out)
|
||||
{
|
||||
int written = 0;
|
||||
int padsize = size % AES_BLOCKSIZE;
|
||||
unsigned char mixed[AES_BLOCKSIZE];
|
||||
|
||||
if (!data || !size || !out)
|
||||
return 0;
|
||||
|
||||
if (!pad && padsize != 0)
|
||||
return 0;
|
||||
|
||||
memcpy(mixed, iv, AES_BLOCKSIZE);
|
||||
|
||||
// Write all but the last block
|
||||
while (written + AES_BLOCKSIZE <= size) {
|
||||
for (int i = 0; i != AES_BLOCKSIZE; i++)
|
||||
mixed[i] ^= *data++;
|
||||
enc.Encrypt(out + written, mixed);
|
||||
memcpy(mixed, out + written, AES_BLOCKSIZE);
|
||||
written += AES_BLOCKSIZE;
|
||||
}
|
||||
if (pad) {
|
||||
// For all that remains, pad each byte with the value of the remaining
|
||||
// space. If there is none, pad by a full block.
|
||||
for (int i = 0; i != padsize; i++)
|
||||
mixed[i] ^= *data++;
|
||||
for (int i = padsize; i != AES_BLOCKSIZE; i++)
|
||||
mixed[i] ^= AES_BLOCKSIZE - padsize;
|
||||
enc.Encrypt(out + written, mixed);
|
||||
written += AES_BLOCKSIZE;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static int CBCDecrypt(const T& dec, const unsigned char iv[AES_BLOCKSIZE], const unsigned char* data, int size, bool pad, unsigned char* out)
|
||||
{
|
||||
unsigned char padsize = 0;
|
||||
int written = 0;
|
||||
bool fail = false;
|
||||
const unsigned char* prev = iv;
|
||||
|
||||
if (!data || !size || !out)
|
||||
return 0;
|
||||
|
||||
if (size % AES_BLOCKSIZE != 0)
|
||||
return 0;
|
||||
|
||||
// Decrypt all data. Padding will be checked in the output.
|
||||
while (written != size) {
|
||||
dec.Decrypt(out, data + written);
|
||||
for (int i = 0; i != AES_BLOCKSIZE; i++)
|
||||
*out++ ^= prev[i];
|
||||
prev = data + written;
|
||||
written += AES_BLOCKSIZE;
|
||||
}
|
||||
|
||||
// When decrypting padding, attempt to run in constant-time
|
||||
if (pad) {
|
||||
// If used, padding size is the value of the last decrypted byte. For
|
||||
// it to be valid, It must be between 1 and AES_BLOCKSIZE.
|
||||
padsize = *--out;
|
||||
fail = !padsize | (padsize > AES_BLOCKSIZE);
|
||||
|
||||
// If not well-formed, treat it as though there's no padding.
|
||||
padsize *= !fail;
|
||||
|
||||
// All padding must equal the last byte otherwise it's not well-formed
|
||||
for (int i = AES_BLOCKSIZE; i != 0; i--)
|
||||
fail |= ((i > AES_BLOCKSIZE - padsize) & (*out-- != padsize));
|
||||
|
||||
written -= padsize;
|
||||
}
|
||||
return written * !fail;
|
||||
}
|
||||
|
||||
AES256CBCEncrypt::AES256CBCEncrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn)
|
||||
: enc(key), pad(padIn)
|
||||
{
|
||||
memcpy(iv, ivIn, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
int AES256CBCEncrypt::Encrypt(const unsigned char* data, int size, unsigned char* out) const
|
||||
{
|
||||
return CBCEncrypt(enc, iv, data, size, pad, out);
|
||||
}
|
||||
|
||||
AES256CBCEncrypt::~AES256CBCEncrypt()
|
||||
{
|
||||
memset(iv, 0, sizeof(iv));
|
||||
}
|
||||
|
||||
AES256CBCDecrypt::AES256CBCDecrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn)
|
||||
: dec(key), pad(padIn)
|
||||
{
|
||||
memcpy(iv, ivIn, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
|
||||
int AES256CBCDecrypt::Decrypt(const unsigned char* data, int size, unsigned char* out) const
|
||||
{
|
||||
return CBCDecrypt(dec, iv, data, size, pad, out);
|
||||
}
|
||||
|
||||
AES256CBCDecrypt::~AES256CBCDecrypt()
|
||||
{
|
||||
memset(iv, 0, sizeof(iv));
|
||||
}
|
||||
|
||||
AES128CBCEncrypt::AES128CBCEncrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn)
|
||||
: enc(key), pad(padIn)
|
||||
{
|
||||
memcpy(iv, ivIn, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
AES128CBCEncrypt::~AES128CBCEncrypt()
|
||||
{
|
||||
memset(iv, 0, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
int AES128CBCEncrypt::Encrypt(const unsigned char* data, int size, unsigned char* out) const
|
||||
{
|
||||
return CBCEncrypt(enc, iv, data, size, pad, out);
|
||||
}
|
||||
|
||||
AES128CBCDecrypt::AES128CBCDecrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn)
|
||||
: dec(key), pad(padIn)
|
||||
{
|
||||
memcpy(iv, ivIn, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
AES128CBCDecrypt::~AES128CBCDecrypt()
|
||||
{
|
||||
memset(iv, 0, AES_BLOCKSIZE);
|
||||
}
|
||||
|
||||
int AES128CBCDecrypt::Decrypt(const unsigned char* data, int size, unsigned char* out) const
|
||||
{
|
||||
return CBCDecrypt(dec, iv, data, size, pad, out);
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) 2015 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
//
|
||||
// C++ wrapper around ctaes, a constant-time AES implementation
|
||||
|
||||
#ifndef BITCOIN_CRYPTO_AES_H
|
||||
#define BITCOIN_CRYPTO_AES_H
|
||||
|
||||
extern "C" {
|
||||
#include "crypto/ctaes/ctaes.h"
|
||||
}
|
||||
|
||||
static const int AES_BLOCKSIZE = 16;
|
||||
static const int AES128_KEYSIZE = 16;
|
||||
static const int AES256_KEYSIZE = 32;
|
||||
|
||||
/** An encryption class for AES-128. */
|
||||
class AES128Encrypt
|
||||
{
|
||||
private:
|
||||
AES128_ctx ctx;
|
||||
|
||||
public:
|
||||
AES128Encrypt(const unsigned char key[16]);
|
||||
~AES128Encrypt();
|
||||
void Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const;
|
||||
};
|
||||
|
||||
/** A decryption class for AES-128. */
|
||||
class AES128Decrypt
|
||||
{
|
||||
private:
|
||||
AES128_ctx ctx;
|
||||
|
||||
public:
|
||||
AES128Decrypt(const unsigned char key[16]);
|
||||
~AES128Decrypt();
|
||||
void Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const;
|
||||
};
|
||||
|
||||
/** An encryption class for AES-256. */
|
||||
class AES256Encrypt
|
||||
{
|
||||
private:
|
||||
AES256_ctx ctx;
|
||||
|
||||
public:
|
||||
AES256Encrypt(const unsigned char key[32]);
|
||||
~AES256Encrypt();
|
||||
void Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const;
|
||||
};
|
||||
|
||||
/** A decryption class for AES-256. */
|
||||
class AES256Decrypt
|
||||
{
|
||||
private:
|
||||
AES256_ctx ctx;
|
||||
|
||||
public:
|
||||
AES256Decrypt(const unsigned char key[32]);
|
||||
~AES256Decrypt();
|
||||
void Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const;
|
||||
};
|
||||
|
||||
class AES256CBCEncrypt
|
||||
{
|
||||
public:
|
||||
AES256CBCEncrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn);
|
||||
~AES256CBCEncrypt();
|
||||
int Encrypt(const unsigned char* data, int size, unsigned char* out) const;
|
||||
|
||||
private:
|
||||
const AES256Encrypt enc;
|
||||
const bool pad;
|
||||
unsigned char iv[AES_BLOCKSIZE];
|
||||
};
|
||||
|
||||
class AES256CBCDecrypt
|
||||
{
|
||||
public:
|
||||
AES256CBCDecrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn);
|
||||
~AES256CBCDecrypt();
|
||||
int Decrypt(const unsigned char* data, int size, unsigned char* out) const;
|
||||
|
||||
private:
|
||||
const AES256Decrypt dec;
|
||||
const bool pad;
|
||||
unsigned char iv[AES_BLOCKSIZE];
|
||||
};
|
||||
|
||||
class AES128CBCEncrypt
|
||||
{
|
||||
public:
|
||||
AES128CBCEncrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn);
|
||||
~AES128CBCEncrypt();
|
||||
int Encrypt(const unsigned char* data, int size, unsigned char* out) const;
|
||||
|
||||
private:
|
||||
const AES128Encrypt enc;
|
||||
const bool pad;
|
||||
unsigned char iv[AES_BLOCKSIZE];
|
||||
};
|
||||
|
||||
class AES128CBCDecrypt
|
||||
{
|
||||
public:
|
||||
AES128CBCDecrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn);
|
||||
~AES128CBCDecrypt();
|
||||
int Decrypt(const unsigned char* data, int size, unsigned char* out) const;
|
||||
|
||||
private:
|
||||
const AES128Decrypt dec;
|
||||
const bool pad;
|
||||
unsigned char iv[AES_BLOCKSIZE];
|
||||
};
|
||||
|
||||
#endif // BITCOIN_CRYPTO_AES_H
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Pieter Wuille
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,41 @@
|
|||
ctaes
|
||||
=====
|
||||
|
||||
Simple C module for constant-time AES encryption and decryption.
|
||||
|
||||
Features:
|
||||
* Simple, pure C code without any dependencies.
|
||||
* No tables or data-dependent branches whatsoever, but using bit sliced approach from https://eprint.iacr.org/2009/129.pdf.
|
||||
* Very small object code: slightly over 4k of executable code when compiled with -Os.
|
||||
* Slower than implementations based on precomputed tables or specialized instructions, but can do ~15 MB/s on modern CPUs.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Compiled with GCC 5.3.1 with -O3, on an Intel(R) Core(TM) i7-4800MQ CPU, numbers in CPU cycles:
|
||||
|
||||
| Algorithm | Key schedule | Encryption per byte | Decryption per byte |
|
||||
| --------- | ------------:| -------------------:| -------------------:|
|
||||
| AES-128 | 2.8k | 154 | 161 |
|
||||
| AES-192 | 3.1k | 169 | 181 |
|
||||
| AES-256 | 4.0k | 191 | 203 |
|
||||
|
||||
Build steps
|
||||
-----------
|
||||
|
||||
Object code:
|
||||
|
||||
$ gcc -O3 ctaes.c -c -o ctaes.o
|
||||
|
||||
Tests:
|
||||
|
||||
$ gcc -O3 ctaes.c test.c -o test
|
||||
|
||||
Benchmark:
|
||||
|
||||
$ gcc -O3 ctaes.c bench.c -o bench
|
||||
|
||||
Review
|
||||
------
|
||||
|
||||
Results of a formal review of the code can be found in http://bitcoin.sipa.be/ctaes/review.zip
|
|
@ -0,0 +1,170 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "sys/time.h"
|
||||
|
||||
#include "ctaes.h"
|
||||
|
||||
static double gettimedouble(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_usec * 0.000001 + tv.tv_sec;
|
||||
}
|
||||
|
||||
static void print_number(double x) {
|
||||
double y = x;
|
||||
int c = 0;
|
||||
if (y < 0.0) {
|
||||
y = -y;
|
||||
}
|
||||
while (y < 100.0) {
|
||||
y *= 10.0;
|
||||
c++;
|
||||
}
|
||||
printf("%.*f", c, x);
|
||||
}
|
||||
|
||||
static void run_benchmark(char *name, void (*benchmark)(void*), void (*setup)(void*), void (*teardown)(void*), void* data, int count, int iter) {
|
||||
int i;
|
||||
double min = HUGE_VAL;
|
||||
double sum = 0.0;
|
||||
double max = 0.0;
|
||||
for (i = 0; i < count; i++) {
|
||||
double begin, total;
|
||||
if (setup != NULL) {
|
||||
setup(data);
|
||||
}
|
||||
begin = gettimedouble();
|
||||
benchmark(data);
|
||||
total = gettimedouble() - begin;
|
||||
if (teardown != NULL) {
|
||||
teardown(data);
|
||||
}
|
||||
if (total < min) {
|
||||
min = total;
|
||||
}
|
||||
if (total > max) {
|
||||
max = total;
|
||||
}
|
||||
sum += total;
|
||||
}
|
||||
printf("%s: min ", name);
|
||||
print_number(min * 1000000000.0 / iter);
|
||||
printf("ns / avg ");
|
||||
print_number((sum / count) * 1000000000.0 / iter);
|
||||
printf("ns / max ");
|
||||
print_number(max * 1000000000.0 / iter);
|
||||
printf("ns\n");
|
||||
}
|
||||
|
||||
static void bench_AES128_init(void* data) {
|
||||
AES128_ctx* ctx = (AES128_ctx*)data;
|
||||
int i;
|
||||
for (i = 0; i < 50000; i++) {
|
||||
AES128_init(ctx, (unsigned char*)ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES128_encrypt_setup(void* data) {
|
||||
AES128_ctx* ctx = (AES128_ctx*)data;
|
||||
static const unsigned char key[16] = {0};
|
||||
AES128_init(ctx, key);
|
||||
}
|
||||
|
||||
static void bench_AES128_encrypt(void* data) {
|
||||
const AES128_ctx* ctx = (const AES128_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES128_encrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES128_decrypt(void* data) {
|
||||
const AES128_ctx* ctx = (const AES128_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES128_decrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES192_init(void* data) {
|
||||
AES192_ctx* ctx = (AES192_ctx*)data;
|
||||
int i;
|
||||
for (i = 0; i < 50000; i++) {
|
||||
AES192_init(ctx, (unsigned char*)ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES192_encrypt_setup(void* data) {
|
||||
AES192_ctx* ctx = (AES192_ctx*)data;
|
||||
static const unsigned char key[16] = {0};
|
||||
AES192_init(ctx, key);
|
||||
}
|
||||
|
||||
static void bench_AES192_encrypt(void* data) {
|
||||
const AES192_ctx* ctx = (const AES192_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES192_encrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES192_decrypt(void* data) {
|
||||
const AES192_ctx* ctx = (const AES192_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES192_decrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES256_init(void* data) {
|
||||
AES256_ctx* ctx = (AES256_ctx*)data;
|
||||
int i;
|
||||
for (i = 0; i < 50000; i++) {
|
||||
AES256_init(ctx, (unsigned char*)ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void bench_AES256_encrypt_setup(void* data) {
|
||||
AES256_ctx* ctx = (AES256_ctx*)data;
|
||||
static const unsigned char key[16] = {0};
|
||||
AES256_init(ctx, key);
|
||||
}
|
||||
|
||||
static void bench_AES256_encrypt(void* data) {
|
||||
const AES256_ctx* ctx = (const AES256_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES256_encrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_AES256_decrypt(void* data) {
|
||||
const AES256_ctx* ctx = (const AES256_ctx*)data;
|
||||
unsigned char scratch[16] = {0};
|
||||
int i;
|
||||
for (i = 0; i < 4000000 / 16; i++) {
|
||||
AES256_decrypt(ctx, 1, scratch, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
AES128_ctx ctx128;
|
||||
AES192_ctx ctx192;
|
||||
AES256_ctx ctx256;
|
||||
run_benchmark("aes128_init", bench_AES128_init, NULL, NULL, &ctx128, 20, 50000);
|
||||
run_benchmark("aes128_encrypt_byte", bench_AES128_encrypt, bench_AES128_encrypt_setup, NULL, &ctx128, 20, 4000000);
|
||||
run_benchmark("aes128_decrypt_byte", bench_AES128_decrypt, bench_AES128_encrypt_setup, NULL, &ctx128, 20, 4000000);
|
||||
run_benchmark("aes192_init", bench_AES192_init, NULL, NULL, &ctx192, 20, 50000);
|
||||
run_benchmark("aes192_encrypt_byte", bench_AES192_encrypt, bench_AES192_encrypt_setup, NULL, &ctx192, 20, 4000000);
|
||||
run_benchmark("aes192_decrypt_byte", bench_AES192_decrypt, bench_AES192_encrypt_setup, NULL, &ctx192, 20, 4000000);
|
||||
run_benchmark("aes256_init", bench_AES256_init, NULL, NULL, &ctx256, 20, 50000);
|
||||
run_benchmark("aes256_encrypt_byte", bench_AES256_encrypt, bench_AES256_encrypt_setup, NULL, &ctx256, 20, 4000000);
|
||||
run_benchmark("aes256_decrypt_byte", bench_AES256_decrypt, bench_AES256_encrypt_setup, NULL, &ctx256, 20, 4000000);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
/*********************************************************************
|
||||
* Copyright (c) 2016 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
/* Constant time, unoptimized, concise, plain C, AES implementation
|
||||
* Based On:
|
||||
* Emilia Kasper and Peter Schwabe, Faster and Timing-Attack Resistant AES-GCM
|
||||
* http://www.iacr.org/archive/ches2009/57470001/57470001.pdf
|
||||
* But using 8 16-bit integers representing a single AES state rather than 8 128-bit
|
||||
* integers representing 8 AES states.
|
||||
*/
|
||||
|
||||
#include "ctaes.h"
|
||||
|
||||
/* Slice variable slice_i contains the i'th bit of the 16 state variables in this order:
|
||||
* 0 1 2 3
|
||||
* 4 5 6 7
|
||||
* 8 9 10 11
|
||||
* 12 13 14 15
|
||||
*/
|
||||
|
||||
/** Convert a byte to sliced form, storing it corresponding to given row and column in s */
|
||||
static void LoadByte(AES_state* s, unsigned char byte, int r, int c) {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
s->slice[i] |= (byte & 1) << (r * 4 + c);
|
||||
byte >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Load 16 bytes of data into 8 sliced integers */
|
||||
static void LoadBytes(AES_state *s, const unsigned char* data16) {
|
||||
int c;
|
||||
for (c = 0; c < 4; c++) {
|
||||
int r;
|
||||
for (r = 0; r < 4; r++) {
|
||||
LoadByte(s, *(data16++), r, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert 8 sliced integers into 16 bytes of data */
|
||||
static void SaveBytes(unsigned char* data16, const AES_state *s) {
|
||||
int c;
|
||||
for (c = 0; c < 4; c++) {
|
||||
int r;
|
||||
for (r = 0; r < 4; r++) {
|
||||
int b;
|
||||
uint8_t v = 0;
|
||||
for (b = 0; b < 8; b++) {
|
||||
v |= ((s->slice[b] >> (r * 4 + c)) & 1) << b;
|
||||
}
|
||||
*(data16++) = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* S-box implementation based on the gate logic from:
|
||||
* Joan Boyar and Rene Peralta, A depth-16 circuit for the AES S-box.
|
||||
* https://eprint.iacr.org/2011/332.pdf
|
||||
*/
|
||||
static void SubBytes(AES_state *s, int inv) {
|
||||
/* Load the bit slices */
|
||||
uint16_t U0 = s->slice[7], U1 = s->slice[6], U2 = s->slice[5], U3 = s->slice[4];
|
||||
uint16_t U4 = s->slice[3], U5 = s->slice[2], U6 = s->slice[1], U7 = s->slice[0];
|
||||
|
||||
uint16_t T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16;
|
||||
uint16_t T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, D;
|
||||
uint16_t M1, M6, M11, M13, M15, M20, M21, M22, M23, M25, M37, M38, M39, M40;
|
||||
uint16_t M41, M42, M43, M44, M45, M46, M47, M48, M49, M50, M51, M52, M53, M54;
|
||||
uint16_t M55, M56, M57, M58, M59, M60, M61, M62, M63;
|
||||
|
||||
if (inv) {
|
||||
uint16_t R5, R13, R17, R18, R19;
|
||||
/* Undo linear postprocessing */
|
||||
T23 = U0 ^ U3;
|
||||
T22 = ~(U1 ^ U3);
|
||||
T2 = ~(U0 ^ U1);
|
||||
T1 = U3 ^ U4;
|
||||
T24 = ~(U4 ^ U7);
|
||||
R5 = U6 ^ U7;
|
||||
T8 = ~(U1 ^ T23);
|
||||
T19 = T22 ^ R5;
|
||||
T9 = ~(U7 ^ T1);
|
||||
T10 = T2 ^ T24;
|
||||
T13 = T2 ^ R5;
|
||||
T3 = T1 ^ R5;
|
||||
T25 = ~(U2 ^ T1);
|
||||
R13 = U1 ^ U6;
|
||||
T17 = ~(U2 ^ T19);
|
||||
T20 = T24 ^ R13;
|
||||
T4 = U4 ^ T8;
|
||||
R17 = ~(U2 ^ U5);
|
||||
R18 = ~(U5 ^ U6);
|
||||
R19 = ~(U2 ^ U4);
|
||||
D = U0 ^ R17;
|
||||
T6 = T22 ^ R17;
|
||||
T16 = R13 ^ R19;
|
||||
T27 = T1 ^ R18;
|
||||
T15 = T10 ^ T27;
|
||||
T14 = T10 ^ R18;
|
||||
T26 = T3 ^ T16;
|
||||
} else {
|
||||
/* Linear preprocessing. */
|
||||
T1 = U0 ^ U3;
|
||||
T2 = U0 ^ U5;
|
||||
T3 = U0 ^ U6;
|
||||
T4 = U3 ^ U5;
|
||||
T5 = U4 ^ U6;
|
||||
T6 = T1 ^ T5;
|
||||
T7 = U1 ^ U2;
|
||||
T8 = U7 ^ T6;
|
||||
T9 = U7 ^ T7;
|
||||
T10 = T6 ^ T7;
|
||||
T11 = U1 ^ U5;
|
||||
T12 = U2 ^ U5;
|
||||
T13 = T3 ^ T4;
|
||||
T14 = T6 ^ T11;
|
||||
T15 = T5 ^ T11;
|
||||
T16 = T5 ^ T12;
|
||||
T17 = T9 ^ T16;
|
||||
T18 = U3 ^ U7;
|
||||
T19 = T7 ^ T18;
|
||||
T20 = T1 ^ T19;
|
||||
T21 = U6 ^ U7;
|
||||
T22 = T7 ^ T21;
|
||||
T23 = T2 ^ T22;
|
||||
T24 = T2 ^ T10;
|
||||
T25 = T20 ^ T17;
|
||||
T26 = T3 ^ T16;
|
||||
T27 = T1 ^ T12;
|
||||
D = U7;
|
||||
}
|
||||
|
||||
/* Non-linear transformation (shared between the forward and backward case) */
|
||||
M1 = T13 & T6;
|
||||
M6 = T3 & T16;
|
||||
M11 = T1 & T15;
|
||||
M13 = (T4 & T27) ^ M11;
|
||||
M15 = (T2 & T10) ^ M11;
|
||||
M20 = T14 ^ M1 ^ (T23 & T8) ^ M13;
|
||||
M21 = (T19 & D) ^ M1 ^ T24 ^ M15;
|
||||
M22 = T26 ^ M6 ^ (T22 & T9) ^ M13;
|
||||
M23 = (T20 & T17) ^ M6 ^ M15 ^ T25;
|
||||
M25 = M22 & M20;
|
||||
M37 = M21 ^ ((M20 ^ M21) & (M23 ^ M25));
|
||||
M38 = M20 ^ M25 ^ (M21 | (M20 & M23));
|
||||
M39 = M23 ^ ((M22 ^ M23) & (M21 ^ M25));
|
||||
M40 = M22 ^ M25 ^ (M23 | (M21 & M22));
|
||||
M41 = M38 ^ M40;
|
||||
M42 = M37 ^ M39;
|
||||
M43 = M37 ^ M38;
|
||||
M44 = M39 ^ M40;
|
||||
M45 = M42 ^ M41;
|
||||
M46 = M44 & T6;
|
||||
M47 = M40 & T8;
|
||||
M48 = M39 & D;
|
||||
M49 = M43 & T16;
|
||||
M50 = M38 & T9;
|
||||
M51 = M37 & T17;
|
||||
M52 = M42 & T15;
|
||||
M53 = M45 & T27;
|
||||
M54 = M41 & T10;
|
||||
M55 = M44 & T13;
|
||||
M56 = M40 & T23;
|
||||
M57 = M39 & T19;
|
||||
M58 = M43 & T3;
|
||||
M59 = M38 & T22;
|
||||
M60 = M37 & T20;
|
||||
M61 = M42 & T1;
|
||||
M62 = M45 & T4;
|
||||
M63 = M41 & T2;
|
||||
|
||||
if (inv){
|
||||
/* Undo linear preprocessing */
|
||||
uint16_t P0 = M52 ^ M61;
|
||||
uint16_t P1 = M58 ^ M59;
|
||||
uint16_t P2 = M54 ^ M62;
|
||||
uint16_t P3 = M47 ^ M50;
|
||||
uint16_t P4 = M48 ^ M56;
|
||||
uint16_t P5 = M46 ^ M51;
|
||||
uint16_t P6 = M49 ^ M60;
|
||||
uint16_t P7 = P0 ^ P1;
|
||||
uint16_t P8 = M50 ^ M53;
|
||||
uint16_t P9 = M55 ^ M63;
|
||||
uint16_t P10 = M57 ^ P4;
|
||||
uint16_t P11 = P0 ^ P3;
|
||||
uint16_t P12 = M46 ^ M48;
|
||||
uint16_t P13 = M49 ^ M51;
|
||||
uint16_t P14 = M49 ^ M62;
|
||||
uint16_t P15 = M54 ^ M59;
|
||||
uint16_t P16 = M57 ^ M61;
|
||||
uint16_t P17 = M58 ^ P2;
|
||||
uint16_t P18 = M63 ^ P5;
|
||||
uint16_t P19 = P2 ^ P3;
|
||||
uint16_t P20 = P4 ^ P6;
|
||||
uint16_t P22 = P2 ^ P7;
|
||||
uint16_t P23 = P7 ^ P8;
|
||||
uint16_t P24 = P5 ^ P7;
|
||||
uint16_t P25 = P6 ^ P10;
|
||||
uint16_t P26 = P9 ^ P11;
|
||||
uint16_t P27 = P10 ^ P18;
|
||||
uint16_t P28 = P11 ^ P25;
|
||||
uint16_t P29 = P15 ^ P20;
|
||||
s->slice[7] = P13 ^ P22;
|
||||
s->slice[6] = P26 ^ P29;
|
||||
s->slice[5] = P17 ^ P28;
|
||||
s->slice[4] = P12 ^ P22;
|
||||
s->slice[3] = P23 ^ P27;
|
||||
s->slice[2] = P19 ^ P24;
|
||||
s->slice[1] = P14 ^ P23;
|
||||
s->slice[0] = P9 ^ P16;
|
||||
} else {
|
||||
/* Linear postprocessing */
|
||||
uint16_t L0 = M61 ^ M62;
|
||||
uint16_t L1 = M50 ^ M56;
|
||||
uint16_t L2 = M46 ^ M48;
|
||||
uint16_t L3 = M47 ^ M55;
|
||||
uint16_t L4 = M54 ^ M58;
|
||||
uint16_t L5 = M49 ^ M61;
|
||||
uint16_t L6 = M62 ^ L5;
|
||||
uint16_t L7 = M46 ^ L3;
|
||||
uint16_t L8 = M51 ^ M59;
|
||||
uint16_t L9 = M52 ^ M53;
|
||||
uint16_t L10 = M53 ^ L4;
|
||||
uint16_t L11 = M60 ^ L2;
|
||||
uint16_t L12 = M48 ^ M51;
|
||||
uint16_t L13 = M50 ^ L0;
|
||||
uint16_t L14 = M52 ^ M61;
|
||||
uint16_t L15 = M55 ^ L1;
|
||||
uint16_t L16 = M56 ^ L0;
|
||||
uint16_t L17 = M57 ^ L1;
|
||||
uint16_t L18 = M58 ^ L8;
|
||||
uint16_t L19 = M63 ^ L4;
|
||||
uint16_t L20 = L0 ^ L1;
|
||||
uint16_t L21 = L1 ^ L7;
|
||||
uint16_t L22 = L3 ^ L12;
|
||||
uint16_t L23 = L18 ^ L2;
|
||||
uint16_t L24 = L15 ^ L9;
|
||||
uint16_t L25 = L6 ^ L10;
|
||||
uint16_t L26 = L7 ^ L9;
|
||||
uint16_t L27 = L8 ^ L10;
|
||||
uint16_t L28 = L11 ^ L14;
|
||||
uint16_t L29 = L11 ^ L17;
|
||||
s->slice[7] = L6 ^ L24;
|
||||
s->slice[6] = ~(L16 ^ L26);
|
||||
s->slice[5] = ~(L19 ^ L28);
|
||||
s->slice[4] = L6 ^ L21;
|
||||
s->slice[3] = L20 ^ L22;
|
||||
s->slice[2] = L25 ^ L29;
|
||||
s->slice[1] = ~(L13 ^ L27);
|
||||
s->slice[0] = ~(L6 ^ L23);
|
||||
}
|
||||
}
|
||||
|
||||
#define BIT_RANGE(from,to) (((1 << ((to) - (from))) - 1) << (from))
|
||||
|
||||
#define BIT_RANGE_LEFT(x,from,to,shift) (((x) & BIT_RANGE((from), (to))) << (shift))
|
||||
#define BIT_RANGE_RIGHT(x,from,to,shift) (((x) & BIT_RANGE((from), (to))) >> (shift))
|
||||
|
||||
static void ShiftRows(AES_state* s) {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
uint16_t v = s->slice[i];
|
||||
s->slice[i] =
|
||||
(v & BIT_RANGE(0, 4)) |
|
||||
BIT_RANGE_LEFT(v, 4, 5, 3) | BIT_RANGE_RIGHT(v, 5, 8, 1) |
|
||||
BIT_RANGE_LEFT(v, 8, 10, 2) | BIT_RANGE_RIGHT(v, 10, 12, 2) |
|
||||
BIT_RANGE_LEFT(v, 12, 15, 1) | BIT_RANGE_RIGHT(v, 15, 16, 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void InvShiftRows(AES_state* s) {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
uint16_t v = s->slice[i];
|
||||
s->slice[i] =
|
||||
(v & BIT_RANGE(0, 4)) |
|
||||
BIT_RANGE_LEFT(v, 4, 7, 1) | BIT_RANGE_RIGHT(v, 7, 8, 3) |
|
||||
BIT_RANGE_LEFT(v, 8, 10, 2) | BIT_RANGE_RIGHT(v, 10, 12, 2) |
|
||||
BIT_RANGE_LEFT(v, 12, 13, 3) | BIT_RANGE_RIGHT(v, 13, 16, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#define ROT(x,b) (((x) >> ((b) * 4)) | ((x) << ((4-(b)) * 4)))
|
||||
|
||||
static void MixColumns(AES_state* s, int inv) {
|
||||
/* The MixColumns transform treats the bytes of the columns of the state as
|
||||
* coefficients of a 3rd degree polynomial over GF(2^8) and multiplies them
|
||||
* by the fixed polynomial a(x) = {03}x^3 + {01}x^2 + {01}x + {02}, modulo
|
||||
* x^4 + {01}.
|
||||
*
|
||||
* In the inverse transform, we multiply by the inverse of a(x),
|
||||
* a^-1(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}. This is equal to
|
||||
* a(x) * ({04}x^2 + {05}), so we can reuse the forward transform's code
|
||||
* (found in OpenSSL's bsaes-x86_64.pl, attributed to Jussi Kivilinna)
|
||||
*
|
||||
* In the bitsliced representation, a multiplication of every column by x
|
||||
* mod x^4 + 1 is simply a right rotation.
|
||||
*/
|
||||
|
||||
/* Shared for both directions is a multiplication by a(x), which can be
|
||||
* rewritten as (x^3 + x^2 + x) + {02}*(x^3 + {01}).
|
||||
*
|
||||
* First compute s into the s? variables, (x^3 + {01}) * s into the s?_01
|
||||
* variables and (x^3 + x^2 + x)*s into the s?_123 variables.
|
||||
*/
|
||||
uint16_t s0 = s->slice[0], s1 = s->slice[1], s2 = s->slice[2], s3 = s->slice[3];
|
||||
uint16_t s4 = s->slice[4], s5 = s->slice[5], s6 = s->slice[6], s7 = s->slice[7];
|
||||
uint16_t s0_01 = s0 ^ ROT(s0, 1), s0_123 = ROT(s0_01, 1) ^ ROT(s0, 3);
|
||||
uint16_t s1_01 = s1 ^ ROT(s1, 1), s1_123 = ROT(s1_01, 1) ^ ROT(s1, 3);
|
||||
uint16_t s2_01 = s2 ^ ROT(s2, 1), s2_123 = ROT(s2_01, 1) ^ ROT(s2, 3);
|
||||
uint16_t s3_01 = s3 ^ ROT(s3, 1), s3_123 = ROT(s3_01, 1) ^ ROT(s3, 3);
|
||||
uint16_t s4_01 = s4 ^ ROT(s4, 1), s4_123 = ROT(s4_01, 1) ^ ROT(s4, 3);
|
||||
uint16_t s5_01 = s5 ^ ROT(s5, 1), s5_123 = ROT(s5_01, 1) ^ ROT(s5, 3);
|
||||
uint16_t s6_01 = s6 ^ ROT(s6, 1), s6_123 = ROT(s6_01, 1) ^ ROT(s6, 3);
|
||||
uint16_t s7_01 = s7 ^ ROT(s7, 1), s7_123 = ROT(s7_01, 1) ^ ROT(s7, 3);
|
||||
/* Now compute s = s?_123 + {02} * s?_01. */
|
||||
s->slice[0] = s7_01 ^ s0_123;
|
||||
s->slice[1] = s7_01 ^ s0_01 ^ s1_123;
|
||||
s->slice[2] = s1_01 ^ s2_123;
|
||||
s->slice[3] = s7_01 ^ s2_01 ^ s3_123;
|
||||
s->slice[4] = s7_01 ^ s3_01 ^ s4_123;
|
||||
s->slice[5] = s4_01 ^ s5_123;
|
||||
s->slice[6] = s5_01 ^ s6_123;
|
||||
s->slice[7] = s6_01 ^ s7_123;
|
||||
if (inv) {
|
||||
/* In the reverse direction, we further need to multiply by
|
||||
* {04}x^2 + {05}, which can be written as {04} * (x^2 + {01}) + {01}.
|
||||
*
|
||||
* First compute (x^2 + {01}) * s into the t?_02 variables: */
|
||||
uint16_t t0_02 = s->slice[0] ^ ROT(s->slice[0], 2);
|
||||
uint16_t t1_02 = s->slice[1] ^ ROT(s->slice[1], 2);
|
||||
uint16_t t2_02 = s->slice[2] ^ ROT(s->slice[2], 2);
|
||||
uint16_t t3_02 = s->slice[3] ^ ROT(s->slice[3], 2);
|
||||
uint16_t t4_02 = s->slice[4] ^ ROT(s->slice[4], 2);
|
||||
uint16_t t5_02 = s->slice[5] ^ ROT(s->slice[5], 2);
|
||||
uint16_t t6_02 = s->slice[6] ^ ROT(s->slice[6], 2);
|
||||
uint16_t t7_02 = s->slice[7] ^ ROT(s->slice[7], 2);
|
||||
/* And then update s += {04} * t?_02 */
|
||||
s->slice[0] ^= t6_02;
|
||||
s->slice[1] ^= t6_02 ^ t7_02;
|
||||
s->slice[2] ^= t0_02 ^ t7_02;
|
||||
s->slice[3] ^= t1_02 ^ t6_02;
|
||||
s->slice[4] ^= t2_02 ^ t6_02 ^ t7_02;
|
||||
s->slice[5] ^= t3_02 ^ t7_02;
|
||||
s->slice[6] ^= t4_02;
|
||||
s->slice[7] ^= t5_02;
|
||||
}
|
||||
}
|
||||
|
||||
static void AddRoundKey(AES_state* s, const AES_state* round) {
|
||||
int b;
|
||||
for (b = 0; b < 8; b++) {
|
||||
s->slice[b] ^= round->slice[b];
|
||||
}
|
||||
}
|
||||
|
||||
/** column_0(s) = column_c(a) */
|
||||
static void GetOneColumn(AES_state* s, const AES_state* a, int c) {
|
||||
int b;
|
||||
for (b = 0; b < 8; b++) {
|
||||
s->slice[b] = (a->slice[b] >> c) & 0x1111;
|
||||
}
|
||||
}
|
||||
|
||||
/** column_c1(r) |= (column_0(s) ^= column_c2(a)) */
|
||||
static void KeySetupColumnMix(AES_state* s, AES_state* r, const AES_state* a, int c1, int c2) {
|
||||
int b;
|
||||
for (b = 0; b < 8; b++) {
|
||||
r->slice[b] |= ((s->slice[b] ^= ((a->slice[b] >> c2) & 0x1111)) & 0x1111) << c1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Rotate the rows in s one position upwards, and xor in r */
|
||||
static void KeySetupTransform(AES_state* s, const AES_state* r) {
|
||||
int b;
|
||||
for (b = 0; b < 8; b++) {
|
||||
s->slice[b] = ((s->slice[b] >> 4) | (s->slice[b] << 12)) ^ r->slice[b];
|
||||
}
|
||||
}
|
||||
|
||||
/* Multiply the cells in s by x, as polynomials over GF(2) mod x^8 + x^4 + x^3 + x + 1 */
|
||||
static void MultX(AES_state* s) {
|
||||
uint16_t top = s->slice[7];
|
||||
s->slice[7] = s->slice[6];
|
||||
s->slice[6] = s->slice[5];
|
||||
s->slice[5] = s->slice[4];
|
||||
s->slice[4] = s->slice[3] ^ top;
|
||||
s->slice[3] = s->slice[2] ^ top;
|
||||
s->slice[2] = s->slice[1];
|
||||
s->slice[1] = s->slice[0] ^ top;
|
||||
s->slice[0] = top;
|
||||
}
|
||||
|
||||
/** Expand the cipher key into the key schedule.
|
||||
*
|
||||
* state must be a pointer to an array of size nrounds + 1.
|
||||
* key must be a pointer to 4 * nkeywords bytes.
|
||||
*
|
||||
* AES128 uses nkeywords = 4, nrounds = 10
|
||||
* AES192 uses nkeywords = 6, nrounds = 12
|
||||
* AES256 uses nkeywords = 8, nrounds = 14
|
||||
*/
|
||||
static void AES_setup(AES_state* rounds, const uint8_t* key, int nkeywords, int nrounds)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* The one-byte round constant */
|
||||
AES_state rcon = {{1,0,0,0,0,0,0,0}};
|
||||
/* The number of the word being generated, modulo nkeywords */
|
||||
int pos = 0;
|
||||
/* The column representing the word currently being processed */
|
||||
AES_state column;
|
||||
|
||||
for (i = 0; i < nrounds + 1; i++) {
|
||||
int b;
|
||||
for (b = 0; b < 8; b++) {
|
||||
rounds[i].slice[b] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* The first nkeywords round columns are just taken from the key directly. */
|
||||
for (i = 0; i < nkeywords; i++) {
|
||||
int r;
|
||||
for (r = 0; r < 4; r++) {
|
||||
LoadByte(&rounds[i >> 2], *(key++), r, i & 3);
|
||||
}
|
||||
}
|
||||
|
||||
GetOneColumn(&column, &rounds[(nkeywords - 1) >> 2], (nkeywords - 1) & 3);
|
||||
|
||||
for (i = nkeywords; i < 4 * (nrounds + 1); i++) {
|
||||
/* Transform column */
|
||||
if (pos == 0) {
|
||||
SubBytes(&column, 0);
|
||||
KeySetupTransform(&column, &rcon);
|
||||
MultX(&rcon);
|
||||
} else if (nkeywords > 6 && pos == 4) {
|
||||
SubBytes(&column, 0);
|
||||
}
|
||||
if (++pos == nkeywords) pos = 0;
|
||||
KeySetupColumnMix(&column, &rounds[i >> 2], &rounds[(i - nkeywords) >> 2], i & 3, (i - nkeywords) & 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void AES_encrypt(const AES_state* rounds, int nrounds, unsigned char* cipher16, const unsigned char* plain16) {
|
||||
AES_state s = {{0}};
|
||||
int round;
|
||||
|
||||
LoadBytes(&s, plain16);
|
||||
AddRoundKey(&s, rounds++);
|
||||
|
||||
for (round = 1; round < nrounds; round++) {
|
||||
SubBytes(&s, 0);
|
||||
ShiftRows(&s);
|
||||
MixColumns(&s, 0);
|
||||
AddRoundKey(&s, rounds++);
|
||||
}
|
||||
|
||||
SubBytes(&s, 0);
|
||||
ShiftRows(&s);
|
||||
AddRoundKey(&s, rounds);
|
||||
|
||||
SaveBytes(cipher16, &s);
|
||||
}
|
||||
|
||||
static void AES_decrypt(const AES_state* rounds, int nrounds, unsigned char* plain16, const unsigned char* cipher16) {
|
||||
/* Most AES decryption implementations use the alternate scheme
|
||||
* (the Equivalent Inverse Cipher), which allows for more code reuse between
|
||||
* the encryption and decryption code, but requires separate setup for both.
|
||||
*/
|
||||
AES_state s = {{0}};
|
||||
int round;
|
||||
|
||||
rounds += nrounds;
|
||||
|
||||
LoadBytes(&s, cipher16);
|
||||
AddRoundKey(&s, rounds--);
|
||||
|
||||
for (round = 1; round < nrounds; round++) {
|
||||
InvShiftRows(&s);
|
||||
SubBytes(&s, 1);
|
||||
AddRoundKey(&s, rounds--);
|
||||
MixColumns(&s, 1);
|
||||
}
|
||||
|
||||
InvShiftRows(&s);
|
||||
SubBytes(&s, 1);
|
||||
AddRoundKey(&s, rounds);
|
||||
|
||||
SaveBytes(plain16, &s);
|
||||
}
|
||||
|
||||
void AES128_init(AES128_ctx* ctx, const unsigned char* key16) {
|
||||
AES_setup(ctx->rk, key16, 4, 10);
|
||||
}
|
||||
|
||||
void AES128_encrypt(const AES128_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16) {
|
||||
while (blocks--) {
|
||||
AES_encrypt(ctx->rk, 10, cipher16, plain16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void AES128_decrypt(const AES128_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16) {
|
||||
while (blocks--) {
|
||||
AES_decrypt(ctx->rk, 10, plain16, cipher16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void AES192_init(AES192_ctx* ctx, const unsigned char* key24) {
|
||||
AES_setup(ctx->rk, key24, 6, 12);
|
||||
}
|
||||
|
||||
void AES192_encrypt(const AES192_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16) {
|
||||
while (blocks--) {
|
||||
AES_encrypt(ctx->rk, 12, cipher16, plain16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AES192_decrypt(const AES192_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16) {
|
||||
while (blocks--) {
|
||||
AES_decrypt(ctx->rk, 12, plain16, cipher16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void AES256_init(AES256_ctx* ctx, const unsigned char* key32) {
|
||||
AES_setup(ctx->rk, key32, 8, 14);
|
||||
}
|
||||
|
||||
void AES256_encrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16) {
|
||||
while (blocks--) {
|
||||
AES_encrypt(ctx->rk, 14, cipher16, plain16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void AES256_decrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16) {
|
||||
while (blocks--) {
|
||||
AES_decrypt(ctx->rk, 14, plain16, cipher16);
|
||||
cipher16 += 16;
|
||||
plain16 += 16;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*********************************************************************
|
||||
* Copyright (c) 2016 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef _CTAES_H_
|
||||
#define _CTAES_H_ 1
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
uint16_t slice[8];
|
||||
} AES_state;
|
||||
|
||||
typedef struct {
|
||||
AES_state rk[11];
|
||||
} AES128_ctx;
|
||||
|
||||
typedef struct {
|
||||
AES_state rk[13];
|
||||
} AES192_ctx;
|
||||
|
||||
typedef struct {
|
||||
AES_state rk[15];
|
||||
} AES256_ctx;
|
||||
|
||||
void AES128_init(AES128_ctx* ctx, const unsigned char* key16);
|
||||
void AES128_encrypt(const AES128_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16);
|
||||
void AES128_decrypt(const AES128_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16);
|
||||
|
||||
void AES192_init(AES192_ctx* ctx, const unsigned char* key24);
|
||||
void AES192_encrypt(const AES192_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16);
|
||||
void AES192_decrypt(const AES192_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16);
|
||||
|
||||
void AES256_init(AES256_ctx* ctx, const unsigned char* key32);
|
||||
void AES256_encrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16);
|
||||
void AES256_decrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,110 @@
|
|||
/*********************************************************************
|
||||
* Copyright (c) 2016 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#include "ctaes.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct {
|
||||
int keysize;
|
||||
const char* key;
|
||||
const char* plain;
|
||||
const char* cipher;
|
||||
} ctaes_test;
|
||||
|
||||
static const ctaes_test ctaes_tests[] = {
|
||||
/* AES test vectors from FIPS 197. */
|
||||
{128, "000102030405060708090a0b0c0d0e0f", "00112233445566778899aabbccddeeff", "69c4e0d86a7b0430d8cdb78070b4c55a"},
|
||||
{192, "000102030405060708090a0b0c0d0e0f1011121314151617", "00112233445566778899aabbccddeeff", "dda97ca4864cdfe06eaf70a0ec0d7191"},
|
||||
{256, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "00112233445566778899aabbccddeeff", "8ea2b7ca516745bfeafc49904b496089"},
|
||||
|
||||
/* AES-ECB test vectors from NIST sp800-38a. */
|
||||
{128, "2b7e151628aed2a6abf7158809cf4f3c", "6bc1bee22e409f96e93d7e117393172a", "3ad77bb40d7a3660a89ecaf32466ef97"},
|
||||
{128, "2b7e151628aed2a6abf7158809cf4f3c", "ae2d8a571e03ac9c9eb76fac45af8e51", "f5d3d58503b9699de785895a96fdbaaf"},
|
||||
{128, "2b7e151628aed2a6abf7158809cf4f3c", "30c81c46a35ce411e5fbc1191a0a52ef", "43b1cd7f598ece23881b00e3ed030688"},
|
||||
{128, "2b7e151628aed2a6abf7158809cf4f3c", "f69f2445df4f9b17ad2b417be66c3710", "7b0c785e27e8ad3f8223207104725dd4"},
|
||||
{192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "6bc1bee22e409f96e93d7e117393172a", "bd334f1d6e45f25ff712a214571fa5cc"},
|
||||
{192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "ae2d8a571e03ac9c9eb76fac45af8e51", "974104846d0ad3ad7734ecb3ecee4eef"},
|
||||
{192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "30c81c46a35ce411e5fbc1191a0a52ef", "ef7afd2270e2e60adce0ba2face6444e"},
|
||||
{192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "f69f2445df4f9b17ad2b417be66c3710", "9a4b41ba738d6c72fb16691603c18e0e"},
|
||||
{256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "6bc1bee22e409f96e93d7e117393172a", "f3eed1bdb5d2a03c064b5a7e3db181f8"},
|
||||
{256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "ae2d8a571e03ac9c9eb76fac45af8e51", "591ccb10d410ed26dc5ba74a31362870"},
|
||||
{256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "30c81c46a35ce411e5fbc1191a0a52ef", "b6ed21b99ca6f4f9f153e7b1beafed1d"},
|
||||
{256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "f69f2445df4f9b17ad2b417be66c3710", "23304b7a39f9f3ff067d8d8f9e24ecc7"}
|
||||
};
|
||||
|
||||
static void from_hex(unsigned char* data, int len, const char* hex) {
|
||||
int p;
|
||||
for (p = 0; p < len; p++) {
|
||||
int v = 0;
|
||||
int n;
|
||||
for (n = 0; n < 2; n++) {
|
||||
assert((*hex >= '0' && *hex <= '9') || (*hex >= 'a' && *hex <= 'f'));
|
||||
if (*hex >= '0' && *hex <= '9') {
|
||||
v |= (*hex - '0') << (4 * (1 - n));
|
||||
} else {
|
||||
v |= (*hex - 'a' + 10) << (4 * (1 - n));
|
||||
}
|
||||
hex++;
|
||||
}
|
||||
*(data++) = v;
|
||||
}
|
||||
assert(*hex == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int i;
|
||||
int fail = 0;
|
||||
for (i = 0; i < sizeof(ctaes_tests) / sizeof(ctaes_tests[0]); i++) {
|
||||
unsigned char key[32], plain[16], cipher[16], ciphered[16], deciphered[16];
|
||||
const ctaes_test* test = &ctaes_tests[i];
|
||||
assert(test->keysize == 128 || test->keysize == 192 || test->keysize == 256);
|
||||
from_hex(plain, 16, test->plain);
|
||||
from_hex(cipher, 16, test->cipher);
|
||||
switch (test->keysize) {
|
||||
case 128: {
|
||||
AES128_ctx ctx;
|
||||
from_hex(key, 16, test->key);
|
||||
AES128_init(&ctx, key);
|
||||
AES128_encrypt(&ctx, 1, ciphered, plain);
|
||||
AES128_decrypt(&ctx, 1, deciphered, cipher);
|
||||
break;
|
||||
}
|
||||
case 192: {
|
||||
AES192_ctx ctx;
|
||||
from_hex(key, 24, test->key);
|
||||
AES192_init(&ctx, key);
|
||||
AES192_encrypt(&ctx, 1, ciphered, plain);
|
||||
AES192_decrypt(&ctx, 1, deciphered, cipher);
|
||||
break;
|
||||
}
|
||||
case 256: {
|
||||
AES256_ctx ctx;
|
||||
from_hex(key, 32, test->key);
|
||||
AES256_init(&ctx, key);
|
||||
AES256_encrypt(&ctx, 1, ciphered, plain);
|
||||
AES256_decrypt(&ctx, 1, deciphered, cipher);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (memcmp(cipher, ciphered, 16)) {
|
||||
fprintf(stderr, "E(key=\"%s\", plain=\"%s\") != \"%s\"\n", test->key, test->plain, test->cipher);
|
||||
fail++;
|
||||
}
|
||||
if (memcmp(plain, deciphered, 16)) {
|
||||
fprintf(stderr, "D(key=\"%s\", cipher=\"%s\") != \"%s\"\n", test->key, test->cipher, test->plain);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (fail == 0) {
|
||||
fprintf(stderr, "All tests successful\n");
|
||||
} else {
|
||||
fprintf(stderr, "%i tests failed\n", fail);
|
||||
}
|
||||
return (fail != 0);
|
||||
}
|
34
src/key.cpp
34
src/key.cpp
|
@ -158,8 +158,8 @@ bool CKey::Check(const unsigned char *vch) {
|
|||
|
||||
void CKey::MakeNewKey(bool fCompressedIn) {
|
||||
do {
|
||||
GetRandBytes(vch, sizeof(vch));
|
||||
} while (!Check(vch));
|
||||
GetRandBytes(keydata.data(), keydata.size());
|
||||
} while (!Check(keydata.data()));
|
||||
fValid = true;
|
||||
fCompressed = fCompressedIn;
|
||||
}
|
||||
|
@ -257,20 +257,18 @@ bool CKey::Load(CPrivKey &privkey, CPubKey &vchPubKey, bool fSkipCheck=false) {
|
|||
bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const {
|
||||
assert(IsValid());
|
||||
assert(IsCompressed());
|
||||
unsigned char out[64];
|
||||
LockObject(out);
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
||||
if ((nChild >> 31) == 0) {
|
||||
CPubKey pubkey = GetPubKey();
|
||||
assert(pubkey.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE);
|
||||
BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, out);
|
||||
BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, vout.data());
|
||||
} else {
|
||||
assert(size() == 32);
|
||||
BIP32Hash(cc, nChild, 0, begin(), out);
|
||||
BIP32Hash(cc, nChild, 0, begin(), vout.data());
|
||||
}
|
||||
memcpy(ccChild.begin(), out+32, 32);
|
||||
memcpy(ccChild.begin(), vout.data()+32, 32);
|
||||
memcpy((unsigned char*)keyChild.begin(), begin(), 32);
|
||||
bool ret = secp256k1_ec_privkey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), out);
|
||||
UnlockObject(out);
|
||||
bool ret = secp256k1_ec_privkey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
|
||||
keyChild.fCompressed = true;
|
||||
keyChild.fValid = ret;
|
||||
return ret;
|
||||
|
@ -286,12 +284,10 @@ bool CExtKey::Derive(CExtKey &out, unsigned int nChild) const {
|
|||
|
||||
void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) {
|
||||
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
|
||||
unsigned char out[64];
|
||||
LockObject(out);
|
||||
CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(out);
|
||||
key.Set(&out[0], &out[32], true);
|
||||
memcpy(chaincode.begin(), &out[32], 32);
|
||||
UnlockObject(out);
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
||||
CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data());
|
||||
key.Set(&vout[0], &vout[32], true);
|
||||
memcpy(chaincode.begin(), &vout[32], 32);
|
||||
nDepth = 0;
|
||||
nChild = 0;
|
||||
memset(vchFingerprint, 0, sizeof(vchFingerprint));
|
||||
|
@ -341,12 +337,10 @@ void ECC_Start() {
|
|||
|
||||
{
|
||||
// Pass in a random blinding seed to the secp256k1 context.
|
||||
unsigned char seed[32];
|
||||
LockObject(seed);
|
||||
GetRandBytes(seed, 32);
|
||||
bool ret = secp256k1_context_randomize(ctx, seed);
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vseed(32);
|
||||
GetRandBytes(vseed.data(), 32);
|
||||
bool ret = secp256k1_context_randomize(ctx, vseed.data());
|
||||
assert(ret);
|
||||
UnlockObject(seed);
|
||||
}
|
||||
|
||||
secp256k1_context_sign = ctx;
|
||||
|
|
41
src/key.h
41
src/key.h
|
@ -49,7 +49,7 @@ private:
|
|||
bool fCompressed;
|
||||
|
||||
//! The actual byte data
|
||||
unsigned char vch[32];
|
||||
std::vector<unsigned char, secure_allocator<unsigned char> > keydata;
|
||||
|
||||
//! Check whether the 32-byte array pointed to be vch is valid keydata.
|
||||
bool static Check(const unsigned char* vch);
|
||||
|
@ -58,38 +58,30 @@ public:
|
|||
//! Construct an invalid private key.
|
||||
CKey() : fValid(false), fCompressed(false)
|
||||
{
|
||||
LockObject(vch);
|
||||
}
|
||||
|
||||
//! Copy constructor. This is necessary because of memlocking.
|
||||
CKey(const CKey& secret) : fValid(secret.fValid), fCompressed(secret.fCompressed)
|
||||
{
|
||||
LockObject(vch);
|
||||
memcpy(vch, secret.vch, sizeof(vch));
|
||||
// Important: vch must be 32 bytes in length to not break serialization
|
||||
keydata.resize(32);
|
||||
}
|
||||
|
||||
//! Destructor (again necessary because of memlocking).
|
||||
~CKey()
|
||||
{
|
||||
UnlockObject(vch);
|
||||
}
|
||||
|
||||
friend bool operator==(const CKey& a, const CKey& b)
|
||||
{
|
||||
return a.fCompressed == b.fCompressed && a.size() == b.size() &&
|
||||
memcmp(&a.vch[0], &b.vch[0], a.size()) == 0;
|
||||
return a.fCompressed == b.fCompressed &&
|
||||
a.size() == b.size() &&
|
||||
memcmp(a.keydata.data(), b.keydata.data(), a.size()) == 0;
|
||||
}
|
||||
|
||||
//! Initialize using begin and end iterators to byte data.
|
||||
template <typename T>
|
||||
void Set(const T pbegin, const T pend, bool fCompressedIn)
|
||||
{
|
||||
if (pend - pbegin != 32) {
|
||||
if (size_t(pend - pbegin) != keydata.size()) {
|
||||
fValid = false;
|
||||
return;
|
||||
}
|
||||
if (Check(&pbegin[0])) {
|
||||
memcpy(vch, (unsigned char*)&pbegin[0], 32);
|
||||
} else if (Check(&pbegin[0])) {
|
||||
memcpy(keydata.data(), (unsigned char*)&pbegin[0], keydata.size());
|
||||
fValid = true;
|
||||
fCompressed = fCompressedIn;
|
||||
} else {
|
||||
|
@ -98,9 +90,9 @@ public:
|
|||
}
|
||||
|
||||
//! Simple read-only vector-like interface.
|
||||
unsigned int size() const { return (fValid ? 32 : 0); }
|
||||
const unsigned char* begin() const { return vch; }
|
||||
const unsigned char* end() const { return vch + size(); }
|
||||
unsigned int size() const { return (fValid ? keydata.size() : 0); }
|
||||
const unsigned char* begin() const { return keydata.data(); }
|
||||
const unsigned char* end() const { return keydata.data() + size(); }
|
||||
|
||||
//! Check whether this private key is valid.
|
||||
bool IsValid() const { return fValid; }
|
||||
|
@ -116,7 +108,7 @@ public:
|
|||
|
||||
/**
|
||||
* Convert the private key to a CPrivKey (serialized OpenSSL private key data).
|
||||
* This is expensive.
|
||||
* This is expensive.
|
||||
*/
|
||||
CPrivKey GetPrivKey() const;
|
||||
|
||||
|
@ -166,8 +158,11 @@ struct CExtKey {
|
|||
|
||||
friend bool operator==(const CExtKey& a, const CExtKey& b)
|
||||
{
|
||||
return a.nDepth == b.nDepth && memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], 4) == 0 && a.nChild == b.nChild &&
|
||||
a.chaincode == b.chaincode && a.key == b.key;
|
||||
return a.nDepth == b.nDepth &&
|
||||
memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], sizeof(vchFingerprint)) == 0 &&
|
||||
a.nChild == b.nChild &&
|
||||
a.chaincode == b.chaincode &&
|
||||
a.key == b.key;
|
||||
}
|
||||
|
||||
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
||||
|
|
|
@ -164,7 +164,7 @@ public:
|
|||
|
||||
/*
|
||||
* Check syntactic correctness.
|
||||
*
|
||||
*
|
||||
* Note that this is consensus critical as CheckSig() calls it!
|
||||
*/
|
||||
bool IsValid() const
|
||||
|
@ -211,8 +211,11 @@ struct CExtPubKey {
|
|||
|
||||
friend bool operator==(const CExtPubKey &a, const CExtPubKey &b)
|
||||
{
|
||||
return a.nDepth == b.nDepth && memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], 4) == 0 && a.nChild == b.nChild &&
|
||||
a.chaincode == b.chaincode && a.pubkey == b.pubkey;
|
||||
return a.nDepth == b.nDepth &&
|
||||
memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], sizeof(vchFingerprint)) == 0 &&
|
||||
a.nChild == b.nChild &&
|
||||
a.chaincode == b.chaincode &&
|
||||
a.pubkey == b.pubkey;
|
||||
}
|
||||
|
||||
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
||||
|
|
|
@ -1130,10 +1130,53 @@ UniValue getspentinfo(const UniValue& params, bool fHelp)
|
|||
return obj;
|
||||
}
|
||||
|
||||
static UniValue RPCLockedMemoryInfo()
|
||||
{
|
||||
LockedPool::Stats stats = LockedPoolManager::Instance().stats();
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("used", uint64_t(stats.used)));
|
||||
obj.push_back(Pair("free", uint64_t(stats.free)));
|
||||
obj.push_back(Pair("total", uint64_t(stats.total)));
|
||||
obj.push_back(Pair("locked", uint64_t(stats.locked)));
|
||||
obj.push_back(Pair("chunks_used", uint64_t(stats.chunks_used)));
|
||||
obj.push_back(Pair("chunks_free", uint64_t(stats.chunks_free)));
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue getmemoryinfo(const UniValue& params, bool fHelp)
|
||||
{
|
||||
/* Please, avoid using the word "pool" here in the RPC interface or help,
|
||||
* as users will undoubtedly confuse it with the other "memory pool"
|
||||
*/
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"getmemoryinfo\n"
|
||||
"Returns an object containing information about memory usage.\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"locked\": { (json object) Information about locked memory manager\n"
|
||||
" \"used\": xxxxx, (numeric) Number of bytes used\n"
|
||||
" \"free\": xxxxx, (numeric) Number of bytes available in current arenas\n"
|
||||
" \"total\": xxxxxxx, (numeric) Total number of bytes managed\n"
|
||||
" \"locked\": xxxxxx, (numeric) Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk.\n"
|
||||
" \"chunks_used\": xxxxx, (numeric) Number allocated chunks\n"
|
||||
" \"chunks_free\": xxxxx, (numeric) Number unused chunks\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getmemoryinfo", "")
|
||||
+ HelpExampleRpc("getmemoryinfo", "")
|
||||
);
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("locked", RPCLockedMemoryInfo()));
|
||||
return obj;
|
||||
}
|
||||
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) okSafeMode
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
{ "control", "getinfo", &getinfo, true }, /* uses wallet if enabled */
|
||||
{ "control", "getmemoryinfo", &getmemoryinfo, true },
|
||||
{ "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */
|
||||
{ "util", "z_validateaddress", &z_validateaddress, true }, /* uses wallet if enabled */
|
||||
{ "util", "createmultisig", &createmultisig, true },
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
#ifndef BITCOIN_SUPPORT_ALLOCATORS_SECURE_H
|
||||
#define BITCOIN_SUPPORT_ALLOCATORS_SECURE_H
|
||||
|
||||
#include "support/pagelocker.h"
|
||||
#include "support/lockedpool.h"
|
||||
#include "support/cleanse.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -39,20 +40,15 @@ struct secure_allocator : public std::allocator<T> {
|
|||
|
||||
T* allocate(std::size_t n, const void* hint = 0)
|
||||
{
|
||||
T* p;
|
||||
p = std::allocator<T>::allocate(n, hint);
|
||||
if (p != NULL)
|
||||
LockedPageManager::Instance().LockRange(p, sizeof(T) * n);
|
||||
return p;
|
||||
return static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (p != NULL) {
|
||||
memory_cleanse(p, sizeof(T) * n);
|
||||
LockedPageManager::Instance().UnlockRange(p, sizeof(T) * n);
|
||||
}
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
LockedPoolManager::Instance().free(p);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "support/lockedpool.h"
|
||||
#include "support/cleanse.h"
|
||||
|
||||
#if defined(HAVE_CONFIG_H)
|
||||
#include "config/bitcoin-config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h> // for mmap
|
||||
#include <sys/resource.h> // for getrlimit
|
||||
#include <limits.h> // for PAGESIZE
|
||||
#include <unistd.h> // for sysconf
|
||||
#endif
|
||||
|
||||
LockedPoolManager* LockedPoolManager::_instance = NULL;
|
||||
std::once_flag LockedPoolManager::init_flag;
|
||||
|
||||
/*******************************************************************************/
|
||||
// Utilities
|
||||
//
|
||||
/** Align up to power of 2 */
|
||||
static inline size_t align_up(size_t x, size_t align)
|
||||
{
|
||||
return (x + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
// Implementation: Arena
|
||||
|
||||
Arena::Arena(void *base_in, size_t size_in, size_t alignment_in):
|
||||
base(static_cast<char*>(base_in)), end(static_cast<char*>(base_in) + size_in), alignment(alignment_in)
|
||||
{
|
||||
// Start with one free chunk that covers the entire arena
|
||||
chunks.emplace(base, Chunk(size_in, false));
|
||||
}
|
||||
|
||||
Arena::~Arena()
|
||||
{
|
||||
}
|
||||
|
||||
void* Arena::alloc(size_t size)
|
||||
{
|
||||
// Round to next multiple of alignment
|
||||
size = align_up(size, alignment);
|
||||
|
||||
// Don't handle zero-sized chunks, or those bigger than MAX_SIZE
|
||||
if (size == 0 || size >= Chunk::MAX_SIZE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto& chunk: chunks) {
|
||||
if (!chunk.second.isInUse() && size <= chunk.second.getSize()) {
|
||||
char* base = chunk.first;
|
||||
size_t leftover = chunk.second.getSize() - size;
|
||||
if (leftover > 0) { // Split chunk
|
||||
chunks.emplace(base + size, Chunk(leftover, false));
|
||||
chunk.second.setSize(size);
|
||||
}
|
||||
chunk.second.setInUse(true);
|
||||
return reinterpret_cast<void*>(base);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Arena::free(void *ptr)
|
||||
{
|
||||
// Freeing the NULL pointer is OK.
|
||||
if (ptr == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto i = chunks.find(static_cast<char*>(ptr));
|
||||
if (i == chunks.end() || !i->second.isInUse()) {
|
||||
throw std::runtime_error("Arena: invalid or double free");
|
||||
}
|
||||
|
||||
i->second.setInUse(false);
|
||||
|
||||
if (i != chunks.begin()) { // Absorb into previous chunk if exists and free
|
||||
auto prev = i;
|
||||
--prev;
|
||||
if (!prev->second.isInUse()) {
|
||||
// Absorb current chunk size into previous chunk.
|
||||
prev->second.setSize(prev->second.getSize() + i->second.getSize());
|
||||
// Erase current chunk. Erasing does not invalidate current
|
||||
// iterators for a map, except for that pointing to the object
|
||||
// itself, which will be overwritten in the next statement.
|
||||
chunks.erase(i);
|
||||
// From here on, the previous chunk is our current chunk.
|
||||
i = prev;
|
||||
}
|
||||
}
|
||||
auto next = i;
|
||||
++next;
|
||||
if (next != chunks.end()) { // Absorb next chunk if exists and free
|
||||
if (!next->second.isInUse()) {
|
||||
// Absurb next chunk size into current chunk
|
||||
i->second.setSize(i->second.getSize() + next->second.getSize());
|
||||
// Erase next chunk.
|
||||
chunks.erase(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Arena::Stats Arena::stats() const
|
||||
{
|
||||
Arena::Stats r;
|
||||
r.used = r.free = r.total = r.chunks_used = r.chunks_free = 0;
|
||||
for (const auto& chunk: chunks) {
|
||||
if (chunk.second.isInUse()) {
|
||||
r.used += chunk.second.getSize();
|
||||
r.chunks_used += 1;
|
||||
} else {
|
||||
r.free += chunk.second.getSize();
|
||||
r.chunks_free += 1;
|
||||
}
|
||||
r.total += chunk.second.getSize();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
#ifdef ARENA_DEBUG
|
||||
void Arena::walk() const
|
||||
{
|
||||
for (const auto& chunk: chunks) {
|
||||
std::cout <<
|
||||
"0x" << std::hex << std::setw(16) << std::setfill('0') << chunk.first <<
|
||||
" 0x" << std::hex << std::setw(16) << std::setfill('0') << chunk.second.getSize() <<
|
||||
" 0x" << chunk.second.isInUse() << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*******************************************************************************/
|
||||
// Implementation: Win32LockedPageAllocator
|
||||
|
||||
#ifdef WIN32
|
||||
/** LockedPageAllocator specialized for Windows.
|
||||
*/
|
||||
class Win32LockedPageAllocator: public LockedPageAllocator
|
||||
{
|
||||
public:
|
||||
Win32LockedPageAllocator();
|
||||
void* AllocateLocked(size_t len, bool *lockingSuccess);
|
||||
void FreeLocked(void* addr, size_t len);
|
||||
size_t GetLimit();
|
||||
private:
|
||||
size_t page_size;
|
||||
};
|
||||
|
||||
Win32LockedPageAllocator::Win32LockedPageAllocator()
|
||||
{
|
||||
// Determine system page size in bytes
|
||||
SYSTEM_INFO sSysInfo;
|
||||
GetSystemInfo(&sSysInfo);
|
||||
page_size = sSysInfo.dwPageSize;
|
||||
}
|
||||
void *Win32LockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess)
|
||||
{
|
||||
len = align_up(len, page_size);
|
||||
void *addr = VirtualAlloc(nullptr, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (addr) {
|
||||
// VirtualLock is used to attempt to keep keying material out of swap. Note
|
||||
// that it does not provide this as a guarantee, but, in practice, memory
|
||||
// that has been VirtualLock'd almost never gets written to the pagefile
|
||||
// except in rare circumstances where memory is extremely low.
|
||||
*lockingSuccess = VirtualLock(const_cast<void*>(addr), len) != 0;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
void Win32LockedPageAllocator::FreeLocked(void* addr, size_t len)
|
||||
{
|
||||
len = align_up(len, page_size);
|
||||
memory_cleanse(addr, len);
|
||||
VirtualUnlock(const_cast<void*>(addr), len);
|
||||
}
|
||||
|
||||
size_t Win32LockedPageAllocator::GetLimit()
|
||||
{
|
||||
// TODO is there a limit on windows, how to get it?
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*******************************************************************************/
|
||||
// Implementation: PosixLockedPageAllocator
|
||||
|
||||
#ifndef WIN32
|
||||
/** LockedPageAllocator specialized for OSes that don't try to be
|
||||
* special snowflakes.
|
||||
*/
|
||||
class PosixLockedPageAllocator: public LockedPageAllocator
|
||||
{
|
||||
public:
|
||||
PosixLockedPageAllocator();
|
||||
void* AllocateLocked(size_t len, bool *lockingSuccess);
|
||||
void FreeLocked(void* addr, size_t len);
|
||||
size_t GetLimit();
|
||||
private:
|
||||
size_t page_size;
|
||||
};
|
||||
|
||||
PosixLockedPageAllocator::PosixLockedPageAllocator()
|
||||
{
|
||||
// Determine system page size in bytes
|
||||
#if defined(PAGESIZE) // defined in limits.h
|
||||
page_size = PAGESIZE;
|
||||
#else // assume some POSIX OS
|
||||
page_size = sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
}
|
||||
void *PosixLockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess)
|
||||
{
|
||||
void *addr;
|
||||
len = align_up(len, page_size);
|
||||
addr = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||
if (addr) {
|
||||
*lockingSuccess = mlock(addr, len) == 0;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
void PosixLockedPageAllocator::FreeLocked(void* addr, size_t len)
|
||||
{
|
||||
len = align_up(len, page_size);
|
||||
memory_cleanse(addr, len);
|
||||
munlock(addr, len);
|
||||
munmap(addr, len);
|
||||
}
|
||||
size_t PosixLockedPageAllocator::GetLimit()
|
||||
{
|
||||
#ifdef RLIMIT_MEMLOCK
|
||||
struct rlimit rlim;
|
||||
if (getrlimit(RLIMIT_MEMLOCK, &rlim) == 0) {
|
||||
if (rlim.rlim_cur != RLIM_INFINITY) {
|
||||
return rlim.rlim_cur;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*******************************************************************************/
|
||||
// Implementation: LockedPool
|
||||
|
||||
LockedPool::LockedPool(std::unique_ptr<LockedPageAllocator> allocator_in, LockingFailed_Callback lf_cb_in):
|
||||
allocator(std::move(allocator_in)), lf_cb(lf_cb_in), cumulative_bytes_locked(0)
|
||||
{
|
||||
}
|
||||
|
||||
LockedPool::~LockedPool()
|
||||
{
|
||||
}
|
||||
void* LockedPool::alloc(size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
// Try allocating from each current arena
|
||||
for (auto &arena: arenas) {
|
||||
void *addr = arena.alloc(size);
|
||||
if (addr) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
// If that fails, create a new one
|
||||
if (new_arena(ARENA_SIZE, ARENA_ALIGN)) {
|
||||
return arenas.back().alloc(size);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LockedPool::free(void *ptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
// TODO we can do better than this linear search by keeping a map of arena
|
||||
// extents to arena, and looking up the address.
|
||||
for (auto &arena: arenas) {
|
||||
if (arena.addressInArena(ptr)) {
|
||||
arena.free(ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("LockedPool: invalid address not pointing to any arena");
|
||||
}
|
||||
|
||||
LockedPool::Stats LockedPool::stats() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
LockedPool::Stats r;
|
||||
r.used = r.free = r.total = r.chunks_used = r.chunks_free = 0;
|
||||
r.locked = cumulative_bytes_locked;
|
||||
for (const auto &arena: arenas) {
|
||||
Arena::Stats i = arena.stats();
|
||||
r.used += i.used;
|
||||
r.free += i.free;
|
||||
r.total += i.total;
|
||||
r.chunks_used += i.chunks_used;
|
||||
r.chunks_free += i.chunks_free;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool LockedPool::new_arena(size_t size, size_t align)
|
||||
{
|
||||
bool locked;
|
||||
// If this is the first arena, handle this specially: Cap the upper size
|
||||
// by the process limit. This makes sure that the first arena will at least
|
||||
// be locked. An exception to this is if the process limit is 0:
|
||||
// in this case no memory can be locked at all so we'll skip past this logic.
|
||||
if (arenas.empty()) {
|
||||
size_t limit = allocator->GetLimit();
|
||||
if (limit > 0) {
|
||||
size = std::min(size, limit);
|
||||
}
|
||||
}
|
||||
void *addr = allocator->AllocateLocked(size, &locked);
|
||||
if (!addr) {
|
||||
return false;
|
||||
}
|
||||
if (locked) {
|
||||
cumulative_bytes_locked += size;
|
||||
} else if (lf_cb) { // Call the locking-failed callback if locking failed
|
||||
if (!lf_cb()) { // If the callback returns false, free the memory and fail, otherwise consider the user warned and proceed.
|
||||
allocator->FreeLocked(addr, size);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
arenas.emplace_back(allocator.get(), addr, size, align);
|
||||
return true;
|
||||
}
|
||||
|
||||
LockedPool::LockedPageArena::LockedPageArena(LockedPageAllocator *allocator_in, void *base_in, size_t size_in, size_t align_in):
|
||||
Arena(base_in, size_in, align_in), base(base_in), size(size_in), allocator(allocator_in)
|
||||
{
|
||||
}
|
||||
LockedPool::LockedPageArena::~LockedPageArena()
|
||||
{
|
||||
allocator->FreeLocked(base, size);
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
// Implementation: LockedPoolManager
|
||||
//
|
||||
LockedPoolManager::LockedPoolManager(std::unique_ptr<LockedPageAllocator> allocator):
|
||||
LockedPool(std::move(allocator), &LockedPoolManager::LockingFailed)
|
||||
{
|
||||
}
|
||||
|
||||
bool LockedPoolManager::LockingFailed()
|
||||
{
|
||||
// TODO: log something but how? without including util.h
|
||||
return true;
|
||||
}
|
||||
|
||||
void LockedPoolManager::CreateInstance()
|
||||
{
|
||||
// Using a local static instance guarantees that the object is initialized
|
||||
// when it's first needed and also deinitialized after all objects that use
|
||||
// it are done with it. I can think of one unlikely scenario where we may
|
||||
// have a static deinitialization order/problem, but the check in
|
||||
// LockedPoolManagerBase's destructor helps us detect if that ever happens.
|
||||
#ifdef WIN32
|
||||
std::unique_ptr<LockedPageAllocator> allocator(new Win32LockedPageAllocator());
|
||||
#else
|
||||
std::unique_ptr<LockedPageAllocator> allocator(new PosixLockedPageAllocator());
|
||||
#endif
|
||||
static LockedPoolManager instance(std::move(allocator));
|
||||
LockedPoolManager::_instance = &instance;
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_SUPPORT_LOCKEDPOOL_H
|
||||
#define BITCOIN_SUPPORT_LOCKEDPOOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* OS-dependent allocation and deallocation of locked/pinned memory pages.
|
||||
* Abstract base class.
|
||||
*/
|
||||
class LockedPageAllocator
|
||||
{
|
||||
public:
|
||||
virtual ~LockedPageAllocator() {}
|
||||
/** Allocate and lock memory pages.
|
||||
* If len is not a multiple of the system page size, it is rounded up.
|
||||
* Returns 0 in case of allocation failure.
|
||||
*
|
||||
* If locking the memory pages could not be accomplished it will still
|
||||
* return the memory, however the lockingSuccess flag will be false.
|
||||
* lockingSuccess is undefined if the allocation fails.
|
||||
*/
|
||||
virtual void* AllocateLocked(size_t len, bool *lockingSuccess) = 0;
|
||||
|
||||
/** Unlock and free memory pages.
|
||||
* Clear the memory before unlocking.
|
||||
*/
|
||||
virtual void FreeLocked(void* addr, size_t len) = 0;
|
||||
|
||||
/** Get the total limit on the amount of memory that may be locked by this
|
||||
* process, in bytes. Return size_t max if there is no limit or the limit
|
||||
* is unknown. Return 0 if no memory can be locked at all.
|
||||
*/
|
||||
virtual size_t GetLimit() = 0;
|
||||
};
|
||||
|
||||
/* An arena manages a contiguous region of memory by dividing it into
|
||||
* chunks.
|
||||
*/
|
||||
class Arena
|
||||
{
|
||||
public:
|
||||
Arena(void *base, size_t size, size_t alignment);
|
||||
virtual ~Arena();
|
||||
|
||||
/** A chunk of memory.
|
||||
*/
|
||||
struct Chunk
|
||||
{
|
||||
/** Most significant bit of size_t. This is used to mark
|
||||
* in-usedness of chunk.
|
||||
*/
|
||||
const static size_t SIZE_MSB = 1LLU << ((sizeof(size_t)*8)-1);
|
||||
/** Maximum size of a chunk */
|
||||
const static size_t MAX_SIZE = SIZE_MSB - 1;
|
||||
|
||||
Chunk(size_t size_in, bool used_in):
|
||||
size(size_in | (used_in ? SIZE_MSB : 0)) {}
|
||||
|
||||
bool isInUse() const { return size & SIZE_MSB; }
|
||||
void setInUse(bool used_in) { size = (size & ~SIZE_MSB) | (used_in ? SIZE_MSB : 0); }
|
||||
size_t getSize() const { return size & ~SIZE_MSB; }
|
||||
void setSize(size_t size_in) { size = (size & SIZE_MSB) | size_in; }
|
||||
private:
|
||||
size_t size;
|
||||
};
|
||||
/** Memory statistics. */
|
||||
struct Stats
|
||||
{
|
||||
size_t used;
|
||||
size_t free;
|
||||
size_t total;
|
||||
size_t chunks_used;
|
||||
size_t chunks_free;
|
||||
};
|
||||
|
||||
/** Allocate size bytes from this arena.
|
||||
* Returns pointer on success, or 0 if memory is full or
|
||||
* the application tried to allocate 0 bytes.
|
||||
*/
|
||||
void* alloc(size_t size);
|
||||
|
||||
/** Free a previously allocated chunk of memory.
|
||||
* Freeing the zero pointer has no effect.
|
||||
* Raises std::runtime_error in case of error.
|
||||
*/
|
||||
void free(void *ptr);
|
||||
|
||||
/** Get arena usage statistics */
|
||||
Stats stats() const;
|
||||
|
||||
#ifdef ARENA_DEBUG
|
||||
void walk() const;
|
||||
#endif
|
||||
|
||||
/** Return whether a pointer points inside this arena.
|
||||
* This returns base <= ptr < (base+size) so only use it for (inclusive)
|
||||
* chunk starting addresses.
|
||||
*/
|
||||
bool addressInArena(void *ptr) const { return ptr >= base && ptr < end; }
|
||||
private:
|
||||
Arena(const Arena& other) = delete; // non construction-copyable
|
||||
Arena& operator=(const Arena&) = delete; // non copyable
|
||||
|
||||
/** Map of chunk address to chunk information. This class makes use of the
|
||||
* sorted order to merge previous and next chunks during deallocation.
|
||||
*/
|
||||
std::map<char*, Chunk> chunks;
|
||||
/** Base address of arena */
|
||||
char* base;
|
||||
/** End address of arena */
|
||||
char* end;
|
||||
/** Minimum chunk alignment */
|
||||
size_t alignment;
|
||||
};
|
||||
|
||||
/** Pool for locked memory chunks.
|
||||
*
|
||||
* To avoid sensitive key data from being swapped to disk, the memory in this pool
|
||||
* is locked/pinned.
|
||||
*
|
||||
* An arena manages a contiguous region of memory. The pool starts out with one arena
|
||||
* but can grow to multiple arenas if the need arises.
|
||||
*
|
||||
* Unlike a normal C heap, the administrative structures are seperate from the managed
|
||||
* memory. This has been done as the sizes and bases of objects are not in themselves sensitive
|
||||
* information, as to conserve precious locked memory. In some operating systems
|
||||
* the amount of memory that can be locked is small.
|
||||
*/
|
||||
class LockedPool
|
||||
{
|
||||
public:
|
||||
/** Size of one arena of locked memory. This is a compromise.
|
||||
* Do not set this too low, as managing many arenas will increase
|
||||
* allocation and deallocation overhead. Setting it too high allocates
|
||||
* more locked memory from the OS than strictly necessary.
|
||||
*/
|
||||
static const size_t ARENA_SIZE = 256*1024;
|
||||
/** Chunk alignment. Another compromise. Setting this too high will waste
|
||||
* memory, setting it too low will facilitate fragmentation.
|
||||
*/
|
||||
static const size_t ARENA_ALIGN = 16;
|
||||
|
||||
/** Callback when allocation succeeds but locking fails.
|
||||
*/
|
||||
typedef bool (*LockingFailed_Callback)();
|
||||
|
||||
/** Memory statistics. */
|
||||
struct Stats
|
||||
{
|
||||
size_t used;
|
||||
size_t free;
|
||||
size_t total;
|
||||
size_t locked;
|
||||
size_t chunks_used;
|
||||
size_t chunks_free;
|
||||
};
|
||||
|
||||
/** Create a new LockedPool. This takes ownership of the MemoryPageLocker,
|
||||
* you can only instantiate this with LockedPool(std::move(...)).
|
||||
*
|
||||
* The second argument is an optional callback when locking a newly allocated arena failed.
|
||||
* If this callback is provided and returns false, the allocation fails (hard fail), if
|
||||
* it returns true the allocation proceeds, but it could warn.
|
||||
*/
|
||||
LockedPool(std::unique_ptr<LockedPageAllocator> allocator, LockingFailed_Callback lf_cb_in = 0);
|
||||
~LockedPool();
|
||||
|
||||
/** Allocate size bytes from this arena.
|
||||
* Returns pointer on success, or 0 if memory is full or
|
||||
* the application tried to allocate 0 bytes.
|
||||
*/
|
||||
void* alloc(size_t size);
|
||||
|
||||
/** Free a previously allocated chunk of memory.
|
||||
* Freeing the zero pointer has no effect.
|
||||
* Raises std::runtime_error in case of error.
|
||||
*/
|
||||
void free(void *ptr);
|
||||
|
||||
/** Get pool usage statistics */
|
||||
Stats stats() const;
|
||||
private:
|
||||
LockedPool(const LockedPool& other) = delete; // non construction-copyable
|
||||
LockedPool& operator=(const LockedPool&) = delete; // non copyable
|
||||
|
||||
std::unique_ptr<LockedPageAllocator> allocator;
|
||||
|
||||
/** Create an arena from locked pages */
|
||||
class LockedPageArena: public Arena
|
||||
{
|
||||
public:
|
||||
LockedPageArena(LockedPageAllocator *alloc_in, void *base_in, size_t size, size_t align);
|
||||
~LockedPageArena();
|
||||
private:
|
||||
void *base;
|
||||
size_t size;
|
||||
LockedPageAllocator *allocator;
|
||||
};
|
||||
|
||||
bool new_arena(size_t size, size_t align);
|
||||
|
||||
std::list<LockedPageArena> arenas;
|
||||
LockingFailed_Callback lf_cb;
|
||||
size_t cumulative_bytes_locked;
|
||||
/** Mutex protects access to this pool's data structures, including arenas.
|
||||
*/
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class to keep track of locked (ie, non-swappable) memory, for use in
|
||||
* std::allocator templates.
|
||||
*
|
||||
* Some implementations of the STL allocate memory in some constructors (i.e., see
|
||||
* MSVC's vector<T> implementation where it allocates 1 byte of memory in the allocator.)
|
||||
* Due to the unpredictable order of static initializers, we have to make sure the
|
||||
* LockedPoolManager instance exists before any other STL-based objects that use
|
||||
* secure_allocator are created. So instead of having LockedPoolManager also be
|
||||
* static-initialized, it is created on demand.
|
||||
*/
|
||||
class LockedPoolManager : public LockedPool
|
||||
{
|
||||
public:
|
||||
/** Return the current instance, or create it once */
|
||||
static LockedPoolManager& Instance()
|
||||
{
|
||||
std::call_once(LockedPoolManager::init_flag, LockedPoolManager::CreateInstance);
|
||||
return *LockedPoolManager::_instance;
|
||||
}
|
||||
|
||||
private:
|
||||
LockedPoolManager(std::unique_ptr<LockedPageAllocator> allocator);
|
||||
|
||||
/** Create a new LockedPoolManager specialized to the OS */
|
||||
static void CreateInstance();
|
||||
/** Called when locking fails, warn the user here */
|
||||
static bool LockingFailed();
|
||||
|
||||
static LockedPoolManager* _instance;
|
||||
static std::once_flag init_flag;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_SUPPORT_LOCKEDPOOL_H
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) 2009-2013 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#include "support/pagelocker.h"
|
||||
|
||||
#if defined(HAVE_CONFIG_H)
|
||||
#include "config/bitcoin-config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
// This is used to attempt to keep keying material out of swap
|
||||
// Note that VirtualLock does not provide this as a guarantee on Windows,
|
||||
// but, in practice, memory that has been VirtualLock'd almost never gets written to
|
||||
// the pagefile except in rare circumstances where memory is extremely low.
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <limits.h> // for PAGESIZE
|
||||
#include <unistd.h> // for sysconf
|
||||
#endif
|
||||
|
||||
LockedPageManager* LockedPageManager::_instance = NULL;
|
||||
boost::once_flag LockedPageManager::init_flag = BOOST_ONCE_INIT;
|
||||
|
||||
/** Determine system page size in bytes */
|
||||
static inline size_t GetSystemPageSize()
|
||||
{
|
||||
size_t page_size;
|
||||
#if defined(WIN32)
|
||||
SYSTEM_INFO sSysInfo;
|
||||
GetSystemInfo(&sSysInfo);
|
||||
page_size = sSysInfo.dwPageSize;
|
||||
#elif defined(PAGESIZE) // defined in limits.h
|
||||
page_size = PAGESIZE;
|
||||
#else // assume some POSIX OS
|
||||
page_size = sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
return page_size;
|
||||
}
|
||||
|
||||
bool MemoryPageLocker::Lock(const void* addr, size_t len)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return VirtualLock(const_cast<void*>(addr), len) != 0;
|
||||
#else
|
||||
return mlock(addr, len) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MemoryPageLocker::Unlock(const void* addr, size_t len)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return VirtualUnlock(const_cast<void*>(addr), len) != 0;
|
||||
#else
|
||||
return munlock(addr, len) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
LockedPageManager::LockedPageManager() : LockedPageManagerBase<MemoryPageLocker>(GetSystemPageSize())
|
||||
{
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2013 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef BITCOIN_SUPPORT_PAGELOCKER_H
|
||||
#define BITCOIN_SUPPORT_PAGELOCKER_H
|
||||
|
||||
#include "support/cleanse.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/once.hpp>
|
||||
|
||||
/**
|
||||
* Thread-safe class to keep track of locked (ie, non-swappable) memory pages.
|
||||
*
|
||||
* Memory locks do not stack, that is, pages which have been locked several times by calls to mlock()
|
||||
* will be unlocked by a single call to munlock(). This can result in keying material ending up in swap when
|
||||
* those functions are used naively. This class simulates stacking memory locks by keeping a counter per page.
|
||||
*
|
||||
* @note By using a map from each page base address to lock count, this class is optimized for
|
||||
* small objects that span up to a few pages, mostly smaller than a page. To support large allocations,
|
||||
* something like an interval tree would be the preferred data structure.
|
||||
*/
|
||||
template <class Locker>
|
||||
class LockedPageManagerBase
|
||||
{
|
||||
public:
|
||||
LockedPageManagerBase(size_t page_size) : page_size(page_size)
|
||||
{
|
||||
// Determine bitmask for extracting page from address
|
||||
assert(!(page_size & (page_size - 1))); // size must be power of two
|
||||
page_mask = ~(page_size - 1);
|
||||
}
|
||||
|
||||
~LockedPageManagerBase()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// For all pages in affected range, increase lock count
|
||||
void LockRange(void* p, size_t size)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
if (!size)
|
||||
return;
|
||||
const size_t base_addr = reinterpret_cast<size_t>(p);
|
||||
const size_t start_page = base_addr & page_mask;
|
||||
const size_t end_page = (base_addr + size - 1) & page_mask;
|
||||
for (size_t page = start_page; page <= end_page; page += page_size) {
|
||||
Histogram::iterator it = histogram.find(page);
|
||||
if (it == histogram.end()) // Newly locked page
|
||||
{
|
||||
locker.Lock(reinterpret_cast<void*>(page), page_size);
|
||||
histogram.insert(std::make_pair(page, 1));
|
||||
} else // Page was already locked; increase counter
|
||||
{
|
||||
it->second += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For all pages in affected range, decrease lock count
|
||||
void UnlockRange(void* p, size_t size)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
if (!size)
|
||||
return;
|
||||
const size_t base_addr = reinterpret_cast<size_t>(p);
|
||||
const size_t start_page = base_addr & page_mask;
|
||||
const size_t end_page = (base_addr + size - 1) & page_mask;
|
||||
for (size_t page = start_page; page <= end_page; page += page_size) {
|
||||
Histogram::iterator it = histogram.find(page);
|
||||
assert(it != histogram.end()); // Cannot unlock an area that was not locked
|
||||
// Decrease counter for page, when it is zero, the page will be unlocked
|
||||
it->second -= 1;
|
||||
if (it->second == 0) // Nothing on the page anymore that keeps it locked
|
||||
{
|
||||
// Unlock page and remove the count from histogram
|
||||
locker.Unlock(reinterpret_cast<void*>(page), page_size);
|
||||
histogram.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get number of locked pages for diagnostics
|
||||
int GetLockedPageCount()
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
return histogram.size();
|
||||
}
|
||||
|
||||
private:
|
||||
Locker locker;
|
||||
boost::mutex mutex;
|
||||
size_t page_size, page_mask;
|
||||
// map of page base address to lock count
|
||||
typedef std::map<size_t, int> Histogram;
|
||||
Histogram histogram;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* OS-dependent memory page locking/unlocking.
|
||||
* Defined as policy class to make stubbing for test possible.
|
||||
*/
|
||||
class MemoryPageLocker
|
||||
{
|
||||
public:
|
||||
/** Lock memory pages.
|
||||
* addr and len must be a multiple of the system page size
|
||||
*/
|
||||
bool Lock(const void* addr, size_t len);
|
||||
/** Unlock memory pages.
|
||||
* addr and len must be a multiple of the system page size
|
||||
*/
|
||||
bool Unlock(const void* addr, size_t len);
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class to keep track of locked (ie, non-swappable) memory pages, for use in
|
||||
* std::allocator templates.
|
||||
*
|
||||
* Some implementations of the STL allocate memory in some constructors (i.e., see
|
||||
* MSVC's vector<T> implementation where it allocates 1 byte of memory in the allocator.)
|
||||
* Due to the unpredictable order of static initializers, we have to make sure the
|
||||
* LockedPageManager instance exists before any other STL-based objects that use
|
||||
* secure_allocator are created. So instead of having LockedPageManager also be
|
||||
* static-initialized, it is created on demand.
|
||||
*/
|
||||
class LockedPageManager : public LockedPageManagerBase<MemoryPageLocker>
|
||||
{
|
||||
public:
|
||||
static LockedPageManager& Instance()
|
||||
{
|
||||
boost::call_once(LockedPageManager::CreateInstance, LockedPageManager::init_flag);
|
||||
return *LockedPageManager::_instance;
|
||||
}
|
||||
|
||||
private:
|
||||
LockedPageManager();
|
||||
|
||||
static void CreateInstance()
|
||||
{
|
||||
// Using a local static instance guarantees that the object is initialized
|
||||
// when it's first needed and also destructed after all objects that use
|
||||
// it are done with it. I can think of one unlikely scenario where we may
|
||||
// have a static destruction order/problem, but the check in
|
||||
// LockedPageManagerBase's destructor helps us detect if that ever happens.
|
||||
static LockedPageManager instance;
|
||||
LockedPageManager::_instance = &instance;
|
||||
}
|
||||
|
||||
static LockedPageManager* _instance;
|
||||
static boost::once_flag init_flag;
|
||||
};
|
||||
|
||||
//
|
||||
// Functions for directly locking/unlocking memory objects.
|
||||
// Intended for non-dynamically allocated structures.
|
||||
//
|
||||
template <typename T>
|
||||
void LockObject(const T& t)
|
||||
{
|
||||
LockedPageManager::Instance().LockRange((void*)(&t), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void UnlockObject(const T& t)
|
||||
{
|
||||
memory_cleanse((void*)(&t), sizeof(T));
|
||||
LockedPageManager::Instance().UnlockRange((void*)(&t), sizeof(T));
|
||||
}
|
||||
|
||||
#endif // BITCOIN_SUPPORT_PAGELOCKER_H
|
|
@ -11,110 +11,214 @@
|
|||
|
||||
BOOST_FIXTURE_TEST_SUITE(allocator_tests, BasicTestingSetup)
|
||||
|
||||
// Dummy memory page locker for platform independent tests
|
||||
static const void *last_lock_addr, *last_unlock_addr;
|
||||
static size_t last_lock_len, last_unlock_len;
|
||||
class TestLocker
|
||||
BOOST_AUTO_TEST_CASE(arena_tests)
|
||||
{
|
||||
// Fake memory base address for testing
|
||||
// without actually using memory.
|
||||
void *synth_base = reinterpret_cast<void*>(0x08000000);
|
||||
const size_t synth_size = 1024*1024;
|
||||
Arena b(synth_base, synth_size, 16);
|
||||
void *chunk = b.alloc(1000);
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
BOOST_CHECK(chunk != nullptr);
|
||||
BOOST_CHECK(b.stats().used == 1008); // Aligned to 16
|
||||
BOOST_CHECK(b.stats().total == synth_size); // Nothing has disappeared?
|
||||
b.free(chunk);
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
BOOST_CHECK(b.stats().used == 0);
|
||||
BOOST_CHECK(b.stats().free == synth_size);
|
||||
try { // Test exception on double-free
|
||||
b.free(chunk);
|
||||
BOOST_CHECK(0);
|
||||
} catch(std::runtime_error &)
|
||||
{
|
||||
}
|
||||
|
||||
void *a0 = b.alloc(128);
|
||||
BOOST_CHECK(a0 == synth_base); // first allocation must start at beginning
|
||||
void *a1 = b.alloc(256);
|
||||
void *a2 = b.alloc(512);
|
||||
BOOST_CHECK(b.stats().used == 896);
|
||||
BOOST_CHECK(b.stats().total == synth_size);
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
b.free(a0);
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
BOOST_CHECK(b.stats().used == 768);
|
||||
b.free(a1);
|
||||
BOOST_CHECK(b.stats().used == 512);
|
||||
void *a3 = b.alloc(128);
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
BOOST_CHECK(b.stats().used == 640);
|
||||
b.free(a2);
|
||||
BOOST_CHECK(b.stats().used == 128);
|
||||
b.free(a3);
|
||||
BOOST_CHECK(b.stats().used == 0);
|
||||
BOOST_CHECK(b.stats().total == synth_size);
|
||||
BOOST_CHECK(b.stats().free == synth_size);
|
||||
|
||||
std::vector<void*> addr;
|
||||
BOOST_CHECK(b.alloc(0) == nullptr); // allocating 0 always returns nullptr
|
||||
#ifdef ARENA_DEBUG
|
||||
b.walk();
|
||||
#endif
|
||||
// Sweeping allocate all memory
|
||||
for (int x=0; x<1024; ++x)
|
||||
addr.push_back(b.alloc(1024));
|
||||
BOOST_CHECK(addr[0] == synth_base); // first allocation must start at beginning
|
||||
BOOST_CHECK(b.stats().free == 0);
|
||||
BOOST_CHECK(b.alloc(1024) == nullptr); // memory is full, this must return nullptr
|
||||
BOOST_CHECK(b.alloc(0) == nullptr);
|
||||
for (int x=0; x<1024; ++x)
|
||||
b.free(addr[x]);
|
||||
addr.clear();
|
||||
BOOST_CHECK(b.stats().total == synth_size);
|
||||
BOOST_CHECK(b.stats().free == synth_size);
|
||||
|
||||
// Now in the other direction...
|
||||
for (int x=0; x<1024; ++x)
|
||||
addr.push_back(b.alloc(1024));
|
||||
for (int x=0; x<1024; ++x)
|
||||
b.free(addr[1023-x]);
|
||||
addr.clear();
|
||||
|
||||
// Now allocate in smaller unequal chunks, then deallocate haphazardly
|
||||
// Not all the chunks will succeed allocating, but freeing nullptr is
|
||||
// allowed so that is no problem.
|
||||
for (int x=0; x<2048; ++x)
|
||||
addr.push_back(b.alloc(x+1));
|
||||
for (int x=0; x<2048; ++x)
|
||||
b.free(addr[((x*23)%2048)^242]);
|
||||
addr.clear();
|
||||
|
||||
// Go entirely wild: free and alloc interleaved,
|
||||
// generate targets and sizes using pseudo-randomness.
|
||||
for (int x=0; x<2048; ++x)
|
||||
addr.push_back(0);
|
||||
uint32_t s = 0x12345678;
|
||||
for (int x=0; x<5000; ++x) {
|
||||
int idx = s & (addr.size()-1);
|
||||
if (s & 0x80000000) {
|
||||
b.free(addr[idx]);
|
||||
addr[idx] = 0;
|
||||
} else if(!addr[idx]) {
|
||||
addr[idx] = b.alloc((s >> 16) & 2047);
|
||||
}
|
||||
bool lsb = s & 1;
|
||||
s >>= 1;
|
||||
if (lsb)
|
||||
s ^= 0xf00f00f0; // LFSR period 0xf7ffffe0
|
||||
}
|
||||
for (void *ptr: addr)
|
||||
b.free(ptr);
|
||||
addr.clear();
|
||||
|
||||
BOOST_CHECK(b.stats().total == synth_size);
|
||||
BOOST_CHECK(b.stats().free == synth_size);
|
||||
}
|
||||
|
||||
/** Mock LockedPageAllocator for testing */
|
||||
class TestLockedPageAllocator: public LockedPageAllocator
|
||||
{
|
||||
public:
|
||||
bool Lock(const void *addr, size_t len)
|
||||
TestLockedPageAllocator(int count_in, int lockedcount_in): count(count_in), lockedcount(lockedcount_in) {}
|
||||
void* AllocateLocked(size_t len, bool *lockingSuccess)
|
||||
{
|
||||
last_lock_addr = addr;
|
||||
last_lock_len = len;
|
||||
return true;
|
||||
*lockingSuccess = false;
|
||||
if (count > 0) {
|
||||
--count;
|
||||
|
||||
if (lockedcount > 0) {
|
||||
--lockedcount;
|
||||
*lockingSuccess = true;
|
||||
}
|
||||
|
||||
return reinterpret_cast<void*>(0x08000000 + (count<<24)); // Fake address, do not actually use this memory
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool Unlock(const void *addr, size_t len)
|
||||
void FreeLocked(void* addr, size_t len)
|
||||
{
|
||||
last_unlock_addr = addr;
|
||||
last_unlock_len = len;
|
||||
return true;
|
||||
}
|
||||
size_t GetLimit()
|
||||
{
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
private:
|
||||
int count;
|
||||
int lockedcount;
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_LockedPageManagerBase)
|
||||
BOOST_AUTO_TEST_CASE(lockedpool_tests_mock)
|
||||
{
|
||||
const size_t test_page_size = 4096;
|
||||
LockedPageManagerBase<TestLocker> lpm(test_page_size);
|
||||
size_t addr;
|
||||
last_lock_addr = last_unlock_addr = 0;
|
||||
last_lock_len = last_unlock_len = 0;
|
||||
// Test over three virtual arenas, of which one will succeed being locked
|
||||
std::unique_ptr<LockedPageAllocator> x(new TestLockedPageAllocator(3, 1));
|
||||
LockedPool pool(std::move(x));
|
||||
BOOST_CHECK(pool.stats().total == 0);
|
||||
BOOST_CHECK(pool.stats().locked == 0);
|
||||
|
||||
/* Try large number of small objects */
|
||||
addr = 0;
|
||||
for(int i=0; i<1000; ++i)
|
||||
{
|
||||
lpm.LockRange(reinterpret_cast<void*>(addr), 33);
|
||||
addr += 33;
|
||||
}
|
||||
/* Try small number of page-sized objects, straddling two pages */
|
||||
addr = test_page_size*100 + 53;
|
||||
for(int i=0; i<100; ++i)
|
||||
{
|
||||
lpm.LockRange(reinterpret_cast<void*>(addr), test_page_size);
|
||||
addr += test_page_size;
|
||||
}
|
||||
/* Try small number of page-sized objects aligned to exactly one page */
|
||||
addr = test_page_size*300;
|
||||
for(int i=0; i<100; ++i)
|
||||
{
|
||||
lpm.LockRange(reinterpret_cast<void*>(addr), test_page_size);
|
||||
addr += test_page_size;
|
||||
}
|
||||
/* one very large object, straddling pages */
|
||||
lpm.LockRange(reinterpret_cast<void*>(test_page_size*600+1), test_page_size*500);
|
||||
BOOST_CHECK(last_lock_addr == reinterpret_cast<void*>(test_page_size*(600+500)));
|
||||
/* one very large object, page aligned */
|
||||
lpm.LockRange(reinterpret_cast<void*>(test_page_size*1200), test_page_size*500-1);
|
||||
BOOST_CHECK(last_lock_addr == reinterpret_cast<void*>(test_page_size*(1200+500-1)));
|
||||
void *a0 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a0);
|
||||
BOOST_CHECK(pool.stats().locked == LockedPool::ARENA_SIZE);
|
||||
void *a1 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a1);
|
||||
void *a2 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a2);
|
||||
void *a3 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a3);
|
||||
void *a4 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a4);
|
||||
void *a5 = pool.alloc(LockedPool::ARENA_SIZE / 2);
|
||||
BOOST_CHECK(a5);
|
||||
// We've passed a count of three arenas, so this allocation should fail
|
||||
void *a6 = pool.alloc(16);
|
||||
BOOST_CHECK(!a6);
|
||||
|
||||
BOOST_CHECK(lpm.GetLockedPageCount() == (
|
||||
(1000*33+test_page_size-1)/test_page_size + // small objects
|
||||
101 + 100 + // page-sized objects
|
||||
501 + 500)); // large objects
|
||||
BOOST_CHECK((last_lock_len & (test_page_size-1)) == 0); // always lock entire pages
|
||||
BOOST_CHECK(last_unlock_len == 0); // nothing unlocked yet
|
||||
pool.free(a0);
|
||||
pool.free(a2);
|
||||
pool.free(a4);
|
||||
pool.free(a1);
|
||||
pool.free(a3);
|
||||
pool.free(a5);
|
||||
BOOST_CHECK(pool.stats().total == 3*LockedPool::ARENA_SIZE);
|
||||
BOOST_CHECK(pool.stats().locked == LockedPool::ARENA_SIZE);
|
||||
BOOST_CHECK(pool.stats().used == 0);
|
||||
}
|
||||
|
||||
/* And unlock again */
|
||||
addr = 0;
|
||||
for(int i=0; i<1000; ++i)
|
||||
{
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(addr), 33);
|
||||
addr += 33;
|
||||
}
|
||||
addr = test_page_size*100 + 53;
|
||||
for(int i=0; i<100; ++i)
|
||||
{
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(addr), test_page_size);
|
||||
addr += test_page_size;
|
||||
}
|
||||
addr = test_page_size*300;
|
||||
for(int i=0; i<100; ++i)
|
||||
{
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(addr), test_page_size);
|
||||
addr += test_page_size;
|
||||
}
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(test_page_size*600+1), test_page_size*500);
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(test_page_size*1200), test_page_size*500-1);
|
||||
// These tests used the live LockedPoolManager object, this is also used
|
||||
// by other tests so the conditions are somewhat less controllable and thus the
|
||||
// tests are somewhat more error-prone.
|
||||
BOOST_AUTO_TEST_CASE(lockedpool_tests_live)
|
||||
{
|
||||
LockedPoolManager &pool = LockedPoolManager::Instance();
|
||||
LockedPool::Stats initial = pool.stats();
|
||||
|
||||
/* Check that everything is released */
|
||||
BOOST_CHECK(lpm.GetLockedPageCount() == 0);
|
||||
void *a0 = pool.alloc(16);
|
||||
BOOST_CHECK(a0);
|
||||
// Test reading and writing the allocated memory
|
||||
*((uint32_t*)a0) = 0x1234;
|
||||
BOOST_CHECK(*((uint32_t*)a0) == 0x1234);
|
||||
|
||||
/* A few and unlocks of size zero (should have no effect) */
|
||||
addr = 0;
|
||||
for(int i=0; i<1000; ++i)
|
||||
pool.free(a0);
|
||||
try { // Test exception on double-free
|
||||
pool.free(a0);
|
||||
BOOST_CHECK(0);
|
||||
} catch(std::runtime_error &)
|
||||
{
|
||||
lpm.LockRange(reinterpret_cast<void*>(addr), 0);
|
||||
addr += 1;
|
||||
}
|
||||
BOOST_CHECK(lpm.GetLockedPageCount() == 0);
|
||||
addr = 0;
|
||||
for(int i=0; i<1000; ++i)
|
||||
{
|
||||
lpm.UnlockRange(reinterpret_cast<void*>(addr), 0);
|
||||
addr += 1;
|
||||
}
|
||||
BOOST_CHECK(lpm.GetLockedPageCount() == 0);
|
||||
BOOST_CHECK((last_unlock_len & (test_page_size-1)) == 0); // always unlock entire pages
|
||||
// If more than one new arena was allocated for the above tests, something is wrong
|
||||
BOOST_CHECK(pool.stats().total <= (initial.total + LockedPool::ARENA_SIZE));
|
||||
// Usage must be back to where it started
|
||||
BOOST_CHECK(pool.stats().used == initial.used);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/ripemd160.h"
|
||||
#include "crypto/sha1.h"
|
||||
#include "crypto/sha256.h"
|
||||
|
@ -16,6 +17,8 @@
|
|||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup)
|
||||
|
||||
|
@ -63,6 +66,127 @@ void TestHMACSHA512(const std::string &hexkey, const std::string &hexin, const s
|
|||
TestVector(CHMAC_SHA512(&key[0], key.size()), ParseHex(hexin), ParseHex(hexout));
|
||||
}
|
||||
|
||||
void TestAES128(const std::string &hexkey, const std::string &hexin, const std::string &hexout)
|
||||
{
|
||||
std::vector<unsigned char> key = ParseHex(hexkey);
|
||||
std::vector<unsigned char> in = ParseHex(hexin);
|
||||
std::vector<unsigned char> correctout = ParseHex(hexout);
|
||||
std::vector<unsigned char> buf, buf2;
|
||||
|
||||
assert(key.size() == 16);
|
||||
assert(in.size() == 16);
|
||||
assert(correctout.size() == 16);
|
||||
AES128Encrypt enc(&key[0]);
|
||||
buf.resize(correctout.size());
|
||||
buf2.resize(correctout.size());
|
||||
enc.Encrypt(&buf[0], &in[0]);
|
||||
BOOST_CHECK_EQUAL(HexStr(buf), HexStr(correctout));
|
||||
AES128Decrypt dec(&key[0]);
|
||||
dec.Decrypt(&buf2[0], &buf[0]);
|
||||
BOOST_CHECK_EQUAL(HexStr(buf2), HexStr(in));
|
||||
}
|
||||
|
||||
void TestAES256(const std::string &hexkey, const std::string &hexin, const std::string &hexout)
|
||||
{
|
||||
std::vector<unsigned char> key = ParseHex(hexkey);
|
||||
std::vector<unsigned char> in = ParseHex(hexin);
|
||||
std::vector<unsigned char> correctout = ParseHex(hexout);
|
||||
std::vector<unsigned char> buf;
|
||||
|
||||
assert(key.size() == 32);
|
||||
assert(in.size() == 16);
|
||||
assert(correctout.size() == 16);
|
||||
AES256Encrypt enc(&key[0]);
|
||||
buf.resize(correctout.size());
|
||||
enc.Encrypt(&buf[0], &in[0]);
|
||||
BOOST_CHECK(buf == correctout);
|
||||
AES256Decrypt dec(&key[0]);
|
||||
dec.Decrypt(&buf[0], &buf[0]);
|
||||
BOOST_CHECK(buf == in);
|
||||
}
|
||||
|
||||
void TestAES128CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout)
|
||||
{
|
||||
std::vector<unsigned char> key = ParseHex(hexkey);
|
||||
std::vector<unsigned char> iv = ParseHex(hexiv);
|
||||
std::vector<unsigned char> in = ParseHex(hexin);
|
||||
std::vector<unsigned char> correctout = ParseHex(hexout);
|
||||
std::vector<unsigned char> realout(in.size() + AES_BLOCKSIZE);
|
||||
|
||||
// Encrypt the plaintext and verify that it equals the cipher
|
||||
AES128CBCEncrypt enc(&key[0], &iv[0], pad);
|
||||
int size = enc.Encrypt(&in[0], in.size(), &realout[0]);
|
||||
realout.resize(size);
|
||||
BOOST_CHECK(realout.size() == correctout.size());
|
||||
BOOST_CHECK_MESSAGE(realout == correctout, HexStr(realout) + std::string(" != ") + hexout);
|
||||
|
||||
// Decrypt the cipher and verify that it equals the plaintext
|
||||
std::vector<unsigned char> decrypted(correctout.size());
|
||||
AES128CBCDecrypt dec(&key[0], &iv[0], pad);
|
||||
size = dec.Decrypt(&correctout[0], correctout.size(), &decrypted[0]);
|
||||
decrypted.resize(size);
|
||||
BOOST_CHECK(decrypted.size() == in.size());
|
||||
BOOST_CHECK_MESSAGE(decrypted == in, HexStr(decrypted) + std::string(" != ") + hexin);
|
||||
|
||||
// Encrypt and re-decrypt substrings of the plaintext and verify that they equal each-other
|
||||
for(std::vector<unsigned char>::iterator i(in.begin()); i != in.end(); ++i)
|
||||
{
|
||||
std::vector<unsigned char> sub(i, in.end());
|
||||
std::vector<unsigned char> subout(sub.size() + AES_BLOCKSIZE);
|
||||
int size = enc.Encrypt(&sub[0], sub.size(), &subout[0]);
|
||||
if (size != 0)
|
||||
{
|
||||
subout.resize(size);
|
||||
std::vector<unsigned char> subdecrypted(subout.size());
|
||||
size = dec.Decrypt(&subout[0], subout.size(), &subdecrypted[0]);
|
||||
subdecrypted.resize(size);
|
||||
BOOST_CHECK(decrypted.size() == in.size());
|
||||
BOOST_CHECK_MESSAGE(subdecrypted == sub, HexStr(subdecrypted) + std::string(" != ") + HexStr(sub));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout)
|
||||
{
|
||||
std::vector<unsigned char> key = ParseHex(hexkey);
|
||||
std::vector<unsigned char> iv = ParseHex(hexiv);
|
||||
std::vector<unsigned char> in = ParseHex(hexin);
|
||||
std::vector<unsigned char> correctout = ParseHex(hexout);
|
||||
std::vector<unsigned char> realout(in.size() + AES_BLOCKSIZE);
|
||||
|
||||
// Encrypt the plaintext and verify that it equals the cipher
|
||||
AES256CBCEncrypt enc(&key[0], &iv[0], pad);
|
||||
int size = enc.Encrypt(&in[0], in.size(), &realout[0]);
|
||||
realout.resize(size);
|
||||
BOOST_CHECK(realout.size() == correctout.size());
|
||||
BOOST_CHECK_MESSAGE(realout == correctout, HexStr(realout) + std::string(" != ") + hexout);
|
||||
|
||||
// Decrypt the cipher and verify that it equals the plaintext
|
||||
std::vector<unsigned char> decrypted(correctout.size());
|
||||
AES256CBCDecrypt dec(&key[0], &iv[0], pad);
|
||||
size = dec.Decrypt(&correctout[0], correctout.size(), &decrypted[0]);
|
||||
decrypted.resize(size);
|
||||
BOOST_CHECK(decrypted.size() == in.size());
|
||||
BOOST_CHECK_MESSAGE(decrypted == in, HexStr(decrypted) + std::string(" != ") + hexin);
|
||||
|
||||
// Encrypt and re-decrypt substrings of the plaintext and verify that they equal each-other
|
||||
for(std::vector<unsigned char>::iterator i(in.begin()); i != in.end(); ++i)
|
||||
{
|
||||
std::vector<unsigned char> sub(i, in.end());
|
||||
std::vector<unsigned char> subout(sub.size() + AES_BLOCKSIZE);
|
||||
int size = enc.Encrypt(&sub[0], sub.size(), &subout[0]);
|
||||
if (size != 0)
|
||||
{
|
||||
subout.resize(size);
|
||||
std::vector<unsigned char> subdecrypted(subout.size());
|
||||
size = dec.Decrypt(&subout[0], subout.size(), &subdecrypted[0]);
|
||||
subdecrypted.resize(size);
|
||||
BOOST_CHECK(decrypted.size() == in.size());
|
||||
BOOST_CHECK_MESSAGE(subdecrypted == sub, HexStr(subdecrypted) + std::string(" != ") + HexStr(sub));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string LongTestString(void) {
|
||||
std::string ret;
|
||||
for (int i=0; i<200000; i++) {
|
||||
|
@ -248,4 +372,71 @@ BOOST_AUTO_TEST_CASE(hmac_sha512_testvectors) {
|
|||
"b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(aes_testvectors) {
|
||||
// AES test vectors from FIPS 197.
|
||||
TestAES128("000102030405060708090a0b0c0d0e0f", "00112233445566778899aabbccddeeff", "69c4e0d86a7b0430d8cdb78070b4c55a");
|
||||
TestAES256("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "00112233445566778899aabbccddeeff", "8ea2b7ca516745bfeafc49904b496089");
|
||||
|
||||
// AES-ECB test vectors from NIST sp800-38a.
|
||||
TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "6bc1bee22e409f96e93d7e117393172a", "3ad77bb40d7a3660a89ecaf32466ef97");
|
||||
TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "ae2d8a571e03ac9c9eb76fac45af8e51", "f5d3d58503b9699de785895a96fdbaaf");
|
||||
TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "30c81c46a35ce411e5fbc1191a0a52ef", "43b1cd7f598ece23881b00e3ed030688");
|
||||
TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "f69f2445df4f9b17ad2b417be66c3710", "7b0c785e27e8ad3f8223207104725dd4");
|
||||
TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "6bc1bee22e409f96e93d7e117393172a", "f3eed1bdb5d2a03c064b5a7e3db181f8");
|
||||
TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "ae2d8a571e03ac9c9eb76fac45af8e51", "591ccb10d410ed26dc5ba74a31362870");
|
||||
TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "30c81c46a35ce411e5fbc1191a0a52ef", "b6ed21b99ca6f4f9f153e7b1beafed1d");
|
||||
TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "f69f2445df4f9b17ad2b417be66c3710", "23304b7a39f9f3ff067d8d8f9e24ecc7");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(aes_cbc_testvectors) {
|
||||
|
||||
// NIST AES CBC 128-bit encryption test-vectors
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090A0B0C0D0E0F", false, \
|
||||
"6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", false, \
|
||||
"ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b2");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "5086cb9b507219ee95db113a917678b2", false, \
|
||||
"30c81c46a35ce411e5fbc1191a0a52ef", "73bed6b8e3c1743b7116e69e22229516");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "73bed6b8e3c1743b7116e69e22229516", false, \
|
||||
"f69f2445df4f9b17ad2b417be66c3710", "3ff1caa1681fac09120eca307586e1a7");
|
||||
|
||||
// The same vectors with padding enabled
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090A0B0C0D0E0F", true, \
|
||||
"6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d8964e0b149c10b7b682e6e39aaeb731c");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", true, \
|
||||
"ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b255e21d7100b988ffec32feeafaf23538");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "5086cb9b507219ee95db113a917678b2", true, \
|
||||
"30c81c46a35ce411e5fbc1191a0a52ef", "73bed6b8e3c1743b7116e69e22229516f6eccda327bf8e5ec43718b0039adceb");
|
||||
TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "73bed6b8e3c1743b7116e69e22229516", true, \
|
||||
"f69f2445df4f9b17ad2b417be66c3710", "3ff1caa1681fac09120eca307586e1a78cb82807230e1321d3fae00d18cc2012");
|
||||
|
||||
// NIST AES CBC 256-bit encryption test-vectors
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"000102030405060708090A0B0C0D0E0F", false, "6bc1bee22e409f96e93d7e117393172a", \
|
||||
"f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"F58C4C04D6E5F1BA779EABFB5F7BFBD6", false, "ae2d8a571e03ac9c9eb76fac45af8e51", \
|
||||
"9cfc4e967edb808d679f777bc6702c7d");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"9CFC4E967EDB808D679F777BC6702C7D", false, "30c81c46a35ce411e5fbc1191a0a52ef",
|
||||
"39f23369a9d9bacfa530e26304231461");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"39F23369A9D9BACFA530E26304231461", false, "f69f2445df4f9b17ad2b417be66c3710", \
|
||||
"b2eb05e2c39be9fcda6c19078c6a9d1b");
|
||||
|
||||
// The same vectors with padding enabled
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"000102030405060708090A0B0C0D0E0F", true, "6bc1bee22e409f96e93d7e117393172a", \
|
||||
"f58c4c04d6e5f1ba779eabfb5f7bfbd6485a5c81519cf378fa36d42b8547edc0");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"F58C4C04D6E5F1BA779EABFB5F7BFBD6", true, "ae2d8a571e03ac9c9eb76fac45af8e51", \
|
||||
"9cfc4e967edb808d679f777bc6702c7d3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"9CFC4E967EDB808D679F777BC6702C7D", true, "30c81c46a35ce411e5fbc1191a0a52ef",
|
||||
"39f23369a9d9bacfa530e263042314612f8da707643c90a6f732b3de1d3f5cee");
|
||||
TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \
|
||||
"39F23369A9D9BACFA530E26304231461", true, "f69f2445df4f9b17ad2b417be66c3710", \
|
||||
"b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "crypter.h"
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/sha512.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "streams.h"
|
||||
|
@ -12,8 +14,33 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const
|
||||
{
|
||||
// This mimics the behavior of openssl's EVP_BytesToKey with an aes256cbc
|
||||
// cipher and sha512 message digest. Because sha512's output size (64b) is
|
||||
// greater than the aes256 block size (16b) + aes256 key size (32b),
|
||||
// there's no need to process more than once (D_0).
|
||||
|
||||
if(!count || !key || !iv)
|
||||
return 0;
|
||||
|
||||
unsigned char buf[CSHA512::OUTPUT_SIZE];
|
||||
CSHA512 di;
|
||||
|
||||
di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size());
|
||||
if(chSalt.size())
|
||||
di.Write(&chSalt[0], chSalt.size());
|
||||
di.Finalize(buf);
|
||||
|
||||
for(int i = 0; i != count - 1; i++)
|
||||
di.Reset().Write(buf, sizeof(buf)).Finalize(buf);
|
||||
|
||||
memcpy(key, buf, WALLET_CRYPTO_KEY_SIZE);
|
||||
memcpy(iv, buf + WALLET_CRYPTO_KEY_SIZE, WALLET_CRYPTO_IV_SIZE);
|
||||
memory_cleanse(buf, sizeof(buf));
|
||||
return WALLET_CRYPTO_KEY_SIZE;
|
||||
}
|
||||
|
||||
bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod)
|
||||
{
|
||||
|
@ -22,13 +49,12 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v
|
|||
|
||||
int i = 0;
|
||||
if (nDerivationMethod == 0)
|
||||
i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0],
|
||||
(unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV);
|
||||
i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, vchKey.data(), vchIV.data());
|
||||
|
||||
if (i != (int)WALLET_CRYPTO_KEY_SIZE)
|
||||
{
|
||||
memory_cleanse(chKey, sizeof(chKey));
|
||||
memory_cleanse(chIV, sizeof(chIV));
|
||||
memory_cleanse(vchKey.data(), vchKey.size());
|
||||
memory_cleanse(vchIV.data(), vchIV.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -38,65 +64,49 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v
|
|||
|
||||
bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV)
|
||||
{
|
||||
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_KEY_SIZE)
|
||||
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE)
|
||||
return false;
|
||||
|
||||
memcpy(&chKey[0], &chNewKey[0], sizeof chKey);
|
||||
memcpy(&chIV[0], &chNewIV[0], sizeof chIV);
|
||||
memcpy(vchKey.data(), chNewKey.data(), chNewKey.size());
|
||||
memcpy(vchIV.data(), chNewIV.data(), chNewIV.size());
|
||||
|
||||
fKeySet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext)
|
||||
bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext) const
|
||||
{
|
||||
if (!fKeySet)
|
||||
return false;
|
||||
|
||||
// max ciphertext len for a n bytes of plaintext is
|
||||
// n + AES_BLOCK_SIZE - 1 bytes
|
||||
int nLen = vchPlaintext.size();
|
||||
int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0;
|
||||
vchCiphertext = std::vector<unsigned char> (nCLen);
|
||||
// n + AES_BLOCKSIZE bytes
|
||||
vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE);
|
||||
|
||||
bool fOk = true;
|
||||
AES256CBCEncrypt enc(vchKey.data(), vchIV.data(), true);
|
||||
size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), &vchCiphertext[0]);
|
||||
if(nLen < vchPlaintext.size())
|
||||
return false;
|
||||
vchCiphertext.resize(nLen);
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
assert(ctx);
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
|
||||
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0;
|
||||
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
if (!fOk) return false;
|
||||
|
||||
vchCiphertext.resize(nCLen + nFLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext)
|
||||
bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const
|
||||
{
|
||||
if (!fKeySet)
|
||||
return false;
|
||||
|
||||
// plaintext will always be equal to or lesser than length of ciphertext
|
||||
int nLen = vchCiphertext.size();
|
||||
int nPLen = nLen, nFLen = 0;
|
||||
|
||||
vchPlaintext = CKeyingMaterial(nPLen);
|
||||
vchPlaintext.resize(nLen);
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
assert(ctx);
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
|
||||
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0;
|
||||
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
if (!fOk) return false;
|
||||
|
||||
vchPlaintext.resize(nPLen + nFLen);
|
||||
AES256CBCDecrypt dec(vchKey.data(), vchIV.data(), true);
|
||||
nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]);
|
||||
if(nLen == 0)
|
||||
return false;
|
||||
vchPlaintext.resize(nLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -104,8 +114,8 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM
|
|||
static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext)
|
||||
{
|
||||
CCrypter cKeyCrypter;
|
||||
std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
|
||||
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
|
||||
std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE);
|
||||
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE);
|
||||
if (!cKeyCrypter.SetKey(vMasterKey, chIV))
|
||||
return false;
|
||||
return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext);
|
||||
|
@ -114,8 +124,8 @@ static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMateri
|
|||
static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
|
||||
{
|
||||
CCrypter cKeyCrypter;
|
||||
std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
|
||||
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
|
||||
std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE);
|
||||
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE);
|
||||
if (!cKeyCrypter.SetKey(vMasterKey, chIV))
|
||||
return false;
|
||||
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
|
||||
|
|
|
@ -15,6 +15,7 @@ class uint256;
|
|||
|
||||
const unsigned int WALLET_CRYPTO_KEY_SIZE = 32;
|
||||
const unsigned int WALLET_CRYPTO_SALT_SIZE = 8;
|
||||
const unsigned int WALLET_CRYPTO_IV_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Private key encryption is done based on a CMasterKey,
|
||||
|
@ -80,44 +81,45 @@ public:
|
|||
CBaseDataStream(vchIn, nTypeIn, nVersionIn) { }
|
||||
};
|
||||
|
||||
namespace wallet_crypto
|
||||
{
|
||||
class TestCrypter;
|
||||
}
|
||||
|
||||
/** Encryption/decryption context with key information */
|
||||
class CCrypter
|
||||
{
|
||||
friend class wallet_crypto::TestCrypter; // for test access to chKey/chIV
|
||||
private:
|
||||
unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
|
||||
unsigned char chIV[WALLET_CRYPTO_KEY_SIZE];
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vchKey;
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vchIV;
|
||||
bool fKeySet;
|
||||
|
||||
int BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const;
|
||||
|
||||
public:
|
||||
bool SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod);
|
||||
bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext);
|
||||
bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext);
|
||||
bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext) const;
|
||||
bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const;
|
||||
bool SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV);
|
||||
|
||||
void CleanKey()
|
||||
{
|
||||
memory_cleanse(chKey, sizeof(chKey));
|
||||
memory_cleanse(chIV, sizeof(chIV));
|
||||
memory_cleanse(vchKey.data(), vchKey.size());
|
||||
memory_cleanse(vchIV.data(), vchIV.size());
|
||||
fKeySet = false;
|
||||
}
|
||||
|
||||
CCrypter()
|
||||
{
|
||||
fKeySet = false;
|
||||
|
||||
// Try to keep the key data out of swap (and be a bit over-careful to keep the IV that we don't even use out of swap)
|
||||
// Note that this does nothing about suspend-to-disk (which will put all our key data on disk)
|
||||
// Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process.
|
||||
LockedPageManager::Instance().LockRange(&chKey[0], sizeof chKey);
|
||||
LockedPageManager::Instance().LockRange(&chIV[0], sizeof chIV);
|
||||
vchKey.resize(WALLET_CRYPTO_KEY_SIZE);
|
||||
vchIV.resize(WALLET_CRYPTO_IV_SIZE);
|
||||
}
|
||||
|
||||
~CCrypter()
|
||||
{
|
||||
CleanKey();
|
||||
|
||||
LockedPageManager::Instance().UnlockRange(&chKey[0], sizeof chKey);
|
||||
LockedPageManager::Instance().UnlockRange(&chIV[0], sizeof chIV);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
// Copyright (c) 2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "test/test_random.h"
|
||||
#include "utilstrencodings.h"
|
||||
#include "test/test_bitcoin.h"
|
||||
#include "wallet/crypter.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(wallet_crypto, BasicTestingSetup)
|
||||
|
||||
bool OldSetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod, unsigned char* chKey, unsigned char* chIV)
|
||||
{
|
||||
if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE)
|
||||
return false;
|
||||
|
||||
int i = 0;
|
||||
if (nDerivationMethod == 0)
|
||||
i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0],
|
||||
(unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV);
|
||||
|
||||
if (i != (int)WALLET_CRYPTO_KEY_SIZE)
|
||||
{
|
||||
memory_cleanse(chKey, sizeof(chKey));
|
||||
memory_cleanse(chIV, sizeof(chIV));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OldEncrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext, const unsigned char chKey[32], const unsigned char chIV[16])
|
||||
{
|
||||
// max ciphertext len for a n bytes of plaintext is
|
||||
// n + AES_BLOCK_SIZE - 1 bytes
|
||||
int nLen = vchPlaintext.size();
|
||||
int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0;
|
||||
vchCiphertext = std::vector<unsigned char> (nCLen);
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
assert(ctx);
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
|
||||
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0;
|
||||
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
if (!fOk) return false;
|
||||
|
||||
vchCiphertext.resize(nCLen + nFLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OldDecrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext, const unsigned char chKey[32], const unsigned char chIV[16])
|
||||
{
|
||||
// plaintext will always be equal to or lesser than length of ciphertext
|
||||
int nLen = vchCiphertext.size();
|
||||
int nPLen = nLen, nFLen = 0;
|
||||
|
||||
vchPlaintext = CKeyingMaterial(nPLen);
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
assert(ctx);
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
|
||||
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0;
|
||||
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
if (!fOk) return false;
|
||||
|
||||
vchPlaintext.resize(nPLen + nFLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
class TestCrypter
|
||||
{
|
||||
public:
|
||||
static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
|
||||
const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
|
||||
const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
|
||||
{
|
||||
unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
|
||||
unsigned char chIV[WALLET_CRYPTO_IV_SIZE];
|
||||
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0);
|
||||
|
||||
OldSetKeyFromPassphrase(passphrase, vchSalt, rounds, 0, chKey, chIV);
|
||||
|
||||
BOOST_CHECK_MESSAGE(memcmp(chKey, crypt.vchKey.data(), crypt.vchKey.size()) == 0, \
|
||||
HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(crypt.vchKey));
|
||||
BOOST_CHECK_MESSAGE(memcmp(chIV, crypt.vchIV.data(), crypt.vchIV.size()) == 0, \
|
||||
HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(crypt.vchIV));
|
||||
|
||||
if(!correctKey.empty())
|
||||
BOOST_CHECK_MESSAGE(memcmp(chKey, &correctKey[0], sizeof(chKey)) == 0, \
|
||||
HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end()));
|
||||
if(!correctIV.empty())
|
||||
BOOST_CHECK_MESSAGE(memcmp(chIV, &correctIV[0], sizeof(chIV)) == 0,
|
||||
HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end()));
|
||||
}
|
||||
|
||||
static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
|
||||
const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
|
||||
const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
|
||||
{
|
||||
TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV);
|
||||
for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i)
|
||||
TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds);
|
||||
}
|
||||
|
||||
|
||||
static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchCiphertext, \
|
||||
const std::vector<unsigned char>& vchPlaintext = std::vector<unsigned char>())
|
||||
{
|
||||
CKeyingMaterial vchDecrypted1;
|
||||
CKeyingMaterial vchDecrypted2;
|
||||
int result1, result2;
|
||||
result1 = crypt.Decrypt(vchCiphertext, vchDecrypted1);
|
||||
result2 = OldDecrypt(vchCiphertext, vchDecrypted2, crypt.vchKey.data(), crypt.vchIV.data());
|
||||
BOOST_CHECK(result1 == result2);
|
||||
|
||||
// These two should be equal. However, OpenSSL 1.0.1j introduced a change
|
||||
// that would zero all padding except for the last byte for failed decrypts.
|
||||
// This behavior was reverted for 1.0.1k.
|
||||
if (vchDecrypted1 != vchDecrypted2 && vchDecrypted1.size() >= AES_BLOCK_SIZE && SSLeay() == 0x100010afL)
|
||||
{
|
||||
for(CKeyingMaterial::iterator it = vchDecrypted1.end() - AES_BLOCK_SIZE; it != vchDecrypted1.end() - 1; it++)
|
||||
*it = 0;
|
||||
}
|
||||
|
||||
BOOST_CHECK_MESSAGE(vchDecrypted1 == vchDecrypted2, HexStr(vchDecrypted1.begin(), vchDecrypted1.end()) + " != " + HexStr(vchDecrypted2.begin(), vchDecrypted2.end()));
|
||||
|
||||
if (vchPlaintext.size())
|
||||
BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted2);
|
||||
}
|
||||
|
||||
static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext,
|
||||
const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
|
||||
{
|
||||
std::vector<unsigned char> vchCiphertext1;
|
||||
std::vector<unsigned char> vchCiphertext2;
|
||||
int result1 = crypt.Encrypt(vchPlaintext, vchCiphertext1);
|
||||
|
||||
int result2 = OldEncrypt(vchPlaintext, vchCiphertext2, crypt.vchKey.data(), crypt.vchIV.data());
|
||||
BOOST_CHECK(result1 == result2);
|
||||
BOOST_CHECK(vchCiphertext1 == vchCiphertext2);
|
||||
|
||||
if (!vchCiphertextCorrect.empty())
|
||||
BOOST_CHECK(vchCiphertext2 == vchCiphertextCorrect);
|
||||
|
||||
const std::vector<unsigned char> vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end());
|
||||
|
||||
if(vchCiphertext1 == vchCiphertext2)
|
||||
TestDecrypt(crypt, vchCiphertext1, vchPlaintext2);
|
||||
}
|
||||
|
||||
static void TestEncrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchPlaintextIn, \
|
||||
const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
|
||||
{
|
||||
TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect);
|
||||
for(std::vector<unsigned char>::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i)
|
||||
TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end()));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(passphrase) {
|
||||
// These are expensive.
|
||||
|
||||
TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \
|
||||
ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \
|
||||
ParseHex("cf2f2691526dd1aa220896fb8bf7c369"));
|
||||
|
||||
std::string hash(GetRandHash().ToString());
|
||||
std::vector<unsigned char> vchSalt(8);
|
||||
GetRandBytes(&vchSalt[0], vchSalt.size());
|
||||
uint32_t rounds = insecure_rand();
|
||||
if (rounds > 30000)
|
||||
rounds = 30000;
|
||||
TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(encrypt) {
|
||||
std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
|
||||
BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
|
||||
TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"));
|
||||
|
||||
for (int i = 0; i != 100; i++)
|
||||
{
|
||||
uint256 hash(GetRandHash());
|
||||
TestCrypter::TestEncrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(decrypt) {
|
||||
std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
|
||||
BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
|
||||
|
||||
// Some corner cases the came up while testing
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9"));
|
||||
|
||||
for (int i = 0; i != 100; i++)
|
||||
{
|
||||
uint256 hash(GetRandHash());
|
||||
TestCrypter::TestDecrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue