(This blog is under construction. Expect incomplete explanation.)
At the heart of QRL, as of this writing, we have the XMSS (eXtended Merkle Signature Scheme) which is used to sign and verify the authenticity of transactions in the
QRL Blockchain. This is supposedly based on the C implementation
xmss-reference which is
based on the document
RFC 8391. Now, in spite of the fact that this signature scheme have been heavily used
and became inseparable for its purpose in the blockchain, it has been lacking
documentation (apart from the RFC 8391) on what is actually happening down the
hood on this QRL port of XMSS.
In the future, QRL might be moving to a different more smaller but quantum
resistant signature scheme but nevertheless, I believe reverse engineering
this piece of fascinating signature scheme might be worth a try.
A brief history of hash based one time signatures
The Leslie Lamport signature scheme
In 1979, Leslie Lamport (yes, that same guy who created \( \LaTeX{} \))
published a paper titled, "Constructing Digital Signatures from a One Way Function" where given a one way hash function \( F() \), a message \( m \), and a digest function \( G: M \rightarrow (1,\ldots,40)^{20} \), which generates a sequence
containing exactly 20 elements of values ranging from 1 to 40, a signature
can be constructed.
As an example, let's assume that our arbitrary message produces this digest
containing 20 elements:
\[ i = G(m) = (14,24,20,28,39,22,36,35,26,4,1,11,38,40,9,15,16,29,8,27)
\].
Given our keys is \( k = (k_0,\ldots,k_{39})\), our signature is \( \sigma
= (k_{i_0},\ldots,k_{i_{19}})\), which is:
\[ \sigma =
(k_{14},k_{24},k_{20},k_{28},k_{39},k_{22},k_{36},k_{35},k_{26},k_{4},k_{1},k_{11},k_{38},k_{40},k_{9},k_{15},k_{16},k_{29},k_{8},k_{27})
\]
Where \( k_n \) is used to denote the nth element in the sequence \( k
\).
Here, given the publicly available, \( \alpha = (F(k_0),\ldots,F(k_{39}))
\) the receiver can verify the signature is valid if \( F(\sigma_j) =
\alpha_{i_j} \) for \( j = 0,\ldots,19 \). Breaking down the notation "\(
\alpha_{i_j} \)", \( i_j \) is used to access the jth element in the
sequence \(i\) which is then used to access the element in the sequence
\(\alpha\), so that when \( j=5 \), \(i_j = 22 \) and the 22th element
in \( \alpha \) is \( F(k_{22}) \), thus \(a_{i_5} = F(k_{22}) \).
The Lamport-Diffie One Time Signature
On that same year, Ralph Merkle published a paper titled "A certified digital signature" where it discusses the Lamport-Diffie One Time Signature and
expanded the previous paper on bits. The idea is if \( y=F(x) \) is
the result of applying the hash function \( F \) on \( x \), then only the
originator of \( y \) can only know \(x\). If everyone knows \( y \) is from
the originator \( O \), then \( O \) revealing \( x \) must propagate some
information since only \( O \) must knows \( x \). Given \( x \),
everyone can verify this comes from \( O \) by computing \(F(x)\) and
comparing it with the publicly available \( y \) which comes from \( O \),
thus is the basis of this system.
Here, for Alice to sign a 4-bit message to Bob, Alice generates exactly 4
random secret keys.\((k_0, k_1, k_2, k_3) \) and publishes \( (F(k_0),
F(k_1), F(k_2), F(k_3)) \). To sign the message \(1101\), Alice reveals \(
(k_0, k_1, k_3) \) to sign the 1's bit. Here, we do not reveal \( k_2 \) and thus whatever key not revealed is automatically understood as
a 0 bit. So far so good but this suffers from the defect that Bob (the
receiver) can choose to disregard all hashes and it would still be a valid
signature. Bob can deny ever receiving all k's (turning 1's into 0's) even though he did.
This defect was foresaw and on that same paper, a solution was proposed,
quoting (emphasis mine):
Lamport and Diffie overcame this problem by signing a new message \( m' \),
which is exactly twice as long as \( m \) and is computed by concatenating
\( m \) with the bitwise complement of \( M \). That is, each bit \( M \) in
the original message is represented by two bits, \( m_i \) and the
complement of \( m_i \) in the new message \( m' \). Clearly, one or the
other bit must be a 0. To alter the message, Bob would have to turn a 0 into
a 1, something he cannot do.
So we would have to double our keys. Instead of signing a message \( 1101
\), we would have to create a new message with its bitwise complement
appended like so \( 1101,0010 \) (where the comma is used to clarify
the division of the message and its complement). And for each 1's bit in
this new message, I reveal \( ( k_0, k_1, k_3, k_6 )\).
The above algorithm would be the same as what's normally demonstrated in
the examples from the internet. That's generating
4 secret keys \( S_{0,0}\ , \ldots , S_{0,3} \)
to sign the 0's bits and another 4 secret keys, \( S_{1,0}\ , \ldots ,
S_{1,3} \) to sign the 1's bits .
\[ S_0 = ( S_{0,0}, S_{0,1}, S_{0,2}, S_{0,3} ) \]
\[ S_1 = ( S_{1,0}, S_{1,1}, S_{1,2}, S_{1,3} ) \]
Each of those secret keys is then hashed individually with \( F \) (a one
way hash function) to produce \( P_{0,0}, \ldots , P_{0,3} \) and \(
P_{1,0}, \ldots , P_{1,3} \) such that \( P_{n,m} = F(S_{n,m}) \) which I publish.
\[ P_0 = ( F(s) \, | \, s \in S_0 ) \]
\[ P_1 = ( F(s) \, | \, s \in S_1 ) \]
Then depending on the bit \( m_i \) (the ith bit in message \( m\
\)), I choose to reveal \( S_{0,i} \) if \( m_i \) is 0, or \( S_{1,i} \) if
\( M_i \) is 1.
\[ \sigma = ( S_{n,i} \, | \, n = m_i \land i=0,1,2,3)\]
The Winternitz Improvement
Shortly before publication, Robert Winternitz of Stanford Mathematics
Department suggested a substantial improvement that trades space for time,
reducing the size of the signed message ultimately. Instead of publishing \(
F(x) \), we publish \( F^k(x) \) where the notation \( F^k(x)\) is used to
apply the function \( F \) on \( x \), \( k \) times:
\[ x \xrightarrow{F} F(x) \xrightarrow{F} F^2(x) \xrightarrow{F}
F^3(x)\]
is the same as:
\[ x \xrightarrow{F} F(x) \xrightarrow{F} F(F(x)) \xrightarrow{F}
F(F(F(x))) \].
Instead of publishing \( y=F(x) \), we publish \( y=F^{16}(x) \). This
gives us an ability to sign 4 more bits of data. To sign the message
\(0000\) we reveal \(x\). To sign the message \(1111\) (decimal 15), we
reveal \( F^{15}(x)\). In theory, we can pick to publish \(y=F^{256}(x)\) to
sign a total of 8-bits instead of \(F^{16}(x)\), thus trading more space for
time, but we'll stick to signing 16 values (0-15) in the next proceeding
sections. This 16 is called the Winternitz parameter (\(
w=16\)).
Notice that if \( F^6(x) \) is revealed, anyone can claim \( F^7() \) so we
would have to add a checksum at the end which we would have to also
sign.
A Toy Merkle Signature Scheme
We're pretty much ready to create our own simplest Merkle Tree Signature
Scheme, a "toy" that is, but its simplicity stills captures the essence of
the original XMSS without going through hell of prerequisites.The tree we'll
be building has a height of 1, giving us the ability to sign 2 distinct
messages.
Where \( l_0 \,\Vert\, l_1\), concatenates \( l_0 \) and
\(l_1\). And down the bottom of \( l_0 \) (and other leafs),
there's an another tree called an, "L-tree" which kind of compresses the
Winternitz public keys (though it's unrecoverable).
And down the bottom again of this \( p_0,\ldots,p_{63} \), are the actual
secret keys, such that \( p_n = F^{16}(s_n) \), where \(s_n\) is a random
256-bit secret key.
Since we're currently at \( l0 \), our ots index is 0. How many secret keys
we'll need is explained below:
Since we'll be signing each 4 bits in the 256 bit message digest, we'll have
to generate \( 256/4 = 64 \) secret keys, each are also 32 bytes long. If
you recall from the previous section, I claimed that we'll need to add a
checksum at the end to prevent an attacker from claiming \( F^{k+1}(x) \)
given \( F^k(x) \), but it turns out if the hash of message is instead
signed, it should still be computationally hard to find a new message hash
that is legible for this kind of attack. I might be wrong, so if you want to
be extra carefull, double the keys and sign the bitwise complement of the
message digest.
Signing
Our signature is a 3 tuple, the ots_index, xmss_auths and wots_keys.
We'll keep it simple and sign the message "hello". This message has a sha256
sum of 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824.
We'll append 0x00 (decimal 0), the current ots index and hash it again
giving 0x586f7f160c29d493245b6ffad87d7a1b2654a6ed95d7cd665ea27056b18e1298.
The base 16 of this hash has 64 elements and is just:
\[ b = (5, 8, 6, 15, \ldots , 8) \] and so on.
The checksum is just the sum of all elements in b which is jdjdjsj and the
base 16 of this checksum is:
\[ c = ( ) \]
The message we'll be signing in the end is:
\[ b' =
For \( i = 0,\ldots,63 \),
Comments
Post a Comment