diff --git a/crypto/finite-field-examples.ts b/crypto/finite-field-examples.ts
new file mode 100644
index 00000000..6aeca112
--- /dev/null
+++ b/crypto/finite-field-examples.ts
@@ -0,0 +1,31 @@
+/**
+ * This file contains some examples of finite fields, to be used for tests
+ */
+import { Fp, Fq, createField } from './finite_field.js';
+
+export { exampleFields };
+
+// some primes
+let pSmall = 101n;
+let pBabybear = (1n << 31n) - 1n;
+let pGoldilocks = (1n << 64n) - (1n << 32n) + 1n;
+let p25519 = (1n << 255n) - 19n;
+let pSecp256k1 = (1n << 256n) - (1n << 32n) - 0b1111010001n;
+let pSecq256k1 = (1n << 256n) - 0x14551231950b75fc4402da1732fc9bebfn;
+let pBls12_381 =
+  0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001n;
+let qBls12_381 =
+  0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001n;
+
+let exampleFields = {
+  Fp,
+  Fq,
+  small: createField(pSmall),
+  babybear: createField(pBabybear),
+  goldilocks: createField(pGoldilocks),
+  f25519: createField(p25519),
+  secp256k1: createField(pSecp256k1),
+  secq256k1: createField(pSecq256k1),
+  bls12_381_base: createField(pBls12_381),
+  bls12_381_scalar: createField(qBls12_381),
+};
diff --git a/crypto/finite-field.unit-test.ts b/crypto/finite-field.unit-test.ts
index c6f7136e..bbfdc577 100644
--- a/crypto/finite-field.unit-test.ts
+++ b/crypto/finite-field.unit-test.ts
@@ -1,21 +1,19 @@
 import { Fp, Fq } from './finite_field.js';
 import assert from 'node:assert/strict';
 import { Random, test } from '../../lib/testing/property.js';
+import { exampleFields } from './finite-field-examples.js';
 
-for (let F of [Fp, Fq]) {
-  // t is computed correctly from p = 2^32 * t + 1
-  assert(F.t * (1n << 32n) + 1n === F.modulus, 't is computed correctly');
+let fields = Object.values(exampleFields);
 
-  // the primitive root of unity is computed correctly as 5^t
-  let generator = 5n;
-  let rootFp = F.power(generator, F.t);
-  assert(rootFp === F.twoadicRoot, 'root of unity is computed correctly');
+for (let F of fields) {
+  // t is computed correctly from p = 2^M * t + 1
+  assert(F.t * (1n << F.M) + 1n === F.modulus, 't, M are computed correctly');
 
   // the primitive roots of unity `r` actually satisfy the equations defining them:
-  let shouldBe1 = F.power(F.twoadicRoot, 1n << 32n);
-  let shouldBeMinus1 = F.power(F.twoadicRoot, 1n << 31n);
-  assert(shouldBe1 === 1n, 'r^(2^32) === 1');
-  assert(shouldBeMinus1 + 1n === F.modulus, 'r^(2^32) === 1');
+  let shouldBe1 = F.power(F.twoadicRoot, 1n << F.M);
+  let shouldBeMinus1 = F.power(F.twoadicRoot, 1n << (F.M - 1n));
+  assert(shouldBe1 === 1n, 'r^(2^M) === 1');
+  assert(shouldBeMinus1 + 1n === F.modulus, 'r^(2^(M-1)) === -1');
 
   // the primitive roots of unity are non-squares
   // -> verifies that the two-adicity is 32, and that they can be used as non-squares in the sqrt algorithm
@@ -57,21 +55,27 @@ for (let F of [Fp, Fq]) {
     } else {
       assert.equal(F.inverse(3n), (p + 1n) / 3n, 'inverse 3');
     }
-    let xInv = F.inverse(x);
-    assert(xInv !== undefined, 'random x is invertible');
-    assert.equal(F.mul(xInv, x), 1n, 'inverse & mul');
+
+    if (x !== 0n) {
+      let xInv = F.inverse(x);
+      assert(xInv !== undefined, 'non-zero is invertible');
+      assert.equal(F.mul(xInv, x), 1n, 'inverse & mul');
+      assert.equal(F.div(y, x), F.mul(y, xInv), 'div & inverse');
+    }
 
     assert.equal(F.div(1n, 0n), undefined, 'div');
     assert.equal(F.div(21n, F.negate(7n)), p - 3n, 'div');
-    assert.equal(F.div(y, x), F.mul(y, xInv), 'div & inverse');
 
     assert.equal(F.square(F.negate(10n)), 100n, 'square');
     let squareX = F.square(x);
     assert(F.isSquare(squareX), 'square + isSquare');
     assert([x, F.negate(x)].includes(F.sqrt(squareX)!), 'square + sqrt');
-    assert(F.isSquare(p - 1n), 'isSquare -1');
-    let i = F.power(F.twoadicRoot, 1n << 30n);
-    assert([i, F.negate(i)].includes(F.sqrt(p - 1n)!), 'sqrt -1');
+
+    if (F.M >= 2n) {
+      assert(F.isSquare(p - 1n), 'isSquare -1');
+      let i = F.power(F.twoadicRoot, 1n << (F.M - 2n));
+      assert([i, F.negate(i)].includes(F.sqrt(p - 1n)!), 'sqrt -1');
+    }
 
     assert.equal(F.power(F.negate(2n), 3n), F.negate(8n), 'power');
     assert.equal(F.power(2n, p - 1n), 1n, 'power mod p-1');
@@ -86,8 +90,11 @@ for (let F of [Fp, Fq]) {
     assert.equal(F.dot([x, y], [y, x]), F.mul(2n, F.mul(x, y)), 'dot');
     assert.equal(F.dot([x, y], [F.negate(y), x]), 0n, 'dot');
 
-    assert(x > 1n << 128n, 'random x is large');
-    assert(x < p - (1n << 128n), 'random x is not small negative');
+    if (p >> 250n) {
+      assert(x > 1n << 128n, 'random x is large');
+      assert(x < p - (1n << 128n), 'random x is not small negative');
+    }
+    assert(x >= 0 && x < p, 'random x is in range');
 
     assert.equal(F.fromNumber(-1), p - 1n, 'fromNumber');
     assert.equal(F.fromBigint(-1n), p - 1n, 'fromBigint');
@@ -98,3 +105,11 @@ for (let F of [Fp, Fq]) {
     assert(!F.equal(5n, p - 5n), 'not equal');
   });
 }
+
+// test that is specialized to Fp, Fq
+for (let F of [Fp, Fq]) {
+  // the primitive root of unity is computed correctly as 5^t
+  let generator = 5n;
+  let rootFp = F.power(generator, F.t);
+  assert(rootFp === F.twoadicRoot, 'root of unity is computed correctly');
+}
diff --git a/crypto/finite_field.ts b/crypto/finite_field.ts
index 3cf52e7a..ce77b867 100644
--- a/crypto/finite_field.ts
+++ b/crypto/finite_field.ts
@@ -1,7 +1,7 @@
 import { bytesToBigInt } from './bigint-helpers.js';
 import { randomBytes } from './random.js';
 
-export { Fp, Fq, FiniteField, p, q, mod, inverse };
+export { createField, Fp, Fq, FiniteField, p, q, mod, inverse };
 
 // CONSTANTS
 
@@ -65,13 +65,13 @@ function inverse(a: bigint, p: bigint) {
   return mod(x, p);
 }
 
-function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint) {
+function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint, M: bigint) {
   // https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm#The_algorithm
   // variable naming is the same as in that link ^
   // Q is what we call `t` elsewhere - the odd factor in p - 1
   // c is a known primitive root of unity
+  // M is the twoadicity = exponent of 2 in factorization of p - 1
   if (n === 0n) return 0n;
-  let M = 32n;
   let t = power(n, (Q - 1n) >> 1n, p); // n^(Q - 1)/2
   let R = mod(t * n, p); // n^((Q - 1)/2 + 1) = n^((Q + 1)/2)
   t = mod(t * R, p); // n^((Q - 1)/2 + (Q + 1)/2) = n^Q
@@ -99,11 +99,11 @@ function isSquare(x: bigint, p: bigint) {
   return sqrt1 === 1n;
 }
 
-function randomField(p: bigint) {
+function randomField(p: bigint, sizeInBytes: number, hiBitMask: number) {
   // strategy: find random 255-bit bigints and use the first that's smaller than p
   while (true) {
-    let bytes = randomBytes(32);
-    bytes[31] &= 0x7f; // zero highest bit, so we get 255 random bits
+    let bytes = randomBytes(sizeInBytes);
+    bytes[sizeInBytes - 1] &= hiBitMask; // zero highest bit, so we get 255 random bits
     let x = bytesToBigInt(bytes);
     if (x < p) return x;
   }
@@ -112,15 +112,34 @@ function randomField(p: bigint) {
 // SPECIALIZATIONS TO FP, FQ
 // these should be mostly trivial
 
-const Fp = createField(p, pMinusOneOddFactor, twoadicRootFp);
-const Fq = createField(q, qMinusOneOddFactor, twoadicRootFq);
+const Fp = createField(p, {
+  oddFactor: pMinusOneOddFactor,
+  twoadicRoot: twoadicRootFp,
+  twoadicity: 32n,
+});
+const Fq = createField(q, {
+  oddFactor: qMinusOneOddFactor,
+  twoadicRoot: twoadicRootFq,
+  twoadicity: 32n,
+});
 type FiniteField = ReturnType<typeof createField>;
 
-function createField(p: bigint, t: bigint, twoadicRoot: bigint) {
+function createField(
+  p: bigint,
+  constants?: { oddFactor: bigint; twoadicRoot: bigint; twoadicity: bigint }
+) {
+  let { oddFactor, twoadicRoot, twoadicity } =
+    constants ?? computeFieldConstants(p);
+  let sizeInBits = p.toString(2).length;
+  let sizeInBytes = Math.ceil(sizeInBits / 8);
+  let sizeHighestByte = sizeInBits - 8 * (sizeInBytes - 1);
+  let hiBitMask = (1 << sizeHighestByte) - 1;
+
   return {
     modulus: p,
-    sizeInBits: 255,
-    t,
+    sizeInBits,
+    t: oddFactor,
+    M: twoadicity,
     twoadicRoot,
     add(x: bigint, y: bigint) {
       return mod(x + y, p);
@@ -152,7 +171,7 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) {
       return isSquare(x, p);
     },
     sqrt(x: bigint) {
-      return sqrt(x, p, t, twoadicRoot);
+      return sqrt(x, p, oddFactor, twoadicRoot, twoadicity);
     },
     power(x: bigint, n: bigint) {
       return power(x, n, p);
@@ -172,7 +191,7 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) {
       return !(x & 1n);
     },
     random() {
-      return randomField(p);
+      return randomField(p, sizeInBytes, hiBitMask);
     },
     fromNumber(x: number) {
       return mod(BigInt(x), p);
@@ -206,3 +225,26 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) {
     },
   };
 }
+
+/**
+ * Compute constants to instantiate a finite field just from the modulus
+ */
+function computeFieldConstants(p: bigint) {
+  // figure out the factorization p - 1 = 2^M * t
+  let oddFactor = p - 1n;
+  let twoadicity = 0n;
+  while ((oddFactor & 1n) === 0n) {
+    oddFactor >>= 1n;
+    twoadicity++;
+  }
+
+  // find z = non-square
+  // start with 2 and increment until we find one
+  let z = 2n;
+  while (isSquare(z, p)) z++;
+
+  // primitive root of unity is z^t
+  let twoadicRoot = power(z, oddFactor, p);
+
+  return { oddFactor, twoadicRoot, twoadicity };
+}