<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Rebuilt]]></title><description><![CDATA[Core technologies, rebuilt from scratch]]></description><link>https://www.dmytrohuz.com</link><image><url>https://substackcdn.com/image/fetch/$s_!JM5w!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png</url><title>Rebuilt</title><link>https://www.dmytrohuz.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 04 Jun 2026 11:30:59 GMT</lastBuildDate><atom:link href="https://www.dmytrohuz.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Dmytro Huz]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[dmytrohuz@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[dmytrohuz@substack.com]]></itunes:email><itunes:name><![CDATA[Dmytro Huz]]></itunes:name></itunes:owner><itunes:author><![CDATA[Dmytro Huz]]></itunes:author><googleplay:owner><![CDATA[dmytrohuz@substack.com]]></googleplay:owner><googleplay:email><![CDATA[dmytrohuz@substack.com]]></googleplay:email><googleplay:author><![CDATA[Dmytro Huz]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Grid Is Not a Pipeline]]></title><description><![CDATA[The European Power Grid for Software Developers, Part 2]]></description><link>https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Fri, 29 May 2026 15:42:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!h0bv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h0bv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h0bv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h0bv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:465371,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h0bv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h0bv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1: The old picture says power flows through a pipe. The real grid is a synchronized graph that must stay balanced in real time.</em></p><p>In the first part, we killed one comfortable idea: electricity is not water.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;837d7012-f5ca-407b-9544-cc60f4ae81d8&quot;,&quot;caption&quot;:&quot;Most requirements in critical industries come from the physical world, and the power grid is no exception. Electricity has rules that cannot be negotiated with. Distance, geography, weather, forests, cities, mountains, sea cables, and even animals shape how the grid must be built and operated.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Electricity Is Not Water: A Better Mental Model for the Grid&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-15T15:10:24.614Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!sq_V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/electricity-is-not-water-a-better&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:197871484,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Now we can kill the next one.</p><p>The power grid is often imagined as a large water system. Somewhere far away there is a power plant, like a pump. Long power lines carry electricity, like pipes. The wires reach a city, then a street, then a house, and when someone opens the &#8220;tap&#8221; by switching on a device, electricity flows from the power plant into the socket.</p><p>It is simple. It is visual. It feels intuitive.</p><p>And it is almost the worst possible picture if you want to understand the real grid.</p><p>The grid is not a pipe where energy sits inside wires waiting to be consumed by devices. A power plant does not fill transmission lines with electrons and send them to your laptop, fridge, or washing machine. The real grid is a synchronized electromagnetic system. It works in real time. It oscillates. It has a frequency. It connects generators, consumers, transformers, substations, transmission lines, distribution grids, storage systems, and control centers into one huge machine.</p><p>And this machine has one brutal rule: generation and consumption must stay balanced all the time.</p><p>Consumers constantly change their behavior. Someone starts an industrial motor. A train accelerates. A factory begins a shift. A city wakes up in the morning. A cloud moves over a solar park. Wind generation rises or drops. A power plant ramps up, ramps down, or disconnects. Nothing is static, and still the whole system must remain synchronized.</p><p>In continental Europe, this synchronization is visible through the 50 Hz grid frequency. APG describes grid frequency as the measure of balance between electricity supply and demand, and one of its core tasks is to keep the system close to 50 Hz continuously. Austrian Power Grid</p><p>So no, the grid is not a pipe.</p><p>It is closer to a living technical machine whose operating state changes all the time. It is measured, switched, protected, transformed, corrected, and balanced continuously. Not because engineers enjoy complexity, but because the system would not work otherwise.</p><p>In this post we will discuss a better model for understanding the grid. We will stop looking at the fake &#8220;power plant to socket&#8221; diagram and rebuild the picture from the center outward. We will start with the high-voltage transmission grid, then attach substations, generators, storage systems, distribution grids, and consumers around it. After that, we will inspect a real slice of the Austrian grid and see how these abstract ideas appear in real infrastructure.</p><p>Because once we stop seeing the grid as a pipe, we can finally start seeing it as a graph.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Rebuilt! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The old diagram is already misleading</h2><p>Most explanations of the grid start with a power plant.</p><p>The diagram usually looks like this:</p><p>Power plant. Transmission line. Substation. Distribution line. House. Socket.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1fsH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1fsH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1fsH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:328114,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1fsH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1fsH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff910e9f3-3f52-4da1-bf49-d28d9934d974_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 2: This picture is easy to understand, but it already teaches the wrong architecture.</em></p><p>At first sight, this seems reasonable because electricity is generated somewhere and consumed somewhere else. But the moment we draw the system this way, we accidentally teach the reader the wrong architecture.</p><p>We make it look like electricity has a source, a route, and a destination. We make it look like a delivery system. A factory produces a product, a road transports it, and a customer receives it.</p><p>But the electrical grid is not a delivery route.</p><p>A generator does not produce a private stream of electricity for one city. A house does not receive electrons that started their journey at one specific power plant. A transmission line is not a pipe with a fixed direction. The grid is a shared synchronized system. Generators inject power into this system. Consumers withdraw power from it. Storage can do both. Power flows change depending on the current physical state of the network.</p><p>That is why starting with a power plant is already dangerous. It keeps us inside the pipe model.</p><p>A better starting point is the network itself. Not the generator, not the socket, but the grid.</p><h2>The grid is a graph, not a chain</h2><p>For a software developer, the better mental model is not a pipe. It is a graph.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Bn4m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Bn4m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Bn4m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:457526,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Bn4m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Bn4m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3e1c50-ec18-4aca-92fc-5b45e8cbb445_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 3: A better model: high-voltage lines form the backbone, substations are nodes, and generation, storage, distribution, and consumers attach to the graph.</em></p><p>Transmission lines are edges. Substations are nodes. Generators, storage systems, distribution grids, industrial consumers, cities, and interconnectors are attached to this graph at different points.</p><p>But we need to be careful here, because this analogy can also become stupid if we push it too far.</p><p>The grid is not the internet. Power does not move like packets. There is no router that says, &#8220;send this megawatt through line A and this other megawatt through line B.&#8221; Power flows according to physics. It depends on voltage, impedance, phase angle, topology, and the current state of the synchronized system.</p><p>Still, the graph model is extremely useful because it breaks the one-way chain illusion.</p><p>In a chain, you start at one end and walk to the other end. In a graph, you first ask what is connected to what. That is exactly how we should look at the grid.</p><p>The high-voltage network is the backbone. Substations connect parts of the backbone to each other and to lower voltage levels. Generators inject power at some nodes. Consumers withdraw power through other nodes. Storage systems can either withdraw or inject, depending on the operating mode. Interconnectors connect countries and control areas. Protection systems watch the graph and disconnect faulty parts when something goes wrong.</p><p>This is already a much better picture.</p><p>The grid is not a road from a power plant to your socket. It is a synchronized graph whose state must be kept inside safe limits.</p><h2>Why we start with the high-voltage grid</h2><p>If we start with a house, the grid looks like a local supply problem.</p><p>If we start with a power plant, the grid looks like a delivery problem.</p><p>But if we start with the high-voltage grid, the architecture becomes visible.</p><p>The high-voltage grid is the strong skeleton of the system. It connects regions, countries, large generation sites, large consumption areas, and lower-voltage networks. In Austria, APG manages the transmission grid at 110 kV, 220 kV, and 380 kV. These voltage levels are used to transport electricity efficiently over long distances and to connect large parts of the country into one operating system. Austrian Power Grid</p><p>This is where the pipe analogy starts to completely fall apart.</p><p>A high-voltage line is not just a long cable from one plant to one city. It is part of a larger network. Depending on generation, consumption, outages, maintenance, weather, and market schedules, power flows can change. A line can carry more or less power. A region can import or export. A storage plant can consume power in pumping mode and produce power in turbine mode. The same physical infrastructure participates in many different operating situations.</p><p>At the European scale, this becomes even more obvious. ENTSO-E (<strong>European Network of Transmission System Operators for Electricity</strong>) provides a transmission system map showing power plants, converters, substations, and high-voltage cables and lines operated by European transmission system operators. The map is useful for seeing the scale and density of the interconnected transmission network, but it should not be read as exact geography because ENTSO-E notes that network elements are not placed at their precise geographic positions. ENTSO-E Transmission System Map</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TrdG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TrdG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 424w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 848w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 1272w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TrdG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png" width="912" height="1072" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1072,&quot;width&quot;:912,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:763357,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TrdG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 424w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 848w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 1272w, https://substackcdn.com/image/fetch/$s_!TrdG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5102640e-09dd-450e-b5f5-7315b9ce2722_912x1072.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 4: Even as a schematic, the European transmission system looks less like a pipe and more like a continental graph. Source: ENTSO-E.</em></p><p>That map is overwhelming, and that is exactly the point.</p><p>The European grid is not a simple line from source to load. It is a continental machine. So instead of trying to understand the whole map at once, we can zoom into one country and then into one real grid node.</p><p>Austria is a good place to do this because the APG grid gives us a concrete high-voltage backbone, and the Austrian grid does not stop at the border. It is part of the wider European synchronous system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9GdN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9GdN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 424w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 848w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 1272w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9GdN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png" width="936" height="624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:624,&quot;width&quot;:936,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100553,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9GdN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 424w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 848w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 1272w, https://substackcdn.com/image/fetch/$s_!9GdN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b8c950-51e0-4f0d-b5f0-e0892070c0b4_936x624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 5: Austria&#8217;s transmission grid gives us a real backbone to inspect, with 110 kV, 220 kV, and 380 kV layers connected through substations. Source: Austrian Power Grid.</em></p><h2>Substations are the nodes where the graph becomes real</h2><p>Once we think in graphs, substations become much more interesting.</p><p>A substation is not just &#8220;the place where voltage goes down.&#8221; That is the school version. It is not completely wrong, but it is far too small.</p><p>A substation is a grid node.</p><p>It is a place where lines meet, voltage levels connect, transformers change voltage, breakers can connect or disconnect parts of the system, measurements are collected, faults are detected, and operators can change the topology of the network.</p><p>In graph language, a substation is where the abstract edge-and-node model becomes physical.</p><p>A transmission line arrives. Another line leaves. A transformer connects the 380 kV grid to the 220 kV grid or the 110 kV grid. A distribution network may be supplied from there. A power plant may be connected nearby. A breaker can disconnect a faulty line. Protection systems can isolate a damaged part of the network before the fault spreads further.</p><p>That is why substations are not secondary details. They are one of the main reasons the grid can be operated at all.</p><p>But in this article, we will not yet open the substation and inspect every component. Busbars, bays, breakers, disconnectors, transformers, protection relays, SCADA, and IEC 61850 deserve their own article. For now, we need only one idea:</p><p>A substation is a controllable node of the grid graph.</p><h2>A real example: Pongau substation</h2><p>Now let us stop speaking only in abstractions.</p><p>APG&#8217;s (<strong>Austrian Power Grid</strong>) Pongau substation is a good real-world example because it shows how much can happen at one node of the grid. </p><p>Pongau is a region in the Austrian federal state of Salzburg, south of the city of Salzburg and close to the Alpine hydropower and pumped-storage area around Kaprun and Tauern. You do not need to know Austrian geography to follow the argument. The only important point is that this node sits between several relevant grid directions.</p><p>APG describes Pongau as a newly constructed 380/220/110/30 kV substation in St. Johann im Pongau, commissioned in the first quarter of 2025 as part of the Salzburg line. The substation includes 380 kV GIS switchgear, two 380/220 kV transformers that connect the 380 kV and 220 kV grids, and two 380/110 kV transformers that connect the 380 kV and 110 kV grids. Austrian Power Grid, Pongau Substation</p><p>Before looking at the substation structure, we should first place it on the map. If you do not know Austrian geography, &#8220;Pongau&#8221; is just a name. The important thing is that it sits in a region where several relevant directions meet: toward Salzburg, toward Kaprun and Tauern, toward Wei&#223;enbach, and toward the regional Salzburg grid.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P3_H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P3_H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P3_H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:224093,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P3_H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!P3_H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4446f6a6-da4e-4c20-bd61-e46185248ad8_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 6: Pongau on the Austrian transmission map. </em></p><p>Pongau is not just a transformer in a field. It is a grid node where different voltage levels, transmission directions, pumped-storage connections, and regional distribution meet.</p><p>This is not a transformer in a field.</p><p>At this one node, different voltage levels meet. A 380 kV system from the Salzburg line is integrated. A connection continues toward Kaprun and Tauern. The 220 kV side connects toward Wei&#223;enbach. The 110 kV side connects to Salzburg Netz and the regional distribution grid. The 30 kV level is local or auxiliary in this simplified view, not a major transmission corridor.</p><p>This is exactly why the graph model is useful.</p><p>If we tried to explain Pongau with the pipe analogy, we would ask the wrong question: where does the electricity come from, and where does it go?</p><p>But the better question is: what does this node connect?</p><p>It connects voltage levels. It connects transmission paths. It connects regional distribution. It connects pumped storage. It strengthens supply reliability. It becomes part of the high-voltage architecture that allows the Austrian grid to operate as a system rather than as isolated local supply islands.</p><p>A substation like Pongau is not a passive object on the way from generator to consumer. It is part of the architecture that makes the whole system usable.</p><h2>Generators do not &#8220;send electricity to houses&#8221;</h2><p>Now that we have the graph, we can attach generators to it.</p><p>A generator is any facility that injects electrical power into the grid. It can be a hydropower plant, a gas plant, a wind farm, a solar plant, a biomass plant, or another generation source.</p><p>But the important word is injects.</p><p>A generator does not choose one household and send energy to it. It pushes power into the synchronized system. That changes the state of the system, and the physical network determines how power flows.</p><p>This is why the simple sentence &#8220;this power plant supplies this city&#8221; is often misleading. It may be true in an administrative, regional, or approximate sense, but electrically the situation is more complex. A power plant is connected to a grid node. Consumers are connected to other grid nodes. The system balances the total injection and withdrawal through the whole network.</p><p>Not let&#8217;s look at Kaprun.</p><p>Kaprun is a small municipality in the Austrian Alps, in the federal state of Salzburg, but in the Austrian power system the name means much more than a tourist village near mountains and reservoirs. The Kaprun area is closely associated with hydropower and pumped-storage infrastructure. It sits near the Alpine water reservoirs and power plants that can turn stored water into electricity when the grid needs power, or consume electricity to pump water back uphill when storing energy makes sense.</p><p>That makes Kaprun perfect for this article. It shows why &#8220;generator&#8221; and &#8220;consumer&#8221; are not always fixed identities. VERBUND (<strong>Austria's leading electricity company and one of the largest producers of hydroelectricity in Europe</strong>) describes the Kaprun power plant group as a storage power plant system with turbine capacity and pumping capacity. Pumped storage is useful because it can generate electricity when water flows down through turbines, but it can also consume electricity when pumps move water back up into a higher reservoir. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8r6j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8r6j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8r6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1589869,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8r6j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!8r6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76da3ef4-d795-4519-87a2-52326f5be37c_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 8: Pumped storage breaks the simple category. It can inject power like a generator or withdraw power like a large consumer.</em></p><p>This destroys another simple category.</p><p>A pumped-storage plant can behave like a generator. And it can behave like a consumer.</p><p>When the system needs power, it can inject power. When there is excess generation or when storage is economically and operationally useful, it can withdraw power and store energy as water at a higher elevation.</p><p>So even the terms &#8220;generator&#8221; and &#8220;consumer&#8221; are not fixed identities forever. They are roles a component can play in the current operating state of the grid.</p><h2>Consumers are not the end of a pipe</h2><p>Consumers also become clearer in the graph model.</p><p>A house is a consumer. A factory is a consumer. A railway system is a consumer. A city is a huge collection of consumers. A distribution grid can look like one large withdrawal point from the perspective of the transmission grid.</p><p>Most individual consumers are not connected directly to the high-voltage transmission grid. They are connected through lower-voltage distribution networks. Those distribution networks are then connected to the transmission grid through substations and transformers.</p><p>So when we zoom out to the transmission level, we should not imagine millions of tiny sockets attached directly to the 380 kV grid. We should imagine large withdrawal areas connected through distribution networks.</p><p>This is another reason the pipe picture fails.</p><p>The grid is not one pipe that becomes smaller and smaller until it reaches your phone charger. It is a layered electrical system. The high-voltage grid forms the backbone. Substations connect it to regional and local networks. Distribution grids bring power closer to consumers. And at each level, voltage, protection, switching, measurement, and operation matter.</p><p>The closer we move to individual homes, the more radial the system often becomes. But the transmission grid itself is designed more like a meshed backbone because important regions should not depend on one fragile path.</p><h2>Redundancy: the graph should not break too easily</h2><p>In graph theory, a bridge is an edge whose removal disconnects part of the graph.</p><p>That idea is useful for thinking about the transmission grid.</p><p>A fragile grid would have critical lines where one failure separates a whole region from the rest of the system. A stronger grid gives important regions more than one way to stay connected. Real grids are not perfectly bridge-free everywhere, but redundancy is one of the key design goals of transmission infrastructure.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_BO8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_BO8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_BO8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1565626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_BO8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_BO8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36fa4aac-3582-4880-b378-00fc5714aad3_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 9: In a fragile graph, one failed edge can disconnect a region. A stronger transmission grid creates alternative paths.</em></p><p>Austria gives a good example here. APG describes the 380 kV Salzburg line as part of Austria&#8217;s extra-high-voltage ring, and says the ring structure allows power to flow to customers from either direction. The Salzburg line closed a gap in the western part of that ring and entered full operation in April 2025. </p><p>The same idea appears in southern Austria. APG&#8217;s Carinthia power grid area project plans a 380 kV connection between Obersielach and Lienz to close the 380 kV grid in southern Austria. APG explains that the 380 kV ring creates a redundant connection because important substations can be supplied from two sides. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gjTl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gjTl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 424w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 848w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 1272w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gjTl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png" width="745" height="558" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:558,&quot;width&quot;:745,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:102321,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/199757049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gjTl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 424w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 848w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 1272w, https://substackcdn.com/image/fetch/$s_!gjTl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5dfb27e-5b75-4883-8b21-006d9b41c2a6_745x558.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is the graph model becoming real engineering.</p><p>We build redundancy because real systems fail. Lines are taken out for maintenance. Weather damages infrastructure. Equipment trips. Loads change. Generation patterns shift. If the grid were a simple tree, every important edge would become a potential disaster.</p><p>A meshed high-voltage grid gives operators more room to keep the system alive.</p><p>Again, we should not confuse this with the internet. The grid does not reroute power like packets. But topology matters. Connectivity matters. Alternative paths matter. A ring is not just a geometric shape. It is a way to reduce dependence on a single corridor.</p><h2>So what is the grid?</h2><p>At this point, we can rebuild the picture.</p><p>The grid is not a pipe. It is not a chain from power plant to socket. It is not a delivery system. It is a synchronized graph built from physical infrastructure.</p><p>High-voltage transmission lines form the backbone. Substations are the nodes where lines, transformers, voltage levels, distribution grids, generators, and storage systems connect. Generators inject power into the system. Consumers withdraw power from it. Pumped storage can do both. Redundancy helps the system survive outages and maintenance. Operators and protection systems keep the graph inside safe limits.</p><p>And all of this is part of a larger synchronized machine.</p><p>The most important idea is not that the grid has many components. The most important idea is that all these components participate in one continuously changing operating state.</p><p>Generation changes. Consumption changes. Storage changes mode. Power flows change. Lines become loaded or unloaded. Substations connect and disconnect parts of the graph. Protection systems wait for faults. Operators watch the system.</p><p>And through all of that, the grid must stay balanced.</p><h2>The real goal: balance</h2><p>The whole grid has one brutal rule: generation and consumption must stay balanced all the time.</p><p>But neither side is stable.</p><p>Consumers switch devices on and off. Factories start machines. Trains accelerate. Cities move through morning peaks and evening peaks. Wind farms produce more or less depending on the weather. Solar production changes with clouds and time of day. Power plants start, stop, ramp up, or reduce output. Pumped-storage plants can suddenly become large consumers or large generators.</p><p>And still, the system has to remain synchronized.</p><p>Across Austria, across neighboring countries, across the continental European grid, the machine has to keep its frequency close to 50 Hz. APG&#8217;s balancing information describes 50 Hz as the set point value in the Continental Europe synchronous area and explains that generation and consumption must stay in constant equilibrium. APG Balancing</p><p>That is the real goal of the grid.</p><p>Not simply to &#8220;send electricity&#8221; from power plants to houses.</p><p>The real goal is to continuously adapt generation, consumption, storage, topology, voltage, and power flows so the whole system remains stable.</p><p>This is why the pipe analogy is not only incomplete. It hides the most beautiful part of the system.</p><p>A pipe does not need synchronization. A pipe does not have frequency. A pipe does not react to every change in the whole continent. A pipe does not have to balance generation and consumption in real time.</p><p>The grid does.</p><p>And now that we can see the grid as a synchronized graph, the next question becomes much more interesting:</p><p>How is this machine operated?</p><p>In the next article, we will look at grid operation and see how all the components we discussed, transmission lines, substations, generators, consumers, storage systems, and control systems, are used to keep generation and consumption balanced in real time.</p><h2>Sources</h2><ul><li><p>Austrian Power Grid: <a href="https://www.apg.at/en/power-grid/power-grid-austria/">[Power Grid Austria]</a></p></li><li><p>Austrian Power Grid: [<a href="https://www.apg.at/en/projects/pongau-substation/">Pongau Substation</a>]</p></li><li><p>Austrian Power Grid: [<a href="https://www.apg.at/en/projects/salzburg-line/">Salzburg Line</a>]</p></li><li><p>Austrian Power Grid: [<a href="https://www.apg.at/en/projects/carinthia-power-grid-area/">Carinthia Power Grid Area</a>]</p></li><li><p>APG Market: [<a href="https://markt.apg.at/en/power-grid/balancing/">Balancing</a>]</p></li><li><p>ENTSO-E: [<a href="https://www.entsoe.eu/data/map/">Transmission System Map</a>]</p></li><li><p>VERBUND: [<a href="https://www.verbund.com/en-at/about-verbund/power-plants">Power plants and hydropower information</a>]</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Rebuilt! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Electricity Is Not Water: A Better Mental Model for the Grid]]></title><description><![CDATA[Electricity Before Infrastructure, Part 1 of The European Power Grid for Software Developers]]></description><link>https://www.dmytrohuz.com/p/electricity-is-not-water-a-better</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/electricity-is-not-water-a-better</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Fri, 15 May 2026 15:10:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sq_V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sq_V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sq_V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sq_V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:507200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sq_V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sq_V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Most requirements in critical industries come from the physical world, and the power grid is no exception. Electricity has rules that cannot be negotiated with. Distance, geography, weather, forests, cities, mountains, sea cables, and even animals shape how the grid must be built and operated.</p><p>Before we talk about energy companies, markets, software, smart meters, substations, or regulation &#8212; we need to understand the physical system underneath. The grid is not just a business system for buying and selling energy. It is a large physical machine that must stay stable every second.</p><p>My approach is simple: deconstruct and rebuild. We start with the smallest useful ideas, understand what problem each one solves, and then follow how they connect into the larger system.</p><p>So let&#8217;s start with electricity itself.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>The Water Analogy Is Too Small</h2><p>A good analogy can make a complicated subject click. A wrong one can quietly block the whole understanding &#8212; for a very long time.</p><p>This happened to me with electricity.</p><p>The common explanation says that electricity is like water flowing through pipes. Voltage is pressure, current is flow, wires are pipes, and a load is something that uses the flow.</p><p>At the beginning, this helps. It gives you a picture for a simple circuit. A battery pushes, current flows, a lamp lights up, a heater becomes warm. Nice.</p><p>But later this picture becomes a wall.</p><p>It does not explain motors. It does not explain generators. It does not explain transformers. It does not explain why AC exists, why the grid uses high voltage, why three phases, or why frequency matters so much. And the frustrating part is not that those topics are hard &#8212; the frustrating part is that the picture in your head has no room for them. You try to apply the analogy and it just... does not fit. So you feel stupid. But you are not stupid. The analogy is just too small.</p><p>Electricity is not only something flowing inside wires. A wire with current is part of an electromagnetic system. Charges move in the conductor, but electric and magnetic fields exist around it &#8212; and those fields are not a side detail. They are the reason motors, generators, transformers, AC grids, and frequency make any sense at all.</p><p>So: keep the water picture for the first few steps. Then throw it away and build a new one.</p><p>The grid is not a pipe system. It is an electromagnetic machine.</p><p>And I want to give you a set of analogies that actually maps to this. Not one Swiss Army Knife analogy that is universally bad at everything. A whole zoo of weird, specific, memorable animals &#8212; each responsible for one idea, each vivid enough that you cannot forget it.</p><p>Let&#8217;s go.</p><div><hr></div><h2>Voltage, Current, Power, and Resistance (Where Pipes Still Help)</h2><p>Here is where the pipe analogy earns its last few minutes on stage.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zEoO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zEoO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zEoO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:263565,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zEoO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zEoO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e39844-30d8-4787-870b-4b3601eb0ed3_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A source creates an electrical push between two points. This push is <strong>voltage</strong>. When we connect a load and close the circuit, charge can move &#8212; and this movement of charge is <strong>current</strong>.</p><p>The load is where energy becomes something useful. Heat in a heater. Light in a lamp. Motion in a motor. Computation in electronics.</p><p><strong>Power</strong> tells us how much energy is delivered per second:</p><blockquote><p>Power = Voltage &#215; Current P = V &#215; I</p></blockquote><p>This formula matters because power is not only voltage and not only current. It is both together. High voltage with almost no current does not deliver much power. High current with very low voltage also does not necessarily deliver much. Useful electrical power comes from the combination.</p><p><strong>Resistance</strong> is the opposition a material gives to current. When current flows through resistance, part of the energy becomes heat. In a heater &#8212; exactly what we want. In a transmission line &#8212; exactly what we do not want.</p><p>This already gives us one of the main grid problems: we need to move huge amounts of energy over long distances without turning the wires into giant heaters.</p><p>But to understand the solution, we first need the piece that the water analogy keeps hiding.</p><div><hr></div><h2>Current Creates a Magnetic Field (The Piece That Changes Everything)</h2><p>When current flows through a wire, a magnetic field appears around that wire.</p><p>Stop here for a second. This is not a small detail. This is the idea that unlocks everything else in the article.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!95V-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!95V-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!95V-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!95V-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!95V-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!95V-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:294144,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!95V-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!95V-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!95V-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!95V-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F791387a1-63fb-4aa8-b722-0b15d13d386b_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The field is not made from tiny pieces of magnet escaping through the insulation. Electrons are not little magnetic balls flying outward. Nothing leaves the wire.</p><p>The idea is stranger and more useful: <strong>moving electric charge creates a magnetic field around the path where it moves</strong>. That is all. Current flows, field appears around the wire. Stronger current, stronger field. Current changes direction, field changes direction.</p><p>A compass near a current-carrying wire reacts &#8212; not because anything jumped out of the wire, but because the wire changed the space around it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yTju!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yTju!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yTju!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yTju!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yTju!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yTju!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:281598,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yTju!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yTju!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yTju!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yTju!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78b85f2e-13b7-405f-b2ec-6d916ae77564_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is the moment where the water analogy breaks completely. Water flowing through a pipe stays inside the pipe. Current in a wire creates an effect outside. Electricity can influence the world without electrons leaving the conductor.</p><p>This is the bridge to motors, generators, and transformers. Everything from here is built on top of this one idea.</p><div><hr></div><h2>Motors and Generators Are the Same Story &#8212; In Two Directions</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rKsj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rKsj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rKsj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:350979,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rKsj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rKsj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad417791-3422-4c32-98cb-904e5bc70e7a_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A motor does not work because electrons hit a wheel the way water hits a turbine.</p><p>A motor works because <strong>magnetic fields push and pull on each other</strong>.</p><p>Imagine a coil of wire. When current flows through it, the coil behaves like an electromagnet. Place it near another magnetic field, and forces appear. Those forces can create torque. Torque can rotate something.</p><p>But here is the problem: one fixed magnetic push is not enough for continuous rotation. If the field stays the same, the rotor moves until it reaches a comfortable position &#8212; and then it stops. The same force that helped it move now holds it there. Like a magnet stuck to your fridge door.</p><p>So the trick is not only to create a magnetic field. The trick is to <strong>keep changing it at the right moment</strong>.</p><p>Change the direction of current &#8212; the field changes direction too. Now the force continues to pull and push the rotor forward instead of letting it settle. Control the timing well enough and you get smooth, continuous rotation.</p><p>This is the basic idea behind electric motors: controlled current &#8594; controlled magnetic fields &#8594; motion.</p><p>A generator is the same story in the opposite direction.</p><p>In a motor, electricity creates motion. In a generator, motion creates electricity. Move a magnet near a coil, or move a coil through a magnetic field &#8212; the magnetic field through the coil changes. A <strong>changing</strong> magnetic field creates an electric field in the conductor, and that electric field pushes charges. The result is voltage.</p><p>A generator does not pour electrical liquid into a wire. It creates voltage because motion changes magnetic fields.</p><p>And because large generators rotate, the voltage they create naturally rises, falls, crosses zero, reverses direction, and repeats. Which brings us to AC.</p><div><hr></div><h2>AC Is Not Useless Back-and-Forth Movement</h2><p>DC means direct current. The push keeps the same direction. A battery is the simplest example.</p><p>AC means alternating current. The voltage rises, falls, crosses zero, reverses, rises in the opposite direction, and repeats. In Europe, the grid does this 50 times per second &#8212; 50 Hz.</p><p>At first, AC feels wrong. If current goes one way and then back again, how does it deliver energy? Doesn&#8217;t it cancel itself?</p><p>The water picture misleads us here again.</p><p>Think of a saw. It moves back and forth, but it cuts wood. A bow drill moves back and forth, but it creates enough heat to start a fire. The motion does not need to travel forever in one direction to do work. The <strong>change</strong> itself is what does the job.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bnvy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bnvy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bnvy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1194572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bnvy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!bnvy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05f89c76-07c3-4f14-b24f-9b69ed2fc3bc_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In AC systems, charges mostly move back and forth locally. The electromagnetic field transfers energy through the system, and loads take energy from that field. Electrons do not need to travel from a power plant to your laptop. The field moves through the system, and energy moves with it.</p><p>AC is useful precisely because it keeps changing. Changing fields are created naturally by rotating generators. Changing fields drive motors. And changing fields make transformers possible &#8212; which turns out to be the key to building any large-scale grid.</p><div><hr></div><h2>High Voltage and Transformers Solve the Distance Problem</h2><p>Now we can come back to the problem we parked earlier: moving energy far without losing most of it as heat.</p><p>The wire between a generator and a city has resistance. Current flowing through resistance turns energy into heat. The formula is:</p><blockquote><p>Line losses = Current&#178; &#215; Resistance P_loss = I&#178; &#215; R</p></blockquote><p>That square is the dangerous part. If current doubles, losses become four times larger. If current becomes ten times larger, losses become one hundred times larger.</p><p>Current is expensive. It heats wires, wastes energy, and forces us to build thicker, heavier, and more costly infrastructure.</p><p>But power is voltage multiplied by current. For the same amount of power, we can choose different combinations: lower voltage and higher current, or higher voltage and lower current. For long distances, the second option wins easily.</p><p>Raise the voltage. Lower the current. Reduce the losses.</p><p>This is why high-voltage transmission lines exist. Not because engineers love dangerous numbers. It is because without high voltage, long-distance transmission is just a very expensive heater.</p><p>But high voltage that is useful for transmission is not something you want coming out of a wall socket. So the grid needs a machine that can raise voltage before the long journey, and lower it again at the destination.</p><p>This machine is the <strong>transformer</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8lr3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8lr3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8lr3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:394085,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8lr3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8lr3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f513f9-14e4-4499-92f7-2a4022da28d5_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A transformer has two coils wrapped around a magnetic core. One coil is connected to one circuit, the other to another circuit. The two coils are not directly connected by a wire. Electrons do not flow from the first coil into the second.</p><p>And still, energy moves across.</p><p>With the water analogy, this looks like magic. With fields, it becomes completely clear:</p><blockquote><p>AC in the first coil</p><p>&#8594; changing magnetic field in the core</p><p>&#8594; changing field reaches the second coil</p><p>&#8594; voltage is induced in the second coil</p><p>&#8594; power is delivered to the load</p></blockquote><p>The field connects the two circuits.</p><p>This also explains why a normal transformer needs AC. Connect steady DC to the first coil and there is only one brief moment &#8212; when the current starts &#8212; where the field changes. After that, the field becomes steady. A steady magnetic field does not continuously induce voltage in the second coil. No change, no transformer action.</p><p>The transformer can also change the voltage level because the two coils can have different numbers of turns:</p><blockquote><p>V_secondary / V_primary &#8776; N_secondary / N_primary</p></blockquote><p>More turns on the second coil &#8212; voltage steps up. Fewer turns &#8212; voltage steps down.</p><p>Now the skeleton of the grid is visible. Generators produce power. Step-up transformers raise the voltage. High-voltage lines carry it over long distances. Substations connect, protect, switch, and transform. Step-down transformers bring voltage back down. Distribution networks bring electricity closer to consumers. Loads turn energy into heat, light, motion, or computation.</p><p>But there is still one physical improvement missing.</p><div><hr></div><h2>Why the Grid Uses Three Phases</h2><p>We have AC, transformers, and high-voltage transmission &#8212; but one important piece is still not quite right for large-scale use.</p><p>Remember the motor problem: we do not only want a magnetic field. We want a magnetic field that keeps the rotor moving smoothly. With a single AC wave, the push changes, weakens, crosses zero, reverses, comes back. It works, but the motor has to fight through those weak and zero points in every cycle.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6mSU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6mSU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6mSU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:294704,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6mSU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6mSU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47f18b1d-e522-4831-8a04-04393bd1f618_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The better idea: use three AC waves instead of one. Not three random waves &#8212; three <strong>coordinated</strong> waves, shifted in time relative to each other. When one phase is near its maximum, the others are at different points in the cycle. When one weakens, another is already growing stronger. Together, they fill in each other&#8217;s gaps and create a much smoother delivery of power.</p><p>For motors, this is especially valuable because three coordinated phases can create a naturally <strong>rotating</strong> magnetic field. Not a push that has to fight through weak spots &#8212; a field that smoothly turns around the motor and pulls the rotor with it. This is almost exactly what a motor wants.</p><p>Now, the elegant part: we do not need six wires to run three separate circuits.</p><p>Each phase needs one conductor &#8212; so we have three phase conductors: L1, L2, and L3. In a balanced system, the currents in these three phases are shifted so that their sum is zero at every moment. Because of that, the return current cancels out, and a separate return wire is not needed for high-voltage transmission.</p><p>This is why you often see three main conductors on transmission towers &#8212; not three complete circuits with six wires, but one coordinated three-phase system using three.</p><p>Three phases are not an arbitrary complication. They solve a physical problem: smoother power delivery, better conditions for motors, and more efficient large-scale transmission with fewer conductors.</p><div><hr></div><h2>Frequency Is the Pulse of the Grid</h2><p>Now we have generators, AC, transformers, high voltage, and three phases. The physical machine is assembled.</p><p>But the grid is not a tank where you can pour electricity in and take it out freely whenever you want. There is some stored energy in rotating machines, magnetic fields, batteries, and other devices &#8212; but the AC grid itself must be balanced continuously. <strong>Continuously.</strong></p><p>Every second, generation and consumption need to stay close to each other.</p><p>If consumers take more power than generators provide, the system starts to slow down. If generation exceeds consumption, the system starts to speed up. And this shows up in one very visible number: <strong>frequency</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kT_i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kT_i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kT_i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:383845,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kT_i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kT_i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04fb3a9d-7db7-4601-94d9-2bd7c643767e_1672x941.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>In Europe, the grid runs at 50 Hz. That means the AC waveform completes 50 full cycles every second. When generation and consumption are balanced, frequency stays close to 50 Hz. If consumption exceeds generation, frequency falls. If generation exceeds consumption, frequency rises.</p><p>Frequency is not just a number in a technical standard. It is one of the signs that shows the balance of the whole synchronized machine. It is the pulse of the grid.</p><p>And this is where the power grid becomes very different from most software systems. In software, we can queue work, buffer messages, retry requests, scale components, and hide temporary imbalance behind storage. The grid has controls and buffers too &#8212; but the physical system still has to obey the immediate relationship between generation, load, voltage, current, phase, and frequency. You cannot queue it. You cannot retry it.</p><p>The grid is an electromagnetic machine spread over a continent, and that machine must stay inside its limits. If the pulse is stable, the system is alive and balanced. If the pulse drifts too far, something is wrong.</p><p>That is the grid&#8217;s arrhythmia.</p><p>And you have to heal it!</p><div><hr></div><h2>What We Have Now And What Is Next</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ey_S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ey_S!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ey_S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:486739,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197871484?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ey_S!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ey_S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc372535f-f9cc-4b72-abc2-d56e3efa7c8f_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We started with the water analogy because it helps in the beginning. It explains voltage, current, resistance, power, and simple circuits.</p><p>But then it breaks.</p><p>It cannot explain how electricity creates motion outside a wire. For that, we need fields.</p><p>Current creates magnetic fields. Magnetic fields push and pull. Controlled magnetic fields create motion &#8212; motors. Reverse the story: motion changes magnetic fields, changing fields create voltage &#8212; generators.</p><p>Then AC stops being strange. It is not useless back-and-forth. It is <strong>repeated change</strong>, and changing fields are exactly what generators, motors, and transformers need.</p><p>Then distance creates the next problem. To move power far away, we reduce current and increase voltage. Because high voltage is not useful everywhere, we use transformers to step it up and back down.</p><p>Then three phases make the system smoother &#8212; better for motors, smoother power delivery, efficient transmission without six wires for three circuits.</p><p>And finally, frequency shows us that the grid is not a passive network of wires. It is a synchronized machine that must stay balanced every second.</p><p>The water analogy was not completely wrong. It was just too small. It helped me enter the first room, and then it locked all the next doors.</p><p>The field-based model opens them.</p><p>It does not make the grid simple, because the grid is not simple. But it makes the complexity <strong>connected</strong>. Motors, generators, transformers, AC, high voltage, three phases, and frequency stop being random technical vocabulary and become parts of the same engineering story.</p><p>In the next part, we can move from physics to structure: power plants, substations, transmission lines, distribution networks, and the full path from generation to consumption.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;55161b6b-2630-4991-83a1-b03850727586&quot;,&quot;caption&quot;:&quot;Figure 1: The old picture says power flows through a pipe. The real grid is a synchronized graph that must stay balanced in real time.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Grid Is Not a Pipeline&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-29T15:42:22.475Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!h0bv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:199757049,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>If you want to follow this rebuild, subscribe and the next part will find you when it is ready.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Rebuilt! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A Developer’s Map of the European Power Grid]]></title><description><![CDATA[The central hub for my series: The European Power Grid for Software Developers]]></description><link>https://www.dmytrohuz.com/p/a-developers-map-of-the-european</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-developers-map-of-the-european</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Tue, 12 May 2026 15:14:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!r2v5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r2v5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r2v5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 424w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 848w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 1272w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r2v5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png" width="1456" height="1030" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1030,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2130113,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/197363882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!r2v5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 424w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 848w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 1272w, https://substackcdn.com/image/fetch/$s_!r2v5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b47f174-07bd-435f-9c8c-363edb0adbfd_1491x1055.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I have spent a lot of time learning technical systems by rebuilding them from first principles.</p><p>With TLS, I tried to understand the protocol not by starting from the final polished design, but by beginning with a simple and broken version. Then I added one missing idea after another: encryption, integrity, key exchange, certificates, and identity. Step by step, the real structure started to make more sense.</p><p>Now I want to apply a similar mindset to another system, but on a much larger scale: the European power grid.</p><p>The grid is not a small topic. It is not only wires, power plants, and substations. It is also physics, real-time operation, markets, regulation, international coordination, forecasting, automation, cybersecurity, and software. It is one of the most critical systems around us, but for many software developers it often remains hidden behind abstract words like &#8220;energy sector,&#8221; &#8220;smart grid,&#8221; &#8220;TSO,&#8221; &#8220;DSO,&#8221; &#8220;balancing,&#8221; &#8220;SCADA,&#8221; or &#8220;grid modernization.&#8221;</p><p>While trying to learn this field, I noticed a recurring problem: many explanations are either too shallow, too regulatory, or too electrical-engineering-heavy. Some give a high-level business overview, but do not explain the physical system underneath. Others focus on laws, institutions, and market rules, but are hard to connect to the actual grid. Electrical engineering resources often go deep into theory, which is valuable, but not always the easiest entry point for software developers who first need a connected map of the whole system.</p><p>That is the gap I want to work through.</p><p>I want to build a central learning hub where I slowly connect these pieces together.</p><p>In this post I will explain what I want to build, why I am building it, and how I want to approach the European power grid from the perspective of a software developer who wants to understand the industry from the ground up.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><h2>Why I Am Starting This</h2><p>My goal is simple: as a software developer, I want to better understand this critical industry and the components it is built from.</p><p>This is not a random topic for me. I already work in this industry, and the closer I get to energy systems, grid-related software, and operational technology, the more I see that software is only one layer of a much larger system. To build better software, make better technical decisions, and understand the real problems behind the requirements, I need a clearer picture of the whole grid.</p><p>I do not want to look at energy software as isolated applications detached from the physical system underneath. I want to understand what the software is connected to, what constraints the grid has, why those constraints exist, and how the different layers of the industry fit together.</p><p>When we build software for ordinary web applications, we can often reason mostly in terms of users, APIs, databases, queues, services, and infrastructure. The physical world still matters, but it is often abstracted away.</p><p>The power grid is different.</p><p>Software in this industry does not exist in a vacuum. It is connected to physical equipment, operational constraints, safety requirements, regulations, market structures, and cross-border coordination. A wrong assumption is not just a wrong assumption inside an application. It can touch real infrastructure, real operators, real assets, and real consequences.</p><p>That makes the field difficult, but also very interesting.</p><p>I want to understand the grid not as a collection of disconnected terms, but as one layered system. I want to see how electricity behaves, how the physical infrastructure implements that behavior, how operators keep the system stable, how organizations divide responsibility, how Europe coordinates the whole system, and where software enters the picture.</p><h2>The Problem With Existing Explanations</h2><p>When I started looking for good explanations of the European power grid, I often found useful pieces, but not a full map.</p><p>Some resources explain the energy sector from a very high level. They describe renewables, transmission, distribution, markets, and policy, but they often stay too far away from the physical system. After reading them, you may know the names of the actors, but still not understand what is actually happening in the grid.</p><p>Other resources are very regulatory. They explain institutions, market rules, network codes, responsibilities, tariffs, and legal structures. That is important, but if you do not already understand the physical and operational system, it is hard to know where those rules attach to reality.</p><p>Then there are electrical engineering resources, which are often much deeper technically. They explain circuits, machines, power systems, load flow, protection, and control theory. These resources are valuable, but they are not always the right first step for a software developer who wants to build a practical mental model before going deeper into equations and specialist theory.</p><p>So the problem is not that the information does not exist.</p><p>The problem is that it is fragmented across different worlds.</p><p>There is the physics world. There is the electrical engineering world. There is the operator world. There is the regulatory world. There is the market world. There is the software world. Each of them has its own language, assumptions, and priorities.</p><p>What I want to build is a bridge between those worlds.</p><h2>Who This Is For</h2><p>I am writing this from the perspective of a software developer, but I do not think the topic is useful only for software developers.</p><p>Many people enter the energy industry from different angles. Some come from cloud engineering, testing, cybersecurity, data engineering, product management, business analysis, operations, regulation, consulting, or management. Many of them face the same problem: they see parts of the system, but not the whole picture.</p><p>Even if my own final focus will be software, I believe that a clearer map of the whole grid can be useful for anyone who wants to understand where their work fits.</p><p>If you work on an energy platform, it helps to know what physical process your data represents.</p><p>If you work on testing, it helps to know which failures matter operationally.</p><p>If you work on cybersecurity, it helps to understand what assets, protocols, and control paths are critical.</p><p>If you work in cloud or data, it helps to know why not every problem in this industry can be treated like a normal SaaS problem.</p><p>If you work in product or management, it helps to understand the difference between a market requirement, an operational requirement, and a physical constraint.</p><p>That is the kind of map I want to build.</p><p>Not a complete electrical engineering education.</p><p>Not a regulatory encyclopedia.</p><p>Not a shallow overview of energy trends.</p><p>A practical, layered explanation of the European power grid for people who want to understand the system behind the software, decisions, and infrastructure.</p><h2>What This Hub Will Become</h2><p>This page will become the central hub for the series.</p><p>I will update it as new articles are published, and over time it should become a structured entry point into the whole topic. The goal is not to explain everything at once. The goal is to build the map piece by piece.</p><p>First, electricity itself.</p><p>Then the physical grid.</p><p>Then operations.</p><p>Then management.</p><p>Then governance and markets.</p><p>Then international coordination.</p><p>Then software.</p><p>I expect this to take time, and that is fine. Complex systems are not understood in one article. They become understandable when we build the right layers in the right order.</p><p>That is what I want to do here.</p><p>I want to take the European power grid, one of the most critical systems around us, and make it more understandable from a developer&#8217;s point of view.</p><p>Not by pretending it is simple.</p><p>But by building the mental model step by step, from physics to software, until the grid starts to feel less like a black box and more like a system that can be understood.</p><div><hr></div><h2>All parts of the series:</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1ed5e11e-35b8-419c-a316-9e95898627f8&quot;,&quot;caption&quot;:&quot;Part 1: Electricity Before Infrastructure<br /><br />Before we talk about energy companies, markets, software, smart meters, substations, or regulation &#8212; we need to understand the physical system underneath. The grid is not just a business system for buying and selling energy. It is a large physical machine that must stay stable every second.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Electricity Is Not Water: A Better Mental Model for the Grid&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-15T15:10:24.614Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!sq_V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c80b9e9-5ddd-4f0c-a4cb-2cb485003afe_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/electricity-is-not-water-a-better&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:197871484,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;173acfd7-5678-4d1d-a0d3-385f9d7fd649&quot;,&quot;caption&quot;:&quot;Part 2: The structure and components of the grid.<br /><br />The power grid is often imagined as a large water system. For a software developer, the better mental model is not a pipeline. It is a graph.<br />Transmission lines are edges. Substations are nodes. Generators, storage systems, distribution grids, industrial consumers, cities, and interconnectors are attached to this graph at different points.<br /><br />&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Grid Is Not a Pipeline&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-29T15:42:22.475Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!h0bv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b765de-ab75-4122-88be-17cf19c0eed2_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:199757049,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p>If you are interested in how the European power grid works, from electricity and infrastructure to operations, governance, and software, subscribe to follow the series as it grows.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Rebuilt is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Rebuilding TLS, Part 4 - Certificates and Trust]]></title><description><![CDATA[How certificates close the man-in-the-middle gap]]></description><link>https://www.dmytrohuz.com/p/rebuilding-tls-part-4-certificates</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/rebuilding-tls-part-4-certificates</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 26 Apr 2026 20:24:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HYYb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HYYb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HYYb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HYYb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:325803,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/195558698?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HYYb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HYYb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Previous Article:</em></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;59aa73da-7378-48a3-ae45-d9dc744b972d&quot;,&quot;caption&quot;:&quot;Overview: Where we are and What Is Still Missing&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 3 &#8212; Building Our First Handshake&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-19T16:38:45.365Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Gn-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:194707545,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>A secure connection to the wrong server is still a broken connection.</p><p>That sentence looks strange at first. If traffic is encrypted, if nobody can read it, if nobody can modify it, then what is still missing?</p><p>The missing piece is identity.</p><p>In the previous parts of this series, we slowly moved from a plain TCP connection to something that started to look like a real secure channel. First, we added encryption. Then we added integrity. Then we stopped using fixed shared keys and introduced a handshake with X25519 and HKDF, so the client and server could derive fresh session keys for every connection.</p><p>That was a big step forward, but it was still not enough.</p><p>The client could now create a strong encrypted channel, but it still had no reliable way to know <strong>who</strong> it had created that channel with. It could be the real server. It could also be an attacker sitting in the middle, performing a separate key exchange with the client and another one with the real server.</p><p>In this post we will discuss why key exchange is not the same as authentication, why certificates exist, how certificate chains work, and how I added a simplified certificate-based authentication layer to my small TLS-like protocol.</p><p>By the end, the protocol will finally move from:</p><blockquote><p>&#8220;I have an encrypted channel with whoever answered.&#8221;</p></blockquote><p>to:</p><blockquote><p>&#8220;I have an encrypted channel with the server that proved its identity.&#8221;</p></blockquote><p>That is the moment where this toy protocol starts to feel much closer to real TLS.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><h2><strong>The Problem We Still Had After Key Exchange</strong></h2><p>At the end of Part 3, the protocol already had a real handshake.</p><p>The client and server exchanged ephemeral X25519 public keys. They used the resulting shared secret as input to HKDF. From that, they derived separate keys for client-to-server and server-to-client traffic. Then they used those keys to protect application data with AES-GCM.</p><p>That is already much better than hardcoding a shared key into both programs.</p><p>A hardcoded key has many problems. If someone gets it once, every connection using that key is compromised. If you want to rotate it, you need to update both sides. If two clients use the same key, one compromised client can affect other connections. It is simple for a demo, but it is not a good model for a real secure protocol.</p><p>X25519 and HKDF solved a different problem. They allowed the client and server to create fresh session keys for every connection without sending the actual secret over the network.</p><p>But there was still a hole.</p><p>The client received a public key from &#8220;the server,&#8221; but it had no way to know whether that public key really belonged to the server. The protocol could protect traffic after the handshake, but the handshake itself was not authenticated.</p><p>That means a man-in-the-middle could sit between the client and server and do this:</p><pre><code><code>Client  &lt;---- key exchange ----&gt;  Attacker  &lt;---- key exchange ----&gt;  Server</code></code></pre><p>From the client&#8217;s point of view, everything looks fine. It completed a key exchange and derived keys.</p><p>From the server&#8217;s point of view, everything also looks fine. It completed a key exchange and derived keys.</p><p>But the attacker is in the middle of both secure channels.</p><p>This is the uncomfortable lesson:</p><p>A secure key exchange with an unauthenticated peer can still give you a secure channel to the attacker.</p><p>That is why Part 4 exists.</p><div><hr></div><h2><strong>Encryption Is Not the Same as Trust</strong></h2><p>It is easy to mix these ideas together because HTTPS makes them feel like one thing.</p><p>When we open a website over HTTPS, we usually think:</p><p>The connection is encrypted, so it is secure.</p><p>But real TLS gives us several properties at the same time. Encryption is only one of them.</p><p>A useful way to separate the ideas is this:</p><pre><code><code>Encryption answers:
Can outsiders read the data?

Integrity answers:
Can outsiders modify the data without being detected?

Key exchange answers:
Can both sides create fresh session keys?

Authentication answers:
Do I know who is on the other side?</code></code></pre><p>Before Part 4, our protocol had the first three. It did not have the fourth.</p><p>That distinction matters because an attacker does not always need to break encryption. Sometimes the attacker only needs to become the endpoint that you encrypt to.</p><p>If the client encrypts data to the attacker&#8217;s key, the encryption still works. The math is not broken. AES-GCM still protects the records. HKDF still derives keys. X25519 still creates a shared secret.</p><p>The problem is not the cryptography. The problem is that the client trusted the wrong public key.</p><p>So the next question becomes simple:</p><p>How does the client know that the server&#8217;s handshake key belongs to the real server?</p><p>This is where certificates enter the story.</p><div><hr></div><h2><strong>What Certificates Actually Add</strong></h2><p>A certificate is not magic. It is also not just a random file that makes browsers happy.</p><p>At a practical level, a certificate connects an identity to a public key.</p><p>For example, a server certificate says something like:</p><pre><code><code>This public key belongs to this server identity.</code></code></pre><p>But the client should not just believe that statement because the server said so. Anyone can generate a key pair and create a file that claims to be <code>example.com</code>.</p><p>So certificates are signed.</p><p>That means another key, usually belonging to a Certificate Authority, signs the certificate data. The client can then verify the signature using the Certificate Authority&#8217;s public key.</p><p>In the real Web PKI, this usually forms a chain:</p><pre><code><code>Root CA
  signs
Intermediate CA
  signs
Server Certificate</code></code></pre><p>The root certificate is already trusted by the client&#8217;s system or browser. The server sends its certificate chain during the handshake. The client verifies each signature in the chain until it reaches a trusted root.</p><p>In my simplified implementation for Part 4, I use the same conceptual model:</p><pre><code><code>Root CA
  &#8595;
Intermediate CA
  &#8595;
Server Certificate</code></code></pre><p>The point is not to recreate all of Web PKI. The point is to make the trust chain visible.</p><p>The client does not trust the server certificate because the server sent it. The client trusts it because it can verify that the certificate was signed through a chain that ends at a trusted root.</p><p>Now the client has something it did not have before:</p><p>A public identity key that is connected to the server certificate and can be verified through a certificate chain.</p><p>But there is still one more important step.</p><div><hr></div><h2><strong>Why the Server Must Sign the Handshake</strong></h2><p>A certificate chain proves that a certificate is valid.</p><p>It does not automatically prove that the server currently speaking in this connection owns the private key for that certificate.</p><p>That difference is important.</p><p>If the server only sends a certificate chain, an attacker could potentially copy that public certificate chain and send it to the client. Public certificates are public. They are not secrets.</p><p>So the server must prove ownership of the corresponding private key.</p><p>In real TLS, this is done with a handshake signature. In TLS 1.3, the server signs data derived from the handshake transcript. This binds the server&#8217;s authenticated identity to the exact handshake that is happening now.</p><p>In my simplified Part 4 implementation, I use the same core idea, but in a smaller form.</p><p>The server has two different types of keys:</p><pre><code><code>Long-term identity key
= connected to the server certificate

Ephemeral X25519 key
= used only for this connection&#8217;s key exchange</code></code></pre><p>The server sends its ephemeral X25519 public key, its certificate chain, and a signature over the handshake data.</p><p>The client verifies three things:</p><pre><code><code>1. Is the certificate chain valid?

2. Does the server certificate contain the expected identity key?

3. Did the server sign this handshake using the private key that matches the certificate?</code></code></pre><p>This is the key conceptual step.</p><p>The ephemeral X25519 key gives us fresh session keys. The certificate gives us identity. The signature connects both worlds together.</p><p>Without that signature, the certificate and the key exchange are two separate facts.</p><p>With the signature, the server says:</p><p>I own the private key for this certificate, and I am binding that identity to this ephemeral key exchange.</p><p>That is what stops the man-in-the-middle from silently replacing the handshake key.</p><div><hr></div><h2><strong>The Architecture of Part 4</strong></h2><p>After Part 4, the handshake looks roughly like this:</p><pre><code><code>Client
  |
  |  ClientHello
  |  ephemeral X25519 public key
  |
  v
Server
  |
  |  ServerHello
  |  ephemeral X25519 public key
  |  certificate chain
  |  handshake signature
  |
  v
Client</code></code></pre><p>Then both sides derive the shared secret using X25519:</p><pre><code><code>client private key + server public key
server private key + client public key</code></code></pre><p>Both sides arrive at the same shared secret without sending that secret over the network.</p><p>Then HKDF turns that shared secret into actual session keys.</p><p>Then AES-GCM protects the application records.</p><p>The important change is that the client no longer accepts the server&#8217;s ephemeral public key blindly. It checks whether the authenticated server signed the handshake.</p><p>The complete shape now looks like this:</p><pre><code><code>Certificate chain
  proves server identity

Handshake signature
  binds server identity to the ephemeral key exchange

X25519
  creates a fresh shared secret

HKDF
  derives directional session keys

AES-GCM
  protects application records</code></code></pre><p>This is still a simplified protocol, but the main pieces now line up with the real TLS story much better.</p><div><hr></div><h2><strong>What We Built in Code</strong></h2><p>For this part, I added a small certificate infrastructure to the project.</p><p>The implementation generates a simple hierarchy:</p><pre><code><code>Root CA
Intermediate CA
Server Certificate</code></code></pre><p>The server uses the server certificate and private key as its long-term identity. During the handshake, it still creates a fresh ephemeral X25519 key pair for the current connection.</p><p>That distinction matters.</p><p>The long-term certificate key is not used to encrypt application data. It is used to prove identity.</p><p>The ephemeral X25519 key is not used as identity. It is used to create a fresh shared secret for this connection.</p><p>This separation is one of the most important design ideas in modern TLS. Long-term keys authenticate. Ephemeral keys protect the session.</p><p>The simplified Part 4 flow is:</p><pre><code><code>1. Client creates an ephemeral X25519 key pair.

2. Client sends its public key.

3. Server creates an ephemeral X25519 key pair.

4. Server sends:
   - its ephemeral public key
   - certificate chain
   - signature over handshake data

5. Client verifies the certificate chain.

6. Client verifies the handshake signature.

7. Both sides derive the shared secret with X25519.

8. Both sides derive session keys with HKDF.

9. Application data is protected with AES-GCM.</code></code></pre><p>The full code is in the GitHub repository:</p><p><a href="https://github.com/DmytroHuzz/rebuilding_tls">https://github.com/DmytroHuzz/rebuilding_tls</a></p><p>The full technical walkthrough is here:</p><p><a href="https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html">https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html</a></p><p>I intentionally keep the article focused on the protocol idea rather than pasting the whole implementation here. Long code blocks inside an article often create an illusion of depth, but they usually make the article harder to read.</p><p>The repository is the right place for the full implementation. The article is the right place for the explanation.</p><div><hr></div><h2><strong>What This Still Is Not</strong></h2><p>This is not real TLS.</p><p>That sentence is important.</p><p>This project is an educational reconstruction of some core TLS ideas. It is not a library. It is not a production protocol. It is not something that should protect real traffic.</p><p>Real TLS has many more details and much stronger guarantees around the handshake. For example, real TLS 1.3 binds the handshake with a transcript hash. It supports negotiation, extensions, certificate validation rules, revocation mechanisms, session resumption, alerts, many edge cases, and years of hardening against attacks that are easy to miss when building a small protocol.</p><p>My version is intentionally smaller.</p><p>It is useful because it makes the main ideas visible:</p><pre><code><code>Why encryption alone is not enough.

Why integrity must be added.

Why fixed keys are weak.

Why key exchange gives fresh session keys.

Why key exchange is still not authentication.

Why certificates exist.

Why the server must sign the handshake.

Why TLS has the shape it has.</code></code></pre><p>That is the real goal of this series.</p><p>Not to replace TLS, but to make TLS less mysterious.</p><h2><strong>Try It Yourself</strong></h2><p>The project is public and runnable.</p><p>The main repository is here:</p><p><a href="https://github.com/DmytroHuzz/rebuilding_tls">https://github.com/DmytroHuzz/rebuilding_tls</a></p><p>The Part 4 walkthrough is here:</p><p><a href="https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html">https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html</a></p><p>The complete series landing page is here:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d90ba029-d3a4-4d48-8461-304373219921&quot;,&quot;caption&quot;:&quot;A year ago I wrote a series of articles about how a web server works.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS From Scratch &#8212; My Complete Learning Journey&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-27T21:39:41.947Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!59qZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:192355388,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:1,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>If you want to understand the path from the beginning, I recommend reading the series in order. Each part fixes one problem and reveals the next one.</p><p>Part 1 starts with encryption. Part 2 adds integrity. Part 3 adds key exchange and session keys. Part 4 adds authentication through certificates and a handshake signature.</p><p>That sequence matters because it shows why TLS is not just &#8220;encryption.&#8221; It is a stack of answers to different problems.</p><div><hr></div><h2><strong>Summary</strong></h2><p>Before Part 4, the protocol could create an encrypted channel, but it could not prove who was on the other side.</p><p>That is a serious problem.</p><p>Encryption protects data from being read. Integrity protects data from being modified. Key exchange creates fresh session keys. But authentication tells the client whether it is talking to the real server or to an attacker in the middle.</p><p>In Part 4, I added that missing authentication layer.</p><p>The client now verifies a certificate chain and checks a handshake signature. The server uses a long-term identity key to authenticate itself, while still using an ephemeral X25519 key for the actual key exchange. HKDF derives session keys, and AES-GCM protects application data.</p><p>The result is still not real TLS, but it now has the core shape:</p><pre><code><code>authenticated handshake
fresh session keys
protected records</code></code></pre><p>And that is the point of this whole series.</p><p>TLS is hard to understand when you only look at the final protocol. There are too many details, too many names, too many moving parts.</p><p>But when you rebuild it step by step, each piece starts to make sense.</p><p>You first feel the problem.</p><p>Then you add the missing mechanism.</p><p>Then you see why the real protocol looks the way it does.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Rebuilt is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Not sure where to begin? Start here!]]></title><description><![CDATA[Rebuild foundational technologies from first principles &#8212; with code, diagrams, and step-by-step explanations.]]></description><link>https://www.dmytrohuz.com/p/not-sure-where-to-begin-start-here</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/not-sure-where-to-begin-start-here</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Thu, 23 Apr 2026 20:19:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QnbP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QnbP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QnbP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QnbP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg" width="1456" height="485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:485,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:200298,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/195278928?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QnbP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QnbP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652866a1-edf5-4fe5-acd8-6e59238da047_2172x724.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>(almost) Everything here is organized into series. Each series takes one foundational technology &#8212; something you use every day &#8212; and rebuilds it from scratch. Pick the topic that makes you most curious and start at Part 1.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h3>&#127760;<em> Want to finally understand how Web Servers work?</em></h3><p><strong>&#8594; Start with: <a href="https://dev.to/dmytro_huz/building-your-own-web-server-part-1-theory-and-foundations-3kgo">Building Your Own Web Server</a></strong><br>You&#8217;ll go from a naive single-threaded server to a non-blocking async design &#8212; and understand exactly why modern servers work the way they do.</p><h3>&#128272;<em> Interested in security?</em></h3><p><strong>&#8594; Cryptography series:</strong></p><p>We are rebuilding the whole evolution: historical, logical and system of cryptography of ideas and technologies behind modern security. From Byte Manipulation to Block ciphers. </p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;78c82810-fa56-4265-909f-bed1790a69d2&quot;,&quot;caption&quot;:&quot;Why this project exists - and why it might matter to you&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding Cryptography From Scratch - My Complete Learning Journey (All Parts Inside)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-01T19:09:39.536Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!SRa_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8a9697d-5837-4c57-947c-4cf941c3bc3d_1024x608.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-cryptography-from-scratch&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:180433391,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>&#8594; </strong>Rebuilding TLS From Scratch </p><p>The TLS series deliberately starts with broken, naive versions and fixes them step by step &#8212; don&#8217;t skip to the end. Each series in the security path builds on the previous one.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;2f8d5ad3-af4b-49bd-b121-800150f58ca2&quot;,&quot;caption&quot;:&quot;A year ago I wrote a series of articles about how a web server works.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS From Scratch &#8212; My Complete Learning Journey&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-27T21:39:41.947Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!59qZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:192355388,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:1,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><h3>&#9201;&#65039;<em> Curious about systems internals?</em></h3><p><strong>&#8594; A Practical Guide to Time for Developers</strong><br>Time for computers is not the same as time for people. And not knowing the difference can lead to painful bugs. In this series, I break down the topic step by step - from time zones and timestamps to clock drift, Linux clocks, NTP, and PTP.</p><p>Start here if you&#8217;ve ever been bitten by timezone bugs, strange timestamps, or clock drift.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;729606f2-5a5b-450e-bd88-b0aebd8ae7b7&quot;,&quot;caption&quot;:&quot;Time looks simple until you have to trust it.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers &#8212; The Complete Series&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-19T21:05:34.894Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!mg5A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-746&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:191519746,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p><em><strong>Did not find anything relevant? Jump into Archive! It is much more there:</strong></em></p><p><strong>&#8594; &#128218; <a href="https://www.dmytrohuz.com/archive">Archive</a></strong></p><div><hr></div><p>Every article has a runnable code companion &#8212; a Python script or Colab notebook. Links are inside each post. And everything is shared on my <strong>Github account</strong>:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://github.com/DmytroHuzz&quot;,&quot;text&quot;:&quot;GitHub&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://github.com/DmytroHuzz"><span>GitHub</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe &#8212; it&#8217;s free.</strong> Get new articles in your inbox. No spam. No paywalls. Core technologies, rebuilt from scratch &#8212; straight to you.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Rebuilding TLS, Part 3 — Building Our First Handshake]]></title><description><![CDATA[We get rid of the pre-shared key assumption, build a simple key exchange handshake, and discover why key agreement alone still does not give us real TLS.]]></description><link>https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 19 Apr 2026 16:38:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Gn-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gn-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gn-u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gn-u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg" width="1456" height="569" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:569,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:250820,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/194707545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Gn-u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Gn-u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Previous Article:</em></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a3b907b8-ab7a-4b77-a780-e20636f0cc84&quot;,&quot;caption&quot;:&quot;In the first part of this series, we built our first fake secure channel:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 2 &#8212; Adding Integrity to the Channel&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-05T21:40:43.808Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!78kv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:193281237,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2><strong>Overview: Where we are and What Is Still Missing</strong></h2><p>In the previous part of this series, we made our fake secure channel much less fake.</p><p>We started with the broken encrypted transport from <a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption">Part 1</a>, added integrity with HMAC, <a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity">added sequence numbers to make the record layer less naive, and then moved to AEAD</a> &#8212; the approach modern systems usually use to protect records.</p><p>At that point, our protocol could already do something meaningful:</p><ul><li><p>encrypt application data</p></li><li><p>detect tampering</p></li><li><p>reject modified records</p></li><li><p>keep some minimal record-layer state</p></li></ul><p>That was a real step forward.</p><p>But it still relied on one very unrealistic assumption:</p><p><strong>both sides already shared the secret keys</strong></p><p>And that is exactly what we need to remove now.</p><p>Because a real secure protocol cannot stop at protecting data after the keys already exist. It also has to answer one of the harder questions first:</p><p><strong>if client and server do not already share a secret, how can they create one over an insecure network in the first place?</strong></p><p>That is the goal of this part.</p><p>We are going to build the next missing layer of the protocol: the handshake.</p><p>The architecture of this step is simple:</p><pre><code><code>Client                           Server
------                           ------
Handshake messages  &lt;---------&gt;  Handshake messages
       |                               |
       v                               v
  shared secret                  shared secret
       |                               |
       +---------&gt; HKDF &lt;--------------+
                    |
                    v
              session keys
                    |
                    v
         protected application data
</code></code></pre><p>The idea is to let the connection create fresh key material dynamically instead of starting with a hardcoded application key.</p><p>We will implement that in three steps.</p><p>First, we will build a handshake with classic Diffie-Hellman, where the shared prime and base are still explicit and visible in the protocol. Then we will replace that version with X25519 to show how modern protocols simplify the same idea. After that, we will use HKDF to derive proper session keys from the raw shared secret.</p><p>That will take us one big step closer to the shape of real TLS.</p><p>But still not all the way.</p><p>Because even if both sides manage to derive the same fresh session keys, one critical problem will remain: they still do not know who is on the other side.</p><p>And that is where this part is heading.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2><strong>A Very Short Note on Public Key Exchange</strong></h2><p>The basic idea of public key exchange is simple.</p><p>Two sides communicate over an insecure network. They exchange some public information. And from that exchange, both sides derive the same shared secret &#8212; without ever sending that secret directly over the wire.</p><p>That is the key point.</p><p>The network can be fully visible.</p><p>An observer can see all handshake messages.</p><p>But the observer still should not be able to derive the same secret.</p><p>That is exactly the kind of mechanism we need now.</p><p>Until this point in the series, our protocol always started with a secret that already existed. Public key exchange changes that. It gives the connection a way to create fresh shared key material dynamically.</p><p>In this article, I do not want to go deep into the mathematics behind it. I only want to use the core idea as the next building block of the protocol.</p><p>If you want the deeper intuition behind why this works, I already wrote about it here:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;dea52c91-5c61-4a96-88ff-8813525b2584&quot;,&quot;caption&quot;:&quot;I started my deep dive into cryptography six months ago. I wanted to deconstruct its internals into basic building blocks and then build them back up again. One simple idea kept pulling me forward&#8212;fascinating me and motivating me to go deeper: how can a crowd of absolute strangers&#8212;over the internet, an inherently insecure medium&#8212;exchange information sec&#8230;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Aha-Moment of Public-Key Encryption&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-13T12:29:49.434Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!uy8G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/the-aha-moment-of-public-key-encryption&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187849238,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>For now, the main idea we need is this:</p><ul><li><p>each side contributes its own private value</p></li><li><p>both sides exchange some public values</p></li><li><p>both sides derive the same shared secret</p></li><li><p>that secret can then become the basis for session keys</p></li></ul><p>So let&#8217;s build that first in the most explicit way, with classic Diffie-Hellman where the shared public parameters are still visible in the handshake.</p><h2><strong>Implementation Part 1 &#8212; Our First Handshake with Classic Diffie-Hellman</strong></h2><p>(The whole code can be find here: <a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3/v1_classic_dh_handshake">https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3/v1_classic_dh_handshake</a> )</p><p>Now let&#8217;s build the first real handshake in the series.</p><p>I want to start with classic Diffie-Hellman, not because this is the final form we want to keep, but because it makes the mechanics of key exchange much more visible.</p><p>In this version, both sides work with the same public parameters:</p><ul><li><p>a prime p</p></li><li><p>a generator g</p></li></ul><p>These values are not secret. In our implementation, the client sends them in the handshake, which makes the whole mechanism more explicit on the wire. That is exactly what I want at this stage. Before we hide the details behind a cleaner modern primitive, I want to make the structure fully visible.</p><p>The actual secret material comes from somewhere else:</p><ul><li><p>the client chooses a private exponent a</p></li><li><p>the server chooses a private exponent b</p></li></ul><p>From those private values, both sides compute public values:</p><ul><li><p>the client computes A = g^a mod p</p></li><li><p>the server computes B = g^b mod p</p></li></ul><p>Then they exchange A and B.</p><p>And this is the key step:</p><ul><li><p>the client computes s = B^a mod p</p></li><li><p>the server computes s = A^b mod p</p></li></ul><p>Both sides end up with the same shared secret, without ever sending that secret directly over the network.</p><p>In diagram form, the handshake looks like this:</p><pre><code><code>Client                                        Server
------                                        ------
choose private a
compute A = g^a mod p

ClientHello(p, g, A)      ---------&gt;

                                              choose private b
                                              compute B = g^b mod p

                          &lt;---------          ServerHello(B)

compute s = B^a mod p                           compute s = A^b mod p
</code></code></pre><p>That is our first real handshake.</p><p>Until now, the protocol always started with a secret key that already existed.</p><p>Now the connection itself creates the secret.</p><p>That is a major shift.</p><h3><strong>The raw Diffie-Hellman math</strong></h3><p>At the lowest level, the core operations are very small. That is one of the nice things about starting with classic Diffie-Hellman: the whole idea is still visible in a few functions.</p><pre><code><code>
# RFC 3526 Group 14: 2048-bit MODP prime
DH_PRIME = int(
    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
    "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
    "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
    "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
    "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
    "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
    "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
    "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
    "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
    "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
    "15728E5A8AACAA68FFFFFFFFFFFFFFFF",
    16,
)

DH_GENERATOR = 2

def generate_private_exponent() -&gt; int:
    return int.from_bytes(os.urandom(32), "big")

def compute_public_value(private: int, g: int, p: int) -&gt; int:
    return pow(g, private, p)

def compute_shared_secret(peer_public: int, private: int, p: int) -&gt; int:
    return pow(peer_public, private, p)
</code></code></pre><p>This is the whole core idea in code:</p><ul><li><p>private exponent stays local</p></li><li><p>public value goes on the wire</p></li><li><p>shared secret is derived independently on both sides</p></li></ul><p>That is the heart of Diffie-Hellman.</p><h3><strong>Client side</strong></h3><pre><code><code>def client_handshake(sock) -&gt; bytes:
    """Perform the client side of the classic DH handshake.

    The client picks the public parameters (p, g) and sends them to the
    server along with its own public DH value.  The server uses those
    parameters to compute its own public value and sends it back.

    Returns the shared secret as bytes.
    """
    # The client chooses p and g.  These are PUBLIC &#8212; not secret.
    # Anyone on the wire can see them, and that is perfectly fine.
    # The security of DH depends on the hardness of the discrete
    # logarithm problem, not on hiding p and g.
    p = DH_PRIME
    g = DH_GENERATOR

    print(f"  Public parameters (chosen by client, sent to server):")
    print(f"    p = {str(p)[:40]}... ({p.bit_length()} bits)")
    print(f"    g = {g}")

    # Step 1: Generate client's private exponent and public value.
    # The private exponent is the ONE thing that stays secret.
    client_private = generate_private_exponent()
    client_public = compute_public_value(client_private, g, p)
    client_public_bytes = int_to_bytes(client_public)

    # Step 2: Send ClientHello with p, g, and our public value.
    # All three are public.  The private exponent is NOT included.
    p_bytes = int_to_bytes(p)
    g_bytes = int_to_bytes(g)

    client_hello = encode_message(
        [
            (TAG_DH_P, p_bytes),
            (TAG_DH_G, g_bytes),
            (TAG_DH_PUBLIC, client_public_bytes),
        ]
    )
    # Step 3: send p, g, and the client&#8217;s public value inside ClientHello
    send_record(sock, client_hello)

    # Step 4: Receive ServerHello with the server's public value.
    server_hello_raw = recv_record(sock)
    fields = decode_message(server_hello_raw)
    server_public_bytes = None
    for tag, value in fields:
        if tag == TAG_DH_PUBLIC:
            server_public_bytes = value
    if server_public_bytes is None:
        raise ValueError("ServerHello missing DH public value")

    server_public = bytes_to_int(server_public_bytes)
    print(f"  &lt;- Received ServerHello")
    print(f"  Server public value B:   {hex_preview(server_public_bytes)}")

    # Step 5: Compute the shared secret.
    # shared = B^a mod p = (g^b)^a mod p = g^(ab) mod p
    shared_int = compute_shared_secret(server_public, client_private, p)
    shared_bytes = int_to_bytes(shared_int)

    return shared_bytes
</code></code></pre><p>On the client side, the flow is:</p><ol><li><p>choose a private exponent</p></li><li><p>compute the public value</p></li><li><p>send p, g, and the client&#8217;s public value inside ClientHello</p></li><li><p>receive the server&#8217;s public value</p></li><li><p>derive the shared secret</p></li></ol><p>That is the first point in the series where the client does not begin with the application key. It participates in creating it.</p><h3><strong>Server side</strong></h3><pre><code><code>def server_handshake(sock) -&gt; bytes:
    """Perform the server side of the classic DH handshake.

    The server receives p, g, and client_public from the ClientHello,
    uses those parameters to generate its own keypair, and sends its
    public value back.

    Returns the shared secret as bytes.
    """
    # Step 1: Receive ClientHello &#8212; parse p, g, and client's public value.
    # The server does NOT assume any particular p or g.  It uses whatever
    # the client proposes.  (In a production system, the server would
    # validate that p is a safe prime and g is a proper generator.
    # We skip that here for clarity.)
    client_hello_raw = recv_record(sock)
    fields = decode_message(client_hello_raw)

    p_bytes = None
    g_bytes = None
    client_public_bytes = None
    for tag, value in fields:
        if tag == TAG_DH_P:
            p_bytes = value
        elif tag == TAG_DH_G:
            g_bytes = value
        elif tag == TAG_DH_PUBLIC:
            client_public_bytes = value

    if p_bytes is None:
        raise ValueError("ClientHello missing DH prime (p)")
    if g_bytes is None:
        raise ValueError("ClientHello missing DH generator (g)")
    if client_public_bytes is None:
        raise ValueError("ClientHello missing DH public value (A)")

    # Deserialize the parameters from bytes.
    p = bytes_to_int(p_bytes)
    g = bytes_to_int(g_bytes)
    client_public = bytes_to_int(client_public_bytes)

    # Step 2: Generate server's private exponent and public value
    # using the p and g received from the client.
    server_private = generate_private_exponent()
    
    # Step 3: Compute server's public value
    server_public = compute_public_value(server_private, g, p)
    server_public_bytes = int_to_bytes(server_public)

    # Step 4: Send ServerHello with our public value.
    # Only B is sent &#8212; p and g are already known from the ClientHello.
    server_hello = encode_message(
        [
            (TAG_DH_PUBLIC, server_public_bytes),
        ]
    )
    send_record(sock, server_hello)

    # Step 5: Compute the shared secret.
    # shared = A^b mod p = (g^a)^b mod p = g^(ab) mod p
    shared_int = compute_shared_secret(client_public, server_private, p)
    shared_bytes = int_to_bytes(shared_int)

    return shared_bytes

</code></code></pre><p>The server does the mirror image:</p><ol><li><p>receive p, g, and the client&#8217;s public value</p></li><li><p>choose its own private exponent</p></li><li><p>compute its own public value</p></li><li><p>send that value back in ServerHello</p></li><li><p>derive the same shared secret from the client&#8217;s public value</p></li></ol><p>So at the end of the handshake, both sides have the same secret &#8212; but that secret was never transmitted directly.</p><p>That is the big win.</p><p>After this step, the connection can create fresh shared key material dynamically.</p><p>That is a much more realistic foundation.</p><p>But it is also still awkward.</p><p>Not conceptually awkward &#8212; educationally this version is very useful &#8212; but operationally awkward. We now have explicit p and g in the handshake, which is nice for understanding the mechanism, but clunky for a modern protocol design.</p><p>That is exactly why the next step will replace this version with X25519.</p><h2><strong>Implementation Part 2 &#8212; Simplifying the Handshake with X25519</strong></h2><p>(The whole code can be find here: https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3/v2_x25519_handshake )</p><p>The classic Diffie-Hellman version was useful because it made the mechanics of the handshake fully visible.</p><p>But it also makes something else visible:</p><p>it is a bit clunky.</p><p>Not conceptually clunky &#8212; educationally it is great &#8212; but operationally clunky. There are more moving parts in the handshake, more explicit protocol fields, and more visible math than modern protocols usually want to expose directly.</p><p>So now we keep the same core idea and simplify the workflow.</p><p>That is where <strong>X25519</strong> comes in.</p><p>The conceptual goal stays exactly the same:</p><ul><li><p>both sides generate ephemeral private/public key pairs</p></li><li><p>both sides exchange public keys</p></li><li><p>both sides derive the same shared secret</p></li><li><p>that secret will later become the basis for session keys</p></li></ul><p>What changes is the <em>shape</em> of the handshake.</p><p>We no longer need to carry an explicit prime and generator through the protocol. We no longer manually perform modular exponentiation with visible p and g. X25519 gives us the same public-key exchange idea in a much cleaner modern form.</p><p>That is why I wanted this section right after the classic DH version.</p><p>Classic DH makes the mechanism visible.</p><p>X25519 shows what the modern streamlined version looks like.</p><h3><strong>Client-side handshake structure</strong></h3><p>Here is the current client handshake implementation:</p><pre><code><code>def client_handshake(sock) -&gt; bytes:
    """Perform the client side of the X25519 handshake.

    Returns the 32-byte shared secret.
    """
    print("\\n[handshake] Client: starting X25519 handshake")

    # Step 1: Generate an ephemeral X25519 keypair.
    # "Ephemeral" means we create a fresh keypair for this session only.
    # The private key never leaves this process and is discarded after use.
    client_private = X25519PrivateKey.generate()
    client_public = client_private.public_key()
    client_public_bytes = client_public.public_bytes(Encoding.Raw, PublicFormat.Raw)

    # Step 2: Send ClientHello with our public key.
    client_hello = encode_message(
        [
            (TAG_X25519_PUBLIC, client_public_bytes),
        ]
    )
    send_record(sock, client_hello)

    # Step 3: Receive ServerHello with the server's public key.
    server_hello_raw = recv_record(sock)
    fields = decode_message(server_hello_raw)
    server_public_bytes = None
    for tag, value in fields:
        if tag == TAG_X25519_PUBLIC:
            server_public_bytes = value
    if server_public_bytes is None:
        raise ValueError("ServerHello missing X25519 public key")

    # Deserialize the server's public key from raw bytes.
    server_public = X25519PublicKey.from_public_bytes(server_public_bytes)

    # Step 4: Compute the shared secret.
    # X25519(client_private, server_public) = X25519(server_private, client_public)
    # This is the elliptic-curve equivalent of g^(ab) mod p from v1.
    shared_secret = client_private.exchange(server_public)

    return shared_secret
</code></code></pre><p>I like this version because it makes the transition very clear.</p><p>The client code no longer has to think about p and g at all. It just performs the handshake, gets the shared secret, and prints it. That is exactly the point of this stage in the series: the workflow becomes smaller, but the underlying purpose stays the same.</p><h3><strong>What changed conceptually</strong></h3><p>Compared to the classic DH version, the protocol has become simpler in three important ways.</p><h3><strong>1. No explicit shared public parameters in the handshake</strong></h3><p>In the previous version, the client sent the prime and generator so the whole structure of classic Diffie-Hellman stayed visible.</p><p>Now that goes away.</p><p>X25519 already gives us a fixed, standard structure for the exchange, so the handshake only needs to carry the public key material.</p><p>That makes the protocol smaller and cleaner.</p><h3><strong>2. The public values are much more compact</strong></h3><p>In the classic DH version, the public values were tied to a large prime-field construction and looked much heavier in the protocol.</p><p>In this version, the public keys are just 32 bytes.</p><p>That is a huge practical simplification.</p><h3><strong>3. The code starts to look more like real modern protocol code</strong></h3><p>This line from the comments says it well:</p><blockquote><p>generate(), exchange(), done.</p></blockquote><p>That is exactly the feeling this section should create.</p><p>We are still doing public-key exchange.</p><p>We are still deriving a shared secret.</p><p>But the implementation shape is now much closer to what modern systems actually use.</p><h3><strong>What this version still does not solve</strong></h3><p>Even after switching to X25519, this version is still simplified:</p><ul><li><p>there is still <strong>no authentication</strong></p></li><li><p>the shared secret is <strong>not yet turned into session keys</strong></p></li><li><p>there is still <strong>no record-layer encryption using the new keys</strong></p></li></ul><p>In the next step, we will add <strong>HKDF</strong> and derive proper working session keys from it.</p><p>That is where the handshake starts to connect back to the record protection we built earlier.</p><h2><strong>Implementation Part 3 &#8212; Deriving Session Keys with HKDF</strong></h2><p>(The whole code can be find here: https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3/v3_hkdf_session_keys)</p><p>At this point, both the classic Diffie-Hellman version and the X25519 version give us the same kind of output:</p><p>a shared secret that both sides can compute independently.</p><p>That is already a big step forward compared to the pre-shared-key model from the previous parts. The connection can now create fresh key material dynamically instead of starting with one hardcoded application key.</p><p>But there is still one important design question left:</p><p><strong>should we use that raw shared secret directly as the application key?</strong></p><p>For a toy demo, we probably could.</p><p>But even here, that would be the wrong direction.</p><p>Because a cleaner protocol separates these two ideas:</p><ul><li><p>the handshake creates a shared secret</p></li><li><p>the protocol derives working session keys from that secret</p></li></ul><p>That is exactly where <strong>HKDF</strong> comes in.</p><p>HKDF is a key-derivation function. Its job is not to invent secrecy out of nowhere, but to take existing secret material and turn it into keys that are better structured and easier to use safely inside the protocol.</p><p>So instead of treating the X25519 output as &#8220;the AES key,&#8221; we will use HKDF to derive proper session keys from it.</p><p>That already makes the protocol feel much closer to real TLS.</p><h3><strong>What changes conceptually</strong></h3><p>The structure now becomes:</p><pre><code><code>X25519 shared secret
        |
        v
      HKDF
        |
        v
  session key material
        |
        v
 protected application data
</code></code></pre><p>This is an important shift.</p><p>Before this step, the handshake produced something secret and we could have stopped there.</p><p>After this step, the handshake produces an <em>input</em> to a key schedule.</p><p>That is a much better protocol design.</p><h3><strong>Why this matters</strong></h3><p>There are two main reasons to do this.</p><h3><strong>1. The raw shared secret is handshake output, not final protocol state</strong></h3><p>The shared secret is the result of key exchange. That does not automatically mean it should be used directly as the application-data key.</p><p>Protocols usually want a cleaner boundary:</p><ul><li><p>handshake result first</p></li><li><p>working keys second</p></li></ul><h3><strong>2. We can derive keys for different purposes</strong></h3><p>Once we introduce a key-derivation step, we are no longer forced into &#8220;one secret for everything.&#8221;</p><p>Even in this toy protocol, that opens the door to a much more realistic design.</p><p>For example, instead of one single AEAD key, we can derive:</p><ul><li><p>client &#8594; server key</p></li><li><p>server &#8594; client key</p></li></ul><p>That is already much closer to how real secure protocols think.</p><div><hr></div><h3><strong>Deriving the keys</strong></h3><p>In the current implementation, HKDF takes the X25519 shared secret and stretches it into 64 bytes of key material.</p><p>Then that material is split into two 32-byte keys:</p><ul><li><p>one for traffic from client to server</p></li><li><p>one for traffic from server to client</p></li></ul><p>That gives us directional keys instead of one shared application key for both directions.</p><p>Here is the key schedule:</p><pre><code><code># key_schedule_x25519.py
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

def derive_session_keys(shared_secret: bytes) -&gt; tuple[bytes, bytes]:
    key_material = HKDF(
        algorithm=hashes.SHA256(),
        length=64,
        salt=None,
        info=b"toy-tls-part-3-x25519",
    ).derive(shared_secret)

    client_to_server_key = key_material[:32]
    server_to_client_key = key_material[32:]

    return client_to_server_key, server_to_client_key
</code></code></pre><p>I like this step a lot because it is small in code, but it changes the protocol mindset in an important way.</p><p>We are no longer thinking:</p><blockquote><p>handshake gives us the key</p></blockquote><p>We are now thinking:</p><blockquote><p>handshake gives us secret material, and the protocol derives the keys it actually wants to use</p></blockquote><p>That is a much stronger model.</p><h3><strong>A small but important detail</strong></h3><p>Notice that the two sides must interpret the derived keys consistently.</p><p>If the client treats the first 32 bytes as the client &#8594; server key, then the server must do the same. Otherwise the channel will immediately break.</p><p>So now the handshake is not only producing shared secret material. It is also establishing a shared rule for how that material becomes working traffic keys.</p><p>That is another reason protocols need structure, not just primitives.</p><div><hr></div><h2><strong>Connecting HKDF back to the record layer</strong></h2><p>Now we can finally connect this part back to what we built earlier.</p><p>In Part 2, we already built an AEAD-protected record layer. But that record layer still depended on hardcoded keys.</p><p>Now that changes.</p><p>The AEAD layer no longer starts with a static key from configuration.</p><p>It receives fresh traffic keys from the handshake.</p><p>So the protocol shape becomes:</p><pre><code><code>Handshake -&gt; X25519 shared secret -&gt; HKDF -&gt; directional session keys -&gt; AEAD protected records
</code></code></pre><p>That is a major milestone in the series.</p><p>At this point, the protocol no longer just looks secure because we wrapped some bytes in encryption. It now has a real high-level structure:</p><ul><li><p>first establish shared key material</p></li><li><p>then derive traffic keys</p></li><li><p>then use those keys to protect application data</p></li></ul><p>That is already much closer to the shape of real TLS.</p><div><hr></div><h2><strong>Using the new session keys</strong></h2><p>Once the keys are derived, the record layer can use them directly.</p><p>Conceptually, the flow now looks like this:</p><h3><strong>Client</strong></h3><pre><code><code>with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    print(f"Connected to {HOST}:{PORT}")

    # ==========================================
    # PHASE 1: HANDSHAKE
    # ==========================================
    # New in Part 3: the handshake dynamically establishes session keys.
    # No pre-shared secret needed.
    client_write_key, server_write_key = client_handshake(client)

    # ==========================================
    # PHASE 2: APPLICATION DATA
    # ==========================================
    # The record layer now uses HKDF-derived keys instead of hardcoded ones.
    # The record format is the same as Part 2 Stage 3 (AEAD).

    # --- Send request (encrypted with client_write_key) ---
    protected = protect_record(client_write_key, send_seq, request)
    send_record(client, protected)
    send_seq += 1

    # --- Receive response (decrypted with server_write_key) ---
    raw_response = recv_record(client)

    try:
        response = unprotect_record(server_write_key, recv_seq, raw_response)
        recv_seq += 1
        print(f"\\n  Decrypted response:\\n  {response.decode('utf-8')}")
    except Exception as e:
        print(f"\\n  *** REJECTED: {e} ***")

print("\\nDone.")
</code></code></pre><ul><li><p>use client_write_key to protect outgoing application data</p></li><li><p>use server_write_key to unprotect incoming application data</p></li></ul><h3><strong>Server</strong></h3><pre><code><code>with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((HOST, PORT))
    server.listen(1)
    print(f"Listening on {HOST}:{PORT}")

    conn, addr = server.accept()
    with conn:
        # ==========================================
        # PHASE 1: HANDSHAKE
        # ==========================================
        client_write_key, server_write_key = server_handshake(conn)

        # ==========================================
        # PHASE 2: APPLICATION DATA
        # ==========================================

        # --- Receive request (decrypted with client_write_key) ---
        raw_request = recv_record(conn)

        try:
            request = unprotect_record(client_write_key, recv_seq, raw_request)
            recv_seq += 1
        except Exception as e:
            print(f"\\n  *** REJECTED: {e} ***")
            print("  Connection closed &#8212; refusing to process invalid data.")
        else:

            # --- Send response (encrypted with server_write_key) ---
            response = (
                "HTTP/1.1 200 OK\\r\\n"
                "Content-Type: text/plain\\r\\n"
                "Content-Length: 13\\r\\n\\r\\n"
                "hello, client"
            ).encode("utf-8")

            protected = protect_record(server_write_key, send_seq, response)
            send_record(conn, protected)
            send_seq += 1

print("\\nDone.")

</code></code></pre><ul><li><p>use client_write_key to unprotect incoming client traffic</p></li><li><p>use server_write_key to protect outgoing server traffic</p></li></ul><p>That means the two directions are now separated.</p><p>This is cleaner than one symmetric application key shared blindly by both directions, and it makes the protocol feel more deliberate.</p><p>Even in this simplified version, that is a meaningful step.</p><div><hr></div><h2><strong>What this step really gave us</strong></h2><p>By adding HKDF, we improved the protocol in a way that is easy to underestimate.</p><p>We did not just &#8220;derive another key.&#8221;</p><p>We made the protocol architecture cleaner.</p><p>Now the handshake and the traffic layer are connected in a more principled way:</p><ul><li><p>the handshake creates shared secret material</p></li><li><p>the key schedule turns that material into working keys</p></li><li><p>the record layer consumes those keys</p></li></ul><p>This is a much better model than treating the raw X25519 result as the final answer.</p><p>And it brings us one step closer to real TLS, where key derivation is not an optional detail, but one of the central pieces of the protocol design.</p><div><hr></div><h2><strong>But we are still not secure</strong></h2><p>And now we arrive at the uncomfortable but necessary part.</p><p>Even with:</p><ul><li><p>a real handshake</p></li><li><p>X25519</p></li><li><p>HKDF</p></li><li><p>fresh directional session keys</p></li><li><p>AEAD-protected records</p></li></ul><p>the protocol still cannot be considered secure enough.</p><p>Why?</p><p>Because all of this still says nothing about <strong>who</strong> is on the other side.</p><p>The handshake can successfully create shared secrets.</p><p>HKDF can successfully derive traffic keys.</p><p>The record layer can successfully protect application data.</p><p>And an attacker can still sit in the middle and run two separate handshakes.</p><p>That is the next lesson.</p><div><hr></div><h2><strong>Still Not Secure &#8212; The Man-in-the-Middle Problem</strong></h2><p>At this point, our protocol already looks much more serious than the one we started with.</p><p>We now have:</p><ul><li><p>a real handshake</p></li><li><p>fresh shared secrets</p></li><li><p>X25519 instead of a pre-shared application key</p></li><li><p>HKDF-derived session keys</p></li><li><p>AEAD-protected application records</p></li></ul><p>That is a long way from the fake secure channel in Part 1.</p><p>But it is still not enough.</p><p>The missing piece is one of the most important ideas in this whole series:</p><p><strong>key exchange is not authentication</strong></p><p>That sentence is easy to read quickly and move on from. But it is worth stopping here, because this is exactly where many protocols fail.</p><p>Our handshake proves that both sides can derive the same shared secret.</p><p>What it does <strong>not</strong> prove is:</p><p><strong>who</strong> is actually on the other side.</p><p>And that difference is the whole problem.</p><h3><strong>The attack</strong></h3><p>Imagine an active attacker sitting between the client and the server.</p><p>Let&#8217;s call her Mallory.</p><p>The client thinks it is talking to the server.</p><p>The server thinks it is talking to the client.</p><p>But Mallory intercepts the handshake and replaces the exchanged public keys with her own.</p><p>In simplified form, the flow looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uLbh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uLbh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 424w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 848w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 1272w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uLbh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png" width="1456" height="1601" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1601,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:556263,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/194707545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uLbh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 424w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 848w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 1272w, https://substackcdn.com/image/fetch/$s_!uLbh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed534692-c29b-4fb3-8154-428e9f5cf001_2525x2776.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>And now something very important happens.</p><p>The handshake still &#8220;works.&#8221;</p><p>But it works in the wrong way.</p><ul><li><p>the <strong>client</strong> ends up with a shared secret with <strong>Mallory</strong></p></li><li><p>the <strong>server</strong> ends up with a different shared secret with <strong>Mallory</strong></p></li><li><p>and <strong>Mallory</strong> now has one valid secure channel to each side</p></li></ul><p>From the point of view of the client and the server, everything looks normal:</p><ul><li><p>key exchange succeeded</p></li><li><p>keys were derived</p></li><li><p>encrypted records verify correctly</p></li><li><p>AEAD tags are valid</p></li></ul><p>And yet the protocol has already failed.</p><p>Because Mallory can now:</p><ol><li><p>decrypt the client&#8217;s traffic</p></li><li><p>read it or modify it</p></li><li><p>re-encrypt it toward the server</p></li><li><p>receive the server&#8217;s response</p></li><li><p>read it or modify it</p></li><li><p>re-encrypt it back toward the client</p></li></ol><p>Neither side can detect this.</p><h3><strong>In The Next Article &#8212; Building the Certificate Infrastructure</strong></h3><p>The handshake only proves one thing:</p><blockquote><p>&#8220;I computed a shared secret with whoever sent me this public key.&#8221;</p></blockquote><p>It does <strong>not</strong> prove:</p><blockquote><p>&#8220;This public key came from the server I actually intended to talk to.&#8221;</p></blockquote><p>That is the missing half.</p><p>To fix this, the client needs a way to verify that the public key it receives during the handshake actually belongs to the server it wanted to talk to.</p><p>That is where the next layer enters:</p><ul><li><p>certificates</p></li><li><p>signatures</p></li><li><p>trust chains</p></li><li><p>certificate authorities</p></li></ul><p>In other words, this is where the protocol must stop proving only that &#8220;someone&#8221; is there and start proving <strong>who</strong> that someone is.</p><p>That is exactly what the next article will build.</p><h3>Summary</h3><p>Our protocol now has secrecy against passive observers.</p><p>It has integrity for protected records.</p><p>It has fresh session keys.</p><p>But it still does not have <strong>identity</strong>.</p><p>And without identity, a correct shared secret with the wrong party is still a protocol failure.</p><p>That is the deeper lesson of Part 3.</p><p>Part 1 taught us:</p><p><strong>confidentiality is not integrity</strong></p><p>Part 2 taught us:</p><p><strong>protecting records is not the same thing as establishing trust</strong></p><p>And now Part 3 adds the next lesson:</p><p><strong>key exchange is not authentication</strong></p><p>That we will solve in the next article!</p><h2><strong>Final Code</strong></h2><p>The full code for this part is available here:</p><p><strong>GitHub:</strong> <a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3">https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3</a></p><h2>Next article</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ed1112e7-0049-4193-bb7e-ec60ded31368&quot;,&quot;caption&quot;:&quot;Previous Article:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 4 - Certificates and Trust&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-26T20:24:36.572Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HYYb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-4-certificates&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:195558698,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Rebuilding TLS, Part 2 — Adding Integrity to the Channel]]></title><description><![CDATA[We teach our protocol to detect tampering, make records less naive with sequence numbers, and then switch to the AEAD style used in real systems.]]></description><link>https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 05 Apr 2026 21:40:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!78kv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!78kv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!78kv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!78kv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!78kv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!78kv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!78kv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3423615,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/193281237?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!78kv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!78kv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!78kv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!78kv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the first part of this series, we built our first fake secure channel:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;0e21ae64-25bc-4008-8f34-84996112a315&quot;,&quot;caption&quot;:&quot;A year ago I wrote a series about how a web server works.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 1 &#8212; Why Encryption Alone Is Not Enough&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-29T18:57:36.417Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!5phz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:192533658,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>We took a simple socket-based client and server, wrapped their communication in AES-CTR with a shared secret key, and got something that already looked much more serious than plain TCP. The traffic stopped being transparent. A passive observer could no longer read the request and response directly.</p><p>That was real progress.</p><p>But it still had a fatal flaw.</p><p>The receiver had no way to know whether the encrypted message had been changed on the way.</p><p>Encryption hid the bytes.</p><p>It did not protect their meaning.</p><p>So in this part, we will fix that.</p><p>We will first add a <strong>MAC</strong> so the receiver can detect tampering. Then we will make the record layer a little less naive by adding a sequence number. And after that, we will take one more step toward the real world and move to <strong>AEAD</strong>, because that is how modern secure protocols usually protect records.</p><p>We still will not have real TLS when we are done.</p><p>But we will have a much more serious record layer than the one from Part 1.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>What we will build in this part</h2><p>The plan for this article is simple:</p><ul><li><p>briefly introduce MACs</p></li><li><p>add HMAC to our encrypted record format</p></li><li><p>make tampering detectable</p></li><li><p>add a sequence number to each record</p></li><li><p>explain why sequence numbers matter</p></li><li><p>then move from our hand-built &#8220;encrypt + MAC&#8221; construction to AEAD, because that is the approach real systems usually use</p></li></ul><p>Just like in Part 1, I want to keep the pattern simple:</p><ul><li><p>explain the idea</p></li><li><p>show the code</p></li><li><p>explain what changed</p></li><li><p>explain what is still broken</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>Why encryption still was not enough</h2><p>At the end of Part 1, our protocol already had one real property:</p><ul><li><p>confidentiality against passive observers</p></li></ul><p>That mattered.</p><p>But it still failed against active attackers.</p><p>Because AES-CTR by itself does not provide integrity, an attacker could modify ciphertext and the receiver would still decrypt it and trust the result. That was the main lesson of the first article:</p><p><strong>confidentiality is not integrity</strong></p><p>So the next missing property is obvious.</p><p>The receiver needs a way to verify that the message arrived unchanged.</p><p>That is what a MAC gives us.</p><div><hr></div><h2>A very short note on MACs</h2><p>MAC stands for <strong>Message Authentication Code</strong>.</p><p>Very roughly, it is a cryptographic tag computed over a message using a secret key.</p><p>The sender computes the tag and sends it together with the message.</p><p>The receiver recomputes the tag and compares it with the one that was received.</p><p>If the tags match, the receiver can trust that:</p><ul><li><p>the message was not modified</p></li><li><p>and it was created by someone who knows the MAC key</p></li></ul><p>If the tags do not match, the message must be rejected.</p><p>In this article, we will use <strong>HMAC-SHA256</strong>.</p><blockquote><p>I do not want to go too deep into HMAC itself here, because the goal of this series is to understand TLS as a protocol. But if you want a deeper explanation of MACs and HMAC, I already wrote about them in my cryptography series, and I&#8217;ll link that here.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f1db8dab-a8e3-4f94-98bd-6e31e78a717d&quot;,&quot;caption&quot;:&quot;In the previous article, we did something slightly ridiculous.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Building Own MAC &#8212; Part 3: Reinventing HMAC from SHA-256&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-23T18:58:16.766Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!QAi6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/building-own-mac-part-3-reinventing&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:185566303,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div></blockquote><p>So for our purposes, the important idea is simple:</p><p><strong>encryption hides the message</strong></p><p><strong>MAC protects the message from silent modification</strong></p><p>That is the missing half we need.</p><div><hr></div><h2>Adding HMAC to the channel</h2><p>Let&#8217;s start by upgrading the record format from Part 1.</p><p>In Part 1, our protected payload was basically:</p><pre><code><code>nonce || ciphertext</code></code></pre><p>Now we will add a MAC tag:</p><pre><code><code>nonce || ciphertext || tag</code></code></pre><p>And the sender will compute the HMAC over:</p><pre><code><code>nonce || ciphertext</code></code></pre><p>So the full logic becomes:</p><h3>Sender</h3><ol><li><p>encrypt plaintext with AES-CTR</p></li><li><p>compute HMAC over <code>nonce || ciphertext</code></p></li><li><p>send <code>nonce || ciphertext || tag</code></p></li></ol><h3>Receiver</h3><ol><li><p>read <code>nonce || ciphertext || tag</code></p></li><li><p>recompute HMAC over <code>nonce || ciphertext</code></p></li><li><p>compare tags</p></li><li><p>only if they match, decrypt the ciphertext</p></li><li><p>otherwise reject the message</p></li></ol><p>There is one more small improvement I want to make here.</p><p>Instead of using one key for everything, we will already separate them:</p><ul><li><p>one key for encryption</p></li><li><p>one key for HMAC</p></li></ul><p>This is still a toy setup, but it is better design than reusing the same bytes for every cryptographic job.</p><h3>HMAC helpers</h3><pre><code><code>import os
import hmac
import hashlib

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# ---------------------------------------------------------------------------
# Keys &#8212; hardcoded for educational purposes.
# In a real protocol, these would be derived from a key exchange (e.g.,
# Diffie-Hellman), not embedded in source code.
# ---------------------------------------------------------------------------

# 32-byte (256-bit) key for AES-256-CTR encryption.
ENC_KEY = b"0123456789ABCDEF0123456789ABCDEF"

# 32-byte key for HMAC-SHA256.  Separate from the encryption key.
MAC_KEY = b"HMAC_KEY_FOR_PART2_DEMO_1234567"

# HMAC-SHA256 produces a 32-byte (256-bit) tag.
TAG_LEN = 32

# AES-CTR nonce is 16 bytes (128 bits).
NONCE_LEN = 16

def encrypt_then_mac(plaintext: bytes) -&gt; bytes:
    """Encrypt a plaintext and append an HMAC tag.

    Returns: nonce (16 B) || ciphertext (N B) || tag (32 B)
    """

    # Step 1: Generate a fresh random nonce for AES-CTR.
    # A new nonce MUST be used for every record &#8212; reusing a nonce with
    # the same key completely breaks CTR-mode security.
    nonce = os.urandom(NONCE_LEN)

    # Step 2: Encrypt the plaintext with AES-256-CTR.
    cipher = Cipher(algorithms.AES(ENC_KEY), modes.CTR(nonce))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()

    # Step 3: Compute HMAC-SHA256 over (nonce || ciphertext).
    # New in Part 2: we authenticate the encrypted record before sending it.
    # The HMAC input includes the nonce so an attacker cannot swap nonces
    # between records without detection.
    mac_input = nonce + ciphertext
    tag = hmac.new(MAC_KEY, mac_input, hashlib.sha256).digest()

    print(f"  [crypto_hmac] encrypt_then_mac:")
    print(f"    nonce    = {nonce.hex()[:32]}...")
    print(f"    ct_len   = {len(ciphertext)} bytes")
    print(f"    tag      = {tag.hex()[:32]}...")

    # Step 4: Assemble the wire format.
    return nonce + ciphertext + tag

def verify_then_decrypt(payload: bytes) -&gt; bytes:
    """Verify the HMAC tag, then decrypt if valid.

    Expects: nonce (16 B) || ciphertext (N B) || tag (32 B)
    Raises ValueError if the tag does not match.
    """

    # Step 1: Parse the record into its components.
    # The tag is always the last 32 bytes.  The nonce is the first 16.
    # Everything in between is ciphertext.
    if len(payload) &lt; NONCE_LEN + TAG_LEN:
        raise ValueError("Record too short to contain nonce + tag")

    nonce = payload[:NONCE_LEN]
    ciphertext = payload[NONCE_LEN:-TAG_LEN]
    received_tag = payload[-TAG_LEN:]

    # Step 2: Recompute the HMAC over (nonce || ciphertext).
    mac_input = nonce + ciphertext
    expected_tag = hmac.new(MAC_KEY, mac_input, hashlib.sha256).digest()

    # Step 3: Compare tags using constant-time comparison.
    # hmac.compare_digest() prevents timing side-channel attacks.
    # A naive `==` comparison can leak information about which byte
    # position differs first, allowing an attacker to forge a valid
    # tag byte by byte.
    if not hmac.compare_digest(received_tag, expected_tag):
        print("  [crypto_hmac] *** MAC VERIFICATION FAILED &#8212; record rejected ***")
        raise ValueError("HMAC verification failed &#8212; record has been tampered with")

    print("  [crypto_hmac] MAC verification: OK")

    # Step 4: Decrypt only after verification succeeds.
    # This is the key benefit of encrypt-then-MAC: we never process
    # unauthenticated ciphertext.
    cipher = Cipher(algorithms.AES(ENC_KEY), modes.CTR(nonce))
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    return plaintext</code></code></pre><p>This is the first big improvement over Part 1.</p><p>The important change is not just that we added a tag.</p><p>It is that the receiver no longer blindly trusts ciphertext and only then discovers what it means. Now the receiver first checks whether the record is authentic and unchanged.</p><p>That is a very different protocol posture.</p><div><hr></div><h2>Updating the client and server</h2><p>Now let&#8217;s plug this into the channel.</p><h3>HMAC-based client</h3><pre><code><code>import socket

from framing import send_record, recv_record
from crypto_hmac import encrypt_then_mac, verify_then_decrypt

HOST = "127.0.0.1"
PORT = 9001

# A toy HTTP-like request &#8212; same spirit as Part 1.
request = (
    "GET /transfer?to=bob&amp;amount=100 HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n"
).encode("utf-8")

print("=" * 60)
print("Part 2 &#8212; HMAC Client (encrypt-then-MAC)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    print(f"Connected to {HOST}:{PORT}")

    # ----- SEND REQUEST -----
    print("\\n--- Sending request ---")
    protected = encrypt_then_mac(request)
    send_record(client, protected)
    print(f"  Record sent ({len(protected)} bytes on wire)")

    # ----- RECEIVE RESPONSE -----
    print("\\n--- Receiving response ---")
    raw_response = recv_record(client)
    response = verify_then_decrypt(raw_response)
    print(f"\\n  Decrypted response:\\n  {response.decode('utf-8')}")

print("\\nDone.")</code></code></pre><h3>HMAC-based server</h3><pre><code><code>import socket

from framing import send_record, recv_record
from crypto_hmac import encrypt_then_mac, verify_then_decrypt

HOST = "127.0.0.1"
PORT = 9001

print("=" * 60)
print("Part 2 &#8212; HMAC Server (encrypt-then-MAC)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    # SO_REUSEADDR lets us restart the server immediately without waiting
    # for the OS to release the port from TIME_WAIT state.
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((HOST, PORT))
    server.listen(1)
    print(f"Listening on {HOST}:{PORT}")

    conn, addr = server.accept()
    with conn:
        print(f"Connected by {addr}")

        # ----- RECEIVE REQUEST -----
        print("\\n--- Receiving request ---")
        raw_request = recv_record(conn)

        try:
            request = verify_then_decrypt(raw_request)
        except ValueError as e:
            # New in Part 2: if the MAC fails, we reject the record loudly.
            # In Part 1 we had no way to detect tampering at all.
            print(f"\\n  *** REJECTED: {e} ***")
            print("  Connection closed &#8212; refusing to process tampered data.")
        else:
            print(f"\\n  Decrypted request:\\n  {request.decode('utf-8')}")

            # ----- SEND RESPONSE -----
            print("--- Sending response ---")
            response = (
                "HTTP/1.1 200 OK\\r\\n"
                "Content-Type: text/plain\\r\\n"
                "Content-Length: 13\\r\\n\\r\\n"
                "hello, client"
            ).encode("utf-8")

            protected = encrypt_then_mac(response)
            send_record(conn, protected)
            print(f"  Record sent ({len(protected)} bytes on wire)")

print("\\nDone.")</code></code></pre><p>The shape of the channel is still familiar.</p><p>That matters.</p><p>We did not replace the whole design.</p><p>We strengthened one missing property.</p><p>That is how protocol evolution should feel.</p><div><hr></div><h4>Let&#8217;s check it on the wire.</h4><p>We try to start the server and client, which we just created.</p><p>Here is our client request&#8217;s data.</p><pre><code><code>-- Sending request ---
[crypto_hmac] encrypt_then_mac:
nonce = 387f0065f8915133473597d8cef15f34...
ct_len = 61 bytes
tag = b7f0db369e5d2a221a18f2d167b5b3a8...
Record sent (109 bytes on wire)
</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JQ4z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JQ4z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 424w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 848w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JQ4z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:651773,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/193281237?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JQ4z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 424w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 848w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!JQ4z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bdddd6f-33a6-499c-b25f-e4d43ecbae2d_2880x1620.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p></p><h2>Detecting tampering</h2><p>Now let&#8217;s revisit the failure from Part 1.</p><p>Previously, if someone modified the ciphertext, the receiver would still decrypt it and accept modified plaintext.</p><p>Now that should no longer work.</p><p>Here is a tiny tampering demo:</p><pre><code><code># tampering_demo_hmac.py
from crypto_hmac import encrypt_then_mac, verify_then_decrypt

original = b"amount=100"
protected = encrypt_then_mac(original)

tampered = bytearray(protected)
tampered[20] ^= 0x08  # flip one bit somewhere in the encrypted body

try:
    result = verify_then_decrypt(bytes(tampered))
    print("Unexpected success:", result)
except ValueError as e:
    print("Tampering detected:", e)
</code></code></pre><p>Now the result should be rejection, not silent acceptance.</p><p>That is exactly what we wanted.</p><p>This is the moment where our channel stops being merely &#8220;encrypted&#8221; and starts being &#8220;protected.&#8221;</p><p>Because now the receiver does not just recover bytes. It verifies them first.</p><p>That is a serious step.</p><div><hr></div><h2>Why we also need a sequence number</h2><p>At this point, we fixed the big flaw from Part 1: silent tampering.</p><p>But the record layer is still naive.</p><p>Why?</p><p>Because even with a valid HMAC, the receiver still has no sense of record position or freshness.</p><p>Imagine an attacker records one valid protected message and sends it again later.</p><p>The HMAC is still valid.</p><p>The ciphertext is still valid.</p><p>And unless the receiver keeps some state, it may accept the same record again.</p><p>That means integrity alone is not the whole story.</p><p>We also need some sense of:</p><ul><li><p>order</p></li><li><p>position</p></li><li><p>repetition</p></li><li><p>replay</p></li></ul><p>This is where sequence numbers come in.</p><p>A sequence number is just a counter that increases with every record:</p><ul><li><p>first record = 0</p></li><li><p>next = 1</p></li><li><p>next = 2</p></li><li><p>and so on</p></li></ul><p>We then include that sequence number in the authenticated data, so the receiver does not just verify &#8220;these bytes were protected,&#8221; but also &#8220;these bytes belong in this position in the stream.&#8221;</p><p>That makes the record layer much less naive.</p><p>It still does not solve every replay problem in every possible system. But for our toy protocol, it is a very good next step.</p><div><hr></div><h2>Updating the record format</h2><p>Now our record becomes:</p><pre><code><code>seq || nonce || ciphertext || tag
</code></code></pre><p>And our HMAC input becomes:</p><pre><code><code>seq || nonce || ciphertext
</code></code></pre><p>So the sender and receiver now both need a little bit of state:</p><ul><li><p>the sender tracks the next sequence number to send</p></li><li><p>the receiver tracks the next sequence number it expects</p></li></ul><p>This is one of those moments where secure transport starts looking more like a real protocol and less like &#8220;some crypto around a socket.&#8221;</p><h3>Sequence-aware HMAC helper</h3><pre><code><code># crypto_hmac_seq.py
import os
import hmac
import hashlib
import struct

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# ---------------------------------------------------------------------------
# Keys &#8212; same as crypto_hmac.py, hardcoded for education.
# ---------------------------------------------------------------------------
ENC_KEY = b"0123456789ABCDEF0123456789ABCDEF"
MAC_KEY = b"HMAC_KEY_FOR_PART2_DEMO_1234567"

TAG_LEN = 32  # HMAC-SHA256 output: 32 bytes (256 bits)
NONCE_LEN = 16  # AES-CTR nonce: 16 bytes (128 bits)
SEQ_LEN = 8  # Sequence number: 8 bytes (64-bit unsigned integer)

def protect_record(seq: int, plaintext: bytes) -&gt; bytes:
    """Encrypt a plaintext record and attach a sequence-aware HMAC tag.

    Args:
        seq:       The current send-side sequence number (0, 1, 2, &#8230;).
        plaintext: The message to protect.

    Returns:
        seq (8 B) || nonce (16 B) || ciphertext (N B) || tag (32 B)
    """

    # Pack the sequence number as an 8-byte big-endian unsigned integer.
    # "!Q" = network byte order, unsigned 64-bit.
    seq_bytes = struct.pack("!Q", seq)

    # Generate a fresh AES-CTR nonce.
    nonce = os.urandom(NONCE_LEN)

    # Encrypt the plaintext.
    cipher = Cipher(algorithms.AES(ENC_KEY), modes.CTR(nonce))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()

    # Compute HMAC over (seq || nonce || ciphertext).
    # The sequence number is included in the MAC input so the integrity
    # check also covers record order/position in the stream.
    mac_input = seq_bytes + nonce + ciphertext
    tag = hmac.new(MAC_KEY, mac_input, hashlib.sha256).digest()

    return seq_bytes + nonce + ciphertext + tag

def verify_and_unprotect(expected_seq: int, payload: bytes) -&gt; bytes:
    """Verify the HMAC and sequence number, then decrypt.

    Args:
        expected_seq: The sequence number the receiver expects next.
        payload:      The raw bytes received: seq || nonce || ct || tag.

    Returns:
        The decrypted plaintext.

    Raises:
        ValueError if the MAC is invalid or the sequence number is wrong.
    """

    min_len = SEQ_LEN + NONCE_LEN + TAG_LEN
    if len(payload) &lt; min_len:
        raise ValueError("Record too short")

    # Step 1: Parse the record.
    seq_bytes = payload[:SEQ_LEN]
    nonce = payload[SEQ_LEN : SEQ_LEN + NONCE_LEN]
    ciphertext = payload[SEQ_LEN + NONCE_LEN : -TAG_LEN]
    received_tag = payload[-TAG_LEN:]

    # Step 2: Recompute HMAC over (seq || nonce || ciphertext).
    mac_input = seq_bytes + nonce + ciphertext
    expected_tag = hmac.new(MAC_KEY, mac_input, hashlib.sha256).digest()

    # Step 3: Constant-time tag comparison.
    if not hmac.compare_digest(received_tag, expected_tag):
        print("  [crypto_hmac_seq] *** MAC VERIFICATION FAILED ***")
        raise ValueError("HMAC verification failed &#8212; record tampered or replayed")

    print("  [crypto_hmac_seq] MAC verification: OK")

    # Step 4: Check the sequence number matches what we expect.
    # Even though the MAC already covers the sequence number (so an
    # attacker cannot change it without invalidating the MAC), we still
    # explicitly verify that it matches our counter.  This catches
    # replayed or reordered records that carry a valid MAC but belong
    # to a different position in the stream.
    (received_seq,) = struct.unpack("!Q", seq_bytes)
    if received_seq != expected_seq:
        print(
            f"  [crypto_hmac_seq] *** SEQUENCE MISMATCH: "
            f"got {received_seq}, expected {expected_seq} ***"
        )
        raise ValueError(
            f"Sequence number mismatch: got {received_seq}, expected {expected_seq}"
        )

    print(
        f"  [crypto_hmac_seq] Sequence number: {received_seq} (expected {expected_seq}) &#8212; OK"
    )

    # Step 5: Decrypt.
    cipher = Cipher(algorithms.AES(ENC_KEY), modes.CTR(nonce))
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    return plaintext

</code></code></pre><p>And now the channel has a bit more memory.</p><p>Not just &#8220;is this record authentic?&#8221;</p><p>But also &#8220;is this the record I expected next?&#8221;</p><p>That is a real protocol improvement.</p><div><hr></div><h2>Updating the client and server</h2><p>Now let&#8217;s plug this into the channel.</p><h3>Sequence-aware HMAC-based client</h3><pre><code><code># client_v2_hmac_seq.py
import socket

from framing import send_record, recv_record
from crypto_hmac_seq import protect_record, verify_and_unprotect

HOST = "127.0.0.1"
PORT = 9003

# Sequence counters &#8212; sender and receiver each maintain their own.
# The sender increments after each record sent.
# The receiver expects consecutive values starting from 0.
send_seq = 0
recv_seq = 0

# A toy HTTP-like request &#8212; same spirit as Part 1.
request = (
    "GET /transfer?to=bob&amp;amount=100 HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n"
).encode("utf-8")

print("=" * 60)
print("Part 2 &#8212; HMAC + Sequence Numbers Client (Stage 2)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    print(f"Connected to {HOST}:{PORT}")

    # ----- SEND REQUEST -----
    print(f"\\n--- Sending request (send_seq={send_seq}) ---")
    protected = protect_record(send_seq, request)
    send_record(client, protected)
    send_seq += 1
    print(f"  Record sent ({len(protected)} bytes on wire)")

    # ----- RECEIVE RESPONSE -----
    print(f"\\n--- Receiving response (expecting recv_seq={recv_seq}) ---")
    raw_response = recv_record(client)

    try:
        response = verify_and_unprotect(recv_seq, raw_response)
        recv_seq += 1
        print(f"\\n  Decrypted response:\\n  {response.decode('utf-8')}")
    except ValueError as e:
        print(f"\\n  *** REJECTED: {e} ***")

print("\\nDone.")

</code></code></pre><h3>Sequence-aware HMAC-based server</h3><pre><code><code># server_v2_hmac_seq.py
import socket

from framing import send_record, recv_record
from crypto_hmac_seq import protect_record, verify_and_unprotect

HOST = "127.0.0.1"
PORT = 9003

# Sequence counters.
# The server's recv_seq tracks the client's send_seq, and vice versa.
send_seq = 0
recv_seq = 0

print("=" * 60)
print("Part 2 &#8212; HMAC + Sequence Numbers Server (Stage 2)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((HOST, PORT))
    server.listen(1)
    print(f"Listening on {HOST}:{PORT}")

    conn, addr = server.accept()
    with conn:
        print(f"Connected by {addr}")

        # ----- RECEIVE REQUEST -----
        print(f"\\n--- Receiving request (expecting recv_seq={recv_seq}) ---")
        raw_request = recv_record(conn)

        try:
            request = verify_and_unprotect(recv_seq, raw_request)
            recv_seq += 1
        except ValueError as e:
            # Rejection: either the MAC is invalid, the sequence number
            # is wrong, or the data was tampered with / replayed.
            print(f"\\n  *** REJECTED: {e} ***")
            print("  Connection closed &#8212; refusing to process invalid data.")
        else:
            print(f"\\n  Decrypted request:\\n  {request.decode('utf-8')}")

            # ----- SEND RESPONSE -----
            print(f"--- Sending response (send_seq={send_seq}) ---")
            response = (
                "HTTP/1.1 200 OK\\r\\n"
                "Content-Type: text/plain\\r\\n"
                "Content-Length: 13\\r\\n\\r\\n"
                "hello, client"
            ).encode("utf-8")

            protected = protect_record(send_seq, response)
            send_record(conn, protected)
            send_seq += 1
            print(f"  Record sent ({len(protected)} bytes on wire)")

print("\\nDone.")

</code></code></pre><div><hr></div><h2>Why real-world systems usually do not stop here</h2><p>At this point, we have something much stronger than Part 1.</p><p>We have:</p><ul><li><p>encryption</p></li><li><p>integrity protection</p></li><li><p>message authentication</p></li><li><p>sequence-aware records</p></li></ul><p>That is already a meaningful protocol.</p><p>But if you look at how real systems are usually built, they do not normally stop at manually composing:</p><ul><li><p>AES-CTR</p></li><li><p>HMAC-SHA256</p></li><li><p>explicit sequence-aware record protection</p></li></ul><p>Why?</p><p>Because modern systems usually prefer a single primitive that gives confidentiality and integrity together.</p><p>That is where <strong>AEAD</strong> comes in.</p><p>We separated these properties on purpose because it makes the protocol easier to understand.</p><p>But the real world usually packages them together.</p><div><hr></div><h2>A very short note on AEAD</h2><p>AEAD stands for <strong>Authenticated Encryption with Associated Data</strong>.</p><p>That sounds heavier than it really is.</p><p>The practical idea is simple:</p><p>An AEAD construction gives us:</p><ul><li><p>encryption</p></li><li><p>integrity/authentication of the encrypted message</p></li><li><p>and the ability to authenticate extra metadata that should not be encrypted</p></li></ul><p>Common examples are:</p><ul><li><p>AES-GCM</p></li><li><p>ChaCha20-Poly1305</p></li></ul><p>This is much closer to how modern secure protocols protect records.</p><p>It is also why I wanted to include AEAD in this part. If we stopped only at &#8220;encrypt + HMAC,&#8221; we would understand the missing property better, but we would still be one step away from how modern systems actually package it.</p><p>So now we take that final step.</p><div><hr></div><h2>Moving our channel to AEAD</h2><p>For the AEAD version, I will use <strong>AES-GCM</strong>.</p><p>The high-level idea is:</p><ul><li><p>the plaintext gets encrypted</p></li><li><p>integrity/authentication is built in</p></li><li><p>and we can include extra metadata as associated data</p></li></ul><p>In our case, the sequence number is a good example of associated data.</p><p>That means:</p><ul><li><p>it does not need to be encrypted</p></li><li><p>but it should still be authenticated</p></li></ul><h3>AEAD-based helper</h3><pre><code><code># crypto_aead.py
import os
import struct

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

# ---------------------------------------------------------------------------
# Key &#8212; a single 256-bit key for AES-GCM.
# With AEAD, we do NOT need separate encryption and MAC keys &#8212; the
# algorithm handles both internally.
# ---------------------------------------------------------------------------
AEAD_KEY = b"AEAD_KEY_PART2_DEMO_FOR_AES_GCM!"  # 32 bytes &#8594; AES-256-GCM

# AES-GCM nonce length: 12 bytes is the recommended (and most efficient) size.
NONCE_LEN = 12

# Sequence number: 8 bytes (64-bit unsigned integer), same as Stage 2.
SEQ_LEN = 8

def protect_record_aead(seq: int, plaintext: bytes) -&gt; bytes:
    """Seal a plaintext record with AES-GCM.

    Args:
        seq:       The current send-side sequence number.
        plaintext: The message to protect.

    Returns:
        seq (8 B) || nonce (12 B) || ciphertext_and_tag (N+16 B)
    """

    # Pack the sequence number as associated data.
    # The sequence number is authenticated but sent in the clear &#8212; the
    # receiver needs it to know which counter value to expect.
    seq_bytes = struct.pack("!Q", seq)

    # Generate a random 12-byte nonce for AES-GCM.
    nonce = os.urandom(NONCE_LEN)

    # Create an AESGCM instance with our key.
    aesgcm = AESGCM(AEAD_KEY)

    # Encrypt and authenticate in one call.
    # AESGCM.encrypt(nonce, data, associated_data) returns
    # ciphertext || 16-byte authentication tag as a single bytes object.
    # The associated_data (seq_bytes) is authenticated but NOT encrypted.
    ciphertext_and_tag = aesgcm.encrypt(nonce, plaintext, seq_bytes)

    print(f"  [crypto_aead] protect_record_aead:")
    print(f"    seq      = {seq}")
    print(f"    nonce    = {nonce.hex()}")
    print(
        f"    sealed   = {len(ciphertext_and_tag)} bytes "
        f"(plaintext {len(plaintext)} + tag 16)"
    )

    return seq_bytes + nonce + ciphertext_and_tag

def unprotect_record_aead(expected_seq: int, payload: bytes) -&gt; bytes:
    """Verify and decrypt an AES-GCM sealed record.

    Args:
        expected_seq: The sequence number the receiver expects next.
        payload:      seq (8 B) || nonce (12 B) || ciphertext_and_tag.

    Returns:
        The decrypted plaintext.

    Raises:
        ValueError if the sequence number is wrong.
        cryptography.exceptions.InvalidTag if decryption/auth fails.
    """

    min_len = SEQ_LEN + NONCE_LEN + 16  # at least seq + nonce + tag
    if len(payload) &lt; min_len:
        raise ValueError("Record too short")

    # Step 1: Parse the record.
    seq_bytes = payload[:SEQ_LEN]
    nonce = payload[SEQ_LEN : SEQ_LEN + NONCE_LEN]
    ciphertext_and_tag = payload[SEQ_LEN + NONCE_LEN :]

    # Step 2: Check the sequence number.
    (received_seq,) = struct.unpack("!Q", seq_bytes)
    if received_seq != expected_seq:
        print(
            f"  [crypto_aead] *** SEQUENCE MISMATCH: "
            f"got {received_seq}, expected {expected_seq} ***"
        )
        raise ValueError(
            f"Sequence number mismatch: got {received_seq}, expected {expected_seq}"
        )

    print(
        f"  [crypto_aead] Sequence number: {received_seq} "
        f"(expected {expected_seq}) &#8212; OK"
    )

    # Step 3: Decrypt and verify in one call.
    # AESGCM.decrypt(nonce, data, associated_data) verifies the auth tag
    # and decrypts.  If anything was tampered with &#8212; the ciphertext, the
    # tag, or the associated data &#8212; it raises InvalidTag.
    aesgcm = AESGCM(AEAD_KEY)
    plaintext = aesgcm.decrypt(nonce, ciphertext_and_tag, seq_bytes)

    print(f"  [crypto_aead] AEAD decryption: OK ({len(plaintext)} bytes)")

    return plaintext

</code></code></pre><p>This code is noticeably simpler.</p><p>That is one of the big practical advantages of AEAD.</p><p>Instead of manually:</p><ul><li><p>encrypting</p></li><li><p>computing HMAC</p></li><li><p>verifying HMAC</p></li><li><p>then decrypting</p></li></ul><p>we use one primitive that already combines confidentiality and integrity.</p><p>And the sequence number fits naturally as associated data.</p><h3>AEAD-based client</h3><pre><code><code># client_v2_aead.py
import socket

from framing import send_record, recv_record
from crypto_aead import protect_record_aead, unprotect_record_aead

HOST = "127.0.0.1"
PORT = 9002

# Sequence counters &#8212; sender and receiver each maintain their own.
# The sender increments after each record sent.
# The receiver expects consecutive values starting from 0.
send_seq = 0
recv_seq = 0

# A toy HTTP-like request.
request = (
    "GET /transfer?to=bob&amp;amount=100 HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n"
).encode("utf-8")

print("=" * 60)
print("Part 2 &#8212; AEAD Client (AES-GCM)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    print(f"Connected to {HOST}:{PORT}")

    # ----- SEND REQUEST -----
    print(f"\\n--- Sending request (send_seq={send_seq}) ---")
    protected = protect_record_aead(send_seq, request)
    send_record(client, protected)
    send_seq += 1
    print(f"  Record sent ({len(protected)} bytes on wire)")

    # ----- RECEIVE RESPONSE -----
    print(f"\\n--- Receiving response (expecting recv_seq={recv_seq}) ---")
    raw_response = recv_record(client)

    try:
        response = unprotect_record_aead(recv_seq, raw_response)
        recv_seq += 1
        print(f"\\n  Decrypted response:\\n  {response.decode('utf-8')}")
    except Exception as e:
        print(f"\\n  *** REJECTED: {e} ***")

print("\\nDone.")

</code></code></pre><h3>AEAD-based server</h3><pre><code><code># server_v2_aead.py
import socket

from framing import send_record, recv_record
from crypto_aead import protect_record_aead, unprotect_record_aead

HOST = "127.0.0.1"
PORT = 9002

# Sequence counters.
# The server's recv_seq tracks the client's send_seq, and vice versa.
send_seq = 0
recv_seq = 0

print("=" * 60)
print("Part 2 &#8212; AEAD Server (AES-GCM)")
print("=" * 60)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((HOST, PORT))
    server.listen(1)
    print(f"Listening on {HOST}:{PORT}")

    conn, addr = server.accept()
    with conn:
        print(f"Connected by {addr}")

        # ----- RECEIVE REQUEST -----
        print(f"\\n--- Receiving request (expecting recv_seq={recv_seq}) ---")
        raw_request = recv_record(conn)

        try:
            request = unprotect_record_aead(recv_seq, raw_request)
            recv_seq += 1
        except Exception as e:
            # AEAD rejection: either the auth tag is invalid, the sequence
            # number is wrong, or the data was tampered with.
            print(f"\\n  *** REJECTED: {e} ***")
            print("  Connection closed &#8212; refusing to process invalid data.")
        else:
            print(f"\\n  Decrypted request:\\n  {request.decode('utf-8')}")

            # ----- SEND RESPONSE -----
            print(f"--- Sending response (send_seq={send_seq}) ---")
            response = (
                "HTTP/1.1 200 OK\\r\\n"
                "Content-Type: text/plain\\r\\n"
                "Content-Length: 13\\r\\n\\r\\n"
                "hello, client"
            ).encode("utf-8")

            protected = protect_record_aead(send_seq, response)
            send_record(conn, protected)
            send_seq += 1
            print(f"  Record sent ({len(protected)} bytes on wire)")

print("\\nDone.")

</code></code></pre><p>This version is already much closer to how modern secure transport actually protects records.</p><p>Not identical to TLS, of course. But structurally much closer.</p><div><hr></div><h2>What we gained</h2><p>At this point, our channel is much stronger than the one from Part 1.</p><p>We now have:</p><ul><li><p>confidentiality</p></li><li><p>integrity protection</p></li><li><p>authenticated records</p></li><li><p>sequence-aware message handling</p></li><li><p>a much more realistic record protection design through AEAD</p></li></ul><p>That is a big improvement.</p><p>The receiver is no longer just decrypting whatever arrives and trusting the result. Now the receiver can reject modified or structurally unexpected records.</p><p>That is a real protocol boundary.</p><div><hr></div><h2>What is still broken</h2><p>And yet, even now, we are still very far from real TLS.</p><p>Because the biggest assumption in our design is still untouched:</p><p><strong>both sides already share the necessary secret keys</strong></p><p>That means we still do not know how to solve the next real problem:</p><ul><li><p>how do two strangers establish fresh secrets?</p></li><li><p>how does the client know it is talking to the right server?</p></li><li><p>how do we scale beyond hardcoded shared secrets?</p></li><li><p>how do we build trust instead of assuming it?</p></li></ul><p>We improved record protection a lot.</p><p>But we still do not have a real way to establish trust.</p><p>That is the next wall.</p><div><hr></div><h2>Summary</h2><p>In this part, we took the encrypted but still incomplete channel from Part 1 and made it much more serious.</p><p>First, we added <strong>HMAC</strong>, which gave the receiver a way to detect tampering.</p><p>Then, we added a <strong>sequence number</strong>, which made the record layer less naive and bound records to their place in the stream.</p><p>Finally, we moved to <strong>AEAD</strong>, because in real-world systems confidentiality and integrity are usually protected together, not assembled manually from separate pieces.</p><p>So this article had two goals:</p><ul><li><p>understand the missing property explicitly</p></li><li><p>then move toward the real-world shape of the solution</p></li></ul><p>That is why we did not stop at HMAC.</p><p>But even after all of this, we still depend on one assumption that makes the whole thing unrealistic:</p><p>we are still starting with pre-shared secret keys.</p><p>And that is exactly what the next part will attack.</p><div><hr></div><h2>Next part &#8212; getting rid of the pre-shared key</h2><p>So we stop here.</p><p>We now have a much better record layer than in Part 1. But we are still relying on a hardcoded shared secret, and that is not how real secure communication between strangers on the internet works.</p><p>In the next part, we will stop assuming both sides already share a secret.</p><p>We will start building a real handshake and establish fresh session keys instead.</p><p>That still will not give us full TLS.</p><p>But it will take us much closer to the real shape of the protocol.</p><div><hr></div><h2>Final code</h2><p>I&#8217;ll put the full code for this part on GitHub here:</p><p><strong><a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_2">[GitHub link to final code]</a></strong></p><div><hr></div><h2>Next article:</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;fedd2861-0c5a-4033-9d31-4d253307661e&quot;,&quot;caption&quot;:&quot;Previous Article:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 3 &#8212; Building Our First Handshake&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-19T16:38:45.365Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Gn-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:194707545,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Rebuilt is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Rebuilding TLS, Part 1 — Why Encryption Alone Is Not Enough]]></title><description><![CDATA[From transparent TCP traffic to encrypted records &#8212; and the first reason TLS needs more than encryption]]></description><link>https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 29 Mar 2026 18:57:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5phz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5phz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5phz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5phz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5phz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5phz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5phz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2856965,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/192533658?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5phz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5phz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5phz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5phz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A year ago I wrote a series about how a web server works.</p><p>I started from a very primitive version and step by step moved toward the same core ideas modern production servers rely on. When I finished that series, I thought the next step would be small.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Wrap it in TLS. Make the communication secure.</p><p>It did not stay small for long.</p><p>What looked like a thin security layer on top of an existing server turned into a much deeper journey into cryptography, authentication, trust, certificates, protocol design, and many details usually hidden behind one familiar phrase: <strong>secure connection</strong>.</p><p>So this series is my attempt to approach TLS the same way I approached the web server: not as a finished black box, but as something we can rebuild from simpler pieces until its shape starts to make sense.</p><p>In this first part, we will start with the most naive version of the problem.</p><p>We will build a very simple socket-based communication channel, see that it is fully transparent, wrap it in encryption with a shared secret key, and then see why that is still not enough.</p><p>That will give us our first fake secure channel.</p><p>And that is exactly where we should start.</p><div><hr></div><h2>What we will build in this part</h2><p>The plan for this article is simple:</p><ul><li><p>build a tiny socket-based client and server</p></li><li><p>send plain text between them</p></li><li><p>look at the traffic and see that everything is visible</p></li><li><p>add shared-key encryption with AES-CTR</p></li><li><p>make the traffic unreadable</p></li><li><p>then show why encryption alone still does not give us a trustworthy secure protocol</p></li></ul><p>We are not trying to build real TLS yet.</p><p>We are trying to make the first mistake on purpose.</p><p>Because once that mistake becomes visible, the next piece of the protocol stops looking optional.</p><div><hr></div><h2>TLS is not SSL</h2><p>Before we start, one small clarification.</p><p>People still often say &#8220;SSL&#8221; when they talk about secure communication on the web. But SSL is the older family of protocols. TLS is its successor.</p><p>So when people say things like &#8220;SSL certificate&#8221; or &#8220;SSL connection,&#8221; in practice they usually mean TLS.</p><p>For modern systems, the relevant protocols are TLS, especially TLS 1.2 and TLS 1.3. This series is about understanding the ideas behind TLS by rebuilding simpler versions of the problems it solves.</p><p>And instead of starting from the finished protocol, we will begin one layer lower &#8212; with plain socket communication.</p><div><hr></div><h2>Step 1 &#8212; A plain socket-based communication channel</h2><p>Let&#8217;s start with the smallest possible thing: a tiny TCP server and a tiny TCP client.</p><p>The client will send an HTTP-like request.</p><p>The server will read it and return an HTTP-like response.</p><p>Nothing secure yet. Just raw bytes moving over a socket.</p><h3>Plain server</h3><pre><code><code># server_plain.py
import socket

HOST = "127.0.0.1"
PORT = 8081

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.bind((HOST, PORT))
    server.listen(1)

    print(f"Listening on {HOST}:{PORT}")
    conn, addr = server.accept()

    with conn:
        print(f"Connected by {addr}")

        data = conn.recv(4096)
        request = data.decode("utf-8")
        print("Received request:")
        print(request)

        response = (
            "HTTP/1.1 200 OK\\r\\n"
            "Content-Type: text/plain\\r\\n"
            "Content-Length: 13\\r\\n"
            "\\r\\n"
            "hello, client"
        )
        conn.sendall(response.encode("utf-8"))
</code></code></pre><h3>Plain client</h3><pre><code><code># client_plain.py
import socket

HOST = "127.0.0.1"
PORT = 8081

request = (
    "GET /transfer?to=bob&amp;amount=100 HTTP/1.1\\r\\n"
    "Host: localhost\\r\\n"
    "\\r\\n"
)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    client.sendall(request.encode("utf-8"))

    response = client.recv(4096)
    print("Received response:")
    print(response.decode("utf-8"))
</code></code></pre><p>This is intentionally tiny.</p><p>The client sends a request like this:</p><pre><code><code>GET /transfer?to=bob&amp;amount=100 HTTP/1.1
Host: localhost
</code></code></pre><p>The server reads it and sends a response back.</p><p>That is all.</p><p>And because it is all plain TCP, anyone who can observe the traffic can read it directly.</p><h3>Looking at the traffic</h3><p>If you capture this communication in Wireshark, the request and response are fully visible in clear text.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aF_t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aF_t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 424w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 848w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 1272w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aF_t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png" width="1456" height="703" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:703,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135818,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/192533658?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aF_t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 424w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 848w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 1272w, https://substackcdn.com/image/fetch/$s_!aF_t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181b7e6-c7b5-4740-9a2a-6d19ac38e57f_1677x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That is our baseline.</p><p>The client can read it.</p><p>The server can read it.</p><p>And anyone on the wire can read it too.</p><p>So the first obvious idea is also the first naive one:</p><p>If the problem is that everyone can read the bytes, let&#8217;s encrypt the bytes.</p><p>That sounds reasonable.</p><p>And it is still not enough.</p><div><hr></div><h2>Step 2 &#8212; Turning bytes into records</h2><p>Before we add encryption, we need one small but important thing: structure.</p><p>TCP gives us a byte stream.</p><p>It does not give us message boundaries.</p><p>So once we stop sending plain text directly and start sending encrypted blobs, we need a way to tell the receiver how many bytes belong to one logical message.</p><p>That means even before security, we need a little bit of protocol design.</p><p>Let&#8217;s define the smallest possible record format:</p><ul><li><p>4 bytes: payload length</p></li><li><p>N bytes: payload</p></li></ul><pre><code><code>
+----------+-----------+
|  length  |  payload  |
| (4 bytes)|  (varies) |
+----------+-----------+
</code></code></pre><p>That is enough for our first version.</p><pre><code><code># framing.py
import struct

def send_record(sock, payload: bytes) -&gt; None:
    header = struct.pack("!I", len(payload))
    sock.sendall(header + payload)

def recv_exact(sock, n: int) -&gt; bytes:
    chunks = []
    remaining = n

    while remaining &gt; 0:
        chunk = sock.recv(remaining)
        if not chunk:
            raise ConnectionError("Connection closed while reading data")
        chunks.append(chunk)
        remaining -= len(chunk)

    return b"".join(chunks)

def recv_record(sock) -&gt; bytes:
    header = recv_exact(sock, 4)
    (length,) = struct.unpack("!I", header)
    return recv_exact(sock, length)
</code></code></pre><p>This is not a crypto step.</p><p>It is a protocol step.</p><p>And that distinction matters more than it first appears. A secure channel is not just &#8220;call encrypt on a string.&#8221; It is a protocol with structure, state, and rules.</p><p>Now that we have a way to send and receive well-defined records, we can finally wrap them in encryption.</p><div><hr></div><h2>Step 3 &#8212; Wrapping the channel in shared-key encryption</h2><p>The most obvious first attempt at secure communication is usually this:</p><ul><li><p>both sides already know the same secret key</p></li><li><p>the sender encrypts the message before sending</p></li><li><p>the receiver decrypts it after receiving</p></li></ul><p>That is exactly what we will do.</p><p>No handshake yet.</p><p>No certificates yet.</p><p>No integrity yet.</p><p>No authentication yet.</p><p>This version is intentionally naive.</p><p>For encryption, I will use AES in CTR mode.</p><p>Very briefly:</p><ul><li><p>AES is a symmetric block cipher</p></li><li><p>CTR mode makes it convenient for encrypting a stream of bytes</p></li><li><p>it gives us confidentiality</p></li><li><p>but it does <strong>not</strong> give us integrity</p></li></ul><p>That last point is the important one for this article.</p><p>If you want a deeper explanation of AES itself, I already wrote about it in my cryptography series: <a href="https://www.dmytrohuz.com/p/building-own-block-cipher-part-3">https://www.dmytrohuz.com/p/building-own-block-cipher-part-3</a>, so I will not go into the internals here.</p><h3>The nonce</h3><p>CTR mode also needs a nonce.</p><p>For now, think of it as a fresh per-message value that must be different for each encryption under the same key.</p><p>It is not secret.</p><p>It just must not be reused.</p><p>So our encrypted payload will look like this:</p><ul><li><p>nonce</p></li><li><p>ciphertext</p></li></ul><h3>Crypto helper</h3><pre><code><code># crypto.py
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# 32-byte shared key for AES-256
SHARED_KEY = b"0123456789ABCDEF0123456789ABCDEF"

def encrypt_message(plaintext: bytes) -&gt; bytes:
    nonce = os.urandom(16)

    cipher = Cipher(algorithms.AES(SHARED_KEY), modes.CTR(nonce))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()

    # Encrypted payload format:
    # nonce || ciphertext
    return nonce + ciphertext

def decrypt_message(payload: bytes) -&gt; bytes:
    nonce = payload[:16]
    ciphertext = payload[16:]

    cipher = Cipher(algorithms.AES(SHARED_KEY), modes.CTR(nonce))
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    return plaintext
</code></code></pre><p>At this point, our wire format becomes:</p><ul><li><p>4-byte length</p></li><li><p>16-byte nonce</p></li><li><p>ciphertext</p></li></ul><pre><code><code>+----------------+----------------+---------------------+
| length (4 B)   | nonce (16 B)   | ciphertext (N bytes)|
+----------------+----------------+---------------------+</code></code></pre><p>That already looks much more like a protocol.</p><div><hr></div><h2>Step 4 &#8212; Encrypt the request and response</h2><p>Now let&#8217;s integrate this into the client and server.</p><h3>Encrypted client</h3><pre><code><code># client_v1.py
import socket

from framing import send_record, recv_record
from crypto import encrypt_message, decrypt_message

HOST = "127.0.0.1"
PORT = 8081

request = (
    "GET /transfer?to=bob&amp;amount=100 HTTP/1.1\\r\\n"
    "Host: localhost\\r\\n"
    "\\r\\n"
).encode("utf-8")

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))

    encrypted_request = encrypt_message(request)
    send_record(client, encrypted_request)

    encrypted_response = recv_record(client)
    response = decrypt_message(encrypted_response)

    print("Received decrypted response:")
    print(response.decode("utf-8"))
</code></code></pre><h3>Encrypted server</h3><pre><code><code># server_v1.py
import socket

from framing import send_record, recv_record
from crypto import encrypt_message, decrypt_message

HOST = "127.0.0.1"
PORT = 8081

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.bind((HOST, PORT))
    server.listen(1)

    print(f"Listening on {HOST}:{PORT}")
    conn, addr = server.accept()

    with conn:
        print(f"Connected by {addr}")

        encrypted_request = recv_record(conn)
        request = decrypt_message(encrypted_request)

        print("Received decrypted request:")
        print(request.decode("utf-8"))

        response = (
            "HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\n"
            "Content-Length: 13\\r\\n\\r\\nhello, client"
        ).encode("utf-8")

        encrypted_response = encrypt_message(response)
        send_record(conn, encrypted_response)
</code></code></pre><p>Now the communication flow changes in an important way.</p><p>Instead of sending readable HTTP-like text directly, the client sends an encrypted record. The server reads the record, decrypts it, and sees the original request.</p><h3>What changes on the wire</h3><p>If you capture this version in Wireshark, the traffic is no longer readable.</p><p>Instead of clear request and response text, you now see opaque binary data.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1qMA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1qMA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 424w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 848w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 1272w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1qMA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png" width="1456" height="703" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:703,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:220787,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/192533658?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1qMA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 424w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 848w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 1272w, https://substackcdn.com/image/fetch/$s_!1qMA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65774ee7-4444-4135-93a9-ccc576fae9ec_1677x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>So yes, we gained something real.</p><p>Let&#8217;s stop and say exactly what that is.</p><div><hr></div><h2>What encryption actually gave us</h2><p>This first version gives us one meaningful property:</p><p><strong>confidentiality against passive observers</strong></p><p>If someone can only observe the traffic, but cannot modify it, they no longer get the plaintext request and response for free.</p><p>That is already better than raw TCP.</p><p>And this is why &#8220;just add encryption&#8221; feels so convincing. It visibly solves a real problem.</p><p>But that visible success can hide another, more dangerous failure.</p><p>Because a secure channel needs more than secrecy.</p><p>It also needs protection against tampering.</p><p>And we still do not have that.</p><div><hr></div><h2>Step 5 &#8212; Why encryption alone is not enough</h2><p>This is the real point of Part 1.</p><p>We encrypted the messages.</p><p>We did <strong>not</strong> make them trustworthy.</p><p>AES-CTR protects confidentiality, but it does not protect integrity.</p><p>That means an active attacker may be able to modify ciphertext, and those modifications will flow through into the decrypted plaintext.</p><p>Very roughly, CTR mode behaves like this:</p><pre><code><code>ciphertext = plaintext XOR keystream
</code></code></pre><p>So if an attacker changes bits in the ciphertext, the corresponding bits change in the plaintext after decryption.</p><p>That property is called <strong>malleability</strong>.</p><p>And protocol messages are usually predictable enough that this becomes useful to an attacker.</p><p>Our example request already has a very predictable structure:</p><pre><code><code>GET /transfer?to=bob&amp;amount=100 HTTP/1.1
Host: localhost
</code></code></pre><p>The exact bytes of <code>amount=100</code> are not random.</p><p>That predictability is enough to hurt us.</p><h3>A tiny isolated demo</h3><p>We do not need a full man-in-the-middle proxy to show the problem. A small isolated example is enough.</p><pre><code><code># ctr_malleability_demo.py
from crypto import encrypt_message, decrypt_message

original = b"amount=100"
encrypted = encrypt_message(original)

nonce = encrypted[:16]
ciphertext = bytearray(encrypted[16:])

# Change '1' -&gt; '9'
# ASCII '1' = 0x31
# ASCII '9' = 0x39
# Difference = 0x08

index_of_digit = len("amount=")
ciphertext[index_of_digit] ^= 0x08

modified = nonce + bytes(ciphertext)
decrypted = decrypt_message(modified)

print("Original :", original)
print("Modified :", decrypted)
</code></code></pre><p>Output:</p><pre><code><code>Original : b'amount=100'
Modified : b'amount=900'
</code></code></pre><p>And that is the failure.</p><p>The attacker did not need the key.</p><p>They did not need to fully decrypt the message first.</p><p>They only needed the ability to modify the encrypted bytes in transit.</p><p>The receiver then decrypts the modified ciphertext and gets modified plaintext &#8212; without any built-in indication that anything went wrong.</p><p>So even though the message is hidden from passive observers, it is still vulnerable to active tampering.</p><p>That is not a secure protocol.</p><p>That is only encrypted transport.</p><div><hr></div><h2>What is still broken</h2><p>At this point, our fake secure channel still has many serious holes.</p><h3>No integrity protection</h3><p>The receiver cannot detect that the ciphertext was modified.</p><h3>No message authentication</h3><p>The receiver has no cryptographic proof that the message came from the expected sender and arrived unchanged.</p><h3>No replay protection</h3><p>An attacker can capture an encrypted message and replay it later.</p><h3>Static shared key</h3><p>Both sides use one long-term shared key for everything.</p><p>That does not scale, and if it leaks, everything built on top of it collapses.</p><h3>No handshake</h3><p>There is no fresh session establishment. The peers do not negotiate anything. They just start encrypting.</p><h3>No peer identity</h3><p>The client does not really know who it is talking to beyond &#8220;someone who can decrypt with this key.&#8221;</p><p>So yes, we improved something.</p><p>But we are still very far from TLS.</p><p>Good.</p><p>That is exactly what Part 1 should make visible.</p><div><hr></div><h2>Summary</h2><p>In this first part, we built a fake secure channel.</p><p>We started with plain socket communication and saw that everything was fully transparent. Then we wrapped the communication in shared-key encryption with AES-CTR, which gave us confidentiality against passive observers.</p><p>That was real progress.</p><p>But it was not enough.</p><p>Because encrypting a message is not the same thing as protecting the message from being changed. Our channel still accepts modified ciphertext, decrypts it, and trusts the result.</p><p>So the first lesson of this series is simple:</p><p><strong>confidentiality is not integrity</strong></p><p>And if we want something that starts to deserve the name secure protocol, we need both.</p><div><hr></div><h2>Next part &#8212; adding integrity with a MAC</h2><p>So we stop here.</p><p>We now have a channel that can hide bytes from passive observers, but cannot reliably detect tampering.</p><p>In the next part, we will keep the same basic setup and fix the biggest hole we exposed here: we will add a <strong>MAC &#8212; a Message Authentication Code</strong>.</p><p>That will take us from:</p><blockquote><p>&#8220;you probably can&#8217;t read this&#8221;</p></blockquote><p>to:</p><blockquote><p>&#8220;you also can&#8217;t silently change this&#8221;</p></blockquote><p>That still will not be real TLS.</p><p>But it will make our fake secure channel one step less fake.</p><div><hr></div><h2>Final code</h2><p>I&#8217;ll put the full code for this part on GitHub here: <a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_1">rebuilding_tls</a></p><div><hr></div><h2>Next article:</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1e716372-eb3d-4b99-ac9c-aac2ac0cf863&quot;,&quot;caption&quot;:&quot;In the first part of this series, we built our first fake secure channel:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 2 &#8212; Adding Integrity to the Channel&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-05T21:40:43.808Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!78kv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:193281237,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Rebuilding TLS From Scratch — My Complete Learning Journey]]></title><description><![CDATA[This collection brings together my attempt to rebuild TLS from first principles]]></description><link>https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Fri, 27 Mar 2026 21:39:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!59qZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!59qZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!59qZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!59qZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2967175,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/192355388?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!59qZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!59qZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2268c1-b8d1-42db-9320-6a750a764263_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A year ago I wrote a series of articles about how a <a href="https://dev.to/dmytro_huz/building-your-own-web-server-part-1-theory-and-foundations-3kgo">web server works</a>.</p><p>I started from a very primitive version and step by step moved toward the same core ideas modern production servers rely on. That journey was already deep enough on its own. But when I finished it, I had one more thing in mind.</p><p>A small improvement.</p><p>Wrap the server in TLS. Make the communication secure.</p><p>At least, that was how it looked from the outside.</p><p>In reality, that &#8220;small improvement&#8221; turned into a much longer journey than I expected. What looked like one tiny feature hidden behind the familiar lock icon in the browser opened the door into a huge world of cryptography, key exchange, authentication, certificates, trust, protocol design, and many layers of details that usually stay invisible when everything just works.</p><p>And that is exactly why I decided to make this series.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><p>I did not want to approach TLS as a finished black box. I did not want to start from the RFC and just repeat the names of the protocol messages until they sounded familiar. I wanted to understand what TLS is, why it exists in the form it exists, and why it needs so many moving parts.</p><p>So this series is my attempt to rebuild it.</p><p>Not the full production-ready TLS implementation, of course. But a step-by-step reconstruction of the logic behind it. We will start from something intentionally naive, something that only looks secure, and in each article we will add one missing piece, one new idea, one more reason why real TLS had to become what it is.</p><p>By the end, I want us to arrive at a simplified TLS-like protocol that is close enough to the real thing to make the real thing much easier to understand.</p><p>Because that is still the real goal.</p><p>I do not want to stay forever in toy examples, and I do not think you want that either. In the final part of the series, I want to take everything we built ourselves and map it to the real TLS protocol: what is different, what extra problems the real one solves, why it is structured the way it is, and maybe also how some popular libraries implement it in practice.</p><p>But I really believe that jumping directly into the finished TLS protocol is the wrong way to learn it.</p><p>TLS is one of those technologies where the final design hides the reasons. If you look at the real protocol too early, you see a forest of details: handshakes, certificates, transcript hashes, traffic secrets, record protection, extensions, verification steps. All of them are there for a reason. But those reasons are much easier to understand when you first build the broken versions that fail without them.</p><p>That is the main idea of this series:</p><p><strong>we will learn TLS by first building the wrong thing, and then fixing it step by step.</strong></p><p>So if your goal is to understand the real TLS better, I would strongly recommend following the whole journey and not jumping directly to the last part.</p><p>As I mentioned, TLS is built on many cryptographic ideas. I will explain the important ones when we need them, but I also already wrote a separate series where I rebuild the main cryptographic foundations from scratch:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d1c91fa2-1989-4ec7-9c1a-cf6ec139d6b2&quot;,&quot;caption&quot;:&quot;Why this project exists - and why it might matter to you&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding Cryptography From Scratch - My Complete Learning Journey (All Parts Inside)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-01T19:09:39.536Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!SRa_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8a9697d-5837-4c57-947c-4cf941c3bc3d_1024x608.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-cryptography-from-scratch&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:180433391,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>So throughout this TLS series, I will link back to those parts whenever we meet a concept that deserves a deeper look.</p><p>This page will be the central hub for the whole project. I will keep it updated as new parts are published, so all articles stay connected in one place.</p><p>I really hope you enjoy this journey as much as I do.</p><div><hr></div><h1><strong>Full Summary of the TLS Series</strong></h1><p><em>This section will be updated as new parts are released.</em></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f6554474-5a3b-4fc1-84c6-686cd8bcec9d&quot;,&quot;caption&quot;:&quot;From transparent TCP traffic to encrypted records &#8212; and the first reason TLS needs more than encryption&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 1 &#8212; Why Encryption Alone Is Not Enough&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-29T18:57:36.417Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!5phz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f085090-d20e-4850-844a-c79a878be8e6_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:192533658,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1ba5526e-56cd-46ad-be1a-ecf7a0624726&quot;,&quot;caption&quot;:&quot;We teach our protocol to detect tampering, make records less naive with sequence numbers, and then switch to the AEAD style used in real systems.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 2 &#8212; Adding Integrity to the Channel&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-05T21:40:43.808Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!78kv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F680242ba-f7c2-4916-990b-5c813987d655_1536x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:193281237,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;75e5e3a4-f06f-48f5-947b-d6e777d285e0&quot;,&quot;caption&quot;:&quot;We get rid of the pre-shared key assumption, build a simple key exchange handshake, and discover why key agreement alone still does not give us real TLS.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 3 &#8212; Building Our First Handshake&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-19T16:38:45.365Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Gn-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb29e52-36ee-433b-a83f-355dc7736265_1536x600.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:194707545,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;04357092-974f-4863-98c1-af7c43351329&quot;,&quot;caption&quot;:&quot;We found out how Certificates and CA solve the problem of trust and prevent MITM attack. And integrated this last piece in out TLS protocol. &quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rebuilding TLS, Part 4 - Certificates and Trust&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-26T20:24:36.572Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HYYb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1a0a1c1-8a6f-4b73-8101-881f128943d8_1536x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/rebuilding-tls-part-4-certificates&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:195558698,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1><strong>What this series is really about</strong></h1><p>This is not just a series about TLS.</p><p>It is also another exercise in the same thing I keep coming back to again and again: taking foundational technology that usually appears to us as a finished black box, opening it up, and rebuilding it from simpler parts until it stops feeling magical.</p><p>That was the idea behind the web server series.</p><p>That was the idea behind the cryptography series.</p><p>And now this is the same idea applied to TLS.</p><div><hr></div><h1><strong>Follow the journey</strong></h1><p>This page will stay the central entry point for the whole series.</p><p>If this kind of deep, step-by-step reconstruction of systems is interesting to you, you can subscribe so you do not miss the next parts.</p><h2><strong>Links</strong></h2><ul><li><p>Part 4 walkthrough (rendered) &#8212; <a href="https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html">dmytrohuzz.github.io/rebuilding_tls/.../walkthrough.html</a></p></li><li><p>Repository &#8212; <a href="https://github.com/DmytroHuzz/rebuilding_tls">github.com/DmytroHuzz/rebuilding_tls</a></p></li><li><p>Questions, feedback, found a bug? &#8212; <a href="https://www.linkedin.com/in/dmitriyhuz/">LinkedIn: dmitriyhuz</a></p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[A Practical Guide to Time for Developers — The Complete Series]]></title><description><![CDATA[Time looks simple until you have to trust it.]]></description><link>https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-746</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-746</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Thu, 19 Mar 2026 21:05:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mg5A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mg5A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mg5A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mg5A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mg5A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!mg5A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbef6652f-dc32-4078-ac16-7d4ac73c0392_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Time looks simple until you have to trust it.</p><p>We use timestamps everywhere: logs, APIs, databases, schedulers, certificates, metrics, traces, distributed systems, industrial systems. But the moment you start asking basic questions &#8212; <em>what exactly is this timestamp measuring? which clock produced it? how does one machine keep time? how do many machines agree on it?</em> &#8212; the topic becomes much deeper than it first appears.</p><p>This series was my attempt to build a practical mental model of time for developers from first principles all the way down to Linux clocks, NTP, PTP, PHCs, and synchronization tools.</p><p>If you want one entry point into the whole topic, this is it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-746?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-746?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h2><strong>What this series covers</strong></h2><p>This series is built as a path:</p><ul><li><p>first, what time actually means in software</p></li><li><p>then, how one computer keeps time</p></li><li><p>then, how many computers synchronize time</p></li><li><p>and finally, how Linux represents all of this in practice</p></li></ul><p>The goal was never to produce a dry reference manual.</p><p>The goal was to make time feel understandable.</p><p>Not just &#8220;I know NTP exists,&#8221; but a real working model of:</p><ul><li><p>what time is</p></li><li><p>how clocks drift</p></li><li><p>why synchronization is hard</p></li><li><p>why timestamp location matters</p></li><li><p>and how Linux exposes all of this in real systems</p></li></ul><h2><strong>The full reading path</strong></h2><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;c40c49bc-90c6-4eac-b83e-aee3b47e2760&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 1 &#8212; What time is in software (physics + agreements)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-01T06:30:46.601Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!8hv5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:189526403,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>This is the foundation.</p><p>If you ever used timestamps without being fully sure what they really mean, start here.</p><p>This part covers the basic language of the topic: what time means in software, why timestamps are not &#8220;time itself,&#8221; what role standards and agreements play, and why the whole subject is more subtle than it first appears.</p><p>This is the part that gives you the mental vocabulary for everything that comes later.</p><div><hr></div><h3></h3><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;785061f6-fbd7-4cdf-b896-43b0116e287c&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 2 &#8212; How one computer keeps time (Linux)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-05T21:16:33.261Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JuSw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:190040669,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Once the theory is clear, the next question is obvious:</p><p>How does one computer actually keep time?</p><p>This part moves inside the machine. It covers the clocks and mechanisms Linux uses to track time, including the RTC, system time, counters, and the distinction between different clock sources and time domains.</p><p>If Part 1 explains what time means, Part 2 explains how a single system turns that idea into something measurable and usable.</p><div><hr></div><h3></h3><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;90da410a-7e34-4143-aee5-98eb6d0d5712&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 3 &#8212; How Computers Share Time&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-16T15:42:35.677Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!0JCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-ec8&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:191125080,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>A single computer can keep time locally.</p><p>A distributed system has a harder problem: many machines must keep time together.</p><p>This part is about synchronization. Why simply setting clocks once does not work. Why drift makes synchronization a continuous process. How NTP and PTP approach the problem. And why the quality of synchronization depends not only on the protocol, but also on where timestamps are taken.</p><p>This is where time stops being a local machine detail and becomes a systems problem.</p><div><hr></div><h3></h3><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;35cb7f76-68b8-40d1-9ed4-b6242e3185b8&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 4 -The Linux Time Sync Cheat Sheet&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Engineer | Writer | Builder - I deconstruct complex systems to first principles and rebuild them into clear engineering mental models, diagrams, and practical tools.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-19T16:36:51.707Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!MEgQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:191493632,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Dmytro&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!t_-c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F046f0d8c-fecd-41e6-a43f-4718cf07a50f_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>This is the practical payoff.</p><p>It turns the concepts from the whole series into the Linux view of the world:</p><ul><li><p>RTC</p></li><li><p>system clock</p></li><li><p>PHC</p></li><li><p>NTP</p></li><li><p>PTP</p></li><li><p>ptp4l</p></li><li><p>phc2sys</p></li><li><p>and the usual synchronization paths between them</p></li></ul><p>This is the compact field guide version &#8212; the part you can actually bookmark and return to when you need to inspect, operate, or debug time synchronization on Linux.</p><div><hr></div><h2><strong>Interactive visuals and supporting materials</strong></h2><p>Along the way, I also started building visual and interactive explanations for some of the harder ideas, such as:</p><ul><li><p>clock drift</p></li><li><p>synchronization by timestamp exchange</p></li><li><p>NTP principles</p></li><li><p>PTP principles</p></li><li><p>software vs hardware timestamping</p></li></ul><p>I want this series to be more than just text. Time is easier to understand when you can see it move.</p><h2><strong>Who this series is for</strong></h2><p>This series should be useful if you work with:</p><ul><li><p>backend or distributed systems</p></li><li><p>Linux and infrastructure</p></li><li><p>observability and logs</p></li><li><p>event ordering</p></li><li><p>industrial or measurement systems</p></li><li><p>networking</p></li><li><p>NTP/PTP</p></li><li><p>or just any system where timestamps need to be trusted</p></li></ul><p>In other words: if time can break your system, this topic is worth understanding properly.</p><h2><strong>Why I wrote this</strong></h2><p>Time is one of the most used and least understood parts of software.</p><p>Most of us interact with it every day, but only occasionally stop to ask what is actually happening underneath. I wanted to fix that for myself first &#8212; and then turn that learning process into something practical and usable for other engineers.</p><p>This series is the result.</p><h2><strong>Final note</strong></h2><p>If you made it through the whole series, you did not just read a few posts about clocks.</p><p>You built a real mental model of time in computing &#8212; from first principles, to clocks inside a machine, to synchronization across networks, to the actual Linux entities and tools that make it work in practice.</p><p>That already puts you ahead of most engineers who touch these systems.</p><p>You now know enough to stop treating time as a mysterious background feature and start seeing it for what it really is:</p><p><strong>infrastructure, measurement, coordination, and engineering.</strong></p><p>Bookmark this page. I&#8217;ll keep it as the main entry point for the whole series.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[A Practical Guide to Time for Developers: Part 4 -The Linux Time Sync Cheat Sheet]]></title><description><![CDATA[RTC, system clock, PHC, NTP, PTP, ptp4l, and phc2sys &#8212; the practical map of how time works in Linux]]></description><link>https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Thu, 19 Mar 2026 16:36:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MEgQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MEgQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MEgQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MEgQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1371588,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191493632?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MEgQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!MEgQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you got here and made it through the previous articles, you have already done the hard part. You are basically a time guru now.</p><p>You know <a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers">what time means in computing</a>, <a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec">how a machine keeps it</a>, why clocks drift, <a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-ec8">how synchronization works, and why timestamp location matters.</a> That is already more than most people ever learn about this topic.</p><p>Now let&#8217;s compress all of that into the Linux view of the world.</p><p>This is the top of the iceberg: a small set of clocks, commands, and tools that represent most of the concepts we have been building up across the series.</p><p>Think of this as the practical cheat sheet &#8212; the 90% version. The one you can use to inspect clocks, understand what is synchronized to what, and handle most everyday Linux time-sync tasks without drowning in documentation.</p><p><em><strong>This is probably the part you will want to bookmark.</strong></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2><strong>The three main clock entities in Linux</strong></h2><p>When people say &#8220;Linux time,&#8221; they often mean one thing. In reality, Linux commonly deals with at least three different clock entities:</p><ul><li><p><strong>system time</strong></p></li><li><p><strong>RTC</strong></p></li><li><p><strong>PHC</strong></p></li></ul><p>They serve different purposes.</p><div><hr></div><h2><strong>1. System time</strong></h2><p>This is the normal wall-clock time used by most applications.</p><p>It is what you usually see when you run:</p><pre><code><code>date
</code></code></pre><p>or:</p><pre><code><code>timedatectl
</code></code></pre><p>Conceptually, this is the kernel&#8217;s main wall clock, commonly associated with CLOCK_REALTIME.</p><p>This is the clock used by:</p><ul><li><p>most user-space applications</p></li><li><p>logs</p></li><li><p>system services</p></li><li><p>everyday time queries</p></li></ul><h3><strong>Quick check</strong></h3><pre><code><code>date
timedatectl
</code></code></pre><h3><strong>Mental model</strong></h3><pre><code><code>System clock = the main OS wall clock
</code></code></pre><div><hr></div><h2><strong>2. RTC (Real-Time Clock)</strong></h2><p>The RTC is the battery-backed hardware clock on the motherboard.</p><p>Its main job is simple: keep time while the machine is powered off.</p><p>Linux often uses it during boot to initialize system time, and may update it again later from system time. But the RTC is usually <strong>not</strong> the main precision synchronization clock during normal operation.</p><h3><strong>Quick check</strong></h3><pre><code><code>sudo hwclock --show
timedatectl
</code></code></pre><h3><strong>Common operations</strong></h3><p>Copy system time to RTC:</p><pre><code><code>sudo hwclock --systohc
</code></code></pre><p>Copy RTC to system time:</p><pre><code><code>sudo hwclock --hctosys
</code></code></pre><h3><strong>Mental model</strong></h3><pre><code><code>RTC = persistent clock for boot/shutdown
</code></code></pre><div><hr></div><h2><strong>3. PHC (PTP Hardware Clock)</strong></h2><p>A PHC is a hardware clock exposed by a PTP-capable network interface.</p><p>This is where Linux gets especially interesting.</p><p>A PHC lives on the NIC, much closer to the real transmit/receive event than the normal system clock. That is why it matters for precise synchronization.</p><p>PHCs usually appear as device files like:</p><pre><code><code>/dev/ptp0
/dev/ptp1
</code></code></pre><h3><strong>Quick check</strong></h3><p>List PHC devices:</p><pre><code><code>ls -l /dev/ptp*
</code></code></pre><p>Check which PHC belongs to a NIC and whether hardware timestamping is supported:</p><pre><code><code>ethtool -T eth0
</code></code></pre><h3><strong>Mental model</strong></h3><pre><code><code>PHC = hardware clock on the NIC, used for precise packet timing
</code></code></pre><div><hr></div><h2><strong>One picture: how they relate</strong></h2><pre><code><code>RTC
  |
  | used mainly at boot / shutdown
  v
System clock (CLOCK_REALTIME)
  ^
  |
  | phc2sys can sync between them
  |
PHC (/dev/ptpX on NIC)
  ^
  |
  | ptp4l syncs PHC to PTP network
  |
PTP network / Grandmaster
</code></code></pre><p>A rough summary:</p><ul><li><p><strong>RTC</strong> keeps time while power is off</p></li><li><p><strong>system clock</strong> is what most software reads</p></li><li><p><strong>PHC</strong> is the precision clock on the NIC</p></li></ul><div><hr></div><h2><strong>How Linux syncs time with NTP</strong></h2><p>In the NTP world, Linux usually synchronizes the <strong>system clock</strong>.</p><p>Common tools:</p><ul><li><p>chronyd</p></li><li><p>systemd-timesyncd</p></li><li><p>older setups may use ntpd</p></li></ul><h3><strong>Check whether NTP is active</strong></h3><pre><code><code>timedatectl
</code></code></pre><h3><strong>If you use chrony</strong></h3><p>Show synchronization state:</p><pre><code><code>chronyc tracking
</code></code></pre><p>Show time sources:</p><pre><code><code>chronyc sources -v
</code></code></pre><h3><strong>Mental model</strong></h3><pre><code><code>NTP servers
   |
   v
chronyd / timesyncd / ntpd
   |
   v
System clock
</code></code></pre><p>In other words, NTP usually targets the <strong>system clock</strong>, not the PHC.</p><div><hr></div><h2><strong>How Linux syncs time with PTP</strong></h2><p>In the PTP world, Linux often follows a two-step path:</p><ol><li><p>synchronize the <strong>PHC</strong> to the PTP network</p></li><li><p>synchronize the <strong>system clock</strong> to that PHC</p></li></ol><p>The two main tools are:</p><ul><li><p>ptp4l</p></li><li><p>phc2sys</p></li></ul><div><hr></div><h2><strong>ptp4l: syncing the NIC-side clock</strong></h2><p>ptp4l speaks PTP on the network and usually synchronizes the PHC associated with the interface.</p><p>Typical example:</p><pre><code><code>sudo ptp4l -i eth0 -m
</code></code></pre><p>Meaning:</p><ul><li><p>i eth0 &#8594; use interface eth0</p></li><li><p>m &#8594; print log messages to stdout</p></li></ul><h3><strong>Mental model</strong></h3><pre><code><code>PTP Grandmaster / network
        |
        v
      ptp4l
        |
        v
PHC on eth0 (/dev/ptpX)
</code></code></pre><p>This is usually where the NIC-side precision timing gets aligned to the network timing domain.</p><div><hr></div><h2><strong>phc2sys: syncing one local clock to another</strong></h2><p>Once the PHC is synchronized, the rest of the machine may still be reading the system clock.</p><p>That is why phc2sys exists.</p><p>Its job is to synchronize one local clock to another.</p><p>The most common use is:</p><p><strong>PHC &#8594; system clock</strong></p><p>Example:</p><pre><code><code>sudo phc2sys -s eth0 -c CLOCK_REALTIME -m
</code></code></pre><p>Meaning:</p><ul><li><p>s eth0 &#8594; source is the PHC associated with eth0</p></li><li><p>c CLOCK_REALTIME &#8594; target is the system clock</p></li><li><p>m &#8594; print status</p></li></ul><h3><strong>Mental model</strong></h3><pre><code><code>PHC on NIC
   |
   v
phc2sys
   |
   v
System clock
</code></code></pre><p>This is the classic Linux PTP flow.</p><div><hr></div><h2><strong>The classic Linux PTP pipeline</strong></h2><pre><code><code>PTP Grandmaster
      |
      v
  [ network ]
      |
      v
NIC hardware timestamping
      |
      v
    ptp4l
      |
      v
PHC (/dev/ptp0) synchronized to PTP domain
      |
    phc2sys
      |
      v
System clock (CLOCK_REALTIME)
      |
      v
Applications / logs / services
</code></code></pre><p>This is one of the most useful diagrams to keep in your head.</p><div><hr></div><h2><strong>PHC to system clock, or system clock to PHC?</strong></h2><p>In precision setups, the common direction is:</p><pre><code><code>PHC &#8594; system clock
</code></code></pre><p>because the PHC is closer to the wire and usually the better timing source in a PTP environment.</p><p>That is why this is a common command:</p><pre><code><code>sudo phc2sys -s eth0 -c CLOCK_REALTIME -m
</code></code></pre><p>But phc2sys is more general than that. It can synchronize clocks in other directions too.</p><div><hr></div><h2><code>ts2phc:</code>Syncing PHCs</h2><p>Linux can synchronize hardware clocks too, but the right tool depends on the synchronization path.</p><p>If the goal is to make the <strong>system clock</strong> follow a PHC, the usual tool is <code>phc2sys</code>. If the goal is to synchronize <strong>one or more PHCs from an external timestamp source</strong> such as PPS or GNSS, the usual tool is <code>ts2phc</code>. <code>ts2phc</code> is specifically designed to synchronize PHCs to external timestamp signals, and it can distribute one source to multiple PHCs.</p><p>Conceptually:</p><pre><code>External PPS / GNSS / timestamp source &#8594; ts2phc &#8594; one or more PHCs
PHC &#8594; phc2sys &#8594; system clock</code></pre><p>A typical <code>ts2phc</code> command looks like this:</p><pre><code>sudo ts2phc -s eth0 -c eth1 -m</code></pre><p>In this form:</p><ul><li><p><code>-s eth0</code> selects the source clock or source interface,<br></p></li><li><p><code>-c eth1</code> selects a PHC to synchronize,<br></p></li><li><p><code>-m</code> prints log messages to stdout. The tool also allows multiple <code>-c</code> options if you want to synchronize more than one PHC from the same source. <br></p></li></ul><p>This is less common than PHC-to-system-clock synchronization, but it matters in systems where multiple hardware clocks need to follow the same precise external reference. </p><div><hr></div><h2><strong>Where software timestamping fits</strong></h2><p>Not every NIC has hardware timestamping. Not every system needs that level of precision.</p><p>Linux can still synchronize clocks using software timestamps, and for many use cases that is completely fine.</p><p>But the practical tradeoff remains the same:</p><ul><li><p><strong>hardware timestamping</strong> gives measurements closer to the real wire event</p></li><li><p><strong>software timestamping</strong> includes more delay and variation from the OS path</p></li></ul><p>That is why software timestamping usually belongs to a looser precision budget, while hardware timestamping is what unlocks much tighter synchronization.</p><div><hr></div><h2><strong>The most useful commands at a glance</strong></h2><h3><strong>Check system time</strong></h3><pre><code><code>date
timedatectl
</code></code></pre><h3><strong>Check RTC</strong></h3><pre><code><code>sudo hwclock --show
</code></code></pre><h3><strong>List PHC devices</strong></h3><pre><code><code>ls -l /dev/ptp*
</code></code></pre><h3><strong>Check NIC timestamping support</strong></h3><pre><code><code>ethtool -T eth0
</code></code></pre><h3><strong>Run PTP on an interface</strong></h3><pre><code><code>sudo ptp4l -i eth0 -m
</code></code></pre><h3><strong>Sync system clock from PHC</strong></h3><pre><code><code>sudo phc2sys -s eth0 -c CLOCK_REALTIME -m
</code></code></pre><h3><strong>Sync PHCs</strong></h3><pre><code><code>sudo ts2phc -s eth0 -c eth1 -m</code></code></pre><h3><strong>Check chrony state</strong></h3><pre><code><code>chronyc tracking
chronyc sources -v
</code></code></pre><div><hr></div><h2><strong>The one table worth remembering</strong></h2><pre><code><code>RTC ------------------&gt; system time at boot / shutdown
NTP daemon -----------&gt; system clock
ptp4l ----------------&gt; PHC
phc2sys --------------&gt; PHC &lt;-&gt; system clock
External PPS / GNSS / timestamp source &#8594; ts2phc &#8594; one or more PHCs
hwclock --systohc ----&gt; system clock -&gt; RTC
hwclock --hctosys ----&gt; RTC -&gt; system clock
</code></code></pre><div><hr></div><h2><strong>The biggest practical lesson</strong></h2><p>When somebody says:</p><p><strong>&#8220;The machine is synchronized.&#8221;</strong></p><p>That is usually too vague.</p><p>The useful follow-up question is:</p><p><strong>Which clock is synchronized?</strong></p><ul><li><p>the RTC?</p></li><li><p>the system clock?</p></li><li><p>the PHC?</p></li><li><p>and which one is the application actually using?</p></li></ul><p>That one question prevents a lot of confusion.</p><div><hr></div><h2><strong>One-screen summary</strong></h2><pre><code><code>RTC
- battery-backed motherboard clock
- keeps time while machine is off
- checked with: hwclock --show

System clock
- main OS wall clock
- used by most applications
- checked with: date, timedatectl
- synchronized by: NTP or by phc2sys from PHC

PHC
- hardware clock on a PTP-capable NIC
- represented as: /dev/ptpX
- checked with: ls /dev/ptp*, ethtool -T eth0
- synchronized by: ptp4l

Typical Linux PTP flow
PTP network -&gt; ptp4l -&gt; PHC -&gt; phc2sys -&gt; system clock
</code></code></pre><div><hr></div><h2><strong>Final note</strong></h2><p>If you made it all the way here, you did not just read a few articles about clocks.</p><p>You built a real mental model of time in computing &#8212; from first principles, to clocks inside a machine, to synchronization across networks, to the actual Linux entities and tools that make it work in practice.</p><p>That already puts you far ahead of most engineers who touch these systems.</p><p>You now know enough to stop treating time as a mysterious background feature and start seeing it for what it really is: infrastructure, measurement, coordination, and engineering.</p><p>And it was the final piece: a practical cheat sheet you can actually use. Bookmark it. Return to it. Break things with it. Fix things with it.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A Practical Guide to Time for Developers: Part 3 — How Computers Share Time]]></title><description><![CDATA[How computers synchronize time with NTP, PTP, and timestamping in Linux]]></description><link>https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-ec8</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-ec8</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Mon, 16 Mar 2026 15:42:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0JCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0JCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0JCc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 424w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 848w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 1272w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0JCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp" width="1000" height="420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:420,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:139314,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0JCc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 424w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 848w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 1272w, https://substackcdn.com/image/fetch/$s_!0JCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Intro</strong></h2><p>Every action film has that scene just before the military operation begins.</p><p>&#8220;Let&#8217;s sync our watches,&#8221; the captain says.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The idea is simple: if every stage of the plan depends on precise coordination, everyone involved has to act in sync and according to the same timeline.</p><p>A while ago, we started our journey with a practical goal: synchronizing the time of many computers. To get there, we first had to understand what time actually is and learn the basic glossary needed to speak the language of this problem and its solutions. In the first part (<a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers</a>), we explored the foundations of time itself. In the second (<a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec</a>), we looked at how time is kept and tracked inside a single computer. Now we are finally ready to move to the next step: how many computers share time with each other.</p><p>Keeping precise time across many computers is not unusual or exotic. In fact, the opposite is true. Distributed systems, industrial networks, telecom infrastructure, financial systems, and measurement environments often involve hundreds or thousands of devices that must stay synchronized within a clearly defined precision budget.</p><p>Let&#8217;s imagine a wind farm. Each turbine is around 120 meters tall and has a warning light at the top. To make the turbines visible to planes at night, the lights should blink every second. And to make the whole field clearly visible as one coordinated structure, those lights should blink simultaneously.</p><p>How can we make that happen?</p><p>The obvious answer is: the turbines need synchronized clocks.</p><p>But how we can keep them in sync for hundreds and thousands devices with amazing accuracy?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!huJQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!huJQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!huJQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:336712,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!huJQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!huJQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd454b3f2-43d7-45e4-8300-54fdef86c487_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let&#8217;s see! </p><h2><strong>Just sync the clocks once?</strong></h2><p>Let&#8217;s start with the most obvious idea: set the same time on all clocks once, and the problem is solved.</p><p>Unfortunately, it does not work that way.</p><p>Every clock has physical behavior behind it. Its frequency is affected by things like oscillator quality, temperature, aging, and other environmental factors. As a result, every clock drifts in its own way. Some also exhibit short-term fluctuations, often described as wander. These effects cannot be fully eliminated, and in practice they mean that two clocks will slowly diverge even if they start perfectly aligned.</p><p>That turns synchronization from a one-time setup task into a continuous process.</p><p>Clocks do not just need to be set. They need to be kept aligned over time. In practice, that means measuring the difference between clocks again and again, then adjusting their time and, more importantly, their rate so that they do not immediately drift apart again.</p><p>You can see how quickly clocks with different rates and wander fall out of sync, even when they start at exactly the same time:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zdtw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zdtw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 424w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 848w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 1272w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zdtw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif" width="644" height="512" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:512,&quot;width&quot;:644,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:510655,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zdtw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 424w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 848w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 1272w, https://substackcdn.com/image/fetch/$s_!zdtw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b507daa-5124-42c2-93c7-fb7792096dc3_644x512.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Feel free to explore the interactive simulation I created for this exact scenario and see how quickly clocks drift out of sync: https://dmytrohuzz.github.io/interactive_demo/clock_sync/index.html</p><h2><strong>From setting time to synchronization</strong></h2><p>Once we accept that clocks drift, a one-time setup stops looking like a real solution. Time is not something you assign once. It is something you keep aligned.</p><p>In practice, synchronization is a feedback loop. A machine compares its local clock to some reference, estimates the difference, adjusts its own clock, and repeats the process again and again.</p><p>The difficult part is that machines cannot read each other&#8217;s clocks directly. They can only communicate over a network, and the network adds delay and uncertainty. So synchronization protocols work indirectly: they exchange messages with timestamps and use those timestamps to estimate the relationship between clocks.</p><p>At the center of that estimate are two questions:</p><ul><li><p>how far apart are the clocks?</p></li><li><p>how much of the observed difference comes from network delay rather than clock error?</p></li></ul><p>This sounds simple in theory, but the key idea only becomes clear once we walk through it step by step.</p><p>A basic synchronization exchange gives us four timestamps:</p><ul><li><p><strong>t1</strong> &#8212; the client sends a request</p></li><li><p><strong>t2</strong> &#8212; the server receives that request</p></li><li><p><strong>t3</strong> &#8212; the server sends a response</p></li><li><p><strong>t4</strong> &#8212; the client receives the response</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Dzv1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Dzv1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 424w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 848w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 1272w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Dzv1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif" width="644" height="838" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc664f7c-78b9-461f-a226-36da449bde39_644x838.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:838,&quot;width&quot;:644,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:493203,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Dzv1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 424w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 848w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 1272w, https://substackcdn.com/image/fetch/$s_!Dzv1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc664f7c-78b9-461f-a226-36da449bde39_644x838.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><p>These four timestamps are the heart of the whole mechanism. Once this pattern becomes intuitive, the rest of the synchronization topic becomes much easier to follow.</p><p>Now imagine the exchange from the client&#8217;s point of view.</p><p>The client sends a request at local time <strong>t1 = 00:00</strong>.</p><p>Later, it receives the response at local time <strong>t4 = 00:04</strong>.</p><p>Inside that response, the server includes its own timestamps:</p><ul><li><p>it received the request at <strong>t2 = 00:06</strong></p></li><li><p>it sent the response at <strong>t3 = 00:06</strong></p></li></ul><p>At first glance, this looks strange. How can the server receive the request at 00:06 if the client sent it at 00:00, and the whole round trip took only four seconds on the client side?</p><p>The answer is simple: <strong>t1 and t2 do not belong to the same timeline</strong>.</p><p>The client clock and the server clock are different local views of time. What synchronization tries to estimate is the relation between those two timelines. In other words, it tries to answer this question:</p><p><strong>If the client sees one moment as 00:00, what does the server call that same moment?</strong></p><p>That relationship is what we call <strong>offset</strong>.</p><p>This is the most important insight in the whole topic: the difference t2 - t1 does not represent only network delay. It contains two things mixed together:</p><ul><li><p>packet travel time</p></li><li><p>clock offset between client and server</p></li></ul><p>A useful way to think about it is with time zones.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EBqI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EBqI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EBqI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1751180,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EBqI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!EBqI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca685d13-2c0c-45ca-85cf-472d423dab3a_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Imagine you leave one city at local time 00:00, travel to another city, and arrive when the local clock there shows 14:00. Then you immediately turn around and come back, arriving home when your original city&#8217;s clock shows 20:00.</p><p>Now suppose the travel time is the same in both directions.</p><p>The first leg, from your city to the other one, includes:</p><p><strong>travel time + time-zone difference</strong></p><p>The return leg includes:</p><p><strong>travel time - time-zone difference</strong></p><p>So if the outward journey appears shorter or longer than the return journey, that difference tells you something about the offset between the two local clocks.</p><p>This is exactly what synchronization protocols exploit.</p><p>Under the usual symmetric-delay assumption, the offset can be estimated as:</p><p><code>offset = ((t2 - t1) + (t3 - t4)) /2</code></p><p>and the round-trip delay as:</p><p><code>delay = (t4 - t1) - (t3 - t2)</code></p><p>The first formula separates clock offset from the two directions of travel. The second removes the server&#8217;s processing time and leaves only the network round-trip time.</p><p>So synchronization is not about directly copying time from one machine to another. It is about observing message exchanges, separating delay from clock difference, and then correcting the local clock based on that estimate.</p><p>That is the core idea behind the whole topic.</p><p>Feel free to play with the simulation here:</p><p><a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/clock_sync_explained">https://dmytrohuzz.github.io/interactive_demo/clock_sync/clock_sync_explained</a></p><div><hr></div><h2><strong>NTP and PTP: two ways to synchronize clocks</strong></h2><p>Over time, two major protocol families became the standard answers to the synchronization problem: <strong>NTP</strong> and <strong>PTP</strong>.</p><p>Both solve the same core problem: a machine cannot read another machine&#8217;s clock directly, so it has to infer the difference by exchanging timestamped messages over a network. From those timestamps, it estimates clock offset and network delay, then adjusts the local clock toward a reference.</p><p>The difference is not the basic idea, but the precision target and the environment they are designed for.</p><h3><strong>NTP: practical synchronization for general systems</strong></h3><p><strong>NTP</strong> &#8212; the Network Time Protocol &#8212; is the general-purpose approach. It is designed to keep clocks reasonably aligned across ordinary systems and ordinary networks.</p><p>Its main principle is simple: a client exchanges request and response messages with a time server, records timestamps on both sides, estimates round-trip delay and clock offset, and then gradually disciplines its own clock. It repeats this process continuously, using multiple measurements to smooth out noise and avoid reacting too aggressively to one bad sample.</p><p>That makes NTP a good fit for:</p><ul><li><p>logs and observability</p></li><li><p>authentication and certificate validation</p></li><li><p>scheduled jobs</p></li><li><p>general wall-clock correctness across servers and infrastructure</p></li></ul><p>NTP does not assume a perfect network. It is built for real environments, where delays vary, paths are not perfectly symmetric, and hosts are under changing load. Its strength is robustness, not extreme precision.</p><p>[<a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/ntp_visualized.html">Interactive Demo</a>]</p><h3><strong>PTP: tighter synchronization for controlled environments</strong></h3><p><strong>PTP</strong> &#8212; the Precision Time Protocol &#8212; targets systems where much tighter agreement between clocks is required.</p><p>Its principle is similar to NTP: devices exchange timing messages, estimate offset and delay, and adjust local clocks. But PTP is designed for local precision networks, where the entire timing path is treated more carefully. In practice, this often means hardware timestamping, PTP-aware switches, and a dedicated timing hierarchy built around a grandmaster clock distributing time to other devices.</p><p>PTP is commonly used in:</p><ul><li><p>industrial and automation systems</p></li><li><p>telecom networks</p></li><li><p>audio and video systems</p></li><li><p>measurement systems</p></li><li><p>finance</p></li><li><p>power and substation environments</p></li></ul><p>PTP is not just &#8220;a more accurate NTP.&#8221; It usually operates in a different class of environment, with tighter timing requirements and more deliberate infrastructure support.</p><p>[<a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/ptp_visualized.html">Interactive Demo</a>]</p><h3><strong>Different tools for different timing budgets</strong></h3><p>So NTP and PTP are not really rivals. They are different engineering choices.</p><p>If the goal is to keep ordinary systems aligned to real time well enough for general infrastructure behavior, NTP is usually the right tool.</p><p>If the goal is to keep clocks tightly aligned in a local timing domain where timing quality directly affects correctness, event ordering, or measurement precision, PTP is often the better fit.</p><p>The key point is this: both protocols depend on timestamp exchange, but the quality of synchronization depends heavily on how those timestamps are produced.</p><p>And that leads to the next question: <strong>where exactly was the timestamp taken?</strong></p><p>This is where timestamping location &#8212; in software or in hardware &#8212; starts to matter.</p><h2><strong>Why timestamp location changes everything</strong></h2><p>At this point, NTP and PTP may still look like protocol problems: exchange messages, estimate offset, correct the clock.</p><p>But in practice, a large part of synchronization quality depends on something more physical:</p><p><strong>where exactly is the timestamp taken?</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Nt1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Nt1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Nt1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:223519,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191125080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Nt1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9Nt1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9057a893-8302-4c5c-bf0a-4997fbb9a8fd_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>That matters because a packet does not appear in software at the exact moment it hits the wire. Between the real network event and the moment the operating system records a timestamp, the packet may pass through the NIC, driver, kernel, interrupt handling, scheduling, and software processing. Every one of those layers can add delay and variation.</p><p>So two timestamps may look equally precise as numbers while representing very different physical moments.</p><h3><strong>Software timestamping</strong></h3><p>With software timestamping, the timestamp is recorded somewhere in the software stack after the packet has already passed through part of the system.</p><p>That makes software timestamping widely available and easy to use, but it also means the measurement includes more uncertainty:</p><ul><li><p>interrupt latency</p></li><li><p>kernel and driver delay</p></li><li><p>scheduling effects</p></li><li><p>queueing and system load</p></li></ul><p>As a result, a software timestamp often reflects when the system handled the packet, not the exact moment the packet crossed the network interface.</p><h3><strong>Hardware timestamping</strong></h3><p>With hardware timestamping, the timestamp is recorded much closer to the real transmit or receive event, typically inside the NIC itself.</p><p>This removes a large part of the software-induced uncertainty and makes the measurement more stable and repeatable. The closer the timestamp is to the actual wire event, the more useful it becomes for precise synchronization.</p><p>That is one of the main reasons PTP can achieve much better accuracy in the right environment: not only because of the protocol itself, but because it is often paired with hardware timestamping and a more carefully controlled timing path.</p><p>So the practical precision limit is not defined by the protocol name alone. It depends on the full measurement path.</p><p>A good rule of thumb is simple:</p><p><strong>the closer the timestamp is to the wire, the better the synchronization can be.</strong></p><div><hr></div><h2><strong>Summary</strong></h2><p>A single computer can keep time locally. A distributed system has a harder task: many machines must keep time together.</p><p>That is why simply setting clocks once is not enough. Real clocks drift, so synchronization has to be continuous. Protocols such as <strong>NTP</strong> and <strong>PTP</strong> address this by exchanging timestamped messages, estimating clock offset and network delay, and repeatedly steering local clocks toward a reference.</p><p>But protocol choice is only part of the story. In practice, synchronization quality also depends heavily on where timestamps are taken. A timestamp captured deep in software carries more uncertainty than one captured close to the physical network event.</p><p>So if this part was about the general idea of shared time &#8212; why it matters, why it is difficult, and how systems approach it &#8212; the next part will move from principle to implementation.</p><p>We will look at how Linux actually does this in practice: NICs, software and hardware timestamping, PHCs, and the tools that connect them into a real synchronization stack.</p><div><hr></div><h2>Next part:</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;42da1442-0c6d-4034-a99c-039a91a7b2de&quot;,&quot;caption&quot;:&quot;If you got here and made it through the previous articles, you have already done the hard part. You are basically a time guru now.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 4 -The Linux Time Sync Cheat Sheet&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-19T16:36:51.707Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!MEgQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6acd666-fe66-4163-a98f-62e564fa6c7e_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-314&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:191493632,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Cloud Sells Geographical Abstraction. Critical Systems Buy Geographical Proximity.]]></title><description><![CDATA[What recent disruptions around AWS infrastructure in the Gulf reveal about latency, sovereignty, and the hidden geography of modern cloud systems]]></description><link>https://www.dmytrohuz.com/p/cloud-sells-geographical-abstraction</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/cloud-sells-geographical-abstraction</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 15 Mar 2026 20:33:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ovmG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ovmG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ovmG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ovmG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5688691-d876-494b-a962-f12f790f6098_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1702675,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/191062210?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ovmG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!ovmG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5688691-d876-494b-a962-f12f790f6098_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We like to talk about &#8220;the cloud&#8221; as if software has escaped geography.</p><p>The interface encourages that illusion. You choose a region, deploy a service, replicate some data, add a failover plan, and the system starts to feel abstract. Compute is elastic. Storage is managed. Infrastructure appears to have become location-independent.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>But critical systems do not really stop living somewhere.</p><p>They still sit on top of power, cooling, fiber, telecom topology, legal jurisdiction, operational teams, and the physics of latency. And the more a system becomes real &#8212; real users, real regulation, real response-time constraints, real business consequence &#8212; the more geography tends to re-enter the design.</p><p>That is the part cloud culture hides well: cloud abstracts geography at the interface level, but many important systems reintroduce geography at the architecture level.</p><p>Recent disruptions around AWS infrastructure in the Gulf make that harder to ignore. Reuters reported issues affecting AWS data centers in the UAE and Bahrain amid Iranian strikes, including problems tied to power and connectivity. Separate Reuters reporting also described incidents in the UAE involving drone interceptions and falling debris in Fujairah. Whether one looks at these as isolated disruptions or as signals of a broader shift, the underlying point is the same: data centers are no longer just &#8220;IT facilities.&#8221; They increasingly sit inside the same map of strategic exposure as energy, ports, and communications infrastructure.</p><p>That does not just make cloud infrastructure more important. It makes geography more important.</p><h2><strong>The abstraction is useful. The abstraction is also misleading.</strong></h2><p>This is not an anti-cloud argument.</p><p>Cloud abstractions are powerful because they compress operational complexity. They let teams build and scale systems without owning every layer directly. They make certain kinds of resilience easier to implement. They lower coordination cost. They make global software feel tractable.</p><p>But useful abstractions have a habit of concealing the substrate.</p><p>In cloud, the concealed substrate is not just hardware. It is geography.</p><p>A lot of modern software is designed as if &#8220;where it runs&#8221; is a secondary implementation detail. For some workloads, that is mostly true. For critical ones, it often is not.</p><p>Latency-sensitive systems want to be physically closer to where requests originate. Regulated systems want data to remain in specific jurisdictions. Operationally important systems want predictable local integration with identity, networking, observability, and response teams. Once those requirements become strong enough, the architecture starts to pull back toward place.</p><p>So while the cloud sells a kind of placelessness, critical systems often buy proximity instead.</p><h2><strong>Latency quietly defeats the fantasy of placeless compute</strong></h2><p>The first force that brings geography back is latency.</p><p>Latency is often described as just a technical metric. In practice, it is a design constraint that pushes infrastructure back into physical space. If response time matters, distance matters. If user experience matters, path length matters. If control loops matter, locality matters.</p><p>This is not theoretical. AWS explicitly recommends choosing regions close to users for latency reasons, and sells Local Zones and Wavelength for workloads that need to run nearer to end users and telecom networks. Microsoft says similar things about Azure Extended Zones for low-latency and data-residency-sensitive workloads.</p><p>At the interface level, cloud encourages us to think in regions, services, and APIs. At the architecture level, latency-sensitive systems often say something much simpler:</p><p><strong>put the system near the place where the consequences happen.</strong></p><p>That is already a partial collapse of abstraction.</p><h2><strong>Sovereignty brings geography back a second time</strong></h2><p>The second force is law.</p><p>Even if a workload could technically run anywhere, that does not mean it is allowed to. Data residency, sector-specific regulation, national security requirements, and jurisdictional constraints all push architecture back toward geography. That is why sovereign cloud offerings now exist at all. AWS has launched a European Sovereign Cloud specifically for stricter residency and operational autonomy requirements, while Google and Microsoft document controls for customers that cannot treat geography as interchangeable.</p><p>This is a useful correction to one of cloud&#8217;s most seductive promises.</p><p>People say cloud gives you geographic flexibility. Sometimes it does. But in important systems, law can be just as constraining as physics. The workload may appear abstract, but its permitted runtime geography can still be narrow.</p><p>Once again, cloud did not remove geography. It pushed geography behind a cleaner control plane.</p><h2><strong>The result is hidden concentration</strong></h2><p>This is where the systems point matters.</p><p>Cloud encourages a mental model of distribution. Critical systems often rebuild concentration underneath that model.</p><p>Not recklessly. Often for completely rational reasons:</p><ul><li><p>lower latency</p></li><li><p>data residency</p></li><li><p>operational simplicity</p></li><li><p>cost structure</p></li><li><p>regional business requirements</p></li><li><p>local ecosystem dependence</p></li></ul><p>Each decision can make sense in isolation. But in aggregate, a system may look globally abstract while the important runtime, data, and dependency paths remain tied to a much narrower geography.</p><p>That is where hidden fragility comes from.</p><p>The problem is not that cloud is fake. The problem is that cloud can make concentration feel more diversified than it really is.</p><p>A clean interface can hide a concentrated substrate.</p><p>And once that substrate becomes strategically important, the illusion gets expensive.</p><p>That is why the AWS example in the Gulf matters beyond the specific incident. It is not only a story about one provider or one region. It is a visible reminder that cloud infrastructure now sits close enough to the center of commerce, communications, and increasingly AI-related compute demand that it begins to resemble critical infrastructure in the older sense of the term. And critical infrastructure is always geographic.</p><h2><strong>Good architecture can counter this &#8212; but not by accident</strong></h2><p>Cloud does not automatically imply weak geographic resilience. In fact, major providers explicitly support multi-region architectures, active-active designs, and cross-region failover. Google recommends multi-region deployments to improve latency and availability, and AWS Well-Architected explicitly warns against consolidating all workload resources into one geographic location. AWS also documents active-active multi-region patterns for low-latency and high-availability systems.</p><p><strong>Cloud does not diversify geography by default just because it is cloud.</strong></p><p>That has to be designed.</p><p>And the design often runs against real pressures:</p><ul><li><p>performance wants locality</p></li><li><p>regulation wants jurisdictional specificity</p></li><li><p>operations want manageable complexity</p></li><li><p>economics want concentration where scale is cheapest</p></li></ul><p>Resilience is not the natural resting state. It is a deliberate counterweight to optimization pressure.</p><p>That is the systems lesson.</p><h2><strong>Why this matters more now</strong></h2><p>This matters more than it used to because modern systems are becoming more dependent on concentrated compute.</p><p>As more of business, communications, platforms, and AI workloads sit on top of large cloud regions and data center clusters, the abstraction becomes more consequential &#8212; and more dangerous to misunderstand. The cloud did not make geography disappear. It made geography easier to ignore until latency, law, outage, or conflict forces it back into view.</p><p>At that point, the important question is no longer whether cloud is &#8220;really distributed.&#8221;</p><p>The better question is:</p><p><strong>How much geographic reality has your architecture quietly reintroduced underneath the abstraction &#8212; and have you designed resilience there on purpose, or just assumed the word cloud already did that for you?</strong></p><p>Because that assumption is where a lot of hidden concentration begins.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[I built ac-trace to question the trust we place in passing tests]]></title><description><![CDATA[ac-trace is an early, narrow experiment in checking whether tested systems are actually defended]]></description><link>https://www.dmytrohuz.com/p/i-built-ac-trace-to-question-the</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/i-built-ac-trace-to-question-the</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Mon, 09 Mar 2026 16:01:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!N-RY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N-RY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N-RY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N-RY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1155279,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/190389898?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N-RY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!N-RY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369aff60-0ae8-46fb-8e16-dc86e3bedde7_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>AI-assisted coding is making one part of software development much faster than another: it is becoming easier to generate code and tests, but not easier to know what is actually protected.</p><p>That gap worries me. A green test suite can look convincing. Coverage can look convincing too. But neither one proves that the acceptance criteria &#8212; the behaviors the system is supposed to guarantee &#8212; are truly defended. And when AI helps produce both the implementation and the tests at speed, it becomes much easier to mistake test activity for real confidence.</p><p>That is why I built <strong><a href="https://github.com/DmytroHuzz/ac-trace">ac-trace</a></strong>, a new open-source tool.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><p>The problem is simple. Teams often treat these as roughly the same thing: tests are passing, code is covered, therefore the requirement is safe. But those are different signals. Code can be exercised without the important behavior being strongly checked. Tests can pass while the actual acceptance criterion is still weakly defended.</p><p>A realistic example: imagine a billing service with a rule that premium users must never be charged above their monthly cap.</p><p>You may have tests for invoice creation. You may have tests that run the premium billing path. You may even hit the exact function where the cap logic lives, so coverage looks good. But if someone removes the cap check or flips the comparison and the tests still pass, then the requirement was never really protected. The code ran. The suite stayed green. The acceptance criterion still had a confidence gap.</p><p>This becomes more dangerous with AI-assisted coding.</p><p>AI is very good at producing plausible code and plausible tests. That is useful. But it also lowers the cost of producing a large amount of evidence that looks reassuring. More tests, more mocks, more fixtures, more green pipelines &#8212; without a proportional increase in justified confidence. The faster teams generate software artifacts, the easier it becomes to confuse speed and volume with protection.</p><p>So I wanted a tool that asks a more specific question: not just whether tests exist, and not just whether code is covered, but whether the tests actually defend the acceptance criteria they are supposed to protect.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZWTg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZWTg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZWTg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:130367,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/190389898?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZWTg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZWTg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba3b4e-398d-46c5-9d06-07f1bc282510_1536x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong><a href="https://github.com/DmytroHuzz/ac-trace">ac-trace</a></strong> maps acceptance criteria to code and tests, then mutates the mapped code to check whether the linked tests actually catch the breakage. In simple terms: if a requirement is really protected, then deliberately breaking the relevant implementation should cause the relevant tests to fail.</p><p>The current scope is intentionally narrow. Right now, <a href="https://github.com/DmytroHuzz/ac-trace">ac-trace</a> focuses on Python + pytest. It uses a YAML manifest, can infer links from annotated tests, and generates reports showing what was mapped and what happened when the mapped code was mutated.</p><p>This is an early experiment, not a grand claim. I am not trying to &#8220;solve testing.&#8221; I just want to make one important gap more visible: passing tests are often a weaker signal than teams think, and AI-assisted coding increases the risk of over-trusting them.</p><p>So this is the launch: <strong><a href="https://github.com/DmytroHuzz/ac-trace">ac-trace</a> is now open source</strong>.</p><p>If this problem sounds familiar, check out the repo. Try it on a small project. Tell me where it is useful, where it is naive, and where it should go next.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://github.com/DmytroHuzz/ac-trace&quot;,&quot;text&quot;:&quot;ac-trace REPO&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://github.com/DmytroHuzz/ac-trace"><span>ac-trace REPO</span></a></p>]]></content:encoded></item><item><title><![CDATA[A Practical Guide to Time for Developers: Part 2 — How one computer keeps time (Linux)]]></title><description><![CDATA[explained by one diagram]]></description><link>https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Thu, 05 Mar 2026 21:16:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JuSw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JuSw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JuSw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JuSw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1389057,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/190040669?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JuSw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!JuSw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Time on a computer <em>looks</em> simple: call now(), get a timestamp, move on.</p><p>In the <a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers">previous article</a>, we discussed the idea that time is just a single point on a timeline. The crucial part is defining <strong>which</strong> timeline that point belongs to.</p><p>For computers, this matters a lot. A system effectively works with two timelines: <strong>real time</strong> and <strong>boot time</strong>. You can convert between them, but they are different measuring systems and shouldn&#8217;t be used interchangeably&#8212;because each timeline serves a different purpose.</p><ul><li><p><strong>Real time</strong> answers: &#8220;What time is it in the real world right now?&#8221;</p></li><li><p><strong>Boot time</strong> answers: &#8220;How much time has passed since X (boot)?&#8221;</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fcPM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fcPM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 424w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 848w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 1272w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fcPM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png" width="514" height="338" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:338,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:20298,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/190040669?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fcPM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 424w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 848w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 1272w, https://substackcdn.com/image/fetch/$s_!fcPM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa96ed778-4a90-4e54-8bc1-812cb7fead06_514x338.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The computer has has an ecosystem - a few components: hardware and software to track and calculate different times in both timelines.</p><p>This part explains that ecosystem using one diagram, top to bottom. The diagram is the spine; everything else is commentary that makes it click: where ticks come from, what the hardware pieces do, why there are multiple &#8220;system times&#8221;, what suspend breaks, where interrupts fit, and how epoch nanoseconds become &#8220;Tuesday 14:03 in Vienna&#8221;.</p><p></p><p>Figure 1 &#8212; The whole pipeline</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!awcK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!awcK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 424w, https://substackcdn.com/image/fetch/$s_!awcK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 848w, https://substackcdn.com/image/fetch/$s_!awcK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 1272w, https://substackcdn.com/image/fetch/$s_!awcK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!awcK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png" width="1456" height="1674" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1674,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:972626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/190040669?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!awcK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 424w, https://substackcdn.com/image/fetch/$s_!awcK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 848w, https://substackcdn.com/image/fetch/$s_!awcK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 1272w, https://substackcdn.com/image/fetch/$s_!awcK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3d486e6-36f6-4d8c-ada9-5492fa001a70_3211x3692.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Four lanes:</p><ul><li><p><strong>RTC</strong>: survives power-off, keeps &#8220;wall time&#8221;</p></li><li><p><strong>CPU counter</strong> (often TSC): ticks while the CPU runs</p></li><li><p><strong>Kernel timekeeper</strong>: turns ticks into several clocks</p></li><li><p><strong>Userspace</strong>: calls clock_gettime() and formats time for humans</p></li></ul><p>Now: top &#8594; bottom.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1><strong>1) Boot &amp; Initialization Phase</strong></h1><h3><strong>1.1 RTC: the &#8220;battery clock&#8221;</strong></h3><p>At the top-left sits the RTC. Think of it as the tiny clock that keeps time while the computer is asleep or powered off. It usually stores calendar-ish values (year/month/day/hour/min/sec). It&#8217;s not &#8220;nanoseconds since 1970&#8221; by nature &#8212; that&#8217;s something software creates later.</p><p>RTC exists so the system doesn&#8217;t boot into the void. Without it, everything starts at &#8220;some default&#8221; until NTP/PTP (or a human) sets the time.</p><h3><strong>1.2 Turning RTC into &#8220;Unix time&#8221;</strong></h3><p>Next step: the kernel reads RTC and converts it into Unix epoch time (seconds + nanoseconds since 1970-01-01T00:00:00Z). That gives a sensible starting point for time-of-day.</p><p>This is still a <em>bootstrap</em> value. RTC isn&#8217;t a precision clock. It&#8217;s a &#8220;good enough to start&#8221; clock.</p><h3><strong>1.3 The CPU counter: ticks while running</strong></h3><p>Now the machine is awake, so Linux wants something faster and more stable than &#8220;ask the RTC all the time.&#8221; Enter the CPU/platform counter &#8212; the <strong>clocksource</strong>. On modern x86, that&#8217;s often the <strong>TSC</strong>.</p><p>Important mindset shift:</p><blockquote><p>TSC is not &#8220;the time.&#8221;</p><p>TSC is &#8220;how many ticks happened since some arbitrary start.&#8221;</p></blockquote><p>It&#8217;s a counter. It&#8217;s only meaningful after conversion.</p><h3><strong>1.4 Calibration: making ticks speak nanoseconds</strong></h3><p>Ticks are just ticks until the kernel knows the counter&#8217;s rate. That&#8217;s why the diagram shows calibration: &#8220;cycles per second&#8221;.</p><p>Linux maintains conversion parameters so it can cheaply do:</p><ul><li><p>delta ticks &#8594; delta nanoseconds</p></li></ul><p>That&#8217;s the key: Linux mostly cares about <strong>deltas</strong>.</p><h3><strong>1.5 Boot finishes by aligning the timelines</strong></h3><p>At the end of boot, two things are true:</p><ul><li><p>there&#8217;s an &#8220;elapsed since boot&#8221; timeline (monotonic) starting at 0</p></li><li><p>there&#8217;s a &#8220;wall clock&#8221; timeline (realtime) aligned to the RTC-derived epoch time</p></li></ul><p>The clean relationship is:</p><blockquote><p><strong>CLOCK_REALTIME = CLOCK_MONOTONIC + wall_clock_offset</strong></p></blockquote><p>At boot, the kernel chooses the offset so realtime matches RTC.</p><p>This one relationship explains half of the weirdness people hit later.</p><div><hr></div><h1><strong>2) Runtime Phase (Continuous Tracking)</strong></h1><h3><strong>2.1 Where ticks come from (without going into physics)</strong></h3><p>Under the hood, some oscillator ticks, hardware counts those ticks, and Linux reads the count. That&#8217;s it at the conceptual level.</p><p>The only thing worth memorizing here:</p><blockquote><p>The hardware gives ticks. The OS gives meaning.</p></blockquote><h3><strong>2.2 The kernel&#8217;s &#8220;working memory&#8221;</strong></h3><p>In the middle of the diagram there&#8217;s a box of variables. That box is basically the kernel&#8217;s timekeeping brain:</p><ul><li><p>tsc_now, tsc_last &#8212; current and previous counter snapshot</p></li><li><p>mult, shift &#8212; ticks&#8594;ns conversion</p></li><li><p>mono &#8212; accumulated elapsed time since boot</p></li><li><p>suspend_ns &#8212; time spent asleep (so BOOTTIME can include it)</p></li><li><p>wall_off &#8212; the offset that turns monotonic into realtime</p></li></ul><p>With those, Linux can build the clock APIs that user space expects.</p><div><hr></div><h1><strong>3) Time requests: clock_gettime() makes everything happen</strong></h1><p>This part of the diagram is the &#8220;action scene&#8221;:</p><ul><li><p>userspace calls clock_gettime(CLOCK_...)</p></li><li><p>kernel reads the counter (RDTSC in the TSC world)</p></li><li><p>kernel updates its state (delta ticks &#8594; delta ns &#8594; accumulate)</p></li><li><p>kernel returns the requested clock</p></li></ul><p>A useful mental shortcut:</p><blockquote><p>The kernel doesn&#8217;t need a metronome to keep time moving.</p><p>It can compute &#8220;now&#8221; on demand by reading a running counter.</p></blockquote><p>(Internally there are periodic activities too, but this is the clean model.)</p><h3><strong>The tiny update loop</strong></h3><p>The heartbeat is:</p><ul><li><p>delta_ticks = tsc_now - tsc_last</p></li><li><p>delta_ns = ticks_to_ns(delta_ticks)</p></li><li><p>mono += delta_ns</p></li></ul><p>Everything else is derived from mono.</p><div><hr></div><h1><strong>4) Kernel clocks: three timelines, three different promises</strong></h1><p>Now the diagram derives the clocks.</p><h3><strong>4.1 CLOCK_MONOTONIC &#8212; for durations</strong></h3><p>The diagram says:</p><blockquote><p>CLOCK_MONOTONIC = mono</p></blockquote><p>That&#8217;s the &#8220;elapsed time&#8221; clock.</p><p>It&#8217;s the clock to use for anything that needs to be sane even if wall time changes:</p><ul><li><p>timeouts</p></li><li><p>retries</p></li><li><p>rate limiting</p></li><li><p>latency measurements</p></li><li><p>&#8220;sleep for X&#8221;</p></li></ul><p>It doesn&#8217;t go backwards, and it doesn&#8217;t jump when someone sets the wall clock.</p><h3><strong>4.2 CLOCK_REALTIME &#8212; for human time</strong></h3><p>The diagram says:</p><blockquote><p>CLOCK_REALTIME = mono + wall_off</p><p><em>(setting time changes wall_off, not mono)</em></p></blockquote><p>This is <em>the</em> sentence.</p><p>Realtime is epoch-based wall time. It&#8217;s the one that becomes &#8220;2026-03-05 13:00:00&#8221;.</p><p>Because it must match the outside world, it&#8217;s adjustable (NTP/PTP/manual set), and that means it can jump. It&#8217;s great for timestamps, terrible for measuring elapsed time.</p><h3><strong>4.3 CLOCK_BOOTTIME &#8212; monotonic that includes sleep</strong></h3><p>The diagram says:</p><blockquote><p>CLOCK_BOOTTIME = mono + suspend_ns</p></blockquote><p>Suspend is where people get surprised: monotonic often pauses while the system sleeps. BOOTTIME exists for the &#8220;time since boot including sleep&#8221; definition.</p><div><hr></div><h1><strong>5) Power management: suspend/resume is where the split matters</strong></h1><p>During suspend:</p><ul><li><p>CPU isn&#8217;t running</p></li><li><p>counters may stop or aren&#8217;t sampled</p></li><li><p>mono doesn&#8217;t move (in the simple model)</p></li><li><p>real world keeps moving</p></li></ul><p>So the diagram does a clever but simple thing:</p><ul><li><p>store persistent time at suspend (often RTC)</p></li><li><p>store persistent time at resume</p></li><li><p>difference = sleep delta</p></li><li><p>add it to suspend_ns so BOOTTIME advances across sleep</p></li><li><p>keep wall clock aligned after resume (effectively by updating the offset)</p></li></ul><p>That&#8217;s why BOOTTIME exists and why wall time remains useful after sleep.</p><div><hr></div><h2><strong>6) From epoch nanoseconds to &#8220;Tuesday 14:03 in Vienna&#8221;</strong></h2><p>Kernel time is a number. Humans want a calendar.</p><p>On Linux, CLOCK_REALTIME is typically represented as <strong>nanoseconds since the Unix epoch</strong> (1970-01-01T00:00:00Z). It&#8217;s just an integer coordinate on a timeline. Converting it into &#8220;Tuesday 14:03 in Vienna&#8221; is a user-space job, and it happens in a few very specific steps.</p><h3><strong>6.1 Step 1 &#8212; split nanoseconds into seconds + remainder</strong></h3><p>Most time libraries work in &#8220;seconds since epoch&#8221; plus a fractional part:</p><ul><li><p>sec = epoch_ns / 1_000_000_000</p></li><li><p>nsec = epoch_ns % 1_000_000_000</p></li></ul><p>That split is practical: seconds are large-scale time, nanoseconds are the sub-second detail.</p><h3><strong>6.2 Step 2 &#8212; interpret seconds as UTC and create a UTC timestamp</strong></h3><p>At this point the number becomes &#8220;a moment&#8221; in UTC:</p><ul><li><p>utc_instant = epoch_seconds_to_utc(sec, nsec)</p></li></ul><h3><strong>6.3 Step 3 &#8212; convert UTC to a named time zone using tzdata rules</strong></h3><p>Now comes the real-world complexity.</p><p>A numeric offset like +01:00 is not a time zone. It&#8217;s just &#8220;the offset right now.&#8221; Real zones (like Europe/Vienna) are a <strong>ruleset</strong>: they include historical changes and DST transitions.</p><p>So the conversion is:</p><ul><li><p>local_instant = convert_utc_to_zone(utc_instant, &#8220;Europe/Vienna&#8221;, tzdata)</p></li></ul><p>That conversion does three things:</p><ol><li><p>finds the correct offset for that instant (+01:00 or +02:00, depending on DST and history)</p></li><li><p>applies that offset</p></li><li><p>produces local calendar fields (year/month/day/hour/min/sec) <em>plus</em> the offset</p></li></ol><p>This is why &#8220;time zones are formatting&#8221; is wrong: it&#8217;s not string styling, it&#8217;s rule evaluation.</p><h3><strong>6.4 Ambiguous and missing local times (DST pain in one minute)</strong></h3><p>DST creates two special situations that break naive systems:</p><p><strong>Ambiguous local time (fall back)</strong></p><p>The clock repeats an hour. The same local time occurs twice.</p><ul><li><p>Example: 2026-10-25 02:30 in many European zones can mean two different instants.</p></li></ul><p><strong>Missing local time (spring forward)</strong></p><p>The clock jumps forward. Some local times never occur.</p><ul><li><p>Example: 02:30 on the spring-forward day might not exist at all.</p></li></ul><p>Notice what happens here: converting <em>from UTC &#8594; local</em> is always unambiguous (UTC instants are unique). The pain happens when converting <em>from local &#8594; UTC</em> without enough context.</p><p>That&#8217;s why systems that store &#8220;local wall time&#8221; without a zone ID eventually end up in a fight with reality.</p><h3><strong>6.5 Step 4 &#8212; format for display or transport (ISO 8601 / RFC 3339)</strong></h3><p>After conversion, formatting is easy:</p><ul><li><p>UTC canonical log style: 2026-03-05T13:03:12.123456789Z</p></li><li><p>Local display style (with offset): 2026-03-05T14:03:12.123456789+01:00</p></li></ul><p>The important thing is that formatted output should preserve:</p><ul><li><p>the offset (or Z)</p></li><li><p>and ideally the zone context when it matters</p></li></ul><h3><strong>6.6 What should be stored vs what should be displayed</strong></h3><p>This is where many systems accidentally create &#8220;time debt.&#8221;</p><p><strong>Store (internally / in DB / across services):</strong></p><ul><li><p>an unambiguous instant:</p><ul><li><p>epoch timestamp (integer + unit), or</p></li><li><p>UTC/RFC3339 timestamp with Z</p></li></ul></li></ul><p><strong>Display (UI / reports):</strong></p><ul><li><p>convert to the user&#8217;s zone at the edge using tzdata.</p></li></ul><p><strong>If civil meaning matters (schedules, payroll, appointments):</strong></p><ul><li><p>store the <em>rule</em>, not just the instant:</p><ul><li><p>&#8220;every day at 09:00 Europe/Vienna&#8221;</p></li><li><p>plus the zone ID</p><p>Because recurring human schedules live in civil time and DST rules matter.</p></li></ul></li></ul><h3><strong>6.7 Tiny practical checklist (saves a lot of bugs)</strong></h3><ul><li><p>Use a <strong>zone ID</strong> (Europe/Vienna), not a fixed offset, for civil-time logic.</p></li><li><p>Keep timestamps in <strong>UTC-like canonical form</strong> internally.</p></li><li><p>Convert to local time only at the edges.</p></li><li><p>Treat &#8220;local naive timestamps&#8221; as incomplete data unless paired with a zone/ruleset.</p></li><li><p>When parsing timestamps, require either:</p><ul><li><p>Z, or</p></li><li><p>an explicit offset, or</p></li><li><p>a zone ID (for civil-time workflows).</p></li></ul></li></ul><div><hr></div><h1><strong>7) Compact model (matches the diagram)</strong></h1><p><strong>Variables</strong></p><ul><li><p>tsc_last, mult, shift</p></li><li><p>mono (ns since boot)</p></li><li><p>suspend_ns (ns spent suspended)</p></li><li><p>wall_off (epoch ns &#8722; mono)</p></li></ul><p><strong>Update step (on each read)</strong></p><ul><li><p>compute delta ticks from the clocksource</p></li><li><p>convert delta ticks &#8594; delta ns</p></li><li><p>accumulate monotonic time: mono += delta_ns</p></li></ul><p><strong>Clock readouts</strong></p><ul><li><p>CLOCK_MONOTONIC = mono</p></li><li><p>CLOCK_BOOTTIME = mono + suspend_ns</p></li><li><p>CLOCK_REALTIME = mono + wall_off <em>(setting time changes wall_off, not mono)</em></p></li></ul><div><hr></div><h2><strong>8) Code: a small Linux-style emulator</strong></h2><p>I tried to create an clear and easy to understand code that would nicely show how everything works together. </p><p>This code mirrors the diagram and uses Linux-like APIs (clock_gettime(CLOCK_...), clock_settime(CLOCK_REALTIME, ...)) to show the interactions between RTC, TSC, and the kernel clocks.</p><p>You can find the result of my experiment here:</p><p><a href="https://github.com/DmytroHuzz/linux_clock_emulator/blob/main/linux_clock.py">https://github.com/DmytroHuzz/linux_clock_emulator/blob/main/linux_clock.py</a></p><h1><strong>9) Developer cheat sheet</strong></h1><ul><li><p>Use <strong>CLOCK_MONOTONIC</strong> for: timeouts, retries, intervals, measuring latency, scheduling &#8220;sleep X&#8221;.</p></li><li><p>Use <strong>CLOCK_BOOTTIME</strong> for elapsed time that should include suspend.</p></li><li><p>Use <strong>CLOCK_REALTIME</strong> for logs, audits, UI timestamps, business meaning.</p></li><li><p>Never compute durations as realtime_end - realtime_start.</p></li><li><p>Time zone conversion is userspace logic (tzdata). Store UTC-like timestamps internally.</p></li></ul><h2><strong>Next: Part 3</strong></h2><p>Part 3 leaves the single machine and goes to the network and we will see how to sync many machines.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1363e9ca-99fc-4937-a697-46057e481de6&quot;,&quot;caption&quot;:&quot;Intro Every action film has that scene just before the military operation begins.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 3 &#8212; How Computers Share Time&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-16T15:42:35.677Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!0JCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff7fad6-3567-42cf-a8e4-f948866f3fd5_1000x420.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-ec8&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:191125080,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to do not miss the next part: </p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[A Practical Guide to Time for Developers: Part 1 — What time is in software (physics + agreements)]]></title><description><![CDATA[The foundations: what you&#8217;re really tracking when you store a timestamp]]></description><link>https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sun, 01 Mar 2026 06:30:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8hv5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8hv5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8hv5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8hv5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1233540,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/189526403?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8hv5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!8hv5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fbc17c1-cd36-4488-8a63-51f076a67229_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Preface to the series</strong></h2><p>I was tasked with synchronizing time across <strong>N computers</strong> with <strong>~1 nanosecond accuracy</strong>. Not &#8220;a laptop over Wi-Fi&#8221; &#8212; a controlled wired setup where hardware timestamping and disciplined clocks make that goal at least a meaningful engineering target.</p><p>At first it sounded trivial. We learn clocks, dates, and time zones as kids. How hard can it be?</p><p>The industry already has a standard solution: <strong>Precision Time Protocol (PTP)</strong>.</p><p>But I wanted to look inside the protocol and understand what it actually does. I expected it to be the easiest part of the whole story. Instead I ran straight into a wall of concepts: <strong>TAI vs UTC, epochs, leap seconds, RTC vs system clock, wall clock vs monotonic time, time zones, na&#239;ve timestamps</strong>. It turns out &#8220;time&#8221; is not a single thing &#8212; it&#8217;s physics, standards, and human conventions layered on top of each other.</p><p>I searched for a single article that explains the whole chain &#8212; something like &#8220;Time for software developers: zero to hero&#8221; or &#8220;From RTC to PTP&#8221; &#8212; and couldn&#8217;t find it. So I decided to write the guide I wished existed: a practical manual for developers that covers the essential concepts, the typical failure modes, and the protocols and algorithms we use to keep and distribute time.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This series has four parts:</p><ol><li><p><strong>What time is (in software)</strong> &#8212; what exactly we&#8217;re tracking, and what &#8220;correct&#8221; even means.</p></li><li><p><strong>How a computer keeps time</strong> &#8212; where ticks come from, how clocks drift, and why operating systems maintain multiple clocks.</p></li><li><p><strong>How systems share time</strong> &#8212; NTP vs PTP, timestamping, asymmetry, and what really limits accuracy.</p></li><li><p><strong>What can go wrong (and how you detect it)</strong> &#8212; validation, monitoring, failure modes, and security/trust of time sources.</p></li></ol><p>Let&#8217;s start with the foundation: <strong>what is time &#8212; physics or agreements?</strong></p><h2>Intro</h2><p>You already know what time <em>feels</em> like. That&#8217;s the trap.</p><p>In software, &#8220;time&#8221; is not one thing. It&#8217;s a mix of <strong>physical reality</strong> (oscillators drift, signals take time to travel), <strong>standards</strong> (UTC, leap seconds), and <strong>human conventions</strong> (time zones, calendars). If you don&#8217;t separate these layers, you end up building systems that look correct in tests and then collapse in production&#8212;usually around midnight, DST, or a &#8220;rare&#8221; edge case.</p><p>This part builds a simple foundation: <strong>what exactly is the thing we&#8217;re tracking when we say &#8220;time&#8221;?</strong></p><div><hr></div><h2><strong>Four different problems people call &#8220;time&#8221;</strong></h2><p>Most confusion comes from mixing these up. People say &#8220;time,&#8221; but they might mean <strong>four totally different things</strong>, and each one requires a different kind of clock, API, and mental model.</p><h3><strong>1) Time-of-day (civil time)</strong></h3><p><strong>Question:</strong> <em>&#8220;What date/time is it right now?&#8221;</em></p><p>This is the time humans care about: calendars, weekdays, business hours, &#8220;yesterday,&#8221; tax reports, contracts.</p><p><strong>Used for:</strong> logs, UI, audit trails, business processes, legal records.</p><p><strong>Typical failure modes:</strong></p><ul><li><p>DST: the same local time can happen twice, or not happen at all.</p></li><li><p>Time zones: &#8220;10:00&#8221; without a zone is not a timestamp, it&#8217;s a vague sentence.</p></li><li><p>Clock corrections: timestamps can jump forward/backward when the system is adjusted.</p></li></ul><p><strong>Rule of thumb:</strong> civil time is great for <em>human meaning</em>, not for measuring anything.</p><h3><strong>2) Duration / intervals</strong></h3><p><strong>Question:</strong> <em>&#8220;How long did it take?&#8221; / &#8220;Wait 500 ms.&#8221;</em></p><p>This is not &#8220;date/time.&#8221; This is <strong>elapsed time</strong>. You don&#8217;t want it to jump. You want it to be steady and monotonic.</p><p><strong>Used for:</strong> timeouts, retries, benchmarks, scheduling, rate limiting.</p><p><strong>Typical failure modes:</strong></p><ul><li><p>Using wall clock for timeouts &#8594; timeout triggers instantly or never triggers after a time correction.</p></li><li><p>Negative durations (&#8220;operation took -3 ms&#8221;) because the clock moved backwards.</p></li><li><p>Inconsistent metrics when different machines have different offsets.</p></li></ul><p><strong>Rule of thumb:</strong> durations must come from a clock that only moves forward.</p><h3><strong>3) Ordering / causality</strong></h3><p><strong>Question:</strong> <em>&#8220;Which event happened first?&#8221;</em> (especially across threads/processes/machines)</p><p>This is the one that causes the most hidden damage. Humans intuitively think timestamps imply ordering. In distributed systems, that&#8217;s often false.</p><p><strong>Used for:</strong> distributed tracing, message processing, state machines, replication, conflict resolution.</p><p><strong>Typical failure modes:</strong></p><ul><li><p>Two machines disagree about &#8220;now&#8221; &#8594; you see &#8220;future&#8221; events in logs.</p></li><li><p>Network delay/scheduling jitter reorder events even if clocks are &#8220;pretty good.&#8221;</p></li><li><p>You use timestamps to order messages and occasionally violate invariants (&#8220;this update happened before its cause&#8221;).</p></li></ul><p><strong>Rule of thumb:</strong> if correctness depends on ordering, don&#8217;t quietly rely on wall-clock time alone. Use explicit ordering mechanisms (sequence numbers, causality-aware designs, etc.) and treat timestamps as <em>metadata</em>.</p><h3><strong>4) Frequency / rate</strong></h3><p><strong>Question:</strong> <em>&#8220;Are two clocks running at the same speed?&#8221;</em></p><p>This isn&#8217;t &#8220;what time is it,&#8221; it&#8217;s <strong>how fast time passes</strong>. For high-precision work (PTP, measurement, telecom), this matters as much as absolute offset.</p><p><strong>Used for:</strong> high-precision sync, control loops, telecom, measurement systems, sampling, sensor fusion.</p><p><strong>Typical failure modes:</strong></p><ul><li><p>You correct offset but ignore drift &#8594; you constantly &#8220;chase&#8221; the reference.</p></li><li><p>Short-term jitter ruins measurements even if average offset looks good.</p></li><li><p>You assume nanosecond <em>resolution</em> implies nanosecond <em>accuracy</em>.</p></li></ul><p><strong>Rule of thumb:</strong> precision time is always a control problem: you manage both offset (sync) and rate (syntonization).</p><div><hr></div><h3><strong>The category mistake that creates most time bugs</strong></h3><p>A lot of bugs are simply <strong>using a tool from one category to solve another</strong>.</p><p>Classic example: using civil time to measure durations:</p><ul><li><p>You record start = wall_clock_now()</p></li><li><p>You record end = wall_clock_now()</p></li><li><p>You compute end - start</p></li></ul><p>It works&#8230; until the system clock is adjusted (NTP/PTP correction, manual change, VM migration, DST misconfig). Then the wall clock can jump backwards, and your &#8220;duration&#8221; becomes negative, your retry logic breaks, or your timeout never fires.</p><p>That&#8217;s not a rare corner case. It&#8217;s an inevitable result of mixing categories.</p><p>If you remember one thing from this section, remember this:</p><blockquote><p><strong>Time-of-day is for meaning. Duration is for measurement. Ordering is for correctness. Frequency is for precision.</strong></p></blockquote><div><hr></div><h2><strong>Basic vocabulary that prevents endless confusion</strong></h2><p>When someone says &#8220;we need <strong>1 ns accuracy</strong>,&#8221; the only correct first reaction is: <em>accuracy of what, relative to what, over what time window, and how will we measure it?</em></p><p>If you don&#8217;t pin down the vocabulary, teams end up arguing for weeks while everyone is technically correct in their own private definition.</p><p>Below are the terms you must keep separate.</p><div><hr></div><h3><strong>Resolution</strong></h3><p><strong>What it is:</strong> the smallest step your clock can <em>represent</em> or <em>report</em>.</p><p>Example: a timestamp API that returns nanoseconds has <strong>1 ns resolution</strong>.</p><p><strong>What it is not:</strong> a guarantee that the clock is correct to 1 ns.</p><p>A clock can happily produce nanosecond-looking numbers while being microseconds (or milliseconds) away from the truth. This is why &#8220;we have nanosecond timestamps&#8221; is almost meaningless by itself.</p><div><hr></div><h3><strong>Precision</strong></h3><p><strong>What it is:</strong> how fine your measurement or reporting is &#8212; how many digits you output and how repeatable your measurement process is.</p><p>Precision often gets confused with resolution. A useful way to think about it:</p><ul><li><p><strong>Resolution</strong> is &#8220;how small a step the counter can show.&#8221;</p></li><li><p><strong>Precision</strong> is &#8220;how finely we can <em>measure</em> and how consistent our measurement results are.&#8221;</p></li></ul><p>You can have high precision measurements of a clock that is not accurate. You can also have a very accurate system that still reports in coarse units.</p><div><hr></div><h3><strong>Accuracy</strong></h3><p><strong>What it is:</strong> how close your clock is to a reference (a trusted source, or &#8220;true time&#8221; in some defined sense).</p><p>Accuracy always depends on:</p><ul><li><p><strong>the reference</strong> (UTC? TAI? GPS? a grandmaster clock?),</p></li><li><p><strong>the path</strong> (network delays),</p></li><li><p><strong>the method</strong> (hardware timestamping vs software),</p></li><li><p><strong>the measurement point</strong> (where you observe time).</p></li></ul><p>So &#8220;1 ns accuracy&#8221; without specifying the reference and measurement method is not a requirement &#8212; it&#8217;s a slogan.</p><div><hr></div><h3><strong>Stability</strong></h3><p><strong>What it is:</strong> how consistently the clock runs over time. In other words: how noisy it is and how much its rate changes.</p><p>Two systems can have the same accuracy at a single moment and wildly different stability:</p><ul><li><p>One stays close for hours.</p></li><li><p>The other drifts immediately and needs constant correction.</p></li></ul><p>In practice, stability is what determines how hard your synchronization algorithm has to work.</p><div><hr></div><h3><strong>Offset</strong></h3><p><strong>What it is:</strong> the difference between your clock and the reference <em>right now</em>.</p><p>If the reference says 12:00:00.000000000 and you say 12:00:00.000000500, your offset is <strong>+500 ns</strong>.</p><p>Offset is the number people usually mean when they casually say &#8220;we&#8217;re synced to X.&#8221;</p><p>But offset alone is not the full story, because it doesn&#8217;t tell you how noisy that offset is or how it behaves over time.</p><div><hr></div><h3><strong>Jitter</strong></h3><p><strong>What it is:</strong> short-term variation &#8212; the &#8220;shake&#8221; around the average.</p><p>If you measure offset once per second and the values bounce around like:</p><p>+20 ns, -15 ns, +35 ns, -10 ns...</p><p>that bounce is jitter.</p><p>Jitter matters because many systems care about instantaneous behavior, not just long-term average. A &#8220;perfect&#8221; average offset is useless if the clock is too noisy for your application.</p><div><hr></div><h3><strong>Wander</strong></h3><p><strong>What it is:</strong> slow changes over longer timescales &#8212; the &#8220;drift of the drift.&#8221;</p><p>Where jitter is rapid noise, wander is a slow trend: temperature changes, oscillator aging, environmental effects, network path changes that persist.</p><p>Wander is what makes a system look great in a short demo and gradually fall apart over hours or days if the control loop can&#8217;t track it.</p><div><hr></div><h3><strong>Synchronization vs syntonization (phase vs rate)</strong></h3><p>This is one of the most important distinctions in precision time, and it&#8217;s usually not named explicitly &#8212; which is why people get confused.</p><ul><li><p><strong>Synchronize</strong> = align <strong>phase</strong> &#8594; reduce <strong>offset</strong></p><p>&#8220;Make our timestamps match right now.&#8221;</p></li><li><p><strong>Syntonize</strong> = align <strong>rate</strong> &#8594; reduce <strong>drift</strong></p><p>&#8220;Make our clocks run at the same speed.&#8221;</p></li></ul><p>If you only synchronize (phase) but don&#8217;t syntonize (rate), you get a system that constantly drifts away and needs repeated &#8220;kicks&#8221; back into place. If you syntonize well, the system stays close with small, smooth corrections.</p><p>That&#8217;s why protocols like <strong>PTP</strong> are not &#8220;set the time once and forget it.&#8221; They run a continuous control loop: measure offset, estimate delay, correct phase and rate, and fight noise (jitter) and slow effects (wander).</p><div><hr></div><p>If you want a single mental model: <strong>precision time is control theory applied to clocks</strong>. The numbers (offset/jitter/wander) are the feedback signals; synchronization and syntonization are the control objectives.</p><div><hr></div><h2><strong>Timescales: what your timestamps are actually referencing</strong></h2><p>A timestamp looks like a number. That&#8217;s why developers treat it like a number.</p><p>But a timestamp is not &#8220;time.&#8221; It&#8217;s a <strong>coordinate</strong> in some system &#8212; and the most important part of that coordinate system is the <strong>timescale</strong>: <em>what kind of &#8220;time&#8221; this number is measuring.</em></p><p>If two systems use different timescales, their timestamps can be perfectly well-formed and still be fundamentally incomparable.</p><div><hr></div><h3><strong>What is a timescale?</strong></h3><p>A <strong>timescale</strong> is a definition of how seconds are counted and how that count is anchored to reality.</p><p>A useful way to think about it:</p><ul><li><p>It defines what &#8220;one second&#8221; means (atomic seconds vs adjusted seconds).</p></li><li><p>It defines whether the count is <strong>continuous</strong> or can <strong>jump</strong>.</p></li><li><p>It defines how it relates to civil time (what humans call &#8220;UTC time&#8221;).</p></li></ul><p>So when someone says &#8220;store timestamps in UTC,&#8221; they&#8217;re implicitly making a choice about a timescale &#8212; and about how the system behaves during edge cases.</p><div><hr></div><h3><strong>TAI (International Atomic Time)</strong></h3><p>TAI is the cleanest mental model for engineers:</p><ul><li><p>it is a <strong>continuous</strong> count of atomic seconds</p></li><li><p>it <strong>does not have leap seconds</strong></p></li><li><p>it does not care about Earth&#8217;s rotation</p></li></ul><p>TAI is what you&#8217;d want if your only goal was: <em>a global, steady clock that never inserts weird discontinuities.</em></p><p>The downside is social, not technical: people don&#8217;t live in TAI. Civil time is defined using UTC.</p><div><hr></div><h3><strong>UTC (Coordinated Universal Time)</strong></h3><p>UTC is the time humans and laws use. It is designed to stay close to Earth rotation, which is irregular. To keep UTC aligned with that, the standard allows <strong>leap seconds</strong>.</p><p>That single detail has a huge consequence:</p><blockquote><p>UTC is not guaranteed to be perfectly continuous.</p></blockquote><p>Most of the time, UTC behaves like a normal continuous timescale. But around leap seconds, systems can:</p><ul><li><p>repeat a second,</p></li><li><p>represent 23:59:60,</p></li><li><p>step,</p></li><li><p>or smear.</p></li></ul><p>So &#8220;UTC&#8221; is a civil agreement that often behaves like a smooth clock &#8212; until it doesn&#8217;t.</p><p>This is why developers eventually run into bugs that sound impossible:</p><ul><li><p>&#8220;Why did the same timestamp appear twice?&#8221;</p></li><li><p>&#8220;Why did time go backwards for a second?&#8221;</p></li><li><p>&#8220;Why do two machines disagree about UTC during the same minute?&#8221;</p></li></ul><div><hr></div><h3><strong>GPS time (and other system times)</strong></h3><p>GPS time is a common example of a system timescale:</p><ul><li><p>it is <strong>continuous</strong> (no leap seconds)</p></li><li><p>it is used internally by a technical system because continuity is convenient</p></li><li><p>it has a <strong>known offset</strong> relative to other scales (like UTC/TAI), but that offset is not &#8220;magically applied&#8221; everywhere the same way</p></li></ul><p>And GPS is not alone. Many systems use their own &#8220;continuous time&#8221; internally because it simplifies math and avoids leap-second edge cases.</p><p>The important point isn&#8217;t the details of GPS time. It&#8217;s the category:</p><blockquote><p>Many technical systems use a continuous timescale internally and only convert to UTC for humans.</p></blockquote><div><hr></div><h3><strong>You don&#8217;t need to memorize offsets &#8212; you need the</strong></h3><h3><strong>contract</strong></h3><p>At this stage, memorizing &#8220;how many seconds UTC differs from TAI&#8221; is not the goal. You can look up numbers.</p><p>The goal is to internalize this:</p><blockquote><p>In software, &#8220;time&#8221; is usually</p><p><strong>a number plus a contract</strong></p></blockquote><ul><li><p><strong>What is it anchored to?</strong> (UTC? TAI? a grandmaster? device-local monotonic time?)</p></li><li><p><strong>Is it continuous, or can it jump?</strong></p></li><li><p><strong>What happens during leap seconds?</strong> (step? smear? ignore? represent 23:59:60?)</p></li><li><p><strong>How do we convert it for humans?</strong></p></li></ul><p>Once you treat timestamps as &#8220;numbers with contracts,&#8221; a lot of time-related confusion disappears &#8212; and the rest becomes an engineering problem you can actually reason about.</p><div><hr></div><h2><strong>Leap seconds: the edge case that isn&#8217;t optional</strong></h2><p>Leap seconds exist for a simple reason: Earth is not a perfect clock.</p><p>Its rotation speed changes slightly due to geophysics, tides, atmosphere, even large-scale events. But civil time is supposed to stay roughly aligned with the Sun (&#8220;noon should be around when the Sun is highest&#8221;). So UTC is designed to track Earth rotation closely enough &#8212; and when the gap grows too large, UTC is adjusted by inserting (and in theory removing) a second.</p><p>That&#8217;s the astronomy story. Here&#8217;s the software story:</p><blockquote><p><strong>The real problem is not that leap seconds exist.</strong></p><p><strong>The real problem is that systems don&#8217;t agree on how to implement them.</strong></p></blockquote><div><hr></div><h3><strong>The trap: &#8220;UTC&#8221; is not one behavior</strong></h3><p>If your fleet contains different operating systems, different kernels, different NTP/PTP stacks, different cloud providers, or even different configuration defaults, you will encounter multiple &#8220;UTC behaviors&#8221; in the wild.</p><p>That means two machines can both claim &#8220;UTC&#8221; and still produce timestamps that aren&#8217;t directly comparable during a leap-second event.</p><div><hr></div><h3><strong>Common behaviors you will encounter</strong></h3><p><strong>1) Explicit leap second (23:59:60)</strong></p><p>Some systems model the extra second as a real extra label in the clock representation.</p><p>This is conceptually honest: there really is an inserted second.</p><p>But it breaks assumptions everywhere:</p><ul><li><p>parsers that reject :60</p></li><li><p>sorting logic that doesn&#8217;t expect it</p></li><li><p>&#8220;every minute has exactly 60 seconds&#8221; code</p></li></ul><p><strong>2) Step / repeat / jump</strong></p><p>Other systems handle the event by effectively repeating a second or stepping the time.</p><p>From a developer perspective this looks like:</p><ul><li><p>timestamps that stop moving forward for a moment</p></li><li><p>a repeated time value</p></li><li><p>or &#8220;time went backwards&#8221; depending on representation</p></li></ul><p>This is poison for anything that assumes monotonic behavior from civil time, especially ordering and durations.</p><p><strong>3) Smear (stretching time over a window)</strong></p><p>Instead of inserting a visible extra second, some environments &#8220;smear&#8221; it: they slightly slow down (or speed up) the clock over a window so that the leap second is absorbed smoothly.</p><p>This avoids a hard discontinuity, which is great for many systems.</p><p>But it introduces a different kind of inconsistency:</p><ul><li><p>during the smear window, your &#8220;UTC&#8221; is <strong>not exactly UTC</strong></p></li><li><p>two systems can disagree because one smears and the other doesn&#8217;t</p></li><li><p>comparisons across vendors become tricky (&#8220;why are we off by hundreds of ms even though we&#8217;re both &#8216;UTC&#8217;?&#8221;)</p></li></ul><div><hr></div><h3><strong>Why this matters even if leap seconds are rare</strong></h3><p>You might think: &#8220;Leap seconds almost never happen. Who cares?&#8221;</p><p>Two reasons you should care anyway:</p><ol><li><p><strong>The edge case exists at the standards level</strong>, so it shows up in libraries, operating systems, and infrastructure &#8212; whether you like it or not. You inherit it.</p></li><li><p><strong>Distributed systems amplify rare events</strong>.</p><p>One leap second can create:</p></li></ol><ul><li><p>broken ordering across machines</p></li><li><p>weird negative durations in logs/metrics pipelines</p></li><li><p>parsing failures in analytics</p></li><li><p>incident timelines that don&#8217;t line up when you need them most</p></li></ul><div><hr></div><h3><strong>What you must decide early (policy, not implementation)</strong></h3><p>If your system needs strict timestamp comparisons, you need an explicit policy. Not a vibe. A policy.</p><p>Key questions:</p><ul><li><p><strong>Do you store time-of-day as UTC timestamps, or as a continuous internal timescale?</strong></p><p>(Many serious systems store continuous internal time and convert for display.)</p></li><li><p><strong>Do you require &#8220;true UTC,&#8221; or are you okay with smeared UTC?</strong></p><p>(If you compare across cloud providers, &#8220;okay with smear&#8221; might be forced on you.)</p></li><li><p><strong>Where do you do conversions?</strong></p><p>Store canonical time internally; convert at the edges (UI, reporting), or the other way around?</p></li></ul><p>You don&#8217;t have to solve leap-second handling in Part 1. That comes later. But you must do the one thing that prevents surprise:</p><blockquote><p><strong>Acknowledge that &#8220;UTC&#8221; is not a single universal runtime behavior.</strong></p></blockquote><div><hr></div><h3><strong>Epochs and units: a timestamp is a coordinate system</strong></h3><p>Almost all practical timestamps in software are just a coordinate:</p><blockquote><p><strong>&#8220;X units since some chosen origin.&#8221;</strong></p></blockquote><p>That origin is an <strong>epoch</strong>. The unit is seconds / milliseconds / nanoseconds (or sometimes &#8220;ticks&#8221;). Together they define the coordinate system your entire platform will live in.</p><p>Most of the time we treat epoch choice as a boring implementation detail. And it <em>is</em> mostly engineering convenience &#8212; until you need long-term compatibility, cross-system integration, or debugging. Then epoch and units suddenly become the difference between &#8220;obvious&#8221; and &#8220;impossible.&#8221;</p><div><hr></div><h3><strong>Epoch: what is your &#8220;zero&#8221;?</strong></h3><p>An <strong>epoch</strong> is simply the timestamp you call &#8220;0.&#8221;</p><p>Common epochs you&#8217;ll encounter:</p><ul><li><p><strong>Unix epoch</strong>: 1970-01-01 (the usual &#8220;POSIX time&#8221; family)</p></li><li><p><strong>System uptime / boot time</strong>: epoch = when the OS booted</p></li><li><p><strong>Process start</strong>: epoch = when a process started</p></li><li><p><strong>Custom epochs</strong>: sometimes chosen for storage size, legacy reasons, or protocol specs</p></li></ul><p>None of these is inherently &#8220;better.&#8221; They serve different purposes.</p><p>The important thing is: once you choose an epoch for storage or APIs, you&#8217;ve created a contract. Changing it later is like changing the unit of distance in the middle of a highway.</p><div><hr></div><h3><strong>Units: the silent multiplier that breaks everything</strong></h3><p>A timestamp number is meaningless unless you know its unit.</p><p>Typical units:</p><ul><li><p>seconds (s)</p></li><li><p>milliseconds (ms)</p></li><li><p>microseconds (&#181;s)</p></li><li><p>nanoseconds (ns)</p></li></ul><p>The most common production bug here is not exotic. It&#8217;s this:</p><ul><li><p>someone sends milliseconds,</p></li><li><p>someone reads seconds,</p></li><li><p>everything looks &#8220;roughly right&#8221; in small tests,</p></li><li><p>and then you ship timestamps that are off by <strong>1000&#215;</strong>.</p></li></ul><p>If your codebase uses multiple units, enforce one of these rules:</p><ul><li><p>include unit in the name (timestamp_ms, timeout_ns)</p></li><li><p>or use a strong type / duration type system</p></li><li><p>or centralize conversions in one place</p></li></ul><p>Don&#8217;t rely on comments and good intentions.</p><div><hr></div><h3><strong>Integers vs floats: why &#8220;it fits in a double&#8221; is not a plan</strong></h3><p>Floats look convenient because you can write 1700000000.123456789.</p><p>The problem: floating point has limited precision, and the bigger the number gets, the fewer distinct fractional steps you can represent. So you end up silently losing sub-millisecond precision (or worse) depending on magnitude.</p><p>Practical rule:</p><ul><li><p><strong>store and transport timestamps as integers</strong></p></li><li><p>attach the unit explicitly</p></li><li><p>if you need fractions for display, convert at the edges</p></li></ul><p>This is especially important once you claim nanoseconds. If you represent &#8220;nanoseconds since 1970&#8221; as a float, you&#8217;re basically begging for precision loss.</p><div><hr></div><h3><strong>You must label the kind of timestamp, not just the number</strong></h3><p>Even if you know the epoch and unit, you still need to know what kind of &#8220;time&#8221; it is.</p><p>Be explicit about whether a timestamp is:</p><ul><li><p><strong>Time-of-day (civil / wall-clock)</strong></p><p>Anchored to a UTC-like timescale. Comparable across machines <em>if</em> they share the same time source and leap-second policy.</p></li><li><p><strong>Monotonic-ish (elapsed time)</strong></p><p>Anchored to boot or process start. Great for measuring durations and scheduling. Usually meaningless to compare across machines, and often not meaningful after reboot.</p></li><li><p><strong>Logical (ordering)</strong></p><p>Anchored to an ordering scheme (sequence numbers, causality). Comparable for ordering, not for &#8220;real-world time.&#8221;</p></li></ul><p>This is the difference between a useful timestamp and a number that accidentally sorts most of the time.</p><div><hr></div><h3><strong>The classic &#8220;1970&#8221; bug and what it really means</strong></h3><p>When someone says: &#8220;Why is this event from 1970?&#8221; it usually means one of these happened:</p><ul><li><p>you interpreted <strong>milliseconds</strong> as <strong>seconds</strong> (or vice versa)</p></li><li><p>you used the wrong epoch (boot-time treated as Unix time)</p></li><li><p>you parsed a timestamp as local civil time when it was UTC (or vice versa)</p></li><li><p>you truncated or overflowed (32-bit seconds, wrong cast)</p></li><li><p>you mixed timescales (rare, but catastrophic when it happens)</p></li></ul><p>The &#8220;1970&#8221; symptom is your system screaming: <em>your coordinate system is inconsistent.</em></p><div><hr></div><h3><strong>Practical rules (expanded)</strong></h3><ul><li><p>Always know your <strong>epoch</strong> and your <strong>unit</strong>. If you can&#8217;t answer both instantly, you don&#8217;t have a timestamp &#8212; you have a random number.</p></li><li><p>Prefer <strong>integer</strong> storage/transport.</p></li><li><p>Encode the unit in the API/type/name.</p></li><li><p>Treat timestamp types as separate domains:</p><ul><li><p>wall-clock for human meaning</p></li><li><p>monotonic for durations</p></li><li><p>logical for ordering</p></li></ul></li><li><p>Never mix different timestamp kinds without explicit conversion and a clear reason.</p></li></ul><div><hr></div><h3><strong>Why this matters for the rest of the series</strong></h3><p>Protocols and OS clocks become much easier to understand once you separate:</p><ul><li><p><strong>what &#8220;time&#8221; means</strong> (timescale and behavior)</p></li><li><p>from <strong>how it&#8217;s encoded</strong> (epoch + unit + representation)</p></li></ul><p>In Part 2 we&#8217;ll look at where these numbers come from inside one machine, and why &#8220;the system time&#8221; is actually several different clocks with different guarantees.</p><div><hr></div><h3><strong>Civil time: time zones, DST, and calendars are not &#8220;formatting&#8221;</strong></h3><p>A lot of engineers treat time zones as UI: &#8220;we&#8217;ll store UTC and just format it for the user.&#8221; That instinct is half right.</p><p>The other half is where projects die: <strong>civil time is not a formatting layer</strong>. It&#8217;s a set of rules that changes across geography <em>and across history</em>. If your system interacts with humans, payroll, contracts, schedules, billing cycles, or &#8220;days,&#8221; you are doing civil-time logic whether you admit it or not.</p><div><hr></div><h3><strong>Civil time is a rules database, not a law of physics</strong></h3><p>Time zones are not just &#8220;UTC+2.&#8221; They are effectively:</p><ul><li><p>a region identifier (e.g., &#8220;Europe/Vienna&#8221;, not &#8220;+01:00&#8221;)</p></li><li><p>plus a historical database of changes:</p><ul><li><p>offsets change over the decades</p></li><li><p>DST rules change (sometimes with very short notice)</p></li><li><p>sometimes entire countries switch policy</p></li></ul></li></ul><p>So &#8220;local time&#8221; is not stable unless you store the <em>zone identifier</em> and consult a time zone database for the correct rule at that date.</p><p>If you store only &#8220;UTC offset at the moment,&#8221; you lose the ability to reproduce civil time correctly later.</p><div><hr></div><h3><strong>DST creates two kinds of broken times: ambiguous and missing</strong></h3><p>Daylight saving time is where naive systems reveal themselves.</p><p><strong>Ambiguous local time (fall back)</strong></p><p>The clock is set back. A local time interval happens twice.</p><p>So a local timestamp like:</p><ul><li><p>2026-10-25 02:30</p></li></ul><p>is ambiguous in many European zones: it could refer to the &#8220;first 02:30&#8221; or the &#8220;second 02:30.&#8221; Without extra context (offset or zone rule), that timestamp is not uniquely defined.</p><p><strong>Missing local time (spring forward)</strong></p><p>The clock jumps forward. Some local times never happen.</p><p>So a local timestamp like &#8220;02:30&#8221; on the DST jump day might literally be invalid: it never occurred.</p><p>This is why &#8220;local time as a primary storage format&#8221; is a trap. Your database will happily store impossible moments.</p><div><hr></div><h3><strong>Calendars are hostile: &#8220;day&#8221; and &#8220;month&#8221; are not durations</strong></h3><p>Civil time mixes clocks with calendars, and calendars don&#8217;t behave like physics.</p><p><strong>&#8220;Add 24 hours&#8221; &#8800; &#8220;add 1 day&#8221;</strong></p><ul><li><p>Adding 24 hours means &#8220;exactly 86,400 seconds later.&#8221;</p></li><li><p>Adding 1 day often means &#8220;same local clock time on the next calendar day.&#8221;</p></li></ul><p>During DST transitions, those diverge. Some days are 23 hours, some are 25. So the &#8220;next day at 09:00&#8221; is not always &#8220;+24h&#8221;.</p><p><strong>&#8220;Add 1 month&#8221; is not a duration at all</strong></p><p>A month is not a fixed number of seconds. It&#8217;s a calendar concept with edge cases:</p><ul><li><p>What is &#8220;one month after January 31&#8221;?</p></li><li><p>Is it February 28/29? March 3? &#8220;clamp to end of month&#8221;? error?</p></li></ul><p>There isn&#8217;t one universally correct answer. There are policies &#8212; and you must choose one explicitly.</p><div><hr></div><h3><strong>The hidden production bugs this causes</strong></h3><p>Civil-time mistakes usually appear as:</p><ul><li><p>duplicated timestamps in logs (same local time twice)</p></li><li><p>scheduling drift (&#8220;meeting moved by one hour&#8221;)</p></li><li><p>billing/payroll disagreements (&#8220;which day counts?&#8221;)</p></li><li><p>impossible events (&#8220;this happened at a time that never existed&#8221;)</p></li><li><p>long-term reproducibility issues (&#8220;it used to show 10:00, now it shows 11:00 for old data&#8221;)</p></li></ul><p>The worst part: these bugs often don&#8217;t show up in unit tests because tests don&#8217;t run across DST boundaries or historical rule changes.</p><div><hr></div><h3><strong>A policy that prevents endless pain (and where it breaks)</strong></h3><p>A sane default for most systems:</p><ul><li><p><strong>Store and exchange</strong> timestamps in a single global standard (typically UTC-like).</p></li><li><p><strong>Convert</strong> to local time zones only at the edges (UI, reports).</p></li><li><p><strong>Keep calendar arithmetic explicit and isolated</strong> (and test DST boundaries).</p></li></ul><p>Two important additions to make this actually work in real systems:</p><ul><li><p>When civil meaning matters (appointments, payroll, &#8220;local midnight&#8221;), store the <strong>time zone ID</strong> (e.g., &#8220;Europe/Vienna&#8221;), not just an offset.</p></li><li><p>If you store recurring schedules (&#8220;every day at 09:00 local time&#8221;), store them as <strong>civil-time rules</strong>, not as precomputed UTC instants.</p></li></ul><p>Because recurring human schedules are defined in civil time &#8212; and civil time changes.</p><div><hr></div><p>If you internalize one idea from this section, make it this:</p><blockquote><p>Local time is not a timestamp.</p><p>It becomes a timestamp only when you attach a time zone rule set &#8212; and accept the edge cases.</p></blockquote><div><hr></div><h3><strong>Physical time vs logical time (distributed systems reality)</strong></h3><p>Even if you had &#8220;perfect&#8221; clock synchronization, distributed systems still wouldn&#8217;t behave like a single machine. Reality gets in the way:</p><ul><li><p><strong>network delay</strong> (packets take time to arrive),</p></li><li><p><strong>asymmetry</strong> (A&#8594;B delay is not necessarily equal to B&#8594;A),</p></li><li><p><strong>scheduling delays</strong> (your process didn&#8217;t run when you think it did),</p></li><li><p><strong>partial failures</strong> (timeouts, retries, partitions),</p></li><li><p>and simply <strong>different perspectives of &#8220;now.&#8221;</strong></p></li></ul><p>This matters because developers use time for two very different purposes:</p><ol><li><p><em>to attach human meaning</em> (&#8220;when did it happen?&#8221;)</p></li><li><p><em>to decide correctness</em> (&#8220;which happened first?&#8221;)</p></li></ol><p>Those are not the same problem.</p><div><hr></div><h3><strong>Two broad approaches to ordering events</strong></h3><h3><strong>Physical timestamps (&#8220;wall clock time&#8221;)</strong></h3><p>This is the familiar one: attach a wall-clock timestamp to events.</p><p><strong>Why it&#8217;s useful:</strong></p><ul><li><p>humans can read it</p></li><li><p>audit trails and legal records need it</p></li><li><p>it&#8217;s great for observability (&#8220;show me what happened around 12:03&#8221;)</p></li><li><p>it helps correlate events across services <em>when sync is good enough</em></p></li></ul><p><strong>Why it&#8217;s risky for correctness:</strong></p><p>Physical time is an approximation. Even with good sync, you can still get:</p><ul><li><p><strong>mis-ordering</strong>: event B appears &#8220;earlier&#8221; than its cause A because A&#8217;s message was delayed or A&#8217;s clock is slightly behind</p></li><li><p><strong>future events</strong>: logs show something that &#8220;happened in the future&#8221; relative to another machine</p></li><li><p><strong>time going backwards</strong> locally when clocks are stepped</p></li></ul><p>So: wall-clock timestamps are excellent metadata. They are a weak foundation for correctness.</p><h3><strong>Logical time (causality-aware ordering)</strong></h3><p>Logical time exists because &#8220;timestamp ordering&#8221; is not the same as &#8220;happened-before ordering.&#8221;</p><p>The core idea is simple:</p><ul><li><p><strong>A happened before B</strong> if A could have influenced B.</p><p>Not because A&#8217;s timestamp is smaller.</p></li></ul><p>Logical clocks (conceptually: Lamport timestamps and vector clocks) encode causality:</p><ul><li><p>If B observed A (directly or indirectly), B must be ordered after A.</p></li><li><p>If two events are independent, they may be concurrent, and ordering them is a policy decision, not a fact revealed by a clock.</p></li></ul><p><strong>Why it&#8217;s useful:</strong></p><ul><li><p>correctness in replication, conflict resolution, messaging systems</p></li><li><p>reasoning about distributed workflows and state machines</p></li><li><p>avoiding &#8220;timestamp lies&#8221; when clocks disagree</p></li></ul><p><strong>Why it&#8217;s not a replacement for wall time:</strong></p><p>Logical time doesn&#8217;t tell you &#8220;it&#8217;s 12:03.&#8221; It tells you &#8220;this depends on that.&#8221;</p><div><hr></div><h3><strong>Mature systems use both (and are explicit about it)</strong></h3><p>Most serious systems end up with two parallel layers:</p><ul><li><p><strong>Physical time</strong> for observability, audit, user-facing meaning, &#8220;what happened when&#8221;</p></li><li><p><strong>Logical ordering / protocol guarantees</strong> where correctness depends on ordering</p></li></ul><p>This is also how you keep sane during incidents:</p><ul><li><p>physical timestamps help humans reconstruct timelines</p></li><li><p>ordering guarantees help the system stay correct even when time is messy</p></li></ul><div><hr></div><h3><strong>The takeaway</strong></h3><p>If you need <strong>correct ordering</strong>, don&#8217;t silently assume wall-clock time gives it.</p><p>Use wall-clock timestamps for meaning and correlation &#8212; but when correctness depends on &#8220;what happened first,&#8221; you need explicit ordering mechanisms (protocol guarantees, sequence numbers, causality-aware clocks, or designs that don&#8217;t depend on global time).</p><p>Because in distributed systems, &#8220;now&#8221; is not a global fact. It&#8217;s a local opinion.</p><div><hr></div><h3><strong>Summary: what &#8220;time&#8221; is, in one sentence</strong></h3><p>In software, time is <strong>a continuously maintained estimate</strong> of some reference, expressed in a chosen coordinate system (timescale + epoch + units), and only then mapped into human conventions like calendars and time zones.</p><p>If that sounds heavier than &#8220;a number that increases,&#8221; good &#8212; because treating time as &#8220;just a number&#8221; is exactly how you end up with negative durations, duplicated local timestamps, and distributed logs that can&#8217;t be reconciled.</p><p>In the next part we&#8217;ll go one layer deeper and get practical: how a single computer actually keeps time &#8212; where ticks come from, what the OS does with them, why there are multiple clocks, and why &#8220;correcting the clock&#8221; can make time jump (and break anything that assumed it couldn&#8217;t).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>Developer rules (keep this as your reference card)</strong></h2><p>If you remember nothing else from this part, remember these:</p><ol><li><p>Decide what problem you&#8217;re solving: <strong>time-of-day vs duration vs ordering vs frequency</strong>.</p></li><li><p>Store/transport canonical time in one global form (typically UTC-like).</p></li><li><p>Measure durations with a <strong>monotonic</strong> clock, not wall time.</p></li><li><p>Treat time zones/DST/calendar arithmetic as <strong>logic</strong>, not formatting.</p></li><li><p>Be explicit about <strong>timescale, epoch, and units</strong>; prefer integer timestamps.</p></li><li><p>Assume leap seconds exist &#8212; and that &#8220;UTC&#8221; may be implemented differently (including smearing).</p></li><li><p>Don&#8217;t assume timestamps provide correct distributed ordering.</p></li><li><p>For serious systems, treat time like a dependency: define trust, monitor offset/jitter, plan failure behavior.</p></li></ol><p>These rules are defaults for most systems. High-precision setups add stricter constraints &#8212; we&#8217;ll get there when we talk about distributing time across machines.</p><div><hr></div><h2>Next part:</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;2b8e9ee6-db13-40cd-919a-6feae55ede15&quot;,&quot;caption&quot;:&quot;Time on a computer looks simple: call now(), get a timestamp, move on.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Practical Guide to Time for Developers: Part 2 &#8212; How one computer keeps time (Linux)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:392416265,&quot;name&quot;:&quot;Dmytro Huz&quot;,&quot;bio&quot;:&quot;Software Engineer in the Energy Sector. AWS Community Builder (Dev Tools). (Re)building core tech in public &#8212; so it stop feeling like magic. Writing at Rebuilt.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4a14e6b-5f68-4257-9ee7-e33b8864d56a_1024x1024.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-05T21:16:33.261Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JuSw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0671b0e-b346-4268-aa3f-03463bde5436_1536x672.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:190040669,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6272314,&quot;publication_name&quot;:&quot;Rebuilt&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!JM5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f89608f-4575-4fe4-9df4-7d6a25e088b2_1254x1254.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Aha-Moment of Public-Key Encryption]]></title><description><![CDATA[A small idea behind the huge topic]]></description><link>https://www.dmytrohuz.com/p/the-aha-moment-of-public-key-encryption</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/the-aha-moment-of-public-key-encryption</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Fri, 13 Feb 2026 12:29:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uy8G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uy8G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uy8G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uy8G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1851982,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/187849238?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uy8G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!uy8G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa57299a6-5758-4c3a-bcd3-443638e6a53c_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>I started my deep <a href="https://www.dmytrohuz.com/p/rebuilding-cryptography-from-scratch">dive into cryptography</a> six months ago. I wanted to deconstruct its internals into basic building blocks and then build them back up again. One simple idea kept pulling me forward&#8212;fascinating me and motivating me to go deeper: how can a crowd of absolute strangers&#8212;over the internet, an inherently insecure medium&#8212;exchange information securely?</p><p>I won&#8217;t lie: I honestly expected to find one simple answer that would &#8220;click&#8221; and give me an Aha moment. Instead, I fell into a rabbit hole. One idea led to another; one logical structure interacted with&#8212;and depended on&#8212;another. Math, history, logic, statistics. Topics and disciplines intertwined into a complicated, sophisticated ornament. No final answers&#8212;only more questions, theories, experiments, and try-and-fail stories. Stories of absolute trust and absolute failure, of elegant ideas that didn&#8217;t work, of intuition that misled, and of randomness that won. Brilliant people built brilliant solutions&#8212;some failed, then came new solutions, and new solutions again, until something finally held. The long, long, long road to security&#8230; Yes, it was a fascinating journey. And in the end, I finally understood how the philosopher&#8217;s stone of public-key encryption works.</p><p>As we saw in the previous articles, the first challenge was to build a secure and reliable way for two peers&#8212;who know each other and share a secret key&#8212;to communicate. It doesn&#8217;t sound difficult, yet it took more than ten articles to show how to do it properly (and how many ways it can go wrong).</p><p>The next challenge is to achieve the same goal for&#8230; strangers&#8230; who share no key at all.</p><p>I want to keep it short this time. The internet is full of articles that implement protocols and asymmetric ciphers. Here I just want to show the core idea as simply as possible. I want to give you the Aha moment I was searching for&#8212;and then point you to deeper references if you&#8217;re interested in real-world usage and implementation. Or we can go further: tell me what topic you want next, and I&#8217;ll happily write about it.</p><p>So, let the show begin&#8212;and please follow my hands very carefully.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>The &#8220;Aha-math&#8221; behind public-key encryption</strong></h2><p>Let&#8217;s take two prime numbers (numbers divisible only by 1 and themselves):</p><pre><code><code>p = 3
q = 11</code></code></pre><p>These will be the foundation of everything that follows.</p><p>Now let&#8217;s multiply them:</p><pre><code><code>n = p * q = 3 * 11 = 33</code></code></pre><p>This number, n, will be our modulus.</p><p>Modulo arithmetic simply means that numbers &#8220;wrap around&#8221; after reaching a certain value. If the result of an operation (addition, multiplication, etc.) is larger than the modulus, we divide by the modulus and take the remainder.</p><p>For example:</p><pre><code><code>result = 23 + 11 = 34
result = 34 % 33 = 1</code></code></pre><p>Because 34 divided by 33 leaves a remainder of 1.</p><p>You can imagine modulo arithmetic as an infinite array that keeps repeating the same sequence of numbers:</p><pre><code><code>modulo_array_33 = [0,1,2,3,...,31,32,0,1,2,3,...,31,32,0,...]
</code></code></pre><p>So when we compute:</p><pre><code><code>print(modulo_array_33[34])
# 1</code></code></pre><p>We &#8220;wrap around&#8221; and land back at 1.</p><div><hr></div><p>Next, let&#8217;s consider all numbers from 1 to 33:</p><pre><code><code>S = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32}</code></code></pre><p>Now we ask: how many of these numbers do <strong>not</strong> share a common divisor with 33?</p><p>In other words, how many numbers are <em>relatively prime</em> to 33?</p><p>There is a beautiful formula for that:</p><pre><code><code>f = (p-1)*(q-1) = (3-1)*(11-1) = 2*10 = 20</code></code></pre><p>So there are <strong>20 numbers</strong> that are relatively prime to 33.</p><p>This number f is known as Euler&#8217;s totient of n. It plays a central role in RSA.</p><div><hr></div><p>Now the most mystical part begins.</p><p>We need to choose a number e that is relatively prime to 20.</p><pre><code><code>e = 3</code></code></pre><p>3 and 20 share no common factors &#8212; so this works.</p><p>Now comes the heart of the magic.</p><p>We need to find a number d such that:</p><pre><code><code>e * d &#8801; 1 (mod f)
3 * d &#8801; 1 (mod 20)</code></code></pre><p>We are looking for a number d that, when multiplied by 3, leaves remainder 1 after division by 20.</p><p>It turns out:</p><pre><code><code>d = 7</code></code></pre><p>Because:</p><pre><code><code>3 * 7 = 21
21 % 20 = 1</code></code></pre><p><strong>You&#8217;ll be surprised &#8212; at least, I was.</strong> At this point we&#8217;ve already built everything we need for public-key encryption. Now let me show you.</p><div><hr></div><p>Suppose we want to encrypt the number:</p><pre><code><code>m = 4</code></code></pre><p>Our public key is two numbers that we previously created:</p><pre><code><code>public_key = (n, e) = (33, 3)</code></code></pre><p>To encrypt:</p><pre><code><code>ciphertext = m**e mod(n) = 4**3 % 33 = 64 % 33 = 31</code></code></pre><p>So the ciphertext is:</p><pre><code><code>31</code></code></pre><p>Now we decrypt using the private key d = 7:</p><pre><code><code>message = ciphertext**d mod(n) = 31**7 % 33 = 27512614111 % 33 = 4 # 0_o</code></code></pre><pre><code><code>4</code></code></pre><p>Exactly the original message.</p><div><hr></div><p>And this &#8212; in its purest, smallest, most transparent form &#8212; is how RSA works.</p><h3><strong>Why does it work?</strong></h3><p>In the small examples, it almost feels like a trick: we raise a number to one power, then to another, and somehow everything &#8220;cancels out&#8221; and the original message comes back. But what&#8217;s really happening is simpler&#8212;and honestly, more beautiful. When you work modulo a number, powers don&#8217;t grow forever; they eventually start repeating. You&#8217;re operating inside a finite world, so exponentiation can&#8217;t keep producing &#8220;new&#8221; values indefinitely&#8212;at some point it must loop. In our tiny example the loop is short, so you can literally watch it happen by hand. RSA chooses the public exponent and the private exponent so that, together, they make you go &#8220;one full loop plus one extra step.&#8221; Encryption moves you forward along that loop, and decryption moves you forward again in a way that lands you exactly back at the starting point. With small numbers the cycle is visible; with real RSA it&#8217;s the same mechanism, just scaled to cycles that are unimaginably large. The math isn&#8217;t magic&#8212;it&#8217;s controlled movement inside a huge circular system, with the steps chosen so that going forward twice brings you back home.</p><p>It took me a while to understand the math behind this. If you want the deeper, more formal explanation, I highly recommend Appendix A of <strong>A Graduate Course in Applied Cryptography</strong>: <a href="https://toc.cryptobook.us/book.pdf">https://toc.cryptobook.us/book.pdf</a></p><h3><strong>Why is it secure?</strong></h3><p>In the examples above I deliberately used tiny numbers (like 3, 11, and 33) because that&#8217;s the only way to see the whole mechanism with your own eyes and not drown in computation. But with numbers that small, RSA is basically a toy: anyone can factor n in seconds, reconstruct the hidden structure, and &#8220;unlock&#8221; everything. In real life it&#8217;s the same exact workflow&#8212;just scaled up brutally. p and q are enormous primes (hundreds of digits), so n becomes massive. It&#8217;s still easy to multiply two huge primes and publish n, but it becomes practically impossible to run that process backwards and recover the original primes from n. And that&#8217;s the whole point: if you can&#8217;t factor n, you can&#8217;t rebuild the private key. Small numbers help you understand the idea; huge numbers are what make the idea survive contact with the real world.</p><p>If you want a more implementation-oriented walkthrough of RSA, here&#8217;s a practical reference: <a href="https://www.geeksforgeeks.org/computer-networks/rsa-algorithm-cryptography/">https://www.geeksforgeeks.org/computer-networks/rsa-algorithm-cryptography/</a></p><h2><strong>Final word</strong></h2><p>I deliberately kept this article as small and abstract as possible&#8212;not to avoid details, but to show the core idea behind the whole concept. There are many related topics that go deeper and wider: real-world protocols, padding schemes, key formats, attacks, implementation traps, and all the practical engineering that turns &#8220;nice math&#8221; into something you can safely deploy. It&#8217;s an endless rabbit hole, and it makes no sense to cram all of it into one post.</p><p>What I wanted here was the quintessence: one core idea. The mechanism. The &#8220;Aha.&#8221; And I hope it landed.</p><p>Thank you for staying with me for so long. With this, I&#8217;m closing my cryptography series and moving on to the next technologies.</p><p>As always, I&#8217;m open to suggestions and requests&#8212;feel free to drop me a note ;)</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building Own MAC — Part 3: Reinventing HMAC from SHA-256]]></title><description><![CDATA[In the previous article, we did something slightly ridiculous.]]></description><link>https://www.dmytrohuz.com/p/building-own-mac-part-3-reinventing</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/building-own-mac-part-3-reinventing</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Fri, 23 Jan 2026 18:58:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QAi6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QAi6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QAi6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QAi6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1814272,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/185566303?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QAi6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!QAi6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bb1c89-ee6c-4240-9150-0469a12ab722_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.dmytrohuz.com/p/building-own-mac-part-2-fixing-aes">In the previous article</a>, we did something slightly ridiculous.</p><p>We took a block cipher &#8212; a tool designed to transform <strong>one block into one block</strong> &#8212; and forced it to behave like something else.</p><p>We wanted authentication.</p><p>We needed a fixed-size tag.</p><p>We had arbitrary-length messages.</p><p>So we built a &#8220;message &#8594; block&#8221; machine out of a &#8220;block &#8594; block&#8221; primitive.</p><p>It worked.</p><p>We reinvented <strong>CMAC</strong>.</p><p>And then an uncomfortable thought appears:</p><p>Why did we do all of that?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2><strong>A strange d&#233;j&#224; vu</strong></h2><p>Look again at the final construction from Part 2.</p><p>It has this shape:</p><ul><li><p>arbitrary-length input</p></li><li><p>processed block by block</p></li><li><p>a small internal state</p></li><li><p>a fixed-size output</p></li><li><p>no way to reverse it</p></li><li><p>sensitive to every bit of input</p></li></ul><p>That shape should feel very familiar.</p><p>Because that is <strong>exactly</strong> the shape of a hash function.</p><p>So the obvious question is:</p><blockquote><p>If hash functions already compress messages into fixed-size values,</p><p>why didn&#8217;t we start there?</p></blockquote><div><hr></div><h2><strong>Fix attempt #1 &#8212; &#8220;Just hash with a secret&#8221;</strong></h2><p>Let&#8217;s do the thing every brain does first.</p><p>We want a tag.</p><p>We have a hash function.</p><p>We also have a secret key.</p><p>So we try:</p><pre><code><code>tag = SHA256(K || M)</code></code></pre><p>Or maybe:</p><pre><code><code>tag = SHA256(M || K)</code></code></pre><p>It feels clean.</p><p>It feels simple.</p><p>It feels <em>much</em> simpler than CMAC.</p><p>No AES.</p><p>No modes.</p><p>No subkeys.</p><p>No final-block gymnastics.</p><p>Before we trust it, we do what this series is about.</p><p>We break it.</p><div><hr></div><h2><strong>A reminder: how SHA-256 actually works</strong></h2><p>SHA-256 is not a black box.</p><p>Internally, it is an <strong>iterative compression machine</strong>.</p><p>Here, <em>compression</em> does <strong>not</strong> mean &#8220;making data smaller&#8221; in the everyday sense.</p><p>It means something more precise:</p><blockquote><p>a function that takes</p><p>a <strong>fixed-size internal state</strong></p><p>and a <strong>fixed-size input block</strong>,</p><p>and produces a <strong>new fixed-size state</strong>.</p></blockquote><p>Nothing is expanded.</p><p>Nothing is reversible.</p><p>Information is <em>folded</em> into state.</p><p>Conceptually, it looks like this:</p><pre><code><code>H0 = IV
H1 = compress(H0, block1)
H2 = compress(H1, block2)
...
output = Hn</code></code></pre><p>One detail matters more than everything else:</p><blockquote><p>The output hash <strong>is the final internal state</strong>.</p></blockquote><p>There is no extra sealing step at the end.</p><p>Which means:</p><p>if you know Hash(M), you know the state <em>after</em> processing M.</p><p>And that detail matters far more than intuition suggests.</p><div><hr></div><h2><strong>Break &#8212; length extension</strong></h2><p>Assume the system uses:</p><pre><code><code>tag = SHA256(K || M)</code></code></pre><p>The attacker sees:</p><ul><li><p>the message M</p></li><li><p>the tag SHA256(K || M)</p></li></ul><p>They do <strong>not</strong> know K.</p><p>But they do know:</p><ul><li><p>the hash algorithm</p></li><li><p>the block size</p></li><li><p>the padding rules</p></li></ul><p>And that is enough.</p><p>Because they can do this:</p><pre><code><code>tag' = SHA256_continue(
          state = tag,
          data  = padding(K || M) || extra
       )</code></code></pre><p>Result:</p><pre><code><code>tag' = SHA256(K || M || padding || extra)</code></code></pre><blockquote><p>The attacker reused the final internal hash state and simply continued the hash computation, producing a valid tag for a longer message without knowing the key.</p></blockquote><p>No key.</p><p>No guessing.</p><p>No cryptanalysis.</p><p>The attacker just extended an authenticated message.</p><p>This is a <strong>length extension attack</strong>.</p><p>And it completely breaks this construction.</p><div><hr></div><h2><strong>Important lesson #1</strong></h2><p>This is not a weakness of SHA-256.</p><p>SHA-256 did exactly what it was designed to do.</p><p>The failure is conceptual:</p><blockquote><p>You treated a structured machine as if it were a black box.</p></blockquote><p>Hash functions expose their internal chaining state by design.</p><p>If your MAC construction allows an attacker to reuse that state, it is broken.</p><div><hr></div><h2><strong>Fix attempt #2 &#8212; &#8220;Fine. Let&#8217;s hash twice.&#8221;</strong></h2><p>Okay.</p><p>If the structure leaks, let&#8217;s hide it.</p><p>What about this?</p><pre><code><code>tag = SHA256(K || SHA256(K || M))</code></code></pre><p>Now the internal state of the first hash is buried inside another hash.</p><p>This feels safer.</p><p>But pause for a moment and look at what we&#8217;re doing.</p><p>We are:</p><ul><li><p>stacking primitives blindly</p></li><li><p>hoping structure disappears</p></li><li><p>having no clear argument <em>why</em> this fixes the problem</p></li></ul><p>This is exactly the pattern we saw in Part 2.</p><p>We&#8217;re patching again.</p><p>And we already know where patching leads.</p><p>So let&#8217;s stop and reset.</p><div><hr></div><h2><strong>Define the problem properly (again)</strong></h2><p>From everything we learned so far, a real hash-based MAC must guarantee:</p><ol><li><p>Only someone with the key can compute a valid tag</p></li><li><p>Only someone with the key can verify a valid tag</p></li><li><p>The message cannot be extended or truncated</p></li><li><p>The internal hash state cannot be reused</p></li><li><p>Variable-length messages must be safe by design</p></li></ol><p>So the real question is not:</p><blockquote><p>&#8220;How do we mix a key into a hash?&#8221;</p></blockquote><p>The real question is:</p><blockquote><p><strong>How do we prevent the attacker from continuing the hash computation?</strong></p></blockquote><div><hr></div><h2><strong>The key insight &#8212; control the boundaries</strong></h2><p>The mistake so far was mixing everything into one stream:</p><ul><li><p>key</p></li><li><p>message</p></li><li><p>finalization</p></li></ul><p>That gave the attacker something extendable.</p><p>So what if we don&#8217;t do that?</p><p>What if:</p><ul><li><p>the key is mixed <strong>before</strong> the message</p></li><li><p>the message is fully compressed</p></li><li><p>the key is mixed <strong>again</strong> after</p></li></ul><p>So the attacker never sees a reusable internal state.</p><p>The shape becomes:</p><pre><code><code>inner = SHA256( (K &#8853; ipad) || M )
tag   = SHA256( (K &#8853; opad) || inner )</code></code></pre><p>Don&#8217;t focus on the constants yet.</p><p>Focus on the structure:</p><ul><li><p>the message is fully absorbed before finalization</p></li><li><p>the attacker never gets a state they can continue</p></li><li><p>the key controls both boundaries</p></li><li><p>length extension becomes impossible</p></li></ul><p>This feels different.</p><p>Because this time, we&#8217;re not guessing.</p><p>We&#8217;re designing.</p><div><hr></div><h2><strong>What are ipad and opad?</strong></h2><p>At first glance, ipad and opad look like magic constants.</p><p>They are not.</p><p>They serve <strong>one very specific purpose</strong>:</p><p><strong>domain separation</strong>.</p><ul><li><p>ipad (inner padding) = byte 0x36 repeated to block size</p></li><li><p>opad (outer padding) = byte 0x5c repeated to block size</p></li></ul><p>They ensure that:</p><ul><li><p>the inner hash and outer hash live in <strong>different domains</strong></p></li><li><p>no internal state can be reused across phases</p></li><li><p>Hash(K &#8853; ipad || M) can never collide structurally with Hash(K &#8853; opad || something_else)</p></li></ul><p>In other words:</p><blockquote><p>ipad and opad prevent the inner hash from being mistaken for the outer hash.</p></blockquote><p>They are not there for randomness.</p><p>They are there to make <em>structure explicit</em> and unforgeable.</p><div><hr></div><h2><strong>Why this construction survives</strong></h2><p>Let&#8217;s stress it the same way we stressed everything else.</p><ul><li><p>Can the attacker extend the message?</p><p>No &#8212; the inner hash is finalized before the outer hash begins.</p></li><li><p>Can they reuse an internal state?</p><p>No &#8212; the state is never exposed in a usable form.</p></li><li><p>Can they fake a tag without the key?</p><p>No &#8212; both passes depend on secret key material.</p></li><li><p>Does variable message length matter?</p><p>No &#8212; the hash function already handles it safely.</p></li></ul><p>This construction doesn&#8217;t feel clever.</p><p>It feels <strong>inevitable</strong>.</p><p>Exactly like CMAC did once all constraints were visible.</p><div><hr></div><h2><strong>Name reveal: HMAC</strong></h2><p>At this point, we can finally say the name.</p><p>The construction we just derived is called:</p><p><strong>HMAC &#8212; Hash-based Message Authentication Code</strong></p><p>And just like with CMAC, the name is the least interesting part.</p><p>The important part is that:</p><ul><li><p>it exists because constraints exist</p></li><li><p>it looks complex because the problem is subtle</p></li><li><p>it survived decades of cryptanalysis because it was designed, not guessed</p></li></ul><div><hr></div><h2><strong>Python implementation (SHA-256 + HMAC)</strong></h2><p>As before, we&#8217;ll use a library for the primitive and write the logic ourselves.</p><p>We are not trying to reimplement SHA-256 bit by bit.</p><p>We are showing the structure clearly.</p><pre><code><code>import hashlib

def sha256(data: bytes) -&gt; bytes:
    return hashlib.sha256(data).digest()

def hmac_sha256(key: bytes, message: bytes) -&gt; bytes:
    block_size = 64  # SHA-256 block size

    if len(key) &gt; block_size:
        key = sha256(key)
    if len(key) &lt; block_size:
        key = key + b"\x00" * (block_size - len(key))

    ipad = bytes([0x36] * block_size)
    opad = bytes([0x5c] * block_size)

    inner = sha256(bytes(k ^ i for k, i in zip(key, ipad)) + message)
    tag   = sha256(bytes(k ^ o for k, o in zip(key, opad)) + inner)

    return tag</code></code></pre><p>There is no magic here.</p><p>Every line corresponds to a design decision we just derived.</p><p>And if you compare the output with Python&#8217;s built-in hmac module, it will match.</p><div><hr></div><h2><strong>Testing the implementation</strong></h2><p>A MAC is useless if you can&#8217;t trust it.</p><p>So we verify our implementation against Python&#8217;s standard library:</p><pre><code>import hmac

def test_hmac():
    key = b"super-secret-key"
    message = b"hello world"

    my_tag = hmac_sha256(key, message)
    std_tag = hmac.new(key, message, hashlib.sha256).digest()

    assert my_tag == std_tag
    print("HMAC implementation verified.")

test_hmac()</code></pre><p>If this assertion passes, your implementation is correct.</p><p>No hand-waving.</p><p>No &#8220;it seems to work&#8221;.</p><p>Just a hard <strong>yes</strong> or <strong>no</strong>.</p><h2><strong>Final symmetry</strong></h2><p>Let&#8217;s zoom out one last time.</p><p>In this series, we built two MACs from scratch:</p><ul><li><p><strong>CMAC</strong> &#8212; built from a block cipher</p></li><li><p><strong>HMAC</strong> &#8212; built from a hash function</p></li></ul><p>Different primitives.</p><p>Same constraints.</p><p>And in both cases, the path was identical:</p><ul><li><p>intuition failed</p></li><li><p>naive fixes broke</p></li><li><p>constraints emerged</p></li><li><p>structure followed</p></li><li><p>names came last</p></li></ul><p>Once you see the constraints, the designs stop looking arbitrary.</p><p>They look inevitable.</p><p>And that was the real goal of this series.</p><p>Not to teach you how to <em>use</em> MACs.</p><p>But to teach you how to <strong>recognize when a construction makes sense</strong> &#8212;</p><p>and when it&#8217;s just intuition lying to you again.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Building Own MAC — Part 2: Fixing AES (and accidentally reinventing CMAC)]]></title><description><![CDATA[Why intuition fails in cryptography]]></description><link>https://www.dmytrohuz.com/p/building-own-mac-part-2-fixing-aes</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/building-own-mac-part-2-fixing-aes</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Mon, 19 Jan 2026 20:32:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2YJ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2YJ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2YJ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2YJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png" width="1456" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1502606,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/185104992?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2YJ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 424w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 848w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 1272w, https://substackcdn.com/image/fetch/$s_!2YJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f63b1-3d56-4578-acde-87945b4cd3f5_1536x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the previous series we finished <a href="https://www.dmytrohuz.com/p/building-own-block-cipher-part-3">AES and its modes</a>. And in the previous article revealed why <a href="https://www.dmytrohuz.com/p/building-own-mac-part-1-encrypted">it is still not secure</a>.</p><p>We can encrypt messages.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We can decrypt messages.</p><p>We can ship.</p><p>And then reality does what reality always does:</p><p>It slaps you.</p><p>Because encryption solves <strong>one</strong> problem:</p><blockquote><p>&#8220;If you don&#8217;t know the key, you can&#8217;t read the message.&#8221;</p></blockquote><p>It does <strong>not</strong> solve this problem:</p><blockquote><p>&#8220;If you don&#8217;t know the key, you can&#8217;t change the message.&#8221;</p></blockquote><p>So we face the classic situation:</p><p>&#9989; we have secrecy</p><p>&#10060; we still don&#8217;t have trust</p><p>And now we do what every engineer does when something breaks:</p><p><strong>we try to patch it fast.</strong></p><p>Let&#8217;s go through the exact fixes your brain naturally generates at 2am.</p><p>Most of them fail.</p><p>Some fail <em>spectacularly</em>.</p><p>And each failure forces one new design constraint&#8230; until we reinvent a real solution.</p><div><hr></div><h1><strong>Goal (simple and brutal)</strong></h1><p>We want the receiver to be able to say:</p><ul><li><p>&#9989; accept</p></li><li><p>&#10060; reject</p></li></ul><p>based on <strong>one small extra value</strong>.</p><p>No philosophy. No &#8220;maybe&#8221;.</p><p>Just: <em>does this message deserve to exist?</em></p><div><hr></div><h1>Fix attempt #1 &#8212; &#8220;Just hash the plaintext&#8221;</h1><p>Classic.</p><pre><code><code>C   = Enc(M)
tag = Hash(M)
send (C, tag)</code></code></pre><p>Receiver decrypts <code>C</code>, recomputes <code>Hash(M)</code>, compares.</p><h3>Break (instant)</h3><p>Hashes have no secrets.</p><p>Attacker changes message &#8594; attacker recomputes hash &#8594; sends new pair.</p><p>&#9989; receiver accepts</p><p>&#9989; attacker wins</p><p>&#9989; we learn nothing except pain</p><h3>Takeaway</h3><p><strong>If the attacker can compute your tag, it&#8217;s not authentication.</strong></p><p>It&#8217;s a checksum.</p><p>So the tag must involve a secret.</p><div><hr></div><h1>Fix attempt #1.5 &#8212; &#8220;Put the hash inside the encrypted message&#8221;</h1><p>Okay, fine.</p><p>Let&#8217;s hide the hash.</p><pre><code><code>payload = M || Hash(M)
C = Enc(payload)
send C</code></code></pre><p>Receiver decrypts, extracts <code>M</code> and the embedded hash, recomputes, compares.</p><p>This feels smart.</p><p>It&#8217;s not.</p><h3>Break (modes bite you)</h3><p>In malleable modes (CTR / OFB / CFB), the attacker can flip bits in ciphertext to flip predictable bits in plaintext.</p><p>So they flip bits in the message part&#8230;</p><p>and flip corresponding bits in the embedded hash part.</p><p>They don&#8217;t need to know the key.</p><p>They don&#8217;t need to know the hash function.</p><p>They don&#8217;t need to understand anything.</p><p>They just move bits.</p><p>Receiver decrypts:</p><pre><code><code>M' || Hash(M')</code></code></pre><p>Hash matches. Receiver accepts.</p><p>0_o</p><h3>Takeaway</h3><p><strong>Hiding a checker does not make it a check.</strong></p><p>If your &#8220;verification data&#8221; lives inside the thing being protected, it can be modified together with it.</p><p>We need the tag to be <em>outside</em> the encrypted payload and unforgeable.</p><div><hr></div><h1>Fix attempt #2 &#8212; &#8220;Encrypt the hash separately&#8221;</h1><p>Next idea:</p><pre><code><code>C   = Enc(M)
tag = Enc(Hash(M))
send (C, tag)</code></code></pre><p>Now the attacker can&#8217;t see the hash, can&#8217;t recompute it.</p><p>So&#8230; done?</p><p>Not even close.</p><h3>Break (you verified&#8230; what exactly?)</h3><p>Now you have two encrypted blobs.</p><p>And the receiver has to answer a painful question:</p><blockquote><p>What exactly are we proving by comparing these?</p></blockquote><ul><li><p>Do we verify the plaintext?</p></li><li><p>The ciphertext?</p></li><li><p>The padding?</p></li><li><p>The length?</p></li><li><p>The context (endpoint / protocol version / message type)?</p></li></ul><p>Nothing is bound. Everything is implied.</p><p>And implied security is not security.</p><p>Also: even if you try to &#8220;compare decrypted hash to recomputed hash&#8221;, you&#8217;re back to the earlier issue &#8212; encryption modes are malleable and protocol context is not bound.</p><h3>Takeaway</h3><p><strong>Encrypting a checker is not the same as verifying a message.</strong></p><p>We need a <em>single verification value</em>, not two blobs that &#8220;seem related&#8221;.</p><div><hr></div><h1>Fix attempt #2.5 &#8212; &#8220;Encrypt the ciphertext and compare&#8221;</h1><p>Okay. Let&#8217;s remove ambiguity.</p><pre><code><code>C   = Enc(M)
tag = Enc(C)
send (C, tag)</code></code></pre><p>Receiver decrypts tag &#8594; gets <code>C'</code> &#8594; compares <code>C' == C</code>.</p><p>Now we have:</p><ul><li><p>a secret</p></li><li><p>a deterministic check</p></li><li><p>a clean yes/no</p></li></ul><p>This looks decent.</p><p>And it still fails.</p><h3>Break (you authenticated bytes, not meaning)</h3><p>This check proves exactly one thing:</p><blockquote><p>&#8220;The ciphertext blob wasn&#8217;t modified.&#8221;</p></blockquote><p>It does <strong>not</strong> prove:</p><ul><li><p>that this ciphertext belongs to <em>this</em> endpoint</p></li><li><p>that it&#8217;s intended for <em>this</em> message type</p></li><li><p>that it&#8217;s valid <em>in this context</em></p></li><li><p>that it&#8217;s fresh</p></li><li><p>that it&#8217;s not a replay</p></li></ul><p>So the attacker doesn&#8217;t need to forge anything.</p><p>They just <strong>replay</strong> a valid <code>(C, tag)</code> where it causes damage.</p><p>&#8220;Transfer $10&#8221; becomes &#8220;Transfer $10 again.&#8221;</p><p>Or becomes &#8220;Approve something you approved yesterday.&#8221;</p><p>You built a very strong proof of internal consistency&#8230; and zero proof of intent.</p><h3>Takeaway</h3><p><strong>Consistency is not authenticity.</strong></p><p>Authentication must bind <em>meaning and context</em>, not just bytes.</p><div><hr></div><h1>Fix attempt #3 &#8212; &#8220;Use a ciphertext artifact as the tag&#8221;</h1><p>Another idea people try:</p><blockquote><p>&#8220;Maybe the last ciphertext block depends on everything. Let&#8217;s use it.&#8221;</p></blockquote><pre><code><code>C   = Enc(M)
tag = last_block(C)</code></code></pre><h3>Break (structural)</h3><p>This &#8220;tag&#8221; depends on:</p><ul><li><p>mode internals</p></li><li><p>padding</p></li><li><p>message length</p></li><li><p>where the last block boundary falls</p></li></ul><p>Truncation, extension, prefixes&#8230; the whole thing becomes ambiguous.</p><h3>Takeaway</h3><p><strong>Artifacts are not tags.</strong></p><p>We need a tag function that we control, end-to-end.</p><div><hr></div><h1>Fix attempt #4 &#8212; &#8220;Fine. Let&#8217;s build a tag ourselves.&#8221;</h1><p>At this point it&#8217;s pretty clear that all &#8220;attach something and encrypt it&#8221; hacks are cursed.</p><p>So let&#8217;s stop patching symptoms and define what we actually need.</p><p>We need a function that takes:</p><ul><li><p>a secret key <code>K</code></p></li><li><p>a message <code>M</code> of <strong>any length</strong></p></li></ul><p>and produces:</p><ul><li><p>a <strong>fixed-size tag</strong> (something like 16 bytes)</p></li></ul><p>So not:</p><ul><li><p>Block &#8594; Block (that&#8217;s what AES gives us)</p></li><li><p>Message &#8594; Message (that&#8217;s what modes give us)</p></li></ul><p>but:</p><blockquote><p>Message &#8594; Block</p></blockquote><p>And yes, AES still sounds useful, because it&#8217;s keyed and strong.</p><p>The only problem is&#8230; AES doesn&#8217;t speak &#8220;message&#8221;. It speaks &#8220;one block&#8221;.</p><p>So we have to build a &#8220;message-to-block machine&#8221; out of &#8220;block-to-block&#8221;.</p><p>Which means: <strong>state</strong>.</p><p>We invent a small internal value <code>X</code> (one AES block), and we feed the message block-by-block:</p><ul><li><p>start with some known initial state <code>X0</code></p></li><li><p>combine the current block with the state</p></li><li><p>run AES</p></li><li><p>repeat</p></li></ul><p>The most natural combine operation is XOR (it keeps the size).</p><p>So the first honest design becomes:</p><pre><code><code>X0 = 0
for each block Bi in message:
    Xi = AES(K, Xi-1 XOR Bi)
tag = Xn</code></code></pre><p>This is the first time we&#8217;re no longer &#8220;encrypting a message&#8221;.</p><p>We&#8217;re doing something else:</p><ul><li><p>the output is fixed-size no matter how long the message is</p></li><li><p>you can&#8217;t decrypt the tag back into the message</p></li><li><p>we are folding the message into a state</p></li></ul><p>That&#8217;s not encryption. That&#8217;s <strong>compression</strong> (in the cryptographic sense).</p><p>Now: does it work?</p><p>It works <em>almost</em> perfectly&#8230; until you remember messages are not fixed-length.</p><p>And the moment message length varies, this construction starts bleeding</p><p>Now: break it.</p><h3>The break: variable-length messages</h3><p>This construction is essentially &#8220;CBC-MAC&#8221;.</p><p>It works for fixed-length messages.</p><p>It breaks for variable length.</p><p>Reason: the output tag is a valid internal state, so extension/splicing tricks become possible unless you bind the message length / finalization rules.</p><p>(And yes, this is a known and very real class of failures: <em>CBC-MAC on variable-length messages is insecure</em>.)</p><h3>Takeaway</h3><p><strong>The end of the message must be cryptographically bound.</strong></p><p>We must make &#8220;this is the final block&#8221; unforgeable.</p><div><hr></div><h1>Fix attempt #5 &#8212; &#8220;Fix the ending (this is where real engineering starts)&#8221;</h1><p>So what exactly breaks in Fix #4?</p><p>Not the chaining itself.</p><p>The break is <strong>the ending</strong>:</p><ul><li><p>messages can end on a block boundary or not</p></li><li><p>messages can have different lengths</p></li><li><p>the final state of one message must not become a reusable internal state for another</p></li></ul><p>So we add finalization rules that make &#8220;this is the end&#8221; cryptographically real.</p><p>Here is the mental model (extended pseudocode):</p><h3>Step A &#8212; Generate two subkeys (K1, K2)</h3><p>We derive subkeys from AES itself:</p><pre><code><code>L  = AES(K,0^128)
K1 = dbl(L)
K2 = dbl(K1)</code></code></pre><p>Where <code>dbl()</code> is &#8220;shift-left-by-1-bit, and if a carry falls off, XOR a constant (Rb) into the last byte&#8221;.</p><p>This is not random ceremony &#8212; it gives us two distinct &#8220;domains&#8221; for the last block.</p><h3>Step B &#8212; Split message into blocks</h3><pre><code><code>B1..B(n-1),last = split_into_16B_blocks(M)</code></code></pre><p>Now two cases:</p><h3>Case 1 &#8212; last block is FULL (exactly 16 bytes)</h3><pre><code><code>M_last = last XOR K1</code></code></pre><h3>Case 2 &#8212; last block is PARTIAL (0..15 bytes)</h3><p>Pad it first (append 0x80 then zeros), then:</p><pre><code><code>last_padded = pad_7816_4(last)
M_last      = last_padded XOR K2</code></code></pre><h3>Step C &#8212; Run the chaining as before, but finalize with M_last</h3><pre><code><code>X = 0^128
for i in 1..(n-1):
    X = AES(K, X XOR Bi)

tag = AES(K, X XOR M_last)</code></code></pre><p>That&#8217;s the &#8220;fixed ending&#8221; logic in full.</p><h3>A very important clarification: there is nothing to decrypt here</h3><p>At this point, it&#8217;s worth stopping for a second and being explicit.</p><p>The output of Fix attempt #5 &#8212; the <strong>tag</strong> &#8212; is <strong>not encrypted data</strong>.</p><p>It is:</p><ul><li><p>not reversible</p></li><li><p>not meant to be decrypted</p></li><li><p>not carrying the message inside it</p></li></ul><p>It is the <strong>final internal state</strong> of a compression process.</p><p>Verification works like this:</p><ol><li><p>The receiver already has the message <code>M</code></p></li><li><p>The receiver recomputes the tag <strong>from scratch</strong> using the same algorithm and the same key</p></li><li><p>The receiver compares the two tags</p></li></ol><p>If they match &#8594; accept</p><p>If they don&#8217;t &#8594; reject</p><p>There is no &#8220;decryption step&#8221; for the tag, because <strong>authentication is not a transformation &#8212; it&#8217;s a decision</strong>.</p><p>This is a crucial mental shift:</p><blockquote><p>Encryption answers: &#8220;What was the message?&#8221;</p><p>MAC answers: <em>&#8220;Can I trust this message?&#8221;</em></p></blockquote><p>Trying to &#8220;decrypt a MAC&#8221; is like trying to &#8220;decrypt a checksum&#8221;.</p><p>There is nothing there to recover.</p><blockquote><p>If you feel uncomfortable that the tag can&#8217;t be decrypted &#8212; good. That discomfort means you&#8217;ve stopped thinking about authentication as encryption.</p></blockquote><div><hr></div><h1>Name reveal: <strong>CMAC</strong> (and what we accidentally reinvented)</h1><p>So what did we just build?</p><ul><li><p>We built a <strong>compression function</strong>: message &#8594; fixed-size tag</p><p>(not zip compression &#8212; cryptographic compression: folding data into state)</p></li><li><p>We built <strong>chaining</strong>: a state that evolves block-by-block.</p></li><li><p>We built a <strong>CBC-style MAC core</strong>: <code>X = AES(K, X XOR Bi)</code>.</p></li><li><p>We discovered the &#8220;variable length&#8221; landmine, and fixed it with:</p><ul><li><p><strong>finalization</strong></p></li><li><p><strong>domain separation</strong> for the last block (full vs partial)</p></li><li><p><strong>subkeys</strong> <code>K1</code>, <code>K2</code></p></li><li><p><strong>padding</strong> for the partial last block</p></li></ul></li></ul><p>And once you assemble those pieces, the whole thing has a name:</p><blockquote><p>CMAC &#8212; Cipher-based Message Authentication Code.</p></blockquote><p>CMAC is what remains after you remove all the broken variants.</p><div><hr></div><h1>Python implementation (AES-CMAC)</h1><p>We&#8217;ll use an existing crypto library (because we are not trying to spend 3 weeks reimplementing AES here).</p><p>Install:</p><pre><code><code>pip install pycryptodome</code></code></pre><p>And here is a minimal CMAC implementation (plus a self-test with NIST vectors):</p><pre><code><code>"""
AES-CMAC (CMAC) &#8212; final implementation.

- Uses PyCryptodome for AES-ECB (the block primitive).
- Implements CMAC per NIST SP 800-38B:
    * Subkeys K1/K2 derived from L = AES_K(0^128)
    * CBC-style chaining with IV=0
    * Special last-block handling:
        - full last block  -&gt; XOR K1
        - partial last block -&gt; pad (7816-4) then XOR K2

Install:
    pip install pycryptodome
"""

from __future__ import annotations
from Crypto.Cipher import AES
from Crypto.Hash import CMAC as CMAC_LIB

BLOCK_SIZE = 16
RB = 0x87  # Rb for 128-bit CMAC


def _xor(a: bytes, b: bytes) -&gt; bytes:
    if len(a) != len(b):
        raise ValueError("XOR requires equal-length inputs.")
    return bytes(x ^ y for x, y in zip(a, b))


def _left_shift_1(block: bytes) -&gt; bytes:
    """Shift a 128-bit block left by 1 bit."""
    if len(block) != BLOCK_SIZE:
        raise ValueError("Expected 16-byte block.")
    out = bytearray(BLOCK_SIZE)
    carry = 0
    for i in range(BLOCK_SIZE - 1, -1, -1):
        out[i] = ((block[i] &lt;&lt; 1) &amp; 0xFF) | carry
        carry = (block[i] &gt;&gt; 7) &amp; 1
    return bytes(out)


def _pad_7816_4(partial: bytes) -&gt; bytes:
    """
    ISO/IEC 7816-4 padding: append 0x80 then zeros to reach 16 bytes.
    Used only when the last block is partial (len &lt; 16).
    """
    if len(partial) &gt;= BLOCK_SIZE:
        raise ValueError("pad_7816_4 expects len(partial) &lt; 16.")
    return partial + b"\x80" + b"\x00" * (BLOCK_SIZE - len(partial) - 1)


def _dbl(block: bytes) -&gt; bytes:
    """
    GF(2^128) doubling used for CMAC subkeys.

    dbl(x) = (x&lt;&lt;1)           if MSB(x) = 0
             (x&lt;&lt;1) XOR Rb    if MSB(x) = 1
    """
    if len(block) != BLOCK_SIZE:
        raise ValueError("Expected 16-byte block.")
    shifted = _left_shift_1(block)
    if block[0] &amp; 0x80:  # MSB(x) = 1
        shifted = shifted[:-1] + bytes([shifted[-1] ^ RB])
    return shifted


def _generate_subkeys(aes_ecb_encrypt) -&gt; tuple[bytes, bytes]:
    """
    Subkeys:
      L  = AES_K(0^128)
      K1 = dbl(L)
      K2 = dbl(K1)
    """
    L = aes_ecb_encrypt(bytes(BLOCK_SIZE))

    K1 = _dbl(L)
    K2 = _dbl(K1)

    return K1, K2


def aes_cmac(key: bytes, msg: bytes) -&gt; bytes:
    """
    Compute AES-CMAC tag (16 bytes).

    key: 16/24/32 bytes (AES-128/192/256)
    msg: arbitrary bytes
    """
    if len(key) not in (16, 24, 32):
        raise ValueError("AES key must be 16, 24, or 32 bytes.")

    aes = AES.new(key, AES.MODE_ECB)

    def aes_ecb_encrypt(block: bytes) -&gt; bytes:
        if len(block) != BLOCK_SIZE:
            raise ValueError("AES-ECB expects 16-byte blocks.")
        return aes.encrypt(block)

    K1, K2 = _generate_subkeys(aes_ecb_encrypt)

    # Number of blocks (CMAC treats empty message as one partial block)
    n = (len(msg) + BLOCK_SIZE - 1) // BLOCK_SIZE
    if n == 0:
        n = 1

    # Split: first n-1 full blocks, and a last block (0..16 bytes)
    blocks = [msg[i * BLOCK_SIZE:(i + 1) * BLOCK_SIZE] for i in range(n - 1)]
    last = msg[(n - 1) * BLOCK_SIZE:]  # may be empty, partial, or full

    # Prepare final block with domain separation
    if len(last) == BLOCK_SIZE:
        m_last = _xor(last, K1)
    else:
        m_last = _xor(_pad_7816_4(last), K2)

    # CBC-like chaining with IV=0 on blocks[0..n-2]
    X = bytes(BLOCK_SIZE)
    for b in blocks:
        if len(b) != BLOCK_SIZE:
            raise ValueError("Internal error: non-full intermediate block.")
        X = aes_ecb_encrypt(_xor(X, b))

    # Final tag
    T = aes_ecb_encrypt(_xor(X, m_last))
    return T


# ---------------------------
# Verification helpers/tests
# ---------------------------

def _hx(s: str) -&gt; bytes:
    return bytes.fromhex(s.replace(" ", "").replace("\n", ""))


def verify_against_library(key: bytes, msg: bytes) -&gt; None:
    """Cross-check our CMAC vs PyCryptodome's CMAC."""
    mine = aes_cmac(key, msg)
    lib = CMAC_LIB.new(key, ciphermod=AES)
    lib.update(msg)
    theirs = lib.digest()
    assert mine == theirs, (
        "CMAC mismatch!\n"
        f"mine   = {mine.hex()}\n"
        f"theirs = {theirs.hex()}"
    )


def self_test() -&gt; None:
    """
    Known CMAC values for the famous NIST key + messages from SP 800-38B context.
    We *also* verify with PyCryptodome CMAC to avoid any &#8220;vector confusion&#8221;.
    """
    key = _hx("2b7e151628aed2a6abf7158809cf4f3c")

    msg1 = _hx("6bc1bee22e409f96e93d7e117393172a")  # 16 bytes
    msg2 = _hx("ae2d8a571e03ac9c9eb76fac45af8e51")  # 16 bytes
    msg3 = _hx("30c81c46a35ce411e5fbc1191a0a52ef")  # 16 bytes
    msg4 = _hx("f69f2445df4f9b17ad2b417be66c3710")  # 16 bytes

    tests = [
        (b"", "bb1d6929e95937287fa37d129b756746"),                       # 0
        (msg1, "070a16b46b4d4144f79bdd9dd04a287c"),                     # 16
        (msg1 + msg2, "ce0cbf1738f4df6428b1d93bf12081c9"),              # 32
        (msg1 + msg2 + msg3[:8], "dfa66747de9ae63030ca32611497c827"),   # 40
        (msg1 + msg2 + msg3 + msg4, "51f0bebf7e3b9d92fc49741779363cfe") # 64
    ]

    for m, expected_hex in tests:
        got = aes_cmac(key, m).hex()
        assert got == expected_hex, f"Vector mismatch: got {got}, expected {expected_hex}"
        verify_against_library(key, m)


if __name__ == "__main__":
    self_test()
    print("AES-CMAC OK (vectors + library cross-check passed)")
</code></code></pre><p>The final version is on github: https://github.com/DmytroHuzz/build_own_mac</p><h1>One last observation (and the bridge to Part 3)</h1><p>Look at what we built.</p><p>This tag function:</p><ul><li><p>takes arbitrary-length input</p></li><li><p>updates a small internal state block by block</p></li><li><p>produces a fixed-size output</p></li></ul><p>That is&#8230; suspiciously familiar.</p><p>It looks like the shape of a hash function.</p><p>So in the next article we&#8217;ll do the same trick again:</p><blockquote><p>Instead of forcing AES to behave like a compressor, let&#8217;s start from a primitive that is <em>already</em> a compressor.</p></blockquote><p>And we&#8217;ll see if we can reinvent a hash-based MAC in the same &#8220;no names until the end&#8221; style.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Dmytro&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building Own MAC (Message Authentication Code): Part 1 - Encrypted, but Not Trusted]]></title><description><![CDATA[Why encryption alone is not enough]]></description><link>https://www.dmytrohuz.com/p/building-own-mac-part-1-encrypted</link><guid isPermaLink="false">https://www.dmytrohuz.com/p/building-own-mac-part-1-encrypted</guid><dc:creator><![CDATA[Dmytro Huz]]></dc:creator><pubDate>Sat, 10 Jan 2026 21:59:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bwTN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bwTN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bwTN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bwTN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg" width="900" height="672" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:672,&quot;width&quot;:900,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143355,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.dmytrohuz.com/i/184157394?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bwTN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bwTN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02b23a1a-ae7b-4e25-ada3-9b75c1634b23_900x672.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Huston, we have a problem</h2><p>All right, now we have a super-duper cipher &#8212; AES. (You don&#8217;t? 0_o That means you missed the previous series of articles where we, with paper, glue, and a bit of magic, built our own AES cipher from scratch. Stop reading. Go grab it: <a href="https://www.dmytrohuz.com/p/building-own-block-cipher-part-3">Building Own Block Cipher: Part 3 - AES</a>)</p><p>We can encrypt any message, and only the person who knows the secret key can decrypt it. The rest of the world would need a billion years to break it.</p><p>Does this mean we&#8217;re fine now? Did we finish cryptography? Is there nothing left to worry about?</p><p><strong>Let&#8217;s make a small experiment.</strong></p><p>We created an API for the bank. And one simplified endpoint looks like this:</p><pre><code><code>POST: /api/transfer

BODY
user=$NAME
transaction=$AMOUNT
</code></code></pre><p>Assume the bank can decrypt the request. Ignore key management for now &#8212; this story is not about that.</p><p>Allice wants to send Bob 10$.</p><p>She prepares the message:</p><pre><code><code>user=Bob; transaction=10
</code></code></pre><p>She encrypts the message with AES and sent to the bank.</p><pre><code><code>POST: /api/transfer
BODY:
"a94f4aabdf..."
</code></code></pre><p>In a few minutes she received a notification from the bank:</p><blockquote><p>Your transaction of <strong>10 000$</strong> to the Bob is successful.</p></blockquote><p>0_o&#8230;</p><p>That was&#8230;unexpected.</p><h3><strong>What just happened?</strong></h3><p>A hacker has been quietly listening to Alice&#8217;s network traffic for weeks.</p><p>He cannot decrypt anything. AES is doing its job.</p><p>But he <em>can</em> see many encrypted transfers going back and forth.</p><p>Over time, he notices something interesting:</p><p>small, predictable changes in the encrypted data cause predictable changes <strong>after decryption</strong>.</p><p>So he modifies the encrypted message &#8212; just a little.</p><p>The bank decrypts the message successfully.</p><p>And that is the problem.</p><p>The message was decrypted&#8230;successfully &#129318;&#127996;&#8205;&#9794;&#65039;</p><p>But it was <strong>not authentic!!!</strong></p><p>Yes, this example is simplified. Real attacks look different.</p><p>But the idea is very real and very dangerous.</p><p>We can no longer blindly trust encrypted data.</p><p>Houston, we have a problem.</p><h2>It&#8217;s a Cipher&#8230; It&#8217;s a Hash&#8230; It&#8217;s SuperMAC</h2><p>So.</p><p>We encrypted the message.</p><p>AES did its job.</p><p>The attacker still won.</p><p>That should feel wrong.</p><p>Let&#8217;s rewind a bit.</p><div><hr></div><h3>What encryption actually promised us</h3><p>Encryption is very honest.</p><p>It promises exactly one thing:</p><blockquote><p>&#8220;If you don&#8217;t know the key, you can&#8217;t read this message.&#8221;</p></blockquote><p>That&#8217;s it.</p><p>No hidden features. No bonus guarantees.</p><p>And to be fair &#8212; it kept that promise perfectly.</p><p>The hacker never learned:</p><ul><li><p>who Alice paid</p></li><li><p>how much she paid</p></li><li><p>what the message even says</p></li></ul><p>AES was innocent.</p><div><hr></div><h3>Then why did everything break?</h3><p>Because we quietly assumed something else.</p><p>We assumed that:</p><blockquote><p>&#8220;If the message decrypts &#8212; it must be OK.&#8221;</p></blockquote><p>And that assumption is false.</p><p>Encryption does <strong>not</strong> promise that:</p><ul><li><p>the message wasn&#8217;t modified</p></li><li><p>the message was constructed intentionally</p></li><li><p>the message makes sense</p></li><li><p>the message is safe to execute</p></li></ul><p>It only promises secrecy.</p><p>And yes &#8212; secrecy <strong>is still necessary</strong>.</p><p>We absolutely want the attacker to stay blind.</p><p>But secrecy alone is not enough.</p><div><hr></div><h3>What the bank actually needed</h3><p>The bank needed one more answer.</p><p>Not a complicated one.</p><p>Just this:</p><blockquote><p>&#8220;Was this message created by someone who knows the secret &#8212; and was it changed on the way?&#8221;</p></blockquote><p>Encryption cannot answer that question.</p><p>So we don&#8217;t throw encryption away.</p><p>We <strong>add something next to it</strong>.</p><p>Not to hide the message.</p><p>But to protect it.</p><div><hr></div><h3>&#8220;Can&#8217;t we just hash it?&#8221;</h3><p>At this point, many people say:</p><blockquote><p>&#8220;Okay, fine. Let&#8217;s just hash the message.&#8221;</p></blockquote><p>Hashes are nice.</p><ul><li><p>change one bit &#8594; completely different output</p></li><li><p>fast</p></li><li><p>simple</p></li></ul><p>But there&#8217;s a problem.</p><p>Hashes have no secrets.</p><p>Anyone can compute them.</p><p>Which means anyone can fake them.</p><p>So hashes alone don&#8217;t help.</p><div><hr></div><h3>So&#8230; SuperMAC?</h3><p>Let&#8217;s make a small trick.</p><p>Alice is still sending a message to the bank.</p><p>She already knows how to encrypt it.</p><p>We don&#8217;t change that part.</p><p>Now we add one more step.</p><p>Before sending the message, Alice takes:</p><ul><li><p>the message itself</p></li><li><p>a secret key (shared with the bank)</p></li></ul><p>And she computes a small extra value.</p><p>Call it a <strong>tag</strong>.</p><p>This tag is not encrypted data.</p><p>It does not hide anything.</p><p>It&#8217;s just a short fingerprint that depends on:</p><ul><li><p>the message</p></li><li><p>and the secret key</p></li></ul><p>Now Alice sends <strong>two things</strong> to the bank:</p><ul><li><p>the encrypted message</p></li><li><p>the tag</p></li></ul><p>That&#8217;s it.</p><div><hr></div><h3>What does the bank do?</h3><p>The bank receives:</p><ul><li><p>the encrypted message</p></li><li><p>the tag</p></li></ul><p>It decrypts the message.</p><p>Then it does the same computation itself:</p><ul><li><p>same message</p></li><li><p>same secret key</p></li></ul><p>If the newly computed tag matches the received one &#8212; good.</p><p>The message was not modified.</p><p>If it doesn&#8217;t &#8212; something is wrong.</p><p>The message is rejected.</p><p>No guessing.</p><p>No &#8220;maybe it&#8217;s fine&#8221;.</p><p>Just a hard <strong>yes</strong> or <strong>no</strong>.</p><h3>So what is a MAC, finally?</h3><p>This is it.</p><p>That <strong>tag</strong> we just computed</p><p><em>is</em> the <strong>Message Authentication Code</strong>.</p><p>Nothing more.</p><p>Nothing less.</p><p>A MAC is:</p><ul><li><p>a small piece of data</p></li><li><p>computed from the message</p></li><li><p>using a secret key</p></li><li><p>and verified on the other side</p></li></ul><p>If the tag matches &#8212; the message is authentic.</p><p>If it doesn&#8217;t &#8212; the message is rejected.</p><p>That&#8217;s the whole mechanism.</p><div><hr></div><h3>Important clarification</h3><p>A MAC does <strong>not</strong> replace encryption.</p><p>We still need encryption.</p><p>We still want secrecy.</p><p>The MAC adds something else.</p><p>It adds:</p><ul><li><p><strong>integrity</strong> &#8212; the message was not modified</p></li><li><p><strong>authenticity</strong> &#8212; the message was created by someone who knows the secret</p></li></ul><p>So the picture now looks like this:</p><ul><li><p><strong>Encryption</strong> hides the message</p></li><li><p><strong>MAC</strong> protects the message</p></li></ul><p>Two different tools.</p><p>Two different guarantees.</p><p>Used together.</p><div><hr></div><h3>Why this extra layer matters</h3><p>Without a MAC:</p><ul><li><p>modified messages can slip through</p></li><li><p>decryption can succeed on garbage</p></li><li><p>the system has no way to say &#8220;stop&#8221;</p></li></ul><p>With a MAC:</p><ul><li><p>every modification is detected</p></li><li><p>every forged message is rejected</p></li><li><p>the system can finally trust what it decrypts</p></li></ul><p>This is why real systems don&#8217;t use encryption alone.</p><p>They use <strong>encryption + authentication</strong>.</p><h2>Final thoughts &#8212; and what comes next</h2><p>Over my career, I&#8217;ve learned one important thing.</p><p>If you can <strong>detect</strong> the problem and then <strong>define</strong> it correctly &#8212; you already solved about <strong>60%</strong> of it.</p><p>Another <strong>38%</strong> is designing the right architecture.</p><p>And the remaining <strong>2%</strong> is implementation.</p><p>In this article, we focused on the biggest and most underestimated part of the problem:</p><p><strong>trusting encrypted data</strong>.</p><p>We saw that encryption alone is not enough.</p><p>We saw why &#8220;it decrypts successfully&#8221; is not a security guarantee.</p><p>And we saw what kind of extra property we are missing.</p><p>Deliberately, we stopped there.</p><div><hr></div><h3>Why we stop here</h3><p>Because a MAC is not a single algorithm.</p><p>It is not a cipher.</p><p>It is not a trick.</p><p>It is not one formula.</p><p>A MAC is a <strong>family of designs</strong>.</p><p>And before touching any code, it&#8217;s much more important to understand:</p><ul><li><p><em>how</em> MACs are built</p></li><li><p><em>which architectures exist</em></p></li><li><p>and <em>which building blocks they rely on</em></p></li></ul><p>That&#8217;s what the next article is about.</p><div><hr></div><h3>What&#8217;s next</h3><p>In the next article, we will:</p><ul><li><p>take a close look at the <strong>two main architectures</strong> used to build MACs</p></li><li><p>zoom in on the <strong>primitives</strong> used inside those architectures</p></li><li><p>understand why these designs work &#8212; and why others don&#8217;t</p></li></ul><p>Only after that, in the final part, we&#8217;ll move to implementation.</p><p>We&#8217;ll:</p><ul><li><p>implement the primitives (starting with SHA-256)</p></li><li><p>and then build a MAC on top of them</p></li><li><p>completely from scratch</p></li></ul><p>No black boxes.</p><p>No &#8220;just trust the library&#8221;.</p><p>No magic.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.dmytrohuz.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.dmytrohuz.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item></channel></rss>