<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://aidangarske.github.io/wolfCOSE/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aidangarske.github.io/wolfCOSE/" rel="alternate" type="text/html" /><updated>2026-05-01T17:56:05+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/feed.xml</id><title type="html">wolfCOSE</title><subtitle>Zero-allocation C COSE/CBOR for embedded, FIPS, and PQC</subtitle><entry><title type="html">wolfCOSE vs The Field: The Smallest, Most Complete COSE Implementation</title><link href="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-vs-the-field/" rel="alternate" type="text/html" title="wolfCOSE vs The Field: The Smallest, Most Complete COSE Implementation" /><published>2026-05-01T10:00:00+00:00</published><updated>2026-05-01T10:00:00+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/blog/wolfcose-vs-the-field</id><content type="html" xml:base="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-vs-the-field/"><![CDATA[<p><em>The Smallest, Most Complete COSE Implementation</em></p>

<p>wolfCOSE delivers 40 algorithms including post-quantum ML-DSA, all 6 COSE message types (<code class="language-plaintext highlighter-rouge">Sign1</code>, <code class="language-plaintext highlighter-rouge">Encrypt0</code>, <code class="language-plaintext highlighter-rouge">Mac0</code>, <code class="language-plaintext highlighter-rouge">Sign</code>, <code class="language-plaintext highlighter-rouge">Encrypt</code>, <code class="language-plaintext highlighter-rouge">Mac</code>), a built-in CBOR engine, zero heap allocation, and a path to FIPS 140-3 compliance via wolfCrypt. All in under 5,500 lines of C99.</p>

<h2 id="measurement-methodology">Measurement Methodology</h2>

<p>All measurements were taken in March 2026 on identical hardware and toolchain.</p>

<ul>
  <li>Platform: Raspberry Pi 5 (aarch64), Debian, 8 GB RAM</li>
  <li>Compiler: GCC 14.2.0</li>
  <li>Optimization: <code class="language-plaintext highlighter-rouge">-Os</code> (optimize for size)</li>
  <li>NCSL tool: <code class="language-plaintext highlighter-rouge">cloc</code> v2.04</li>
  <li>Binary tool: <code class="language-plaintext highlighter-rouge">size</code> (Berkeley format)</li>
  <li>Every library was built from source <code class="language-plaintext highlighter-rouge">master</code></li>
</ul>

<h2 id="compiled-binary-size">Compiled Binary Size</h2>

<h3 id="cose--cbor-combined-text--os-aarch64-gcc-1420">COSE + CBOR Combined (.text, -Os, aarch64, GCC 14.2.0)</h3>

<p>Every COSE library needs a CBOR engine. Most depend on an external one. wolfCOSE includes its own. This allows for an insanely lightweight CBOR engine out of the box, with just 2.7 KB used for CBOR. A fair comparison should count both.</p>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>COSE .text</th>
      <th>CBOR .text</th>
      <th>Total .text</th>
      <th>.data</th>
      <th>.bss</th>
      <th>Algos</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE (min)</strong></td>
      <td>4.8 KB</td>
      <td>2.7 KB</td>
      <td><strong>7.5 KB</strong></td>
      <td>0</td>
      <td>0</td>
      <td>ES256</td>
    </tr>
    <tr>
      <td><strong>wolfCOSE (full)</strong></td>
      <td>22.9 KB</td>
      <td>2.7 KB</td>
      <td><strong>25.6 KB</strong></td>
      <td>0</td>
      <td>0</td>
      <td>40</td>
    </tr>
    <tr>
      <td>libcose+NanoCBOR</td>
      <td>11.9 KB</td>
      <td>6.9 KB</td>
      <td>18.8 KB</td>
      <td>0</td>
      <td>16</td>
      <td>~2</td>
    </tr>
    <tr>
      <td>t_cose+QCBOR</td>
      <td>5.1 KB</td>
      <td>25.5 KB</td>
      <td>30.6 KB</td>
      <td>0</td>
      <td>16</td>
      <td>7</td>
    </tr>
    <tr>
      <td>COSE-C+cn-cbor</td>
      <td>68.5 KB</td>
      <td>8.7 KB</td>
      <td>77.3 KB</td>
      <td>96</td>
      <td>88</td>
      <td>~30</td>
    </tr>
  </tbody>
</table>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wolfCOSE (min)       ███                              7.5 KB (ES256 Sign1)
libcose+NanoCBOR     ███████                         18.8 KB (~2 algos)
wolfCOSE (full)      ██████████                      25.6 KB (40 algos)
t_cose+QCBOR         ████████████                    30.6 KB (7 algos)
COSE-C+cn-cbor       ██████████████████████████████  77.3 KB (~30 algos)
</code></pre></div></div>

<p>Key takeaways:</p>

<ul>
  <li>wolfCOSE minimal (<code class="language-plaintext highlighter-rouge">Sign1</code>-sign-only, ECC) is 7.5 KB. That is smaller than any other implementation.</li>
  <li>wolfCOSE full is smaller than <code class="language-plaintext highlighter-rouge">t_cose+QCBOR</code> while supporting 5.7x more algorithms.</li>
  <li>wolfCOSE is 3x smaller than <code class="language-plaintext highlighter-rouge">COSE-C</code> while being pure C99 (<code class="language-plaintext highlighter-rouge">COSE-C</code> is C++).</li>
  <li><code class="language-plaintext highlighter-rouge">t_cose</code>’s README claims 3.5 to 4.8 KB of <code class="language-plaintext highlighter-rouge">.text</code>. That is <code class="language-plaintext highlighter-rouge">t_cose</code> itself. QCBOR adds approximately 25.5 KB of <code class="language-plaintext highlighter-rouge">.text</code> (<code class="language-plaintext highlighter-rouge">qcbor_decode.o</code> alone is 21.7 KB). The real footprint is around 30.6 KB.</li>
  <li>wolfCOSE’s built-in CBOR engine is 2.7 KB. QCBOR is 25.5 KB (9.4x larger). NanoCBOR is 6.9 KB (2.6x larger).</li>
  <li><code class="language-plaintext highlighter-rouge">libcose</code> is small but only implements approximately 2 algorithms with its libsodium backend (EdDSA + ChaCha20-Poly1305).</li>
</ul>

<h2 id="why-wolfcose-is-the-smallest">Why wolfCOSE Is the Smallest</h2>

<p>The size advantage is the result of four deliberate design choices, not a single trick.</p>

<ol>
  <li><strong>A CBOR engine purpose-built for COSE.</strong> General-purpose CBOR libraries like QCBOR handle every CBOR type, indefinite-length encoding, tagged values, floating point, and deeply nested structures. wolfCOSE’s CBOR engine handles exactly what COSE needs and nothing more. The result is 2.7 KB of <code class="language-plaintext highlighter-rouge">.text</code> versus 25.5 KB for QCBOR.</li>
  <li><strong>238 compile-time guards.</strong> Every message type and algorithm family can be conditionally compiled out via <code class="language-plaintext highlighter-rouge">WOLFCOSE_NO_*</code> macros. The minimum build strips down to 7.5 KB. You only pay flash for what you use.</li>
  <li><strong>Zero dynamic allocation.</strong> No <code class="language-plaintext highlighter-rouge">malloc</code>, no <code class="language-plaintext highlighter-rouge">calloc</code>, no <code class="language-plaintext highlighter-rouge">free</code>, and no heap bookkeeping pulled in by the standard library. Every API takes caller-provided buffers and returns a length.</li>
  <li><strong>Single dependency.</strong> wolfCOSE depends on wolfCrypt and nothing else. There is no second CBOR library, no separate crypto backend, and no glue layer linking them together.</li>
</ol>

<h2 id="compile-time-stripping-pay-only-for-what-you-use">Compile-Time Stripping: Pay Only for What You Use</h2>

<table>
  <thead>
    <tr>
      <th>Config</th>
      <th>COSE .text</th>
      <th>CBOR .text</th>
      <th>Total</th>
      <th>Included</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Full (all 40 algos, all 6 types)</td>
      <td>22.9 KB</td>
      <td>2.7 KB</td>
      <td>25.6 KB</td>
      <td>Everything</td>
    </tr>
    <tr>
      <td>Minimal (<code class="language-plaintext highlighter-rouge">Sign1</code>-sign, ECC-only)</td>
      <td>4.8 KB</td>
      <td>2.7 KB</td>
      <td>7.5 KB</td>
      <td>ES256 Sign1 + CBOR + Key</td>
    </tr>
  </tbody>
</table>

<h2 id="per-algorithm-efficiency">Per-Algorithm Efficiency</h2>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Total .text</th>
      <th>Algorithms</th>
      <th>KB per Algorithm</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE (full)</strong></td>
      <td>25.6 KB</td>
      <td>40</td>
      <td><strong>0.64 KB</strong></td>
    </tr>
    <tr>
      <td>libcose+NanoCBOR</td>
      <td>18.8 KB</td>
      <td>~2</td>
      <td>9.40 KB</td>
    </tr>
    <tr>
      <td>COSE-C+cn-cbor</td>
      <td>77.3 KB</td>
      <td>~30</td>
      <td>2.58 KB</td>
    </tr>
    <tr>
      <td>t_cose+QCBOR</td>
      <td>30.6 KB</td>
      <td>7</td>
      <td>4.37 KB</td>
    </tr>
  </tbody>
</table>

<p>wolfCOSE delivers 0.64 KB per algorithm. That is 6.8x more efficient than <code class="language-plaintext highlighter-rouge">t_cose+QCBOR</code>.</p>

<h2 id="source-code-size-ncsl-via-cloc">Source Code Size (NCSL via cloc)</h2>

<h3 id="cose--cbor-combined">COSE + CBOR Combined</h3>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Lang</th>
      <th>COSE</th>
      <th>CBOR</th>
      <th>Total NCSL</th>
      <th>Ratio</th>
      <th>Algos</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td>C99</td>
      <td>4,959</td>
      <td>502</td>
      <td><strong>5,461</strong></td>
      <td>1.0x</td>
      <td>40</td>
    </tr>
    <tr>
      <td>t_cose+QCBOR</td>
      <td>C99</td>
      <td>1,617</td>
      <td>4,908</td>
      <td>6,525</td>
      <td>1.2x</td>
      <td>7</td>
    </tr>
    <tr>
      <td>libcose+NanoCBOR</td>
      <td>C99</td>
      <td>1,678</td>
      <td>889</td>
      <td>2,567</td>
      <td>0.5x</td>
      <td>~2</td>
    </tr>
    <tr>
      <td>COSE-C+cn-cbor</td>
      <td>C++</td>
      <td>10,579</td>
      <td>1,288</td>
      <td>11,867</td>
      <td>2.2x</td>
      <td>~30</td>
    </tr>
    <tr>
      <td>go-cose+fxcbor</td>
      <td>Go</td>
      <td>2,637</td>
      <td>5,973</td>
      <td>8,610</td>
      <td>1.6x</td>
      <td>7</td>
    </tr>
    <tr>
      <td>pycose</td>
      <td>Py</td>
      <td>3,495</td>
      <td>(ext)</td>
      <td>3,495+</td>
      <td>0.6x+</td>
      <td>~12</td>
    </tr>
    <tr>
      <td>COSE-JAVA</td>
      <td>Java</td>
      <td>3,478</td>
      <td>(ext)</td>
      <td>3,478+</td>
      <td>0.6x+</td>
      <td>~8</td>
    </tr>
  </tbody>
</table>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>libcose+NanoCBOR     ██████                          2,567 (~2 algos)
wolfCOSE             ██████████████                  5,461 (40 algos)
t_cose+QCBOR         ████████████████                6,525 (7 algos)
go-cose+fxcbor       ██████████████████████          8,610 (7 algos)
COSE-C+cn-cbor       ██████████████████████████████ 11,867 (~30 algos)
</code></pre></div></div>

<h3 id="algorithm-density-algos-per-1000-ncsl">Algorithm Density (Algos per 1,000 NCSL)</h3>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Total NCSL</th>
      <th>Algorithms</th>
      <th>Algos / 1K Lines</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td>5,461</td>
      <td>40</td>
      <td><strong>7.3</strong></td>
    </tr>
    <tr>
      <td>pycose</td>
      <td>3,495+</td>
      <td>~12</td>
      <td>~3.4</td>
    </tr>
    <tr>
      <td>COSE-C+cn-cbor</td>
      <td>11,867</td>
      <td>~30</td>
      <td>2.5</td>
    </tr>
    <tr>
      <td>COSE-JAVA</td>
      <td>3,478+</td>
      <td>~8</td>
      <td>~2.3</td>
    </tr>
    <tr>
      <td>t_cose+QCBOR</td>
      <td>6,525</td>
      <td>7</td>
      <td>1.1</td>
    </tr>
    <tr>
      <td>go-cose+fxcbor</td>
      <td>8,610</td>
      <td>7</td>
      <td>0.8</td>
    </tr>
  </tbody>
</table>

<p>wolfCOSE delivers 7.3 algorithms per 1,000 lines. That is 6.6x denser than <code class="language-plaintext highlighter-rouge">t_cose+QCBOR</code>.</p>

<h3 id="cose-logic-only-no-cbor">COSE Logic Only (No CBOR)</h3>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>COSE-Only</th>
      <th>CBOR Dep</th>
      <th>Algorithms</th>
      <th>COSE Algos/KLOC</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td>4,959</td>
      <td>502 (built-in)</td>
      <td>40</td>
      <td><strong>8.1</strong></td>
    </tr>
    <tr>
      <td>t_cose</td>
      <td>1,617</td>
      <td>4,908 (QCBOR)</td>
      <td>7</td>
      <td>4.3</td>
    </tr>
    <tr>
      <td>libcose</td>
      <td>1,678</td>
      <td>889 (NanoCBOR)</td>
      <td>~2</td>
      <td>1.2</td>
    </tr>
    <tr>
      <td>COSE-C</td>
      <td>10,579</td>
      <td>1,288 (cn-cbor)</td>
      <td>~30</td>
      <td>2.8</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">t_cose</code> is smaller in COSE-only lines but it only supports 7 signing algorithms with no encryption or MAC. wolfCOSE packs 40 algorithms across all 6 COSE message types into 3,342 more lines.</p>

<h2 id="algorithm-support">Algorithm Support</h2>

<h3 id="at-a-glance">At a Glance</h3>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Lang</th>
      <th>Sign</th>
      <th>Enc</th>
      <th>MAC</th>
      <th>Key Mgmt</th>
      <th>Total</th>
      <th>PQ</th>
      <th>FIPS Path</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td>C99</td>
      <td>11</td>
      <td>12</td>
      <td>7</td>
      <td>10</td>
      <td><strong>40</strong></td>
      <td>ML-DSA</td>
      <td>Via wolfCrypt #4718</td>
    </tr>
    <tr>
      <td>COSE-C</td>
      <td>C++</td>
      <td>~4</td>
      <td>~12</td>
      <td>~7</td>
      <td>~7</td>
      <td>~30</td>
      <td>No</td>
      <td>Via OpenSSL FIPS</td>
    </tr>
    <tr>
      <td>pycose</td>
      <td>Py</td>
      <td>~7</td>
      <td>~3</td>
      <td>~2</td>
      <td> </td>
      <td>~12</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>COSE-JAVA</td>
      <td>Java</td>
      <td>~4</td>
      <td>~2</td>
      <td>~2</td>
      <td> </td>
      <td>~8</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>t_cose</td>
      <td>C99</td>
      <td>7</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>7</td>
      <td>No</td>
      <td>Via OpenSSL FIPS</td>
    </tr>
    <tr>
      <td>go-cose</td>
      <td>Go</td>
      <td>7</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>7</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>libcose</td>
      <td>C99</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>~2</td>
      <td>No</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<h3 id="signing-algorithms-cose_sign1--cose_sign">Signing Algorithms (COSE_Sign1 / COSE_Sign)</h3>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>go-cose</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ES256 (P-256)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ES384 (P-384)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ES512 (P-521)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>EdDSA (Ed25519)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>EdDSA (Ed448)</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>PS256 (RSA-PSS)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>PS384 (RSA-PSS)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>PS512 (RSA-PSS)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ML-DSA-44</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ML-DSA-65</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ML-DSA-87</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>11</strong></td>
      <td>7</td>
      <td>~4</td>
      <td>7</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

<p>wolfCOSE is the only COSE implementation with ML-DSA (FIPS 204) post-quantum signatures and the only one supporting Ed448.</p>

<h3 id="encryption-algorithms-cose_encrypt0--cose_encrypt">Encryption Algorithms (COSE_Encrypt0 / COSE_Encrypt)</h3>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>wolfCOSE</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A128GCM</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>A192GCM</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>A256GCM</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>ChaCha20-Poly1305</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>AES-CCM (8 variants)</td>
      <td>Yes (all 8)</td>
      <td>Yes (all 8)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>12</strong></td>
      <td>~12</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">t_cose</code> and <code class="language-plaintext highlighter-rouge">go-cose</code> have zero encryption support.</p>

<h3 id="mac-algorithms-cose_mac0--cose_mac">MAC Algorithms (COSE_Mac0 / COSE_Mac)</h3>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>wolfCOSE</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>HMAC-256/256</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>HMAC-384/384</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>HMAC-512/512</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>AES-MAC-128-64</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>AES-MAC-256-64</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>AES-MAC-128-128</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>AES-MAC-256-128</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>7</strong></td>
      <td>~7</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">t_cose</code>, <code class="language-plaintext highlighter-rouge">go-cose</code>, and <code class="language-plaintext highlighter-rouge">libcose</code> have zero MAC support.</p>

<h3 id="key-management-algorithms">Key Management Algorithms</h3>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>wolfCOSE</th>
      <th>COSE-C</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Direct</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>A128KW / A192KW / A256KW</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>ECDH-ES + HKDF-256/512</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>ECDH-SS + HKDF-256/512</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>ECDH-ES + A128KW / A192KW / A256KW</td>
      <td>Yes</td>
      <td>Partial</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>10</strong></td>
      <td>~7</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">t_cose</code>, <code class="language-plaintext highlighter-rouge">go-cose</code>, and <code class="language-plaintext highlighter-rouge">libcose</code> have zero key management support.</p>

<h3 id="cose-message-types">COSE Message Types</h3>

<table>
  <thead>
    <tr>
      <th>Type</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
      <th>go-cose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sign1</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Encrypt0</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Mac0</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Sign (multi-signer)</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Encrypt (multi-recipient)</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Mac (multi-recipient)</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>6/6</strong></td>
      <td>1/6</td>
      <td>6/6</td>
      <td>4/6</td>
      <td>1/6</td>
    </tr>
  </tbody>
</table>

<h2 id="what-only-wolfcose-has">What Only wolfCOSE Has</h2>

<table>
  <thead>
    <tr>
      <th>Capability</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
      <th>go-cose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Post-Quantum (ML-DSA)</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Path to FIPS 140-3</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>DO-178C path with wolfTPM</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Secure boot path with wolfBoot</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Zero heap allocation</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Built-in CBOR</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>MISRA-C compliance (striving)</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>N/A</td>
    </tr>
    <tr>
      <td>Ed448 support</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>All 6 COSE types</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>CLI tool</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>15 CI workflows</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Coverity + ASan CI</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>COSE_Key all types</td>
      <td>Yes</td>
      <td>No</td>
      <td>Partial</td>
      <td>No</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<h2 id="zero-dynamic-allocation">Zero Dynamic Allocation</h2>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Heap Alloc</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td>Zero</td>
      <td>Safety-critical: automotive, aerospace, medical</td>
    </tr>
    <tr>
      <td>t_cose</td>
      <td>Zero</td>
      <td>Similar embedded design</td>
    </tr>
    <tr>
      <td>libcose</td>
      <td>Zero</td>
      <td>Minimal allocation model</td>
    </tr>
    <tr>
      <td>COSE-C</td>
      <td>malloc/calloc</td>
      <td>OpenSSL heap dependency, cn-cbor uses calloc</td>
    </tr>
    <tr>
      <td>go-cose</td>
      <td>GC-managed</td>
      <td>Go runtime + garbage collector</td>
    </tr>
    <tr>
      <td>pycose</td>
      <td>GC-managed</td>
      <td>Python interpreter + GC</td>
    </tr>
    <tr>
      <td>COSE-JAVA</td>
      <td>GC-managed</td>
      <td>JVM + garbage collector</td>
    </tr>
  </tbody>
</table>

<p>Zero allocation means: no heap fragmentation, no use-after-free, no double-free, no allocation latency, no OOM crashes. This is a hard requirement for safety-critical embedded systems where <code class="language-plaintext highlighter-rouge">malloc</code> is prohibited.</p>

<h2 id="fips-140-3-and-certifications">FIPS 140-3 and Certifications</h2>

<p>wolfCOSE itself is not FIPS validated. wolfCOSE’s sole cryptographic dependency is wolfCrypt, which holds <a href="https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718">FIPS 140-3 Certificate #4718</a>. Since wolfCrypt is the only dependency, there is a direct, clean path to FIPS compliance when required.</p>

<table>
  <thead>
    <tr>
      <th>Certification</th>
      <th>wolfCOSE (via wolfCrypt)</th>
      <th>All Others</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>FIPS 140-3</td>
      <td>Path via wolfCrypt #4718</td>
      <td>Via OpenSSL FIPS</td>
    </tr>
    <tr>
      <td>DO-178C</td>
      <td>Path via wolfCrypt and wolfTPM</td>
      <td>None</td>
    </tr>
    <tr>
      <td>Common Criteria</td>
      <td>Path via wolfCrypt EAL4+</td>
      <td>None</td>
    </tr>
  </tbody>
</table>

<p>Other libraries that use OpenSSL as a backend (<code class="language-plaintext highlighter-rouge">COSE-C</code>, <code class="language-plaintext highlighter-rouge">t_cose</code>) can also achieve FIPS compliance through OpenSSL’s FIPS module, but this requires a separate FIPS-specific OpenSSL build and adds significant binary size and complexity. wolfCrypt is purpose-built for embedded and constrained environments.</p>

<h2 id="misra-c-compliance">MISRA C Compliance</h2>

<p>wolfCOSE strives for MISRA C compliance and is checked in CI on every pull request via three complementary checkers (PR #16). Although wolfCOSE is not fully MISRA C compliant, it adheres to as many rules as it can while documenting deviations from the 2023 standard.</p>

<h3 id="coverage-summary">Coverage Summary</h3>

<table>
  <thead>
    <tr>
      <th>Area</th>
      <th>cppcheck (2012)</th>
      <th>Compiler + clang-tidy (2023)</th>
      <th>Commercial</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Syntax Rules</td>
      <td>High (~90%)</td>
      <td>High (~95%)</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Essential Types</td>
      <td>Medium (~50%)</td>
      <td>High (~80%)</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Data Flow</td>
      <td>Low (~30%)</td>
      <td>Medium (~50%)</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Std Lib Safety</td>
      <td>Low (~20%)</td>
      <td>Medium (~60%)</td>
      <td>100%</td>
    </tr>
  </tbody>
</table>

<p>MISRA C:2012 is fully tested via cppcheck’s MISRA addon. MISRA C:2023 achieves approximately 80% coverage via compiler warnings and clang-tidy checks. Full 2023 verification requires commercial tooling (LDRA, Polyspace).</p>

<h2 id="post-quantum-cryptography">Post-Quantum Cryptography</h2>

<p>wolfCOSE is the only COSE implementation, in any language, with native post-quantum digital signatures.</p>

<table>
  <thead>
    <tr>
      <th>PQ Algorithm</th>
      <th>wolfCOSE</th>
      <th>All Others</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ML-DSA-44 (FIPS 204, Level 2)</td>
      <td>Native</td>
      <td>None</td>
    </tr>
    <tr>
      <td>ML-DSA-65 (FIPS 204, Level 3)</td>
      <td>Native</td>
      <td>None</td>
    </tr>
    <tr>
      <td>ML-DSA-87 (FIPS 204, Level 5)</td>
      <td>Native</td>
      <td>None</td>
    </tr>
  </tbody>
</table>

<p>Roadmap: XMSS, LMS/HSS (FIPS 205), ML-KEM (FIPS 203), SLH-DSA (SPHINCS+).</p>

<h2 id="testing-and-ci">Testing and CI</h2>

<h3 id="test-suite">Test Suite</h3>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Test functions</td>
      <td>176</td>
      <td>~15</td>
      <td>~20</td>
      <td>~10</td>
    </tr>
    <tr>
      <td>Test NCSL</td>
      <td>15,173</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Test tiers</td>
      <td>3 (unit, comp, scenario)</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <td>Round-trip all algos</td>
      <td><code class="language-plaintext highlighter-rouge">wolfcose_tool test --all</code></td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Interop tests</td>
      <td>Yes (RFC vectors)</td>
      <td>Partial</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Error path injection</td>
      <td>Yes (<code class="language-plaintext highlighter-rouge">force_failure.c</code>)</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Real-world scenarios</td>
      <td>5</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<h3 id="code-coverage">Code Coverage</h3>

<table>
  <thead>
    <tr>
      <th>File</th>
      <th>Line Coverage</th>
      <th>Branch Coverage</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">wolfcose.c</code></td>
      <td>99.3%</td>
      <td>High</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">wolfcose_cbor.c</code></td>
      <td>100%</td>
      <td>100%</td>
    </tr>
  </tbody>
</table>

<h3 id="ci-pipeline-15-workflows">CI Pipeline (15 Workflows)</h3>

<p>wolfCOSE has 15 GitHub Actions workflows covering the full development lifecycle. 13 trigger on every pull request, plus a nightly orchestrator and a wolfSSL-versions matrix that run on a schedule overnight.</p>

<table>
  <thead>
    <tr>
      <th>CI Workflow</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">build-test.yml</code> (Ubuntu latest + 22.04, macOS)</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">multi-compiler.yml</code> (GCC 10 to 14, Clang 14 to 18)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">static-analysis.yml</code> (cppcheck, scan-build, <code class="language-plaintext highlighter-rouge">-fanalyzer</code>)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">coverity.yml</code> (nightly Coverity defect analysis)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sanitizer.yml</code> (ASan + UBSan)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">coverage.yml</code> (gcov + lcov, threshold enforced)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">minimal-build.yml</code> (minimal wolfSSL configuration)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">misra-2012.yml</code> (cppcheck addon)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">misra-2023.yml</code> (compiler strict + clang-tidy)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">comprehensive-tests.yml</code> (~240 algorithm combinations)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">examples.yml</code> (lifecycle demo + tool round-trip)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scenarios.yml</code> (real-world scenario examples)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">codespell.yml</code> (spell checking)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">nightly.yml</code> (nightly orchestrator on master)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">wolfssl-versions.yml</code> (every wolfSSL 5.x release, nightly)</td>
      <td>Yes</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<p>The <code class="language-plaintext highlighter-rouge">nightly.yml</code> orchestrator re-runs the full CI suite on <code class="language-plaintext highlighter-rouge">master</code> each night. This catches breakage from upstream changes between PRs. The <code class="language-plaintext highlighter-rouge">wolfssl-versions.yml</code> matrix runs nightly against every wolfSSL 5.x release to verify ongoing compatibility with the crypto backend.</p>

<h2 id="embedded-suitability">Embedded Suitability</h2>

<table>
  <thead>
    <tr>
      <th>Requirement</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>No malloc</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>No floating point</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>No external CBOR</td>
      <td>Yes</td>
      <td>No (QCBOR)</td>
      <td>No (cn-cbor)</td>
      <td>No (NanoCBOR)</td>
    </tr>
    <tr>
      <td>Fixed-size buffers</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>C99, no C++</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No (C++)</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Bare-metal compatible</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>RTOS compatible</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Partial</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">#ifdef</code> configurability</td>
      <td>238 guards</td>
      <td>Minimal</td>
      <td>Minimal</td>
      <td>Minimal</td>
    </tr>
    <tr>
      <td>Stack usage (<code class="language-plaintext highlighter-rouge">.su</code>)</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<h2 id="architecture">Architecture</h2>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>wolfCOSE</th>
      <th>t_cose</th>
      <th>COSE-C</th>
      <th>libcose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Language</td>
      <td>C99</td>
      <td>C99</td>
      <td>C++ (C API)</td>
      <td>C99</td>
    </tr>
    <tr>
      <td>Source files</td>
      <td>2 (.c)</td>
      <td>5 (.c)+adapter</td>
      <td>14 (.cpp)</td>
      <td>11 (.c)</td>
    </tr>
    <tr>
      <td>MISRA-C</td>
      <td>Striving (2012+2023)</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Dependencies</td>
      <td>wolfCrypt only</td>
      <td>QCBOR+OpenSSL</td>
      <td>cn-cbor+OpenSSL</td>
      <td>NanoCBOR+libsodium</td>
    </tr>
    <tr>
      <td>Crypto backend</td>
      <td>wolfCrypt (FIPS path)</td>
      <td>OpenSSL/PSA</td>
      <td>OpenSSL</td>
      <td>libsodium</td>
    </tr>
    <tr>
      <td>COSE_Key support</td>
      <td>All types</td>
      <td>No</td>
      <td>Partial</td>
      <td>Minimal</td>
    </tr>
    <tr>
      <td>All 6 COSE types</td>
      <td>Yes</td>
      <td>No (Sign1 only)</td>
      <td>Yes</td>
      <td>Partial</td>
    </tr>
  </tbody>
</table>

<h2 id="tooling-wolfcose_tool">Tooling: wolfcose_tool</h2>

<p>wolfCOSE ships with <code class="language-plaintext highlighter-rouge">wolfcose_tool</code>, a full CLI for key generation, signing, verification, encryption, decryption, and automated round-trip testing. No other C COSE library ships a CLI tool.</p>

<table>
  <thead>
    <tr>
      <th>Capability</th>
      <th>wolfCOSE</th>
      <th>Others</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Keygen (ECC, EdDSA, RSA, ML-DSA)</td>
      <td>Yes</td>
      <td>None</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">COSE_Sign1</code> sign/verify</td>
      <td>Yes</td>
      <td>None</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">COSE_Encrypt0</code> encrypt/decrypt</td>
      <td>Yes</td>
      <td>None</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">COSE_Mac0</code> create/verify</td>
      <td>Yes</td>
      <td>None</td>
    </tr>
    <tr>
      <td>Round-trip test all algorithms</td>
      <td><code class="language-plaintext highlighter-rouge">test --all</code></td>
      <td>None</td>
    </tr>
    <tr>
      <td>Single algorithm test</td>
      <td><code class="language-plaintext highlighter-rouge">test -a ES256</code></td>
      <td>None</td>
    </tr>
  </tbody>
</table>

<h2 id="a-fair-comparison">A Fair Comparison</h2>

<p>wolfCOSE implements all 6 COSE message types (<code class="language-plaintext highlighter-rouge">Sign1</code>, <code class="language-plaintext highlighter-rouge">Encrypt0</code>, <code class="language-plaintext highlighter-rouge">Mac0</code>, <code class="language-plaintext highlighter-rouge">Sign</code>, <code class="language-plaintext highlighter-rouge">Encrypt</code>, <code class="language-plaintext highlighter-rouge">Mac</code>) with 40 algorithms. This comparison measures every library the same way: COSE source plus required CBOR dependency, NCSL via <code class="language-plaintext highlighter-rouge">cloc</code>, <code class="language-plaintext highlighter-rouge">.text</code> via <code class="language-plaintext highlighter-rouge">size</code>, excluding tests and examples.</p>

<p>Libraries like <code class="language-plaintext highlighter-rouge">COSE-C</code> also support all 6 COSE types plus CounterSign. <code class="language-plaintext highlighter-rouge">t_cose</code>, <code class="language-plaintext highlighter-rouge">go-cose</code>, and <code class="language-plaintext highlighter-rouge">libcose</code> are more limited in scope. The comparison reports exactly what each library does and does not support, and all numbers are from actual builds on the same system.</p>

<h2 id="summary">Summary</h2>

<table>
  <thead>
    <tr>
      <th>Claim</th>
      <th>Evidence</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Smallest minimal <code class="language-plaintext highlighter-rouge">.text</code></td>
      <td>7.5 KB (Sign1-sign-only, ECC) vs 18.8 KB (libcose), 30.6 KB (t_cose+QCBOR)</td>
    </tr>
    <tr>
      <td>Smallest full <code class="language-plaintext highlighter-rouge">.text</code></td>
      <td>25.6 KB / 40 algos vs 30.6 KB / 7 (t_cose+QCBOR), 77.3 KB / ~30 (COSE-C)</td>
    </tr>
    <tr>
      <td>Most algorithms</td>
      <td>40 vs next-best ~30 (COSE-C)</td>
    </tr>
    <tr>
      <td>Best per-algo efficiency</td>
      <td>0.64 KB/algo vs 4.37 (t_cose+QCBOR), 2.58 (COSE-C)</td>
    </tr>
    <tr>
      <td>Only PQ-native COSE</td>
      <td>ML-DSA-44/65/87 built in. No other has any.</td>
    </tr>
    <tr>
      <td>Path to FIPS 140-3</td>
      <td>Via wolfCrypt Certificate #4718 (sole dependency)</td>
    </tr>
    <tr>
      <td>Zero heap allocation</td>
      <td>No <code class="language-plaintext highlighter-rouge">malloc</code> in any code path</td>
    </tr>
    <tr>
      <td>Built-in CBOR</td>
      <td>502-line / 2.7 KB engine vs 4,908 / 25.5 KB (QCBOR)</td>
    </tr>
    <tr>
      <td>All 6 COSE types</td>
      <td>Sign1, Encrypt0, Mac0, Sign, Encrypt, Mac</td>
    </tr>
    <tr>
      <td>Most comprehensive CI</td>
      <td>15 workflows: Coverity, ASan, UBSan, multi-compiler, MISRA, nightly orchestrator, wolfSSL-versions matrix</td>
    </tr>
    <tr>
      <td>Highest code coverage</td>
      <td><code class="language-plaintext highlighter-rouge">wolfcose.c</code> &gt;= 99.3%, <code class="language-plaintext highlighter-rouge">wolfcose_cbor.c</code> = 100%</td>
    </tr>
    <tr>
      <td>Only full CLI tool</td>
      <td>keygen, sign, verify, encrypt, decrypt, round-trip all algos</td>
    </tr>
    <tr>
      <td>Highest algo density</td>
      <td>7.3 algos/KLOC vs next-best ~3.4 (pycose)</td>
    </tr>
    <tr>
      <td>MISRA-C striving</td>
      <td>2012 fully checked, 2023 ~80% via 3 free checkers</td>
    </tr>
  </tbody>
</table>

<h2 id="learn-more">Learn More</h2>

<ul>
  <li>All wolfCOSE blog posts: <a href="https://aidangarske.github.io/wolfCOSE/">https://aidangarske.github.io/wolfCOSE/</a></li>
  <li>wolfCOSE wiki and documentation: <a href="https://github.com/aidangarske/wolfCOSE/wiki">https://github.com/aidangarske/wolfCOSE/wiki</a></li>
  <li>Contact wolfSSL for commercial licensing or production support: <a href="https://www.wolfssl.com/contact/">https://www.wolfssl.com/contact/</a></li>
</ul>

<p>Measurements taken March 2026. NCSL via <code class="language-plaintext highlighter-rouge">cloc</code> v2.04, excluding tests. Binary sizes on ARM aarch64 (RPi 5), GCC 14.2.0, <code class="language-plaintext highlighter-rouge">-Os</code>. Verified by building each project from source.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The Smallest, Most Complete COSE Implementation]]></summary></entry><entry><title type="html">wolfCOSE: zero alloc C COSE for embedded</title><link href="https://aidangarske.github.io/wolfCOSE/blog/introducing-wolfcose/" rel="alternate" type="text/html" title="wolfCOSE: zero alloc C COSE for embedded" /><published>2026-04-30T12:00:00+00:00</published><updated>2026-04-30T12:00:00+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/blog/introducing-wolfcose</id><content type="html" xml:base="https://aidangarske.github.io/wolfCOSE/blog/introducing-wolfcose/"><![CDATA[<p><em>An experimental project from a wolfSSL developer</em></p>

<p>Most C COSE libraries make you choose. You either get a small footprint with one message type (<code class="language-plaintext highlighter-rouge">t_cose</code> does <code class="language-plaintext highlighter-rouge">Sign1</code> only, and depends on QCBOR plus OpenSSL or mbedTLS), or every message type with <code class="language-plaintext highlighter-rouge">malloc</code> and OpenSSL (<code class="language-plaintext highlighter-rouge">COSE-C</code> does all six but ships at ~77 KB). For embedded teams already in the wolfSSL ecosystem, neither fits without doubling the crypto footprint.</p>

<p>wolfCOSE is the missing third option. It implements the full RFC 9052 message set (<code class="language-plaintext highlighter-rouge">Sign1</code>, <code class="language-plaintext highlighter-rouge">Sign</code>, <code class="language-plaintext highlighter-rouge">Encrypt0</code>, <code class="language-plaintext highlighter-rouge">Encrypt</code>, <code class="language-plaintext highlighter-rouge">Mac0</code>, <code class="language-plaintext highlighter-rouge">Mac</code>) in <strong>7.5 KB minimum <code class="language-plaintext highlighter-rouge">.text</code></strong> (25.6 KB full build), with <strong>zero dynamic allocation</strong> and <strong>40 algorithms</strong> including native ML-DSA-44/65/87. As far as we can tell, this is also the first COSE implementation in any language with production-tested post-quantum signatures.</p>

<h2 id="a-note-on-the-project">A Note on the Project</h2>

<p>wolfCOSE was developed by a wolfSSL developer, with support from wolfSSL engineering. It is currently an <strong>experimental project</strong> built on wolfCrypt, not an officially adopted wolfSSL product. If you are interested in production use or would like wolfSSL to formally support wolfCOSE, contact <a href="mailto:facts@wolfssl.com">facts@wolfssl.com</a>.</p>

<p>What that means concretely:</p>

<ul>
  <li>The API surface is not frozen. Function signatures may change before 1.0, and the multi-signer / multi-recipient APIs are particularly likely to evolve based on early adopter feedback.</li>
  <li>Test coverage is high (99.3% measured on <code class="language-plaintext highlighter-rouge">wolfcose.c</code>, 100% on <code class="language-plaintext highlighter-rouge">wolfcose_cbor.c</code>, with a CI-enforced minimum of 97%) but we are still expanding edge-case coverage.</li>
  <li>COSE algorithm IDs for ML-DSA (<code class="language-plaintext highlighter-rouge">-48</code>, <code class="language-plaintext highlighter-rouge">-49</code>, <code class="language-plaintext highlighter-rouge">-50</code>) come from an IETF draft. The cryptographic primitive (FIPS 204 ML-DSA) is final; the integer code points could shift.</li>
  <li>Production deployments should pin a specific commit, review every upgrade, or wait for adoption / 1.0.</li>
</ul>

<p>If any of that is a blocker, get in touch. Formal support and stability commitments are exactly the conversation I want to have.</p>

<h2 id="why-i-built-it">Why I Built It</h2>

<p>I wrote this project mostly as a challenge, to see how well I could build a library from scratch compared to the other COSE libraries out there. I also noticed that of course there was no wolfcrypt use cases and thought that the world needed blessed with a little wolfcrypt magic! A more practical reason is that <strong>wolfBoot</strong>, wolfSSL’s secure bootloader, may eventually need a COSE library for SUIT manifest verification. The existing path for COSE in the embedded boot chain requires <code class="language-plaintext highlighter-rouge">t_cose</code>, which depends on OpenSSL or mbedTLS for its crypto backend. That dependency is a non-starter for a bootloader that already uses wolfCrypt: it doubles the crypto footprint and introduces a second trust boundary.</p>

<p>wolfCOSE eliminates that dependency entirely. wolfBoot can verify <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> firmware manifests using the same wolfCrypt it already links for secure boot, with no additional crypto library on the flash. The same library also covers the multi-signer and encryption use cases that SUIT profiles are evolving toward.</p>

<h2 id="what-is-in-it-today">What Is in It Today</h2>

<ul>
  <li><strong>Full RFC 9052 message set:</strong> <code class="language-plaintext highlighter-rouge">Sign1</code>, <code class="language-plaintext highlighter-rouge">Sign</code>, <code class="language-plaintext highlighter-rouge">Encrypt0</code>, <code class="language-plaintext highlighter-rouge">Encrypt</code>, <code class="language-plaintext highlighter-rouge">Mac0</code>, <code class="language-plaintext highlighter-rouge">Mac</code>. Multi-signer and multi-recipient supported, not stubbed.</li>
  <li><strong>40 algorithms:</strong> ECDSA (P-256/384/521), EdDSA, RSA-PSS, AES-GCM, AES-CCM, ChaCha20-Poly1305, AES Key Wrap, HMAC-SHA-256/384/512, ECDH-ES, and <strong>ML-DSA-44/65/87</strong>. (The CLI tool’s <code class="language-plaintext highlighter-rouge">test --all</code> exercises a 17-algorithm round-trip subset of those for quick smoke testing; the 40 figure counts every distinct COSE algorithm ID across signing, encryption, MAC, and key distribution.)</li>
  <li><strong>Zero dynamic allocation.</strong> Every API takes caller-provided buffers. Stack crypto material zeroized with <code class="language-plaintext highlighter-rouge">wc_ForceZero</code> on every exit.</li>
  <li><strong>Compile-time stripping:</strong> 238 <code class="language-plaintext highlighter-rouge">#ifdef</code> guards. Minimum build is 7.5 KB; full build is 25.6 KB.</li>
  <li><strong>MISRA-C:2023 striving</strong> with three CI checkers.</li>
  <li><strong>Path to FIPS 140-3</strong> via wolfCrypt’s <a href="https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718">Certificate #4718</a>.</li>
  <li><strong>15 GitHub Actions workflows</strong> (13 on every PR, plus a nightly orchestrator and a wolfSSL-versions matrix), <strong>~240 algorithm-combination tests</strong>, AddressSanitizer + UndefinedBehaviorSanitizer in CI, Coverity nightly scan.</li>
</ul>

<h2 id="what-i-would-love-from-early-adopters">What I Would Love from Early Adopters</h2>

<ul>
  <li><strong>Build it on your toolchain.</strong> We test on Linux/macOS with GCC 10–14 and Clang 14–18. If you build on something else (IAR, ARMCC, TI CCS, Renesas, embedded Clang variants) and it fails, file an issue.</li>
  <li><strong>Run the lifecycle demo on your target.</strong> <code class="language-plaintext highlighter-rouge">make demo</code> exercises keygen, sign, verify, and key serialization end to end across ECC, EdDSA, AEAD, HMAC, and ML-DSA-44. For ML-DSA-65 and ML-DSA-87 round-trips, use <code class="language-plaintext highlighter-rouge">./tools/wolfcose_tool test -a ML-DSA-65</code>.</li>
  <li><strong>Tell us if you want production support.</strong> If wolfCOSE is on a critical path for you, that is the signal that turns this from an experimental project into a supported one.</li>
</ul>

<h2 id="read-more">Read More</h2>

<ul>
  <li><a href="/wolfCOSE/blog/what-is-cose/">What is COSE? A short introduction</a>. Background on COSE itself for readers who want context before the technical posts.</li>
  <li><a href="/wolfCOSE/blog/wolfcose-full-rfc9052/">The Smallest Complete COSE Library for Embedded</a>. The size benchmark versus <code class="language-plaintext highlighter-rouge">t_cose</code>, <code class="language-plaintext highlighter-rouge">libcose</code>, and <code class="language-plaintext highlighter-rouge">COSE-C</code>, plus the multi-signer and multi-recipient design and the purpose-built CBOR engine.</li>
  <li><a href="/wolfCOSE/blog/wolfcose-pqc-cose/">The First COSE Implementation with ML-DSA</a>. How FIPS 204 ML-DSA drops into <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> and <code class="language-plaintext highlighter-rouge">COSE_Sign</code>, the hybrid classical-PQC migration story, and the wire-size honesty section.</li>
</ul>

<h2 id="try-it">Try It</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/aidangarske/wolfCOSE
<span class="nb">cd </span>wolfCOSE
make <span class="o">&amp;&amp;</span> make <span class="nb">test
</span>make tool <span class="o">&amp;&amp;</span> ./tools/wolfcose_tool <span class="nb">test</span> <span class="nt">--all</span>
</code></pre></div></div>

<p>Repo: <a href="https://github.com/aidangarske/wolfCOSE">https://github.com/aidangarske/wolfCOSE</a>
Wiki: <a href="https://github.com/aidangarske/wolfCOSE/wiki">https://github.com/aidangarske/wolfCOSE/wiki</a></p>

<p>GPLv3, with commercial licensing available from wolfSSL upon adoption. For interest in production support, contact <a href="mailto:facts@wolfssl.com">facts@wolfssl.com</a>.</p>

<p><code class="language-plaintext highlighter-rouge">github.com/aidangarske/wolfCOSE | facts@wolfssl.com</code></p>]]></content><author><name></name></author><summary type="html"><![CDATA[An experimental project from a wolfSSL developer]]></summary></entry><entry><title type="html">The First COSE Implementation with ML-DSA</title><link href="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-pqc-cose/" rel="alternate" type="text/html" title="The First COSE Implementation with ML-DSA" /><published>2026-04-30T11:00:00+00:00</published><updated>2026-04-30T11:00:00+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/blog/wolfcose-pqc-cose</id><content type="html" xml:base="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-pqc-cose/"><![CDATA[<p><em>Production-Tested Post-Quantum Signatures in wolfCOSE</em></p>

<p>If you are signing CBOR payloads on an embedded device and you have started worrying about “harvest now, decrypt later,” that worry now extends to signatures too. Long-lived firmware artifacts, attestation reports, supply-chain manifests: anything signed today with ECDSA or RSA can be retroactively forged by an adversary with a cryptographically relevant quantum computer.</p>

<p>wolfCOSE now has native ML-DSA-44, ML-DSA-65, and ML-DSA-87 support. As far as we can tell, this is the first COSE implementation, in any language, with production-tested post-quantum digital signatures.</p>

<p>A note on the project: wolfCOSE was developed by Aidan Garske, a wolfSSL developer, with support from wolfSSL engineering. It is not currently an officially adopted wolfSSL product. It is an experimental project built on wolfCrypt and the wolfSSL ecosystem. If you are interested in using wolfCOSE in production or would like wolfSSL to formally support it, reach out to <a href="mailto:facts@wolfssl.com">facts@wolfssl.com</a> and we are happy to discuss adoption and commercial support.</p>

<h2 id="the-cose-pqc-landscape">The COSE PQC Landscape</h2>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>PQC Support</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE</strong></td>
      <td><strong>ML-DSA-44 / 65 / 87</strong></td>
    </tr>
    <tr>
      <td>t_cose</td>
      <td>None</td>
    </tr>
    <tr>
      <td>COSE-C</td>
      <td>None</td>
    </tr>
    <tr>
      <td>pycose</td>
      <td>None</td>
    </tr>
    <tr>
      <td>go-cose</td>
      <td>None</td>
    </tr>
    <tr>
      <td>libcose</td>
      <td>None</td>
    </tr>
    <tr>
      <td>COSE-JAVA</td>
      <td>None</td>
    </tr>
  </tbody>
</table>

<h2 id="what-is-actually-in-the-box">What Is Actually in the Box</h2>

<p>The cleanest way to describe wolfCOSE’s ML-DSA implementation is to be precise about which spec each layer comes from:</p>

<ul>
  <li><strong>The cryptographic primitive</strong> is ML-DSA from <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf">FIPS 204 final</a> (published August 2024). We get it from wolfCrypt, which holds <a href="https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718">FIPS 140-3 Certificate #4718</a>. We use the context-aware API (<code class="language-plaintext highlighter-rouge">wc_dilithium_sign_ctx_msg</code>), which is what FIPS 204 final requires.</li>
  <li><strong>The COSE algorithm registration</strong> comes from <a href="https://datatracker.ietf.org/doc/draft-ietf-cose-dilithium/"><code class="language-plaintext highlighter-rouge">draft-ietf-cose-dilithium</code></a> (consolidating into <code class="language-plaintext highlighter-rouge">draft-ietf-cose-pqc-algs</code>). That draft assigns COSE algorithm IDs <code class="language-plaintext highlighter-rouge">-48</code>, <code class="language-plaintext highlighter-rouge">-49</code>, <code class="language-plaintext highlighter-rouge">-50</code> to ML-DSA-44 / 65 / 87 and defines the COSE_Key encoding (<code class="language-plaintext highlighter-rouge">kty=OKP</code>, <code class="language-plaintext highlighter-rouge">crv=ML-DSA-*</code>).</li>
  <li><strong>The COSE message envelope</strong> is RFC 9052. Once you have a signature primitive and an algorithm ID, ML-DSA drops into <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> and <code class="language-plaintext highlighter-rouge">COSE_Sign</code> exactly the way ES256 does.</li>
</ul>

<p>The honest framing: the cryptography is final and FIPS-validated; the COSE algorithm IDs are still in IETF draft, which means the integer values could shift before the RFC is published. We track the latest draft and will update if IANA assigns different code points. The actual signatures you produce today are FIPS 204 ML-DSA. The integers we wrap them in are the only thing that is draft.</p>

<h2 id="signing-with-ml-dsa-in-cose_sign1">Signing with ML-DSA in COSE_Sign1</h2>

<p>Once your wolfSSL is built with <code class="language-plaintext highlighter-rouge">--enable-dilithium</code>, signing a CBOR payload with a 2,420-byte ML-DSA-44 signature looks identical to signing it with ES256:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;wolfcose/wolfcose.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;wolfssl/wolfcrypt/dilithium.h&gt;</span><span class="cp">
</span>
<span class="n">dilithium_key</span>  <span class="n">dlKey</span><span class="p">;</span>
<span class="n">WOLFCOSE_KEY</span>   <span class="n">coseKey</span><span class="p">;</span>
<span class="n">WC_RNG</span>         <span class="n">rng</span><span class="p">;</span>

<span class="n">wc_InitRng</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rng</span><span class="p">);</span>
<span class="n">wc_dilithium_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dlKey</span><span class="p">);</span>
<span class="n">wc_dilithium_set_level</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dlKey</span><span class="p">,</span> <span class="n">WC_ML_DSA_44</span><span class="p">);</span>
<span class="n">wc_dilithium_make_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dlKey</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rng</span><span class="p">);</span>

<span class="n">wc_CoseKey_Init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">coseKey</span><span class="p">);</span>
<span class="n">wc_CoseKey_SetDilithium</span><span class="p">(</span><span class="o">&amp;</span><span class="n">coseKey</span><span class="p">,</span> <span class="n">WOLFCOSE_ALG_ML_DSA_44</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dlKey</span><span class="p">);</span>

<span class="kt">uint8_t</span> <span class="n">scratch</span><span class="p">[</span><span class="mi">8192</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">wc_CoseSign1_Sign</span><span class="p">(</span><span class="o">&amp;</span><span class="n">coseKey</span><span class="p">,</span> <span class="n">WOLFCOSE_ALG_ML_DSA_44</span><span class="p">,</span>
                            <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>                    <span class="cm">/* kid, kidLen */</span>
                            <span class="n">payload</span><span class="p">,</span> <span class="n">payloadLen</span><span class="p">,</span>
                            <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>           <span class="cm">/* detached payload, ext-AAD */</span>
                            <span class="n">scratch</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">scratch</span><span class="p">),</span>
                            <span class="n">out</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">out</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">outLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rng</span><span class="p">);</span>
</code></pre></div></div>

<p>That is the entire integration surface. The verifier side uses <code class="language-plaintext highlighter-rouge">wc_CoseSign1_Verify</code> with a public-only <code class="language-plaintext highlighter-rouge">dilithium_key</code>, and the COSE_Key serialization works for ML-DSA the same way it works for Ed25519: <code class="language-plaintext highlighter-rouge">kty=OKP</code>, with <code class="language-plaintext highlighter-rouge">crv</code> set to the ML-DSA level.</p>

<h2 id="hybrid-signatures-with-cose_sign">Hybrid Signatures with COSE_Sign</h2>

<p>The reason wolfCOSE has full <code class="language-plaintext highlighter-rouge">COSE_Sign</code> support (not just <code class="language-plaintext highlighter-rouge">Sign1</code>) is that the most likely deployment path for ML-DSA over the next several years is alongside a classical signature, not as a replacement. Standards bodies are explicit that hybrid is the recommended migration approach, and <code class="language-plaintext highlighter-rouge">COSE_Sign</code> is the COSE structure for it.</p>

<p>Here is a firmware manifest signed by both ES256 (today’s verifier) and ML-DSA-65 (tomorrow’s verifier), in one COSE structure:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* eccKey and mlDsaKey are WOLFCOSE_KEY*, set up earlier via
   wc_CoseKey_SetEcc() and wc_CoseKey_SetDilithium() respectively. */</span>
<span class="n">WOLFCOSE_SIGNATURE</span> <span class="n">signers</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">algId</span>  <span class="o">=</span> <span class="n">WOLFCOSE_ALG_ES256</span><span class="p">,</span>
      <span class="p">.</span><span class="n">key</span>    <span class="o">=</span> <span class="o">&amp;</span><span class="n">eccKey</span><span class="p">,</span>
      <span class="p">.</span><span class="n">kid</span>    <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="s">"vendor-classic"</span><span class="p">,</span> <span class="p">.</span><span class="n">kidLen</span> <span class="o">=</span> <span class="mi">14</span> <span class="p">},</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">algId</span>  <span class="o">=</span> <span class="n">WOLFCOSE_ALG_ML_DSA_65</span><span class="p">,</span>
      <span class="p">.</span><span class="n">key</span>    <span class="o">=</span> <span class="o">&amp;</span><span class="n">mlDsaKey</span><span class="p">,</span>
      <span class="p">.</span><span class="n">kid</span>    <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="s">"vendor-pqc"</span><span class="p">,</span>     <span class="p">.</span><span class="n">kidLen</span> <span class="o">=</span> <span class="mi">10</span> <span class="p">},</span>
<span class="p">};</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">wc_CoseSign_Sign</span><span class="p">(</span><span class="n">signers</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span>
                       <span class="n">firmware</span><span class="p">,</span> <span class="n">firmwareLen</span><span class="p">,</span>
                       <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
                       <span class="n">scratch</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">scratch</span><span class="p">),</span>
                       <span class="n">out</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">out</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">outLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rng</span><span class="p">);</span>
</code></pre></div></div>

<p>Per RFC 9052 §4.1, the verifier walks the <code class="language-plaintext highlighter-rouge">COSE_Signature</code> array and selects the signer to validate by matching the <code class="language-plaintext highlighter-rouge">alg</code> and <code class="language-plaintext highlighter-rouge">kid</code> headers it knows about, not by array position. Devices in the field that still only know ES256 select the <code class="language-plaintext highlighter-rouge">vendor-classic</code> signer and skip the ML-DSA one. Newer devices select the <code class="language-plaintext highlighter-rouge">vendor-pqc</code> signer and skip the ECC one. When everyone has migrated, you drop the classical signer and your code path is one line shorter. No re-signing campaigns, no flag-day cutovers.</p>

<h2 id="the-wire-size-impact">The Wire-Size Impact</h2>

<p>Post-quantum signatures are not a free lunch. The wire-size impact is real and worth knowing before you architect a system around it.</p>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>Public Key</th>
      <th>Signature</th>
      <th>NIST Level</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ES256 (P-256)</td>
      <td>64 B</td>
      <td>64 B</td>
      <td>(classical 128)</td>
    </tr>
    <tr>
      <td>Ed25519</td>
      <td>32 B</td>
      <td>64 B</td>
      <td>(classical 128)</td>
    </tr>
    <tr>
      <td>ML-DSA-44</td>
      <td>1,312 B</td>
      <td>2,420 B</td>
      <td>2</td>
    </tr>
    <tr>
      <td>ML-DSA-65</td>
      <td>1,952 B</td>
      <td>3,293 B</td>
      <td>3</td>
    </tr>
    <tr>
      <td>ML-DSA-87</td>
      <td>2,592 B</td>
      <td>4,595 B</td>
      <td>5</td>
    </tr>
  </tbody>
</table>

<p>A <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> with ML-DSA-44 is about <strong>40x larger</strong> than the same message with Ed25519. If you are shipping firmware over LoRaWAN, that matters. If you are storing attestation reports in a database, it matters less. Plan accordingly.</p>

<p>What ML-DSA does not cost you, surprisingly, is verification time. ML-DSA verification is faster than ECDSA P-256 verification on a Cortex-M4, because there is no point multiplication. It is all small-integer arithmetic over polynomial rings. The expensive operation is signing, and even that is manageable. The real cost is the bytes on the wire.</p>

<h2 id="why-we-did-this-in-cose-now">Why We Did This in COSE Now</h2>

<p>There is a fair question: why bother integrating ML-DSA into COSE now, before the IETF draft is final? Three reasons:</p>

<ol>
  <li><strong>CNSA 2.0 timelines.</strong> The NSA’s <a href="https://media.defense.gov/2022/Sep/07/2003071834/-1/-1/0/CSI_CNSA_2.0_ALGORITHMS_.PDF">CNSA 2.0</a> guidance requires PQC algorithms in software/firmware signing by <strong>2025</strong>, full PQC-only by <strong>2030</strong>. Devices being designed today will outlive the deadline. Shipping the COSE integration now means people who need to start prototyping have something to build against.</li>
  <li><strong>The crypto is final, the wire format is the easy part.</strong> FIPS 204 is not moving. Whatever IANA assigns as final COSE alg IDs, swapping <code class="language-plaintext highlighter-rouge">-48</code>/<code class="language-plaintext highlighter-rouge">-49</code>/<code class="language-plaintext highlighter-rouge">-50</code> for the final values is a one-line change on our side and a recompile on yours.</li>
  <li><strong>Constrained-device PQC needs a real home.</strong> Most PQC-in-protocol work has happened in TLS 1.3 and CMS. COSE is what you actually use on a microcontroller that does not have room for an X.509 stack: IoT firmware signing, attestation tokens, sensor authentication. If COSE does not get PQC, the embedded story has a hole in it.</li>
</ol>

<h2 id="try-it">Try It</h2>

<p>Build wolfSSL with <code class="language-plaintext highlighter-rouge">--enable-dilithium</code> (or <code class="language-plaintext highlighter-rouge">--enable-cryptonly --enable-dilithium</code> for a PQC-only build), then:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/aidangarske/wolfCOSE
<span class="nb">cd </span>wolfCOSE
make tool
./tools/wolfcose_tool keygen <span class="nt">-a</span> ML-DSA-44 <span class="nt">-o</span> pqc.key
./tools/wolfcose_tool sign <span class="nt">-k</span> pqc.key <span class="nt">-a</span> ML-DSA-44 <span class="nt">-i</span> data.bin <span class="nt">-o</span> data.cose
./tools/wolfcose_tool verify <span class="nt">-k</span> pqc.key <span class="nt">-i</span> data.cose
./tools/wolfcose_tool <span class="nb">test</span> <span class="nt">-a</span> ML-DSA-87
</code></pre></div></div>

<p>A complete keygen / sign / verify lifecycle for ML-DSA-44 lives in <code class="language-plaintext highlighter-rouge">examples/lifecycle_demo.c</code> and runs via <code class="language-plaintext highlighter-rouge">make demo</code>. ML-DSA-65 and ML-DSA-87 round-trips go through the CLI: <code class="language-plaintext highlighter-rouge">./tools/wolfcose_tool test -a ML-DSA-65</code>.</p>

<h2 id="what-is-next">What Is Next</h2>

<p>ML-DSA is the first PQC algorithm in wolfCOSE. The roadmap from here:</p>

<ul>
  <li><strong>SLH-DSA (FIPS 205, SPHINCS+):</strong> Stateless hash-based signatures. Slower than ML-DSA but with a different security assumption (hash functions vs. lattices). Useful for certificate roots where signing speed does not matter.</li>
  <li><strong>LMS / XMSS (NIST SP 800-208):</strong> Stateful hash-based signatures. The right tool for firmware signing where you can manage the state.</li>
  <li><strong>ML-KEM (FIPS 203, Kyber):</strong> For <code class="language-plaintext highlighter-rouge">COSE_Encrypt</code> recipient algorithms, replacing ECDH-ES.</li>
</ul>

<p>If you have a deployment where one of these is on a critical path, get in touch. That is how we prioritize.</p>

<h2 id="resources">Resources</h2>

<ul>
  <li>Repo: <a href="https://github.com/aidangarske/wolfCOSE">https://github.com/aidangarske/wolfCOSE</a></li>
  <li>Wiki: <a href="https://github.com/aidangarske/wolfCOSE/wiki">https://github.com/aidangarske/wolfCOSE/wiki</a></li>
  <li>FIPS 204 (ML-DSA): <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf">https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf</a></li>
  <li>IETF draft (COSE algorithm IDs): <a href="https://datatracker.ietf.org/doc/draft-ietf-cose-dilithium/">https://datatracker.ietf.org/doc/draft-ietf-cose-dilithium/</a></li>
  <li>wolfCrypt FIPS 140-3 cert #4718: <a href="https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718">https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718</a></li>
</ul>

<p>GPLv3, with commercial licensing available from wolfSSL. We do support engagements for teams that need help wiring this into a specific platform, particularly if you are racing a CNSA 2.0 deadline.</p>

<p><code class="language-plaintext highlighter-rouge">github.com/aidangarske/wolfCOSE | facts@wolfssl.com</code></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Production-Tested Post-Quantum Signatures in wolfCOSE]]></summary></entry><entry><title type="html">The Smallest Complete COSE Library for Embedded</title><link href="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-full-rfc9052/" rel="alternate" type="text/html" title="The Smallest Complete COSE Library for Embedded" /><published>2026-04-30T10:00:00+00:00</published><updated>2026-04-30T10:00:00+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/blog/wolfcose-full-rfc9052</id><content type="html" xml:base="https://aidangarske.github.io/wolfCOSE/blog/wolfcose-full-rfc9052/"><![CDATA[<p><em>Single-Actor and Multi-Actor Sign, Encrypt, and MAC in One Library</em></p>

<p>wolfCOSE now has full multi-signer and multi-recipient support, making it the smallest C COSE library to implement the entire RFC 9052 message set: <code class="language-plaintext highlighter-rouge">COSE_Sign1</code>, <code class="language-plaintext highlighter-rouge">COSE_Sign</code>, <code class="language-plaintext highlighter-rouge">COSE_Encrypt0</code>, <code class="language-plaintext highlighter-rouge">COSE_Encrypt</code>, <code class="language-plaintext highlighter-rouge">COSE_Mac0</code>, <code class="language-plaintext highlighter-rouge">COSE_Mac</code>. No malloc, no external CBOR dependency, no caveats.</p>

<p>If you have worked with COSE on a constrained device, you know the landscape. Most embedded C libraries either implement only <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> (<code class="language-plaintext highlighter-rouge">t_cose</code>, <code class="language-plaintext highlighter-rouge">go-cose</code>, <code class="language-plaintext highlighter-rouge">libcose</code>) or implement everything but require a heap allocator and several thousand lines of dependencies (<code class="language-plaintext highlighter-rouge">COSE-C</code>). wolfCOSE fills the gap: a complete RFC 9052 implementation without dragging in OpenSSL or <code class="language-plaintext highlighter-rouge">cn-cbor</code>.</p>

<p>A note on the project: wolfCOSE was developed by Aidan Garske, a wolfSSL developer, with support from wolfSSL engineering. It is currently an experimental project built on wolfCrypt, not an officially adopted wolfSSL product. If you are interested in production use or would like wolfSSL to formally support wolfCOSE, contact <a href="mailto:facts@wolfssl.com">facts@wolfssl.com</a>.</p>

<h2 id="why-multi-actor-messages-matter">Why Multi-Actor Messages Matter</h2>

<p><code class="language-plaintext highlighter-rouge">COSE_Sign1</code> is great when one entity signs one payload. It is not enough when:</p>

<ul>
  <li>You are shipping firmware that requires dual-control approval (silicon vendor + OEM both sign).</li>
  <li>You are rolling out a hybrid classical/PQC signature during the post-quantum migration: one ML-DSA signature, one ECDSA signature, both attached to the same artifact.</li>
  <li>You are broadcasting an encrypted config to a fleet of devices, each with its own KEK or ECDH keypair, and you do not want to encrypt the payload N times.</li>
  <li>You are sending a MAC’d message to a multicast group where each subscriber has a different shared secret with the broadcaster.</li>
</ul>

<p>These are exactly the scenarios <code class="language-plaintext highlighter-rouge">COSE_Sign</code>, <code class="language-plaintext highlighter-rouge">COSE_Encrypt</code>, and <code class="language-plaintext highlighter-rouge">COSE_Mac</code> were designed for, and they are the ones that disappeared from “alpha” or “experimental” status in most other C COSE libraries.</p>

<h2 id="dual-signing-a-firmware-manifest">Dual-Signing a Firmware Manifest</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* vendorKey and oemKey are WOLFCOSE_KEY*, prepared earlier via
   wc_CoseKey_SetEcc() and wc_CoseKey_SetDilithium() respectively. */</span>
<span class="n">WOLFCOSE_SIGNATURE</span> <span class="n">signers</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">algId</span> <span class="o">=</span> <span class="n">WOLFCOSE_ALG_ES256</span><span class="p">,</span>
      <span class="p">.</span><span class="n">key</span>   <span class="o">=</span> <span class="o">&amp;</span><span class="n">vendorKey</span><span class="p">,</span>
      <span class="p">.</span><span class="n">kid</span>   <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="s">"vendor-2026"</span><span class="p">,</span> <span class="p">.</span><span class="n">kidLen</span> <span class="o">=</span> <span class="mi">11</span> <span class="p">},</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">algId</span> <span class="o">=</span> <span class="n">WOLFCOSE_ALG_ML_DSA_65</span><span class="p">,</span>
      <span class="p">.</span><span class="n">key</span>   <span class="o">=</span> <span class="o">&amp;</span><span class="n">oemKey</span><span class="p">,</span>
      <span class="p">.</span><span class="n">kid</span>   <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="s">"oem-pqc-1"</span><span class="p">,</span>   <span class="p">.</span><span class="n">kidLen</span> <span class="o">=</span> <span class="mi">9</span> <span class="p">},</span>
<span class="p">};</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">wc_CoseSign_Sign</span><span class="p">(</span><span class="n">signers</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span>
                       <span class="n">firmware</span><span class="p">,</span> <span class="n">firmwareLen</span><span class="p">,</span>
                       <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
                       <span class="n">scratch</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">scratch</span><span class="p">),</span>
                       <span class="n">out</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">out</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">outLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rng</span><span class="p">);</span>
</code></pre></div></div>

<p>Two signers, two algorithms, one COSE structure. The verifier picks an index and a public key. Multi-recipient encryption follows the same shape: you hand wolfCOSE an array of <code class="language-plaintext highlighter-rouge">WOLFCOSE_RECIPIENT</code> describing how each recipient learns the content key (Direct, AES Key Wrap, ECDH-ES, ECDH-ES+KW), and you get one ciphertext addressable by every recipient.</p>

<h2 id="honest-comparison">Honest Comparison</h2>

<p>wolfCOSE was benchmarked against the four most-used C / C++ / Go COSE libraries on a Raspberry Pi 5 (aarch64, GCC 14.2, <code class="language-plaintext highlighter-rouge">-Os</code>), measuring <code class="language-plaintext highlighter-rouge">.text</code> size with <code class="language-plaintext highlighter-rouge">size</code>, source lines with <code class="language-plaintext highlighter-rouge">cloc</code>. Every library was built from master.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wolfCOSE (min)       ███                              7.5 KB (ES256 Sign1)
libcose+NanoCBOR     ███████                         18.8 KB (~2 algos)
wolfCOSE (full)      ██████████                      25.6 KB (40 algos)
t_cose+QCBOR         ████████████                    30.6 KB (7 algos)
COSE-C+cn-cbor       ██████████████████████████████  77.3 KB (~30 algos)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">t_cose</code>’s README claims 3.5 to 4.8 KB of <code class="language-plaintext highlighter-rouge">.text</code>. That is <code class="language-plaintext highlighter-rouge">t_cose</code> itself. QCBOR, which <code class="language-plaintext highlighter-rouge">t_cose</code> requires and ships separately, adds ~25.5 KB of <code class="language-plaintext highlighter-rouge">.text</code> on its own (<code class="language-plaintext highlighter-rouge">qcbor_decode.o</code> is 21.7 KB). The full footprint is ~30.6 KB once you include the CBOR engine you actually need to parse a COSE message. wolfCOSE’s built-in CBOR engine is 2.7 KB.</p>

<p><code class="language-plaintext highlighter-rouge">libcose</code> is genuinely small (~18.8 KB combined with NanoCBOR), but its libsodium backend implements two algorithms: EdDSA and ChaCha20-Poly1305. If you need ES256, RSA-PSS, AES-GCM, or HMAC-SHA256, you are not comparing the same thing.</p>

<p><code class="language-plaintext highlighter-rouge">COSE-C</code> is the only other library with all six message types, but it is C++ with a C façade, depends on <code class="language-plaintext highlighter-rouge">cn-cbor</code> (heap-allocated) and OpenSSL, and clocks in at ~77 KB.</p>

<h2 id="per-algorithm-efficiency">Per-Algorithm Efficiency</h2>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Total .text</th>
      <th>Algorithms</th>
      <th>KB / Algorithm</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>wolfCOSE (full)</strong></td>
      <td>25.6 KB</td>
      <td>40</td>
      <td><strong>0.64 KB</strong></td>
    </tr>
    <tr>
      <td>COSE-C+cn-cbor</td>
      <td>77.3 KB</td>
      <td>~30</td>
      <td>2.58 KB</td>
    </tr>
    <tr>
      <td>t_cose+QCBOR</td>
      <td>30.6 KB</td>
      <td>7</td>
      <td>4.37 KB</td>
    </tr>
    <tr>
      <td>libcose+NanoCBOR</td>
      <td>18.8 KB</td>
      <td>~2</td>
      <td>9.40 KB</td>
    </tr>
  </tbody>
</table>

<p>wolfCOSE delivers ~6.8x more algorithms per KB of code than <code class="language-plaintext highlighter-rouge">t_cose+QCBOR</code>.</p>

<h2 id="a-purpose-built-cbor-engine">A Purpose-Built CBOR Engine</h2>

<p>wolfCOSE includes its own CBOR encoder/decoder in a single file (<code class="language-plaintext highlighter-rouge">wolfcose_cbor.c</code>, 502 NCSL, 2.7 KB <code class="language-plaintext highlighter-rouge">.text</code>). This was a deliberate design choice for embedded targets, not an oversight. General-purpose CBOR libraries like QCBOR (4,908 NCSL, 25.5 KB <code class="language-plaintext highlighter-rouge">.text</code>) are full-featured implementations that handle every CBOR type, indefinite-length encoding, tagged values, floating point, and deeply nested structures. wolfCOSE’s CBOR engine handles exactly what COSE needs: definite-length maps, byte strings, text strings, integers, arrays, and CBOR tags. Nothing more.</p>

<p>The tradeoff is clear: wolfCOSE’s CBOR engine is not a general-purpose CBOR library. It does not parse arbitrary CBOR documents, and it is not intended for applications that need full CBOR flexibility. What it does is keep the total COSE + CBOR footprint under 8 KB for a minimal build, which is the difference between fitting on a Cortex-M0 with 64 KB of flash and not fitting at all.</p>

<p>For teams that need wolfCOSE on an embedded target, this is the right tradeoff. The CBOR engine is small because the use case is small: encode and decode COSE messages, nothing else.</p>

<h2 id="compile-time-stripping-is-the-design">Compile-Time Stripping Is the Design</h2>

<p>The full library is 22.9 KB of <code class="language-plaintext highlighter-rouge">.text</code> for the COSE layer; combined with the 2.7 KB CBOR engine, that is the 25.6 KB full-build total quoted in the comparison above. The “I just need ES256 Sign1 and nothing else” build is 4.8 KB of COSE + 2.7 KB of CBOR = 7.5 KB total. You opt out of features you do not need:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-DWOLFCOSE_NO_SIGN     -DWOLFCOSE_NO_ENCRYPT  -DWOLFCOSE_NO_MAC
-DWOLFCOSE_NO_ENCRYPT0 -DWOLFCOSE_NO_MAC0
-DWOLFCOSE_NO_SIGN1_SIGN
</code></pre></div></div>

<p>This matters because the people who care most about COSE are the people who care most about flash. 238 compile-time guards across the library ensure you only pay for what you use.</p>

<h2 id="zero-allocation-misra-striving-fips-path">Zero Allocation, MISRA-Striving, FIPS Path</h2>

<p><strong>No <code class="language-plaintext highlighter-rouge">malloc</code> anywhere in wolfCOSE.</strong> Every API takes caller-provided buffers, returns a length, and zeroizes its own stack with <code class="language-plaintext highlighter-rouge">wc_ForceZero</code> before returning. Safety-critical embedded teams that ban heap allocation can use this on bare metal.</p>

<p><strong>MISRA-C:2023 striving.</strong> Single-exit functions, no recursion, no function pointers in the hot path, three CI checkers (<code class="language-plaintext highlighter-rouge">cppcheck --addon=misra</code> for 2012, GCC strict + clang-tidy for ~80% of 2023). Deviations are documented rather than hidden.</p>

<p><strong>A real path to FIPS 140-3.</strong> wolfCrypt holds <a href="https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4718">Certificate #4718</a>. wolfCOSE’s crypto goes through one dependency that is FIPS-validated. wolfCOSE is not validated itself, but the path is clean.</p>

<h2 id="ci">CI</h2>

<p>wolfCOSE has 15 GitHub Actions workflows: 13 trigger on every PR, plus a nightly orchestrator and a wolfSSL-versions matrix that run on a schedule. Together they cover the full development lifecycle:</p>

<ul>
  <li><strong>Build + Test:</strong> multi-platform (Ubuntu / macOS), multi-compiler (GCC 10 to 14, Clang 14 to 18)</li>
  <li><strong>Static analysis:</strong> cppcheck + Clang scan-build + GCC <code class="language-plaintext highlighter-rouge">-fanalyzer</code></li>
  <li><strong>Coverity:</strong> nightly scan</li>
  <li><strong>Sanitizers:</strong> AddressSanitizer + UndefinedBehaviorSanitizer</li>
  <li><strong>Code coverage:</strong> threshold enforced (&gt;=97% on <code class="language-plaintext highlighter-rouge">wolfcose.c</code>, 100% on <code class="language-plaintext highlighter-rouge">wolfcose_cbor.c</code>)</li>
  <li><strong>MISRA-C:2012:</strong> <code class="language-plaintext highlighter-rouge">cppcheck --addon=misra</code> with all wolfCOSE macros defined</li>
  <li><strong>MISRA-C:2023:</strong> GCC strict warnings + clang-tidy (<code class="language-plaintext highlighter-rouge">bugprone-*</code>, <code class="language-plaintext highlighter-rouge">cert-*</code>, <code class="language-plaintext highlighter-rouge">clang-analyzer-*</code>, <code class="language-plaintext highlighter-rouge">misc-*</code>)</li>
  <li><strong>Minimal build matrix:</strong> 6 configurations testing different <code class="language-plaintext highlighter-rouge">WOLFCOSE_NO_*</code> combinations</li>
  <li><strong>Comprehensive algorithm tests:</strong> ~240 algorithm-combination round-trips</li>
  <li><strong>Real-world scenarios:</strong> firmware signing, attestation, fleet config, group broadcast, multi-party approval</li>
  <li><strong>Examples build:</strong> all example programs compile and link cleanly</li>
  <li><strong>wolfSSL integration:</strong> builds against wolfSSL to verify crypto backend compatibility</li>
  <li><strong>Codespell:</strong> typo checking across the codebase</li>
  <li><strong>Nightly orchestrator:</strong> re-runs the full CI suite on master each night (catches breakage from upstream changes between PRs)</li>
  <li><strong>wolfSSL-versions matrix:</strong> nightly compatibility check against every wolfSSL 5.x release</li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">wolfcose_tool</code> CLI ships with the project and round-trip tests every algorithm with <code class="language-plaintext highlighter-rouge">wolfcose_tool test --all</code>.</p>

<h2 id="why-we-built-this">Why We Built This</h2>

<p>A key driver for wolfCOSE was enabling SUIT manifest verification in <strong>wolfBoot</strong>, wolfSSL’s secure bootloader. The existing path for COSE in the embedded boot chain required <code class="language-plaintext highlighter-rouge">t_cose</code>, which depends on OpenSSL or mbedTLS for its crypto backend. That dependency is a non-starter for a bootloader that already uses wolfCrypt: it doubles the crypto footprint and introduces a second trust boundary.</p>

<p>wolfCOSE eliminates that dependency entirely. wolfBoot can verify <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> firmware manifests using the same wolfCrypt it already links for secure boot, with no additional crypto library on the flash. The same library also covers the multi-signer and encryption use cases that SUIT profiles are evolving toward.</p>

<p><code class="language-plaintext highlighter-rouge">t_cose</code> is a well-engineered library with strong <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> support, and <code class="language-plaintext highlighter-rouge">COSE-C</code> covers the full message set. wolfCOSE is built for teams that are already in the wolfSSL ecosystem and need COSE without adding OpenSSL, cn-cbor, or a heap allocator to their build.</p>

<h2 id="try-it">Try It</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/aidangarske/wolfCOSE
<span class="nb">cd </span>wolfCOSE
make <span class="o">&amp;&amp;</span> make <span class="nb">test
</span>make tool <span class="o">&amp;&amp;</span> ./tools/wolfcose_tool <span class="nb">test</span> <span class="nt">--all</span>
</code></pre></div></div>

<p>Repo: <a href="https://github.com/aidangarske/wolfCOSE">https://github.com/aidangarske/wolfCOSE</a>
Wiki: <a href="https://github.com/aidangarske/wolfCOSE/wiki">https://github.com/aidangarske/wolfCOSE/wiki</a></p>

<p>GPLv3, with commercial licensing available from wolfSSL upon adoption. For interest in production support, contact <a href="mailto:facts@wolfssl.com">facts@wolfssl.com</a>.</p>

<p><code class="language-plaintext highlighter-rouge">github.com/aidangarske/wolfCOSE | facts@wolfssl.com</code></p>

<p>Numbers measured March 2026 on a Raspberry Pi 5 (aarch64, GCC 14.2, <code class="language-plaintext highlighter-rouge">-Os</code>); every library was built from master with its default release flags. File an issue if you spot a build flag we got wrong on your favorite library and we will re-run.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Single-Actor and Multi-Actor Sign, Encrypt, and MAC in One Library]]></summary></entry><entry><title type="html">What is COSE? A short introduction</title><link href="https://aidangarske.github.io/wolfCOSE/blog/what-is-cose/" rel="alternate" type="text/html" title="What is COSE? A short introduction" /><published>2026-04-30T09:00:00+00:00</published><updated>2026-04-30T09:00:00+00:00</updated><id>https://aidangarske.github.io/wolfCOSE/blog/what-is-cose</id><content type="html" xml:base="https://aidangarske.github.io/wolfCOSE/blog/what-is-cose/"><![CDATA[<p><em>CBOR Object Signing and Encryption: JOSE for the embedded world</em></p>

<p>If you have ever shipped a JSON Web Token, you know the JOSE family: JWT, JWS, JWE. They are how the web does authenticated and encrypted structured data. JSON is verbose but human-readable, so the wire format trades bytes for ease of debugging. That tradeoff makes sense in a browser. It does not make sense in a sensor that talks over LoRaWAN with a 51-byte payload budget.</p>

<p>COSE stands for CBOR Object Signing and Encryption. It is defined in <a href="https://www.rfc-editor.org/rfc/rfc9052">RFC 9052</a> and <a href="https://www.rfc-editor.org/rfc/rfc9053">RFC 9053</a>, and it is the JOSE equivalent for binary, constrained-device protocols.</p>

<h2 id="cbor-in-one-paragraph">CBOR in One Paragraph</h2>

<p>CBOR (<a href="https://www.rfc-editor.org/rfc/rfc8949">RFC 8949</a>) is a binary serialization format that encodes the same data model as JSON (maps, arrays, integers, byte strings, text strings, booleans, null) but in fewer bytes and with no parsing ambiguity. A small integer is one byte. A short string is its UTF-8 bytes prefixed by a one-byte length. There are no quotes, commas, or whitespace to skip. A typical IoT message is 30 to 50 percent smaller in CBOR than the equivalent JSON, and a CBOR parser fits in 2 to 3 KB of code.</p>

<p>CBOR alone gives you compact serialization. It does not give you signatures, authentication, or encryption. That is what COSE adds.</p>

<h2 id="what-cose-is">What COSE Is</h2>

<p>COSE wraps cryptographic operations around a CBOR payload. The structures are intentionally close to their JOSE cousins. If you have written JWS, <code class="language-plaintext highlighter-rouge">COSE_Sign</code> will look familiar. The difference is that every byte is CBOR.</p>

<p>There are six COSE message types, in three pairs:</p>

<table>
  <thead>
    <tr>
      <th>Operation</th>
      <th>One-actor variant</th>
      <th>Many-actor variant</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Digital signature</td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Sign1</code></td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Sign</code></td>
    </tr>
    <tr>
      <td>Authenticated encryption</td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Encrypt0</code></td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Encrypt</code></td>
    </tr>
    <tr>
      <td>Message authentication code</td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Mac0</code></td>
      <td><code class="language-plaintext highlighter-rouge">COSE_Mac</code></td>
    </tr>
  </tbody>
</table>

<p>The <code class="language-plaintext highlighter-rouge">*0</code> variants are the simple case: one signer, or one recipient, or one MAC. The non-<code class="language-plaintext highlighter-rouge">0</code> variants support multiple actors. That covers multiple signers on the same payload (for hybrid classical/PQC migrations or multi-party approvals), multiple recipients on the same encrypted message (for fleet broadcast), or multiple MAC tags (for multicast groups).</p>

<p>A <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> looks like this on the wire (CBOR diagnostic notation):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>18([
  h'A10126',                  / protected: { 1: -7 (alg = ES256) } /
  { 4: h'766B65792D31' },     / unprotected: { 4: "vkey-1" (kid) } /
  h'48656C6C6F',              / payload: "Hello" /
  h'30450221...3045'          / signature: ECDSA over Sig_structure /
])
</code></pre></div></div>

<p>Three things to notice. First: the algorithm identifier is a small integer (-7 for ES256), not a string like JWT’s <code class="language-plaintext highlighter-rouge">"ES256"</code>. Second: the protected header is itself CBOR-encoded inside a byte string, so the verifier signs over the exact bytes the signer used (no canonicalization disputes). Third: the whole structure is one CBOR array, four elements long. There is nothing to parse beyond CBOR.</p>

<h2 id="why-not-just-use-jose--jwt">Why Not Just Use JOSE / JWT?</h2>

<p>If you are writing a web service, you should probably use JOSE. JWT has tooling, library support in every language, and the verbosity does not matter when you are sending it over HTTP. COSE exists for the cases where JOSE does not fit:</p>

<ul>
  <li><strong>Wire size matters.</strong> Over LoRaWAN, BLE, or NB-IoT, every byte costs power and latency. A <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> with a P-256 signature is roughly 90 bytes; the equivalent JWS with the same key is roughly 250.</li>
  <li><strong>You do not have a JSON parser.</strong> A JSON parser plus a Base64URL decoder eats a meaningful chunk of a microcontroller with 256 KB of flash. CBOR + COSE fits in under 10 KB.</li>
  <li><strong>You do not have an X.509 stack.</strong> JOSE assumes you can validate certificates. On a sensor running a bare-metal RTOS, you often cannot. COSE works with raw public keys (<code class="language-plaintext highlighter-rouge">kty=OKP</code> / <code class="language-plaintext highlighter-rouge">kty=EC2</code> / <code class="language-plaintext highlighter-rouge">kty=RSA</code>) referenced by <code class="language-plaintext highlighter-rouge">kid</code>, no PKI required.</li>
  <li><strong>Symmetric protocols matter.</strong> A surprising amount of IoT runs on shared symmetric keys provisioned at manufacture. <code class="language-plaintext highlighter-rouge">COSE_Mac0</code> and <code class="language-plaintext highlighter-rouge">COSE_Encrypt0</code> map cleanly onto that. JOSE has the same primitives but is rarely deployed that way.</li>
</ul>

<h2 id="where-cose-shows-up-in-the-real-world">Where COSE Shows Up in the Real World</h2>

<ul>
  <li><strong>Firmware signing.</strong> <a href="https://datatracker.ietf.org/wg/suit/about/">SUIT</a> (Software Updates for IoT) is the IETF working group standardizing OTA firmware updates for constrained devices. SUIT manifests are signed CBOR documents. <code class="language-plaintext highlighter-rouge">COSE_Sign1</code> covers single-vendor signing, and <code class="language-plaintext highlighter-rouge">COSE_Sign</code> covers multi-party (silicon vendor + OEM) approval.</li>
  <li><strong>Remote attestation.</strong> <a href="https://datatracker.ietf.org/wg/rats/about/">EAT</a> (Entity Attestation Tokens) is the CBOR/COSE equivalent of a JWT used to attest device boot state, key provenance, and TEE measurements.</li>
  <li><strong>CWT.</strong> <a href="https://www.rfc-editor.org/rfc/rfc8392">CBOR Web Tokens</a> are JWT but in CBOR. They are used in OAuth profiles for constrained environments.</li>
  <li><strong>Group communication.</strong> OSCORE and Group OSCORE use COSE structures to authenticate CoAP messages between IoT devices and gateways.</li>
  <li><strong>Post-quantum migration.</strong> As classical signatures are being replaced with ML-DSA and SLH-DSA, COSE is one of the few protocols that already has draft IANA assignments for PQC algorithm IDs.</li>
</ul>

<h2 id="what-you-need-to-use-cose">What You Need to Use COSE</h2>

<ul>
  <li>A CBOR encoder/decoder (RFC 8949). Many exist; sizes vary from ~2 KB (NanoCBOR, wolfCOSE’s built-in engine) to ~25 KB (QCBOR).</li>
  <li>A crypto library that implements the algorithms you want to use. ECDSA P-256 covers most of what is deployed today; AES-GCM and HMAC-SHA-256 are the common symmetric choices; ML-DSA is the PQC option.</li>
  <li>A COSE library that wires the two together. The current C / C++ options are <code class="language-plaintext highlighter-rouge">t_cose</code> (Sign1 only, requires QCBOR + OpenSSL or mbedTLS), <code class="language-plaintext highlighter-rouge">COSE-C</code> (full message set, requires cn-cbor + OpenSSL), <code class="language-plaintext highlighter-rouge">libcose</code> (Sign1 only, libsodium backend), and <a href="https://github.com/aidangarske/wolfCOSE">wolfCOSE</a> (full message set, wolfCrypt backend, no malloc).</li>
</ul>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="https://www.rfc-editor.org/rfc/rfc9052">RFC 9052</a> defines the COSE structures and processing rules.</li>
  <li><a href="https://www.rfc-editor.org/rfc/rfc9053">RFC 9053</a> specifies the initial COSE algorithm set.</li>
  <li><a href="https://www.rfc-editor.org/rfc/rfc8949">RFC 8949</a> defines CBOR, the binary serialization format used by COSE.</li>
  <li>The <a href="https://www.iana.org/assignments/cose/cose.xhtml">IANA COSE registry</a> lists assigned algorithm identifiers and parameter values.</li>
  <li>The <a href="https://datatracker.ietf.org/wg/suit/about/">SUIT working group</a> develops firmware update standards built on COSE.</li>
</ul>

<p>If you have a specific use case (firmware signing, attestation, fleet config) and you are wondering whether COSE is the right tool, the short answer is: probably yes, if your devices are constrained. The longer answer depends on what crypto stack you already have and how much flash you have left.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[CBOR Object Signing and Encryption: JOSE for the embedded world]]></summary></entry></feed>