diff --git a/doc.go b/doc.go
index ef6fe62a8262f08587dbd928757518e83c167d55..a8783a1b9cd1cf3a2fbbb6cb15d7ca538ea85f8e 100644
--- a/doc.go
+++ b/doc.go
@@ -1 +1,78 @@
-package pbc
\ No newline at end of file
+/*
+	Package pbc provides structures for building pairing-based cryptosystems. It
+	is a wrapper around the Pairing-Based Cryptography (PBC) Library authored by
+	Ben Lynn (https://crypto.stanford.edu/pbc/).
+
+	This wrapper provides access to all PBC functions. It supports generation of
+	various types of elliptic curves and pairings, element initialization, I/O,
+	and arithmetic. These features can be used to quickly build pairing-based or
+	conventional cryptosystems.
+
+	The PBC library is designed to be extremely fast. Internally, it uses GMP
+	for arbitrary-precision arithmetic. It also includes a wide variety of
+	optimizations that make pairing-based cryptography highly efficient. To
+	improve performance, PBC does not perform type checking to ensure that
+	operations actually make sense. The Go wrapper provides the ability to add
+	compatibility checks to most operations, or to use unchecked elements to
+	maximize performance.
+
+	Since this library provides low-level access to pairing primitives, it is
+	very easy to construct insecure systems. This library is intended to be used
+	by cryptographers or to implement well-analyzed cryptosystems.
+
+	Pairings
+
+	Cryptographic pairings are defined over three mathematical groups: G1, G2,
+	and GT, where each group is typically of the same order r. Additionally, a
+	bilinear map e maps a pair of elements — one from G1 and another from G2 —
+	to an element in GT. This map e has the following additional property:
+
+		For some generator g in G1, generator h in G2, and x and y in Zr:
+		e(gˣ, hʸ) = e(g,h)ˣʸ
+
+	If G1 == G2, then a pairing is said to be symmetric. Otherwise, it is
+	asymmetric.	Pairings can be used to construct a variety of efficient
+	cryptosystems.
+
+	Supported Pairings
+
+	The PBC library currently supports 5 different types of pairings, each with
+	configurable parameters. These types are designated alphabetically, roughly
+	in chronological order of introduction. Type A, D, E, F, and G pairings are
+	implemented in the library. Each type has different time and space
+	requirements. For more information about the types, see the documentation
+	for the corresponding generator calls, or the PBC manual page at
+	https://crypto.stanford.edu/pbc/manual/ch08s03.html.
+
+	Dependencies
+
+	This package must be compiled using cgo. It also requires the installation
+	of GMP and PBC. During the build process, this package will attempt to
+	include <gmp.h> and <pbc/pbc.h>, and then dynamically link to GMP and PBC.
+	It also expects a POSIX-like environment for several C functions. For this
+	reason, this package cannot be used in Windows without a POSIX compatibility
+	layer and a gcc compiler.
+
+	Most systems include a package for GMP. To install GMP in Debian / Ubuntu:
+
+		sudo apt-get install libgmp-dev
+
+	For an RPM installation with YUM:
+
+		sudo yum install gmp
+
+	For installation with FINK (http://www.finkproject.org/) on Mac OS X:
+
+		sudo fink install gmp gmp-shlibs
+
+	For more information or to compile from source, visit https://gmplib.org/
+
+	To install the PBC library, download the appropriate files for your system
+	from https://crypto.stanford.edu/pbc/download.html. The source can be
+	compiled and installed using the usual GNU Build System:
+
+		./configure
+		make
+		make install
+*/
+package pbc
diff --git a/doc_test.go b/doc_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..121f8cd7463dd2f7357cf446dd361b2990444adb
--- /dev/null
+++ b/doc_test.go
@@ -0,0 +1,22 @@
+package pbc
+
+// This example generates a pairing and some random group elements, then applies
+// the pairing operation.
+func Example() {
+	// In a real application, generate this once and publish it
+	params := pbc.GenerateA(160, 512)
+
+	pairing := params.NewPairing()
+
+	// Initialize group elements. pbc automatically handles garbage collection.
+	g := pairing.NewG1()
+	h := pairing.NewG2()
+	x := pairing.NewGT()
+
+	// Generate random group elements and pair them
+	g.Rand()
+	h.Rand()
+	fmt.Printf("g = %v\nh = %v\n", g, h)
+	x.Pair(g, h)
+	fmt.Printf("e(g,h) = %v\n", x)
+}
diff --git a/element.go b/element.go
index 4e02c13f71e23f1787251155c501c476d6d4fbe2..92febd6330cddc7e030fe56328a55364ec925f4f 100644
--- a/element.go
+++ b/element.go
@@ -13,29 +13,33 @@ import (
 )
 
 type Element interface {
+	// NewFieldElement initializes a new element in the same group as this one.
+	// The returned element will be unchecked if and only if this element is.
 	NewFieldElement() Element
 
+	// Methods to directly set the value of the element:
 	Set0() Element
 	Set1() Element
 	SetInt32(int32) Element
 	SetBig(*big.Int) Element
 	Set(Element) Element
 
+	// Methods to hash a value into a group element:
 	SetFromHash([]byte) Element
 	SetFromStringHash(s string, h hash.Hash) Element
 
-	SetBytes([]byte) Element
-	SetXBytes([]byte) Element
-	SetCompressedBytes([]byte) Element
-
+	// SetString sets the value from an exported string representation.
 	SetString(s string, base int) (Element, bool)
 
+	// Methods to support the fmt package's Print and Scan functions:
 	Format(fmt.State, rune)
 	Scan(fmt.ScanState, rune) error
 
+	// Methods to export the element in human-readable format:
 	BigInt() *big.Int
 	String() string
 
+	// Methods to export the element as a sequence of bytes:
 	BytesLen() int
 	Bytes() []byte
 	XBytesLen() int
@@ -43,19 +47,30 @@ type Element interface {
 	CompressedBytesLen() int
 	CompressedBytes() []byte
 
+	// Methods to import an element from a sequence of bytes:
+	SetBytes([]byte) Element
+	SetXBytes([]byte) Element
+	SetCompressedBytes([]byte) Element
+
+	// Methods to retrieve sub-elements (coordinates for points, coefficients
+	// for polynomials):
 	Len() int
 	Item(int) Element
 	X() *big.Int
 	Y() *big.Int
 
+	// Methods to determine the mathematical properties of the element:
 	Is0() bool
 	Is1() bool
 	IsSquare() bool
 	Sign() int
 
+	// Methods to compare elements:
 	Cmp(x Element) int
 	Equals(x Element) bool
 
+	// Methods to perform arithmetic operations. Not all operations are valid
+	// for all groups.
 	Add(x, y Element) Element
 	Sub(x, y Element) Element
 	Mul(x, y Element) Element
@@ -69,6 +84,7 @@ type Element interface {
 	Neg(x Element) Element
 	Invert(x Element) Element
 
+	// Methods to exponentiate elements:
 	PowBig(x Element, i *big.Int) Element
 	PowZn(x, i Element) Element
 	Pow2Big(x Element, i *big.Int, y Element, j *big.Int) Element
@@ -76,117 +92,73 @@ type Element interface {
 	Pow3Big(x Element, i *big.Int, y Element, j *big.Int, z Element, k *big.Int) Element
 	Pow3Zn(x, i, y, j, z, k Element) Element
 
+	// Methods to perform pre-processed exponentiation:
 	PreparePower() Power
 	PowerBig(Power, *big.Int) Element
 	PowerZn(Power, Element) Element
 
+	// Methods to brute-force discrete logarithms in the group:
 	BruteForceDL(g, h Element) Element
 	PollardRhoDL(g, h Element) Element
 
+	// Rand sets the element to a random group element.
 	Rand() Element
 
+	// Pairing operations:
 	Pair(x, y Element) Element
 	ProdPair(elements ...Element) Element
 	ProdPairSlice(x, y []Element) Element
 
+	// Methods to perform pre-processed pairing operations:
 	PreparePairer() Pairer
 	PairerPair(Pairer, Element) Element
 
-	impl() *elementImpl
-}
-
-type elementImpl struct {
-	pairing *pairingImpl
-	data    *C.struct_element_s
-}
-
-type checkedElement struct {
-	unchecked elementImpl
-	fieldPtr  *C.struct_field_s
-	isInteger bool
-}
-
-func (pairing *pairingImpl) NewG1() Element                 { return makeChecked(pairing, G1, pairing.data.G1) }
-func (pairing *pairingImpl) NewG2() Element                 { return makeChecked(pairing, G2, pairing.data.G2) }
-func (pairing *pairingImpl) NewGT() Element                 { return makeChecked(pairing, GT, &pairing.data.GT[0]) }
-func (pairing *pairingImpl) NewZr() Element                 { return makeChecked(pairing, Zr, &pairing.data.Zr[0]) }
-func (pairing *pairingImpl) NewElement(field Field) Element { return makeElement(pairing, field) }
+	// Pairing returns the pairing associated with this element.
+	Pairing() Pairing
 
-func clearElement(element *elementImpl) {
-	println("clearelement")
-	C.element_clear(element.data)
-}
-
-func initElement(element *elementImpl, pairing *pairingImpl, initialize bool, field Field) {
-	element.data = &C.struct_element_s{}
-	element.pairing = pairing
-	if initialize {
-		switch field {
-		case G1:
-			C.element_init_G1(element.data, pairing.data)
-		case G2:
-			C.element_init_G2(element.data, pairing.data)
-		case GT:
-			C.element_init_GT(element.data, pairing.data)
-		case Zr:
-			C.element_init_Zr(element.data, pairing.data)
-		default:
-			panic(ErrUnknownField)
-		}
-	}
-	runtime.SetFinalizer(element, clearElement)
-}
-
-func makeElement(pairing *pairingImpl, field Field) *elementImpl {
-	element := &elementImpl{}
-	initElement(element, pairing, true, field)
-	return element
-}
-
-func makeChecked(pairing *pairingImpl, field Field, fieldPtr *C.struct_field_s) *checkedElement {
-	element := &checkedElement{
-		fieldPtr:  fieldPtr,
-		isInteger: field == Zr,
-	}
-	initElement(&element.unchecked, pairing, true, field)
-	return element
+	data() *C.struct_element_s
 }
 
+// Power stores pre-processed information to quickly exponentiate an element.
+// A Power can be generated for Element x by calling x.PreparePower(). When
+// PowBig or PowZn is called with Element target and integer i, the result of
+// x^i will be stored in target.
 type Power interface {
-	PowBig(i *big.Int) Element
-	PowZn(i Element) Element
+	PowBig(target Element, i *big.Int) Element
+	PowZn(target Element, i Element) Element
 }
 
 type powerImpl struct {
-	target Element
-	data   *C.struct_element_pp_s
+	data *C.struct_element_pp_s
 }
 
-func (power *powerImpl) PowBig(i *big.Int) Element {
-	return power.target.PowerBig(power, i)
+func (power *powerImpl) PowBig(target Element, i *big.Int) Element {
+	return target.PowerBig(power, i)
 }
 
-func (power *powerImpl) PowZn(i Element) Element {
-	return power.target.PowerZn(power, i)
+func (power *powerImpl) PowZn(target Element, i Element) Element {
+	return target.PowerZn(power, i)
 }
 
 func clearPower(power *powerImpl) {
-	println("clearpower")
 	C.element_pp_clear(power.data)
 }
 
-func initPower(target Element) Power {
+func initPower(source Element) Power {
 	power := &powerImpl{
-		target: target,
-		data:   &C.struct_element_pp_s{},
+		data: &C.struct_element_pp_s{},
 	}
-	C.element_pp_init(power.data, target.impl().data)
+	C.element_pp_init(power.data, source.data())
 	runtime.SetFinalizer(power, clearPower)
 	return power
 }
 
+// Pairer stores pre-processed information to quickly pair an element. A Pairer
+// can be generated for Element x by calling x.PreparePairer(). When Pair is
+// called with Elements target and y, the result of e(x,y) will be stored in
+// target.
 type Pairer interface {
-	Pair(target Element, x Element) Element
+	Pair(target Element, y Element) Element
 }
 
 type pairerImpl struct {
@@ -194,12 +166,11 @@ type pairerImpl struct {
 	data   *C.struct_pairing_pp_s
 }
 
-func (pairer *pairerImpl) Pair(target Element, x Element) Element {
-	return target.PairerPair(pairer, x)
+func (pairer *pairerImpl) Pair(target Element, y Element) Element {
+	return target.PairerPair(pairer, y)
 }
 
 func clearPairer(pairer *pairerImpl) {
-	println("clearpairer")
 	C.pairing_pp_clear(pairer.data)
 }
 
@@ -208,7 +179,7 @@ func initPairer(source Element) Pairer {
 		source: source,
 		data:   &C.struct_pairing_pp_s{},
 	}
-	C.pairing_pp_init(pairer.data, source.impl().data, source.impl().pairing.data)
+	C.pairing_pp_init(pairer.data, source.data(), source.Pairing().(*pairingImpl).data)
 	runtime.SetFinalizer(pairer, clearPairer)
 	return pairer
 }
diff --git a/element_checked.go b/element_checked.go
index e0c915bd541b958e063f32bbee538f2a0d5c7b83..07d200991bebadb079117c682e85b7043d0dd551 100644
--- a/element_checked.go
+++ b/element_checked.go
@@ -10,7 +10,24 @@ import (
 	"math/big"
 )
 
-func (el *checkedElement) impl() *elementImpl { return &el.unchecked }
+type checkedElement struct {
+	unchecked uncheckedElement
+	fieldPtr  *C.struct_field_s
+	isInteger bool
+}
+
+func makeChecked(pairing *pairingImpl, field Field, fieldPtr *C.struct_field_s) *checkedElement {
+	element := &checkedElement{
+		fieldPtr:  fieldPtr,
+		isInteger: field == Zr,
+	}
+	initUnchecked(&element.unchecked, pairing, true, field)
+	return element
+}
+
+func (el *checkedElement) data() *C.struct_element_s { return el.unchecked.d }
+
+func (el *checkedElement) Pairing() Pairing { return el.unchecked.pairing }
 
 func element2Checked(x Element) *checkedElement {
 	checked, ok := x.(*checkedElement)
@@ -46,8 +63,8 @@ func (el *checkedElement) checkInteger() {
 func (el *checkedElement) NewFieldElement() Element {
 	newElement := &checkedElement{}
 	*newElement = *el
-	initElement(&newElement.unchecked, el.unchecked.pairing, false, G1)
-	C.element_init_same_as(newElement.unchecked.data, el.unchecked.data)
+	initUnchecked(&newElement.unchecked, el.unchecked.pairing, false, G1)
+	C.element_init_same_as(newElement.unchecked.d, el.unchecked.d)
 	return newElement
 }
 
@@ -146,9 +163,9 @@ func (el *checkedElement) Item(i int) Element {
 	if i >= el.Len() {
 		panic(ErrOutOfRange)
 	}
-	uncheckedData := el.unchecked.Item(i).(*elementImpl)
+	uncheckedData := el.unchecked.Item(i).(*uncheckedElement)
 	item := &checkedElement{
-		fieldPtr:  uncheckedData.data.field,
+		fieldPtr:  uncheckedData.d.field,
 		isInteger: uncheckedData.Len() == 0,
 	}
 	item.unchecked = *uncheckedData
@@ -336,14 +353,14 @@ func (el *checkedElement) ProdPairSlice(x, y []Element) Element {
 
 func (el *checkedElement) PreparePairer() Pairer { return initPairer(el) }
 
-func (el *checkedElement) PairerPair(pairer Pairer, x Element) Element {
+func (el *checkedElement) PairerPair(pairer Pairer, y Element) Element {
 	in1 := element2Checked(pairer.(*pairerImpl).source)
-	in2 := element2Checked(x)
+	in2 := element2Checked(y)
 	pairing := el.unchecked.pairing.data
 	checkFieldsMatch(in1.fieldPtr, pairing.G1)
 	checkFieldsMatch(in2.fieldPtr, pairing.G2)
 	checkFieldsMatch(el.fieldPtr, &pairing.GT[0])
-	el.unchecked.PairerPair(pairer, x)
+	el.unchecked.PairerPair(pairer, y)
 	return el
 }
 
diff --git a/element_fmt.go b/element_fmt.go
index 3ed783065dce6d1f411e8696246cae92700e0937..3c528cf5875197048b48689d2ac94541f95d2ac1 100644
--- a/element_fmt.go
+++ b/element_fmt.go
@@ -20,11 +20,11 @@ import (
 	"unsafe"
 )
 
-func (el *elementImpl) errorFormat(f fmt.State, c rune, err string) {
+func (el *uncheckedElement) errorFormat(f fmt.State, c rune, err string) {
 	fmt.Fprintf(f, "%%!%c(%s pbc.Element)", c, err)
 }
 
-func (el *elementImpl) nativeFormat(f fmt.State, c rune) {
+func (el *uncheckedElement) nativeFormat(f fmt.State, c rune) {
 	base := 10
 	if width, ok := f.Width(); ok {
 		if width < 2 || width > 36 {
@@ -35,7 +35,7 @@ func (el *elementImpl) nativeFormat(f fmt.State, c rune) {
 	}
 	var buf *C.char
 	var bufLen C.size_t
-	if C.element_out_str_wrapper(&buf, &bufLen, C.int(base), el.data) == 0 {
+	if C.element_out_str_wrapper(&buf, &bufLen, C.int(base), el.d) == 0 {
 		el.errorFormat(f, c, "INTERNALERROR")
 		return
 	}
@@ -44,14 +44,14 @@ func (el *elementImpl) nativeFormat(f fmt.State, c rune) {
 	fmt.Fprintf(f, "%s", str)
 }
 
-func (el *elementImpl) customFormat(f fmt.State, c rune) {
+func (el *uncheckedElement) customFormat(f fmt.State, c rune) {
 	count := el.Len()
 	if count == 0 {
 		el.BigInt().Format(f, c)
 	} else {
 		fmt.Fprintf(f, "[")
 		for i := 0; i < count; i++ {
-			el.Item(i).impl().customFormat(f, c)
+			el.Item(i).(*uncheckedElement).customFormat(f, c)
 			if i+1 < count {
 				fmt.Fprintf(f, ", ")
 			}
@@ -60,7 +60,7 @@ func (el *elementImpl) customFormat(f fmt.State, c rune) {
 	}
 }
 
-func (el *elementImpl) Format(f fmt.State, c rune) {
+func (el *uncheckedElement) Format(f fmt.State, c rune) {
 	switch c {
 	case 'v':
 		if f.Flag('#') {
@@ -85,13 +85,13 @@ func (el *checkedElement) Format(f fmt.State, c rune) {
 	}
 }
 
-func (el *elementImpl) String() string {
+func (el *uncheckedElement) String() string {
 	return fmt.Sprintf("%s", el)
 }
 
 func (el *checkedElement) String() string { return el.unchecked.String() }
 
-func (el *elementImpl) Scan(state fmt.ScanState, verb rune) error {
+func (el *uncheckedElement) Scan(state fmt.ScanState, verb rune) error {
 	if verb != 's' && verb != 'v' {
 		return ErrBadVerb
 	}
diff --git a/element_unchecked.go b/element_unchecked.go
index 86757b3e7e864aa8a35081f4181122c146055d1a..5ab103d03e825d0eaf5f1c7c595faff6d5fe952c 100644
--- a/element_unchecked.go
+++ b/element_unchecked.go
@@ -8,49 +8,87 @@ import "C"
 import (
 	"hash"
 	"math/big"
+	"runtime"
 	"unsafe"
 )
 
-func (el *elementImpl) impl() *elementImpl { return el }
+type uncheckedElement struct {
+	pairing *pairingImpl
+	d       *C.struct_element_s
+}
+
+func clearUnchecked(element *uncheckedElement) {
+	C.element_clear(element.d)
+}
+
+func initUnchecked(element *uncheckedElement, pairing *pairingImpl, initialize bool, field Field) {
+	element.d = &C.struct_element_s{}
+	element.pairing = pairing
+	if initialize {
+		switch field {
+		case G1:
+			C.element_init_G1(element.d, pairing.data)
+		case G2:
+			C.element_init_G2(element.d, pairing.data)
+		case GT:
+			C.element_init_GT(element.d, pairing.data)
+		case Zr:
+			C.element_init_Zr(element.d, pairing.data)
+		default:
+			panic(ErrUnknownField)
+		}
+	}
+	runtime.SetFinalizer(element, clearUnchecked)
+}
+
+func makeUnchecked(pairing *pairingImpl, field Field) *uncheckedElement {
+	element := &uncheckedElement{}
+	initUnchecked(element, pairing, true, field)
+	return element
+}
+
+func (el *uncheckedElement) data() *C.struct_element_s { return el.d }
+
+func (el *uncheckedElement) Pairing() Pairing { return el.pairing }
 
-func (el *elementImpl) NewFieldElement() Element {
-	newElement := &elementImpl{}
-	initElement(newElement, el.pairing, false, G1)
-	C.element_init_same_as(newElement.data, el.data)
+func (el *uncheckedElement) NewFieldElement() Element {
+	newElement := &uncheckedElement{}
+	initUnchecked(newElement, el.pairing, false, G1)
+	C.element_init_same_as(newElement.d, el.d)
 	return newElement
 }
 
-func (el *elementImpl) Set0() Element {
-	C.element_set0(el.data)
+func (el *uncheckedElement) Set0() Element {
+	C.element_set0(el.d)
 	return el
 }
 
-func (el *elementImpl) Set1() Element {
-	C.element_set1(el.data)
+func (el *uncheckedElement) Set1() Element {
+	C.element_set1(el.d)
 	return el
 }
 
-func (el *elementImpl) SetInt32(i int32) Element {
-	C.element_set_si(el.data, C.long(i))
+func (el *uncheckedElement) SetInt32(i int32) Element {
+	C.element_set_si(el.d, C.long(i))
 	return el
 }
 
-func (el *elementImpl) SetBig(i *big.Int) Element {
-	C.element_set_mpz(el.data, &big2mpz(i)[0])
+func (el *uncheckedElement) SetBig(i *big.Int) Element {
+	C.element_set_mpz(el.d, &big2mpz(i)[0])
 	return el
 }
 
-func (el *elementImpl) Set(src Element) Element {
-	C.element_set(el.data, src.impl().data)
+func (el *uncheckedElement) Set(src Element) Element {
+	C.element_set(el.d, src.data())
 	return el
 }
 
-func (el *elementImpl) SetFromHash(hash []byte) Element {
-	C.element_from_hash(el.data, unsafe.Pointer(&hash[0]), C.int(len(hash)))
+func (el *uncheckedElement) SetFromHash(hash []byte) Element {
+	C.element_from_hash(el.d, unsafe.Pointer(&hash[0]), C.int(len(hash)))
 	return el
 }
 
-func (el *elementImpl) SetFromStringHash(s string, h hash.Hash) Element {
+func (el *uncheckedElement) SetFromStringHash(s string, h hash.Hash) Element {
 	h.Reset()
 	if _, err := h.Write([]byte(s)); err != nil {
 		panic(ErrHashFailure)
@@ -58,108 +96,108 @@ func (el *elementImpl) SetFromStringHash(s string, h hash.Hash) Element {
 	return el.SetFromHash(h.Sum([]byte{}))
 }
 
-func (el *elementImpl) SetBytes(buf []byte) Element {
-	C.element_from_bytes(el.data, (*C.uchar)(unsafe.Pointer(&buf[0])))
+func (el *uncheckedElement) SetBytes(buf []byte) Element {
+	C.element_from_bytes(el.d, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
 
-func (el *elementImpl) SetXBytes(buf []byte) Element {
-	C.element_from_bytes_x_only(el.data, (*C.uchar)(unsafe.Pointer(&buf[0])))
+func (el *uncheckedElement) SetXBytes(buf []byte) Element {
+	C.element_from_bytes_x_only(el.d, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
 
-func (el *elementImpl) SetCompressedBytes(buf []byte) Element {
-	C.element_from_bytes_compressed(el.data, (*C.uchar)(unsafe.Pointer(&buf[0])))
+func (el *uncheckedElement) SetCompressedBytes(buf []byte) Element {
+	C.element_from_bytes_compressed(el.d, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
 
-func (el *elementImpl) SetString(s string, base int) (Element, bool) {
+func (el *uncheckedElement) SetString(s string, base int) (Element, bool) {
 	cstr := C.CString(s)
 	defer C.free(unsafe.Pointer(cstr))
 
-	if ok := C.element_set_str(el.data, cstr, C.int(base)); ok == 0 {
+	if ok := C.element_set_str(el.d, cstr, C.int(base)); ok == 0 {
 		return nil, false
 	}
 	return el, true
 }
 
-func (el *elementImpl) BigInt() *big.Int {
+func (el *uncheckedElement) BigInt() *big.Int {
 	mpz := newMpz()
-	C.element_to_mpz(&mpz[0], el.data)
+	C.element_to_mpz(&mpz[0], el.d)
 	return mpz2big(mpz)
 }
 
-func (el *elementImpl) BytesLen() int {
-	return int(C.element_length_in_bytes(el.data))
+func (el *uncheckedElement) BytesLen() int {
+	return int(C.element_length_in_bytes(el.d))
 }
 
-func (el *elementImpl) writeBytes(buf []byte) C.int {
-	return C.element_to_bytes((*C.uchar)(unsafe.Pointer(&buf[0])), el.data)
+func (el *uncheckedElement) writeBytes(buf []byte) C.int {
+	return C.element_to_bytes((*C.uchar)(unsafe.Pointer(&buf[0])), el.d)
 }
 
-func (el *elementImpl) Bytes() []byte {
+func (el *uncheckedElement) Bytes() []byte {
 	buf := make([]byte, el.BytesLen())
 	el.writeBytes(buf)
 	return buf
 }
 
-func (el *elementImpl) XBytesLen() int {
-	return int(C.element_length_in_bytes_x_only(el.data))
+func (el *uncheckedElement) XBytesLen() int {
+	return int(C.element_length_in_bytes_x_only(el.d))
 }
 
-func (el *elementImpl) writeXBytes(buf []byte) C.int {
-	return C.element_to_bytes_x_only((*C.uchar)(unsafe.Pointer(&buf[0])), el.data)
+func (el *uncheckedElement) writeXBytes(buf []byte) C.int {
+	return C.element_to_bytes_x_only((*C.uchar)(unsafe.Pointer(&buf[0])), el.d)
 }
 
-func (el *elementImpl) XBytes() []byte {
+func (el *uncheckedElement) XBytes() []byte {
 	buf := make([]byte, el.XBytesLen())
 	el.writeXBytes(buf)
 	return buf
 }
 
-func (el *elementImpl) CompressedBytesLen() int {
-	return int(C.element_length_in_bytes_compressed(el.data))
+func (el *uncheckedElement) CompressedBytesLen() int {
+	return int(C.element_length_in_bytes_compressed(el.d))
 }
 
-func (el *elementImpl) writeCompressedBytes(buf []byte) C.int {
-	return C.element_to_bytes_compressed((*C.uchar)(unsafe.Pointer(&buf[0])), el.data)
+func (el *uncheckedElement) writeCompressedBytes(buf []byte) C.int {
+	return C.element_to_bytes_compressed((*C.uchar)(unsafe.Pointer(&buf[0])), el.d)
 }
 
-func (el *elementImpl) CompressedBytes() []byte {
+func (el *uncheckedElement) CompressedBytes() []byte {
 	buf := make([]byte, el.CompressedBytesLen())
 	el.writeCompressedBytes(buf)
 	return buf
 }
 
-func (el *elementImpl) Len() int {
-	return int(C.element_item_count(el.data))
+func (el *uncheckedElement) Len() int {
+	return int(C.element_item_count(el.d))
 }
 
-func (el *elementImpl) Item(i int) Element {
-	return &elementImpl{
+func (el *uncheckedElement) Item(i int) Element {
+	return &uncheckedElement{
 		pairing: el.pairing,
-		data:    C.element_item(el.data, C.int(i)),
+		d:       C.element_item(el.d, C.int(i)),
 	}
 }
 
-func (el *elementImpl) X() *big.Int {
+func (el *uncheckedElement) X() *big.Int {
 	return el.Item(0).BigInt()
 }
 
-func (el *elementImpl) Y() *big.Int {
+func (el *uncheckedElement) Y() *big.Int {
 	return el.Item(1).BigInt()
 }
 
-func (el *elementImpl) Is0() bool {
-	return C.element_is0(el.data) != 0
+func (el *uncheckedElement) Is0() bool {
+	return C.element_is0(el.d) != 0
 }
 
-func (el *elementImpl) Is1() bool {
-	return C.element_is1(el.data) != 0
+func (el *uncheckedElement) Is1() bool {
+	return C.element_is1(el.d) != 0
 }
 
-func (el *elementImpl) IsSquare() bool {
-	return C.element_is_sqr(el.data) != 0
+func (el *uncheckedElement) IsSquare() bool {
+	return C.element_is_sqr(el.d) != 0
 }
 
 func normalizeSign(sign int64) int {
@@ -172,131 +210,131 @@ func normalizeSign(sign int64) int {
 	return 0
 }
 
-func (el *elementImpl) Sign() int {
-	return normalizeSign(int64(C.element_sign(el.data)))
+func (el *uncheckedElement) Sign() int {
+	return normalizeSign(int64(C.element_sign(el.d)))
 }
 
-func (el *elementImpl) Cmp(x Element) int {
-	return normalizeSign(int64(C.element_cmp(el.data, x.impl().data)))
+func (el *uncheckedElement) Cmp(x Element) int {
+	return normalizeSign(int64(C.element_cmp(el.d, x.data())))
 }
 
-func (el *elementImpl) Equals(x Element) bool { return el.Cmp(x) == 0 }
+func (el *uncheckedElement) Equals(x Element) bool { return el.Cmp(x) == 0 }
 
-func (el *elementImpl) Add(x, y Element) Element {
-	C.element_add(el.data, x.impl().data, y.impl().data)
+func (el *uncheckedElement) Add(x, y Element) Element {
+	C.element_add(el.d, x.data(), y.data())
 	return el
 }
 
-func (el *elementImpl) Sub(x, y Element) Element {
-	C.element_sub(el.data, x.impl().data, y.impl().data)
+func (el *uncheckedElement) Sub(x, y Element) Element {
+	C.element_sub(el.d, x.data(), y.data())
 	return el
 }
 
-func (el *elementImpl) Mul(x, y Element) Element {
-	C.element_mul(el.data, x.impl().data, y.impl().data)
+func (el *uncheckedElement) Mul(x, y Element) Element {
+	C.element_mul(el.d, x.data(), y.data())
 	return el
 }
 
-func (el *elementImpl) MulBig(x Element, i *big.Int) Element {
-	C.element_mul_mpz(el.data, x.impl().data, &big2mpz(i)[0])
+func (el *uncheckedElement) MulBig(x Element, i *big.Int) Element {
+	C.element_mul_mpz(el.d, x.data(), &big2mpz(i)[0])
 	return el
 }
 
-func (el *elementImpl) MulInt32(x Element, i int32) Element {
-	C.element_mul_si(el.data, x.impl().data, C.long(i))
+func (el *uncheckedElement) MulInt32(x Element, i int32) Element {
+	C.element_mul_si(el.d, x.data(), C.long(i))
 	return el
 }
 
-func (el *elementImpl) MulZn(x, y Element) Element {
-	C.element_mul_zn(el.data, x.impl().data, y.impl().data)
+func (el *uncheckedElement) MulZn(x, y Element) Element {
+	C.element_mul_zn(el.d, x.data(), y.data())
 	return el
 }
 
-func (el *elementImpl) Div(x, y Element) Element {
-	C.element_div(el.data, x.impl().data, y.impl().data)
+func (el *uncheckedElement) Div(x, y Element) Element {
+	C.element_div(el.d, x.data(), y.data())
 	return el
 }
 
-func (el *elementImpl) Double(x Element) Element {
-	C.element_double(el.data, x.impl().data)
+func (el *uncheckedElement) Double(x Element) Element {
+	C.element_double(el.d, x.data())
 	return el
 }
 
-func (el *elementImpl) Halve(x Element) Element {
-	C.element_halve(el.data, x.impl().data)
+func (el *uncheckedElement) Halve(x Element) Element {
+	C.element_halve(el.d, x.data())
 	return el
 }
 
-func (el *elementImpl) Square(x Element) Element {
-	C.element_square(el.data, x.impl().data)
+func (el *uncheckedElement) Square(x Element) Element {
+	C.element_square(el.d, x.data())
 	return el
 }
 
-func (el *elementImpl) Neg(x Element) Element {
-	C.element_neg(el.data, x.impl().data)
+func (el *uncheckedElement) Neg(x Element) Element {
+	C.element_neg(el.d, x.data())
 	return el
 }
 
-func (el *elementImpl) Invert(x Element) Element {
-	C.element_invert(el.data, x.impl().data)
+func (el *uncheckedElement) Invert(x Element) Element {
+	C.element_invert(el.d, x.data())
 	return el
 }
 
-func (el *elementImpl) PowBig(x Element, i *big.Int) Element {
-	C.element_pow_mpz(el.data, x.impl().data, &big2mpz(i)[0])
+func (el *uncheckedElement) PowBig(x Element, i *big.Int) Element {
+	C.element_pow_mpz(el.d, x.data(), &big2mpz(i)[0])
 	return el
 }
 
-func (el *elementImpl) PowZn(x, i Element) Element {
-	C.element_pow_zn(el.data, x.impl().data, i.impl().data)
+func (el *uncheckedElement) PowZn(x, i Element) Element {
+	C.element_pow_zn(el.d, x.data(), i.data())
 	return el
 }
 
-func (el *elementImpl) Pow2Big(x Element, i *big.Int, y Element, j *big.Int) Element {
-	C.element_pow2_mpz(el.data, x.impl().data, &big2mpz(i)[0], y.impl().data, &big2mpz(j)[0])
+func (el *uncheckedElement) Pow2Big(x Element, i *big.Int, y Element, j *big.Int) Element {
+	C.element_pow2_mpz(el.d, x.data(), &big2mpz(i)[0], y.data(), &big2mpz(j)[0])
 	return el
 }
 
-func (el *elementImpl) Pow2Zn(x, i, y, j Element) Element {
-	C.element_pow2_zn(el.data, x.impl().data, i.impl().data, y.impl().data, j.impl().data)
+func (el *uncheckedElement) Pow2Zn(x, i, y, j Element) Element {
+	C.element_pow2_zn(el.d, x.data(), i.data(), y.data(), j.data())
 	return el
 }
 
-func (el *elementImpl) Pow3Big(x Element, i *big.Int, y Element, j *big.Int, z Element, k *big.Int) Element {
-	C.element_pow3_mpz(el.data, x.impl().data, &big2mpz(i)[0], y.impl().data, &big2mpz(j)[0], z.impl().data, &big2mpz(k)[0])
+func (el *uncheckedElement) Pow3Big(x Element, i *big.Int, y Element, j *big.Int, z Element, k *big.Int) Element {
+	C.element_pow3_mpz(el.d, x.data(), &big2mpz(i)[0], y.data(), &big2mpz(j)[0], z.data(), &big2mpz(k)[0])
 	return el
 }
 
-func (el *elementImpl) Pow3Zn(x, i, y, j, z, k Element) Element {
-	C.element_pow3_zn(el.data, x.impl().data, i.impl().data, y.impl().data, j.impl().data, z.impl().data, k.impl().data)
+func (el *uncheckedElement) Pow3Zn(x, i, y, j, z, k Element) Element {
+	C.element_pow3_zn(el.d, x.data(), i.data(), y.data(), j.data(), z.data(), k.data())
 	return el
 }
 
-func (el *elementImpl) PreparePower() Power { return initPower(el) }
+func (el *uncheckedElement) PreparePower() Power { return initPower(el) }
 
-func (el *elementImpl) PowerBig(power Power, i *big.Int) Element {
-	C.element_pp_pow(el.data, &big2mpz(i)[0], power.(*powerImpl).data)
+func (el *uncheckedElement) PowerBig(power Power, i *big.Int) Element {
+	C.element_pp_pow(el.d, &big2mpz(i)[0], power.(*powerImpl).data)
 	return el
 }
 
-func (el *elementImpl) PowerZn(power Power, i Element) Element {
-	C.element_pp_pow_zn(el.data, i.impl().data, power.(*powerImpl).data)
+func (el *uncheckedElement) PowerZn(power Power, i Element) Element {
+	C.element_pp_pow_zn(el.d, i.data(), power.(*powerImpl).data)
 	return el
 }
 
-func (el *elementImpl) Pair(x, y Element) Element {
-	C.pairing_apply(el.data, x.impl().data, y.impl().data, el.pairing.data)
+func (el *uncheckedElement) Pair(x, y Element) Element {
+	C.pairing_apply(el.d, x.data(), y.data(), el.pairing.data)
 	return el
 }
 
-func (el *elementImpl) doProdPair(in1, in2 []C.struct_element_s) Element {
+func (el *uncheckedElement) doProdPair(in1, in2 []C.struct_element_s) Element {
 	x := (*C.element_t)(unsafe.Pointer(&in1[0]))
 	y := (*C.element_t)(unsafe.Pointer(&in2[0]))
-	C.element_prod_pairing(el.data, x, y, C.int(len(in1)))
+	C.element_prod_pairing(el.d, x, y, C.int(len(in1)))
 	return el
 }
 
-func (el *elementImpl) ProdPair(elements ...Element) Element {
+func (el *uncheckedElement) ProdPair(elements ...Element) Element {
 	n := len(elements)
 	if n%2 != 0 {
 		panic(ErrBadPairList)
@@ -305,13 +343,13 @@ func (el *elementImpl) ProdPair(elements ...Element) Element {
 	in1 := make([]C.struct_element_s, half)
 	in2 := make([]C.struct_element_s, half)
 	for i, j := 0, 0; j < n; i, j = i+1, j+2 {
-		in1[i] = *elements[j].impl().data
-		in2[i] = *elements[j+1].impl().data
+		in1[i] = *elements[j].data()
+		in2[i] = *elements[j+1].data()
 	}
 	return el.doProdPair(in1, in2)
 }
 
-func (el *elementImpl) ProdPairSlice(x, y []Element) Element {
+func (el *uncheckedElement) ProdPairSlice(x, y []Element) Element {
 	n := len(x)
 	if n != len(y) {
 		panic(ErrBadPairList)
@@ -319,30 +357,30 @@ func (el *elementImpl) ProdPairSlice(x, y []Element) Element {
 	in1 := make([]C.struct_element_s, n)
 	in2 := make([]C.struct_element_s, n)
 	for i := 0; i < n; i++ {
-		in1[i] = *x[i].impl().data
-		in2[i] = *y[i].impl().data
+		in1[i] = *x[i].data()
+		in2[i] = *y[i].data()
 	}
 	return el.doProdPair(in1, in2)
 }
 
-func (el *elementImpl) PreparePairer() Pairer { return initPairer(el) }
+func (el *uncheckedElement) PreparePairer() Pairer { return initPairer(el) }
 
-func (el *elementImpl) PairerPair(pairer Pairer, x Element) Element {
-	C.pairing_pp_apply(el.data, x.impl().data, pairer.(*pairerImpl).data)
+func (el *uncheckedElement) PairerPair(pairer Pairer, y Element) Element {
+	C.pairing_pp_apply(el.d, y.data(), pairer.(*pairerImpl).data)
 	return el
 }
 
-func (el *elementImpl) BruteForceDL(g, h Element) Element {
-	C.element_dlog_brute_force(el.data, g.impl().data, h.impl().data)
+func (el *uncheckedElement) BruteForceDL(g, h Element) Element {
+	C.element_dlog_brute_force(el.d, g.data(), h.data())
 	return el
 }
 
-func (el *elementImpl) PollardRhoDL(g, h Element) Element {
-	C.element_dlog_pollard_rho(el.data, g.impl().data, h.impl().data)
+func (el *uncheckedElement) PollardRhoDL(g, h Element) Element {
+	C.element_dlog_pollard_rho(el.d, g.data(), h.data())
 	return el
 }
 
-func (el *elementImpl) Rand() Element {
-	C.element_random(el.data)
+func (el *uncheckedElement) Rand() Element {
+	C.element_random(el.d)
 	return el
 }
diff --git a/errors.go b/errors.go
index 8ad9ad44216d52733b38f5fd00ba51c6114313ad..5dcb89367a4b26034e9a2b46f94aa3ae3c7c3101 100644
--- a/errors.go
+++ b/errors.go
@@ -4,6 +4,7 @@ import "errors"
 
 var (
 	ErrInvalidParamString = errors.New("invalid pairing parameters")
+	ErrNoSuitableCurves   = errors.New("no suitable curves were found")
 	ErrUnknownField       = errors.New("unchecked element initialized in unknown field")
 	ErrIllegalOp          = errors.New("operation is illegal for elements of this type")
 	ErrUncheckedOp        = errors.New("unchecked element passed to checked operation")
diff --git a/generation.go b/generation.go
index b3b33a6cf5b600d1c4057692061504917c66c71d..8de154d7552d660993184287ca8c7928c2a489d4 100644
--- a/generation.go
+++ b/generation.go
@@ -1,15 +1,28 @@
 package pbc
 
 /*
+#include <stdint.h>
 #include <pbc/pbc.h>
 
-int acceptPairingD(pbc_cm_t cm, void* p) {
-	pbc_param_init_d_gen((pbc_param_ptr)p, cm);
-	return 1;
-}
+typedef struct {
+	int typeD;
+	pbc_param_ptr params;
+	uint32_t rbits;
+	uint32_t qbits;
+} check_pairing_settings_t;
+
+int checkPairing(pbc_cm_t cm, void* p) {
+	check_pairing_settings_t* settings = (check_pairing_settings_t*)p;
 
-int acceptPairingG(pbc_cm_t cm, void* p) {
-	pbc_param_init_g_gen((pbc_param_ptr)p, cm);
+	unsigned int rbits = (unsigned int)mpz_sizeinbase(cm->r, 2);
+	unsigned int qbits = (unsigned int)mpz_sizeinbase(cm->q, 2);
+	if (rbits < settings->rbits || qbits < settings->qbits) return 0;
+
+	if (settings->typeD) {
+		pbc_param_init_d_gen(settings->params, cm);
+	} else {
+		pbc_param_init_g_gen(settings->params, cm);
+	}
 	return 1;
 }
 */
@@ -20,38 +33,122 @@ import (
 	"unsafe"
 )
 
+// GenerateA generates a pairing on the curve y^2 = x^3 + x over the field F_q
+// for some prime q = 3 mod 4. Type A pairings are symmetric (i.e., G1 == G2).
+// Type A pairings are best used when speed of computation is the primary
+// concern.
+//
+// To be secure, generic discrete log algorithms must be infeasible in groups of
+// order r, and finite field discrete log algorithms must be infeasible in
+// groups of order q^2.
+//
+// For example:
+// 	params := pbc.GenerateA(160, 512)
 func GenerateA(rbits uint32, qbits uint32) Params {
 	params := makeParams()
 	C.pbc_param_init_a_gen(params.data, C.int(rbits), C.int(qbits))
 	return params
 }
 
-func GenerateA1(n *big.Int) Params {
+// GenerateA1 generates a type A pairing given a fixed order for G1, G2, and GT.
+// This form of pairing can be used to produce groups of composite order, where
+// r is the product of two large primes. In this case, r should infeasible to
+// factor. Each prime should be at least 512 bits (causing r to be 1024 bits in
+// general), but preferably 1024 bits or more.
+func GenerateA1(r *big.Int) Params {
 	params := makeParams()
-	C.pbc_param_init_a1_gen(params.data, &big2mpz(n)[0])
+	C.pbc_param_init_a1_gen(params.data, &big2mpz(r)[0])
 	return params
 }
 
-func GenerateD(d uint32, bitlimit uint32) Params {
-	params := makeParams()
-	C.pbc_cm_search_d((*[0]byte)(C.acceptPairingD), unsafe.Pointer(params.data), C.uint(d), C.uint(bitlimit))
-	return params
+// GenerateD generates a pairing on a curve with embedding degree 6 whose order
+// is h * r where r is prime and h is a small constant. Type D pairings are
+// asymmetric, but have small group elements. This makes them well-suited for
+// applications where message size is the primary concern, but speed is also
+// important.
+//
+// Parameters are generated using the constant multiplication (CM) method for a
+// given fundamental discriminant D. It is required that D > 0, no square of an
+// odd prime divides D, and D = 0 or 3 mod 4. The bitlimit parameter sets a cap
+// on the number of bits in the group order. It is possible that for some values
+// of D, no suitable curves can be found. In this case, GenerateD returns nil
+// and ErrNoSuitableCurves.
+//
+// The rbits and qbits parameters sit minimum sizes for group orders. To be
+// secure, generic discrete log algorithms must be infeasible in groups of order
+// r, and finite field discrete log algorithms must be infeasible in groups of
+// order q^6.
+//
+// For example:
+// 	params, err := pbc.GenerateD(9563, 160, 171, 500)
+func GenerateD(d uint32, rbits uint32, qbits uint32, bitlimit uint32) (Params, error) {
+	return generateWithCM(true, d, rbits, qbits, bitlimit)
 }
 
+// GenerateE generates a pairing entirely within a order r subgroup of an order
+// q field. These pairings are symmetric, but serve little purpose beyond being
+// mathematically interesting. Use of these pairings is not recommended unless
+// new algorithms are discovered for solving discrete logs in elliptic curves as
+// easily as for finite fields.
+//
+// For security, generic discrete log algorithms must be infeasible in groups of
+// order r, and finite field discrete log algorithms must be infeasible in
+// finite fields of order q.
+//
+// For example:
+// 	params, err := pbc.GenerateE(160, 1024)
 func GenerateE(rbits uint32, qbits uint32) Params {
 	params := makeParams()
 	C.pbc_param_init_e_gen(params.data, C.int(rbits), C.int(qbits))
 	return params
 }
 
+// GenerateF generates an asymmetric pairing with extremely small group
+// elements. This is the best pairing to use when space is an overriding
+// priority. However, type F pairings are slow compared to the other types. Type
+// D pairings provide a more balanced alternative.
+//
+// The bits parameter specifies the approximate number of bits in the group
+// order, r, and the order of the base field, q. For security, generic discrete
+// log algorithms must be infeasible in groups of order r, and finite field
+// discrete log algorithms must be infeasible in finite fields of order q^12.
+//
+// For example:
+// 	params, err := pbc.GenerateF(160)
 func GenerateF(bits uint32) Params {
 	params := makeParams()
 	C.pbc_param_init_f_gen(params.data, C.int(bits))
 	return params
 }
 
-func GenerateG(d uint32, bitlimit uint32) Params {
+// GenerateG generates a pairing on a curve with embedding degree 10 whose order
+// is h * r where r is prime and h is a small constant. Type G pairings are
+// asymmetric, but have extremely small group elements. However, these pairings
+// are even slower than type F pairings, making type F a better choice.
+//
+// Like type D pairings, parameters are generated using the constant
+// multiplication (CM) method. See the GenerateD function for a description of
+// the parameters.
+//
+// For example:
+// 	params, err := pbc.GenerateG(9563, 160, 171, 500)
+func GenerateG(d uint32, rbits uint32, qbits uint32, bitlimit uint32) (Params, error) {
+	return generateWithCM(false, d, qbits, rbits, bitlimit)
+}
+
+func generateWithCM(typeD bool, d uint32, rbits uint32, qbits uint32, bitlimit uint32) (Params, error) {
 	params := makeParams()
-	C.pbc_cm_search_d((*[0]byte)(C.acceptPairingG), unsafe.Pointer(params.data), C.uint(d), C.uint(bitlimit))
-	return params
+	settings := &C.check_pairing_settings_t{
+		params: params.data,
+		rbits:  C.uint32_t(rbits),
+		qbits:  C.uint32_t(qbits),
+	}
+	if typeD {
+		settings.typeD = C.int(1)
+	}
+	res := C.pbc_cm_search_d((*[0]byte)(C.checkPairing), unsafe.Pointer(settings), C.uint(d), C.uint(bitlimit))
+	if res != 1 {
+		return nil, ErrNoSuitableCurves
+	}
+	return params, nil
 }
diff --git a/gmp_big.go b/gmp_big.go
index 0668c77304693e4359f7d53c3424cc0a8e9b0dd2..11f5acb036a0f03e405640f42227d904f32697ea 100644
--- a/gmp_big.go
+++ b/gmp_big.go
@@ -15,7 +15,6 @@ var wordSize C.size_t
 var bitsPerWord C.size_t
 
 func clearMpz(x *C.mpz_t) {
-	println("clearmpz")
 	C.mpz_clear(&x[0])
 }
 
@@ -26,6 +25,7 @@ func newMpz() *C.mpz_t {
 	return out
 }
 
+// big2thisMpz imports the value of num into out
 func big2thisMpz(num *big.Int, out *C.mpz_t) {
 	words := num.Bits()
 	if len(words) > 0 {
@@ -33,12 +33,14 @@ func big2thisMpz(num *big.Int, out *C.mpz_t) {
 	}
 }
 
+// big2mpz allocates a new mpz_t and imports a big.Int value
 func big2mpz(num *big.Int) *C.mpz_t {
 	out := newMpz()
 	big2thisMpz(num, out)
 	return out
 }
 
+// mpz2thisBig imports the value of num into out
 func mpz2thisBig(num *C.mpz_t, out *big.Int) {
 	wordsNeeded := (C.mpz_sizeinbase(&num[0], 2) + (bitsPerWord - 1)) / bitsPerWord
 	words := make([]big.Word, wordsNeeded)
@@ -47,6 +49,7 @@ func mpz2thisBig(num *C.mpz_t, out *big.Int) {
 	out.SetBits(words)
 }
 
+// mpz2big allocates a new big.Int and imports an mpz_t value
 func mpz2big(num *C.mpz_t) (out *big.Int) {
 	out = &big.Int{}
 	mpz2thisBig(num, out)
diff --git a/pairing.go b/pairing.go
index 0687a1ca305968f5460828e54dc2e2f6b49fc037..fb45fc6b279cb1a8a22efb182651e3ae6290ce65 100644
--- a/pairing.go
+++ b/pairing.go
@@ -11,6 +11,9 @@ import (
 	"runtime"
 )
 
+// Field denotes the various possible algebraic structures associated with a
+// pairing. G1, G2, and GT are the groups involved in the pairing operation. Zr
+// is the field of integers with order r, where r is the order of the groups.
 type Field int
 
 const (
@@ -20,9 +23,20 @@ const (
 	Zr Field = iota
 )
 
+// Pairing represents a pairing and its associated groups. The primary use of a
+// pairing object is the initialization of group elements. Elements can be
+// created in G1, G2, GT, or Zr. Additionally, elements can either be checked
+// or unchecked. Unchecked elements are slightly faster, but do not check to
+// ensure that operations make sense. Checked elements defend against a variety
+// of errors. For more details, see the Element documentation.
 type Pairing interface {
+	// IsSymmetric returns true if G1 == G2 for this pairing.
 	IsSymmetric() bool
 
+	// Various methods to return the sizes of group elements.
+	// *Length() returns the length of an element in bytes.
+	// *XLength() returns the length of an X coordinate only, in bytes.
+	// *CompressedLength() returns the length of a compressed element in bytes.
 	G1Length() uint
 	G1XLength() uint
 	G1CompressedLength() uint
@@ -32,10 +46,13 @@ type Pairing interface {
 	GTLength() uint
 	ZrLength() uint
 
+	// Initialization methods for group elements.
 	NewG1() Element
 	NewG2() Element
 	NewGT() Element
 	NewZr() Element
+
+	// Initializes an element without type checking.
 	NewElement(Field) Element
 }
 
@@ -43,24 +60,29 @@ type pairingImpl struct {
 	data *C.struct_pairing_s
 }
 
-func NewPairing(params io.Reader) (Pairing, error) {
+// NewPairing instantiates a pairing from a set of parameters.
+func NewPairing(params Params) Pairing {
+	pairing := makePairing()
+	C.pairing_init_pbc_param(pairing.data, params.(*paramsImpl).data)
+	return pairing
+}
+
+// NewPairingFromReader loads pairing parameters from a Reader and instantiates
+// a pairing.
+func NewPairingFromReader(params io.Reader) (Pairing, error) {
 	buf := new(bytes.Buffer)
 	buf.ReadFrom(params)
 	return NewPairingFromString(buf.String())
 }
 
+// NewPairingFromString loads pairing parameters from a string and instantiates
+// a pairing.
 func NewPairingFromString(params string) (Pairing, error) {
 	p, err := NewParamsFromString(params)
 	if err != nil {
 		return nil, err
 	}
-	return NewPairingFromParams(p), nil
-}
-
-func NewPairingFromParams(params Params) Pairing {
-	pairing := makePairing()
-	C.pairing_init_pbc_param(pairing.data, params.(*paramsImpl).data)
-	return pairing
+	return NewPairing(p), nil
 }
 
 func (pairing *pairingImpl) IsSymmetric() bool {
@@ -99,8 +121,13 @@ func (pairing *pairingImpl) ZrLength() uint {
 	return uint(C.pairing_length_in_bytes_Zr(pairing.data))
 }
 
+func (pairing *pairingImpl) NewG1() Element                 { return makeChecked(pairing, G1, pairing.data.G1) }
+func (pairing *pairingImpl) NewG2() Element                 { return makeChecked(pairing, G2, pairing.data.G2) }
+func (pairing *pairingImpl) NewGT() Element                 { return makeChecked(pairing, GT, &pairing.data.GT[0]) }
+func (pairing *pairingImpl) NewZr() Element                 { return makeChecked(pairing, Zr, &pairing.data.Zr[0]) }
+func (pairing *pairingImpl) NewElement(field Field) Element { return makeUnchecked(pairing, field) }
+
 func clearPairing(pairing *pairingImpl) {
-	println("clearpairing")
 	C.pairing_clear(pairing.data)
 }
 
diff --git a/params.go b/params.go
index 5813f81e154a404c3a182178f7860e0885a00ed2..82212a44c39217952a4e2a0254c71ec90fc28ae7 100644
--- a/params.go
+++ b/params.go
@@ -15,10 +15,16 @@ import "C"
 
 import (
 	"io"
+	"io/ioutil"
 	"runtime"
 	"unsafe"
 )
 
+// Params represents the parameters required for creating a pairing. Parameters
+// cn be generated using the generation functions or read from a Reader.
+// Normally, parameters are generated once using a generation program and then
+// distributed with the final application. Parameters can be exported for this
+// purpose using the WriteTo or String methods.
 type Params interface {
 	NewPairing() Pairing
 	WriteTo(w io.Writer) (n int64, err error)
@@ -29,6 +35,16 @@ type paramsImpl struct {
 	data *C.struct_pbc_param_s
 }
 
+// NewParams loads pairing parameters from a Reader.
+func NewParams(r io.Reader) (Params, error) {
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+	return NewParamsFromString(string(b))
+}
+
+// NewParamsFromString loads pairing parameters from a string.
 func NewParamsFromString(s string) (Params, error) {
 	cstr := C.CString(s)
 	defer C.free(unsafe.Pointer(cstr))
@@ -40,8 +56,9 @@ func NewParamsFromString(s string) (Params, error) {
 	return params, nil
 }
 
+// NewPairing creates a Pairing using these parameters.
 func (params *paramsImpl) NewPairing() Pairing {
-	return NewPairingFromParams(params)
+	return NewPairing(params)
 }
 
 func (params *paramsImpl) WriteTo(w io.Writer) (n int64, err error) {
@@ -61,7 +78,6 @@ func (params *paramsImpl) String() string {
 }
 
 func clearParams(params *paramsImpl) {
-	println("clearparams")
 	C.pbc_param_clear(params.data)
 }
 
diff --git a/utils.go b/utils.go
index 656a5e17bfab9f68b812223c8a433a69597b6ceb..b990cea77f71361897951255d2cfa409d5214b89 100644
--- a/utils.go
+++ b/utils.go
@@ -18,8 +18,11 @@ import (
 
 var logging bool
 
+// Logging returns true if PBC will send status messages to stderr.
 func Logging() bool { return logging }
 
+// SetLogging enables or disables sending PBC status messages to stderr.
+// Messages are hidden by default.
 func SetLogging(log bool) {
 	logging = log
 	if log {
@@ -29,14 +32,23 @@ func SetLogging(log bool) {
 	}
 }
 
+// RandomSource generates random numbers for consumption by PBC. Rand returns a
+// random integer in [0,limit).
 type RandomSource interface {
 	Rand(limit *big.Int) *big.Int
 }
 
 var randomProvider RandomSource
 
+// RandomProvider returns the current random number source for use by PBC.
 func RandomProvider() RandomSource { return randomProvider }
 
+// SetRandomProvider sets the random number source for use by PBC. If provider
+// is nil, then PBC will use its internal random number generator, which is the
+// default mode. If provider is non-nil, then requests for random numbers will
+// be serviced by Go instead of the internal C functions. This is slower, but
+// provides greater control. Several convenience functions are provided to set
+// common sources of random numbers.
 func SetRandomProvider(provider RandomSource) {
 	randomProvider = provider
 	if provider == nil {
@@ -76,8 +88,12 @@ func (provider randProvider) Rand(limit *big.Int) (result *big.Int) {
 	return
 }
 
+// SetCryptoRandom causes PBC to use the crypto/rand package with the globally
+// shared rand.Reader as the source of random numbers.
 func SetCryptoRandom() { SetReaderRandom(cryptorand.Reader) }
 
+// SetReaderRandom causes PBC to use the crypto/rand package to generate random
+// numbers using the given reader as an entropy source.
 func SetReaderRandom(reader io.Reader) {
 	if reader == nil {
 		panic(ErrIllegalNil)
@@ -85,8 +101,14 @@ func SetReaderRandom(reader io.Reader) {
 	SetRandomProvider(&readerProvider{reader})
 }
 
+// SetRandRandom causes PBC to use the given source of random numbers.
 func SetRandRandom(rand *rand.Rand) { SetRandomProvider(&randProvider{rand}) }
 
+// SetDefaultRandom causes PBC to use its internal source of random numbers.
+// This is the default mode of operation. Internally, PBC will attempt to read
+// from /dev/urandom if it exists, or from the Microsoft Crypto API on Windows.
+// If neither of these sources is available, the library will fall back to an
+// insecure PRNG.
 func SetDefaultRandom() { SetRandomProvider(nil) }
 
 func init() {