Repository: bitcoin-core/secp256k1
Pull request #566 (2018-10-22 15:21:11)

Enable context creation in preallocated memory
@apoelstra 

This builds on #557.

Manually managing memory is always a pain in the ass in some way. I tried to keep the pain manageable. I'm open to suggestions to make this less ugly or error-prone.

to do:
 * tests
 * export functions

real-or-random: Addressed your comments including the memory leak (reason was that I didn't really implement context cloning) and tidied up a little.

Open things:
 - This removes the ability to clone `ecmult_context` and `ecmult_gen_context` individually without cloning an entire `secp256k1_context` in order to keep the code simple. Cloning the full `secp256k1_context` is still supported. We don't clone these "subcontexts" internally, and I don't see a reason why we would need this. Thoughts?
 - Do we need a `secp256k1_context_prealloc_clone()` to clone into preallocated memory?
 - One disadvantage of the approach that I implemented is that the user may call screw it up by calling `secp256k1_context_prealloc_size(flags)` with different flags than `secp256k1_context_prealloc_create(..., flags)`. But I think this is acceptable.
 - todo: Exporting functions in public header (I will do this at the end once we're clear about the other points)
apoelstra: 1. No, it's fine if we lose the ability to clone individual parts of a context object. We only had that separation for code cleanliness.
2. I'd like to have a `secp256k1_context_prealloc_clone` but I don't know if it'd be useful. Just in case.
3. Yeah, if the user is inconsistent with flags they'll get UB. Agreed that this is fine, that's a pretty silly thing to do.
real-or-random:  * squashed
 * implemented cloning into prealloc memory
 * added tests
 * exported API functions

I can drop the last commit which creates a separate header file. I think it's useful because the average user does not need the new function but the new header file somehow violates the idea that additional headers are modules.

We could make the life easier for the user if we store in the context whether we allocated the memory  or the user. (And then we need only one destroy function and the user cannot screw it up.)
gmaxwell: The interface probably needs to document the required alignment of caller memory (at least size that it must be aligned similarly to the result of malloc, up to the largest primitive type).  Unless we want to handle aligning the struct ourselves. 

It would be useful to test this stuff on something that traps on unaligned reads (not x86, which does so only in freaky cases like unaligned SIMD loads using aligned instructions but only when they cross a page boundary)...
real-or-random: > It would be useful to test this stuff on something that traps on unaligned reads (not x86, which just runs slower but traps only in freaky cases like unaligned SIMD loads using aligned instructions but only when they cross a page boundary)...
Indeed. I haven't tested it but this looks promising for x86 and might do to job: https://stackoverflow.com/a/17748435/2725281

> I might also suggest calling the function something like prealloced not prealloc, functions are usually read as verbs that describe what they do. So I would assume that prealloc performs preallocation.
Good idea.

real-or-random: > Indeed. I haven't tested it but this looks promising for x86 and might do to job: https://stackoverflow.com/a/17748435/2725281

Well no. It does what it promises, i.e., throwing SIGBUS at you when access unaligned memory, but there are too many false positives. If anyone is interested: You need to link statically (otherwise you get a SIGBUS in the dynamic linker), better disable openssl tests and don't use gmp (otherwise you get a SIGBUS in gmp). But even then, gcc will leave you with too many unaligned accesses in the binary.

apoelstra: Can you try adding `-mno-unaligned-access` to the CFLAGS?
real-or-random: That flag is not available for x86. I did some digging and I don't think there's a way to force gcc or clang to perform only aligned accesses. Unfortunately, qemu does not help either; it just won't trap.
real-or-random: Renamed the functions and rebased on the changes in #557 
apoelstra: I think we're ready to define a public API for this
apoelstra: ACK except `static` nit.

I don't feel very strongly about it, but I'd prefer we drop the commit that uses a separate header file.
real-or-random: fixed... (Haven't touched the separate header so far, I can still do that.)
real-or-random:  * rebased on master (no code changes besides merging)
 * squashed fixups (no code changes)
 * squashed the "rename from _prealloc to _preallocated" commit into the individual commits (no code changes) 
sipa: Concept ACK
real-or-random: addressed @sipa's comments
real-or-random: I forced-push (only changed the fixups). Changes:  
 * Replaced CHECKs by VERIFY_CHECKS in manual_alloc() because we use CHECKs only in tests AFAIU
 * Removed "Unreachable" comments... They're kind of inaccurate. This really depends on the behavior of the callbacks.
 * Added a note to the docs that malloc() is at most called once in the normal create and clone functions (as suggested by @gmaxwell) 

cc @apoelstra 
apoelstra: utACK nitfixes
gmaxwell: > because we use CHECKs only in tests AFAIU

This concerned me some but so far I don't see a case where it's obviously wrong yet (haven't reviewed the whole thing yet).

But I think it might be useful to comment on runtime failure handling works.  I think our design is this:  The program can find itself in an impossible state (IS) as a result of memory corruption/hardware fault, miscompilation, operating system failure, or a serious programming error in the library itself or the caller (e.g. malloc fails, a non-nullable argument is null, a loop is out of range etc.).

A few kinds of IS get detected at runtime, particularly ones which get detected as a product of tests at the API boundary which wouldn't make any sense to make VERIFY_CHECKS (for multiple reasons including that those are only used in our own tests and couldn't detect bad interactions with a caller).

It's also generally considered impolite for libraries to be abort() prone: Many libraries haven't given this stuff much thought and have sometimes called abort() for normal error handling rather than actual corruption. You could imagine a signature verifier that aborted if the DER input didn't deserialize, instead of just returning false... and imagine how cross an application author would be to discover that behaviour in the field.  Even when abort() usage is intended to be limited to impossible states, sometimes the library authors make a mistake and a state is both possible and otherwise harmless... this, and performance, is why we prefer to do most of our impossible state detection via VERIFY_CHECKs that run only in our tests where mistakes in their logic won't introduce real bugs. So we generally only want to runtime detect IS in cases that have the best tradeoffs:  Cases that are really unambiguously wrong e.g. a null pointer we're about to dereference vs some complicated algebraic condition  and cases that arise out of interactions with the caller since our tests can't discover errors in the caller or in caller-library interactions are both candidates for runtime detection.

When an IS is detected we certainly don't want to just continue on like nothing bad happened. But we can't just return errors: (1) The IS may be detected in a location where it is not sensible to return an error (after all, the error is "impossible" so bending the design to accommodate returning might be a bad change), (2) the IS may happen inside an external interface which "cannot fail". (3) Returning on an IS could be confused for a normal false condition like an invalid signature (so IS happens and a CHECKSIG NOT passes when it shouldn't), (4) because IS is "impossible" applications would never be tested for handling them safely in any case. (5)  If an IS happened it may be the case that the entire process is dangerously corrupted and couldn't be trusted to do much error handling anyway.

So that's all well and good and suggests for abort() being the only reasonable way to handle an IS when detected.  However, even abort() may not be the safest thing for any particular application to do. For some device abort() might infinite loop and what we need to do is trigger a hardware reset. Aborting on a failure might, in some case, just end up bricking the device-- perhaps the IS failure should cause the device to restart with a null config.  It might amplify a benign bug in an optional component into a massive DOS, etc.  So we offer a callback to allow custom handling of these states.   The reason that we don't use CHECK in the library code is because we instead use error callbacks to allow that customization.

But what happens if the IS prevents us from getting access to the callback? E.g. what if our context is uninitialized or the callback pointer is null?   That is a case where using directly calling a default error handler is justified:  silently proceeding in a known corrupted state is still something we don't want to do. But we'd want to minimize the number of locations that do that and document, so that specialized applications that really want to be unconditionally abort() free, can go and make it happen and handle the cases that can't be addressed via a callback in whatever way makes sense for them. (E.g. by replacing the default handler with one suitable for their device).

Right now we have some cases where we can't use the callback where use the default handler, and a bunch of other places where we are essentially relying on a null pointer dereference to work well enough (e.g. a dereference of ctx) for the purpose of stopping execution.  Probably more of these places should be turned into explicitly calling the default handler because on some systems (esp embedded ones) a null pointer dereference will not crash.

CHECK is used in the tests because we can do whatever we want in the tests... and aborting on test failure is fine and generally interacts well with dynamic instrumentation tools like valgrind/afl/etc. 
real-or-random: fixed (and improved the comment about a single malloc too).

I can squash if someone else wants to have a closer look.
real-or-random: Okay, I changed my mind and squashed.
sipa: utACK, I really like the approach used to represent context objects now as a single allocated blob.
real-or-random: Addressed @sipa's comment
real-or-random: (Closed and opened to trigger travis rebuild)
elichai: This enables using the library without it calling any mallocs, right?
If so then this would be awesome to `no-std` environments :) 
real-or-random: > This enables using the library without it calling any mallocs, right?
> If so then this would be awesome for `no-std` environments :)

Correct.
sipa: @gmaxwell Anything that's left to address here?
gmaxwell: ACK.  The documentation for this however need some guidance on the lifetime management of the caller provided memory (it could be done in another PR but it must be done).

It should make clear that the caller is obligated to make sure the buffer it provides lives at least as long as the ctx object and while the context exists the buffer is the exclusive property of that context (e.g. you cannot move it, you cannot create two contexts using the same buffer)... and that after destroying the context the caller it's up to the caller to free the buffer (if required).

[Basically don't assume that the user has a feel for how memory management works... everything that I just described is obvious if you have any idea of how it would be implemented, but we can't assume that the user has given it that much thought... or they might be use to programming languages that manage lifetimes for them]

We might also want to include a note that it's easier and less failure prone to use the normal functions in environments where dynamic allocation isn't a problem.
real-or-random: @gmaxwell I added a commit to explain all of this. Can you re-ACK if it's okay?
elichai: Any updates here? both this and #595 are what's missing to use this library in a `no-std` environment 
real-or-random: I pushed a fixup because more warnings appeared with gcc and ARM as target. I can squash when you looked at the diff / whenever you want.
gmaxwell: ACK the fixups ack the text. 

Patch:
diff --git a/Makefile.am b/Makefile.am
index 9e5b7dc..21df09f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,6 +8,7 @@ else
 JNI_LIB =
 endif
 include_HEADERS = include/secp256k1.h
+include_HEADERS += include/secp256k1_preallocated.h
 noinst_HEADERS =
 noinst_HEADERS += src/scalar.h
 noinst_HEADERS += src/scalar_4x64.h
diff --git a/include/secp256k1.h b/include/secp256k1.h
index 43af09c..e0d87a1 100644
--- a/include/secp256k1.h
+++ b/include/secp256k1.h
@@ -33,9 +33,10 @@ extern "C" {
  *  verification).
  *
  *  A constructed context can safely be used from multiple threads
- *  simultaneously, but API call that take a non-const pointer to a context
+ *  simultaneously, but API calls that take a non-const pointer to a context
  *  need exclusive access to it. In particular this is the case for
- *  secp256k1_context_destroy and secp256k1_context_randomize.
+ *  secp256k1_context_destroy, secp256k1_context_preallocated_destroy,
+ *  and secp256k1_context_randomize.
  *
  *  Regarding randomization, either do it once at creation time (in which case
  *  you do not need any locking for the other calls), or use a read-write lock.
@@ -163,7 +164,8 @@ typedef int (*secp256k1_nonce_function)(
 #define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9)
 #define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8)
 
-/** Flags to pass to secp256k1_context_create. */
+/** Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
+ *  secp256k1_context_preallocated_create. */
 #define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
 #define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
 #define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT)
@@ -186,7 +188,11 @@ typedef int (*secp256k1_nonce_function)(
  */
 SECP256K1_API extern const secp256k1_context *secp256k1_context_no_precomp;
 
-/** Create a secp256k1 context object.
+/** Create a secp256k1 context object (in dynamically allocated memory).
+ *
+ *  This function uses malloc to allocate memory. It is guaranteed that malloc is
+ *  called at most once for every call of this function. If you need to avoid dynamic
+ *  memory allocation entirely, see the functions in secp256k1_preallocated.h.
  *
  *  Returns: a newly created context object.
  *  In:      flags: which parts of the context to initialize.
@@ -197,7 +203,11 @@ SECP256K1_API secp256k1_context* secp256k1_context_create(
     unsigned int flags
 ) SECP256K1_WARN_UNUSED_RESULT;
 
-/** Copies a secp256k1 context object.
+/** Copy a secp256k1 context object (into dynamically allocated memory).
+ *
+ *  This function uses malloc to allocate memory. It is guaranteed that malloc is
+ *  called at most once for every call of this function. If you need to avoid dynamic
+ *  memory allocation entirely, see the functions in secp256k1_preallocated.h.
  *
  *  Returns: a newly created context object.
  *  Args:    ctx: an existing context to copy (cannot be NULL)
@@ -206,10 +216,18 @@ SECP256K1_API secp256k1_context* secp256k1_context_clone(
     const secp256k1_context* ctx
 ) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
 
-/** Destroy a secp256k1 context object.
+/** Destroy a secp256k1 context object (created in dynamically allocated memory).
  *
  *  The context pointer may not be used afterwards.
- *  Args:   ctx: an existing context to destroy (cannot be NULL)
+ *
+ *  The context to destroy must have been created using secp256k1_context_create
+ *  or secp256k1_context_clone. If the context has instead been created using
+ *  secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone, the
+ *  behaviour is undefined. In that case, secp256k1_context_preallocated_destroy must
+ *  be used instead.
+ *
+ *  Args:   ctx: an existing context to destroy, constructed using
+ *               secp256k1_context_create or secp256k1_context_clone
  */
 SECP256K1_API void secp256k1_context_destroy(
     secp256k1_context* ctx
@@ -636,7 +654,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_mul(
  * contexts not initialized for signing; then it will have no effect and return 1.
  *
  * You should call this after secp256k1_context_create or
- * secp256k1_context_clone, and may call this repeatedly afterwards.
+ * secp256k1_context_clone (and secp256k1_context_preallocated_create or
+ * secp256k1_context_clone, resp.), and you may call this repeatedly afterwards.
  */
 SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize(
     secp256k1_context* ctx,
diff --git a/include/secp256k1_preallocated.h b/include/secp256k1_preallocated.h
new file mode 100644
index 0000000..0fb64a5
--- /dev/null
+++ b/include/secp256k1_preallocated.h
@@ -0,0 +1,128 @@
+#ifndef SECP256K1_PREALLOCATED_H
+#define SECP256K1_PREALLOCATED_H
+
+#include "secp256k1.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The module provided by this header file is intended for settings in which it
+ * is not possible or desirable to rely on dynamic memory allocation. It provides
+ * functions for creating, cloning, and destroying secp256k1 context objects in a
+ * contiguous fixed-size block of memory provided by the caller.
+ *
+ * Context objects created by functions in this module can be used like contexts
+ * objects created by functions in secp256k1.h, i.e., they can be passed to any
+ * API function that excepts a context object (see secp256k1.h for details). The
+ * only exception is that context objects created by functions in this module
+ * must be destroyed using secp256k1_context_preallocated_destroy (in this
+ * module) instead of secp256k1_context_destroy (in secp256k1.h).
+ *
+ * It is guaranteed that functions in by this module will not call malloc or its
+ * friends realloc, calloc, and free.
+ */
+
+/** Determine the memory size of a secp256k1 context object to be created in
+ *  caller-provided memory.
+ *
+ *  The purpose of this function is to determine how much memory must be provided
+ *  to secp256k1_context_preallocated_create.
+ *
+ *  Returns: the required size of the caller-provided memory block
+ *  In:      flags:    which parts of the context to initialize.
+ */
+SECP256K1_API size_t secp256k1_context_preallocated_size(
+    unsigned int flags
+) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Create a secp256k1 context object in caller-provided memory.
+ *
+ *  The caller must provide a pointer to a rewritable contiguous block of memory
+ *  of size at least secp256k1_context_preallocated_size(flags) bytes, suitably
+ *  aligned to hold an object of any type.
+ *
+ *  The block of memory is exclusively owned by the created context object during
+ *  the lifetime of this context object, which begins with the call to this
+ *  function and ends when a call to secp256k1_context_preallocated_destroy
+ *  (which destroys the context object again) returns. During the lifetime of the
+ *  context object, the caller is obligated not to access this block of memory,
+ *  i.e., the caller may not read or write the memory, e.g., by copying the memory
+ *  contents to a different location or trying to create a second context object
+ *  in the memory. In simpler words, the prealloc pointer (or any pointer derived
+ *  from it) should not be used during the lifetime of the context object.
+ *
+ *  Returns: a newly created context object.
+ *  In:      prealloc: a pointer to a rewritable contiguous block of memory of
+ *                     size at least secp256k1_context_preallocated_size(flags)
+ *                     bytes, as detailed above (cannot be NULL)
+ *           flags:    which parts of the context to initialize.
+ *
+ *  See also secp256k1_context_randomize (in secp256k1.h)
+ *  and secp256k1_context_preallocated_destroy.
+ */
+SECP256K1_API secp256k1_context* secp256k1_context_preallocated_create(
+    void* prealloc,
+    unsigned int flags
+) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Determine the memory size of a secp256k1 context object to be copied into
+ *  caller-provided memory.
+ *
+ *  Returns: the required size of the caller-provided memory block.
+ *  In:      ctx: an existing context to copy (cannot be NULL)
+ */
+SECP256K1_API size_t secp256k1_context_preallocated_clone_size(
+    const secp256k1_context* ctx
+) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Copy a secp256k1 context object into caller-provided memory.
+ *
+ *  The caller must provide a pointer to a rewritable contiguous block of memory
+ *  of size at least secp256k1_context_preallocated_size(flags) bytes, suitably
+ *  aligned to hold an object of any type.
+ *
+ *  The block of memory is exclusively owned by the created context object during
+ *  the lifetime of this context object, see the description of
+ *  secp256k1_context_preallocated_create for details.
+ *
+ *  Returns: a newly created context object.
+ *  Args:    ctx:      an existing context to copy (cannot be NULL)
+ *  In:      prealloc: a pointer to a rewritable contiguous block of memory of
+ *                     size at least secp256k1_context_preallocated_size(flags)
+ *                     bytes, as detailed above (cannot be NULL)
+ */
+SECP256K1_API secp256k1_context* secp256k1_context_preallocated_clone(
+    const secp256k1_context* ctx,
+    void* prealloc
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Destroy a secp256k1 context object that has been created in
+ *  caller-provided memory.
+ *
+ *  The context pointer may not be used afterwards.
+ *
+ *  The context to destroy must have been created using
+ *  secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone.
+ *  If the context has instead been created using secp256k1_context_create or
+ *  secp256k1_context_clone, the behaviour is undefined. In that case,
+ *  secp256k1_context_destroy must be used instead.
+ *
+ *  If required, it is the responsibility of the caller to deallocate the block
+ *  of memory properly after this function returns, e.g., by calling free on the
+ *  preallocated pointer given to secp256k1_context_preallocated_create or
+ *  secp256k1_context_preallocated_clone.
+ *
+ *  Args:   ctx: an existing context to destroy, constructed using
+ *               secp256k1_context_preallocated_create or
+ *               secp256k1_context_preallocated_clone (cannot be NULL)
+ */
+SECP256K1_API void secp256k1_context_preallocated_destroy(
+    secp256k1_context* ctx
+);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_PREALLOCATED_H */
diff --git a/src/ecmult.h b/src/ecmult.h
index 3d75a96..29fe0b0 100644
--- a/src/ecmult.h
+++ b/src/ecmult.h
@@ -20,10 +20,10 @@ typedef struct {
 #endif
 } secp256k1_ecmult_context;
 
+static const size_t SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE;
 static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx);
-static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const secp256k1_callback *cb);
-static void secp256k1_ecmult_context_clone(secp256k1_ecmult_context *dst,
-                                           const secp256k1_ecmult_context *src, const secp256k1_callback *cb);
+static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, void **prealloc);
+static void secp256k1_ecmult_context_finalize_memcpy(secp256k1_ecmult_context *dst, const secp256k1_ecmult_context *src);
 static void secp256k1_ecmult_context_clear(secp256k1_ecmult_context *ctx);
 static int secp256k1_ecmult_context_is_built(const secp256k1_ecmult_context *ctx);
 
diff --git a/src/ecmult_gen.h b/src/ecmult_gen.h
index 7564b70..b136e94 100644
--- a/src/ecmult_gen.h
+++ b/src/ecmult_gen.h
@@ -28,10 +28,10 @@ typedef struct {
     secp256k1_gej initial;
 } secp256k1_ecmult_gen_context;
 
+static const size_t SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE;
 static void secp256k1_ecmult_gen_context_init(secp256k1_ecmult_gen_context* ctx);
-static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context* ctx, const secp256k1_callback* cb);
-static void secp256k1_ecmult_gen_context_clone(secp256k1_ecmult_gen_context *dst,
-                                               const secp256k1_ecmult_gen_context* src, const secp256k1_callback* cb);
+static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context* ctx, void **prealloc);
+static void secp256k1_ecmult_gen_context_finalize_memcpy(secp256k1_ecmult_gen_context *dst, const secp256k1_ecmult_gen_context* src);
 static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context* ctx);
 static int secp256k1_ecmult_gen_context_is_built(const secp256k1_ecmult_gen_context* ctx);
 
diff --git a/src/ecmult_gen_impl.h b/src/ecmult_gen_impl.h
index d64505d..f818d45 100644
--- a/src/ecmult_gen_impl.h
+++ b/src/ecmult_gen_impl.h
@@ -7,6 +7,7 @@
 #ifndef SECP256K1_ECMULT_GEN_IMPL_H
 #define SECP256K1_ECMULT_GEN_IMPL_H
 
+#include "util.h"
 #include "scalar.h"
 #include "group.h"
 #include "ecmult_gen.h"
@@ -14,23 +15,32 @@
 #ifdef USE_ECMULT_STATIC_PRECOMPUTATION
 #include "ecmult_static_context.h"
 #endif
+
+#ifndef USE_ECMULT_STATIC_PRECOMPUTATION
+    static const size_t SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE = ROUND_TO_ALIGN(sizeof(*((secp256k1_ecmult_gen_context*) NULL)->prec));
+#else
+    static const size_t SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE = 0;
+#endif
+
 static void secp256k1_ecmult_gen_context_init(secp256k1_ecmult_gen_context *ctx) {
     ctx->prec = NULL;
 }
 
-static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx, const secp256k1_callback* cb) {
+static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx, void **prealloc) {
 #ifndef USE_ECMULT_STATIC_PRECOMPUTATION
     secp256k1_ge prec[1024];
     secp256k1_gej gj;
     secp256k1_gej nums_gej;
     int i, j;
+    size_t const prealloc_size = SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE;
+    void* const base = *prealloc;
 #endif
 
     if (ctx->prec != NULL) {
         return;
     }
 #ifndef USE_ECMULT_STATIC_PRECOMPUTATION
-    ctx->prec = (secp256k1_ge_storage (*)[64][16])checked_malloc(cb, sizeof(*ctx->prec));
+    ctx->prec = (secp256k1_ge_storage (*)[64][16])manual_alloc(prealloc, prealloc_size, base, prealloc_size);
 
     /* get the generator */
     secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g);
@@ -85,7 +95,7 @@ static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx
         }
     }
 #else
-    (void)cb;
+    (void)prealloc;
     ctx->prec = (secp256k1_ge_storage (*)[64][16])secp256k1_ecmult_static_context;
 #endif
     secp256k1_ecmult_gen_blind(ctx, NULL);
@@ -95,27 +105,18 @@ static int secp256k1_ecmult_gen_context_is_built(const secp256k1_ecmult_gen_cont
     return ctx->prec != NULL;
 }
 
-static void secp256k1_ecmult_gen_context_clone(secp256k1_ecmult_gen_context *dst,
-                                               const secp256k1_ecmult_gen_context *src, const secp256k1_callback* cb) {
-    if (src->prec == NULL) {
-        dst->prec = NULL;
-    } else {
+static void secp256k1_ecmult_gen_context_finalize_memcpy(secp256k1_ecmult_gen_context *dst, const secp256k1_ecmult_gen_context *src) {
 #ifndef USE_ECMULT_STATIC_PRECOMPUTATION
-        dst->prec = (secp256k1_ge_storage (*)[64][16])checked_malloc(cb, sizeof(*dst->prec));
-        memcpy(dst->prec, src->prec, sizeof(*dst->prec));
+    if (src->prec != NULL) {
+        /* We cast to void* first to suppress a -Wcast-align warning. */
+        dst->prec = (secp256k1_ge_storage (*)[64][16])(void*)((unsigned char*)dst + ((unsigned char*)src->prec - (unsigned char*)src));
+    }
 #else
-        (void)cb;
-        dst->prec = src->prec;
+    (void)dst, (void)src;
 #endif
-        dst->initial = src->initial;
-        dst->blind = src->blind;
-    }
 }
 
 static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context *ctx) {
-#ifndef USE_ECMULT_STATIC_PRECOMPUTATION
-    free(ctx->prec);
-#endif
     secp256k1_scalar_clear(&ctx->blind);
     secp256k1_gej_clear(&ctx->initial);
     ctx->prec = NULL;
diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h
index a93a0df..b60b1f8 100644
--- a/src/ecmult_impl.h
+++ b/src/ecmult_impl.h
@@ -10,6 +10,7 @@
 #include <string.h>
 #include <stdint.h>
 
+#include "util.h"
 #include "group.h"
 #include "scalar.h"
 #include "ecmult.h"
@@ -310,6 +311,13 @@ static void secp256k1_ecmult_odd_multiples_table_storage_var(const int n, secp25
     } \
 } while(0)
 
+static const size_t SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE =
+    ROUND_TO_ALIGN(sizeof((*((secp256k1_ecmult_context*) NULL)->pre_g)[0]) * ECMULT_TABLE_SIZE(WINDOW_G))
+#ifdef USE_ENDOMORPHISM
+    + ROUND_TO_ALIGN(sizeof((*((secp256k1_ecmult_context*) NULL)->pre_g_128)[0]) * ECMULT_TABLE_SIZE(WINDOW_G))
+#endif
+    ;
+
 static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx) {
     ctx->pre_g = NULL;
 #ifdef USE_ENDOMORPHISM
@@ -317,8 +325,10 @@ static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx) {
 #endif
 }
 
-static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const secp256k1_callback *cb) {
+static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, void **prealloc) {
     secp256k1_gej gj;
+    void* const base = *prealloc;
+    size_t const prealloc_size = SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE;
 
     if (ctx->pre_g != NULL) {
         return;
@@ -331,7 +341,7 @@ static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const
         size_t size = sizeof((*ctx->pre_g)[0]) * ((size_t)ECMULT_TABLE_SIZE(WINDOW_G));
         /* check for overflow */
         VERIFY_CHECK(size / sizeof((*ctx->pre_g)[0]) == ((size_t)ECMULT_TABLE_SIZE(WINDOW_G)));
-        ctx->pre_g = (secp256k1_ge_storage (*)[])checked_malloc(cb, size);
+        ctx->pre_g = (secp256k1_ge_storage (*)[])manual_alloc(prealloc, sizeof((*ctx->pre_g)[0]) * ECMULT_TABLE_SIZE(WINDOW_G), base, prealloc_size);
     }
 
     /* precompute the tables with odd multiples */
@@ -345,7 +355,7 @@ static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const
         size_t size = sizeof((*ctx->pre_g_128)[0]) * ((size_t) ECMULT_TABLE_SIZE(WINDOW_G));
         /* check for overflow */
         VERIFY_CHECK(size / sizeof((*ctx->pre_g_128)[0]) == ((size_t)ECMULT_TABLE_SIZE(WINDOW_G)));
-        ctx->pre_g_128 = (secp256k1_ge_storage (*)[])checked_malloc(cb, size);
+        ctx->pre_g_128 = (secp256k1_ge_storage (*)[])manual_alloc(prealloc, sizeof((*ctx->pre_g_128)[0]) * ECMULT_TABLE_SIZE(WINDOW_G), base, prealloc_size);
 
         /* calculate 2^128*generator */
         g_128j = gj;
@@ -357,22 +367,14 @@ static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const
 #endif
 }
 
-static void secp256k1_ecmult_context_clone(secp256k1_ecmult_context *dst,
-                                           const secp256k1_ecmult_context *src, const secp256k1_callback *cb) {
-    if (src->pre_g == NULL) {
-        dst->pre_g = NULL;
-    } else {
-        size_t size = sizeof((*dst->pre_g)[0]) * ((size_t)ECMULT_TABLE_SIZE(WINDOW_G));
-        dst->pre_g = (secp256k1_ge_storage (*)[])checked_malloc(cb, size);
-        memcpy(dst->pre_g, src->pre_g, size);
+static void secp256k1_ecmult_context_finalize_memcpy(secp256k1_ecmult_context *dst, const secp256k1_ecmult_context *src) {
+    if (src->pre_g != NULL) {
+        /* We cast to void* first to suppress a -Wcast-align warning. */
+        dst->pre_g = (secp256k1_ge_storage (*)[])(void*)((unsigned char*)dst + ((unsigned char*)(src->pre_g) - (unsigned char*)src));
     }
 #ifdef USE_ENDOMORPHISM
-    if (src->pre_g_128 == NULL) {
-        dst->pre_g_128 = NULL;
-    } else {
-        size_t size = sizeof((*dst->pre_g_128)[0]) * ((size_t)ECMULT_TABLE_SIZE(WINDOW_G));
-        dst->pre_g_128 = (secp256k1_ge_storage (*)[])checked_malloc(cb, size);
-        memcpy(dst->pre_g_128, src->pre_g_128, size);
+    if (src->pre_g_128 != NULL) {
+        dst->pre_g_128 = (secp256k1_ge_storage (*)[])(void*)((unsigned char*)dst + ((unsigned char*)(src->pre_g_128) - (unsigned char*)src));
     }
 #endif
 }
@@ -382,10 +384,6 @@ static int secp256k1_ecmult_context_is_built(const secp256k1_ecmult_context *ctx
 }
 
 static void secp256k1_ecmult_context_clear(secp256k1_ecmult_context *ctx) {
-    free(ctx->pre_g);
-#ifdef USE_ENDOMORPHISM
-    free(ctx->pre_g_128);
-#endif
     secp256k1_ecmult_context_init(ctx);
 }
 
@@ -442,7 +440,7 @@ static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a,
     CHECK(carry == 0);
     while (bit < 256) {
         CHECK(secp256k1_scalar_get_bits(&s, bit++, 1) == 0);
-    } 
+    }
 #endif
     return last_set_bit + 1;
 }
diff --git a/src/gen_context.c b/src/gen_context.c
index 87d296e..82c605c 100644
--- a/src/gen_context.c
+++ b/src/gen_context.c
@@ -8,6 +8,7 @@
 
 #include "basic-config.h"
 #include "include/secp256k1.h"
+#include "util.h"
 #include "field_impl.h"
 #include "scalar_impl.h"
 #include "group_impl.h"
@@ -26,6 +27,7 @@ static const secp256k1_callback default_error_callback = {
 
 int main(int argc, char **argv) {
     secp256k1_ecmult_gen_context ctx;
+    void *prealloc, *base;
     int inner;
     int outer;
     FILE* fp;
@@ -38,15 +40,17 @@ int main(int argc, char **argv) {
         fprintf(stderr, "Could not open src/ecmult_static_context.h for writing!\n");
         return -1;
     }
-    
+
     fprintf(fp, "#ifndef _SECP256K1_ECMULT_STATIC_CONTEXT_\n");
     fprintf(fp, "#define _SECP256K1_ECMULT_STATIC_CONTEXT_\n");
     fprintf(fp, "#include \"src/group.h\"\n");
     fprintf(fp, "#define SC SECP256K1_GE_STORAGE_CONST\n");
     fprintf(fp, "static const secp256k1_ge_storage secp256k1_ecmult_static_context[64][16] = {\n");
 
+    base = checked_malloc(&default_error_callback, SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE);
+    prealloc = base;
     secp256k1_ecmult_gen_context_init(&ctx);
-    secp256k1_ecmult_gen_context_build(&ctx, &default_error_callback);
+    secp256k1_ecmult_gen_context_build(&ctx, &prealloc);
     for(outer = 0; outer != 64; outer++) {
         fprintf(fp,"{\n");
         for(inner = 0; inner != 16; inner++) {
@@ -65,10 +69,11 @@ int main(int argc, char **argv) {
     }
     fprintf(fp,"};\n");
     secp256k1_ecmult_gen_context_clear(&ctx);
-    
+    free(base);
+
     fprintf(fp, "#undef SC\n");
     fprintf(fp, "#endif\n");
     fclose(fp);
-    
+
     return 0;
 }
diff --git a/src/scratch_impl.h b/src/scratch_impl.h
index abed713..5049084 100644
--- a/src/scratch_impl.h
+++ b/src/scratch_impl.h
@@ -7,14 +7,9 @@
 #ifndef _SECP256K1_SCRATCH_IMPL_H_
 #define _SECP256K1_SCRATCH_IMPL_H_
 
+#include "util.h"
 #include "scratch.h"
 
-/* Using 16 bytes alignment because common architectures never have alignment
- * requirements above 8 for any of the types we care about. In addition we
- * leave some room because currently we don't care about a few bytes.
- * TODO: Determine this at configure time. */
-#define ALIGNMENT 16
-
 static secp256k1_scratch* secp256k1_scratch_create(const secp256k1_callback* error_callback, size_t max_size) {
     secp256k1_scratch* ret = (secp256k1_scratch*)checked_malloc(error_callback, sizeof(*ret));
     if (ret != NULL) {
@@ -71,7 +66,7 @@ static void secp256k1_scratch_deallocate_frame(secp256k1_scratch* scratch) {
 static void *secp256k1_scratch_alloc(secp256k1_scratch* scratch, size_t size) {
     void *ret;
     size_t frame = scratch->frame - 1;
-    size = ((size + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT;
+    size = ROUND_TO_ALIGN(size);
 
     if (scratch->frame == 0 || size + scratch->offset[frame] > scratch->frame_size[frame]) {
         return NULL;
diff --git a/src/secp256k1.c b/src/secp256k1.c
index 73adc6e..edfe1f3 100644
--- a/src/secp256k1.c
+++ b/src/secp256k1.c
@@ -5,6 +5,7 @@
  **********************************************************************/
 
 #include "include/secp256k1.h"
+#include "include/secp256k1_preallocated.h"
 
 #include "util.h"
 #include "num_impl.h"
@@ -64,15 +65,50 @@ static const secp256k1_context secp256k1_context_no_precomp_ = {
 };
 const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_no_precomp_;
 
-secp256k1_context* secp256k1_context_create(unsigned int flags) {
-    secp256k1_context* ret = (secp256k1_context*)checked_malloc(&default_error_callback, sizeof(secp256k1_context));
+size_t secp256k1_context_preallocated_size(unsigned int flags) {
+    size_t ret = ROUND_TO_ALIGN(sizeof(secp256k1_context));
+
+    if (EXPECT((flags & SECP256K1_FLAGS_TYPE_MASK) != SECP256K1_FLAGS_TYPE_CONTEXT, 0)) {
+            secp256k1_callback_call(&default_illegal_callback,
+                                    "Invalid flags");
+            return 0;
+    }
+
+    if (flags & SECP256K1_FLAGS_BIT_CONTEXT_SIGN) {
+        ret += SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE;
+    }
+    if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) {
+        ret += SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE;
+    }
+    return ret;
+}
+
+size_t secp256k1_context_preallocated_clone_size(const secp256k1_context* ctx) {
+    size_t ret = ROUND_TO_ALIGN(sizeof(secp256k1_context));
+    VERIFY_CHECK(ctx != NULL);
+    if (secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)) {
+        ret += SECP256K1_ECMULT_GEN_CONTEXT_PREALLOCATED_SIZE;
+    }
+    if (secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)) {
+        ret += SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE;
+    }
+    return ret;
+}
+
+secp256k1_context* secp256k1_context_preallocated_create(void* prealloc, unsigned int flags) {
+    void* const base = prealloc;
+    size_t prealloc_size;
+    secp256k1_context* ret;
+
+    VERIFY_CHECK(prealloc != NULL);
+    prealloc_size = secp256k1_context_preallocated_size(flags);
+    ret = (secp256k1_context*)manual_alloc(&prealloc, sizeof(secp256k1_context), base, prealloc_size);
     ret->illegal_callback = default_illegal_callback;
     ret->error_callback = default_error_callback;
 
     if (EXPECT((flags & SECP256K1_FLAGS_TYPE_MASK) != SECP256K1_FLAGS_TYPE_CONTEXT, 0)) {
             secp256k1_callback_call(&ret->illegal_callback,
                                     "Invalid flags");
-            free(ret);
             return NULL;
     }
 
@@ -80,30 +116,62 @@ secp256k1_context* secp256k1_context_create(unsigned int flags) {
     secp256k1_ecmult_gen_context_init(&ret->ecmult_gen_ctx);
 
     if (flags & SECP256K1_FLAGS_BIT_CONTEXT_SIGN) {
-        secp256k1_ecmult_gen_context_build(&ret->ecmult_gen_ctx, &ret->error_callback);
+        secp256k1_ecmult_gen_context_build(&ret->ecmult_gen_ctx, &prealloc);
     }
     if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) {
-        secp256k1_ecmult_context_build(&ret->ecmult_ctx, &ret->error_callback);
+        secp256k1_ecmult_context_build(&ret->ecmult_ctx, &prealloc);
+    }
+
+    return (secp256k1_context*) ret;
+}
+
+secp256k1_context* secp256k1_context_create(unsigned int flags) {
+    size_t const prealloc_size = secp256k1_context_preallocated_size(flags);
+    secp256k1_context* ctx = (secp256k1_context*)checked_malloc(&default_error_callback, prealloc_size);
+    if (EXPECT(secp256k1_context_preallocated_create(ctx, flags) == NULL, 0)) {
+        free(ctx);
+        return NULL;
     }
 
+    return ctx;
+}
+
+secp256k1_context* secp256k1_context_preallocated_clone(const secp256k1_context* ctx, void* prealloc) {
+    size_t prealloc_size;
+    secp256k1_context* ret;
+    VERIFY_CHECK(ctx != NULL);
+    ARG_CHECK(prealloc != NULL);
+
+    prealloc_size = secp256k1_context_preallocated_clone_size(ctx);
+    ret = (secp256k1_context*)prealloc;
+    memcpy(ret, ctx, prealloc_size);
+    secp256k1_ecmult_gen_context_finalize_memcpy(&ret->ecmult_gen_ctx, &ctx->ecmult_gen_ctx);
+    secp256k1_ecmult_context_finalize_memcpy(&ret->ecmult_ctx, &ctx->ecmult_ctx);
     return ret;
 }
 
 secp256k1_context* secp256k1_context_clone(const secp256k1_context* ctx) {
-    secp256k1_context* ret = (secp256k1_context*)checked_malloc(&ctx->error_callback, sizeof(secp256k1_context));
-    ret->illegal_callback = ctx->illegal_callback;
-    ret->error_callback = ctx->error_callback;
-    secp256k1_ecmult_context_clone(&ret->ecmult_ctx, &ctx->ecmult_ctx, &ctx->error_callback);
-    secp256k1_ecmult_gen_context_clone(&ret->ecmult_gen_ctx, &ctx->ecmult_gen_ctx, &ctx->error_callback);
+    secp256k1_context* ret;
+    size_t prealloc_size;
+
+    VERIFY_CHECK(ctx != NULL);
+    prealloc_size = secp256k1_context_preallocated_clone_size(ctx);
+    ret = (secp256k1_context*)checked_malloc(&ctx->error_callback, prealloc_size);
+    ret = secp256k1_context_preallocated_clone(ctx, ret);
     return ret;
 }
 
-void secp256k1_context_destroy(secp256k1_context* ctx) {
+void secp256k1_context_preallocated_destroy(secp256k1_context* ctx) {
     CHECK(ctx != secp256k1_context_no_precomp);
     if (ctx != NULL) {
         secp256k1_ecmult_context_clear(&ctx->ecmult_ctx);
         secp256k1_ecmult_gen_context_clear(&ctx->ecmult_gen_ctx);
+    }
+}
 
+void secp256k1_context_destroy(secp256k1_context* ctx) {
+    if (ctx != NULL) {
+        secp256k1_context_preallocated_destroy(ctx);
         free(ctx);
     }
 }
diff --git a/src/util.h b/src/util.h
index e1f5b76..9deb61b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -84,6 +84,47 @@ static SECP256K1_INLINE void *checked_realloc(const secp256k1_callback* cb, void
     return ret;
 }
 
+#if defined(__BIGGEST_ALIGNMENT__)
+#define ALIGNMENT __BIGGEST_ALIGNMENT__
+#else
+/* Using 16 bytes alignment because common architectures never have alignment
+ * requirements above 8 for any of the types we care about. In addition we
+ * leave some room because currently we don't care about a few bytes. */
+#define ALIGNMENT 16
+#endif
+
+#define ROUND_TO_ALIGN(size) (((size + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT)
+
+/* Assume there is a contiguous memory object with bounds [base, base + max_size)
+ * of which the memory range [base, *prealloc_ptr) is already allocated for usage,
+ * where *prealloc_ptr is an aligned pointer. In that setting, this functions
+ * reserves the subobject [*prealloc_ptr, *prealloc_ptr + alloc_size) of
+ * alloc_size bytes by increasing *prealloc_ptr accordingly, taking into account
+ * alignment requirements.
+ *
+ * The function returns an aligned pointer to the newly allocated subobject.
+ *
+ * This is useful for manual memory management: if we're simply given a block
+ * [base, base + max_size), the caller can use this function to allocate memory
+ * in this block and keep track of the current allocation state with *prealloc_ptr.
+ *
+ * It is VERIFY_CHECKed that there is enough space left in the memory object and
+ * *prealloc_ptr is aligned relative to base.
+ */
+static SECP256K1_INLINE void *manual_alloc(void** prealloc_ptr, size_t alloc_size, void* base, size_t max_size) {
+    size_t aligned_alloc_size = ROUND_TO_ALIGN(alloc_size);
+    void* ret;
+    VERIFY_CHECK(prealloc_ptr != NULL);
+    VERIFY_CHECK(*prealloc_ptr != NULL);
+    VERIFY_CHECK(base != NULL);
+    VERIFY_CHECK((unsigned char*)*prealloc_ptr >= (unsigned char*)base);
+    VERIFY_CHECK(((unsigned char*)*prealloc_ptr - (unsigned char*)base) % ALIGNMENT == 0);
+    VERIFY_CHECK((unsigned char*)*prealloc_ptr - (unsigned char*)base + aligned_alloc_size <= max_size);
+    ret = *prealloc_ptr;
+    *((unsigned char**)prealloc_ptr) += aligned_alloc_size;
+    return ret;
+}
+
 /* Macro for restrict, when available and not in a VERIFY build. */
 #if defined(SECP256K1_BUILD) && defined(VERIFY)
 # define SECP256K1_RESTRICT


Test patch:
diff --git a/src/tests.c b/src/tests.c
index 5566560..908858b 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -16,6 +16,7 @@
 
 #include "secp256k1.c"
 #include "include/secp256k1.h"
+#include "include/secp256k1_preallocated.h"
 #include "testrand_impl.h"
 
 #ifdef ENABLE_OPENSSL_TESTS
@@ -137,23 +138,47 @@ void random_scalar_order(secp256k1_scalar *num) {
     } while(1);
 }
 
-void run_context_tests(void) {
+void run_context_tests(int use_prealloc) {
     secp256k1_pubkey pubkey;
     secp256k1_pubkey zero_pubkey;
     secp256k1_ecdsa_signature sig;
     unsigned char ctmp[32];
     int32_t ecount;
     int32_t ecount2;
-    secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
-    secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
-    secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
-    secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+    secp256k1_context *none;
+    secp256k1_context *sign;
+    secp256k1_context *vrfy;
+    secp256k1_context *both;
+    void *none_prealloc = NULL;
+    void *sign_prealloc = NULL;
+    void *vrfy_prealloc = NULL;
+    void *both_prealloc = NULL;
 
     secp256k1_gej pubj;
     secp256k1_ge pub;
     secp256k1_scalar msg, key, nonce;
     secp256k1_scalar sigr, sigs;
 
+    if (use_prealloc) {
+        none_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE));
+        sign_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN));
+        vrfy_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY));
+        both_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY));
+        CHECK(none_prealloc != NULL);
+        CHECK(sign_prealloc != NULL);
+        CHECK(vrfy_prealloc != NULL);
+        CHECK(both_prealloc != NULL);
+        none = secp256k1_context_preallocated_create(none_prealloc, SECP256K1_CONTEXT_NONE);
+        sign = secp256k1_context_preallocated_create(sign_prealloc, SECP256K1_CONTEXT_SIGN);
+        vrfy = secp256k1_context_preallocated_create(vrfy_prealloc, SECP256K1_CONTEXT_VERIFY);
+        both = secp256k1_context_preallocated_create(both_prealloc, SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+    } else {
+        none = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
+        sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
+        vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
+        both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+    }
+
     memset(&zero_pubkey, 0, sizeof(zero_pubkey));
 
     ecount = 0;
@@ -163,14 +188,57 @@ void run_context_tests(void) {
     secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, NULL);
     CHECK(vrfy->error_callback.fn != sign->error_callback.fn);
 
+    /* check if sizes for cloning are consistent */
+    CHECK(secp256k1_context_preallocated_clone_size(none) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE));
+    CHECK(secp256k1_context_preallocated_clone_size(sign) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN));
+    CHECK(secp256k1_context_preallocated_clone_size(vrfy) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY));
+    CHECK(secp256k1_context_preallocated_clone_size(both) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY));
+
     /*** clone and destroy all of them to make sure cloning was complete ***/
     {
         secp256k1_context *ctx_tmp;
 
-        ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_destroy(ctx_tmp);
-        ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_destroy(ctx_tmp);
-        ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_destroy(ctx_tmp);
-        ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_destroy(ctx_tmp);
+        if (use_prealloc) {
+            /* clone into a non-preallocated context and then again into a new preallocated one. */
+            ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(none_prealloc); none_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); CHECK(none_prealloc != NULL);
+            ctx_tmp = none; none = secp256k1_context_preallocated_clone(none, none_prealloc); secp256k1_context_destroy(ctx_tmp);
+
+            ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(sign_prealloc); sign_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); CHECK(sign_prealloc != NULL);
+            ctx_tmp = sign; sign = secp256k1_context_preallocated_clone(sign, sign_prealloc); secp256k1_context_destroy(ctx_tmp);
+
+            ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(vrfy_prealloc); vrfy_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); CHECK(vrfy_prealloc != NULL);
+            ctx_tmp = vrfy; vrfy = secp256k1_context_preallocated_clone(vrfy, vrfy_prealloc); secp256k1_context_destroy(ctx_tmp);
+
+            ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(both_prealloc); both_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); CHECK(both_prealloc != NULL);
+            ctx_tmp = both; both = secp256k1_context_preallocated_clone(both, both_prealloc); secp256k1_context_destroy(ctx_tmp);
+        } else {
+            /* clone into a preallocated context and then again into a new non-preallocated one. */
+            void *prealloc_tmp;
+
+            prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); CHECK(prealloc_tmp != NULL);
+            ctx_tmp = none; none = secp256k1_context_preallocated_clone(none, prealloc_tmp); secp256k1_context_destroy(ctx_tmp);
+            ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(prealloc_tmp);
+
+            prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); CHECK(prealloc_tmp != NULL);
+            ctx_tmp = sign; sign = secp256k1_context_preallocated_clone(sign, prealloc_tmp); secp256k1_context_destroy(ctx_tmp);
+            ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(prealloc_tmp);
+
+            prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); CHECK(prealloc_tmp != NULL);
+            ctx_tmp = vrfy; vrfy = secp256k1_context_preallocated_clone(vrfy, prealloc_tmp); secp256k1_context_destroy(ctx_tmp);
+            ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(prealloc_tmp);
+
+            prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); CHECK(prealloc_tmp != NULL);
+            ctx_tmp = both; both = secp256k1_context_preallocated_clone(both, prealloc_tmp); secp256k1_context_destroy(ctx_tmp);
+            ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_preallocated_destroy(ctx_tmp);
+            free(prealloc_tmp);
+        }
     }
 
     /* Verify that the error callback makes it across the clone. */
@@ -229,10 +297,6 @@ void run_context_tests(void) {
     secp256k1_context_set_illegal_callback(vrfy, NULL, NULL);
     secp256k1_context_set_illegal_callback(sign, NULL, NULL);
 
-    /* This shouldn't leak memory, due to already-set tests. */
-    secp256k1_ecmult_gen_context_build(&sign->ecmult_gen_ctx, NULL);
-    secp256k1_ecmult_context_build(&vrfy->ecmult_ctx, NULL);
-
     /* obtain a working nonce */
     do {
         random_scalar_order_test(&nonce);
@@ -247,12 +311,25 @@ void run_context_tests(void) {
     CHECK(secp256k1_ecdsa_sig_verify(&both->ecmult_ctx, &sigr, &sigs, &pub, &msg));
 
     /* cleanup */
-    secp256k1_context_destroy(none);
-    secp256k1_context_destroy(sign);
-    secp256k1_context_destroy(vrfy);
-    secp256k1_context_destroy(both);
+    if (use_prealloc) {
+        secp256k1_context_preallocated_destroy(none);
+        secp256k1_context_preallocated_destroy(sign);
+        secp256k1_context_preallocated_destroy(vrfy);
+        secp256k1_context_preallocated_destroy(both);
+        free(none_prealloc);
+        free(sign_prealloc);
+        free(vrfy_prealloc);
+        free(both_prealloc);
+    } else {
+        secp256k1_context_destroy(none);
+        secp256k1_context_destroy(sign);
+        secp256k1_context_destroy(vrfy);
+        secp256k1_context_destroy(both);
+    }
     /* Defined as no-op. */
     secp256k1_context_destroy(NULL);
+    secp256k1_context_preallocated_destroy(NULL);
+
 }
 
 void run_scratch_tests(void) {
@@ -5062,7 +5139,8 @@ int main(int argc, char **argv) {
     printf("random seed = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", seed16[0], seed16[1], seed16[2], seed16[3], seed16[4], seed16[5], seed16[6], seed16[7], seed16[8], seed16[9], seed16[10], seed16[11], seed16[12], seed16[13], seed16[14], seed16[15]);
 
     /* initialize */
-    run_context_tests();
+    run_context_tests(0);
+    run_context_tests(1);
     run_scratch_tests();
     ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
     if (secp256k1_rand_bits(1)) {