diff --git a/docs/shrincs_spec/archive_fors/1-motivation.tex b/docs/shrincs_spec/archive_fors/1-motivation.tex new file mode 100644 index 0000000..c4f3c8b --- /dev/null +++ b/docs/shrincs_spec/archive_fors/1-motivation.tex @@ -0,0 +1,60 @@ +\section{Motivation} +Current Bitcoin signature schemes (ECDSA\cite{ecdsa_fips186} and Schnorr\cite{bip340} over secp256k1) are vulnerable to quantum attacks via Shor's algorithm\cite{shor1994algorithms, shor1997polynomial}. Hash-based signature schemes offer post-quantum security relying only on the security of cryptographic hash functions, which are believed to resist quantum attacks apart from Grover's quadratic speedup (mitigated by doubling hash output lengths). + +The key challenges for deploying hash-based signatures in Bitcoin are: +\begin{enumerate} + \item \textbf{Signature size}: NIST-standardized SLH-DSA signatures \cite{slhdsa_fips205} range from 7KB to 50KB, significantly larger than current 64-byte Schnorr signatures. This increases transaction fees and reduces the network's throughput. + \item \textbf{Stateful vs. Stateless tradeoff}: Stateful schemes (e.g., XMSS \cite{nist_sp800_208}) offer compact signatures but require careful state management to prevent key reuse. Additionally, Bitcoin users expect to restore wallets from static seed phrases (BIP-39\cite{bip39}). Pure stateful schemes break this model since signing state cannot be recovered from a seed alone, the signer must know which one-time keys have already been used. Stateless schemes (e.g., SLH-DSA) are robust but produce large signatures. +\end{enumerate} + +\subsection{SHRINCS} +SHRINCS (Shrunken SHPINCS) is a hybrid post-quantum signature scheme that combines the efficiency of stateful signatures with the robustness of stateless ones. It provides with two parameters set depending on what matters for verifier: + \begin{itemize} + \item SHRINCS-B (Bitcoin-optimized): Minimizes signature size at the cost of more verification hashes. + \item SHRINCS-L (Liquid-optimized): Minimizes verification cost at the cost of larger signatures. + \end{itemize} +The advantages of SHRINCS are following: +\begin{itemize} + \item \textbf{Efficient stateful path.} During normal operation with intact state, SHRINCS uses an Unbalanced XMSS (UXMSS) tree with WOTS+C one-time signatures. Signature sizes and verification costs are: + \begin{itemize} + \item SHRINCS-B: \textbf{only} \texttt{308 + 16q} bytes and \texttt{2042 + q} hashes, which makes this setting practical from the \textit{witness size} perspective + \item SHRINCS-L: \texttt{1076 + 16q} bytes and \textbf{only} \texttt{54 + q} hashes, which makes this setting practical from the \textit{verification complexity} perspective + \end{itemize} + bytes (where \texttt{q >= 1} is the signature counter). + \item \textbf{Robust stateless fallback.} When the state is lost, corrupted, or exhausted, SHRINCS falls back to a SPHINCS+-based stateless signature (2856 or 4392 bytes). This is still smaller than standard SLH-DSA. + \item \textbf{Static seed backup.} Users can back up their wallet using a standard seed phrase. Upon recovery, the wallet operates in stateless-only mode, preserving funds while accepting larger signatures. + \item \textbf{The number of supported signatures}. The construction supports up to 159 (SHRINCS-B) and 207 (SHRINCS-L) stateful signatures and up to $2^{20}$ stateless signatures, which should be enough for wallets. +\end{itemize} + +\subsection{Limitations and Practical Considerations} +Users and developers should be aware of the following limitations inherent to stateful signature schemes. + +\paragraph{State Management Across Devices.} The signing state (specifically, the counter \texttt{q} indicating which one-time keys have been already used) cannot be safely shared across multiple devices without coordination. Two devices signing with the same \texttt{q} value would reuse a one-time signature, which leads to key compromise. Some mitigation strategies can be: +\begin{itemize} + \item Dedicate disjoint ranges of leaf indices to different devices. + \item Use a single signing device with secure state storage (other devices are stateless backups). + \item Accept stateless-only operation for multi-device wallets. +\end{itemize} + +\paragraph{State Corruption and Power Failure.} +If a signing operation is interrupted (e.g., due to power failure) after producing a signature but before persisting the updated state, the signer may not know whether the one-time key was used. Subsequent signing attempts risk catastrophic key reuse. Approaches to mitigate: +\begin{itemize} + \item Incrementing the state counter before the signature. If signing fails, a leaf is wasted but security is preserved (we utilize this approach in the specification). + \item State corruption detection, to maintain checksums or redundant state copies to detect corruption. If corruption is detected, fall back to stateless mode. + \item Other state and backup risks and management techniques are described in \cite{draft-ietf-pquip-hbs-state-03}. +\end{itemize} + +\paragraph{State Growth with Multiple Addresses.} +Each SHRINCS-based address requires its own signing state. As users generate new addresses (as recommended for privacy), they must maintain state for each address that may be used for future spending. This state must be preserved for as long as the address holds funds. Mitigation: +\begin{itemize} + \item Wallet backups must include current state for all active addresses, not just the seed. + \item Addresses can be marked as "stateless-only" after backup, accepting larger signatures for those addresses. +\end{itemize} + +\paragraph{Variable Stateful Signature Sizes.} +SHRINCS signatures presume variable signature size and verification complexity depending on: (1) the number of already produced stateful signatures; (2) the signing mode. In decentralized system we need to consider how the cost of the signature verification is calculated to prevent spam attacks. +\begin{itemize} + \item That's basically the reason this specification introduced two modes for SHRINCS. SHRINCS-B is designed for accounting systems, where the user pays for each transaction byte, so the signature size matters. SHRINCS-L is better suited to systems that calculate transaction cost based on the number of computation units consumed. + \item If such or a similar post-quantum signature construction is accepted in Bitcoin, it's likely to be done with one (e.g. \texttt{OP\_SHRINCS}) or the set (e.g. \texttt{OP\_WOTS, OP\_FORS, OP\_XMSS}) of opcodes, 1 byte per each. If the signature presumes a dynamic number of hashes to be verified (and the cost of verifying "short" and "long" chains is the same), it can lead to spam with longer verification paths. + \item In the case of systems where the fee additionally is calculated based on the computation complexity of the transaction, we could implement a kind of efficient pre-compiles, but in the case of dynamic signatures, we need to introduce additional parameters. +\end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/10-shrincs.tex b/docs/shrincs_spec/archive_fors/10-shrincs.tex new file mode 100644 index 0000000..1ca222b --- /dev/null +++ b/docs/shrincs_spec/archive_fors/10-shrincs.tex @@ -0,0 +1,255 @@ +\section{SHRINCS} +This section specifies the complete SHRINCS signature scheme by composing the building blocks defined in previous sections. + +\subsection{Key Generation} +The \texttt{SHRINCS.KeyGen} function generates a fresh key pair and initializes the signing state. It samples a 3n-byte random seed, partitions it into \texttt{SK.seed} (for key derivation), \texttt{SK.prf} (for message randomization), and \texttt{PK.seed} (for domain separation). It then computes \texttt{PK.sf} (the stateful UXMSS root) and \texttt{PK.sl} (the stateless hypertree root), combining them into \texttt{PK.root} via a tweakable hash. The initial state sets \texttt{q=0} with \texttt{valid=true}, indicating the stateful path is available starting from signature number 1. + +\begin{verbatim} + Algorithm: SHRINCS.KeyGen() + Output: + SK: secret key + PK: public key + state: initial signing state + + 1. seed ←$ {0,1}^(3n) + 2. SK.seed ← seed[0:n] + 3. SK.prf ← seed[n:2n] + 4. PK.seed ← seed[2n:3n] + 5. ADRS ← new_ADRS() + 6. PK.sf ← uxmss_root(SK.seed, PK.seed, ADRS) + 7. setLayerAddress(ADRS, d - 1) + 8. setTreeAddress(ADRS, 0) + 9. PK.sl ← xmss_root(SK.seed, PK.seed, ADRS, h_prime) + 10. setLayerAddress(ADRS, 0) + 11. setTreeAddress(ADRS, 0) + 12. setTypeAndClear(ADRS, ROOT) + 13. PK.root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) + 14. SK ← (SK.seed, SK.prf, PK.seed, PK.sf, PK.sl, PK.root) + 15. PK ← (PK.seed, PK.root) + 16. state ← { q: 0, valid: true } + 17. return (SK, PK, state) +\end{verbatim} + +\subsection{Recovery from Seed} +The \texttt{SHRINCS.Restore} function recovers a key pair from a backed-up seed. This enables the standard Bitcoin workflow of restoring a wallet from a seed phrase. However, since the signing state (which \texttt{q} values have been used) cannot be recovered from the seed alone, the stateful path is disabled by setting \texttt{valid=false}. The recovered wallet can only use stateless signatures, which are larger but do not require state tracking. This is the key tradeoff that enables static backups while maintaining security. +\begin{verbatim} + Algorithm: SHRINCS.Restore(seed) + Input: + seed: master seed + Output: + SK: secret key + PK: public key + state: invalid state (stateless only) + + 1. SK.seed ← seed[0:n] + 2. SK.prf ← seed[n:2n] + 3. PK.seed ← seed[2n:3n] + 4. ADRS ← new_ADRS() + 5. PK.sf ← uxmss_root(SK.seed, PK.seed, ADRS) + 6. setLayerAddress(ADRS, d - 1) + 7. setTreeAddress(ADRS, 0) + 8. PK.sl ← xmss_root(SK.seed, PK.seed, ADRS, h_prime) + 9. setLayerAddress(ADRS, 0) + 10. setTreeAddress(ADRS, 0) + 11. setTypeAndClear(ADRS, ROOT) + 12. PK.root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) + 13. SK ← (SK.seed, SK.prf, PK.seed, PK.sf, PK.sl, PK.root) + 14. PK ← (PK.seed, PK.root) + 15. state ← { q: false, valid: false } + 16. return (SK, PK, state) +\end{verbatim} + +\subsection{Stateful signing} +The \texttt{SHRINCS.SignStateful} function signs a message using the efficient stateful (UXMSS) path. It first checks that state is valid and that signature slots remain (\texttt{q <= hsf + 1}). It then increments \texttt{q} and updates the state before generating the signature. This ordering is critical: if the process is interrupted after signing but before state persistence, the one-time key would be consumed without the state reflecting it, risking catastrophic key reuse on retry (see Section~1.2). + +The signature includes \texttt{PK.sl} prepended to enable unified verification. This produces minimal signatures (324 bytes for q = 1 in SHRINCS-B) that grow linearly with \texttt{q}. If state is invalid or exhausted, the function returns "false" and the caller should use stateless signing instead. +\begin{verbatim} + Algorithm: SHRINCS.SignStateful(m, SK, state) + Input: + m: message to sign + SK: secret key (SK.seed, SK.prf, PK.seed, PK.sf, PK.sl, PK.root) + state: current signing state { q, valid } + Output: + sig: stateful signature, or $\bot$ if state is invalid/exhausted + state_prime: updated state + + 1. if not state.valid: + return (false, state) + 2. q ← state.q + 1 + 3. if q > hsf + 1: + return (false, state) + 4. state_prime ← { q: q, valid: true } + 5. ADRS ← new_ADRS() + 6. uxmss_sig ← uxmss_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, q) + 7. sig ← PK.sl || uxmss_sig + 8. return (sig, state_prime) +\end{verbatim} + +\subsection{Stateless signing} +The \texttt{SHRINCS.SignStateless} function signs a message using the stateless (SPHINCS+C) path. This is used when state is lost, corrupted, or exhausted. + +SHRINCS uses a unified digest for the stateless path: a single call to \texttt{H\_msg\_fors} produces a digest that encodes both the FORS leaf indices (determining which leaves to reveal in the few-time signature) and the hypertree tree/leaf indices (determining which XMSS trees and WOTS+C key pairs to use at each layer). The grinding counter \texttt{ctr} is iterated until the grinding condition is satisfied on this unified digest. + +The digest output length is \texttt{(roundup(k*a + hsl) / 8)} bytes. The first \texttt{k*a} bits encode the \texttt{k} FORS leaf indices (with the last a bits required to be zero by grinding). The remaining \texttt{hsl} bits encode the hypertree path indices. + +The signature is constructed bottom-up: first a FORS+C signature on the message, then \texttt{d} XMSS signatures on successive layer roots ascending through the hypertree. The signature includes \texttt{PK.sf} prepended to enable unified verification. + +\begin{verbatim} + Algorithm: SHRINCS.SignStateless(m, SK) + Input: + m: message to sign + SK: secret key + Output: + sig: stateless signature + + 1. ADRS ← new_ADRS() + 2. setLayerAddress(ADRS, 0) + 3. setTreeAddress(ADRS, 0) + 4. (fors_sig, digest) ← fors_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS) + 5. (tree_idx, leaf_idx) ← parse_idx(digest) + 6. indices ← fors_msg_to_indices(digest) + 7. setLayerAddress(ADRS, 0) + 8. setTreeAddress(ADRS, tree_idx[0] * 2^h_prime + leaf_idx[0]) + 9. fors_pk ← fors_pkFromSig(fors_sig, indices, PK.seed, ADRS) + 10. ht_sig ← [] + 11. msg ← fors_pk + 12. for layer from 0 to d - 1: + a. setLayerAddress(ADRS, layer) + b. setTreeAddress(ADRS, tree_idx[layer]) + c. xmss_sig ← xmss_sign(msg, SK.seed, SK.prf, PK.seed, PK.root, ADRS, h', leaf_idx[layer]) + d. ht_sig ← ht_sig || xmss_sig + e. if layer < d - 1: + msg ← xmss_root(SK.seed, PK.seed, ADRS, h') // we may cache or reuse the root computed during xmss_sign + 13. sig ← PK.sf || fors_sig || ht_sig + 14. return sig +\end{verbatim} + +\subsection{Index Parsing} +The \texttt{parse\_idx} function extracts tree and leaf indices for each hypertree layer from the stateless digest. In the unified digest scheme, these bits follow the \texttt{k*a} bits used for FORS indices. The \texttt{hsl} bits are partitioned into \texttt{d} pairs of \texttt{(leaf\_idx, tree\_idx)}, extracted from least significant (layer 0) to most significant (layer \texttt{d-1}), with \texttt{h'} bits per leaf index. +%\mknote{This is a bit confusin, that we extract the indices of the OTS/FTS schemes used, but not the indices of the revealed leaves in FORS. With the Hashing changes, it needs a double-check that everything is computed correctly and the same data is not used for two purposes. } +\begin{verbatim} + Algorithm: parse_idx(digest) + Input: + digest: message digest + Output: + tree_idx: array of d tree indices + leaf_idx: array of d leaf indices + + 1. ht_offset ← k * a + 2. ht_bits ← extract_bits(digest, ht_offset, hsl) + 3. idx ← ht_bits + 4. tree_idx ← [] + 5. leaf_idx ← [] + 6. for layer from 0 to d - 1: + a. leaf_idx[layer] ← idx AND (2^h_prime - 1) + b. idx ← idx >> h_prime + c. tree_idx[layer] ← idx + 7. return (tree_idx, leaf_idx) +\end{verbatim} +\paragraph{Note:} The total digest must encode \texttt{k*a + hsl = 6*22 + 24 = 156} bits. The \texttt{H\_msg\_fors} output length (Section~5.4.2) must be set accordingly: \texttt{roundup((k*a + hsl) / 8) = 20} bytes. This is a correction from the original \texttt{roundup((k*a) / 8) = 17} bytes, which was insufficient for the unified digest. + +\subsection{Stateful verification} +The \texttt{SHRINCS.VerifyStateful} function verifies a stateful signature. It extracts \texttt{PK.sl} from the signature, parses the UXMSS signature, and infers \texttt{q} from the authentication path length. It then uses the two-phase WOTS+C hashing to recover \texttt{PK.sf} via \texttt{uxmss\_pkFromSig} and verifies that \texttt{Tw(PK.seed, ADRS, PK.sf || PK.sl)} equals \texttt{PK.root}. +\begin{verbatim} + Algorithm: SHRINCS.VerifyStateful(m, sig, PK) + Input: + m: message + sig: stateful signature + PK: public key + Output: + valid: boolean + + 1. ADRS ← new_ADRS() + 2. PK.sl ← sig[0 : n] + 3. uxmss_sig ← sig[n : ] + 4. wots_sig_size ← R_SIZE + c + l * n + 5. auth_len ← len(uxmss_sig) - wots_sig_size + 6. if auth_len < 0 or (auth_len mod n) != 0: + return false + 7. q_raw ← auth_len / n + 8. if q_raw < 1 or q_raw > hsf: + return false + 9. wots_sig ← uxmss_sig[0 : wots_sig_size] + 10. auth ← uxmss_sig[wots_sig_size : ] + 11. if q_raw < hsf: + candidates ← {q_raw} + else: + candidates ← {hsf, hsf + 1} + 12. for each q in candidates: + a. ADRS ← new_ADRS() + b. PK.sf ← uxmss_pkFromSig(wots_sig, auth, m, PK.seed, PK.root, ADRS, q) + c. if PK.sf == false: + continue + d. setLayerAddress(ADRS, 0) + setTreeAddress(ADRS, 0) + setTypeAndClear(ADRS, ROOT) + expected_root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) + e. if expected_root == PK.root: + return true + 13. return false +\end{verbatim} + +\subsection{Stateless verification} +The \texttt{SHRINCS.VerifyStateless} function verifies a stateless signature. It extracts \texttt{PK.sf} from the signature, recomputes the unified digest from the FORS+C signature's \texttt{R} and \texttt{ctr}, extracts both FORS indices and hypertree indices from the same digest, then verifies bottom-up through the hypertree. +\begin{verbatim} + Algorithm: SHRINCS.VerifyStateless(m, sig, PK) + Input: + m: message + sig: stateless signature + PK: public key + Output: + valid: boolean + + 1. ADRS ← new_ADRS() + 2. PK.sf ← sig[0 : n] + 3. offset ← n + 4. fors_sig_size ← R_SIZE + (k - 1) * (n + a * n) + 5. fors_sig ← sig[offset : offset + fors_sig_size] + 6. offset ← offset + fors_sig_size + 7. R ← fors_sig[0 : R_SIZE] + 8. setTypeAndClear(ADRS, SL_H_MSG) + 9. digest ← H_msg_fors(ADRS, R, PK.seed, PK.root, m) + 10. indices ← fors_msg_to_indices(digest) + 11. if indices[k-1] != 0: + return false + 12. (tree_idx, leaf_idx) ← parse_idx(digest) + 13. setLayerAddress(ADRS, 0) + 14. setTreeAddress(ADRS, tree_idx[0] * 2^h_prime + leaf_idx[0]) + 15. fors_pk ← fors_pkFromSig(fors_sig, indices, PK.seed, ADRS) + 16. msg ← fors_pk + 17. xmss_sig_size ← R_SIZE + c + l * n + h_prime * n + 18. for layer from 0 to d - 1: + a. xmss_sig ← sig[offset : offset + xmss_sig_size] + b. offset ← offset + xmss_sig_size + c. wots_sig ← xmss_sig[0 : R_SIZE + c + l * n] + d. auth ← xmss_sig[R_SIZE + c + l * n : R_SIZE + c + l * n + h' * n] + e. setLayerAddress(ADRS, layer) + f. setTreeAddress(ADRS, tree_idx[layer]) + g. msg ← xmss_pkFromSig(wots_sig, auth, msg, PK.seed, PK.root, ADRS, h_prime, leaf_idx[layer]) + h. if msg == false: + return false + 19. PK.sl ← msg + 20. setLayerAddress(ADRS, 0) + 21. setTreeAddress(ADRS, 0) + 22. setTypeAndClear(ADRS, ROOT) + 23. expected_root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) + 24. return (expected_root == PK.root) +\end{verbatim} + +\subsection{Unified Verification} +The \texttt{SHRINCS.Verify} function provides a single entry point for verifying either stateful or stateless signatures. It distinguishes between the two types based on signature length: stateful signatures have a maximum size of \texttt{max\_sf\_size = n + R\_SIZE + c + l*n + hsf*n} bytes (both \texttt{q = hsf} and \texttt{q = hsf + 1} produce hsf auth-path nodes), while stateless signatures are strictly larger (see Section~8.7 for the derivation that this bound always holds). +\begin{verbatim} + Algorithm: SHRINCS.Verify(m, sig, PK) + Input: + m: message + sig: signature + PK: public key + Output: + valid: boolean + + 1. max_sf_size ← n + R_SIZE + c + l * n + hsf * n + 2. if len(sig) <= max_sf_size: + return SHRINCS.VerifyStateful(m, sig, PK) + else: + return SHRINCS.VerifyStateless(m, sig, PK) +\end{verbatim} \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/11-data-structures.tex b/docs/shrincs_spec/archive_fors/11-data-structures.tex new file mode 100644 index 0000000..95d4f83 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/11-data-structures.tex @@ -0,0 +1,102 @@ +\section{Data structures} + +This section specifies the binary encoding of SHRINCS keys and signatures. All multi-byte integers are encoded in big-endian byte order unless otherwise specified. + +\subsection{Secret Key Format} +The secret key consists of six \texttt{n}-byte values: +\begin{verbatim} +SK.seed : n bytes // Seed for key derivation +SK.prf : n bytes // Secret for PRF_msg +PK.seed : n bytes // Domain separator +PK.sf : n bytes // Stateful public key +PK.sl : n bytes // Stateless public key +PK.root : n bytes // Combined public key +\end{verbatim} +Total size: \texttt{6n = 96 bytes}. The first \texttt{3n} bytes (\texttt{SK.seed || SK.prf || PK.seed}) constitute the master seed that can be backed up using standard seed phrase mechanisms. The remaining \texttt{3n} bytes can be deterministically recomputed from the seed. + +\subsection{Public Key Format} +The public key consists of two \texttt{n}-byte values: +\begin{verbatim} +PK.seed : n bytes // Domain separator +PK.root : n bytes // Root public key +\end{verbatim} +Total size: \texttt{2n = 32 bytes}. + +\subsection{Stateful Signature Format} +A stateful signature consists of the stateless public key followed by a UXMSS signature: +\begin{verbatim} +PK.sl : n bytes // Stateless public key +R : R_SIZE bytes // Randomness for message hashing +ctr : c bytes // Grinding counter +chain_values: l * n bytes // WOTS+C signature +auth_path : min(q, hsf) * n bytes // UXMSS authentication path +\end{verbatim} +The signature size depends on the signature index \texttt{q}: +\begin{verbatim} +SHRINCS-B (n=16, R_SIZE=32, c=4, l=16): + PK.sl : 16 bytes + R : 32 bytes + ctr : 4 bytes + chain_values: 256 bytes + auth_path : min(q, hsf) * 16 bytes + + Total: 308 + 16*min(q, hsf) bytes + +SHRINCS-L (n=16, R_SIZE=32, c=4, l=64): + PK.sl : 16 bytes + R : 32 bytes + ctr : 4 bytes + chain_values: 1024 bytes + auth_path : min(q, hsf) * 16 bytes + + Total: 1076 + 16*min(q, hsf) +\end{verbatim} + +\subsection{Stateless Signature Format} +A stateless signature consists of the stateful public key, a FORS+C signature, and \texttt{d} XMSS signatures forming the hypertree: +\begin{verbatim} +PK.sf : n bytes // Stateful public key +FORS+C signature: + fors_R : R_SIZE bytes // Randomness (encodes grinding result) + fors_sigs: (k-1) * (n + a*n) bytes // k-1 trees +Hypertree: d XMSS signatures. For each layer i: + xmss_R[i] : R_SIZE bytes // Randomness for WOTS+C + xmss_ctr[i] : c bytes // Grinding counter for WOTS+C + xmss_chains[i]: l * n bytes // WOTS+C chain values + xmss_auth[i] : h_prime * n bytes // XMSS authentication path + +SHRINCS-B (n=16, R_SIZE=32, c=4, k=6, a=22, d=2, h'=12, l=16): + PK.sf : 16 bytes + FORS+C signature: + fors_R : 32 bytes + fors_sigs : 5 * (16 + 22*16) = 5 * 368 = 1840 bytes + subtotal : 1872 bytes + XMSS hypertree (2 layers): + per layer : 32 + 4 + 256 + 192 = 484 bytes + 2 layers : 968 bytes + Total: 16 + 1872 + 968 = 2856 bytes + +SHRINCS-L (n=16, R_SIZE=32, c=4, k=6, a=22, d=2, h'=12, l=64): + PK.sf : 16 bytes + FORS+C signature: + fors_R : 32 bytes + fors_sigs : 5 * (16 + 22*16) = 1840 bytes + subtotal : 1872 bytes + XMSS hypertree (2 layers): + per layer : 32 + 4 + 1024 + 192 = 1252 bytes + 2 layers : 2504 bytes + Total: 16 + 1872 + 2504 = 4392 bytes +\end{verbatim} + +\subsection{Signature Type Indication} + +Verifiers distinguish between stateful and stateless signatures by length (Algorithm 10.8): +\begin{center} + \texttt{max\_stateful\_size = n + R\_SIZE + c + l*n + hsf*n} +\end{center} +\begin{itemize} + \item For SHRINCS-B: \texttt{max\_stateful\_size = 16 + 32 + 4 + 256 + 158*16 = 2836} bytes + \item For SHRINCS-L: \texttt{max\_stateful\_size = 16 + 32 + 4 + 1024 + 206*16 = 4372} bytes +\end{itemize} + +Signatures with length \texttt{<= max\_stateful\_size} are processed as stateful; larger signatures as stateless. \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/2-notation.tex b/docs/shrincs_spec/archive_fors/2-notation.tex new file mode 100644 index 0000000..f84e4f0 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/2-notation.tex @@ -0,0 +1,16 @@ +\section{Notation} + +Throughout this specification: + +\begin{itemize} + \item[] \texttt{||} \quad denotes byte string concatenation + \item[] \texttt{[a:b]} \quad denotes a substring from byte index \texttt{a} to \texttt{(b-1)} + \item[] \texttt{[0:a]} \quad denotes first \texttt{a} bytes (truncation) + \item[] \texttt{←\$} \quad uniform random sampling + \item[] \texttt{n} \quad is the security parameter in bytes + \item[] \texttt{q} \quad is the stateful signature counter + \item[] \texttt{H} \quad denotes a cryptographic hash function + \item[] \texttt{Tw} \quad denotes a tweakable hash function + \item[] \texttt{PRF} \quad denotes a pseudorandom function + \item[] \texttt{$\bot$} \quad (or \texttt{false}) used for failure returns +\end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/3-overview.tex b/docs/shrincs_spec/archive_fors/3-overview.tex new file mode 100644 index 0000000..2fb6d89 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/3-overview.tex @@ -0,0 +1,106 @@ +\section{Overview} +SHRINCS is designed in the way it can operate in two modes: \textbf{stateful} and \textbf{stateless}. Stateful mode is represented by the instance of Unbalanced XMSS (UXMSS) with a root public key \texttt{PK.sf}. An optimized variant of SPHINCS+ is used to support the stateless operating mode (which is represented by the public key \texttt{PK.sl}). The root public key is a hashed concatenation of (\texttt{PK.sf}, \texttt{PK.sl}), which is being converted to the Bitcoin address. + +During normal operation with intact state, the user generates signatures according to efficient stateful path. Each leaf of the UXMSS is represented by WOTS+C one time signature instance. UXMSS is right-skewed, which makes the first stateful signature (\texttt{q = 1}) the cheapest from the verification and size perspective and adds 16 bytes and one additional hash operation for each next stateful signature, up to \texttt{q = hsf}. The final signature (\texttt{q = hsf + 1}) has the same size and verification cost as \texttt{q = hsf}. A right-skewed UXMSS is a tree construction in which every internal node has a one-time signature leaf as its left child and a UXMSS subtree as its right child, yielding a recursively right-branching authentication tree (as shown on the Figure~1). + +If the state is corrupted (or the limit for stateful operations is met), the scheme can continue operating with stateless mode. Stateless mode utilizes an optimized SPHINCS+ variant with WOTS+C signatures in regular XMSS trees on each layer in the hypertree and FORS+C for few-time signature scheme. The general construction of SHRINCS signature is presented on the Figure~1. +\begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{content/img/general.png} + \caption{SHRINCS architecture} + \label{fig:shrincs} +\end{figure} + +\subsection{Key Pair Derivation and Size} +SHRINCS keys are derived from a single random seed: +\begin{verbatim} + seed ←$ {0,1}^(3*8*n) // 3n bytes +\end{verbatim} +Seed allows to derive a private key \texttt{SK ← (SK.seed, SK.prf, PK.seed, PK.sf, PK.sl, PK.root)}: +\begin{verbatim} + SK.seed ← seed[0:n] // Seed for key derivation + SK.prf ← seed[n:2n] // Secret needed for PRF_msg + PK.seed ← seed[2n:3n] // Domain separator for different SHRINCS key pairs + PK.sf ← compute_stateful_root(PK.seed, ADRS, SK.seed) // Public stateful key + PK.sl ← compute_stateless_root(PK.seed, ADRS, SK.seed) // Public stateless key + PK.root ← H(PK.seed, ADRS, PK.sf, PK.sl) // Root public key +\end{verbatim} +where \texttt{ADRS} is a tweak also known as a context address (see Section~5.5). \\ + +The size of the secret key is \texttt{6n} bytes. A public key consists of two values \texttt{PK ← (PK.seed, PK.root)} and has a size of \texttt{2n} bytes. + +\subsection{Signature Size and Verification Cost} +We have different methods to calculate signature size and verification cost based on the used mode. +\subsubsection{Stateful} +Stateful signatures consist of: +\begin{enumerate} + \item \texttt{PK.sl}: the stateless public key (for unified verification) + \item \texttt{R}: randomness for message hashing (with the size \texttt{R\_SIZE}) + \item \texttt{ctr}: grinding counter of the size \texttt{c} + \item \texttt{chain\_values}: WOTS+C signature (\texttt{l} chains) + \item \texttt{auth\_path}: UXMSS authentication path (\texttt{min(q, hsf)} nodes) +\end{enumerate} + +Signature size calculation: +\begin{verbatim} + General formula: size = PK.sl_SIZE + R_SIZE + c + l*n + min(q, hsf)*n + SHRINCS-B.size = 16 + 32 + 4 + 16*16 + q*16 = 308 + 16q + SHRINCS-L.size = 16 + 32 + 4 + 64*16 + q*16 = 1076 + 16q +\end{verbatim} + +The signature verification cost is: +\begin{verbatim} + General formula: hash_ops = (w-1)*l - S_wn + min(q, hsf) + 2 + SHRINCS-B.hash_ops = 255*16 - 2040 + q + 2 = 2042 + q + SHRINCS-L.hash_ops = 3*64 - 140 + q + 2 = 54 + q +\end{verbatim} + +\paragraph{Note:} \texttt{w} is the Winternitz parameter and \texttt{S\_wn} is a target grinding value for WOTS+C instance (see Section~6). Two additional hashes include: (1) the hash to receive a compressed WOTS+C key; (2) the hash to obtain the root public key. + +\subsubsection{Stateless} +Stateless signatures consist of: +\begin{enumerate} + \item \texttt{PK.sf}: the stateful public key (unified verification) + \item FORS+C signature: few-time signature on the message + \item HT signature: \texttt{d} XMSS signatures forming the hypertree +\end{enumerate} + +Signature size calculation: +\begin{verbatim} + General formula: size = PK.sf_SIZE + fors_size(R_SIZE + (k-1)*(n + a*n)) + + xmss_layer_size(R_SIZE + c + l*n + h'*n) * d + + SHRINCS-B.size = 16 + fors_size(32 + 5*(16 + 22*16)) + + xmss_layer_size(32 + 4 + 16*16 + 12*16) * 2 + = 16 + (32 + 1840) + (32 + 4 + 256 + 192) * 2 + = 16 + 1872 + 968 = 2856 + + SHRINCS-L.size = 16 + fors_size(32 + 5*(16 + 22*16)) + + xmss_layer_size(32 + 4 + 64*16 + 12*16) * 2 + = 16 + (32 + 1840) + (32 + 4 + 1024 + 192) * 2 + = 16 + 1872 + 2504 = 4392 +\end{verbatim} + +The signature verification cost is: +\begin{verbatim} + General formula: + fors_hashes = (k-1)*(1 + a) + 1 + wots_per_layer = (w-1)*l - S_wn + 1 + xmss_per_layer = wots_per_layer + h' + total = fors_hashes + d*xmss_per_layer + 1 + + SHRINCS-B: + fors_hashes = 5*23 + 1 = 116 + wots_per_layer = (256-1)*16 - 2040 + 1 = 2041 + xmss_per_layer = 2041 + 12 = 2053 + total = 116 + 2*2053 + 1 = 4223 hashes + + SHRINCS-L: + fors_hashes = 116 + wots_per_layer = (w-1)*l - S_wn + 1 = 53 + xmss_per_layer = wots_per_layer + h' = 53 + 12 = 65 + total = fors_hashes + d*xmss_per_layer + 1 + = 116 + 2*65 + 1 = 247 hashes +\end{verbatim} + +\paragraph{Note:} \texttt{k} (number of trees) and \texttt{a} (tree height) are parameters of FORS+ (see Section~9). \texttt{h'} is the height of internal tree in HT. One additional hash is required to obtain the root public key after stateless public key recovery. \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/4-params.tex b/docs/shrincs_spec/archive_fors/4-params.tex new file mode 100644 index 0000000..62cba91 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/4-params.tex @@ -0,0 +1,30 @@ +\section{Parameters} +This specification provides with the list of parameters for different SHRINCS instances, depending on verifier requirements: +\begin{enumerate} + \item SHRINCS-B: size matters (instance preferable for Bitcoin). + \item SHRINCS-L: verification complexity matters (the number of hashes to verify the signature). +\end{enumerate} + +\begin{center} +\begin{tabular}{ c c c c c} + Parameter & SHRINCS-B & SHRINCS-L & Description \\ + \hline + \texttt{H} & \texttt{SHA-256} & \texttt{SHA-256} & Hash function \\ + \texttt{OTS} & \texttt{WOTS+C} & \texttt{WOTS+C} & One-time signature \\ + \texttt{FTS} & \texttt{FORS+C} & \texttt{FORS+C} & Few-time signature \\ + \texttt{n} & $16$ & $16$ & Security parameter (bytes) \\ + \texttt{w} & $256$ & $4$ & Winternitz parameter \\ + \texttt{l} & $16$ & $64$ & The number of WOTS+C chains \\ + \texttt{S\_wn} & $2040$ & $140$ & Target sum for WOTS+C \\ + \texttt{hsf} & $158$ & $206$ & Maximum stateful tree height \\ + \texttt{hsl} & $24$ & $24$ & Maximum stateless hypertree height \\ + \texttt{d} & $2$ & $2$ & Stateless hypertree layers \\ + \texttt{h'} & $12$ & $12$ & XMSS tree height per hypertree layer \texttt{hsl/d = 12} \\ + \texttt{a} & $22$ & $22$ & FORS tree height \\ + \texttt{k} & $6$ & $6$ & Number of FORS+C trees \\ + \texttt{c} & $4$ & $4$ & Counter size for WOTS+C grinding (bytes) \\ + \texttt{R\_SIZE} & $32$ & $32$ & The size (bytes) of the randomness in the signature \\ +\end{tabular} +\end{center} + +\paragraph{Note:} Parameters for stateless instance is selected in the way to support up to $2^{20}$ signatures \cite{kudinov_nick_2025_hbs_bitcoin}. \texttt{hsf} is chosen in the way that the maximum stateful signature remains smaller than a stateless signature: \texttt{n + R\_SIZE + c + l*n + hsf*n < stateless\_size}. \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/5-functions-and-addresses.tex b/docs/shrincs_spec/archive_fors/5-functions-and-addresses.tex new file mode 100644 index 0000000..7900e23 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/5-functions-and-addresses.tex @@ -0,0 +1,185 @@ +\section{Underlying Functions and Addresses} +This section defines the cryptographic primitives and addressing scheme used throughout SHRINCS. +\subsection{Hash Function} + +SHRINCS uses SHA-256 truncated to \texttt{n} bytes for all internal hash operations. +\begin{center} + \texttt{H(m) = SHA-256(m)[0:n]} +\end{center} + +\paragraph{SHA-256 Block Precomputation} +For SHA-256, the compression function \texttt{C(H,M)} operates on 64-byte blocks. Since \texttt{PK.seed} is 16 bytes, we pad it to 64 bytes to enable precomputation of the first block: + +\begin{center} + \texttt{P = C(IV, PK.seed || 0\string^48)} +\end{center} + +Then we use the following precomputation \texttt{P} in the next compression function iteration. +Every function that uses SHA-256 with it's argument involving PK.seed will use this precomputation value. This optimization allows to reduce the number of hashing operation, which leads to faster computation, especially in the grinding parts of the SHRINCS. + +Additionally, we will denote the concatenation \texttt{PK.seed || 0\string^48} as \texttt{PK.seed*}. + +\subsection{Tweakable Hash Function} +To prevent multi-target attacks and ensure domain separation between the various components, we employ the tweakable hash function construction defined in SPHINCS+ \cite{sphincsplus_paper2019}: + +\begin{center} + \texttt{Tw(PK.seed, ADRS, m) = H(PK.seed* || ADRS || m)} +\end{center} + +This usage of tweaked hashing is critical. It ensures that a hash computed at one position in the Merkle tree cannot be substituted for a hash at another position, effectively mitigating generic tree-grafting attacks. + +\subsection{Pseudorandom Functions} +SHRINCS uses two pseudorandom functions for different purposes: key derivation and getting the randomizer for the signature. + +For deterministic derivation of secret key material (WOTS+C chain starting values, FORS secret keys): + +\begin{center} + \texttt{PRF(PK.seed, ADRS, SK.seed) = Tw(PK.seed*, ADRS, SK.seed)} +\end{center} + +The address structure \texttt{ADRS} ensures that each derived value is unique and bound to its position in the scheme. + +For generating the randomizer \texttt{R} used in message hashing: + +\begin{center} + \texttt{PRF\_msg(SK.prf, PK.seed, opt\_rand, m) = HMAC-SHA-256(SK.prf, PK.seed || opt\_rand || m)[0:R\_SIZE]}, +\end{center} +where \texttt{opt\_rand} is an optional randomness (\texttt{n} bytes), can be \texttt{PK.seed} for deterministic signing. +For the stateless signing path, the FORS+C grinding counter is iterated inside \texttt{PRF\_msg} to produce a fresh \texttt{R} on each attempt. The counter-augmented variant is: + +\begin{center} + \texttt{PRF\_msg\_ctr(SK.prf, PK.seed, opt\_rand, ctr, m) = HMAC-SHA-256(SK.prf, PK.seed || opt\_rand || ctr || m)[0:R\_SIZE]} +\end{center} + +where \texttt{ctr} is a counter incremented during FORS+C grinding (see Section~9.4). The verifier never sees \texttt{ctr}, it only receives the final \texttt{R} that satisfied the grinding condition. This design keeps the grinding work inside the signer's PRF evaluation while allowing the verifier to check the result using only \texttt{R} and the message hash. + +\subsection{Message Hash Function} +SHRINCS uses different hashing strategies for WOTS+C and FORS+C, optimized for their respective use cases. + +\subsubsection{WOTS+C: Two-Phase Hashing} +WOTS+C uses a two-phase approach for message hashing and grinding. This design is necessary because: +\begin{itemize} + \item User messages need randomized hashing (phase 1 + phase 2) + \item Internal XMSS signatures sign tree roots that are already hashes (phase 2 only) +\end{itemize} + +\paragraph{Phase 1. Message Digest.} Computes a randomized hash of the user message: +\begin{center} + \texttt{H\_msg(ADRS, R, PK.seed, PK.root, m) = SHA-256(PK.seed* || ADRS || R || PK.root || m)[0:n]} +\end{center} +This is computed once per signature, producing an n-byte digest. + +\paragraph{Phase 2. Grinding Hash.} Takes the digest and a counter: +\begin{center} + \texttt{H\_grind(ADRS, PK.seed, digest, ctr) = SHA-256(PK.seed* || ADRS || digest || ctr)[0:n]} +\end{center} + +\subsubsection{FORS+C: Single-Phase Hashing} +FORS+C always signs user messages (never tree roots), so it uses a simpler single-phase approach. The grinding counter is not included in the message hash. Instead, it is iterated inside \texttt{PRF\_msg\_ctr} (Section~5.3) to produce a fresh \texttt{R} on each grinding attempt. The verifier only sees the final \texttt{R}: +\begin{center} + \texttt{H\_msg\_fors(ADRS, R, PK.seed, PK.root, m) = SHA-256(PK.seed* || ADRS || R || PK.root || m)[0:roundup((k*a + hsl) / 8)]} +\end{center} + +The output length is roundup((k*a + hsl) / 8) = 20 bytes for both parameter sets, encoding both the \texttt{k} FORS leaf indices and the \texttt{hsl} hypertree path indices in a unified digest (see Section~10.5). + +\subsection{Tweak Structure and Domain Separation} +Tweaks are structured as follows to ensure uniqueness across all scheme components: + +\begin{center} +\texttt{ADRS = layer || tree\_address || type || words} +\end{center} +where: +\begin{itemize} + \item[] \texttt{layer}: (4 bytes) layer in hypertree (\texttt{0} = bottom, \texttt{d-1} = max) + \item[] \texttt{tree\_address}: (12 bytes) tree address within layer + \item[] \texttt{type}: (4 bytes) address type + \item[] \texttt{words}: (12 bytes) type-dependent 12 bytes +\end{itemize} +A total \texttt{ADRS} size is 32 bytes. + +\subsubsection{Address Types} +\begin{verbatim} +0x00: SF_WOTS_HASH: Stateful WOTS+C chain hashing +0x01: SF_WOTS_PK: Stateful WOTS+C public key compression +0x02: SF_TREE: Stateful tree internal nodes +0x03: SF_WOTS_GRIND: Stateful WOTS+C grinding +0x04: SF_H_MSG: Stateful message hash +0x05: SF_WOTS_PRF: Stateful PRF for WOTS+C key derivation +0x06: FORS_HASH: FORS leaf hashing +0x07: FORS_TREE: FORS tree internal nodes +0x08: FORS_PK: FORS roots compression +0x09: FORS_PRF: PRF for FORS secret key derivation +0x0A: SL_WOTS_HASH: Stateless WOTS+C chain hashing +0x0B: SL_WOTS_PK: Stateless WOTS+C public key compression +0x0C: SL_TREE: Stateless tree internal nodes +0x0D: SL_WOTS_GRIND: Stateless WOTS+C grinding +0x0E: SL_H_MSG: Stateless message hash +0x0F: SL_WOTS_PRF: Stateless PRF for WOTS+C key derivation +0x10: ROOT: Root public key computation +\end{verbatim} +Summarizing, \texttt{(0x00, 0x01, 0x02, 0x03, 0x04, 0x05)} are stateful only types, \texttt{(0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)} are stateless only and \texttt{(0x10)} is shared type. + +\subsubsection{Address Manipulation Functions} +\begin{verbatim} +new_ADRS(): + return toByte(0, 32) + +setLayerAddress(ADRS, layer): + ADRS[0:4] ← toByte(layer, 4) + +setTreeAddress(ADRS, tree_addr): + ADRS[4:16] ← toByte(tree_addr, 12) + +setTypeAndClear(ADRS, type): + ADRS[16:20] ← toByte(type, 4) + ADRS[20:32] ← toByte(0, 12) + +setKeyPairAddress(ADRS, keypair): + ADRS[20:24] ← toByte(keypair, 4) + +setChainAddress(ADRS, chain): + ADRS[24:28] ← toByte(chain, 4) + +setHashAddress(ADRS, hash): + ADRS[28:32] ← toByte(hash, 4) + +setTreeHeight(ADRS, height): + ADRS[24:28] ← toByte(height, 4) + +setTreeIndex(ADRS, index): + ADRS[28:32] ← toByte(index, 4) +\end{verbatim} + +Note that \texttt{setTreeHeight} and \texttt{setChainAddress} both write to bytes \texttt{[24:28]}. These operations are context-dependent so there mustn't be a conflict. + +\subsubsection{Type-Dependent Words} +The list of type-dependent words for address tweaking is the following: +\begin{table}[h!] +\centering +\begin{tabular}{c c c c c} +\hline +\textbf{Type} & \textbf{Name} & \textbf{word1 [20:24]} & \textbf{word2 [24:28]} & \textbf{word3 [28:32]} \\ +\hline + +0x00 & \texttt{SF\_WOTS\_HASH} & \texttt{keypair} & \texttt{chain} & \texttt{hash} \\ +0x01 & \texttt{SF\_WOTS\_PK} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x02 & \texttt{SF\_TREE} & \texttt{keypair} & \texttt{height} & \texttt{index} \\ +0x03 & \texttt{SF\_WOTS\_GRIND} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x04 & \texttt{SF\_H\_MSG} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x05 & \texttt{SF\_WOTS\_PRF} & \texttt{keypair} & \texttt{chain} & \texttt{0} \\ +0x06 & \texttt{FORS\_HASH} & \texttt{tree\_idx} & \texttt{0} & \texttt{leaf\_idx} \\ +0x07 & \texttt{FORS\_TREE} & \texttt{tree\_idx} & \texttt{height} & \texttt{index} \\ +0x08 & \texttt{FORS\_PK} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x09 & \texttt{FORS\_PRF} & \texttt{tree\_idx} & \texttt{0} & \texttt{leaf\_idx} \\ +0x0A & \texttt{SL\_WOTS\_HASH} & \texttt{keypair} & \texttt{chain} & \texttt{hash} \\ +0x0B & \texttt{SL\_WOTS\_PK} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x0C & \texttt{SL\_TREE} & \texttt{keypair} & \texttt{height} & \texttt{index} \\ +0x0D & \texttt{SL\_WOTS\_GRIN} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x0E & \texttt{SL\_H\_MSG} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x0F & \texttt{SL\_WOTS\_PRF} & \texttt{keypair} & \texttt{chain} & \texttt{0} \\ +0x10 & \texttt{ROOT} & \texttt{0} & \texttt{0} & \texttt{0} \\ + +\hline +\end{tabular} +\caption{Type-dependent interpretation of ADRS words \texttt{(bytes [20:32])}} +\end{table} \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/6-wots+c.tex b/docs/shrincs_spec/archive_fors/6-wots+c.tex new file mode 100644 index 0000000..3ae6687 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/6-wots+c.tex @@ -0,0 +1,314 @@ +\section{WOTS+C} +The standard Winternitz One-Time Signature (WOTS+) is the building block of most modern hash-based schemes. It works by encoding the message into a series of small integers and using them to iterate hash chains \cite{hulsing2013wotsplus}. A significant portion of a WOTS+ signature size (approx 20\%) is dedicated to "Checksum," which is necessary to prevent a forgery attack where an adversary extends the hash chains forward. WOTS+C (compressed WOTS+) eliminates the transmission of this checksum entirely, achieving a smaller signature size. + +In WOTS+, the checksum $C$ is calculated from the message digest $D$. +\begin{align} +C = \sum_{i=1}^{l} (w - 1 - d_i) +\end{align} + +This checksum is then signed using additional chains. In WOTS+C \cite{wotsc_sphincsc2022}, instead of calculating a checksum for a fixed message digest, the signer modifies the message digest until it possesses a specific, pre-determined checksum property. This is done by hashing the message with a random salt \texttt{R} (phase 1), then iterating over a counter \texttt{ctr} applied to the resulting digest (phase 2) until the output satisfies a global target condition. + +\subsection{The Target Condition} +WOTS utilize a parameter \texttt{w} to define the number of hashchains and correspondingly their length. The message digest length is \texttt{n} bytes so \texttt{l = 8n/(log2(w))}. The values $d_{i\in[l]}$ are treated as integers in \texttt{0...(w-1)}. + +Let \texttt{S\_wn} be a protocol parameter (often near the expected value, but may be chosen larger to trade signing/grinding cost for verification cost). If the hash output is uniform, the expected value of a single digit is \texttt{d\_i = (w-1)/2}. For \texttt{l} digits, the expected \texttt{S\_wn} is: +\begin{align} +S_{wn} = \sum_{i=0}^{l-1}d_i +\end{align} + +By enforcing this condition during signing (including grinding), the verifier can simply assume the checksum is \texttt{S\_wn} without receiving it. If the reconstructed digest does not sum to \texttt{S\_wn}, the signature is invalid. + +Additionally we can manipulate a target condition to decrease the cost of signature verification. All \texttt{l}-chains consist of \texttt{St = (w-1)*l} hashing operations. Increasing \texttt{S\_wn} increases grinding cost but reduces the verification complexity to \texttt{(S\_t - S\_wn)} hashes. Concrete parameters are presented in Section~4. +% could be not so clear, check later + +\subsection{Base-w Representation} +WOTS represents a message digest in base \texttt{w} so that each digit is an integer in \texttt{[0...w-1]}. These digits directly determine the number of hashes to apply to each WOTS chain element during signing and verification. The \texttt{base\_w()} function converts a byte string into an array of \texttt{out\_len} base-w digits. In this specification we assume \texttt{w} is a power of two for efficiency, though this is not a strict requirement of the construction. + +\begin{verbatim} + Algorithm: base_w(m, w, out_len) + m: byte string + w: Winternitz parameter + out_len: output length + Output: + b_w: array of out_len integers in [0, w-1] + + 1. in_idx ← 0 + 2. bits ← 0 + 3. total ← 0 + 4. b_w ← [] + 5. for i from 0 to (out_len - 1): + a. if bits == 0: + total ← m[in_idx] + in_idx ← in_idx + 1 + bits ← 8 + b. bits ← bits - log2(w) + c. b_w[i] ← (total >> bits) AND (w - 1) + 6. return b_w +\end{verbatim} + +We provide optimized versions for \texttt{w = 256} and \texttt{w = 4} proposed by different SHRINCS instances. For \texttt{w=256}: +\begin{verbatim} + Algorithm: base_w256(m, out_len) + Input: + m: byte string + out_len: output length + Output: + b_w: array of out_len integers in [0, 255] + + 1. b_w ← [] + 2. for i from 0 to (out_len - 1): + a. b_w[i] ← m[i] + 3. return b_w +\end{verbatim} +For \texttt{w=4}: +\begin{verbatim} + Algorithm: base_w4(m, out_len) + Input: + m: byte string + out_len: output length + Output: + b_w: array of out_len integers in [0,3] + + 1. b_w ← [] + 2. for j from 0 to (out_len/4 - 1): + a. x ← m[j] + b. b_w[4*j + 0] ← (x >> 6) AND 3 + c. b_w[4*j + 1] ← (x >> 4) AND 3 + d. b_w[4*j + 2] ← (x >> 2) AND 3 + e. b_w[4*j + 3] ← x AND 3 + 3. return b_w +\end{verbatim} + +\subsection{Chain Function} +WOTS derives signature components and public key material by iteratively applying the tweakable hash function along a Winternitz chain. The function \texttt{chain()} takes an n-byte input value \texttt{m} and applies \texttt{Tw(PK.seed, ADRS, ·)} exactly \texttt{steps} times, beginning at chain position \texttt{start}. For each iteration, the address field hash inside \texttt{ADRS} is set to the current chain position \texttt{i} (from \texttt{start} to \texttt{start + steps - 1}) to ensure domain separation between different chain steps. The output is the resulting n-byte value after advancing the chain by steps, which is used both for producing signature chain values (advancing from \texttt{0} to \texttt{msg[i]}) and for reconstructing end-of-chain values during verification (advancing from \texttt{msg[i]} to \texttt{w-1}). + +The \texttt{mode} parameter determines which address types are used: \texttt{SF} for the stateful tree instance or \texttt{SL} for the stateless instance. +\begin{verbatim} + Algorithm: chain(m, start, steps, PK.seed, ADRS) + Input: + m: input + start: starting index + steps: number of iterations + PK.seed: public seed + ADRS: tweak (type already set to SF_WOTS_HASH or SL_WOTS_HASH) + Output: + n-byte chain output + + 1. tmp ← m + 2. for i from start to (start + steps - 1): + a. setHashAddress(ADRS, i) + b. tmp ← Tw(PK.seed, ADRS, tmp) + 3. return tmp +\end{verbatim} + +\subsection{Key Generation} +To create a WOTS public key for a given \texttt{keypair} index, we derive one secret starting value per chain using \texttt{PRF}, then advance each value to the end of its chain. The resulting array of end-of-chain values is compressed into a single n-byte WOTS public key via the tweakable hash. The compression uses address type \texttt{SF\_WOTS\_PK} (stateful) or \texttt{SL\_WOTS\_PK} (stateless). +\begin{verbatim} + Algorithm: wots_PKgen(SK.seed, PK.seed, ADRS, keypair, mode) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak + keypair: keypair index + mode: SF or SL + Output: + pk: compressed WOTS+C public key + + if mode == SF: + WOTS_HASH ← SF_WOTS_HASH + WOTS_PK ← SF_WOTS_PK + WOTS_PRF_TYPE ← SF_WOTS_PRF + else: + WOTS_HASH ← SL_WOTS_HASH + WOTS_PK ← SL_WOTS_PK + WOTS_PRF_TYPE ← SL_WOTS_PRF + + 1. tmp ← [] + 2. for i from 0 to l - 1: + a. setTypeAndClear(ADRS, WOTS_PRF_TYPE) + b. setKeyPairAddress(ADRS, keypair) + c. setChainAddress(ADRS, i) + d. sk_i ← PRF(PK.seed, ADRS, SK.seed) + e. setTypeAndClear(ADRS, WOTS_HASH) + f. setKeyPairAddress(ADRS, keypair) + g. setChainAddress(ADRS, i) + h. tmp[i] ← chain(sk_i, 0, w - 1, PK.seed, ADRS) + 3. setTypeAndClear(ADRS, WOTS_PK) + 4. setKeyPairAddress(ADRS, keypair) + 5. pk ← Tw(PK.seed, ADRS, tmp[0] || ... || tmp[l-1]) + 6. return pk +\end{verbatim} + +\subsection{Message Digest Grinding} +WOTS+C uses a two-phase hashing approach (see Section~5.4.1): +\begin{enumerate} + \item Phase 1 \texttt{(H\_msg)}: Computes a randomized digest of the user message. This is performed once per signature and produces an \texttt{n}-byte digest. For internal XMSS signatures (which sign tree roots that are already hashes), phase 1 is skipped and the tree root is used directly as the digest. + \item Phase 2 \texttt{(H\_grind)}: Takes the phase 1 digest (or tree root), \texttt{PK.seed}, and a counter, producing a candidate n-byte output. This is iterated during grinding until the target condition is met. \texttt{PK.seed} is included to bind the grinding output to the key pair. +\end{enumerate} + +The function \texttt{wots\_grind()} takes a pre-computed digest and increments \texttt{ctr} until the base-w digits of the Phase 2 output satisfy the target sum \texttt{S\_wn}. It returns the found \texttt{ctr} and the corresponding digit array. + +\begin{verbatim} + Algorithm: wots_grind(PK.seed, digest, ADRS, keypair, mode) + Input: + PK.seed: public seed + digest: a digest from phase 1 H_msg, or a tree root for internal signatures + ADRS: tweak + keypair: keypair + mode: SF or SL + Output: + msg: array of l integers in [0, w-1] + ctr: final counter value used + + 1. if mode == SF: + setTypeAndClear(ADRS, SF_WOTS_GRIND) + else: + setTypeAndClear(ADRS, SL_WOTS_GRIND) + 2. setKeyPairAddress(ADRS, keypair) + 3. for ctr from 0 to 2^32 - 1: // c = 4 bytes + a. grind_out ← H_grind(ADRS, PK.seed, digest, ctr) + b. msg ← base_w(grind_out, w, l) + c. sum ← 0 + d. for i from 0 to (l - 1): + sum ← sum + msg[i] + e. if sum == S_wn: + return (msg, ctr) + 4. abort with error // caller should resample R and retry +\end{verbatim} + +Additionally we need to define a func which allows to calculate the digest without grinding (by providing the \texttt{ctr} value directly). + +\begin{verbatim} + Algorithm: wots_digest(PK.seed, digest, ctr, ADRS, keypair, mode) + Input: + digest: digest from phase 1 H_msg, or a tree root + ctr: counter value + ADRS: tweak + keypair: keypair + mode: SF or SL + Output: + msg: array of l integers in [0, w-1] + valid: boolean indicating if sum == S_wn + + 1. if mode == SF: + setTypeAndClear(ADRS, SF_WOTS_GRIND) + else: + setTypeAndClear(ADRS, SL_WOTS_GRIND) + 2. setKeyPairAddress(ADRS, keypair) + 3. grind_out ← H_grind(ADRS, PK.seed, digest, ctr) + 4. msg ← base_w(grind_out, w, l) + 5. sum ← 0 + 6. for i from 0 to l - 1: + sum ← sum + msg[i] + 7. valid ← (sum == S_wn) + 8. return (msg, valid) +\end{verbatim} + +\subsection{Signature Generation} +To sign a user message, WOTS+C first computes the phase 1 digest, then grinds to find a valid counter. For each chain, it reveals the chain value after exactly \texttt{msg[i]} steps (starting from the secret-derived chain \texttt{start}). The signature carries \texttt{R}, the grinding counter \texttt{ctr}, and the \texttt{l} chain values. + +For internal XMSS signatures (signing tree roots at layers \texttt{1..d-1} of the hypertree), phase 1 is skipped: the tree root serves directly as the digest input to \texttt{wots\_grind}. In this case \texttt{R} is still generated and included in the signature to maintain a uniform format, but it does not affect the digest computation. +\begin{verbatim} + Algorithm: wots_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, keypair, mode, is_internal) + Input: + m: message + SK.seed: secret seed + SK.prf: PRF key for randomness + PK.seed: public seed + PK.root: public key root + ADRS: tweak + keypair: keypair index + mode: SF or SL + is_internal: boolean (true for internal XMSS tree-root signatures) + Output: + sig: WOTS+C signature (R || ctr || chain_values) + + if mode == SF: + WOTS_HASH ← SF_WOTS_HASH + WOTS_PRF_TYPE ← SF_WOTS_PRF + H_MSG_TYPE ← SF_H_MSG + else: + WOTS_HASH ← SL_WOTS_HASH + WOTS_PRF_TYPE ← SL_WOTS_PRF + H_MSG_TYPE ← SL_H_MSG + + 1. opt_rand ← random(n) or PK.seed // randomized or deterministic + 2. R ← PRF_msg(SK.prf, PK.seed, opt_rand, m) + 3. if is_internal: + digest ← m + else: + setTypeAndClear(ADRS, H_MSG_TYPE) + digest ← H_msg(ADRS, R, PK.seed, PK.root, m) + 4. (msg, ctr) ← wots_grind(PK.seed, digest, ADRS, keypair, mode) + 5. sig ← R || ctr + 6. for i from 0 to (l - 1): + a. setTypeAndClear(ADRS, WOTS_PRF_TYPE) + b. setKeyPairAddress(ADRS, keypair) + c. setChainAddress(ADRS, i) + d. sk_i ← PRF(PK.seed, ADRS, SK.seed) + e. setTypeAndClear(ADRS, WOTS_HASH) + f. setKeyPairAddress(ADRS, keypair) + g. setChainAddress(ADRS, i) + h. sig ← sig || chain(sk_i, 0, msg[i], PK.seed, ADRS) + 7. return sig +\end{verbatim} + +\subsection{Signature Verification (Public Key Recovery)} +Verification reconstructs the WOTS+C public key implied by the signature. It first recomputes the message digest using the two-phase approach (or directly from the tree root for internal signatures), then verifies the target sum. For each chain element, the verifier continues hashing forward from the signature's provided intermediate value for \texttt{(w - 1 - msg[i])} steps to reach the end-of-chain value. Compressing all end-of-chain values yields the recovered WOTS+C public key. +\begin{verbatim} + Algorithm: wots_pkFromSig(sig, m, PK.seed, PK.root, ADRS, keypair, mode, is_internal) + Input: + sig: WOTS+C signature (R || ctr || chain_values) + m: signed message (user message or tree root) + PK.seed: public seed + PK.root: root public key + ADRS: tweak + keypair: keypair index + mode: SF or SL + is_internal: boolean (true for internal XMSS tree-root signatures) + Output: + pk: recovered public key (n bytes), or "false" if invalid + + if mode == SF: + WOTS_HASH ← SF_WOTS_HASH + WOTS_PK ← SF_WOTS_PK + H_MSG_TYPE ← SF_H_MSG + else: + WOTS_HASH ← SL_WOTS_HASH + WOTS_PK ← SL_WOTS_PK + H_MSG_TYPE ← SL_H_MSG + + 1. R ← sig[0 : R_SIZE] + 2. ctr ← sig[R_SIZE : R_SIZE + c] + 3. chains_start ← R_SIZE + c + 4. if is_internal: + digest ← m + else: + setTypeAndClear(ADRS, H_MSG_TYPE) + digest ← H_msg(ADRS, R, PK.seed, PK.root, m) + 5. (msg, valid) ← wots_digest(PK.seed, digest, ctr, ADRS, keypair, mode) + 6. if not valid: + return false + 7. tmp ← [] + 8. for i from 0 to (l - 1): + a. setTypeAndClear(ADRS, WOTS_HASH) + b. setKeyPairAddress(ADRS, keypair) + c. setChainAddress(ADRS, i) + d. sig_i ← sig[chains_start + i * n : chains_start + (i+1) * n] + e. tmp[i] ← chain(sig_i, msg[i], w - 1 - msg[i], PK.seed, ADRS) + 9. setTypeAndClear(ADRS, WOTS_PK) + 10. setKeyPairAddress(ADRS, keypair) + 11. pk ← Tw(PK.seed, ADRS, tmp[0] || ... || tmp[l-1]) + 12. return pk +\end{verbatim} + +\subsection{Contribution to the Signature Size and Verification Complexity} +Each WOTS+C signature contributes the following to the enclosing signature: +\begin{itemize} + \item \texttt{R}: \texttt{R\_SIZE} bytes + \item \texttt{ctr}: \texttt{c} bytes + \item chain values: \texttt{l*n} bytes +\end{itemize} +Total: \texttt{R\_SIZE + c + l*n} bytes (292 bytes for SHRINCS-B and 1060 bytes for SHRINCS-L) + +The verification cost is \texttt{(w-1)*l - S\_wn + 1} hashes per WOTS+C instance (2041 hashes for SHRINCS-B and 53 hashes for SHRINCS-L): the verifier completes each chain forward by \texttt{(w - 1 - msg[i])} steps, and the sum of all forward steps is \texttt{(w-1)*l - S\_wn} (since the sum of \texttt{msg[i]} values equals \texttt{S\_wn}). One additional hash is needed for the public key compression. \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/7-xmss.tex b/docs/shrincs_spec/archive_fors/7-xmss.tex new file mode 100644 index 0000000..8fbbbdd --- /dev/null +++ b/docs/shrincs_spec/archive_fors/7-xmss.tex @@ -0,0 +1,155 @@ +\section{XMSS} +SHRINCS employs two distinct Merkle tree constructions: a standard balanced XMSS tree for the stateless hypertree, and an Unbalanced XMSS (UXMSS) tree optimized for the stateful component (specified in Section~8). + +The stateless signature mode uses a SPHINCS+-style hypertree consisting of \texttt{d} layers, where each layer contains standard balanced XMSS trees. Each XMSS tree authenticates \texttt{2\string^h'} WOTS+C key pairs, where \texttt{h' = hsl / d} is the height of trees within each layer. + +\subsection{XMSS Tree Structure} +An XMSS tree of height \texttt{h'} is a complete binary Merkle tree where: +\begin{itemize} + \item Leaves: The \texttt{2\string^h'} leaves are WOTS+C public keys, computed via \texttt{wots\_PKgen} with mode \texttt{SL}. + \item Internal nodes: Each internal node is the tweakable hash of its two children, using address type \texttt{SL\_TREE}. + \item Root: The tree root serves as either the layer's contribution to the hypertree or (for the top layer) as \texttt{PK.sl}. +\end{itemize} + +Layer indexing within the stateless hypertree is the following: +\begin{itemize} + \item Layer 0: Bottom layer (is used to sign the FORS+C public keys) + \item Layer d-1: Top layer (root is \texttt{PK.sl}) +\end{itemize} + +\subsection{XMSS TreeHash} +The \texttt{xmss\_treehash} function computes subtree roots within an XMSS tree. It takes a target height and a leftmost leaf index, then recursively builds the subtree by computing leaves (WOTS+C public keys) at height 0 and hashing pairs of children to form internal nodes. This function is the core building block used both for computing the full tree root (by calling it with \texttt{target\_height = h'} and \texttt{start\_idx = 0}) and for generating individual sibling nodes needed in authentication paths (with arbitrary \texttt{start\_idx}). + +\begin{verbatim} + Algorithm: xmss_treehash(SK.seed, PK.seed, ADRS, target_height, start_idx) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak + target_height: height of output node (0 = leaf level) + start_idx: leftmost leaf index in the subtree + Output: + node: hash value + + 1. if target_height == 0: + a. pk ← wots_PKgen(SK.seed, PK.seed, ADRS, start_idx, SL) + b. return pk + 2. left ← xmss_treehash(SK.seed, PK.seed, ADRS, target_height - 1, start_idx) + 3. right ← xmss_treehash(SK.seed, PK.seed, ADRS, target_height - 1, start_idx + + + 2^(target_height - 1)) + 4. setTypeAndClear(ADRS, SL_TREE) + 5. setTreeHeight(ADRS, target_height) + 6. setTreeIndex(ADRS, start_idx >> target_height) + 7. return Tw(PK.seed, ADRS, left || right) +\end{verbatim} + +\paragraph{Note:} \texttt{start\_idx} need not be zero. When computing a full tree root, it is called with \texttt{start\_idx = 0}. When computing a sibling node for an authentication path, \texttt{start\_idx} is the leftmost leaf index of that sibling's subtree. + +\subsection{XMSS Root Computation} +The \texttt{xmss\_root} function computes the root of an entire XMSS tree by invoking \texttt{xmss\_treehash} with the full tree height and starting from leaf index 0. The resulting root hash represents the public key for this XMSS tree instance, which either becomes \texttt{PK.sl} (for the top hypertree layer) or is signed by the layer above (for lower layers). +\begin{verbatim} + Algorithm: xmss_root(SK.seed, PK.seed, ADRS, h_prime) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak + h_prime: tree height + Output: + root: tree root (n bytes) + + 1. return xmss_treehash(SK.seed, PK.seed, ADRS, h_prime, 0) +\end{verbatim} + +\subsection{XMSS Authentication Path Generation} +For a leaf at index \texttt{idx}, the authentication path consists of the sibling nodes at each level from the leaf up to (but not including) the root. The authentication path contains \texttt{h\_prime} nodes, each of size \texttt{n} bytes, allowing a verifier to reconstruct the path from the leaf to the root. + +At height \texttt{h}, the sibling subtree's leftmost leaf index is determined by flipping the h-th bit of \texttt{idx} (using XOR) and clearing all lower bits. The sibling node is then computed by calling \texttt{xmss\_treehash} on the subtree rooted at that position. +\begin{verbatim} + Algorithm: xmss_auth_path(SK.seed, PK.seed, ADRS, h_prime, idx) + Input: + SK.seed, PK.seed: seeds + ADRS: tweak + h_prime: tree height + idx: leaf index being authenticated + Output: + auth: authentication path (h_prime * n bytes) + + 1. auth ← [] + 2. for h from 0 to h_prime - 1: + a. sibling_start ← (idx XOR (1 << h)) AND (0xFFFFFFFF << h) + b. auth ← auth || xmss_treehash(SK.seed, PK.seed, ADRS, h, sibling_start) + 3. return auth +\end{verbatim} +\paragraph{Note:} The expression \texttt{(idx XOR (1 << h)) AND (0xFFFFFFFF << h)} flips the \texttt{h}-th bit of \texttt{idx} to identify the sibling, then clears the lower \texttt{h} bits to obtain the leftmost leaf index of the sibling's subtree of height \texttt{h}. + +\subsection{XMSS Public Key Recovery from Signature} +Given a WOTS+C signature and authentication path, this algorithm recovers the XMSS tree root. First, it recovers the WOTS+C public key (the leaf value) from the WOTS+C signature. Then, starting from the leaf, it iteratively combines the current node with the corresponding authentication path node to compute the parent, continuing until reaching the root. At each height \texttt{h}, the \texttt{h}-th bit of idx determines whether the current node is the left child \texttt{(bit = 0)} or right child \texttt{(bit = 1)}, which dictates the ordering when hashing the pair. + +The message \texttt{m} being verified is either a FORS+C public key (at layer 0) or a lower-layer tree root (at layers \texttt{1...d-1}). In both cases, m is already a hash, so \texttt{wots\_pkFromSig} is called with \texttt{is\_internal = true} (phase 1 of message hashing is skipped; see Section~6.7). + +\begin{verbatim} + Algorithm: xmss_pkFromSig(wots_sig, auth, m, PK.seed, PK.root, ADRS, h_prime, idx) + Input: + wots_sig: WOTS+C signature + auth: authentication path (h_prime * n bytes) + m: signed message (FORS+C public key or lower-layer tree root) + PK.seed: public seed + PK.root: root public key + ADRS: tweak + h_prime: tree height + idx: leaf index + Output: + root: computed tree root (n bytes), or "false" if invalid + + 1. pk ← wots_pkFromSig(wots_sig, m, PK.seed, PK.root, ADRS, idx, SL, true) + 2. if pk == false: + return false + 3. node ← pk + 4. for h from 0 to h_prime - 1: + a. setTypeAndClear(ADRS, SL_TREE) + b. setTreeHeight(ADRS, h + 1) + c. setTreeIndex(ADRS, idx >> 1) + d. auth_node ← auth[h * n : (h + 1) * n] + e. if (idx AND 1) == 0: + node ← Tw(PK.seed, ADRS, node || auth_node) + else: + node ← Tw(PK.seed, ADRS, auth_node || node) + f. idx ← idx >> 1 + 5. return node +\end{verbatim} + +\subsection{XMSS Signature Generation (Stateless Layer)} +To sign a message within the stateless hypertree, the XMSS signature generation produces a WOTS+C signature on the message followed by the authentication path for the corresponding leaf. The message being signed is typically either a FORS+C public key (at layer 0) or the root of a lower-layer XMSS tree (at layers 1 through \texttt{d-1}). In both cases the message is already a hash value, so \texttt{wots\_sign} is called with \texttt{is\_internal = true}. + +The leaf index \texttt{idx} determines which WOTS+C key pair is used; this index is determined from the message digest during stateless signing (see Section~10.5). + +\begin{verbatim} + Algorithm: xmss_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, h_prime, idx) + Input: + m: message to sign (lower-layer root or FORS public key) + SK.seed: secret seed + SK.prf: PRF key + PK.seed: public seed + PK.root: root public key + ADRS: tweak + h_prime: tree height + idx: leaf index to use + + Output: + sig: XMSS signature (WOTS+C signature || authentication path) + + 1. wots_sig ← wots_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, idx, SL, true) + 2. auth ← xmss_auth_path(SK.seed, PK.seed, ADRS, h_prime, idx) + 3. return wots_sig || auth +\end{verbatim} + +\subsection{Contribution to Signature Size and Verification Complexity} +Each XMSS signature consists of a WOTS+C signature plus an authentication path of \texttt{h'} nodes: \texttt{R\_SIZE + c + (l + h') * n} bytes (484 bytes for SHRINCS-B and 1252 bytes for SHRINCS-L). + +The full stateless hypertree consists of \texttt{d = 2} layers, giving: +\begin{itemize} + \item 968 bytes and 4106 hashes for SHRINCS-B + \item 2504 bytes and 130 hashes for SHRINCS-L +\end{itemize} + +\paragraph{Note:} This excludes the FORS+C contribution (see Section~9). The total stateless signature size and verification cost are the sum of FORS+C and hypertree components, as presented in Section~3.2.2. \ No newline at end of file diff --git a/docs/shrincs_spec/archive_fors/8-uxmss.tex b/docs/shrincs_spec/archive_fors/8-uxmss.tex new file mode 100644 index 0000000..4425c27 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/8-uxmss.tex @@ -0,0 +1,188 @@ +\section{UXMSS. The Stateful Tree} +Standard XMSS uses a balanced binary tree, which minimizes the maximum path length but imposes a logarithmic cost on every signature. For \texttt{N} signatures, every signature has size proportional to \texttt{log\_2(N)}. SHRINCS utilizes an Unbalanced Merkle Tree (specifically a "Right-Skewed" tree) to optimize for the typical Bitcoin use case: a wallet that performs very few transactions in its lifecycle. + +In this topology the \texttt{q}-th signature requires a path of length \texttt{q}. This linear growth is superior to logarithmic growth for small \texttt{q}. For \texttt{q=1}, the path is just 16 bytes. Even for \texttt{q=10}, the path is $10 \times 16 = 160$ bytes, which is extremely small compared to the stateless part. +\subsection{UXMSS Tree Structure} +The UXMSS tree is constructed recursively as a right-skewed binary tree with \texttt{hsf + 1} leaves. To describe the tree structure, we use "level" to denote depth from the root, with level 0 at the root and level \texttt{hsf - 1} at the bottom internal node. Note that this UXMSS convention is the inverse of the convention used in the balanced XMSS trees of the stateless component (Section~7). However, the ADRS height field for internal nodes is set to (\texttt{hsf - level}), so both constructions use increasing height values toward the root. +The UXMSS tree is constructed as follows: +\begin{itemize} + \item Level 0 is the root public key \texttt{PK.sf}. + \item Level \texttt{i} (for \texttt{0 <= i <= hsf-2}, internal): + \begin{itemize} + \item Left child: WOTS+C public key for signature \texttt{q=i+1} + \item Right child: Root of subtree for signatures \texttt{q>i+1} + \end{itemize} + \item Level \texttt{hsf-1} (bottom) + \begin{itemize} + \item Left child: WOTS+C public key for signature \texttt{q=hsf} + \item Right child: WOTS+C public key for signature \texttt{q=hsf+1} + \end{itemize} +\end{itemize} + +This means the tree has \texttt{hsf + 1} leaves (one per valid signature index \texttt{q $\in$ \{1, ..., hsf + 1\}}), and every internal node has a WOTS+C leaf as its left child and a UXMSS subtree (or a second leaf at the bottom) as its right child. + +\subsection{UXMSS TreeHash} +% \mknote{Should we have a unified API for these functions with similar logic: xmss treehash(SK.seed, PK.seed, ADRS, target height, start idx). Is level the target hash that is output? If Lvl is 1 what do we output: WOTSpk or rightsubtree.} +The \texttt{uxmss\_treehash} function computes subtree roots within the right-skewed UXMSS tree. Unlike balanced XMSS where treehash recursively computes both subtrees symmetrically, UXMSS treehash follows the right-skewed structure: the left child is always a WOTS+C public key (a leaf), while the right child is either another UXMSS subtree (for internal levels) or a second WOTS+C public key (at the bottom level). The level parameter indicates depth from the root, with level 0 being the root and level \texttt{hsf - 1} being the bottom internal node. + +All UXMSS operations use the stateful address types (\texttt{SF\_WOTS\_HASH}, \texttt{SF\_WOTS\_PK}, \texttt{SF\_TREE}) and are fixed at \texttt{layer = 0}, \texttt{tree\_address = 0} (since there is exactly one stateful tree per SHRINCS key pair). + +\begin{verbatim} + Algorithm: uxmss_treehash(SK.seed, PK.seed, ADRS, level) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak (layer = 0, tree_address = 0) + level: depth from root (0 = root, hsf - 1 = bottom internal node) + Output: + node: hash value (n bytes) + + 1. left ← wots_PKgen(SK.seed, PK.seed, ADRS, level + 1, SF) + 2. if level == hsf - 1: + right ← wots_PKgen(SK.seed, PK.seed, ADRS, hsf + 1, SF) + else: + right ← uxmss_treehash(SK.seed, PK.seed, ADRS, level + 1) + 3. setTypeAndClear(ADRS, SF_TREE) + 4. setTreeHeight(ADRS, hsf - level) + 5. setTreeIndex(ADRS, 0) + 6. return Tw(PK.seed, ADRS, left || right) +\end{verbatim} +\paragraph{Note:} The keypair index passed to \texttt{wots\_PKgen} corresponds to the signature number \texttt{q}. At level \texttt{i}, the left child is the leaf for \texttt{q = i + 1}. At the bottom level (\texttt{hsf - 1}), the right child is the leaf for \texttt{q = hsf + 1}. + +\subsection{UXMSS Root Computation} +The \texttt{uxmss\_root} function computes \texttt{PK.sf}, the root of the stateful UXMSS tree. It initializes the address structure with layer 0 and tree address 0 (since there is only one stateful tree), then invokes \texttt{uxmss\_treehash} starting from level 0. The resulting root, combined with \texttt{PK.sl} from the stateless tree, forms the basis for \texttt{PK.root}. +\begin{verbatim} + Algorithm: uxmss_root(SK.seed, PK.seed, ADRS) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak + Output: + PK.sf: stateful public key + + 1. setLayerAddress(ADRS, 0) + 2. setTreeAddress(ADRS, 0) + 3. return uxmss_treehash(SK.seed, PK.seed, ADRS, 0) +\end{verbatim} + +\subsection{UXMSS Authentication Path Generation} +For signature number \texttt{1 <= q <= hsf + 1}, the authentication path contains \texttt{min(q, hsf)} sibling nodes needed to reconstruct \texttt{PK.sf} from the WOTS+C public key at position \texttt{q}. The structure of the path differs based on whether \texttt{q <= hsf} or \texttt{q = hsf + 1}. For \texttt{q <= hsf}, the first sibling is either a subtree root (if \texttt{q < hsf}) or the rightmost WOTS+C key (if \texttt{q = hsf}), followed by q-1 WOTS+C public keys ascending toward the root. For \texttt{q = hsf + 1} (the rightmost leaf), all hsf siblings are WOTS+C public keys. This asymmetric structure reflects the right-skewed nature of UXMSS. + +\begin{verbatim} + Algorithm: uxmss_auth_path(SK.seed, PK.seed, ADRS, q) + Input: + SK.seed, PK.seed: seeds + ADRS: address + q: signature number (1 to hsf + 1) + Output: + auth: authentication path (min(q, hsf)*n bytes) + + 1. auth ← [] + 2. setLayerAddress(ADRS, 0) + 3. setTreeAddress(ADRS, 0) + 4. if q <= hsf: + if q == hsf: + auth ← auth || wots_PKgen(SK.seed, PK.seed, ADRS, hsf + 1, SF) + else: + auth ← auth || uxmss_treehash(SK.seed, PK.seed, ADRS, q) + for i from 1 to q - 1: + auth ← auth || wots_PKgen(SK.seed, PK.seed, ADRS, q - i, SF) + 5. else: + for i from 0 to hsf - 1: + auth ← auth || wots_PKgen(SK.seed, PK.seed, ADRS, hsf - i, SF) + 6. return auth +\end{verbatim} +\paragraph{Note:} For \texttt{q <= hsf}, the authentication path has exactly \texttt{q} nodes. For \texttt{q = hsf + 1}, it has \texttt{hsf} nodes, since the rightmost leaf shares its parent's other child (a WOTS+C key) rather than a subtree root. Consequently, both \texttt{q = hsf} and \texttt{q = hsf + 1} produce authentication paths of identical byte length (\texttt{hsf * n} bytes). A verifier receiving only the signature cannot determine \texttt{q} from the authentication path length alone when the path has \texttt{hsf} nodes. This ambiguity is resolved during verification by trying both candidate values and accepting whichever reconstructs a valid \texttt{PK.sf} root. + +\subsection{UXMSS Public Key Recovery from Signature} +Given a WOTS+C signature and authentication path, this algorithm reconstructs \texttt{PK.sf}. First, it recovers the WOTS+C public key from the signature. Then, starting from this leaf, it iteratively combines the current node with authentication path nodes to compute parent nodes until reaching the root. + +The combination order follows from the tree structure: +\begin{itemize} + \item Case \texttt{q <= hsf}: The recovered key is a left child. The first combination pairs the recovered key (left) with the first auth node (right). All subsequent combinations have the auth node (a WOTS+C key from a lower \texttt{q} value) on the left and the current node on the right. + \item Case \texttt{q = hsf + 1}: The recovered key is the rightmost leaf (a right child). All auth nodes are WOTS+C keys on the left. +\end{itemize} + +\begin{verbatim} + Algorithm: uxmss_pkFromSig(wots_sig, auth, m, PK.seed, PK.root, ADRS, q) + Input: + wots_sig: WOTS+C signature + auth: authentication path + m: signed message + PK.seed: public seed + PK.root: root public key + ADRS: address + q: signature number + Output: + root: computed PK.sf, or "false" if invalid + + 1. setLayerAddress(ADRS, 0) + 2. setTreeAddress(ADRS, 0) + 3. pk ← wots_pkFromSig(wots_sig, m, PK.seed, PK.root, ADRS, q, SF, false) + 4. if pk == false: + return false + 5. node ← pk + 6. setTypeAndClear(ADRS, SF_TREE) + 7. if q <= hsf: + a. setTreeHeight(ADRS, hsf - (q - 1)) + b. setTreeIndex(ADRS, 0) + c. node ← Tw(PK.seed, ADRS, node || auth[0 : n]) + for i from 1 to q - 1: + a. setTreeHeight(ADRS, hsf - (q - 1 - i)) + b. setTreeIndex(ADRS, 0) + c. node ← Tw(PK.seed, ADRS, auth[i * n : (i + 1) * n] || node) + 8. else: + for i from 0 to hsf - 1: + a. setTreeHeight(ADRS, i + 1) + b. setTreeIndex(ADRS, 0) + c. node ← Tw(PK.seed, ADRS, auth[i * n : (i + 1) * n] || node) + 9. return node +\end{verbatim} + +\paragraph{Note:} In step 3, \texttt{is\_internal = false} because the stateful UXMSS signs user messages directly (requiring phase 1 + phase 2 hashing). This differs from the stateless XMSS layers which always sign tree roots or FORS public keys (\texttt{is\_internal = true}). + +\subsection{UXMSS Signature Generation} +To sign a message using the stateful path, UXMSS signature generation produces a WOTS+C signature followed by the authentication path for position \texttt{q}. The signature number \texttt{q} must be tracked as state and incremented after each use to ensure one-time signature security. As noted in Section~1.2, the state counter should be persisted before generating the signature to prevent key reuse in case of interruption. + +The resulting signature size is \texttt{|wots\_sig| + min(q, hsf)*n} bytes, which grows linearly with \texttt{q} up to \texttt{q = hsf}. The last two signatures (\texttt{q = hsf} and \texttt{q = hsf + 1}) have identical size. For the first signature (\texttt{q = 1}), this yields the minimal signature that makes SHRINCS attractive for Bitcoin applications. +\begin{verbatim} + Algorithm: uxmss_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, q) + Input: + m: message + SK.seed: secret seed + SK.prf: PRF key + PK.seed: public seed + PK.root: root public key + ADRS: address + q: signature number + Output: + sig: UXMSS signature (WOTS+C signature || authentication path) + + 1. setLayerAddress(ADRS, 0) + 2. setTreeAddress(ADRS, 0) + 3. wots_sig ← wots_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, q, SF, false) + 4. auth ← uxmss_auth_path(SK.seed, PK.seed, ADRS, q) + 5. return wots_sig || auth +\end{verbatim} + +\subsection{Contribution to Signature Size and Verification Complexity} +Each UXMSS signature consists of a WOTS+C signature plus an authentication path whose length depends on the signature index \texttt{q}: +\begin{center} + \texttt{total = R\_SIZE + c + l*n + min(q, hsf)*n} +\end{center} + +For concrete instances and signatures' number: +\begin{verbatim} + SHRINCS-B: + q=1: 32 + 4 + 16*16 + 16 = 308 bytes + q=10: 32 + 4 + 16*16 + 16*10 = 452 bytes + q=158: 32 + 4 + 16*16 + 16*158 = 2820 bytes + q=159(max): 32 + 4 + 16*16 + 16*158 = 2820 bytes // same as q=158; < 2856 (stateless) + SHRINCS-L: + q=1: 32 + 4 + 64*16 + 16 = 1076 bytes + q=10: 32 + 4 + 64*16 + 16*10 = 1220 bytes + q=206: 32 + 4 + 64*16 + 16*206 = 4356 bytes + q=207(max): 32 + 4 + 64*16 + 16*206 = 4356 bytes // same as q=206; < 4392 (stateless) +\end{verbatim} + +Verification cost per UXMSS signature is calculated as WOTS+C verification cost + \texttt{min(q, hsf)} hashes. \ No newline at end of file diff --git a/docs/shrincs_spec/content/9-fors.tex b/docs/shrincs_spec/archive_fors/9-fors.tex similarity index 100% rename from docs/shrincs_spec/content/9-fors.tex rename to docs/shrincs_spec/archive_fors/9-fors.tex diff --git a/docs/shrincs_spec/archive_fors/a-grinding-complexity.tex b/docs/shrincs_spec/archive_fors/a-grinding-complexity.tex new file mode 100644 index 0000000..f9beb07 --- /dev/null +++ b/docs/shrincs_spec/archive_fors/a-grinding-complexity.tex @@ -0,0 +1,133 @@ +\section{Expected Grinding Complexity} +This section analyzes the computational cost of the grinding operations in WOTS+C and FORS+C for both SHRINCS parameter sets. + +\subsection{Probabilistic Analysis} + +\subsubsection{WOTS+C Grinding} +The grinding operation searches for a counter $\texttt{ctr}$ such that the sum of base-$w$ digits equals the target $S_{wn}$. Each digest attempt produces $l$ independent random digits, each uniformly distributed in $[0, w-1]$. + +For a single digit: +\begin{align} + \mathbb{E}[d_i] &= \frac{w-1}{2} \\ + \text{Var}[d_i] &= \frac{w^2-1}{12} +\end{align} + +For the sum of $l$ digits: +\begin{align} + \mathbb{E}\left[\sum d_i\right] &= l \cdot \frac{w-1}{2} \\ + \text{Var}\left[\sum d_i\right] &= l \cdot \frac{w^2-1}{12} \\ + \sigma &= \sqrt{l \cdot \frac{w^2-1}{12}} +\end{align} + +By the Central Limit Theorem, for moderate $l$, the sum distribution approximates: +\begin{align} +\sum d_i \sim \mathcal{N}\left(\mu = l \cdot \frac{w-1}{2}, \sigma^2\right) +\end{align} + +The probability of hitting exactly $S_{wn}$ can be approximated as: +\begin{align} + P\left(\sum d_i = S_{wn}\right) \approx \frac{1}{\sigma\sqrt{2\pi}} \cdot \exp\left(-\frac{(S_{wn} - \mu)^2}{2\sigma^2}\right) +\end{align} + +Expected grinding iterations: +\begin{align} + \mathbb{E}[\text{iterations}] = \frac{1}{P(\sum d_i = S_{wn})} +\end{align} + +\subsubsection{FORS+C Grinding} +The grinding operation searches for $\text{indices}[k-1] = 0$, meaning the last $a$ bits of the digest must all be zero. This is a deterministic bit condition with: +\begin{align} + P(\text{indices}[k-1] = 0) &= \frac{1}{2^a} \\ + \mathbb{E}[\text{iterations}] &= 2^a +\end{align} + +\subsection{Concrete Parameters} + +\subsubsection{SHRINCS-B ($w=256$, $l=16$, $S_{wn}=2040$)} +\paragraph{Statistic calculation:} +\begin{align} + \mu &= 16 \cdot \frac{255}{2} = 2040 \\ + \sigma^2 &= 16 \cdot \frac{256^2-1}{12} \approx 87381 \\ + \sigma &\approx 295.6 +\end{align} + +\paragraph{Analysis:} +\begin{itemize} + \item $S_{wn} = 2040$ is exactly at the expected value ($\mu = 2040$) + \item Distance from mean: $(2040 - 2040)/295.6 = 0$ standard deviations +\end{itemize} + +\paragraph{Approximate probability:} +\begin{align} + P(\sum d_i = 2040) \approx \frac{1}{295.6 \cdot \sqrt{2\pi}} \approx \frac{1}{741} +\end{align} + +\paragraph{Expected grinding cost:} +\begin{itemize} + \item $\mathbb{E}[\text{iterations}] \approx 741$ iterations + \item Per iteration: 2 SHA-256 compression (Phase 2 operates on 68-byte input + \item Total: $\approx 741$ SHA-256 compressions +\end{itemize} + +\paragraph{Practical performance:} +\begin{itemize} + \item $\approx$1-2 million SHA-256/sec + \item Expected grinding time: $<$1 millisecond +\end{itemize} + +\subsubsection{SHRINCS-L ($w=4$, $l=64$, $S_{wn}=140$)} + +\paragraph{Statistic calculation:} +\begin{align} + \mu &= 64 \cdot \frac{3}{2} = 96 \\ + \sigma^2 &= 64 \cdot \frac{4^2-1}{12} = 80 \\ + \sigma &\approx 8.94 +\end{align} + +\paragraph{Analysis:} +\begin{itemize} + \item $S_{wn} = 140$ is significantly above the expected value + \item Distance from mean: $(140 - 96)/8.94 \approx 4.92$ standard deviations +\end{itemize} + +\paragraph{Approximate probability:} +\begin{align} + P(\sum d_i = 140) &\approx \frac{1}{8.94\sqrt{2\pi}} \cdot \exp\left(-\frac{4.92^2}{2}\right) \nonumber \\ + &\approx \frac{1}{22.4} \cdot \exp(-12.1) \nonumber \\ + &\approx \frac{1}{22.4} \cdot 5.5 \times 10^{-6} \nonumber \\ + &\approx 2.45 \times 10^{-7} +\end{align} + +\paragraph{Expected grinding cost:} +\begin{itemize} + \item $\mathbb{E}[\text{iterations}] \approx 4.1$m iterations + \item Per iteration: 1 SHA-256 compression + \item Total: $\approx 4.1$ million SHA-256 compressions +\end{itemize} + +\paragraph{Practical performance:} +\begin{itemize} + \item $\approx$1-2 million SHA-256/sec + \item Expected grinding time: 2-4 seconds per signature +\end{itemize} + +\subsection{FORS+C Grinding Complexity} +Both SHRINCS-B and SHRINCS-L use identical FORS+C parameters: +\begin{itemize} + \item $k = 6$ trees + \item $a = 22$ (tree height) +\end{itemize} + +Grinding condition: $\text{indices}[k-1] = 0$ (last tree index must be zero) + +\paragraph{Expected grinding cost:} +\begin{itemize} + \item $\mathbb{E}[\text{iterations}] = 2^a = 2^{22} = 4.1$m iterations + \item Per iteration: 1 HMAC-SHA-256 + 1 SHA-256 +\end{itemize} + +\paragraph{Practical performance:} +\begin{itemize} + \item $\sim$1-2 million SHA-256/sec + \item Expected grinding time: 4-8 seconds per stateless signature +\end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/content/1-motivation.tex b/docs/shrincs_spec/content/1-motivation.tex index c4f3c8b..19dc754 100644 --- a/docs/shrincs_spec/content/1-motivation.tex +++ b/docs/shrincs_spec/content/1-motivation.tex @@ -12,18 +12,19 @@ \subsection{SHRINCS} \begin{itemize} \item SHRINCS-B (Bitcoin-optimized): Minimizes signature size at the cost of more verification hashes. \item SHRINCS-L (Liquid-optimized): Minimizes verification cost at the cost of larger signatures. + \item SHRINCS-B32 (Ligthing-optimized): Minimizes signature size and increses signature usage at the cost of more verification hashes. \end{itemize} The advantages of SHRINCS are following: \begin{itemize} \item \textbf{Efficient stateful path.} During normal operation with intact state, SHRINCS uses an Unbalanced XMSS (UXMSS) tree with WOTS+C one-time signatures. Signature sizes and verification costs are: \begin{itemize} - \item SHRINCS-B: \textbf{only} \texttt{308 + 16q} bytes and \texttt{2042 + q} hashes, which makes this setting practical from the \textit{witness size} perspective + \item SHRINCS-B/B32: \textbf{only} \texttt{308 + 16q} bytes and \texttt{2042 + q} hashes, which makes this setting practical from the \textit{witness size} perspective \item SHRINCS-L: \texttt{1076 + 16q} bytes and \textbf{only} \texttt{54 + q} hashes, which makes this setting practical from the \textit{verification complexity} perspective \end{itemize} bytes (where \texttt{q >= 1} is the signature counter). - \item \textbf{Robust stateless fallback.} When the state is lost, corrupted, or exhausted, SHRINCS falls back to a SPHINCS+-based stateless signature (2856 or 4392 bytes). This is still smaller than standard SLH-DSA. + \item \textbf{Robust stateless fallback.} When the state is lost, corrupted, or exhausted, SHRINCS falls back to a SPHINCS+-based stateless signature (SHRINCS-B/L: 2568 or 4104 bytes, and SHRINCS-B: 3680 bytes). This is still smaller than standard SLH-DSA. \item \textbf{Static seed backup.} Users can back up their wallet using a standard seed phrase. Upon recovery, the wallet operates in stateless-only mode, preserving funds while accepting larger signatures. - \item \textbf{The number of supported signatures}. The construction supports up to 159 (SHRINCS-B) and 207 (SHRINCS-L) stateful signatures and up to $2^{20}$ stateless signatures, which should be enough for wallets. + \item \textbf{The number of supported signatures}. The construction supports up to 141 (SHRINCS-B), 189 (SHRINCS-L), and 210 (SHRINCS-B32) stateful signatures and up to $2^{20}$ or $2^{32}$ stateless signatures, which should be enough for wallets or Ligthing network. \end{itemize} \subsection{Limitations and Practical Considerations} @@ -54,7 +55,7 @@ \subsection{Limitations and Practical Considerations} \paragraph{Variable Stateful Signature Sizes.} SHRINCS signatures presume variable signature size and verification complexity depending on: (1) the number of already produced stateful signatures; (2) the signing mode. In decentralized system we need to consider how the cost of the signature verification is calculated to prevent spam attacks. \begin{itemize} - \item That's basically the reason this specification introduced two modes for SHRINCS. SHRINCS-B is designed for accounting systems, where the user pays for each transaction byte, so the signature size matters. SHRINCS-L is better suited to systems that calculate transaction cost based on the number of computation units consumed. - \item If such or a similar post-quantum signature construction is accepted in Bitcoin, it's likely to be done with one (e.g. \texttt{OP\_SHRINCS}) or the set (e.g. \texttt{OP\_WOTS, OP\_FORS, OP\_XMSS}) of opcodes, 1 byte per each. If the signature presumes a dynamic number of hashes to be verified (and the cost of verifying "short" and "long" chains is the same), it can lead to spam with longer verification paths. + \item That's basically the reason this specification introduced two modes for SHRINCS. SHRINCS-B/B32 is designed for accounting systems, where the user pays for each transaction byte, so the signature size matters. SHRINCS-L is better suited to systems that calculate transaction cost based on the number of computation units consumed. + \item If such or a similar post-quantum signature construction is accepted in Bitcoin, it's likely to be done with one (e.g. \texttt{OP\_SHRINCS}) or the set (e.g. \texttt{OP\_WOTS, OP\_PORS, OP\_XMSS}) of opcodes, 1 byte per each. If the signature presumes a dynamic number of hashes to be verified (and the cost of verifying "short" and "long" chains is the same), it can lead to spam with longer verification paths. \item In the case of systems where the fee additionally is calculated based on the computation complexity of the transaction, we could implement a kind of efficient pre-compiles, but in the case of dynamic signatures, we need to introduce additional parameters. \end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/content/10-shrincs.tex b/docs/shrincs_spec/content/10-shrincs.tex index 1ca222b..c67dbe4 100644 --- a/docs/shrincs_spec/content/10-shrincs.tex +++ b/docs/shrincs_spec/content/10-shrincs.tex @@ -88,11 +88,11 @@ \subsection{Stateful signing} \subsection{Stateless signing} The \texttt{SHRINCS.SignStateless} function signs a message using the stateless (SPHINCS+C) path. This is used when state is lost, corrupted, or exhausted. -SHRINCS uses a unified digest for the stateless path: a single call to \texttt{H\_msg\_fors} produces a digest that encodes both the FORS leaf indices (determining which leaves to reveal in the few-time signature) and the hypertree tree/leaf indices (determining which XMSS trees and WOTS+C key pairs to use at each layer). The grinding counter \texttt{ctr} is iterated until the grinding condition is satisfied on this unified digest. +SHRINCS uses a unified digest for the stateless path: a single call to \texttt{pors\_digest\_to\_indices} produces a XOF output that encodes both the PORS+FP leaf indices (determining which leaves to reveal in the few-time signature) and the hypertree tree/leaf indices (determining which XMSS trees and WOTS+C key pairs to use at each layer). The grinding counter \texttt{ctr} is iterated until the grinding condition is satisfied on this unified digest. -The digest output length is \texttt{(roundup(k*a + hsl) / 8)} bytes. The first \texttt{k*a} bits encode the \texttt{k} FORS leaf indices (with the last a bits required to be zero by grinding). The remaining \texttt{hsl} bits encode the hypertree path indices. +Let \texttt{C := roundup(k * 2\string^b / t) + magrin}, so the XOF output length is \texttt{roundup((C * b + offset + hsl)/8)}. The first \texttt{C * b + offset} bits encode the \texttt{k} PORS+FP leaf indices. The remaining \texttt{hsl} bits encode the hypertree path indices. -The signature is constructed bottom-up: first a FORS+C signature on the message, then \texttt{d} XMSS signatures on successive layer roots ascending through the hypertree. The signature includes \texttt{PK.sf} prepended to enable unified verification. +The signature is constructed bottom-up: first a PORS+FP signature on the message, then \texttt{d} XMSS signatures on successive layer roots ascending through the hypertree. The signature includes \texttt{PK.sf} prepended to enable unified verification. \begin{verbatim} Algorithm: SHRINCS.SignStateless(m, SK) @@ -105,14 +105,14 @@ \subsection{Stateless signing} 1. ADRS ← new_ADRS() 2. setLayerAddress(ADRS, 0) 3. setTreeAddress(ADRS, 0) - 4. (fors_sig, digest) ← fors_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS) - 5. (tree_idx, leaf_idx) ← parse_idx(digest) - 6. indices ← fors_msg_to_indices(digest) + 4. (pors_sig, digest) ← pors_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS) + 5. indices, xof_out ← pors_digest_to_indices(digest) + 6. (tree_idx, leaf_idx) ← parse_idx(xof_out) 7. setLayerAddress(ADRS, 0) 8. setTreeAddress(ADRS, tree_idx[0] * 2^h_prime + leaf_idx[0]) - 9. fors_pk ← fors_pkFromSig(fors_sig, indices, PK.seed, ADRS) + 9. pors_pk ← pors_pkFromSig(pors_sig, indices, PK.seed, ADRS) 10. ht_sig ← [] - 11. msg ← fors_pk + 11. msg ← pors_pk 12. for layer from 0 to d - 1: a. setLayerAddress(ADRS, layer) b. setTreeAddress(ADRS, tree_idx[layer]) @@ -120,23 +120,24 @@ \subsection{Stateless signing} d. ht_sig ← ht_sig || xmss_sig e. if layer < d - 1: msg ← xmss_root(SK.seed, PK.seed, ADRS, h') // we may cache or reuse the root computed during xmss_sign - 13. sig ← PK.sf || fors_sig || ht_sig + 13. sig ← PK.sf || pors_sig || ht_sig 14. return sig \end{verbatim} \subsection{Index Parsing} -The \texttt{parse\_idx} function extracts tree and leaf indices for each hypertree layer from the stateless digest. In the unified digest scheme, these bits follow the \texttt{k*a} bits used for FORS indices. The \texttt{hsl} bits are partitioned into \texttt{d} pairs of \texttt{(leaf\_idx, tree\_idx)}, extracted from least significant (layer 0) to most significant (layer \texttt{d-1}), with \texttt{h'} bits per leaf index. -%\mknote{This is a bit confusin, that we extract the indices of the OTS/FTS schemes used, but not the indices of the revealed leaves in FORS. With the Hashing changes, it needs a double-check that everything is computed correctly and the same data is not used for two purposes. } +The \texttt{parse\_idx} function extracts tree and leaf indices for each hypertree layer from the stateless digest. +In the unified digest scheme, these bits follow the XOF output from \texttt{pors\_digest\_to\_indices}, resulting in \texttt{C * b + 16} bits used for PORS+FP indices. The \texttt{hsl} bits are partitioned into \texttt{d} pairs of \texttt{(leaf\_idx, tree\_idx)}, extracted from least significant (layer 0) to most significant (layer \texttt{d-1}), with \texttt{h'} bits per leaf index. +%\mknote{This is a bit confusin, that we extract the indices of the OTS/FTS schemes used, but not the indices of the revealed leaves in PORS. With the Hashing changes, it needs a double-check that everything is computed correctly and the same data is not used for two purposes. } \begin{verbatim} - Algorithm: parse_idx(digest) + Algorithm: parse_idx(xof_out) Input: - digest: message digest + xof_out: XOF output Output: tree_idx: array of d tree indices leaf_idx: array of d leaf indices - 1. ht_offset ← k * a - 2. ht_bits ← extract_bits(digest, ht_offset, hsl) + 1. ht_offset ← roundup(2^b * k/t + margin) * b + offset + 2. ht_bits ← extract_bits(xof_out, ht_offset, hsl) 3. idx ← ht_bits 4. tree_idx ← [] 5. leaf_idx ← [] @@ -146,7 +147,7 @@ \subsection{Index Parsing} c. tree_idx[layer] ← idx 7. return (tree_idx, leaf_idx) \end{verbatim} -\paragraph{Note:} The total digest must encode \texttt{k*a + hsl = 6*22 + 24 = 156} bits. The \texttt{H\_msg\_fors} output length (Section~5.4.2) must be set accordingly: \texttt{roundup((k*a + hsl) / 8) = 20} bytes. This is a correction from the original \texttt{roundup((k*a) / 8) = 17} bytes, which was insufficient for the unified digest. +\paragraph{Note:} The total XOF output must encode \texttt{C * b + offset + hsl} bits. For SHRINCS-B/L the XOF length is 472 bits, and for SHRINCS-B32 is 456 bits (Section~9.3). \subsection{Stateful verification} The \texttt{SHRINCS.VerifyStateful} function verifies a stateful signature. It extracts \texttt{PK.sl} from the signature, parses the UXMSS signature, and infers \texttt{q} from the authentication path length. It then uses the two-phase WOTS+C hashing to recover \texttt{PK.sf} via \texttt{uxmss\_pkFromSig} and verifies that \texttt{Tw(PK.seed, ADRS, PK.sf || PK.sl)} equals \texttt{PK.root}. @@ -190,7 +191,7 @@ \subsection{Stateful verification} \end{verbatim} \subsection{Stateless verification} -The \texttt{SHRINCS.VerifyStateless} function verifies a stateless signature. It extracts \texttt{PK.sf} from the signature, recomputes the unified digest from the FORS+C signature's \texttt{R} and \texttt{ctr}, extracts both FORS indices and hypertree indices from the same digest, then verifies bottom-up through the hypertree. +The \texttt{SHRINCS.VerifyStateless} function verifies a stateless signature. It extracts \texttt{PK.sf} from the signature, recomputes the unified digest from the PORS+FP signature's \texttt{R} and \texttt{ctr}, extracts both PORS+FP indices and hypertree indices from the same digest, then verifies bottom-up through the hypertree. \begin{verbatim} Algorithm: SHRINCS.VerifyStateless(m, sig, PK) Input: @@ -203,22 +204,28 @@ \subsection{Stateless verification} 1. ADRS ← new_ADRS() 2. PK.sf ← sig[0 : n] 3. offset ← n - 4. fors_sig_size ← R_SIZE + (k - 1) * (n + a * n) - 5. fors_sig ← sig[offset : offset + fors_sig_size] - 6. offset ← offset + fors_sig_size - 7. R ← fors_sig[0 : R_SIZE] + 4. pors_sig_size ← R_SIZE + (k + m_max)*n + 5. pors_sig ← sig[offset : offset + pors_sig_size] + 6. offset ← offset + pors_sig_size + 7. R ← pors_sig[0 : R_SIZE] 8. setTypeAndClear(ADRS, SL_H_MSG) - 9. digest ← H_msg_fors(ADRS, R, PK.seed, PK.root, m) - 10. indices ← fors_msg_to_indices(digest) - 11. if indices[k-1] != 0: + 9. digest ← H_msg_pors(ADRS, R, PK.seed, PK.root, m) + 10. indices, xof_out ← pors_digest_to_indices(digest) + 11. A ← pors_octopus(indices) + 12. if |A| > m_max: return false - 12. (tree_idx, leaf_idx) ← parse_idx(digest) - 13. setLayerAddress(ADRS, 0) - 14. setTreeAddress(ADRS, tree_idx[0] * 2^h_prime + leaf_idx[0]) - 15. fors_pk ← fors_pkFromSig(fors_sig, indices, PK.seed, ADRS) - 16. msg ← fors_pk - 17. xmss_sig_size ← R_SIZE + c + l * n + h_prime * n - 18. for layer from 0 to d - 1: + 13. for i from R_SIZE + k*n + |A| to pors_sig_size: + a. pad_val ← pors_sig[offset : offset + n] + b. if pad_val != toByte(0, n): + return false + c. offset ← offset + n + 14. (tree_idx, leaf_idx) ← parse_idx(xof_out) + 15. setLayerAddress(ADRS, 0) + 16. setTreeAddress(ADRS, tree_idx[0] * 2^h_prime + leaf_idx[0]) + 17. pors_pk ← pors_pkFromSig(pors_sig, indices, PK.seed, ADRS) + 18. msg ← pors_pk + 19. xmss_sig_size ← R_SIZE + c + l * n + h_prime * n + 20. for layer from 0 to d - 1: a. xmss_sig ← sig[offset : offset + xmss_sig_size] b. offset ← offset + xmss_sig_size c. wots_sig ← xmss_sig[0 : R_SIZE + c + l * n] @@ -228,12 +235,12 @@ \subsection{Stateless verification} g. msg ← xmss_pkFromSig(wots_sig, auth, msg, PK.seed, PK.root, ADRS, h_prime, leaf_idx[layer]) h. if msg == false: return false - 19. PK.sl ← msg - 20. setLayerAddress(ADRS, 0) - 21. setTreeAddress(ADRS, 0) - 22. setTypeAndClear(ADRS, ROOT) - 23. expected_root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) - 24. return (expected_root == PK.root) + 21. PK.sl ← msg + 22. setLayerAddress(ADRS, 0) + 23. setTreeAddress(ADRS, 0) + 24. setTypeAndClear(ADRS, ROOT) + 25. expected_root ← Tw(PK.seed, ADRS, PK.sf || PK.sl) + 26. return (expected_root == PK.root) \end{verbatim} \subsection{Unified Verification} @@ -250,6 +257,6 @@ \subsection{Unified Verification} 1. max_sf_size ← n + R_SIZE + c + l * n + hsf * n 2. if len(sig) <= max_sf_size: return SHRINCS.VerifyStateful(m, sig, PK) - else: + else: return SHRINCS.VerifyStateless(m, sig, PK) \end{verbatim} \ No newline at end of file diff --git a/docs/shrincs_spec/content/11-data-structures.tex b/docs/shrincs_spec/content/11-data-structures.tex index 95d4f83..63512e4 100644 --- a/docs/shrincs_spec/content/11-data-structures.tex +++ b/docs/shrincs_spec/content/11-data-structures.tex @@ -33,7 +33,7 @@ \subsection{Stateful Signature Format} \end{verbatim} The signature size depends on the signature index \texttt{q}: \begin{verbatim} -SHRINCS-B (n=16, R_SIZE=32, c=4, l=16): +SHRINCS-B/B32 (n=16, R_SIZE=32, c=4, l=16): PK.sl : 16 bytes R : 32 bytes ctr : 4 bytes @@ -53,39 +53,50 @@ \subsection{Stateful Signature Format} \end{verbatim} \subsection{Stateless Signature Format} -A stateless signature consists of the stateful public key, a FORS+C signature, and \texttt{d} XMSS signatures forming the hypertree: +A stateless signature consists of the stateful public key, a PORS+FP signature, and \texttt{d} XMSS signatures forming the hypertree: \begin{verbatim} PK.sf : n bytes // Stateful public key -FORS+C signature: - fors_R : R_SIZE bytes // Randomness (encodes grinding result) - fors_sigs: (k-1) * (n + a*n) bytes // k-1 trees +PORS+FP signature: + pors_R : R_SIZE bytes // Randomness (encodes grinding result) + pors_sigs: (k+m_max)*n bytes // k revealed secret keys Hypertree: d XMSS signatures. For each layer i: xmss_R[i] : R_SIZE bytes // Randomness for WOTS+C xmss_ctr[i] : c bytes // Grinding counter for WOTS+C xmss_chains[i]: l * n bytes // WOTS+C chain values xmss_auth[i] : h_prime * n bytes // XMSS authentication path -SHRINCS-B (n=16, R_SIZE=32, c=4, k=6, a=22, d=2, h'=12, l=16): +SHRINCS-B (n=16, R_SIZE=32, c=4, k=6, m_max = 91, d=2, h'=12, l=16): PK.sf : 16 bytes - FORS+C signature: - fors_R : 32 bytes - fors_sigs : 5 * (16 + 22*16) = 5 * 368 = 1840 bytes - subtotal : 1872 bytes + PORS+FP signature: + pors_R : 32 bytes + pors_sigs : (6 + 91) * 16 = 97 * 16 = 1552 bytes + subtotal : 1584 bytes XMSS hypertree (2 layers): per layer : 32 + 4 + 256 + 192 = 484 bytes 2 layers : 968 bytes - Total: 16 + 1872 + 968 = 2856 bytes + Total: 16 + 1584 + 968 = 2568 bytes -SHRINCS-L (n=16, R_SIZE=32, c=4, k=6, a=22, d=2, h'=12, l=64): +SHRINCS-L (n=16, R_SIZE=32, c=4, k=6, m_max = 91, d=2, h'=12, l=64): PK.sf : 16 bytes - FORS+C signature: - fors_R : 32 bytes - fors_sigs : 5 * (16 + 22*16) = 1840 bytes - subtotal : 1872 bytes + PORS+FP signature: + pors_R : 32 bytes + pors_sigs : (6 + 91) * 16 = 97 * 16 = 1552 bytes + subtotal : 1584 bytes XMSS hypertree (2 layers): per layer : 32 + 4 + 1024 + 192 = 1252 bytes 2 layers : 2504 bytes - Total: 16 + 1872 + 2504 = 4392 bytes + Total: 16 + 1584 + 2504 = 4104 bytes + + SHRINCS-B32 (n=16, R_SIZE=32, c=4, k=11, m_max = 111, d=4, h'=8, l=16): + PK.sf : 16 bytes + PORS+FP signature: + pors_R : 32 bytes + pors_sigs : (11 + 111) * 16 = 122 * 16 = 1952 bytes + subtotal : 1984 bytes + XMSS hypertree (2 layers): + per layer : 32 + 4 + 256 + 128 = 420 bytes + 4 layers : 1680 bytes + Total: 16 + 1984 + 1680 = 3680 bytes \end{verbatim} \subsection{Signature Type Indication} @@ -95,8 +106,9 @@ \subsection{Signature Type Indication} \texttt{max\_stateful\_size = n + R\_SIZE + c + l*n + hsf*n} \end{center} \begin{itemize} - \item For SHRINCS-B: \texttt{max\_stateful\_size = 16 + 32 + 4 + 256 + 158*16 = 2836} bytes - \item For SHRINCS-L: \texttt{max\_stateful\_size = 16 + 32 + 4 + 1024 + 206*16 = 4372} bytes + \item For SHRINCS-B: \texttt{max\_stateful\_size = 16 + 32 + 4 + 256 + 141*16 = 2564} bytes + \item For SHRINCS-L: \texttt{max\_stateful\_size = 16 + 32 + 4 + 1024 + 189*16 = 4100} bytes + \item For SHRINCS-B32: \texttt{max\_stateful\_size = 16 + 32 + 4 + 256 + 210*16 = 3668} bytes \end{itemize} Signatures with length \texttt{<= max\_stateful\_size} are processed as stateful; larger signatures as stateless. \ No newline at end of file diff --git a/docs/shrincs_spec/content/2-notation.tex b/docs/shrincs_spec/content/2-notation.tex index f84e4f0..6a17d86 100644 --- a/docs/shrincs_spec/content/2-notation.tex +++ b/docs/shrincs_spec/content/2-notation.tex @@ -13,4 +13,5 @@ \section{Notation} \item[] \texttt{Tw} \quad denotes a tweakable hash function \item[] \texttt{PRF} \quad denotes a pseudorandom function \item[] \texttt{$\bot$} \quad (or \texttt{false}) used for failure returns + \item[] \texttt{|.|} \quad denotes an array size \end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/content/3-overview.tex b/docs/shrincs_spec/content/3-overview.tex index 2fb6d89..3be3b54 100644 --- a/docs/shrincs_spec/content/3-overview.tex +++ b/docs/shrincs_spec/content/3-overview.tex @@ -3,7 +3,7 @@ \section{Overview} During normal operation with intact state, the user generates signatures according to efficient stateful path. Each leaf of the UXMSS is represented by WOTS+C one time signature instance. UXMSS is right-skewed, which makes the first stateful signature (\texttt{q = 1}) the cheapest from the verification and size perspective and adds 16 bytes and one additional hash operation for each next stateful signature, up to \texttt{q = hsf}. The final signature (\texttt{q = hsf + 1}) has the same size and verification cost as \texttt{q = hsf}. A right-skewed UXMSS is a tree construction in which every internal node has a one-time signature leaf as its left child and a UXMSS subtree as its right child, yielding a recursively right-branching authentication tree (as shown on the Figure~1). -If the state is corrupted (or the limit for stateful operations is met), the scheme can continue operating with stateless mode. Stateless mode utilizes an optimized SPHINCS+ variant with WOTS+C signatures in regular XMSS trees on each layer in the hypertree and FORS+C for few-time signature scheme. The general construction of SHRINCS signature is presented on the Figure~1. +If the state is corrupted (or the limit for stateful operations is met), the scheme can continue operating with stateless mode. Stateless mode utilizes an optimized SPHINCS+ variant with WOTS+C signatures in regular XMSS trees on each layer in the hypertree and PORS+FP for few-time signature scheme. The general construction of SHRINCS signature is presented on the Figure~1. \begin{figure}[h!] \centering \includegraphics[width=1\linewidth]{content/img/general.png} @@ -61,46 +61,56 @@ \subsubsection{Stateless} Stateless signatures consist of: \begin{enumerate} \item \texttt{PK.sf}: the stateful public key (unified verification) - \item FORS+C signature: few-time signature on the message + \item PORS+FP signature: few-time signature on the message \item HT signature: \texttt{d} XMSS signatures forming the hypertree \end{enumerate} Signature size calculation: \begin{verbatim} - General formula: size = PK.sf_SIZE + fors_size(R_SIZE + (k-1)*(n + a*n)) + General formula: size = PK.sf_SIZE + pors_size(R_SIZE + (k + m_max)*n) + xmss_layer_size(R_SIZE + c + l*n + h'*n) * d - SHRINCS-B.size = 16 + fors_size(32 + 5*(16 + 22*16)) + SHRINCS-B.size = 16 + pors_size(32 + (6 + 91)*16) + xmss_layer_size(32 + 4 + 16*16 + 12*16) * 2 - = 16 + (32 + 1840) + (32 + 4 + 256 + 192) * 2 - = 16 + 1872 + 968 = 2856 + = 16 + (32 + 1552) + (32 + 4 + 256 + 192) * 2 + = 16 + 1584 + 968 = 2568 - SHRINCS-L.size = 16 + fors_size(32 + 5*(16 + 22*16)) + SHRINCS-L.size = 16 + pors_size(32 + (6+91)*16) + xmss_layer_size(32 + 4 + 64*16 + 12*16) * 2 - = 16 + (32 + 1840) + (32 + 4 + 1024 + 192) * 2 - = 16 + 1872 + 2504 = 4392 + = 16 + (32 + 1552) + (32 + 4 + 1024 + 192) * 2 + = 16 + 1584 + 2504 = 4104 + + SHRINCS-B32.size = 16 + pors_size(32 + (11 + 111)*16) + + xmss_layer_size(32 + 4 + 16*16 + 8*16) * 4 + = 16 + (32 + 1952) + (32 + 4 + 256 + 128) * 4 + = 16 + 1984 + 1680 = 3680 \end{verbatim} The signature verification cost is: \begin{verbatim} General formula: - fors_hashes = (k-1)*(1 + a) + 1 + pors_hashes = 2*k + m_max wots_per_layer = (w-1)*l - S_wn + 1 xmss_per_layer = wots_per_layer + h' - total = fors_hashes + d*xmss_per_layer + 1 + total = pors_hashes + d*xmss_per_layer + 1 SHRINCS-B: - fors_hashes = 5*23 + 1 = 116 + pors_hashes = 2*6 + 91 = 103 wots_per_layer = (256-1)*16 - 2040 + 1 = 2041 xmss_per_layer = 2041 + 12 = 2053 - total = 116 + 2*2053 + 1 = 4223 hashes + total = 103 + 2*2053 + 1 = 4210 hashes SHRINCS-L: - fors_hashes = 116 - wots_per_layer = (w-1)*l - S_wn + 1 = 53 - xmss_per_layer = wots_per_layer + h' = 53 + 12 = 65 - total = fors_hashes + d*xmss_per_layer + 1 - = 116 + 2*65 + 1 = 247 hashes + pors_hashes = 103 + wots_per_layer = (4-1)*64 - 140 + 1 = 53 + xmss_per_layer = 53 + 12 = 65 + total = 103 + 2*65 + 1 = 234 hashes + + SHRINCS-B32: + pors_hashes = 2*11 + 111 = 133 + wots_per_layer = (256-1)*16 - 2040 + 1 = 2041 + xmss_per_layer = 2041 + 8 = 2049 + total = 133 + 4*2049 + 1 = 8330 hashes \end{verbatim} -\paragraph{Note:} \texttt{k} (number of trees) and \texttt{a} (tree height) are parameters of FORS+ (see Section~9). \texttt{h'} is the height of internal tree in HT. One additional hash is required to obtain the root public key after stateless public key recovery. \ No newline at end of file +\paragraph{Note:} \texttt{k} (number of trees) and \texttt{m\_max} (maximum Octopus authentication set) are parameters of PORS+FP (see Section~9). \texttt{h'} is the height of internal tree in HT. One additional hash is required to obtain the root public key after stateless public key recovery. \ No newline at end of file diff --git a/docs/shrincs_spec/content/4-params.tex b/docs/shrincs_spec/content/4-params.tex index 62cba91..ef40363 100644 --- a/docs/shrincs_spec/content/4-params.tex +++ b/docs/shrincs_spec/content/4-params.tex @@ -3,28 +3,32 @@ \section{Parameters} \begin{enumerate} \item SHRINCS-B: size matters (instance preferable for Bitcoin). \item SHRINCS-L: verification complexity matters (the number of hashes to verify the signature). + \item SHRINCS-B32: size and number of signatures matters (instance preferable for Ligthing). \end{enumerate} \begin{center} \begin{tabular}{ c c c c c} - Parameter & SHRINCS-B & SHRINCS-L & Description \\ + Parameter & SHRINCS-B & SHRINCS-L & SHRINCS-B32 & Description \\ \hline - \texttt{H} & \texttt{SHA-256} & \texttt{SHA-256} & Hash function \\ - \texttt{OTS} & \texttt{WOTS+C} & \texttt{WOTS+C} & One-time signature \\ - \texttt{FTS} & \texttt{FORS+C} & \texttt{FORS+C} & Few-time signature \\ - \texttt{n} & $16$ & $16$ & Security parameter (bytes) \\ - \texttt{w} & $256$ & $4$ & Winternitz parameter \\ - \texttt{l} & $16$ & $64$ & The number of WOTS+C chains \\ - \texttt{S\_wn} & $2040$ & $140$ & Target sum for WOTS+C \\ - \texttt{hsf} & $158$ & $206$ & Maximum stateful tree height \\ - \texttt{hsl} & $24$ & $24$ & Maximum stateless hypertree height \\ - \texttt{d} & $2$ & $2$ & Stateless hypertree layers \\ - \texttt{h'} & $12$ & $12$ & XMSS tree height per hypertree layer \texttt{hsl/d = 12} \\ - \texttt{a} & $22$ & $22$ & FORS tree height \\ - \texttt{k} & $6$ & $6$ & Number of FORS+C trees \\ - \texttt{c} & $4$ & $4$ & Counter size for WOTS+C grinding (bytes) \\ - \texttt{R\_SIZE} & $32$ & $32$ & The size (bytes) of the randomness in the signature \\ + \texttt{H} & \texttt{SHA-256} & \texttt{SHA-256} & \texttt{SHA-256} & Hash function \\ + \texttt{OTS} & \texttt{WOTS+C} & \texttt{WOTS+C} & \texttt{WOTS+C} & One-time signature \\ + \texttt{FTS} & \texttt{PORS+FP} & \texttt{PORS+FP} & \texttt{PORS+FP} & Few-time signature \\ + \texttt{n} & $16$ & $16$ & $16$ & Security parameter (bytes) \\ + \texttt{w} & $256$ & $4$ & $256$ & Winternitz parameter \\ + \texttt{l} & $16$ & $64$ & $16$ & The number of WOTS+C chains \\ + \texttt{S\_wn} & $2040$ & $140$ & $2040$ & Target sum for WOTS+C \\ + \texttt{hsf} & $141$ & $189$ & $210$ & Maximum stateful tree height \\ + \texttt{hsl} & $24$ & $24$ & $32$ & Maximum stateless hypertree height \\ + \texttt{d} & $2$ & $2$ & $4$ & Stateless hypertree layers \\ + \texttt{h'} & $12$ & $12$ & $8$ & XMSS tree height per hypertree layer \\ + \texttt{t} & $9245141$ & $9245141$ & $109571$ & The number of secret values in PORS+FP tree \\ + \texttt{b} & $24$ & $24$ & $17$ & PORS+FP tree height \\ + \texttt{k} & $6$ & $6$ & $11$ & Number of PORS+FP revealed tree leafs \\ + \texttt{m\_max} & $91$ & $91$ & $111$ & Maximum size of the Octopus authentication path \\ + \texttt{c} & $4$ & $4$ & $4$ & Counter size for WOTS+C grinding (bytes) \\ + \texttt{R\_SIZE} & $32$ & $32$ & $32$ & The size (bytes) of the randomness in the signature \\ + \end{tabular} \end{center} -\paragraph{Note:} Parameters for stateless instance is selected in the way to support up to $2^{20}$ signatures \cite{kudinov_nick_2025_hbs_bitcoin}. \texttt{hsf} is chosen in the way that the maximum stateful signature remains smaller than a stateless signature: \texttt{n + R\_SIZE + c + l*n + hsf*n < stateless\_size}. \ No newline at end of file +\paragraph{Note:} Parameters for SHRINCS-B and SHRINCS-L is selected in the way to support up to $2^{20}$ signatures \cite{kudinov_nick_2025_hbs_bitcoin}, while SHRINCS-B32 supports up to $2^{32}$ signatures. \texttt{hsf} is chosen in the way that the maximum stateful signature remains smaller than a stateless signature: \texttt{n + R\_SIZE + c + l*n + hsf*n < stateless\_size}. \ No newline at end of file diff --git a/docs/shrincs_spec/content/5-functions-and-addresses.tex b/docs/shrincs_spec/content/5-functions-and-addresses.tex index 7900e23..d955ac6 100644 --- a/docs/shrincs_spec/content/5-functions-and-addresses.tex +++ b/docs/shrincs_spec/content/5-functions-and-addresses.tex @@ -31,7 +31,7 @@ \subsection{Tweakable Hash Function} \subsection{Pseudorandom Functions} SHRINCS uses two pseudorandom functions for different purposes: key derivation and getting the randomizer for the signature. -For deterministic derivation of secret key material (WOTS+C chain starting values, FORS secret keys): +For deterministic derivation of secret key material (WOTS+C chain starting values, PORS+FP secret keys): \begin{center} \texttt{PRF(PK.seed, ADRS, SK.seed) = Tw(PK.seed*, ADRS, SK.seed)} @@ -45,16 +45,16 @@ \subsection{Pseudorandom Functions} \texttt{PRF\_msg(SK.prf, PK.seed, opt\_rand, m) = HMAC-SHA-256(SK.prf, PK.seed || opt\_rand || m)[0:R\_SIZE]}, \end{center} where \texttt{opt\_rand} is an optional randomness (\texttt{n} bytes), can be \texttt{PK.seed} for deterministic signing. -For the stateless signing path, the FORS+C grinding counter is iterated inside \texttt{PRF\_msg} to produce a fresh \texttt{R} on each attempt. The counter-augmented variant is: +For the stateless signing path, the PORS+FP grinding counter is iterated inside \texttt{PRF\_msg} to produce a fresh \texttt{R} on each attempt. The counter-augmented variant is: \begin{center} \texttt{PRF\_msg\_ctr(SK.prf, PK.seed, opt\_rand, ctr, m) = HMAC-SHA-256(SK.prf, PK.seed || opt\_rand || ctr || m)[0:R\_SIZE]} \end{center} -where \texttt{ctr} is a counter incremented during FORS+C grinding (see Section~9.4). The verifier never sees \texttt{ctr}, it only receives the final \texttt{R} that satisfied the grinding condition. This design keeps the grinding work inside the signer's PRF evaluation while allowing the verifier to check the result using only \texttt{R} and the message hash. +where \texttt{ctr} is a counter incremented during PORS+FP grinding (see Section~9.4). The verifier never sees \texttt{ctr}, it only receives the final \texttt{R} that satisfied the grinding condition. This design keeps the grinding work inside the signer's PRF evaluation while allowing the verifier to check the result using only \texttt{R} and the message hash. \subsection{Message Hash Function} -SHRINCS uses different hashing strategies for WOTS+C and FORS+C, optimized for their respective use cases. +SHRINCS uses different hashing strategies for WOTS+C and PORS+FP, optimized for their respective use cases. \subsubsection{WOTS+C: Two-Phase Hashing} WOTS+C uses a two-phase approach for message hashing and grinding. This design is necessary because: @@ -74,13 +74,23 @@ \subsubsection{WOTS+C: Two-Phase Hashing} \texttt{H\_grind(ADRS, PK.seed, digest, ctr) = SHA-256(PK.seed* || ADRS || digest || ctr)[0:n]} \end{center} -\subsubsection{FORS+C: Single-Phase Hashing} -FORS+C always signs user messages (never tree roots), so it uses a simpler single-phase approach. The grinding counter is not included in the message hash. Instead, it is iterated inside \texttt{PRF\_msg\_ctr} (Section~5.3) to produce a fresh \texttt{R} on each grinding attempt. The verifier only sees the final \texttt{R}: +\subsubsection{PORS+FP: Two-Phase Hashing} +PORS+FP consists of two phases, where we obatain the digest, and using this digest compute the XOF output for obtaining indices in the signing process. +The grinding counter is not included in the message hash. Instead, it is iterated inside \texttt{PRF\_msg\_ctr} (Section~5.3) to produce a fresh \texttt{R} on each grinding attempt. The verifier only sees the final \texttt{R}: +For XOF we don't need to store the \texttt{blk} value either (Section~9.3). + +\paragraph{Phase 1. Message Digest.} Computes a randomized hash of the user message with the randomness \texttt{R}: + \begin{center} - \texttt{H\_msg\_fors(ADRS, R, PK.seed, PK.root, m) = SHA-256(PK.seed* || ADRS || R || PK.root || m)[0:roundup((k*a + hsl) / 8)]} + \texttt{H\_msg\_pors(ADRS, R, PK.seed, PK.root, m) = SHA-256(PK.seed* || ADRS || R || PK.root || m)[0:n]} \end{center} +The output length is n bytes. + +\paragraph{Phase 2. XOF Block Digest.} Computes a XOF hash blocks from the message digest, using \texttt{blk} to produce different blocks: -The output length is roundup((k*a + hsl) / 8) = 20 bytes for both parameter sets, encoding both the \texttt{k} FORS leaf indices and the \texttt{hsl} hypertree path indices in a unified digest (see Section~10.5). +\begin{center} + \texttt{H\_xof\_pors(ADRS, PK.seed, digest, blk) = SHA-256(PK.seed* || ADRS || digest || blk)} +\end{center} \subsection{Tweak Structure and Domain Separation} Tweaks are structured as follows to ensure uniqueness across all scheme components: @@ -105,19 +115,20 @@ \subsubsection{Address Types} 0x03: SF_WOTS_GRIND: Stateful WOTS+C grinding 0x04: SF_H_MSG: Stateful message hash 0x05: SF_WOTS_PRF: Stateful PRF for WOTS+C key derivation -0x06: FORS_HASH: FORS leaf hashing -0x07: FORS_TREE: FORS tree internal nodes -0x08: FORS_PK: FORS roots compression -0x09: FORS_PRF: PRF for FORS secret key derivation -0x0A: SL_WOTS_HASH: Stateless WOTS+C chain hashing -0x0B: SL_WOTS_PK: Stateless WOTS+C public key compression -0x0C: SL_TREE: Stateless tree internal nodes -0x0D: SL_WOTS_GRIND: Stateless WOTS+C grinding -0x0E: SL_H_MSG: Stateless message hash -0x0F: SL_WOTS_PRF: Stateless PRF for WOTS+C key derivation -0x10: ROOT: Root public key computation +0x06: PORS_HASH: PORS+FP leaf hashing +0x07: PORS_TREE: PORS+FP tree internal nodes +0x08: PORS_PK: PORS+FP roots compression +0x09: PORS_PRF: PRF for PORS+FP secret key derivation +0x0A: PORS_XOF: XOF for PORS+FP indices extraction +0x0B: SL_WOTS_HASH: Stateless WOTS+C chain hashing +0x0C: SL_WOTS_PK: Stateless WOTS+C public key compression +0x0D: SL_TREE: Stateless tree internal nodes +0x0E: SL_WOTS_GRIND: Stateless WOTS+C grinding +0x0F: SL_H_MSG: Stateless message hash +0x10: SL_WOTS_PRF: Stateless PRF for WOTS+C key derivation +0x11: ROOT: Root public key computation \end{verbatim} -Summarizing, \texttt{(0x00, 0x01, 0x02, 0x03, 0x04, 0x05)} are stateful only types, \texttt{(0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)} are stateless only and \texttt{(0x10)} is shared type. +Summarizing, \texttt{(0x00, 0x01, 0x02, 0x03, 0x04, 0x05)} are stateful only types, \texttt{(0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10)} are stateless only and \texttt{(0x11)} is shared type. \subsubsection{Address Manipulation Functions} \begin{verbatim} @@ -167,17 +178,18 @@ \subsubsection{Type-Dependent Words} 0x03 & \texttt{SF\_WOTS\_GRIND} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ 0x04 & \texttt{SF\_H\_MSG} & \texttt{0} & \texttt{0} & \texttt{0} \\ 0x05 & \texttt{SF\_WOTS\_PRF} & \texttt{keypair} & \texttt{chain} & \texttt{0} \\ -0x06 & \texttt{FORS\_HASH} & \texttt{tree\_idx} & \texttt{0} & \texttt{leaf\_idx} \\ -0x07 & \texttt{FORS\_TREE} & \texttt{tree\_idx} & \texttt{height} & \texttt{index} \\ -0x08 & \texttt{FORS\_PK} & \texttt{0} & \texttt{0} & \texttt{0} \\ -0x09 & \texttt{FORS\_PRF} & \texttt{tree\_idx} & \texttt{0} & \texttt{leaf\_idx} \\ -0x0A & \texttt{SL\_WOTS\_HASH} & \texttt{keypair} & \texttt{chain} & \texttt{hash} \\ -0x0B & \texttt{SL\_WOTS\_PK} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ -0x0C & \texttt{SL\_TREE} & \texttt{keypair} & \texttt{height} & \texttt{index} \\ -0x0D & \texttt{SL\_WOTS\_GRIN} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ -0x0E & \texttt{SL\_H\_MSG} & \texttt{0} & \texttt{0} & \texttt{0} \\ -0x0F & \texttt{SL\_WOTS\_PRF} & \texttt{keypair} & \texttt{chain} & \texttt{0} \\ -0x10 & \texttt{ROOT} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x06 & \texttt{PORS\_HASH} & \texttt{0} & \texttt{0} & \texttt{leaf\_idx} \\ +0x07 & \texttt{PORS\_TREE} & \texttt{0} & \texttt{height} & \texttt{index} \\ +0x08 & \texttt{PORS\_PK} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x09 & \texttt{PORS\_PRF} & \texttt{tree\_idx} & \texttt{0} & \texttt{leaf\_idx} \\ +0x0A & \texttt{PORS\_XOF} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x0B & \texttt{SL\_WOTS\_HASH} & \texttt{keypair} & \texttt{chain} & \texttt{hash} \\ +0x0C & \texttt{SL\_WOTS\_PK} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x0D & \texttt{SL\_TREE} & \texttt{keypair} & \texttt{height} & \texttt{index} \\ +0x0E & \texttt{SL\_WOTS\_GRIN} & \texttt{keypair} & \texttt{0} & \texttt{0} \\ +0x0F & \texttt{SL\_H\_MSG} & \texttt{0} & \texttt{0} & \texttt{0} \\ +0x10 & \texttt{SL\_WOTS\_PRF} & \texttt{keypair} & \texttt{chain} & \texttt{0} \\ +0x11 & \texttt{ROOT} & \texttt{0} & \texttt{0} & \texttt{0} \\ \hline \end{tabular} diff --git a/docs/shrincs_spec/content/7-xmss.tex b/docs/shrincs_spec/content/7-xmss.tex index 8fbbbdd..f023603 100644 --- a/docs/shrincs_spec/content/7-xmss.tex +++ b/docs/shrincs_spec/content/7-xmss.tex @@ -13,7 +13,7 @@ \subsection{XMSS Tree Structure} Layer indexing within the stateless hypertree is the following: \begin{itemize} - \item Layer 0: Bottom layer (is used to sign the FORS+C public keys) + \item Layer 0: Bottom layer (is used to sign the PORS+FP public keys) \item Layer d-1: Top layer (root is \texttt{PK.sl}) \end{itemize} @@ -85,14 +85,14 @@ \subsection{XMSS Authentication Path Generation} \subsection{XMSS Public Key Recovery from Signature} Given a WOTS+C signature and authentication path, this algorithm recovers the XMSS tree root. First, it recovers the WOTS+C public key (the leaf value) from the WOTS+C signature. Then, starting from the leaf, it iteratively combines the current node with the corresponding authentication path node to compute the parent, continuing until reaching the root. At each height \texttt{h}, the \texttt{h}-th bit of idx determines whether the current node is the left child \texttt{(bit = 0)} or right child \texttt{(bit = 1)}, which dictates the ordering when hashing the pair. -The message \texttt{m} being verified is either a FORS+C public key (at layer 0) or a lower-layer tree root (at layers \texttt{1...d-1}). In both cases, m is already a hash, so \texttt{wots\_pkFromSig} is called with \texttt{is\_internal = true} (phase 1 of message hashing is skipped; see Section~6.7). +The message \texttt{m} being verified is either a PORS+FP public key (at layer 0) or a lower-layer tree root (at layers \texttt{1...d-1}). In both cases, m is already a hash, so \texttt{wots\_pkFromSig} is called with \texttt{is\_internal = true} (phase 1 of message hashing is skipped; see Section~6.7). \begin{verbatim} Algorithm: xmss_pkFromSig(wots_sig, auth, m, PK.seed, PK.root, ADRS, h_prime, idx) Input: wots_sig: WOTS+C signature auth: authentication path (h_prime * n bytes) - m: signed message (FORS+C public key or lower-layer tree root) + m: signed message (PORS+FP public key or lower-layer tree root) PK.seed: public seed PK.root: root public key ADRS: tweak @@ -119,14 +119,14 @@ \subsection{XMSS Public Key Recovery from Signature} \end{verbatim} \subsection{XMSS Signature Generation (Stateless Layer)} -To sign a message within the stateless hypertree, the XMSS signature generation produces a WOTS+C signature on the message followed by the authentication path for the corresponding leaf. The message being signed is typically either a FORS+C public key (at layer 0) or the root of a lower-layer XMSS tree (at layers 1 through \texttt{d-1}). In both cases the message is already a hash value, so \texttt{wots\_sign} is called with \texttt{is\_internal = true}. +To sign a message within the stateless hypertree, the XMSS signature generation produces a WOTS+C signature on the message followed by the authentication path for the corresponding leaf. The message being signed is typically either a PORS+FP public key (at layer 0) or the root of a lower-layer XMSS tree (at layers 1 through \texttt{d-1}). In both cases the message is already a hash value, so \texttt{wots\_sign} is called with \texttt{is\_internal = true}. The leaf index \texttt{idx} determines which WOTS+C key pair is used; this index is determined from the message digest during stateless signing (see Section~10.5). \begin{verbatim} Algorithm: xmss_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS, h_prime, idx) Input: - m: message to sign (lower-layer root or FORS public key) + m: message to sign (lower-layer root or PORS+FP public key) SK.seed: secret seed SK.prf: PRF key PK.seed: public seed @@ -144,12 +144,13 @@ \subsection{XMSS Signature Generation (Stateless Layer)} \end{verbatim} \subsection{Contribution to Signature Size and Verification Complexity} -Each XMSS signature consists of a WOTS+C signature plus an authentication path of \texttt{h'} nodes: \texttt{R\_SIZE + c + (l + h') * n} bytes (484 bytes for SHRINCS-B and 1252 bytes for SHRINCS-L). +Each XMSS signature consists of a WOTS+C signature plus an authentication path of \texttt{h'} nodes: \texttt{R\_SIZE + c + (l + h') * n} bytes (484 and 420 bytes for SHRINCS-B/B32, and 1252 bytes for SHRINCS-L). -The full stateless hypertree consists of \texttt{d = 2} layers, giving: +The full stateless hypertree consists of \texttt{d=2} or \texttt{d=4} (SHRINCS-B32) layers, giving: \begin{itemize} \item 968 bytes and 4106 hashes for SHRINCS-B \item 2504 bytes and 130 hashes for SHRINCS-L + \item 1680 bytes and 8196 hashes for SHRINCS-B32 \end{itemize} -\paragraph{Note:} This excludes the FORS+C contribution (see Section~9). The total stateless signature size and verification cost are the sum of FORS+C and hypertree components, as presented in Section~3.2.2. \ No newline at end of file +\paragraph{Note:} This excludes the PORS+FP contribution (see Section~9). The total stateless signature size and verification cost are the sum of PORS+FP and hypertree components, as presented in Section~3.2.2. \ No newline at end of file diff --git a/docs/shrincs_spec/content/8-uxmss.tex b/docs/shrincs_spec/content/8-uxmss.tex index 4425c27..ad0c261 100644 --- a/docs/shrincs_spec/content/8-uxmss.tex +++ b/docs/shrincs_spec/content/8-uxmss.tex @@ -139,7 +139,7 @@ \subsection{UXMSS Public Key Recovery from Signature} 9. return node \end{verbatim} -\paragraph{Note:} In step 3, \texttt{is\_internal = false} because the stateful UXMSS signs user messages directly (requiring phase 1 + phase 2 hashing). This differs from the stateless XMSS layers which always sign tree roots or FORS public keys (\texttt{is\_internal = true}). +\paragraph{Note:} In step 3, \texttt{is\_internal = false} because the stateful UXMSS signs user messages directly (requiring phase 1 + phase 2 hashing). This differs from the stateless XMSS layers which always sign tree roots or PORS+FP public key (\texttt{is\_internal = true}). \subsection{UXMSS Signature Generation} To sign a message using the stateful path, UXMSS signature generation produces a WOTS+C signature followed by the authentication path for position \texttt{q}. The signature number \texttt{q} must be tracked as state and incremented after each use to ensure one-time signature security. As noted in Section~1.2, the state counter should be persisted before generating the signature to prevent key reuse in case of interruption. @@ -176,13 +176,19 @@ \subsection{Contribution to Signature Size and Verification Complexity} SHRINCS-B: q=1: 32 + 4 + 16*16 + 16 = 308 bytes q=10: 32 + 4 + 16*16 + 16*10 = 452 bytes - q=158: 32 + 4 + 16*16 + 16*158 = 2820 bytes - q=159(max): 32 + 4 + 16*16 + 16*158 = 2820 bytes // same as q=158; < 2856 (stateless) + q=140: 32 + 4 + 16*16 + 16*140 = 2548 bytes + q=141(max): 32 + 4 + 16*16 + 16*141 = 2564 bytes // same as q=140; < 2568 (stateless) SHRINCS-L: q=1: 32 + 4 + 64*16 + 16 = 1076 bytes q=10: 32 + 4 + 64*16 + 16*10 = 1220 bytes - q=206: 32 + 4 + 64*16 + 16*206 = 4356 bytes - q=207(max): 32 + 4 + 64*16 + 16*206 = 4356 bytes // same as q=206; < 4392 (stateless) + q=188: 32 + 4 + 64*16 + 16*188 = 4084 bytes + q=189(max): 32 + 4 + 64*16 + 16*189 = 4100 bytes // same as q=188; < 4104 (stateless) + + SHRINCS-B32: + q=1: 32 + 4 + 16*16 + 16 = 308 bytes + q=10: 32 + 4 + 16*16 + 16*10 = 452 bytes + q=209: 32 + 4 + 16*16 + 16*209 = 3652 bytes + q=210(max): 32 + 4 + 16*16 + 16*210 = 3668 bytes // same as q=209; < 3680 (stateless) \end{verbatim} Verification cost per UXMSS signature is calculated as WOTS+C verification cost + \texttt{min(q, hsf)} hashes. \ No newline at end of file diff --git a/docs/shrincs_spec/content/9-pors.tex b/docs/shrincs_spec/content/9-pors.tex new file mode 100644 index 0000000..c129eaa --- /dev/null +++ b/docs/shrincs_spec/content/9-pors.tex @@ -0,0 +1,373 @@ +\section{PORS+FP} +PORS (PRNG to Obtain a Random Subset) is a few-time signature scheme that uses a \emph{single} Merkle tree over \texttt{t} +leaves. PORS reveals \texttt{k} leaves from a single tree and uses the Octopus algorithm~\cite{pors_octopus2018} to compute the minimal +set of authentication nodes needed to verify all \texttt{k} leaves simultaneously, +exploiting shared nodes to shorten the proof. PORS+FP~\cite{pors_fp2025} adds +\emph{forced pruning} on top: the signer grinds a counter until the resulting +$k$-subset of leaves produces an Octopus authentication set of size at most +\texttt{m\_max} nodes, bounding the signature to a fixed maximum size. + +\subsection{PORS Structure} + +PORS consists of a single Merkle tree over \texttt{t} leaves: +\begin{itemize} + \item The tree has \texttt{t} leaves (not necessarily a power of~2; the tree is + left-filled as described in Section~2.1) + \item Each leaf is derived from \texttt{SK.seed} using \texttt{PRF} with address + type \texttt{PORS\_PRF} + \item The message together with a grinding randomness \texttt{R}, + is hashed to a n-byte digest which is then expanded into \texttt{k} distinct + indices in $[t] = \{0,1,...,t-1\}$ + \item The Octopus algorithm computes the minimal authentication set \texttt{A} for those + \texttt{k} leaves +\end{itemize} + +The PORS+FP public key is the tweakable hash of the single tree root: +\begin{center} + \texttt{PORS+FP PK = Tw(PK.seed, ADRS, root)}, +\end{center} +using address type \texttt{PORS\_PK}. Forced pruning guarantees +$|A| \le m_{\max}$, bounding the signature to a fixed maximum size. + +\subsection{Grinding Condition} +\label{subsec:pors_grind_condition} + +PORS+FP uses the same two-call message-hash interface (Section~5.4.2): +\begin{verbatim} + R ← PRF_msg_ctr(SK.prf, PK.seed, opt_rand, ctr, m) + digest ← H_msg_pors(ADRS, R, PK.seed, PK.root, m) + A ← pors_octopus(pors_digest_to_indices(digest)) + condition: |A| <= m_max +\end{verbatim} + +The signer searches for an \texttt{R} that satisfies this condition by +iterating a counter inside \texttt{PRF\_msg\_ctr}. Each counter value produces +a fresh \texttt{R}, which is then evaluated via \texttt{H\_msg\_pors}. The +verifier only sees the final \texttt{R} that satisfied the condition and +recomputes the same digest deterministically. Expected grinding cost depends on the distribution of $|A|$; + +\subsection{Digest to Indices} +\label{subsec:pors_digest_to_indices} + +The \texttt{pors\_digest\_to\_indices} function converts a message digest and a grinding +counter into \texttt{k} \emph{distinct} indices in $\{0,1,...,t-1\}$. Distinctness is required by the Octopus +algorithm. We use rejection sampling over SHA256 XOF output. + +\begin{verbatim} + Algorithm: pors_digest_to_indices(digest) + Input: + digest: 32-byte value + Output: + indices: array of k distinct integers in [0, t-1] + xof_block: XOF output with size 256*blk + + 1. b ← roundup(log2(t)) // bits per candidate + 2. c ← floor(256 / b) // number of candidates per 256-bit block + 3. min_blk ← roundup(roundup(2^b * k / t)/c) // expected number of XOF blocks + 4. indices ← [] + 5. xof_out ← "" + 6. setTypeAndClear(ADRS, PORS_XOF) + 7. for blk from 0 to 2^32 - 1: + a. block ← H_xof_pors(ADRS, PKseed, digest, blk) + b. xof_out ← xof_out || block + c. for i from 0 to c - 1: + if |indices| == k: + break + candidate ← extract_bits(block, i * b, b) + if candidate < t and candidate not in indices: + indices ← indices || [candidate] + d. if |indices| == k and blk = min_blk - 1: + return (indices, xof_out[0:total_bits]) + 8. abort with error +\end{verbatim} + +where \texttt{total\_bits = (roundup(2\string^b * k / t) + margin) * b + offset + hsl} is the length of the XOF output (see Section~5.4.2). +In our case, for chosen parameters of SHRINCS-B and SHRINCS-L, and \texttt{margin = 7}, \texttt{offset = 16}, we get the follwing length: \texttt{total\_bits = (roundup(2\string^24 * 6 / 9245141) + 7) * 24 + 16 + 24 = 472} bits or 59 bytes. +For SHRINCS-B32 parameters we set \texttt{margin = 10}, \texttt{offset = 16}, and in the result is: \texttt{total\_bits = (roundup(2\string^17 * 11 / 109571) + 10) * 17 + 16 + 32 = 456} bits or 57 bytes. + +\paragraph{Note:} \texttt{extract\_bits(x, offset, length)} extracts length bits from byte string \texttt{x} starting at bit position offset, returned as an unsigned integer. Bits are numbered in big-endian order: bit \texttt{0} is the most significant bit of byte \texttt{x[0]}. + +\subsection{Octopus for the PORS+FP Tree} + +The Octopus algorithm~\cite{pors_octopus2018} computes the minimal set of authentication +nodes whose values are sufficient to recompute the tree root from \texttt{k} revealed +leaves. It is invoked during grinding (to check the size condition), during signing +(to determine which node values to include in the signature), and implicitly during +verification (to determine which nodes to read from the signature). Because all +three parties run the same algorithm on the same index set, the authentication node +ordering is unambiguous. + +For an imperfect, left-filled tree of \texttt{t} leaves we initialise the algorithm by +partitioning the \texttt{k} input indices by their actual depth in the tree, +then proceed identically to the standard Octopus formulation. + +\begin{verbatim} + Algorithm: pors_octopus(indices) + Input: + indices: array of k distinct leaf indices in [0, t-1], sorted ascending + Output: + A: array of authentication node (lvl, idx) pairs + + 1. h ← ceil(log2(t)) + 2. s ← t - 2^(h-1) + 3. I ← [] + 4. P ← [] + 5. A ← [] + 6. for each i in indices: + a. if i < 2 * s: + I ← I || [(0, i)] + b. else: + P ← P || [(1, i - s)] + 7. for current_lvl from 0 to h - 1: + a. next_P ← [] + b. i ← 0 + c. while i < |I|: + (lvl, idx) ← I[i] + sib_idx ← idx XOR 1 + if i + 1 < |I| and I[i+1][1] == sib_idx: + i ← i + 2 + else: + A ← A || [(current_lvl, sib_idx)] + i ← i + 1 + next_P ← next_P || [(current_lvl + 1, idx >> 1)] + + d. I ← next_P || P + e. P ← [] + f. if |I| == 1 and I[0][1] == 0: + break + 8. return A +\end{verbatim} + +\paragraph{Note:} When \texttt{t} is a power of~2, $s = t/2$ and all leaves are at depth +\texttt{h}, reducing step~4 to $I = \{(h, i) : i \in \texttt{indices}\}$ and recovering +the standard Octopus formulation. The algorithm performs no hash evaluations; its +cost is $O(k \log t)$ set operations. + +\subsection{Grinding} + +The \texttt{pors\_grind} function searches for a randomness \texttt{R} such that +the Octopus authentication set for the resulting index set has size at most +\texttt{m\_max}. The winning \texttt{R} is returned and included directly in the enclosing +SPHINCS signature; the verifier recomputes \texttt{digest} and \texttt{indices} +deterministically from \texttt{R} and \texttt{m}. + +\begin{verbatim} + Algorithm: pors_grind(m, SK.prf, PK.seed, PK.root, ADRS, opt_rand) + Input: + m: message + SK.prf: PRF key for randomness generation + PK.seed: public seed + PK.root: public key root + ADRS: tweak + opt_rand: optional randomness (n bytes) + Output: + indices: array of k integers in [0, t-1] + R: randomness satisfying the forced-pruning condition + digest: H_msg_pors output for that R + + 1. setTypeAndClear(ADRS, SL_H_MSG) + 2. for ctr from 0 to 2^32 - 1: + a. R ← PRF_msg_ctr(SK.prf, PK.seed, opt_rand, ctr, m) + b. digest ← H_msg_pors(ADRS, R, PK.seed, PK.root, m) + c. indices ← pors_digest_to_indices(digest) + d. A ← pors_octopus(indices) + e. if |A| <= m_max: + return (indices, R, digest) + 3. abort with error +\end{verbatim} + +\paragraph{Note:} Each iteration costs one \texttt{PRF\_msg\_ctr} evaluation plus +one \texttt{H\_msg\_pors} evaluation and dominates the Octopus authentication cost. The address type \texttt{SL\_H\_MSG} is set +at the start and remains fixed for the duration of grinding. + +\subsection{Secret Key Generation} + +The \texttt{pors\_SKgen} function derives the secret value for a single PORS leaf. + +\begin{verbatim} + Algorithm: pors_SKgen(SK.seed, PK.seed, ADRS, leaf_idx) + Input: + SK.seed: secret seed + PK.seed: public seed + ADRS: tweak + leaf_idx: leaf index in [0, t-1] + Output: + sk: secret value (n bytes) + + 1. setTypeAndClear(ADRS, PORS_PRF) + 2. setKeyPairAddress(ADRS, 0) + 3. setTreeIndex(ADRS, leaf_idx) + 4. sk ← PRF(PK.seed, ADRS, SK.seed) + 5. return sk +\end{verbatim} + +\subsection{TreeHash} + +The \texttt{pors\_treehash} function recursively computes the value associated with +node \texttt{(target\_height, idx)} in the single PORS+FP Merkle tree, where +\texttt{target\_height} is the height of the node counted upward from the leaves and \texttt{idx} +is its horizontal index at that height. The tree is left-filled with \texttt{t} leaves: letting $h = \lceil \log t \rceil$ and $s = t - 2^{h-1}$, the +leftmost $2s$ leaves sit at depth $h$ (or height~0) and the +remaining $t - 2s$ leaves sit at depth $h - 1$ (height~1). + +\begin{verbatim} + Algorithm: pors_treehash(SK.seed, PK.seed, ADRS, target_height, idx) + Input: + SK.seed, PK.seed: seeds + ADRS: tweak + target_height: height of output node + idx: horizontal index of the node at target_height + Output: + node: hash value (n bytes) + + 1. h ← ceil(log2(t)) + 2. s ← t - 2^(h-1) + 3. if target_height == 0: + a. sk ← pors_SKgen(SK.seed, PK.seed, ADRS, idx) + b. setTypeAndClear(ADRS, PORS_HASH) + c. setKeyPairAddress(ADRS, 0) + d. setTreeHeight(ADRS, 0) + e. setTreeIndex(ADRS, idx) + f. return Tw(PK.seed, ADRS, sk) + 4. if target_height == 1 and idx >= s: + a. leaf_idx ← s + idx + b. sk ← pors_SKgen(SK.seed, PK.seed, ADRS, idx) + c. setTypeAndClear(ADRS, PORS_HASH) + d. setKeyPairAddress(ADRS, 0) + e. setTreeHeight(ADRS, 0) + f. setTreeIndex(ADRS, idx) + g. return Tw(PK.seed, ADRS, sk) + 5. left ← pors_treehash_paper(SK.seed, PK.seed, ADRS, target_height - 1, 2*idx) + 6. right ← pors_treehash_paper(SK.seed, PK.seed, ADRS, target_height - 1, 2*idx + 1) + 7. setTypeAndClear(ADRS, PORS_TREE) + 8. setKeyPairAddress(ADRS, 0) + 9. setTreeHeight(ADRS, target_height) + 10. setTreeIndex(ADRS, idx) + 11. return Tw(PK.seed, ADRS, left || right) +\end{verbatim} + +\subsection{PORS+FP Authentication path} + +The \texttt{pors\_auth\_path} function generates the minimal set of authentication nodes required to verify all \texttt{k} revealed leaves simultaneously within the single PORS+FP Merkle tree. +Rather than authenticating each leaf independently, it employs the Octopus algorithm to identify shared internal nodes, which significantly shortens the resulting proof. +For every node coordinate \texttt{(lvl, idx)} returned by the Octopus algorithm, the function computes the required hash value by invoking \texttt{pors\_treehash}. +In this context, \texttt{lvl} specifies the height of the authentication node, and \texttt{idx} represents the leftmost leaf index of the subtree rooted at that node. +\begin{verbatim} + Algorithm: pors_auth_path(SK.seed, PK.seed, ADRS, indices) + Input: + SK.seed, PK.seed: seeds + ADRS: tweak + indices: array of k distinct leaf indices in [0, t-1], sorted ascending + Output: + auth: authentication path (sequence of n-byte hashes) + + 1. auth ← [] + 2. A ← pors_octopus(indices) + 3. for each (lvl, idx) in A: + a. node ← pors_treehash(SK.seed, PK.seed, ADRS, lvl, idx) + b. auth ← auth || node + 4. return auth +\end{verbatim} + +\subsection{Signature generation} +The \texttt{pors\_sign} function produces a PORS+FP signature. Due to forced pruning, the Octopus authentication set is guaranteed to contain at most $m_{\max}$ nodes. The signature format is: +\begin{center} + \texttt{R || sk\_\{i\_1\} || ... || sk\_\{i\_k\} || y\_\{a\_1\} || ... || y\_\{a\_{|A|}\}} +\end{center} +where $\{i_1,\dots,i_k\} = I$ are the \texttt{k} revealed leaf indices and $\{a_1,\dots,a_{|A|}\} = A$ are the Octopus authentication node labels (in the canonical Octopus traversal order). +We pad additional n-byte 0 strings to make the length of the signature be fixed with exactly size of \texttt{(k + m\_max) * n} bytes. + +\begin{verbatim} + Algorithm: pors_sign(m, SK.seed, SK.prf, PK.seed, PK.root, ADRS) + Input: + m: message + SK.seed: secret seed + SK.prf: PRF key + PK.seed: public seed + PK.root: public key root + ADRS: tweak + Output: + sig: PORS+FP signature + digest: the digest that satisfied the grinding condition + + 1. opt_rand ← random(n) or PK.seed + 2. (indices, R, digest) ← pors_grind(m, SK.prf, PK.seed, PK.root, ADRS, opt_rand) + 3. sig ← R + 4. for each i in indices (in increasing order): + a. sk_i ← pors_SKgen(SK.seed, PK.seed, ADRS, i) + b. sig ← sig || sk_i + 5. auth_path ← pors_auth_path(SK.seed, PK.seed, ADRS, indices) + 6. sig ← sig || auth_path + 7. for ctr from |auth_path| to m_max - 1: + a. sig ← sig || toByte(0, n) + 8. return (sig, digest) +\end{verbatim} + +\subsection{Public Key Recovery} +The \texttt{pors\_pkFromSig} function recovers the PORS+FP public key from a signature. +For each of the \texttt{k} revealed leaves, it hashes the secret key to obtain the leaf value, then runs the Octopus-guided root recomputation using the supplied authentication nodes. +Verification succeeds if and only if the recovered root matches \texttt{PK.root}. + +\begin{verbatim} + Algorithm: pors_pkFromSig(sig, indices, PK.seed, PK.root, ADRS) + Input: + sig: PORS+FP signature (R || secret leaves || auth nodes) + indices: PORS+FP k indices from digest + PK.seed: public seed + PK.root: public key root + ADRS: tweak (layer and tree_address set by caller) + Output: + pk: PORS public key + + 1. offset ← R_SIZE + 2. h ← ceil(log2(t)) + 3. s ← t - 2^(h-1) + 4. I ← [] + 5. P ← [] + 6. for i from 0 to k - 1: + a. sk_i ← sig[offset : offset + n] + b. offset ← offset + n + c. setTypeAndClear(ADRS, PORS_HASH) + d. setKeyPairAddress(ADRS, 0) + e. setTreeHeight(ADRS, 0) + f. setTreeIndex(ADRS, indices[i]) + g. val ← Tw(PK.seed, ADRS, sk_i) + h. if indices[i] < 2*s: + I ← I || [(0, indices[i], val)] + i. else: + P ← P || [(1, indices[i] - s, val)] + 7. for current_lvl from 0 to h - 1: + a. paired ← [false] * |I| + b. for i from 0 to |I| - 1: + if i + 1 < |I| and I[i+1][1] == I[i][1] XOR 1: + paired[i] ← true + paired[i + 1] ← true + c. next_P ← [] + d. for i from 0 to |I| - 1: + (lvl, idx, val) ← I[i] + if paired[i] and (idx AND 1) == 0: + continue + setTypeAndClear(ADRS, PORS_TREE) + setKeyPairAddress(ADRS, 0) + setTreeHeight(ADRS, current_lvl + 1) + setTreeIndex(ADRS, idx >> 1) + if paired[i]: + sib_val ← I[i-1][2] + parent_val ← Tw(PK.seed, ADRS, sib_val || val) + else: + auth_val ← sig[offset : offset + n] + offset ← offset + n + if (idx AND 1) == 0: + parent_val ← Tw(PK.seed, ADRS, val || auth_val) + else: + parent_val ← Tw(PK.seed, ADRS, auth_val || val) + + next_P ← next_P || [(current_lvl + 1, idx >> 1, parent_val)] + + e. I ← next_P || P + f. P ← [] + 8. root ← I[0][2] + 9. setTypeAndClear(ADRS, PORS_PK) + 10. pk ← Tw(PK.seed, ADRS, root) + 11. return pk +\end{verbatim} \ No newline at end of file diff --git a/docs/shrincs_spec/content/a-grinding-complexity.tex b/docs/shrincs_spec/content/a-grinding-complexity.tex index f9beb07..74ae877 100644 --- a/docs/shrincs_spec/content/a-grinding-complexity.tex +++ b/docs/shrincs_spec/content/a-grinding-complexity.tex @@ -1,7 +1,46 @@ \section{Expected Grinding Complexity} -This section analyzes the computational cost of the grinding operations in WOTS+C and FORS+C for both SHRINCS parameter sets. +This section analyzes the computational cost of the grinding operations in WOTS+C and PORS+FP for both SHRINCS parameter sets. \subsection{Probabilistic Analysis} +\subsubsection{Rejection Sampling} +Rejection sampling used to obtain $k$ distinct indices from the set $\{0,1,...,t-1\}$ in \texttt{pors\_digest\_to\_indices} function, where $t$ is an arbitrary number that is not the power of 2. +Inside of \texttt{pors\_digest\_to\_indices} we evaluate some number of SHA256 blocks, and sequentialy extract $b = \lceil\log_2{(t)}\rceil$ bits for every index, which we will call a \emph{candidate}. +A candidate that is distinct among previous candidates and is in $[0,t-1]$ is "good". +The extraction of next candidate is independent from previous draws index. Moreover, each candidate chosen uniformly from $\{0,1,...,2^b-1\}$, and rejetion smapling does not affect the uniformity of choosing a candidate. + +Let's define probability that extracted $i$-th index is distinct: +\begin{align} + p^{(i)}_c = \frac{t-(i-1)}{t} +\end{align} + +and define the probability that the obtained index will be in range $[0,t-1]$: +\begin{align} + p_r = \frac{t}{2^b} +\end{align} + +Then the probability of obtaining "good" i-th candidate is simply the product of $p^{(i)}_c\cdot p_r$. Then +\begin{align} + p_i = \frac{t-(i-1)}{t}\cdot \frac{t}{2^b} = \frac{t - (i-1)}{2^b} +\end{align} + +As we can see, each candidate draw is the Bernoulli trial with probability of success $p_i$. +Furthermore, each draw of good candidate obeys the geometric distribution with success probability $p_i$ and expected number of candidates $1/p_i$. +The expected total number of candidates to obtain $k$ "good" is: +\begin{align} + \mathbb{E}\left[\text{total candidates}\right] = \sum_{i=1}^{k} \frac{1}{p_i} = 2^b\sum_{i=1}^{k} \frac{1}{t - (i-1)} +\end{align} + +If $k \ll t$, then we can aproximate the expression (6) as: +\begin{align} + \mathbb{E}\left[\text{total candidates}\right] \approx 2^b\cdot\frac{k}{t} +\end{align} + +The expected number of hashes during rejection smapling is the following: + +\begin{align} + \mathbb{E}[\text{hashes}] = \left\lceil \frac{\mathbb{E}[\text{total candidates}]}{\left\lfloor \frac{256}{b}\right\rfloor}\right\rceil +\end{align} + \subsubsection{WOTS+C Grinding} The grinding operation searches for a counter $\texttt{ctr}$ such that the sum of base-$w$ digits equals the target $S_{wn}$. Each digest attempt produces $l$ independent random digits, each uniformly distributed in $[0, w-1]$. @@ -26,20 +65,23 @@ \subsubsection{WOTS+C Grinding} The probability of hitting exactly $S_{wn}$ can be approximated as: \begin{align} - P\left(\sum d_i = S_{wn}\right) \approx \frac{1}{\sigma\sqrt{2\pi}} \cdot \exp\left(-\frac{(S_{wn} - \mu)^2}{2\sigma^2}\right) + \text{Pr}\left(\sum d_i = S_{wn}\right) \approx \frac{1}{\sigma\sqrt{2\pi}} \cdot \exp\left(-\frac{(S_{wn} - \mu)^2}{2\sigma^2}\right) \end{align} Expected grinding iterations: \begin{align} - \mathbb{E}[\text{iterations}] = \frac{1}{P(\sum d_i = S_{wn})} + \mathbb{E}[\text{iterations}] = \frac{1}{\text{Pr}(\sum d_i = S_{wn})} \end{align} -\subsubsection{FORS+C Grinding} -The grinding operation searches for $\text{indices}[k-1] = 0$, meaning the last $a$ bits of the digest must all be zero. This is a deterministic bit condition with: +\subsubsection{PORS+FP Grinding} +The grinding operation searches for $|A| \le m_{\max}$. Let $|A| = S(t,k)$. Then the expected number of iterations during grinding is the following: \begin{align} - P(\text{indices}[k-1] = 0) &= \frac{1}{2^a} \\ - \mathbb{E}[\text{iterations}] &= 2^a + \text{Pr}(|A| \le m_{\max}) &= \sum_{m=1}^{m_{\max}} \text{Pr}(S(t,k) = m)\\ + \mathbb{E}[\text{iterations}] &= \frac{1}{\text{Pr}\left(|A| \le m_{\max}\right)} \end{align} +where $\text{Pr}(S(t,k) = m)$ is the probability that the size of authentication set is exactly $m$. +The analytical expression for this probability descibed and derived in~\cite{pors_fp2025}. +We omit to describe the formula directly, due to the complex derivation. \subsection{Concrete Parameters} @@ -59,13 +101,13 @@ \subsubsection{SHRINCS-B ($w=256$, $l=16$, $S_{wn}=2040$)} \paragraph{Approximate probability:} \begin{align} - P(\sum d_i = 2040) \approx \frac{1}{295.6 \cdot \sqrt{2\pi}} \approx \frac{1}{741} + \text{Pr}\left(\sum d_i = 2040\right) \approx \frac{1}{295.6 \cdot \sqrt{2\pi}} \approx \frac{1}{741} \end{align} \paragraph{Expected grinding cost:} \begin{itemize} \item $\mathbb{E}[\text{iterations}] \approx 741$ iterations - \item Per iteration: 2 SHA-256 compression (Phase 2 operates on 68-byte input + \item Per iteration: 2 SHA-256 compression (Phase 2 operates on 68-byte input) \item Total: $\approx 741$ SHA-256 compressions \end{itemize} @@ -92,7 +134,7 @@ \subsubsection{SHRINCS-L ($w=4$, $l=64$, $S_{wn}=140$)} \paragraph{Approximate probability:} \begin{align} - P(\sum d_i = 140) &\approx \frac{1}{8.94\sqrt{2\pi}} \cdot \exp\left(-\frac{4.92^2}{2}\right) \nonumber \\ + \text{Pr}\left(\sum d_i = 140\right) &\approx \frac{1}{8.94\sqrt{2\pi}} \cdot \exp\left(-\frac{4.92^2}{2}\right) \nonumber \\ &\approx \frac{1}{22.4} \cdot \exp(-12.1) \nonumber \\ &\approx \frac{1}{22.4} \cdot 5.5 \times 10^{-6} \nonumber \\ &\approx 2.45 \times 10^{-7} @@ -111,23 +153,48 @@ \subsubsection{SHRINCS-L ($w=4$, $l=64$, $S_{wn}=140$)} \item Expected grinding time: 2-4 seconds per signature \end{itemize} -\subsection{FORS+C Grinding Complexity} -Both SHRINCS-B and SHRINCS-L use identical FORS+C parameters: +\subsection{PORS+FP Grinding Complexity} +Both SHRINCS-B and SHRINCS-L use identical PORS+FP parameters: \begin{itemize} \item $k = 6$ trees - \item $a = 22$ (tree height) + \item $t = 9245141$ (number of leaves) + \item $m_{\max} = 91$ \end{itemize} -Grinding condition: $\text{indices}[k-1] = 0$ (last tree index must be zero) +And for SHRINCS-B32: + +\begin{itemize} + \item $k = 11$ trees + \item $t = 109571$ (number of leaves) + \item $m_{\max} = 111$ +\end{itemize} + +Grinding condition: $|A| \le m_{\max}$ \paragraph{Expected grinding cost:} + +For SHRINCS-B/L: + +\begin{itemize} + \item $\mathbb{E}[\text{iterations}] = 2526130 \approx 2.5$m iterations + \item Per iteration: 1 HMAC-SHA-256 + $(1 + \mathbb{E}[\text{hashes}])$ SHA-256 = 1 HMAC-SHA-256 + 3 SHA-256 +\end{itemize} + +\paragraph{Practical performance:} +\begin{itemize} + \item $\sim$1-2 million SHA-256/sec + \item Expected grinding time: 5-10 seconds per stateless signature +\end{itemize} + +For SHRINCS-B32: + \begin{itemize} - \item $\mathbb{E}[\text{iterations}] = 2^a = 2^{22} = 4.1$m iterations - \item Per iteration: 1 HMAC-SHA-256 + 1 SHA-256 + \item $\mathbb{E}[\text{iterations}] = 17718$ iterations + \item Per iteration: 1 HMAC-SHA-256 + $(1 + \mathbb{E}[\text{hashes}])$ SHA-256 = 1 HMAC-SHA-256 + 3 SHA-256 \end{itemize} \paragraph{Practical performance:} \begin{itemize} \item $\sim$1-2 million SHA-256/sec - \item Expected grinding time: 4-8 seconds per stateless signature + \item Expected grinding time: 35 milliseconds per stateless signature \end{itemize} \ No newline at end of file diff --git a/docs/shrincs_spec/main.bib b/docs/shrincs_spec/main.bib index cd39c0f..830e20c 100644 --- a/docs/shrincs_spec/main.bib +++ b/docs/shrincs_spec/main.bib @@ -128,7 +128,25 @@ @misc{wotsc_sphincsc2022 url = {https://eprint.iacr.org/2022/778} } +@misc{pors_fp2025, + author = {Mehdi Abri and Jonathan Katz}, + title = {Shorter Hash-Based Signatures Using Forced Pruning}, + howpublished = {Cryptology {ePrint} Archive, Paper 2025/2069}, + year = {2025}, + url = {https://eprint.iacr.org/2025/2069} +} +@inproceedings{pors_octopus2018, + author = {Jean-Philippe Aumasson and Guillaume Endignoux}, + title = {Improving Stateless Hash-Based Signatures}, + booktitle = {Topics in Cryptology -- {CT-RSA} 2018}, + series = {Lecture Notes in Computer Science}, + volume = {10808}, + pages = {219--242}, + publisher = {Springer}, + year = {2018}, + doi = {10.1007/978-3-319-76953-0\_12} +} https://github.com/eyalr0/sphincsplusc \ No newline at end of file diff --git a/docs/shrincs_spec/main.pdf b/docs/shrincs_spec/main.pdf index 8c2c453..f08087d 100644 Binary files a/docs/shrincs_spec/main.pdf and b/docs/shrincs_spec/main.pdf differ diff --git a/docs/shrincs_spec/main.tex b/docs/shrincs_spec/main.tex index 23d2e7f..050c008 100644 --- a/docs/shrincs_spec/main.tex +++ b/docs/shrincs_spec/main.tex @@ -95,7 +95,7 @@ \input{content/8-uxmss.tex} % --- Section 9: FORS+C --- -\input{content/9-fors.tex} +\input{content/9-pors.tex} % --- Section 10: SHRINCS --- \input{content/10-shrincs.tex}