An XMSS signature consumes one leaf, so the key that made it must not sign at that
index
again. This returns the key with next_index set to one past the signature's
index
(refusing a signature that was not made by this key), which is the key to save and to
use
for the next signature. The signature object itself carries no key material.
Usage
xmss_advance(key, signature, message = NULL)
xmss_register_key(key_to_register)
xmss_forget_used_indices(key_to_forget = NULL)Arguments
- key
The
bricklayer_signing_keythat madesignature.- signature
The XMSS signature it made, from
capsule_sign(), or the attestation, bundle or recode manifest that carries it.- message
The message the signature was made over; when given, the signature is verified against the key first, so an edited or foreign signature cannot advance it. Without it, the signature's index is accepted only up to the highest index the record shows this key has used here, so an edited index cannot burn the key's remaining leaves; a signature made on another machine needs
message.- key_to_register
For
xmss_register_key(): a signing key generated on another machine, regenerated from given seeds, or whose record was deleted, that has never signed; it is added to the record so that it may sign fromnext_index. A key the record shows has signed is refused.- key_to_forget
For
xmss_forget_used_indices(): a signing key (the record is keyed by its secret seed) whose entry alone is reset to "nothing used" (it may sign from index 0 again);NULLremoves the whole record (every key, and the record of the releases before 0.5.12), after which only keys generated afterwards, or vouched for byxmss_register_key(), may sign.
Value
The key with next_index advanced past the signature's index and past
every index the record knows this key has used.
Details
The package records the highest index each key has signed with and refuses that index
again, within the session and across sessions on this machine. The record is the file
xmss_used_indices.txt under
tools::R_user_dir("rmoriebricklayer", "config"),
which is where CRAN policy lets a package keep its own configuration. A key is entered
under a fingerprint of its secret seed (sk_seed), not under the root it
carries, so editing a stale copy's root does not make it a new key, and a
signature
whose root does not match the seeds is withheld.
pqc_keygen()
registers a key it generates from fresh randomness. A key built from given
seeds is not registered, because it may be one that has signed elsewhere: it signs
from
index 0 only after
xmss_register_key()
vouches for it, and the same holds for any key
the record does not know (from another machine, or whose record was deleted).
Every reservation runs under an operating-system file lock (flock on Unix,
LockFileEx on Windows) on xmss_used_indices.txt.flock, which the system
releases when the process ends, so a crashed session leaves no lock for another
process to
take over. A record that cannot be read, or that cannot be written in full (a full
disk),
stops the signature. The record of the releases before 0.5.12 (xmss_index.json
in
the same directory) is read and merged, and kept as xmss_index.json.migrated
once
the new record has been written.
Another path can be set with the option rmoriebricklayer.xmss_index_file or the
environment variable RMBL_XMSS_INDEX_FILE: an absolute path to a regular file,
or
to one that does not exist yet (a device such as /dev/null, a directory or a
relative path is refused). The option FALSE (or, except on Windows, where an
empty
environment variable is an unset one, the value "") turns the file off: then
only
the session remembers, every signature warns, and a stale copy of the key reloaded
elsewhere signs a used index again. The record does not travel with the key and is
lost if
the file is deleted, so a key copied to another machine is protected only by the
next_index you saved: save the advanced key after every signature. In CI or a
container with a fresh home directory, a key that was not generated in that run is
unknown
to the record: register it once with
xmss_register_key(),
or point the record at a file that persists. Examples and vignettes point the record
at a temporary file so that checks and builds leave the configuration directory alone.
The record keeps one line per key for good (an exhausted key's line is what stops a
stale copy of it), and is read and rewritten whole at every signature: a whole
signature took 0.5 s with a hundred thousand keys in the record (measured on an x86-64
laptop; a slower machine takes longer), and longer when several processes sign at once
(each waits its turn for the lock, up to 10 s).
xmss_forget_used_indices()
resets one key's entry, or removes the whole record, and with it
the protection against re-signing: use it for test keys only.
See also
capsule_sign(),
xmss_forget_used_indices()
Examples
op <- options(rmoriebricklayer.xmss_index_file = tempfile()) # keep this example's
# one-time index record out of your configuration directory; leave the default in use
key <- pqc_keygen(height = 2)
s1 <- capsule_sign("manifest-1", key)
key <- xmss_advance(key, s1)
key$next_index
#> [1] 1
s2 <- capsule_sign("manifest-2", key)
capsule_verify("manifest-2", s2, signing_public_key(key))
#> [1] TRUE
# With the message, the signature is verified before the key advances.
key <- xmss_advance(key, s2, message = "manifest-2")
key$next_index
#> [1] 2
# A key regenerated from seeds is unknown to the record until registered.
s <- paste(rep("ab", 32), collapse = "")
k2 <- pqc_keygen(height = 1, sk_seed = s, pub_seed = s, sk_prf = s)
try(capsule_sign("m", k2))
#> Error : this XMSS key is not in the used-index record on this machine, and it claims never to have signed (next_index 0): it was generated elsewhere or from given seeds, or the record was deleted. If it has never signed, register it with xmss_register_key(key); if it has, carry it forward with xmss_advance(key, its_last_signature, message), or generate a new key with pqc_keygen()
invisible(xmss_register_key(k2))
capsule_verify("m", capsule_sign("m", k2), signing_public_key(k2))
#> [1] TRUE
# Reset a test key's entry so it may sign from index 0 again.
xmss_forget_used_indices(k2)
# A signature that is not an XMSS signature cannot advance a key.
try(xmss_advance(key, list()))
#> Error : `signature` must be an XMSS signature from capsule_sign() (or the attestation, bundle or recode manifest that carries it)
options(op)
