Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/shrincs_spec/archive_fors/1-motivation.tex
Original file line number Diff line number Diff line change
@@ -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}
255 changes: 255 additions & 0 deletions docs/shrincs_spec/archive_fors/10-shrincs.tex
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should derive ht_offset from the same C := roundup(k * 2^b / t) + 7 formula

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}
Loading