Subject: ssh-ed25519 implementations

From: Mark Baushke <[email-masked]>
Date: Wed, 10 May 2017 16:18:29 +0000

Hi,

Eric Rescorla <[email-masked]> has brought to my attention that in
https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-04 it is
currently specifying the SSH encoding of secrets on the wire using the
mpint process as described in section 5 of [RFC4251] while RFC 7748
describes using a little-endian format:

  GF(2^448 - 2^224 - 1) and are encoded as an array of bytes, u,
  in little-endian order such that u[0] + 256*u[1] + 256^2*u[2] + ... +

This seems to be what is being implemeneted for
[email-masked], so I should make
an explicit note of this in the draft.

However, I am unaware of any curve448-sha512 implementations at
present and would like consensus that it should also follow the mpint
method rather than the RFC 7748 method.

Please reply to [email-masked] with your opinions.

        Thank you,
        -- Mark

---

From: "Mark D. Baushke" <[email-masked]>
Date: Wed, 10 May 2017 09:57:26 -0700

Eric Rescorla <[email-masked]> writes:

> On Wed, May 10, 2017 at 9:18 AM, Mark Baushke <[email-masked]> wrote:
> 
> > Hi,
> >
> Thanks. To be clear, I'm not saying this is the wrong thing in the draft
> (though I do think it's kind of an unfortunate outcome). I just think it's
> critically important to be clear.

Thank you for the clarification. I agree it is unfortunate.

> I tend to think the 7748 method, but all the options are pretty terrible
> here

I am agnostic on the best way to deal with curve448 for SSH.
I agree all options are unlpleasant.

RFC 8032 vs implementations for ssh-ed25519 will have the same issue.

So, if https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519
is updated to point to RFC 8032, it will likely need to worry about
the mpint vs little-endian encoding issue as well. :-(

	-- Mark

---

From: Eric Rescorla <[email-masked]>
Date: Wed, 10 May 2017 09:20:37 -0700

-Ekr

---

From: Ron Frederick <[email-masked]>
Date: Wed, 10 May 2017 19:49:29 -0700

> Please reply to [email-masked] with your opinions.

I have implemented for ssh-ed25519 public keys and certificates in AsyncSSH, and my implementation currently uses opaque byte strings taken from the implementation of curve25519 in libsodium when encoding public and private keys. These byte strings are encoded as SSH ‘string’ values (4-byte big-endian string length followed by an array of bytes of that length). The public key is a single string value and the private key is the concatenation of two string values (public key followed by private key, each with its own preceding 4-byte length value). This implementation interoperates with OpenSSH.

More details can be found in the Internet Draft at:

https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-00 <https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-00>

This refers to:

https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 <https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03>

I have also implemented [email-masked] <mailto:[email-masked]> key exchange as documented at:

https://git.libssh.org/projects/libssh.git/plain/doc/[email-masked] <https://git.libssh.org/projects/libssh.git/plain/doc/[email-masked]>

OpenSSH also implements this, and makes it available under both this name and more recently as just “curve25519-sha256”.

Here, the public key values exchanged in messages like KEX_ECDH_INIT and KEX_ECDH_REPLY are opaque byte strings similar to the above. As discussed in https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-04 <https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-04>, the final computed shared secret value is converted to an integer by taking the 32-byte point obtained by scalar multiplication and treating the bytes as a bigendian (network byte order) value, but this value is never directly sent on the wire, so I’m not sure the “mpint" encoding ever applies to it. The only values on the wire are the public keys and signature values, all of which are encoded as strings.
-- 
Ron Frederick
[email-masked]

---

From: Ron Frederick <[email-masked]>
Date: Wed, 10 May 2017 19:49:29 -0700

This refers to:

---

From: "Mark D. Baushke" <[email-masked]>
Date: Thu, 11 May 2017 06:32:32 -0700

Hi Eric & Ron & Brian & Simon,

Given input from folks so far, I think it would be better if both
Curve25519 and Curve448 continued to use the "mpint" format for K when
generating a hash even though this is not what RFC7748 suggests.

Would it make sense to include the following text to the end of section
2.1 of https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-04 ?

    When performing the X25519 or X448 operations, the integer values
    there will be encoded into byte strings by doing a fix-length
    unsigned litle-endian conversion, per [RFC7748]. It is only later
    when these byte strings are then passed to the ECDH code in SSH that
    the bytes are re-interpreted as a fixed-length unsigned big-endian
    integer value K, and then later that K value is encoded as a
    variable-length signed "mpint" before being fed to the hash
    algorithm used for key generation.

to help clarify the differences between RFC7748 and what is happening in
SSH?

Much of this text is borrowed from what Ron Frederick has written to me,
any remaining confusion is my fault.

I think that the above text should help clear up the confusion that Eric
noted in this section of code.

If there are no problems with this text, I will release the -05 draft
with it.

	Thank you,
	-- Mark

---

From: "Mark D. Baushke" <[email-masked]>
Date: Thu, 11 May 2017 07:39:11 -0700

Hi Setphan,

I think I will use this text...

    To clarify a corner-case in this conversion, when X is encoded as an
    mpint K, in order to calculate the exchange hash, it may vary as
    follows:

        Trim all leading zero-bytes of X. If X is all zero-bytes,
	then the key exchange must fail.

        If the high bit of X is set, the mpint format requires
	a zero byte to be prepended.

        The length of the encoded K may not be the same as the
	original length of X due to trimming or prepending a
	zero-byte as needed for "mpint" format.

I will also add your suggested pseudo code.

	Thank you,
	-- Mark

---

From: Daniel Migault <[email-masked]>
Date: Thu, 11 May 2017 14:05:15 +0000

I believe it is nicer to have Curve25519 and Curve448 should be coherent.  The text is clarifying.

-----Original Message-----
From: [email-masked] [mailto:[email-masked]] On Behalf Of Mark D. Baushke
Sent: Thursday, May 11, 2017 9:33 AM
To: Eric Rescorla <[email-masked]>; Ron Frederick <[email-masked]>; Brian Smith <[email-masked]>; denis bider <[email-masked]>; Simon Tatham <[email-masked]>
Cc: [email-masked]; [email-masked]
Subject: Re: ssh-ed25519 implementations 

Given input from folks so far, I think it would be better if both
Curve25519 and Curve448 continued to use the "mpint" format for K when generating a hash even though this is not what RFC7748 suggests.

to help clarify the differences between RFC7748 and what is happening in SSH?

Much of this text is borrowed from what Ron Frederick has written to me, any remaining confusion is my fault.

I think that the above text should help clear up the confusion that Eric noted in this section of code.

If there are no problems with this text, I will release the -05 draft with it.

	Thank you,
	-- Mark

---

From: Stefan Bühler <[email-masked]>
Date: Thu, 11 May 2017 15:15:31 +0200

Hi,

> an explicit note of this in the draft.

While we are on the topic of converting the shared secret bytes X
generated by Curve* to an mpint, I'd like to point out that the
draft-ietf-curdle-ssh-curves-04 is not clear regarding leading zeroes:

>    If X has leading zero bytes, the mpint format requires such bytes
>    to be skipped.  In this case, the length of the encoded K will be
>    smaller.

If X has one leading zero byte, and the highest bit of the second byte
is set, K will be exactly X, not shorter.

Maybe it would be better to describe an algorithm for the conversion, like:

- trim all leading zero bytes
- at least one byte must remain ("Clients and servers MUST fail the key
  exchange if [...], or if the derived shared secret only consists of
  zero bits.")
- if the highest bit of the first byte of the remaining string is set,
  prepend one zero byte

Or as pseudo code:

    k := x;
    while (k.length() > 0 && k[0] == 0) k = k[1:];
    assert(k.length() > 0);
    if 0 != (k[0] & 0x80) k = '\0' .. k;

cheers,
Stefan

---

From: Ron Frederick <[email-masked]>
Date: Thu, 11 May 2017 19:27:30 -0700

Hi Mark,

> If there are no problems with this text, I will release the -05 draft

I just took a look at the updated draft. Here’s a copy of the text in section 2.1 and some inline suggestions:

2.1 <https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-05#section-2.1>.  Shared Secret Encoding

   The following step differs from [RFC5656 <https://tools.ietf.org/html/rfc5656>], which uses a different
   conversion.  This is not intended to modify that text generally, but
   only to be applicable to the scope of the mechanism described in this
   document.

   The shared secret, K, is defined in [RFC4253 <https://tools.ietf.org/html/rfc4253>] as a multiple precision
   integer (mpint).  Curve25519/448 outputs a binary string X, which is

[Ron] RFC 4253 actually says that K is “encoded as an mpint”. I’d suggest saying “The shared secret, K, is defined in [RFC4253] as an integer."

   the 32 or 56 byte point obtained by scalar multiplication of the
   other side's public key and the local private key scalar.  The 32 or
   56 bytes of X are converted into K by interpreting the bytes as an
   unsigned fixed-length integer encoded in network byte order.  This
   conversion follows the normal "mpint" process as described in section <https://tools.ietf.org/html/rfc4251#section-5>
   5 of [RFC4251] <https://tools.ietf.org/html/rfc4251#section-5>.

[Ron] There are actually two conversions here, from the byte string X to the integer K, and then from K back to a byte string to be fed to the hash used for key generation. This text makes it sound like the conversion from X to K should follow the “mpint” conversion, but that’s not the case. The sentence about how to convert X into the integer K looks good, but I would drop the last sentence here and begin a new paragraph which discusses the second conversion from the integer K back to a byte string for insertion into the hash, such as:

The integer K is then fed along with other data to the key exchange method’s hash function to generate encryption keys. During this process, [RFC4253] specifies that K should be encoded as an “mpint” as described in section 5 of [RFC4251]. This conversion may result in a value which varies from the fixed-length byte string X as follows:

This new paragraph can replace the next paragraph below.

   o  Trim all leading zero-bytes of X.  If X is all zero-bytes, then
      the key exchange MUST fail.

[Ron] Are you sure this is necessary? I don’t see any mention of this restriction in RFC 4253. If the integer value of K is 0, it can still be encoded as a valid mpInt.

   o  If the high bit of X is set, the mpint format requires a zero byte
      to be prepended.

   o  The length of the encoded K may not be the same as the original
      length of X due to trimming or prepending zero-bytes as needed for
      "mpint" format.

[Ron] Actually, after doing all of this, the mpint conversion also requires the that resulting byte string be preceded by a 4-byte big-endian length value. So, even if the X value has no bytes removed or prepended, it will be 4 bytes longer than it was originally. There’s no mention of this length here.

   Or, as pseudo code:

                 k := x;

                                 Figure 1

   When performing the X25519 or X448 operations, the integer values
   there will be encoded into byte strings by doing a fix-length
   unsigned litle-endian conversion, per [RFC7748 <https://tools.ietf.org/html/rfc7748>].  It is only later
   when these byte strings are then passed to the ECDH code in SSH that
   the bytes are re-interpreted as a fixed-length unsigned big-endian
   integer value K, and then later that K value is encoded as a
   variable-length signed "mpint" before being fed to the hash algorithm
   used for key generation.

[Ron] With the changes I have proposed above, I’m not sure this last paragraph is needed. Alternately, I’d think about working some of this text into the two paragraphs above that discuss the conversion from X to K and then from K to an mpint. Also, “fix-length” here should be “fixed-length”. I think it is better to cover this point before diving into the details of how the “mpint” conversion is different from the original X byte string.
-- 
Ron Frederick
[email-masked]

---

From: "Mark D. Baushke" <[email-masked]>
Date: Thu, 11 May 2017 22:21:52 -0700

Hi Ron,

> section 2.1 and some inline suggestions:

I am always interested in making the text better.

> 2.1 Shared Secret Encoding
> 
>    The following step differs from [RFC5656], which uses a different
>    The shared secret, K, is defined in [RFC4253] as a multiple precision
> [Ron] RFC 4253 actually says that K is “encoded as an mpint”. I’d
> suggest saying “The shared secret, K, is defined in [RFC4253] as an
> integer."

Section 8 of RFC 5253 actually says "It computes K = e^y mod p" which
means that K is a multiple precision integer.

As an aside, it really should end up be larger than a simple integer.
Not stated, but the math works best if it is on the order of at least
half the number of bits of q such that g^(xy) is greater than p which
lets the mod operation do something useful.

>    conversion follows the normal "mpint" process as described in section
>    5 of [RFC4251].
> 
> [Ron] There are actually two conversions here, from the byte string X
> to the integer K, and then from K back to a byte string to be fed to
> the hash used for key generation. This text makes it sound like the
> conversion from X to K should follow the “mpint” conversion, but
> that’s not the case. 

True, that is confusing and should be fixed.

> The sentence about how to convert X into the integer K looks good, but
> I would drop the last sentence here and begin a new paragraph which
> discusses the second conversion from the integer K back to a byte
> string for insertion into the hash, such as:
> 
>   The integer K is then fed along with other data to the key exchange
>   method’s hash function to generate encryption keys. During this
>   process, [RFC4253] specifies that K should be encoded as an “mpint”
>   as described in section 5 of [RFC4251]. This conversion may result
>   in a value which varies from the fixed-length byte string X as
>   follows:

> [Ron] Are you sure this is necessary? 

Yes.

> I don’t see any mention of this restriction in RFC 4253. If the
> integer value of K is 0, it can still be encoded as a valid mpInt.

It is not in RFC4253, it is in RFC4251 where it says:

   mpint

      Represents multiple precision integers in two's complement format,
      stored as a string, 8 bits per byte, MSB first.  Negative numbers
      have the value 1 as the most significant bit of the first byte of
      the data partition.  If the most significant bit would be set for
      a positive number, the number MUST be preceded by a zero byte.
      Unnecessary leading bytes with the value 0 or 255 MUST NOT be
      included.  The value zero MUST be stored as a string with zero
      bytes of data.

The check for X is all zero-bytes needs to be done per RFC7748:

   Both now share K = X25519(a, X25519(b, 9)) = X25519(b, X25519(a, 9))
   as a shared secret.  Both MAY check, without leaking extra
   information about the value of K, whether K is the all-zero value and
   abort if so (see below).
   ...elided...
   The check for the all-zero value results from the fact that the
   X25519 function produces that value if it operates on an input
   corresponding to a point with small order, where the order divides
   the cofactor of the curve (see Section 7).  The check may be
   performed by ORing all the bytes together and checking whether the
   result is zero, as this eliminates standard side-channels in software
   implementations.

I suppose that could be done first in linear time by oring all of the
bytes without leaking side channel information...

        k := x
        zbcheck := 0
	for byte in k: zbcheck |= k[byte];
        assert(zbcheck > 0);
        while (k.length() > 0 &amp;&amp; k[0] == 0) k := k[1:];
        if 0 != (k[0] &amp; 0x80) k := '\0' .. k;    

which is kind of ugly...

> [Ron] Actually, after doing all of this, the mpint conversion also
> requires the that resulting byte string be preceded by a 4-byte
> big-endian length value. So, even if the X value has no bytes removed
> or prepended, it will be 4 bytes longer than it was originally.
> There’s no mention of this length here.

True, because the prepended length is not a part of the hash to the best
of my recollection. Am I mistaken?

>                  if 0 != (k[0] & 0x80) k = '\0' .. k;

Hmmm... should the above use := for assignments everywhere like this?

              k := x;
              while (k.length() > 0 &amp;&amp; k[0] == 0) k := k[1:];
              assert(k.length() > 0);
              if 0 != (k[0] &amp; 0x80) k := '\0' .. k;

> [Ron] With the changes I have proposed above, I’m not sure this last
> paragraph is needed. Alternately, I’d think about working some of this
> text into the two paragraphs above that discuss the conversion from X
> to K and then from K to an mpint. 

I am also not sure, but I have left it in for now. Please see my 'diff'
below for the changes to see if it looks better or worse to you.

> Also, “fix-length” here should be “fixed-length”.

Sure, I can make that change.

> I think it is better to cover this point before diving into the
> details of how the “mpint” conversion is different from the original X
> byte string.

Hmmm.... here is how I have updated the text:

--- draft-ietf-curdle-ssh-curves-05.xml	2017-05-11 11:58:15.000000000 -0700
+++ draft-ietf-curdle-ssh-curves-06.xml	2017-05-11 22:18:37.000000000 -0700
@@ -169,8 +169,15 @@
           key and the local private key scalar.  The 32 or 56 bytes of
           X are converted into K by interpreting the bytes as an
           unsigned fixed-length integer encoded in network byte order.
+        </t>
+
+        <t>
+          The integer K is then fed along with other data to the key
+          exchange method's hash function to generate encryption keys.
           This conversion follows the normal "mpint" process as
-          described in section 5 of <xref target="RFC4251"/>.
+          described in section 5 of <xref target="RFC4251"/> which
+          requires that unnecessary leading bytes with the value 0
+          MUST NOT be included.
         </t>
 
         <t>
@@ -181,32 +188,36 @@
           <list style="symbols">
 
             <t>
-              Trim all leading zero-bytes of X. If X is all
-              zero-bytes, then the key exchange MUST fail.
+              Trim all leading zero-bytes of X, as required in section
+              5 of <xref target="RFC4251"/>. If X is all
+              zero-bytes, then the key exchange MUST fail as required
+              in section 6 of <xref target="RFC7748"/>.
             </t>
 
             <t>
-              If the high bit of X is set, the mpint format requires a
-              zero byte to be prepended.
+              Given X is a positive, if the MSB of X is set, then
+              the "mpint" format requires a zero-byte to be prepended.
             </t>
 
             <t>
-              The length of the encoded K may not be the same as the
-              original length of X due to trimming or prepending
-              zero-bytes as needed for "mpint" format.
+              The length of the "mpint" form of K may not be the same
+              as the original length of X due to trimming or
+              prepending zero-byte values as needed for "mpint"
+              format.
             </t>
 
           </list>
 
           <figure anchor="pseudo.code">
             <preamble>
-              Or, as pseudo code:
+              Or, as pseudo code (without dealing with side-channel
+              issues):
             </preamble>
             <artwork>
               k := x;
-              while (k.length() > 0 &amp;&amp; k[0] == 0) k = k[1:];
+              while (k.length() > 0 &amp;&amp; k[0] == 0) k := k[1:];
               assert(k.length() > 0);
-              if 0 != (k[0] &amp; 0x80) k = '\0' .. k;
+              if 0 != (k[0] &amp; 0x80) k := '\0' .. k;
             </artwork>
           </figure>
         </t>
@@ -214,7 +225,7 @@
         <t>
           When performing the X25519 or X448 operations, the integer
           values there will be encoded into byte strings by doing a
-          fix-length unsigned litle-endian conversion, per <xref
+          fixed-length unsigned litle-endian conversion, per <xref
           target="RFC7748"/>. It is only later when these byte strings
           are then passed to the ECDH code in SSH that the bytes are
           re-interpreted as a fixed-length unsigned big-endian integer

---

From: Ron Frederick <[email-masked]>
Date: Fri, 12 May 2017 22:24:09 -0700

Hi Mark,

> means that K is a multiple precision integer.

Section 8 is specific to the original Diffie Hellman key exchange. The computation is different for ECDH (described in section 4 of RFC 5656), but that states "The SSH framework requires that the shared key be an integer.” and then goes on to describe how to do the conversion from an EC field element. For X25519, I think it makes sense to state something similar.

> lets the mod operation do something useful.

I agree that in all of these cases the integer K is likely to be very large (much larger than something which would fit in a traditional integer value in a language like C). However, “mpint” here really is just an encoding. In Python, for example, K can be represented as a plain Python “int” type, since that natively supports arbitrarily large integers (limited only by available memory, I think). The “mpint” conversion here actually changes the type from an “int” to a byte array, which is needed to be able to feed the bytes to the hash function. Even in other languages where large integers are represented as some form of an array already, it’s important that the exact representation of this integer as a byte array is specified, so that all parties compute the hash in a consistent manner.

>      included.  The value zero MUST be stored as a string with zero

That does not say that 0 is an invalid value. It simply says that when converting to an “mpint" byte array, 0 is represented by a zero-byte array.

>   result is zero, as this eliminates standard side-channels in software

Ok.

I still have some doubts about whether this belongs in this specification, though, as it seems like the abort operation should be performed by the code implementing the X25519 multiplication. Before returning the byte array X, I would expect that code to check for an all-zeroes result and return an error, so the code handling this value in SSH would never even see the case where the converted value K ends up being 0.

As one data point, I checked the libsodium implementation, and it was changed back in November of 2015 (release 1.0.7) to perform this check and return an error if the result of the scalar multiplication ends up being all-zeroes. I don’t see a specific test vector which I can use to actually confirm that this works, though — does anyone happen to have one?

> of my recollection. Am I mistaken?

Yes - the fixed-size 4-byte length is always included as part of the bytes fed to the hash.

> below for the changes to see if it looks better or worse to you.

This definitely looks better. It still needs additional text to cover the insertion of the fixed length bytes in front of the encoded “mpint” value, though.
-- 
Ron Frederick
[email-masked]

---

From: "Mark D. Baushke" <[email-masked]>
Date: Sat, 13 May 2017 07:51:02 -0700

Hi Ron,

I have made some adjustments. Here is a unified diff of the relevant
changes to the .txt form of the draft. If the pseudo-code looks bad,
let me know if I should just remove it or not.

--- draft-ietf-curdle-ssh-curves-05.txt	2017-05-11 11:58:23.000000000 -0700
+++ draft-ietf-curdle-ssh-curves-06.txt	2017-05-13 07:46:54.000000000 -0700
@@ -140,47 +140,64 @@
    only to be applicable to the scope of the mechanism described in this
    document.
 
-   The shared secret, K, is defined in [RFC4253] as a multiple precision
-   integer (mpint).  Curve25519/448 outputs a binary string X, which is
-   the 32 or 56 byte point obtained by scalar multiplication of the
-   other side's public key and the local private key scalar.  The 32 or
-   56 bytes of X are converted into K by interpreting the bytes as an
-   unsigned fixed-length integer encoded in network byte order.  This
-   conversion follows the normal "mpint" process as described in section
-   5 of [RFC4251].
+   The shared secret, K, is defined in [RFC4253] and [RFC5656] as an
+   integer encoded as a multiple precision integer (mpint).
+   Curve25519/448 outputs a binary string X, which is the 32 or 56 byte
+   point obtained by scalar multiplication of the other side's public
+   key and the local private key scalar.  The 32 or 56 bytes of X are
+   converted into K by interpreting the octets as an unsigned fixed-
+   length integer encoded in network byte order.
+
+   The fixed-length integer is then minimized into the minimum number of
+   octets to represent a positve mpint.  This conversion follows the
+   normal "mpint" process as described in section 5 of [RFC4251] which
+   requires that unnecessary leading bytes with the value 0 MUST NOT be
+   included.  The length of the integer is then prepended with a 4 octet
+   big-endian integer which is the length in octets of the minimized K.
+
+   The mpint K is then fed along with other data to the key exchange
+   method's hash function to generate encryption keys.

-   o  Trim all leading zero-bytes of X.  If X is all zero-bytes, then
-      the key exchange MUST fail.
-
-   o  If the high bit of X is set, the mpint format requires a zero byte
-      to be prepended.
-
-   o  The length of the encoded K may not be the same as the original
-      length of X due to trimming or prepending zero-bytes as needed for
-      "mpint" format.

-   Or, as pseudo code:
+   o  Trim all leading zero-bytes of X, as required in section 5 of
+      [RFC4251].  If X is all zero-bytes, then the key exchange MUST
+      fail as required in section 6 of [RFC7748].
+
+   o  Given X is a positive, if the MSB of X is set, then the "mpint"
+      format requires a zero-byte to be prepended.
+
+   o  The length of the "mpint" form of K may not be the same as the
+      original length of X due to trimming or prepending zero-byte
+      values as needed for "mpint" format. prepend K with the big-endian
+      number of octets for the length of K.
+
+   Or, as pseudo code (without dealing with side-channel issues):
 
                  k := x;
-                 while (k.length() > 0 && k[0] == 0) k = k[1:];
+                 while (k.length() > 0 && k[0] == 0) k := k[1:];
                  assert(k.length() > 0);
-                 if 0 != (k[0] & 0x80) k = '\0' .. k;
+                 if 0 != (k[0] & 0x80) k := '\0' .. k;
+                 l[0] := k.lengh() >> 24;
+                 l[1] := (k.lengh() >> 16) & 0xff;
+                 l[2] := (k.lengh() >> 8) & 0xff;
+                 l[3] := k.lengh() & 0xff;
+                 k := l .. k;
 
                                  Figure 1
 
    When performing the X25519 or X448 operations, the integer values
-   there will be encoded into byte strings by doing a fix-length
+   there will be encoded into byte strings by doing a fixed-length
    unsigned litle-endian conversion, per [RFC7748].  It is only later
    when these byte strings are then passed to the ECDH code in SSH that
    the bytes are re-interpreted as a fixed-length unsigned big-endian

---

From: Ron Frederick <[email-masked]>
Date: Sun, 14 May 2017 08:27:14 -0700

Hi Mark,

> let me know if I should just remove it or not.

I would lean toward removing the pseudo-code, and just letting RFC 4251 and the examples provided in section 5 there speak for themselves. If you do that, you probably don’t even need to describe the details of how to encode the length. All I was really looking for here is a mention that the length is present after the conversion, since you had so much detail about the conversion of the integer itself. However, if you just reference this encoding mechanism you may be fine without it. You could replace the second and third paragraphs below with:

The integer K is then encoded as an mpint using the process described in section 5 of [RFC451] and the resulting bytes are fed as described in [RFC4253] to the key exchange method’s hash function to generate encryption keys.

-- 
Ron Frederick
[email-masked]