diff --git a/doc.go b/doc.go
index a8783a1b9cd1cf3a2fbbb6cb15d7ca538ea85f8e..f6b74274511b76a82e6a483fda26b257d4d67a9f 100644
--- a/doc.go
+++ b/doc.go
@@ -61,7 +61,7 @@
 
 		sudo yum install gmp
 
-	For installation with FINK (http://www.finkproject.org/) on Mac OS X:
+	For installation with Fink (http://www.finkproject.org/) on Mac OS X:
 
 		sudo fink install gmp gmp-shlibs
 
diff --git a/element.go b/element.go
index c20e9ffb5575f9e62074a6f77cabd1b902824b83..5408cd9c227976eda11953071b79224f68b61e4b 100644
--- a/element.go
+++ b/element.go
@@ -4,8 +4,65 @@ package pbc
 #include <pbc/pbc.h>
 */
 import "C"
+
 import "runtime"
 
+// Element represents an element in one of the algebraic structures associated
+// with a pairing. Arithmetic operations can be performed on elements to
+// complete computations. Elements can also be paired using the associated
+// pairing's bilinear map. Elements can be exported or imported in a variety of
+// formats.
+//
+// The arithmetic methods for Elements generally follow the style of big.Int. A
+// typical operation has a signature like this:
+//
+// 	func (el *Element) Add(x, y *Element) *Element
+//
+// This method stores x + y in el and returns el. Since these arithmetic
+// operations return the targets, they can be used in method chaining:
+//
+// 	x.Add(a, b).Mul(x, c).Square(x)
+//
+// This assigns x = ((a+b)*c)^2.
+//
+// Whenever possible, the methods defined on Element use the same names as
+// those in the math/big package.
+//
+// The addition and multiplication functions perform addition and
+// multiplication operations in rings and fields. For groups of points on an
+// elliptic curve, such as the G1 and G2 groups associated with pairings, both
+// addition and multiplication represent the group operation (and similarly
+// both 0 and 1 represent the identity element). It is recommended that
+// programs choose one convention and stick with it to avoid confusion.
+//
+// Not all operations are valid for all elements. For example, pairing
+// operations require an element from G1, and element from G2, and a target
+// from GT. As another example, elements in a ring cannot be inverted in
+// general.
+//
+// The PBC library does not attempt to detect invalid element operations. If an
+// invalid operation is performed, several outcomes are possible. In the best
+// case, the operation will be treated as a no-op. The target element might
+// be set to a nonsensical value. In the worst case, the program may segfault.
+//
+// The pbc wrapper provides some protection against invalid operations. When
+// elements are initialized by a Pairing, they can either be created as checked
+// or unchecked. Unchecked elements do not perform any sanity checks; calls are
+// passed directly to the C library, with the possible consequences mentioned
+// above. Checked elements attempt to catch a variety of errors, such as when
+// elements from mismatched algebraic structures or pairings. If an error is
+// detected, the operation will panic with ErrIllegalOp, ErrUncheckedOp,
+// ErrIncompatible, or a similar error.
+//
+// The decision on whether or not to check operations is based solely on
+// whether or not the target element is checked. Thus, if an unchecked element
+// is passed a checked element as part of an operation, the operation will not
+// be checked. Checked elements expect that all arguments to their methods are
+// also checked, and will panic with ErrUncheckedOp if not.
+//
+// Note that not all possible errors can be detected by checked elements;
+// ultimately, it is the responsibility of the caller to ensure that the
+// requested computations make sense.
 type Element struct {
 	pairing *Pairing // Prevents garbage collection
 	cptr    *C.struct_element_s
@@ -47,6 +104,7 @@ func makeCheckedElement(pairing *Pairing, field Field, fieldPtr *C.struct_field_
 	element.checked = true
 	element.fieldPtr = fieldPtr
 	element.isInteger = (field == Zr)
+	element.Set0()
 	return element
 }
 
@@ -79,3 +137,10 @@ func (el *Element) checkInteger() {
 		panic(ErrIllegalOp)
 	}
 }
+
+func (el *Element) checkPoint() {
+	el.ensureChecked()
+	if el.isInteger {
+		panic(ErrIllegalOp)
+	}
+}
diff --git a/element_arith.go b/element_arith.go
index b2420175ddb28b7a24b7bc72ffe473f2c594c0b8..210e593a17f552172079bec12e6eb32413984b15 100644
--- a/element_arith.go
+++ b/element_arith.go
@@ -4,62 +4,79 @@ package pbc
 #include <pbc/pbc.h>
 */
 import "C"
+
 import (
 	"math/big"
 	"runtime"
 	"unsafe"
 )
 
+// Set0 sets el to zero and returns el. For curves, this sets the element to
+// the infinite point (identity element).
 func (el *Element) Set0() *Element {
 	C.element_set0(el.cptr)
 	return el
 }
 
+// Set1 sets el to one and returns el. For curves, this sets the element to the
+// infinite point (identity element).
 func (el *Element) Set1() *Element {
 	C.element_set1(el.cptr)
 	return el
 }
 
+// Rand sets el to a random value and returns el. For algebraic structures
+// where this does not make sense, this is equivalent to Set0.
 func (el *Element) Rand() *Element {
 	C.element_random(el.cptr)
 	return el
 }
 
-func normalizeSign(sign int64) int {
-	if sign > 0 {
-		return 1
-	}
-	if sign < 0 {
-		return -1
-	}
-	return 0
-}
-
-func (el *Element) Cmp(x *Element) int {
+// Equals returns true if el == x.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
+func (el *Element) Equals(x *Element) bool {
 	if el.checked {
 		el.checkCompatible(x)
 	}
-	return normalizeSign(int64(C.element_cmp(el.cptr, x.cptr)))
+	return int64(C.element_cmp(el.cptr, x.cptr)) == 0
 }
 
-func (el *Element) Equals(x *Element) bool { return el.Cmp(x) == 0 }
-
+// Is0 returns true if el is zero (or the identity element for curves).
 func (el *Element) Is0() bool {
 	return C.element_is0(el.cptr) != 0
 }
 
+// Is1 returns true if el is one (or the identity element for curves).
 func (el *Element) Is1() bool {
 	return C.element_is1(el.cptr) != 0
 }
 
+// IsSquare returns true if el is a perfect square (quadratic residue).
 func (el *Element) IsSquare() bool {
 	return C.element_is_sqr(el.cptr) != 0
 }
 
+// Sign returns 0 if el is 0. If el is not 0, the behavior depends on the
+// algebraic structure, but has the property that el.Sign() == !neg.Sign()
+// where neg is the negation of el.
 func (el *Element) Sign() int {
-	return normalizeSign(int64(C.element_sign(el.cptr)))
+	sign := int64(C.element_sign(el.cptr))
+	if sign > 0 {
+		return 1
+	}
+	if sign < 0 {
+		return -1
+	}
+	return 0
 }
 
+// Add sets el = x + y and returns el. For curve points, + denotes the group
+// operation.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure.
 func (el *Element) Add(x, y *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -68,6 +85,11 @@ func (el *Element) Add(x, y *Element) *Element {
 	return el
 }
 
+// Sub sets el = x - y and returns el. More precisely, el = x + (-y). For curve
+// points, + denotes the group operation.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure.
 func (el *Element) Sub(x, y *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -76,6 +98,11 @@ func (el *Element) Sub(x, y *Element) *Element {
 	return el
 }
 
+// Mul sets el = x * y and returns el. For curve points, * denotes the group
+// operation.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure.
 func (el *Element) Mul(x, y *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -84,6 +111,11 @@ func (el *Element) Mul(x, y *Element) *Element {
 	return el
 }
 
+// MulBig sets el = i * x and returns el. More precisely, el = x + x + ... + x
+// where there are i x's. For curve points, + denotes the group operation.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) MulBig(x *Element, i *big.Int) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -92,6 +124,12 @@ func (el *Element) MulBig(x *Element, i *big.Int) *Element {
 	return el
 }
 
+// MulInt32 sets el = i * x and returns el. More precisely,
+// el = x + x + ... + x where there are i x's. For curve points, + denotes the
+// group operation.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) MulInt32(x *Element, i int32) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -100,15 +138,27 @@ func (el *Element) MulInt32(x *Element, i int32) *Element {
 	return el
 }
 
-func (el *Element) MulZn(x, y *Element) *Element {
+// MulZn sets el = i * x and returns el. More precisely,
+// el = x + x + ... + x where there are i x's. For curve points, + denotes the
+// group operation.
+//
+// Requirements:
+// el and x must be from the same algebraic structure; and
+// i must be an element of an integer mod ring (e.g., Zn for some n).
+func (el *Element) MulZn(x, i *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
-		y.checkInteger()
+		i.checkInteger()
 	}
-	C.element_mul_zn(el.cptr, x.cptr, y.cptr)
+	C.element_mul_zn(el.cptr, x.cptr, i.cptr)
 	return el
 }
 
+// Div sets el = x / y and returns el. More precisely, el = x * (1/y). For
+// curve points, * denotes the group operation.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure.
 func (el *Element) Div(x, y *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -117,6 +167,10 @@ func (el *Element) Div(x, y *Element) *Element {
 	return el
 }
 
+// Double sets el = x + x and returns el.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) Double(x *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -125,6 +179,10 @@ func (el *Element) Double(x *Element) *Element {
 	return el
 }
 
+// Halve sets el = x / 2 and returns el.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) Halve(x *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -133,6 +191,10 @@ func (el *Element) Halve(x *Element) *Element {
 	return el
 }
 
+// Square sets el = x * x and returns el.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) Square(x *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -141,6 +203,10 @@ func (el *Element) Square(x *Element) *Element {
 	return el
 }
 
+// Neg sets el = -x and returns el.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) Neg(x *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -149,6 +215,10 @@ func (el *Element) Neg(x *Element) *Element {
 	return el
 }
 
+// Invert sets el = 1/x and returns el.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) Invert(x *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -157,6 +227,11 @@ func (el *Element) Invert(x *Element) *Element {
 	return el
 }
 
+// PowBig sets el = x^i and returns el. More precisely, el = x * x * ... * x
+// where there are i x's. For curve points, * denotes the group operation.
+//
+// Requirements:
+// el and x must be from the same algebraic structure.
 func (el *Element) PowBig(x *Element, i *big.Int) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -165,6 +240,13 @@ func (el *Element) PowBig(x *Element, i *big.Int) *Element {
 	return el
 }
 
+// PowZn sets el = x^i and returns el. More precisely, el = x * x * ... * x
+// where there are i x's. For curve points, * denotes the group operation.
+//
+// Requirements:
+// el and x must be from the same algebraic structure; and
+// i must be an element of an integer mod ring (e.g., Zn for some n, typically
+// the order of the algebraic structure that x lies in).
 func (el *Element) PowZn(x, i *Element) *Element {
 	if el.checked {
 		el.checkCompatible(x)
@@ -174,6 +256,11 @@ func (el *Element) PowZn(x, i *Element) *Element {
 	return el
 }
 
+// Pow2Big sets el = x^i * y^j and returns el. This is generally faster than
+// performing separate exponentiations.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure.
 func (el *Element) Pow2Big(x *Element, i *big.Int, y *Element, j *big.Int) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -182,6 +269,13 @@ func (el *Element) Pow2Big(x *Element, i *big.Int, y *Element, j *big.Int) *Elem
 	return el
 }
 
+// Pow2Zn sets el = x^i * y^j and returns el. This is generally faster than
+// performing separate exponentiations.
+//
+// Requirements:
+// el, x, and y must be from the same algebraic structure; and
+// i and j must be elements of integer mod rings (e.g., Zn for some n,
+// typically the order of the algebraic structures that x and y lie in).
 func (el *Element) Pow2Zn(x, i, y, j *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y)
@@ -192,6 +286,11 @@ func (el *Element) Pow2Zn(x, i, y, j *Element) *Element {
 	return el
 }
 
+// Pow3Big sets el = x^i * y^j * z^k and returns el. This is generally faster
+// than performing separate exponentiations.
+//
+// Requirements:
+// el, x, y, and z must be from the same algebraic structure.
 func (el *Element) Pow3Big(x *Element, i *big.Int, y *Element, j *big.Int, z *Element, k *big.Int) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y, z)
@@ -200,6 +299,13 @@ func (el *Element) Pow3Big(x *Element, i *big.Int, y *Element, j *big.Int, z *El
 	return el
 }
 
+// Pow3Big sets el = x^i * y^j * z^k and returns el. This is generally faster
+// than performing separate exponentiations.
+//
+// Requirements:
+// el, x, y, and z must be from the same algebraic structure; and
+// i, j, and k must be elements of integer mod rings (e.g., Zn for some n,
+// typically the order of the algebraic structures that x, y, and z lie in).
 func (el *Element) Pow3Zn(x, i, y, j, z, k *Element) *Element {
 	if el.checked {
 		el.checkAllCompatible(x, y, z)
@@ -214,18 +320,30 @@ func (el *Element) Pow3Zn(x, i, y, j, z, k *Element) *Element {
 // 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.
+// x^i will be stored in target. Once a Power has been generated, the original
+// element can be changed without affecting the pre-processed data.
 type Power struct {
 	source *Element // Prevents garbage collection
 	pp     *C.struct_element_pp_s
 }
 
+// Source returns the Element for which the pre-processed data was generated.
 func (power *Power) Source() *Element { return power.source }
 
+// PowBig sets target = s^i where s was the source Element for the Power, and
+// returns target. It is equivalent to target.PowerBig(power, i).
+//
+// Requirements:
+// target and s must be from the same algebraic structure.
 func (power *Power) PowBig(target *Element, i *big.Int) *Element {
 	return target.PowerBig(power, i)
 }
 
+// PowZn sets target = s^i where s was the source Element for the Power, and
+// returns target. It is equivalent to target.PowerZn(power, i).
+//
+// Requirements:
+// target and s must be from the same algebraic structure.
 func (power *Power) PowZn(target *Element, i *Element) *Element {
 	return target.PowerZn(power, i)
 }
@@ -234,6 +352,9 @@ func clearPower(power *Power) {
 	C.element_pp_clear(power.pp)
 }
 
+// PreparePower generates pre-processing data for repeatedly exponentiating el.
+// The returned Power can be used to raise el to a power several times, and is
+// generally faster than repeatedly calling the standard Pow methods on el.
 func (el *Element) PreparePower() *Power {
 	power := &Power{
 		source: el,
@@ -244,19 +365,42 @@ func (el *Element) PreparePower() *Power {
 	return power
 }
 
+// PowerBig sets el = s^i where s was the source Element for the Power, and
+// returns el. It is equivalent to power.PowBig(el, i).
+//
+// Requirements:
+// el and s must be from the same algebraic structure.
 func (el *Element) PowerBig(power *Power, i *big.Int) *Element {
+	if el.checked {
+		el.checkCompatible(power.source)
+	}
 	C.element_pp_pow(el.cptr, &big2mpz(i)[0], power.pp)
 	return el
 }
 
+// PowerZn sets el = s^i where s was the source Element for the Power, and
+// returns el. It is equivalent to power.PowZn(el, i).
+//
+// Requirements:
+// el and s must be from the same algebraic structure; and
+// i must be an element of an integer mod ring (e.g., Zn for some n, typically
+// the order of the algebraic structure that s lies in).
 func (el *Element) PowerZn(power *Power, i *Element) *Element {
 	if el.checked {
+		el.checkCompatible(power.source)
 		i.checkInteger()
 	}
 	C.element_pp_pow_zn(el.cptr, i.cptr, power.pp)
 	return el
 }
 
+// Pair sets el = e(x,y) where e denotes the pairing operation, and returns el.
+//
+// Requirements:
+// el, x, and y must belong to the same pairing;
+// el must belong to the pairing's GT group;
+// x must belong to the pairing's G1 group (or G2 for symmetric pairings); and
+// y must belong to the pairing's G2 group (or G1 for symmetric pairings).
 func (el *Element) Pair(x, y *Element) *Element {
 	if el.checked {
 		x.ensureChecked()
@@ -277,6 +421,21 @@ func (el *Element) doProdPair(in1, in2 []C.struct_element_s) *Element {
 	return el
 }
 
+// ProdPair sets el to the product of several pairings, and returns el. The
+// elements are paired in groups of two.
+//
+// For example:
+// 	el.ProdPair(a,b,c,d,e,f)
+// will set el = e(a,b) * e(c,d) * e(e,f).
+//
+// Requirements:
+// all elements must belong to the same pairing;
+// el must belong to the pairing's GT group;
+// there must be an even number of parameters;
+// odd numbered parameters must belong to the pairing's G1 group (or G2 for
+// symmetric pairings); and
+// even numbered parameters must belong to the pairing's G2 group (or G1 for
+// symmetric pairings).
 func (el *Element) ProdPair(elements ...*Element) *Element {
 	n := len(elements)
 	if n%2 != 0 {
@@ -302,6 +461,21 @@ func (el *Element) ProdPair(elements ...*Element) *Element {
 	return el.doProdPair(in1, in2)
 }
 
+// ProdPairSlice sets el to the product of several pairings, and returns el.
+// Elements from x will be paired with elements in y having the same index.
+//
+// For example:
+// 	el.ProdPairSlice([]*Element{a,b,c}, []*Element{d,e,f})
+// will set el = e(a,d) * e(b,e) * e(c,f).
+//
+// Requirements:
+// all elements must belong to the same pairing;
+// el must belong to the pairing's GT group;
+// the slices must have the same number of elements;
+// elements in x must belong to the pairing's G1 group (or G2 for symmetric
+// pairings); and
+// elements in y must belong to the pairing's G2 group (or G1 for symmetric
+// pairings).
 func (el *Element) ProdPairSlice(x, y []*Element) *Element {
 	n := len(x)
 	if n != len(y) {
@@ -332,14 +506,25 @@ func (el *Element) ProdPairSlice(x, y []*Element) *Element {
 // 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.
+// target. Once a Pairer has been generated, the original element can be
+// changed without affecting the pre-processed data.
 type Pairer struct {
 	source *Element // Prevents garbage collection
 	pp     *C.struct_pairing_pp_s
 }
 
+// Source returns the Element for which the pre-processed data was generated.
 func (pairer *Pairer) Source() *Element { return pairer.source }
 
+// Pair sets target = e(s,y) and returns target, where e denotes the pairing
+// operation, and s was the source Element for the Pairer. It is equivalent to
+// target.PairerPair(pairer, y).
+//
+// Requirements:
+// target, s, and y must belong to the same pairing;
+// target must belong to the pairing's GT group;
+// s must belong to the pairing's G1 group (or G2 for symmetric pairings); and
+// y must belong to the pairing's G2 group (or G1 for symmetric pairings).
 func (pairer *Pairer) Pair(target *Element, y *Element) *Element {
 	return target.PairerPair(pairer, y)
 }
@@ -348,7 +533,16 @@ func clearPairer(pairer *Pairer) {
 	C.pairing_pp_clear(pairer.pp)
 }
 
+// PreparePairer generates pre-processing data for repeatedly pairing el. The
+// returned Pairer can be used to pair el several times, and is generally
+// faster than repeatedly calling Pair on el.
+//
+// Requirements:
+// el must belong to the pairing's G1 group (or G2 for symmetric pairings).
 func (el *Element) PreparePairer() *Pairer {
+	if el.checked {
+		checkFieldsMatch(el.fieldPtr, el.pairing.cptr.G1)
+	}
 	pairer := &Pairer{
 		source: el,
 		pp:     &C.struct_pairing_pp_s{},
@@ -358,6 +552,15 @@ func (el *Element) PreparePairer() *Pairer {
 	return pairer
 }
 
+// PairerPair sets el = e(s,y) and returns el, where e denotes the pairing
+// operation, and s was the source Element for the Pairer. It is equivalent to
+// pairer.Pair(el, y).
+//
+// Requirements:
+// el, s, and y must belong to the same pairing;
+// el must belong to the pairing's GT group;
+// s must belong to the pairing's G1 group (or G2 for symmetric pairings); and
+// y must belong to the pairing's G2 group (or G1 for symmetric pairings).
 func (el *Element) PairerPair(pairer *Pairer, y *Element) *Element {
 	if el.checked {
 		pairer.source.ensureChecked()
diff --git a/element_fmt.go b/element_fmt.go
index 410ad1ed887613b63fc2256605f02c31fb2b28ce..a6f67353be7b72baf5284c39c146d3bb16e12ed4 100644
--- a/element_fmt.go
+++ b/element_fmt.go
@@ -60,6 +60,19 @@ func (el *Element) customFormat(f fmt.State, c rune) {
 	}
 }
 
+// Format is a support routine for fmt.Formatter. It accepts many formats. The
+// 'v' (value) and 's' (string) verbs will format the Element using the PBC
+// library's internal formatting routines. These verbs accept variable widths
+// to specify the base of the integers. Valid values are 2 to 36, inclusive.
+//
+// If the 'v' verb is used with the '#' (alternate format) flag, the output is
+// metadata about the element in a pseudo-Go syntax. Checked elements will
+// print more information than unchecked elements in this mode.
+//
+// If the 'd', 'b', 'o', 'x', or 'X' verbs are used, then the element is
+// formatted within Go. The syntax approximates the PBC library's formatting,
+// but integers are converted to big.Int for formatting. All of the verbs and
+// flags that can be used in math/big will be used to format the elements.
 func (el *Element) Format(f fmt.State, c rune) {
 	switch c {
 	case 'v':
@@ -81,10 +94,14 @@ func (el *Element) Format(f fmt.State, c rune) {
 	}
 }
 
+// String converts el to a string using the default PBC library format.
 func (el *Element) String() string {
 	return fmt.Sprintf("%s", el)
 }
 
+// SetString sets el to the value contained in s. Returns (el, true) if
+// successful, and (nil, false) if an error occurs. s is expected to be in the
+// same format produced by String().
 func (el *Element) SetString(s string, base int) (*Element, bool) {
 	cstr := C.CString(s)
 	defer C.free(unsafe.Pointer(cstr))
@@ -95,16 +112,24 @@ func (el *Element) SetString(s string, base int) (*Element, bool) {
 	return el, true
 }
 
+// Scan is a support routine for fmt.Scanner. It accepts the verbs 's' and 'v'
+// only; only strings produced in PBC library format can be scanned. The width
+// is used to denote the base of integers in the data.
 func (el *Element) Scan(state fmt.ScanState, verb rune) error {
+	// Verify verbs
 	if verb != 's' && verb != 'v' {
 		return ErrBadVerb
 	}
+
+	// Verify base
 	base, ok := state.Width()
 	if !ok {
 		base = 10
 	} else if base < 2 || base > 36 {
 		return ErrBadVerb
 	}
+
+	// Compute valid integer symbols
 	maxDigit := '9'
 	maxAlpha := 'z'
 	if base < 10 {
@@ -116,6 +141,9 @@ func (el *Element) Scan(state fmt.ScanState, verb rune) error {
 
 	state.SkipSpace()
 
+	// Validate the input using a state machine (passing PBC invalid input is
+	// likely to lead to bad outcomes)
+
 	tokensFound := make([]uint, 0, 5)
 	inToken := false
 	justDescended := false
@@ -176,6 +204,8 @@ ReadLoop:
 		}
 		justDescended = (r == ']')
 	}
+
+	// The string seems valid; pass it to PBC
 	if _, ok := el.SetString(buf.String(), base); !ok {
 		return ErrBadInput
 	}
diff --git a/element_io.go b/element_io.go
index 739a50998ef528df49abcd2c10b453a7390a7520..b57c5fdfe23114d38193cce3e1513fcd590ad0c1 100644
--- a/element_io.go
+++ b/element_io.go
@@ -4,12 +4,17 @@ package pbc
 #include <pbc/pbc.h>
 */
 import "C"
+
 import (
 	"hash"
 	"math/big"
 	"unsafe"
 )
 
+// BigInt converts the Element to a big.Int if such an operation makes sense.
+//
+// Requirements:
+// el is expressible as an integer (e.g., an element of Zn, but not a point).
 func (el *Element) BigInt() *big.Int {
 	if el.checked {
 		el.checkInteger()
@@ -19,6 +24,10 @@ func (el *Element) BigInt() *big.Int {
 	return mpz2big(mpz)
 }
 
+// Set sets the value of el to be the same as src.
+//
+// Requirements:
+// el and src must be from the same algebraic structure.
 func (el *Element) Set(src *Element) *Element {
 	if el.checked {
 		el.checkCompatible(src)
@@ -27,6 +36,11 @@ func (el *Element) Set(src *Element) *Element {
 	return el
 }
 
+// SetInt32 sets the value of el to the integer i. This operation is only valid
+// for elements in integer fields (e.g., Zr for a pairing).
+//
+// Requirements:
+// el must be an element of an integer mod ring (e.g., Zn for some n).
 func (el *Element) SetInt32(i int32) *Element {
 	if el.checked {
 		el.checkInteger()
@@ -35,6 +49,11 @@ func (el *Element) SetInt32(i int32) *Element {
 	return el
 }
 
+// SetBig sets the value of el to the integer i. This operation is only valid
+// for elements in integer fields (e.g., Zr for a pairing).
+//
+// Requirements:
+// el must be an element of an integer mod ring (e.g., Zn for some n).
 func (el *Element) SetBig(i *big.Int) *Element {
 	if el.checked {
 		el.checkInteger()
@@ -43,11 +62,14 @@ func (el *Element) SetBig(i *big.Int) *Element {
 	return el
 }
 
+// SetFromHash generates el deterministically from the bytes in hash.
 func (el *Element) SetFromHash(hash []byte) *Element {
 	C.element_from_hash(el.cptr, unsafe.Pointer(&hash[0]), C.int(len(hash)))
 	return el
 }
 
+// SetFromStringHash hashes s with h and then calls SetFromHash. h may or may
+// not be a cryptographic hash, depending on the higher level protocol.
 func (el *Element) SetFromStringHash(s string, h hash.Hash) *Element {
 	h.Reset()
 	if _, err := h.Write([]byte(s)); err != nil {
@@ -56,10 +78,12 @@ func (el *Element) SetFromStringHash(s string, h hash.Hash) *Element {
 	return el.SetFromHash(h.Sum([]byte{}))
 }
 
+// BytesLen returns the number of bytes needed to represent el.
 func (el *Element) BytesLen() int {
 	return int(C.element_length_in_bytes(el.cptr))
 }
 
+// Bytes exports el as a byte sequence.
 func (el *Element) Bytes() []byte {
 	buf := make([]byte, el.BytesLen())
 	written := C.element_to_bytes((*C.uchar)(unsafe.Pointer(&buf[0])), el.cptr)
@@ -69,16 +93,31 @@ func (el *Element) Bytes() []byte {
 	return buf
 }
 
+// SetBytes imports a sequence exported by Bytes() and sets the value of el.
 func (el *Element) SetBytes(buf []byte) *Element {
 	C.element_from_bytes(el.cptr, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
 
+// XBytesLen returns the number of bytes needed to represent el's X coordinate.
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) XBytesLen() int {
+	if el.checked {
+		el.checkPoint()
+	}
 	return int(C.element_length_in_bytes_x_only(el.cptr))
 }
 
+// XBytes exports el's X coordinate as a byte sequence.
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) XBytes() []byte {
+	if el.checked {
+		el.checkPoint()
+	}
 	buf := make([]byte, el.XBytesLen())
 	written := C.element_to_bytes_x_only((*C.uchar)(unsafe.Pointer(&buf[0])), el.cptr)
 	if int64(written) > int64(len(buf)) {
@@ -87,16 +126,44 @@ func (el *Element) XBytes() []byte {
 	return buf
 }
 
+// SetXBytes imports a sequence exported by XBytes() and sets el to be a point
+// on the curve with the given X coordinate. In general, this point is not
+// unique. For each X coordinate, there exist two different points (for the
+// pairings in PBC), and they are inverses of each other. An application can
+// deal with this by either exporting the sign of the element along with the X
+// coordinate, or by testing the value to see if it makes sense in the higher
+// level protocol (and inverting it if it does not).
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) SetXBytes(buf []byte) *Element {
+	if el.checked {
+		el.checkPoint()
+	}
 	C.element_from_bytes_x_only(el.cptr, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
 
+// CompressedBytesLen returns the number of bytes needed to represent a
+// compressed form of el.
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) CompressedBytesLen() int {
+	if el.checked {
+		el.checkPoint()
+	}
 	return int(C.element_length_in_bytes_compressed(el.cptr))
 }
 
+// CompressedBytes exports el in a compressed form as a byte sequence.
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) CompressedBytes() []byte {
+	if el.checked {
+		el.checkPoint()
+	}
 	buf := make([]byte, el.CompressedBytesLen())
 	written := C.element_to_bytes_compressed((*C.uchar)(unsafe.Pointer(&buf[0])), el.cptr)
 	if int64(written) > int64(len(buf)) {
@@ -105,7 +172,15 @@ func (el *Element) CompressedBytes() []byte {
 	return buf
 }
 
+// SetCompressedBytes imports a sequence exported by CompressedBytes() and sets
+// the value of el.
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) SetCompressedBytes(buf []byte) *Element {
+	if el.checked {
+		el.checkPoint()
+	}
 	C.element_from_bytes_compressed(el.cptr, (*C.uchar)(unsafe.Pointer(&buf[0])))
 	return el
 }
diff --git a/element_misc.go b/element_misc.go
index 2b6fdaec624025c83eba8efd6792e8ce0138d1ad..51f11c9b7093ed31b8bc045d74a627611938784c 100644
--- a/element_misc.go
+++ b/element_misc.go
@@ -4,10 +4,14 @@ package pbc
 #include <pbc/pbc.h>
 */
 import "C"
+
 import "math/big"
 
+// Pairing returns the pairing associated with this element.
 func (el *Element) Pairing() *Pairing { return el.pairing }
 
+// NewFieldElement creates a new element in the same field as el. The new
+// element will be unchecked if and only if el is unchecked.
 func (el *Element) NewFieldElement() *Element {
 	newElement := makeUncheckedElement(el.pairing, false, G1)
 	C.element_init_same_as(newElement.cptr, el.cptr)
@@ -19,10 +23,17 @@ func (el *Element) NewFieldElement() *Element {
 	return newElement
 }
 
+// Len returns the length of this element. For points, this is the number of
+// coordinates. For polynomials, it is the number of coefficients. For infinite
+// points, it is zero. For all other values, it is zero.
 func (el *Element) Len() int {
 	return int(C.element_item_count(el.cptr))
 }
 
+// Item returns the specified sub-element. For points, this returns a
+// coordinate. For polynomials, it returns a coefficient. For other elements,
+// this operation is invalid. i must be greater than or equal to 0 and less
+// than el.Len(). Bounds checking is only performed for checked elements.
 func (el *Element) Item(i int) *Element {
 	if el.checked && i >= el.Len() {
 		panic(ErrOutOfRange)
@@ -31,6 +42,9 @@ func (el *Element) Item(i int) *Element {
 		pairing: el.pairing,
 		cptr:    C.element_item(el.cptr, C.int(i)),
 	}
+	if newElement.cptr == nil {
+		panic(ErrOutOfRange)
+	}
 	if el.checked {
 		newElement.fieldPtr = newElement.cptr.field
 		newElement.isInteger = (newElement.Len() == 0)
@@ -38,14 +52,28 @@ func (el *Element) Item(i int) *Element {
 	return newElement
 }
 
+// X returns the X coordinate of el. Equivalent to el.Item(0).BigInt().
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) X() *big.Int {
 	return el.Item(0).BigInt()
 }
 
+// Y returns the Y coordinate of el. Equivalent to el.Item(1).BigInt().
+//
+// Requirements:
+// el must be a point on an elliptic curve.
 func (el *Element) Y() *big.Int {
 	return el.Item(1).BigInt()
 }
 
+// BruteForceDL sets el such that g^el = h using brute force.
+//
+// Requirements:
+// g and h must be from the same algebraic structure; and
+// el must be an element of an integer mod ring (e.g., Zn for some n, typically
+// the order of the algebraic structure that g lies in).
 func (el *Element) BruteForceDL(g, h *Element) *Element {
 	if el.checked {
 		el.checkInteger()
@@ -56,6 +84,12 @@ func (el *Element) BruteForceDL(g, h *Element) *Element {
 	return el
 }
 
+// PollardRhoDL sets el such that g^el = h using Pollard rho method.
+//
+// Requirements:
+// g and h must be from the same algebraic structure; and
+// el must be an element of an integer mod ring (e.g., Zn for some n, typically
+// the order of the algebraic structure that g lies in).
 func (el *Element) PollardRhoDL(g, h *Element) *Element {
 	if el.checked {
 		el.checkInteger()
diff --git a/doc_test.go b/example_test.go
similarity index 53%
rename from doc_test.go
rename to example_test.go
index 3bbe420c4903da1621e1f9e2414530a9e4265765..537f3b17ecd9dffa1873ad2ef612eecb8152b7ee 100644
--- a/doc_test.go
+++ b/example_test.go
@@ -1,12 +1,16 @@
-package pbc
+package pbc_test
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/Nik-U/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 := GenerateA(160, 512)
+	params := pbc.GenerateA(160, 512)
 
 	pairing := params.NewPairing()
 
@@ -23,3 +27,12 @@ func Example() {
 	x.Pair(g, h)
 	fmt.Printf("e(g,h) = %s\n", x)
 }
+
+func ExampleElement_Format() {
+	fmt.Printf("%v", element)    // Print in PBC format
+	fmt.Printf("%s", element)    // Same as above
+	fmt.Printf("%36v", element)  // Print in PBC format, base 36
+	fmt.Printf("%#v", element)   // Print metadata about element
+	fmt.Printf("%d", element)    // Print with Go
+	fmt.Printf("%010o", element) // Print with Go, zero-padded width-10 octal
+}
diff --git a/generation.go b/generation.go
index e35ea3dc51a0546a410225ded97bfa92d1588128..9e2a9fb2bff53383e044b023fc644041ef3b81e2 100644
--- a/generation.go
+++ b/generation.go
@@ -44,6 +44,8 @@ import (
 //
 // For example:
 // 	params := pbc.GenerateA(160, 512)
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s03.html
 func GenerateA(rbits uint32, qbits uint32) *Params {
 	params := makeParams()
 	C.pbc_param_init_a_gen(params.cptr, C.int(rbits), C.int(qbits))
@@ -55,6 +57,8 @@ func GenerateA(rbits uint32, qbits uint32) *Params {
 // 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.
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s03.html
 func GenerateA1(r *big.Int) *Params {
 	params := makeParams()
 	C.pbc_param_init_a1_gen(params.cptr, &big2mpz(r)[0])
@@ -81,6 +85,8 @@ func GenerateA1(r *big.Int) *Params {
 //
 // For example:
 // 	params, err := pbc.GenerateD(9563, 160, 171, 500)
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s06.html
 func GenerateD(d uint32, rbits uint32, qbits uint32, bitlimit uint32) (*Params, error) {
 	return generateWithCM(true, d, rbits, qbits, bitlimit)
 }
@@ -97,6 +103,8 @@ func GenerateD(d uint32, rbits uint32, qbits uint32, bitlimit uint32) (*Params,
 //
 // For example:
 // 	params, err := pbc.GenerateE(160, 1024)
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s07.html
 func GenerateE(rbits uint32, qbits uint32) *Params {
 	params := makeParams()
 	C.pbc_param_init_e_gen(params.cptr, C.int(rbits), C.int(qbits))
@@ -115,6 +123,8 @@ func GenerateE(rbits uint32, qbits uint32) *Params {
 //
 // For example:
 // 	params, err := pbc.GenerateF(160)
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s08.html
 func GenerateF(bits uint32) *Params {
 	params := makeParams()
 	C.pbc_param_init_f_gen(params.cptr, C.int(bits))
@@ -132,6 +142,8 @@ func GenerateF(bits uint32) *Params {
 //
 // For example:
 // 	params, err := pbc.GenerateG(9563, 160, 171, 500)
+//
+// More details: https://crypto.stanford.edu/pbc/manual/ch08s09.html
 func GenerateG(d uint32, rbits uint32, qbits uint32, bitlimit uint32) (*Params, error) {
 	return generateWithCM(false, d, qbits, rbits, bitlimit)
 }
diff --git a/pairing.go b/pairing.go
index e7f532c3d9210fa165a539ef0765f4831e4c0d57..9d49331d277be565caa6b0bc204dba0c7ec3f65c 100644
--- a/pairing.go
+++ b/pairing.go
@@ -25,10 +25,8 @@ const (
 
 // 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.
+// created in G1, G2, GT, or Zr. Additionally, elements can be checked or
+// unchecked. See the Element type for more details.
 type Pairing struct {
 	cptr *C.struct_pairing_s
 }
@@ -63,55 +61,71 @@ func (pairing *Pairing) IsSymmetric() bool {
 	return C.pairing_is_symmetric(pairing.cptr) != 0
 }
 
+// G1Length returns the size of elements in G1, in bytes.
 func (pairing *Pairing) G1Length() uint {
 	return uint(C.pairing_length_in_bytes_G1(pairing.cptr))
 }
 
+// G1XLength returns the size of X coordinates of elements in G1, in bytes.
 func (pairing *Pairing) G1XLength() uint {
 	return uint(C.pairing_length_in_bytes_x_only_G1(pairing.cptr))
 }
 
+// G1CompressedLength returns the size of compressed elements in G1, in bytes.
 func (pairing *Pairing) G1CompressedLength() uint {
 	return uint(C.pairing_length_in_bytes_compressed_G1(pairing.cptr))
 }
 
+// G2Length returns the size of elements in G2, in bytes.
 func (pairing *Pairing) G2Length() uint {
 	return uint(C.pairing_length_in_bytes_G2(pairing.cptr))
 }
 
+// G2XLength returns the size of X coordinates of elements in G2, in bytes.
 func (pairing *Pairing) G2XLength() uint {
 	return uint(C.pairing_length_in_bytes_x_only_G2(pairing.cptr))
 }
 
+// G2CompressedLength returns the size of compressed elements in G2, in bytes.
 func (pairing *Pairing) G2CompressedLength() uint {
 	return uint(C.pairing_length_in_bytes_compressed_G2(pairing.cptr))
 }
 
+// GTLength returns the size of elements in GT, in bytes.
 func (pairing *Pairing) GTLength() uint {
 	return uint(C.pairing_length_in_bytes_GT(pairing.cptr))
 }
 
+// ZrLength returns the size of elements in Zr, in bytes.
 func (pairing *Pairing) ZrLength() uint {
 	return uint(C.pairing_length_in_bytes_Zr(pairing.cptr))
 }
 
+// NewG1 creates a new checked element in G1.
 func (pairing *Pairing) NewG1() *Element {
 	return makeCheckedElement(pairing, G1, pairing.cptr.G1)
 }
 
+// NewG2 creates a new checked element in G2.
 func (pairing *Pairing) NewG2() *Element {
 	return makeCheckedElement(pairing, G2, pairing.cptr.G2)
 }
 
+// NewGT creates a new checked element in GT.
 func (pairing *Pairing) NewGT() *Element {
 	return makeCheckedElement(pairing, GT, &pairing.cptr.GT[0])
 }
 
+// NewZr creates a new checked element in Zr.
 func (pairing *Pairing) NewZr() *Element {
 	return makeCheckedElement(pairing, Zr, &pairing.cptr.Zr[0])
 }
 
-func (pairing *Pairing) NewElement(field Field) *Element {
+// NewUncheckedElement creates a new unchecked element in the target field.
+// Unchecked elements are dangerous; see the Element documentation before
+// deciding to use this method. It is safer to create elements using the NewG1,
+// NewG2, NewGT, or NewZr methods.
+func (pairing *Pairing) NewUncheckedElement(field Field) *Element {
 	return makeUncheckedElement(pairing, true, field)
 }
 
diff --git a/params.go b/params.go
index 87fb87b1e956fa54d0ac68697eafa43a77d409d3..5d623af7ef95fe135ec29c31f1c33b91b72db2f2 100644
--- a/params.go
+++ b/params.go
@@ -21,10 +21,17 @@ import (
 )
 
 // Params represents the parameters required for creating a pairing. Parameters
-// cn be generated using the generation functions or read from a Reader.
+// can 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.
+//
+// For applications requiring fast computation, type A pairings are preferred.
+// Applications requiring small message sizes should consider type D pairings.
+// If speed is not a concern, type F pairings yield the smallest messages at
+// the cost of additional computation. Applications requiring symmetric
+// pairings should use type A. If a specific group order must be used (e.g.,
+// for composite orders), then type A1 pairings are required.
 type Params struct {
 	cptr *C.struct_pbc_param_s
 }
@@ -55,11 +62,13 @@ func (params *Params) NewPairing() *Pairing {
 	return NewPairing(params)
 }
 
+// WriteTo writes the pairing parameters to a Writer.
 func (params *Params) WriteTo(w io.Writer) (n int64, err error) {
 	count, err := io.WriteString(w, params.String())
 	return int64(count), err
 }
 
+// String returns a string representation of the pairing parameters.
 func (params *Params) String() string {
 	var buf *C.char
 	var bufLen C.size_t