Galileo OSNMA chain renewal

Galileo OSNMA (open service navigation message authentication) is a cryptographic system that is used to authenticate the navigation message (satellite ephemeris and clocks, etc.) in the Galileo GNSS. I have spoken before about OSNMA in this blog, since I implemented an OSNMA library in Rust a few years back. A good introduction to OSNMA for readers unfamiliar with how it works can be found in Bert Hubert‘s short series of OSNMA posts. The OSNMA system is currently in the public observation test phase.

On July 4, an OSNMA live test notification went out with the following message:

EVENT DESCRIPTION:  USERS ARE ADVISED THAT, AS PART OF THE PUBLIC OBSERVATION TEST PHASE ACTIVITIES, A TESLA CHAIN RENEWAL IS PLANNED ON 2025-07-07 10:00 UTC AND THE TRANSITION WILL OCCUR ON 2025-07-08 10:00 UTC. THE TESLA CHAIN RENEWAL PROCESS IS DESCRIBED IN THE OSNMA SIS ICD (LINK).

NOTE THAT USER RECEIVERS SHALL PREVENT THE USE OF ANY CHAIN THAT HAS BEEN SUBJECT TO A RENEWAL PROCESS.

I have used the utilities from the Galmon project to record the Galileo INAV data received by a uBlox GNSS receiver that I have at home. This dataset can be used to test OSNMA implementations and to study how the chain renewal was done. The dataset is publised in Zenodo as “Galileo INAV data for OSNMA chain renewal test in July 2025“. In this post I study the chain renewal using my galileo-osnma Rust implementation.

Section 5.5.3 in the OSNMA SIS ICD describes how a TESLA chain renewal should happen. The goal of a chain renewal is to transition the system to a different TESLA chain. Recall that a TESLA chain is generated from an initial key that is never disclosed and that corresponds to some timestamp in the future. A one-way function is repeatedly applied to this key in order to derive the TESLA chain keys backwards in time. This is done until the timestamp when the chain starts is reached. Applying the one-way function once more, the KROOT is generated, which formally has a timestamp previous to the beginning of the chain. The KROOT can be disclosed to receivers, which can then use it to check whether a TESLA key belongs to the chain or not by applying the one-way function as many times as needed to reach the KROOT, and compare with the KROOT they have. Therefore, a chain renewal can happen because the chain comes to an end, as the timestamp of the initial key is reached, or for any other reason (except because of a security compromise in the current chain, since there is a different chain revocation procedure for that).

Chain renewal, taken from the OSNMA SIS ICD v1.1

As shown in the diagram, the chain renewal happens in two steps. During the Step 1, the CPKS (chain and pubkey status) contained in the NMA header of each HKROOT message (which is transmitted every 30 seconds) is EOC (end of chain), indicating receivers that a chain renewal is about to happen. The CID (chain ID) in the NMA header is still the ID of the current chain. During Step 1, alternatively DSMs (digitally signed messages) for the KROOT of the current chain and for the KROOT of the next chain are transmitted. These DSM-KROOT messages contain the KROOT and an ECDSA signature that allows receivers to authenticate the transmitted KROOT against a public key. The goal of transmitting the two KROOTs alternatively is to allow receivers that are just powered on without a valid KROOT to obtain the KROOT for the current chain, while also allowing all receivers that are powered on to obtain the KROOT for the next chain and to store it so that it can be used immediately when the chain change happens (but we will see some pitfalls below).

In Step 2, the CID changes to the ID of the new chain, and the system begins using the new chain. The CPKS is switched back to nominal, and the only DSM-KROOT broadcast is for the new chain. Receivers that had obtained the KROOT of the new chain during Step 1 can start using the new chain immediately. Other receivers need to wait some time to obtain a DSM-KROOT for the new chain.

To process the dataset with galileo-osnma, first I have concatenated all the hourly protobuf files into a single file by doing

cat 4/{9..23}.pb.gnss {5..9}/{0..23}.pb.\
gnss 10/{0..5}.pb.gnss > chain-renewal.pb.gnss

Then I can run galmon-osnma in the following way:

RUST_LOG=trace,galileo_osnma::galmon::transport=info \
RUST_LOG_STYLE=always <chain-renewal.pb.gnss \
galmon-osnma \
--pubkey OSNMA_PublicKey_20240115100000_key.pem \
--pkid 1 \
--merkle-root ... 2>&1 | less -R

I am not showing the Merkle tree root, which is a 256-bit key in hex, because I am unsure about whether I am allowed to redistribute the OSNMA cryptographic material, as it is only available to users registered on the European GNSS Service Centre portal. I have extracted this Merkle tree root from the file OSNMA_MerkleTree_20240115100000_newPKID_1.xml. See the galileo-osnma README for more information about how to obtain this cryptographic material.

In the log we can see that at the beginning of the dataset, on 2025-07-04, a DSM-KROOT with the following data is transmitted.

DSM size = 8 blocks. missing 0 blocks
completed DSM with id = 4, size = 104 bytes
DSM contents [21, d0, 49, 22, 05, 45, 81, 6a, ad, 6d, 03, 57,
f1, 0a, 53, f5, 18, 22, a0, 14, 7c, 94, 75, 7c, 45, bc, 03,
26, 34, da, 74, b9, a0, de, f2, 5a, 8e, 64, 10, f9, e7, cf,
9e, 80, d3, 7c, d6, 0f, f4, 4c, 78, 24, e2, d7, 4f, c3, 9c,
07, 14, 92, b3, 3c, 33, 08, 97, c7, 56, 47, 3f, bf, 8c, 7e,
10, 71, 0e, 0a, 47, 2b, cc, 94, ab, 92, 45, 29, 43, 2a, f6,
e6, 96, 06, 0a, e2, 93, 07, 7b, 99, 89, d8, 12, 99, 11, 2b,
70, 8e]
verified KROOT with public key id 1
current NMA header: NmaHeader { nma_status: Test, chain_id: 3,
chain_and_pubkey_status: Nominal }
storing KROOT Key { data: [10, 83, 245, 24, 34, 160, 20, 124,
148, 117, 124, 69, 188, 3, 38, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0], chain: Chain { id: 3,
hash_function: Sha256, mac_function: HmacSha256,
key_size_bytes: 16, tag_size_bits: 40, maclt: 34,
alpha: 117293090822129 }, gst_subframe: Gst { wn: 1349,
tow: 464370 }, _validated: Validated } in slot 0 (vacant)

The WN and TOW for the KROOT correspond to 2025-07-04 08:59:30 GST. This DSM-KROOT was received at 2025-07-04 09:45:29 GST. OSNMA uses the concept of a floating KROOT. This means that the KROOT that is usually distributed is not the KROOT for the complete TESLA chain, but rather a TESLA key that corresponds to some timestamp already in the past. This serves to reduce the number of one-way function evaluations that a receiver needs to perform from a current TESLA key to reach the KROOT that it has. In this DSM-KROOT we see that the current CID is 3, and that the CPKS is nominal. The chain renewal has not begun yet.

The same DSM is transmitted multiple times, which galileo-osnma ignores because it has already received it. A new DSM with ID 5 is received at 2025-07-04 10:02:59 GST. It contains a more updated floating KROOT, which galileo-osnma ignores because it already has a more recent authenticated TESLA key.

DSM size = 8 blocks. missing 0 blocks
completed DSM with id = 5, size = 104 bytes
DSM contents [21, d0, 49, 22, 05, 45, 82, 6a, ad, 6d, 03, 57,
f1, d6, a3, f2, d2, 01, a0, 34, 20, 76, cd, a5, 32, 77, 98,
de, 8a, 8d, f8, b3, 5f, b5, 5b, 6a, 5c, 4a, b9, 6c, c8, 24,
64, 51, 0e, 68, f7, ae, a9, 1c, 2b, 0e, 72, 0f, 10, f7, 47,
ab, 3a, 4c, d5, d3, 4c, 15, 12, 80, a1, 23, 53, ca, be, fb,
68, 6a, c7, f2, 21, cc, 8b, 18, ef, fc, af, e5, da, 1e, ad,
f9, 25, f2, 1b, c1, 14, 9a, 65, 5d, 76, 64, 0d, d8, b5, 07,
60, c4]
verified KROOT with public key id 1
current NMA header: NmaHeader { nma_status: Test, chain_id: 3,
chain_and_pubkey_status: Nominal }

The next new DSM, with ID 6, is received at 2025-07-04 11:04:29 GST. This has a new updated floating KROOT. We see that the floating KROOT that is broadcast via DSM-KROOT is updated approximately every hour.

A new DSM with ID 12 is received at 2025-07-04 12:05:29 GST. This is a DSM-PKR, which carries an ECDSA public key that can be authenticated against the Merkle tree. This is used by receivers that do not have the current ECDSA public key. As stated in Section 5.4 of the OSNMA ICD, a DSM-PKR is transmitted alternatively with a DSM-KROOT for periods of 30 minutes starting at 00:00, 06:00, 12:00, and 18:00 GST every day. DSM IDs 0-11 are allocated to DSM-KROOT, and DSM IDs 12-15 are allocated to DSM-PKR.

DSM size = 13 blocks. missing 0 blocks
completed DSM with id = 12, size = 169 bytes
DSM contents [70, 94, 1b, d3, 4e, a7, df, 66, 8b, 6f, c5, be,
75, c1, d9, 34, 64, d1, 09, bc, 61, 5c, b5, 2c, 81, 24, 84,
7f, af, b0, 9c, bb, 2b, 6a, af, de, 28, 01, 7b, f0, 74, 4d,
42, 81, 9c, e4, 0e, 3a, 0c, da, 1e, ca, 3f, 7a, 4e, a6, 7e,
13, 4e, 7a, a7, 14, c1, e8, 43, de, 73, d2, 09, e4, c5, bc,
dc, 34, cd, 11, 7f, 2f, e4, 0f, d0, 8b, 11, 00, 09, 99, 7a,
d2, b3, 29, 1d, 3a, 2c, f2, 99, 43, f9, 84, de, 36, 69, e6,
da, 55, 12, 92, 97, 9e, 5b, 8d, 04, 57, 87, fa, 96, 7c, 57,
cc, 23, 63, 8a, 30, 23, 76, 14, ed, d9, 17, 1a, 11, 03, 97,
eb, 43, 78, 9a, a0, f6, d0, 52, a6, 38, 46, 8e, cf, 52, 78,
e6, f6, df, 84, 65, ec, b8, d8, b8, 4b, 8c, 7a, 35, 01, f7,
3b, 1b, 0e, e5, 24, 58, e0]
verified public key in DSM-PKR: DsmPkr {
number_of_blocks: Some(13), message_id: 0,
intermediate_tree_node_0: [148, 27, 211, 78, 167, 223, 102,
139, 111, 197, 190, 117, 193, 217, 52, 100, 209, 9, 188, 97,
92, 181, 44, 129, 36, 132, 127, 175, 176, 156, 187, 43],
intermediate_tree_node_1: [106, 175, 222, 40, 1, 123, 240,
116, 77, 66, 129, 156, 228, 14, 58, 12, 218, 30, 202, 63, 122,
78, 166, 126, 19, 78, 122, 167, 20, 193, 232, 67],
intermediate_tree_node_2: [222, 115, 210, 9, 228, 197, 188,
220, 52, 205, 17, 127, 47, 228, 15, 208, 139, 17, 0, 9, 153,
122, 210, 179, 41, 29, 58, 44, 242, 153, 67, 249],
intermediate_tree_node_3: [132, 222, 54, 105, 230, 218, 85,
18, 146, 151, 158, 91, 141, 4, 87, 135, 250, 150, 124, 87,
204, 35, 99, 138, 48, 35, 118, 20, 237, 217, 23, 26],
new_public_key_type: EcdsaKey(P256Sha256),
new_public_key_id: 1, new_public_key: Some([3, 151, 235, 67,
120, 154, 160, 246, 208, 82, 166, 56, 70, 142, 207, 82, 120,
230, 246, 223, 132, 101, 236, 184, 216, 184, 75, 140, 122,
53, 1, 247, 59]), padding: Some([27, 14, 229, 36, 88, 224]) }

This pattern of transmission of DSMs continues for the next few days. The DSM-KROOT is regularly updated with a more recent floating key, and the DSM-PKR with ID 12 is transmitted 3 times every 6 hours.

We can skip forward to the first time that a CPKS of end-of-chain is seen. This happens at 2025-07-07 10:02:29 GST.

DSM size = 8 blocks. missing 0 blocks
completed DSM with id = 5, size = 104 bytes
DSM contents [21, d0, 49, 22, 05, 46, 21, 6a, ad, 6d, 03, 57,
f1, 24, 0e, 5c, 00, 82, 9b, bf, e3, e9, 92, 28, 44, 1b, 22,
ca, 5e, f0,ed, bb, 53, 2e, bb, 59, 9d, 20, f1, 9d, 7d, a6,
6c, 5b, 41, 8a, 94, 79, 10, 3d, c7, 1b, 2e, 17, 28, c9, 5b,
fc, d5, 7f, 6f, 7c, 08, 0a, 48, 99, 98, 96, af, 01, 0a, 3c,
14, ac, 31, d9, 74, 41, b0, d8, 35, 51, 24, f8, 95, e6, b8,
0d, 69, 0a, 92, 28, e0, bc, 39, 30, 2e, a4, 95, b7, 99, ad,
73, 07]
verified KROOT with public key id 1
current NMA header: NmaHeader { nma_status: Test, chain_id: 3,
chain_and_pubkey_status: EndOfChain }

This DSM-KROOT is the first one that carries an NMA header with CPKS of end-of-chain. Note that even though the NMA header is contained in each of the DSM blocks, and can be read as soon as one such block is received, its value should not be used until it is authenticated by verifying the DSM-KROOT against the public key, since that is the way in which the NMA header is authenticated. Therefore, galileo-osnma does not even look at the NMA header before authenticating it.

The next DSM is received at 2025-07-07 10:06:29 GST, which makes sense because these DSMs have a size of 8 blocks, so it takes 4 minutes to receive them unless the receiver sees multiple satellites that transmit different blocks at the same time.

DSM size = 8 blocks. missing 0 blocks
completed DSM with id = 6, size = 104 bytes
DSM contents [21, 10, 49, 22, 05, 46, 3a, 1d, 2f, 2b, c5, 18,
51, 03, 8a, 25, 64, bb, 45, 26, 7d, 03, b5, 5a, 26, 08, e6,
6e, 81, 63, 24, 7b, 2a, a0, 39, 97, fa, 12, 43, f6, 67, 8d,
e2, 99, 13, 0e, 15, 0a, 70, 4b, 0f, 21, ea, b6, 10, 25, 21,
42, 87, 95, 80, d7, 84, 24, a7, 4c, 7d, 23, bb, a7, 0c, 9a,
ba, 33, 9a, a8, 7f, 16, fe, 07, c0, f3, 67, 3d, b6, 23, 3b,
13, a7, 89, 24, 5c, 10, ce, 10, 58, 47, 71, cc, 96, 02, 8f,
1c, 1d]
verified KROOT with public key id 1
current NMA header: NmaHeader { nma_status: Test, chain_id: 3,
chain_and_pubkey_status: EndOfChain }
storing KROOT Key { data: [3, 138, 37, 100, 187, 69, 38, 125,
3, 181, 90, 38, 8, 230, 110, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0], chain: Chain { id: 0,
hash_function: Sha256, mac_function: HmacSha256,
key_size_bytes: 16, tag_size_bits: 40, maclt: 34,
alpha: 32088435005521 }, gst_subframe: Gst { wn: 1350,
tow: 208770 }, _validated: Validated } in slot 1 (vacant)

This DSM has a different ID compared to the previous one (6 rather than 5). The KROOT that it carries is for the new chain. We can see that the chain for the KROOT has CID 0 rather than 3. The cryptographic parameters of the new chain, except for the alpha, which is used to prevent dictionary attacks and is randomly selected for each chain, are the same as for the current chain. The timestamp of the KROOT is 2025-07-08 09:59:30 GST. This makes sense, because according to the live test notification the new chain will begin usage at 2025-07-08 10:00:00 GST (the notification says UTC, but it is likely forgetting about the 18 leap second offset between GST and UTC). So the KROOT is one chain step (30 seconds) previous to the first TESLA key to be used in the chain.

The DSM that is transmitted changes back and forth a few times between ID 5 (DSM-KROOT for the current chain) and ID 6 (DSM-KROOT for the new chain) until the DSM-KROOT for the current chain is updated with a more recent floating KROOT, using DSM ID 7. DSMs 7 and 6 keep alternating.

At 2025-07-07 12:03:29 GST, a DSM with ID 13 is received.

DSM size = 13 blocks. missing 0 blocks
completed DSM with id = 13, size = 169 bytes
DSM contents [70, 94, 1b, d3, 4e, a7, df, 66, 8b, 6f, c5, be,
75, c1, d9, 34, 64, d1, 09, bc, 61, 5c, b5, 2c, 81, 24, 84,
7f, af, b0, 9c, bb, 2b, 6a, af, de, 28, 01, 7b, f0, 74, 4d,
42, 81, 9c, e4, 0e, 3a, 0c, da, 1e, ca, 3f, 7a, 4e, a6, 7e,
13, 4e, 7a, a7, 14, c1, e8, 43, de, 73, d2, 09, e4, c5, bc,
dc, 34, cd, 11, 7f, 2f, e4, 0f, d0, 8b, 11, 00, 09, 99, 7a,
d2, b3, 29, 1d, 3a, 2c, f2, 99, 43, f9, 84, de, 36, 69, e6,
da, 55, 12, 92, 97, 9e, 5b, 8d, 04, 57, 87, fa, 96, 7c, 57,
cc, 23, 63, 8a, 30, 23, 76, 14, ed, d9, 17, 1a, 11, 03, 97,
eb, 43, 78, 9a, a0, f6, d0, 52, a6, 38, 46, 8e, cf, 52, 78,
e6, f6, df, 84, 65, ec, b8, d8, b8, 4b, 8c, 7a, 35, 01, f7,
3b, 1b, 0e, e5, 24, 58, e0]
verified public key in DSM-PKR: DsmPkr {
number_of_blocks: Some(13), message_id: 0,
intermediate_tree_node_0: [148, 27, 211, 78, 167, 223, 102,
139, 111, 197, 190, 117, 193, 217, 52, 100, 209, 9, 188, 97,
92, 181, 44, 129, 36, 132, 127, 175, 176, 156, 187, 43],
intermediate_tree_node_1: [106, 175, 222, 40, 1, 123, 240,
116, 77, 66, 129, 156, 228, 14, 58, 12, 218, 30, 202, 63, 122,
78, 166, 126, 19, 78, 122, 167, 20, 193, 232, 67],
intermediate_tree_node_2: [222, 115, 210, 9, 228, 197, 188,
220, 52, 205, 17, 127, 47, 228, 15, 208, 139, 17, 0, 9, 153,
122, 210, 179, 41, 29, 58, 44, 242, 153, 67, 249],
intermediate_tree_node_3: [132, 222, 54, 105, 230, 218, 85,
18, 146, 151, 158, 91, 141, 4, 87, 135, 250, 150, 124, 87,
204, 35, 99, 138, 48, 35, 118, 20, 237, 217, 23, 26],
new_public_key_type: EcdsaKey(P256Sha256),
new_public_key_id: 1, new_public_key: Some([3, 151, 235, 67,
120, 154, 160, 246, 208, 82, 166, 56, 70, 142, 207, 82, 120,
230, 246, 223, 132, 101, 236, 184, 216, 184, 75, 140, 122,
53, 1, 247, 59]), padding: Some([27, 14, 229, 36, 88, 224]) }

I don’t know why ID 12 is not re-used, since the contents of the DSM-PKR exactly are the same as before.

The scheduling of the DSMs continues in the same way, with DSM ID 6 containing the KROOT of the new chain alternating with a DSM-KROOT for the floating KROOT of the current chain, and DSM ID 13 transmitted 2 or 3 times every 6 hours.

We can skip forward to the next time that the CPKS becomes nominal again. This happens at 2025-07-08 10:02:29 GST, when a new DSM-KROOT is received and the NMA header is authenticated.

DSM size = 8 blocks. missing 0 blocks
completed DSM with id = 8, size = 104 bytes
DSM contents [21, 10, 49, 22, 05, 46, 3a, 1d, 2f, 2b, c5, 18,
51, 03, 8a, 25, 64, bb, 45, 26, 7d, 03, b5, 5a, 26, 08, e6,
6e, 81, a1, e4, 72, 15, 50, 2c, cf, e5, 64, 9f, 17, 44, 24,
3c, 84, 7a, 90, 55, ed, 6b, f8, 63, c8, d8, b9, 4a, be, 55,
70, e4, a2, 58, 57, 1b, 06, fd, 33, 3b, 1e, b8, 8a, 65, ac,
0b, f0, 93, fc, 28, c4, 07, 1a, bc, 3e, c6, 78, 0d, 42, 85,
54, f1, 28, bd, 56, 53, 9c, cb, 2e, 24, e3, a4, 0d, 72, 9f,
f8, dd]
verified KROOT with public key id 1
current NMA header: NmaHeader { nma_status: Test, chain_id: 0,
chain_and_pubkey_status: Nominal }

We see that the CID is now 0 and the CPSK is back to nominal.

However, the switch in the TESLA chain was supposed to happen slightly before this, exactly at 2025-07-08 10:00:00 GST. If we go back in the log we find some errors from galileo-osnma. They look like this:

could not validate TESLA key Key { data: [15, 82, 245, 204,
146, 38, 5, 53, 208, 225, 44, 185, 44, 129, 101, 213, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
chain: Chain { id: 3, hash_function: Sha256,
mac_function: HmacSha256, key_size_bytes: 16,
tag_size_bits: 40, maclt: 34, alpha: 117293090822129 },
gst_subframe: Gst { wn: 1350, tow: 208800 },
_validated: NotValidated }
using Key { data: [146, 44, 151, 12, 140, 82, 114, 255, 224,
193, 143, 184, 51, 74, 130, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0], chain: Chain { id: 3,
hash_function: Sha256, mac_function: HmacSha256,
key_size_bytes: 16, tag_size_bits: 40, maclt: 34,
alpha: 117293090822129 }, gst_subframe: Gst { wn: 1350,
tow: 208770 }, _validated: Validated }: WrongOneWayFunction

Note that the GST for this key is exactly 2025-07-08 10:00:00 GST. It is the first TESLA key that is transmitted for the new chain. However, galileo-osnma has wrongly identified it as a key for the old chain (CID 3), and so is trying to validate it using the wrong floating KROOT. The problem is that, as I have mentioned above, even though the CID in the NMA header has already changed from 3 to 0, galileo-osnma doesn’t look at the CID until it has authenticated the NMA header by verifying the DSM-KROOT. This is known behaviour in galileo-osnma. It also happens when processing the test vectors.

The Galileo OSNMA Receiver Guidelines v1.3 do not mention explicitly whether a receiver may or must use the CID before the NMA header has been authenticated by using the DSM-KROOT. They mention (in Section 4.2.1) that before using the NMA status, which is another field in the NMA header, it should be authenticated either by validating the DSM-KROOT or by using tag verification (since it is used to generate the tags). I am not completely sure that using the CID without authenticating the NMA header does not introduce any security risks, so for the time being I will keep requiring the CID to be authenticated before use in my galileo-osnma implementation. Note that waiting to authenticate the new CID with the DSM-KROOT defeats all the purpose of transmitting the DSM-KROOT for the new chain during Phase 1, so this is not ideal either. I think that this situation is a good example of the need for a reference implementation that has been security audited, or for much more detailed receiver guidelines that define exactly all the procedures that a receiver may or must use.

Another aspect where I think the Galileo documentation is not great is regarding what should be done with the cryptographic material for the old chain after the chain renewal happens. The OSNMA SIS ICD v1.1 says the following in the description of Step 2: “The previous chain i is considered expired and the receiver shall discard any
parameter related to the previous chain.”. This was reminded in the live test notification: “NOTE THAT USER RECEIVERS SHALL PREVENT THE USE OF ANY CHAIN THAT HAS BEEN SUBJECT TO A RENEWAL PROCESS”.

However, galileo-osnma does something that goes against rule. For processing slow MAC, when a TESLA key is validated, galileo-osnma fetches the MACKs that have been received 330 seconds before the TESLA key. These MACKs are validated against the TESLA key of the next subframe, which can still belong to the old chain if the renewal has happened less than 300 seconds ago. Such TESLA key is computed by using the most recently received TESLA key from the appropriate chain, thus potentially using a key for an expired chain.

An alternative way to implement slow MAC processing would be to validate the MACKs as soon as possible, and keep track of the fact that they have been validated. From an implementation point of view, this is more difficult, since it requires scanning the stored MACKs any time that a new TESLA key is received to see which ones have not been validated yet and should be validated. This is because the receiver is not guaranteed to successfully get a TESLA key in the subframe after the MAC (reception might fail), so it makes more sense to wait for MACK validation until the moment in which the MACK is to be used, since the chances of having received an appropriate TESLA key by then are higher.

After the DSM-KROOT that authenticates the new NMA header is received, so that the change to CID 0 is validated, galileo-osnma is able to authenticate the TESLA key transmitted on that subframe.

new TESLA key Key { data: [130, 106, 61, 159, 154, 5, 250, 
238, 224, 155, 133, 181, 189, 204, 58, 32, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0], chain: Chain { id: 0,
hash_function: Sha256, mac_function: HmacSha256,
key_size_bytes: 16, tag_size_bits: 40, maclt: 34,
alpha: 32088435005521 }, gst_subframe: Gst { wn: 1350,
tow: 208920 }, _validated: Validated }
successfully validated by Key { data: [3, 138, 37, 100, 187,
69, 38, 125, 3, 181, 90, 38, 8, 230, 110, 129, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], chain: Chain { id: 0,
hash_function: Sha256, mac_function: HmacSha256,
key_size_bytes: 16, tag_size_bits: 40, maclt: 34,
alpha: 32088435005521 }, gst_subframe: Gst { wn: 1350,
tow: 208770 }, _validated: Validated }

Note that this TESLA key corresponds to 2025-07-08 10:02:00 GST and has been validated by a key corresponding to 2025-07-08 09:59:30 GST, which is the KROOT that we had already received.

After this DSM-KROOT with ID 8, new DSM-KROOTs with updated floating KROOTs for the new chain are transmitted. Interestingly, the next DSM-PKR that is transmitted uses ID 14. Yet again, this DSM-PKR has the same contents as before, so I don’t know why the DSM ID is incremented. In the rest of the dataset, the DSM scheduling continues as usual. The DSM-KROOT ID is incremented every time that the floating KROOT is updated, and a DSM-PRK with ID 14 is transmitted 3 times every 6 hours.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.