TL;DR: Native segwit refers to segwit outputs without P2SH-wrapper. Bech32 is the address format used to represent native segwit-v0 locking scripts.
Wrapped segwit vs native segwit
When segwit activated, it introduced the zeroth generation of segwit, segwit-v0. The corresponding locking scripts came in two flavors:
The forward-compatible "wrapped ...
I'm going to provide a more algebraic explanation of the issue.
Every valid (and invalid) Bech32 string can be seen as a polynomial in the variable x, where the coefficients are elements of GF(32). I'm going to ignore the GF(32) part in what follows, so it suffices to say that they're simply numbers from 0 to 31 inclusive, with weird addition and ...
Pieter Wuille's comment gives a nice summary:
basically: take a bech32 string, xor a 1 into the last character, then push or pop as many 'q's as you like, and then xor a 1 into the last character again... should always give you a valid new bech32 string
Checksum code taken from Bitcoin Core is:
uint32_t PolyMod(const data& v)
uint32_t c = 1;
You differentiate a Taproot scriptPubKey and a P2WSH scriptPubKey by their witness version. 1 for Taproot, 0 for P2WSH.
A "P2WSH with version 1" doesn't exist, it would just be treated as a Taproot scriptPubKey by upgraded nodes and as an anyone-can-spend by legacy ones.
P2WSH: <0> <32-bytes push>
Taproot: <1> <32-bytes push>...
As far as I can tell "length extension mutation weakness" is the sole problem that needed to be resolved and is resolved by bech32m. But "length extension mutation weakness" includes both insertion and deletion.
Insertion: if a valid Bech32 string has the suffix p, inserting a single q character immediately before the p will produce ...