Browse WireGuard specs (19 of 38)

Proofs / WireGuard / Encryption and the primitives

ae_split_poly1305

Poly1305 gives the same tag for the message in one update or in any pieces, for any key.

spec
33 lines of C
with its headers
73 lines
status
Proved
19 of 38
On this page

The spec

The spec is the C program harness/ae_split_poly1305.c, 33 lines, shown whole as it is in the repository at 3c1fc13. It includes harness/wg_laws.h (40 lines). Its first comment states the property in full, with every bound.

C
// Law ae_split_poly1305: For any 32-byte key and any message (up to PTRDIFF_MAX bytes), poly1305_init,
// poly1305_update over the whole message and poly1305_final give the same 16-byte tag as poly1305_update over the
// message in any pieces of any lengths (empty pieces too).
#include "wg_laws.h"

int main(void) {
	// Choose the inputs: any key, any message
	u8 key[POLY1305_KEY_SIZE];
	nondet_bytes(key, sizeof key);
	size_t len = upto(PTRDIFF_MAX);
	u8 *message = any_bytes(len);
	if (message == NULL)
		return 0;
	// In one call
	struct poly1305_desc_ctx one, many;
	u8 whole[POLY1305_DIGEST_SIZE], pieces[POLY1305_DIGEST_SIZE];
	poly1305_init(&one, key);
	poly1305_update(&one, message, len);
	poly1305_final(&one, whole);
	// In any pieces. RFC 8439, 2.5.1: "divide the message into 16-byte blocks. The last one might be shorter":
	// the tag depends on the key and the message only, not on how the message was handed over.
	poly1305_init(&many, key);
	size_t done = 0;
	while (done < len || nondet_u8() != 0) {
		size_t take = upto(len - done);
		poly1305_update(&many, message + done, take);
		done += take;
	}
	poly1305_final(&many, pieces);
	// Check
	gp_assert(bytes_eq(whole, pieces, POLY1305_DIGEST_SIZE));
	return 0;
}
harness/wg_laws.h, 40 lines
C
// wg_laws.h: trivial helpers shared by the WireGuard laws (each law includes it once). No models, no algorithms.
#include "quickproof.h"
#include "noise.h"
#include <crypto/blake2s.h>
#include <crypto/chacha.h>
#include <crypto/poly1305.h>
#include <crypto/chacha20poly1305.h>
bool wg_counter_validate(struct noise_replay_counter *counter, u64 nonce);   // receive.c's counter_validate (S19)
#define REJECT_AFTER (U64_MAX - COUNTER_WINDOW_SIZE - 1)   // messages.h: REJECT_AFTER_MESSAGES, 2^64 - 8130
static size_t upto(size_t max) {                      // any value from 0 to max
	size_t value = nondet_size();
	return value < max ? value : max;
}
static u64 any_u64(void) {                            // any 64-bit value
	u64 high = nondet_u32();
	return high << 32 | nondet_u32();
}
static u8 *any_bytes(size_t n) {                      // a new object of n arbitrary bytes (NULL if malloc fails)
	u8 *bytes = malloc(n);
	if (bytes != NULL && n != 0)
		nondet_bytes(bytes, n);
	return bytes;
}
static u8 *copy_of(const u8 *source, size_t n) {      // a new object holding source[0..n-1]
	u8 *copy = malloc(n);
	for (size_t i = 0; copy != NULL && i < n; i++)
		copy[i] = source[i];
	return copy;
}
static int bytes_eq(const u8 *a, const u8 *b, size_t n) {   // a[0..n-1] == b[0..n-1]
	for (size_t i = 0; i < n; i++)
		if (a[i] != b[i])
			return 0;
	return 1;
}
static void new_counter(struct noise_replay_counter *c) {    // a new key's replay counter: zero (keypair_create)
	c->counter = 0;
	for (size_t i = 0; i < COUNTER_BITS_TOTAL / BITS_PER_LONG; i++)
		c->backtrack[i] = 0;
}

The theorem

QuickProof generated this statement from the spec, in src/contracts/ae_split_poly1305.v:

Rocq
Theorem ae_split_poly1305 : harness_safe whole_prog.

src/contracts/laws.v states it on the spec's own program, whole_prog (the spec linked with the code under test), with its proof:

Rocq
Theorem ae_split_poly1305 : harness_safe WgNoise.contracts.ae_split_poly1305.whole_prog.
Proof. exact (proj1 (proj2 (proj2 (proj2 (proj2 WgNoise.contracts.suite.suite))))). Qed.

Read as a statement about runs, it says that no run of the program has undefined behaviour or a failing check, for every value the spec can draw. That reading rests on VST's adequacy theorem, which is not yet instantiated in Rocq (formerly Coq) for QuickProof's environment.

The C files (units) its program links
  • lib/crypto/poly1305.c
  • lib/crypto/poly1305-donna32.c
  • quickproof/libc/libc.c

What it leaves out

  • What the first comment of the spec excludes, and its bounds.
  • Like every spec of this proof: runs of more than one thread (the locks and RCU are those of one thread), termination (a run that never returns is not a counterexample), and binaries built by a compiler (the theorem is about the code as CompCert defines C for 64-bit x86).
  • The kernel around the code is a shim of plain C for one thread, and the code carries rewrites of idioms that VST cannot handle. Both are tested against the kernel's code, not proved.

The summary page says what the proof as a whole covers.

How it was proved

AI agents wrote the proof, and Rocq checks it. qp verify passes on this repository: this statement is the one generated from the spec at the contract commit, and its proof uses only Rocq's standard axioms and VST's abstract malloc library.