Why zero-allocation parsing changes the shape of COSE handling in firmware
Zero-allocation parsing removes a familiar escape hatch. There is no quiet growth into malloc and no hidden buffer to absorb sloppy sizing, so each message path has to fit the RAM you already have.
For COSE, that tends to force a stricter split between parsing, crypto, and output handling. The parser cannot wander off and make space for itself. It has to work against caller-provided storage and stop when the buffer contract is not good enough.
That design suits constrained targets because the memory profile stays flat. A minimal build is reported at 7.5 KB of text with zero .data and .bss, and a full COSE lifecycle can run in under 1 KB of RAM, excluding wolfCrypt internals. The point is not that embedded systems magically become roomy. The point is that the library does not make them worse.
Caller-provided buffers, fixed RAM, and the bits that actually stay small
The practical limit is usually not the parser alone. It is the whole message path, from CBOR input through COSE structure handling to the final encoded output. If the buffer is too small, the library has to fail early and cleanly instead of growing into a problem.
That behaviour matters for firmware security too. A bounded buffer keeps memory use visible, which makes it easier to reason about what an attacker can force the code to do. Hidden allocation often turns malformed input into a resource problem. Fixed RAM turns it into a normal failure path.
wolfCOSE also supports COSEKey and COSEKeySet serialisation, which keeps key handling in the same constrained model. That is useful on smaller targets where keys, messages, and state all compete for the same tiny pool of memory.
wolfSSL build choices that decide which COSE paths are available
The available COSE paths depend on how wolfSSL is built. A minimal profile can be kept down to ECC plus AES-GCM, SHA-384, SHA-512, and key generation. A broader build enables more of the COSE surface, including Ed25519, Ed448, curve25519, AES-CCM, RSASSA-PSS, ChaCha20, Poly1305, Dilithium, HKDF, and AES key wrap.
That is not just a feature list. It decides whether the firmware can use a given signing or encryption path without carrying dead code for algorithms it will never touch. On a constrained target, that matters because unused crypto support still has a size cost.
The wolfSSL version also matters. The documented minimum is v5.8.0-stable, with a public wcForceZero symbol, final FIPS 204 ML-DSA support, and context-aware wcdilithium*ctx_msg APIs. Older 5.x releases need source-level changes, which is the sort of detail that tends to surface only after someone tries to build it on a real board.
Signing, encryption, and MAC flows without heap use
wolfCOSE covers all six COSE message types, including the multi-signer and multi-recipient variants. The API set is split along the same lines: wcCoseSign1Sign and wcCoseSign1Verify for single-signer flows, wcCoseSignSign and wcCoseSignVerify for multi-signer messages, wcCoseEncrypt0Encrypt and wcCoseEncrypt0Decrypt for direct encryption, wcCoseEncryptEncrypt and wcCoseEncryptDecrypt for recipient-based encryption, and wcCoseMac0Create and wcCoseMac0Verify plus wcCoseMacCreate and wcCoseMacVerify for MACed messages.
That breadth matters because embedded firmware does not always want the same COSE shape. Some devices only need a signed blob. Others need encrypted payloads, message authentication, or both. The no-heap approach keeps those choices within the same memory model instead of changing the allocator story every time a different message type is used.
Keep the buffer contract tight from encode through verify
The safe version of this pattern is boring: size the output buffer for the largest expected message, pass it in, and treat a short write as a failure, not a warning. Verification paths need the same discipline. A parser that accepts input but cannot keep the downstream state bounded is not helping much.
The trouble usually shows up at the edges. Multi-recipient and multi-signer messages add structure, and structure adds bytes. Key serialisation can also expand in ways that are easy to forget if the test case only uses one neat example message.
Test the failure paths, not just the happy round trip
Round-trip tests are useful, but they are not enough on their own. A zero-allocation implementation can look fine when the message fits, then fail in less tidy ways once a buffer is too small or a crypto path is disabled in the build.
The useful tests are the awkward ones: oversized inputs, unsupported algorithms, truncated output buffers, and forced crypto failures. wolfCOSE’s test tooling includes explicit coverage for failure paths, including a coverage-force-failure target. That is the sort of thing that catches the broken edge case before it turns into a firmware bug that only appears after deployment.
What to check before shipping on a constrained target
Start with the build profile. If the target only needs a narrow COSE set, do not carry algorithms it will never use. If the device needs broader support, accept the size cost and make the buffer limits honest. Silent optimism tends to get expensive on small RAM budgets.
Then check the lifecycle, not just one function call. Signing, verification, encryption, decryption, MAC creation, MAC verification, key encode, and key decode all need to behave inside the same memory envelope. The repository’s test coverage includes unit tests, CLI round-trip tests, static analysis, MISRA checks, and crypto failure-path testing, which is the right shape for this sort of code.
The last check is the one people skip because it looks dull: confirm the exact wolfSSL version and algorithm support before the firmware build is frozen. A constrained COSE path is only useful if it still exists in the binary you ship.


