Roughtime Message Anatomy
CONTENTSThis post A) digs into the details of Roughtime protocol messages, and B) demonstrate use of the Nearenough client library1. It’s intended as a reference more than anything.
Familiarity with Roughtime is assumed. If needed, consult this prior post for an intoduction to the protocol and how it works.
Nearenough Client API
RoughtimeClient is the high-level Nearenough API for interacting with a Roughtime server. It provides
six methods:
- createRequest()- Constructs a RoughTime client request
- processResponse(RtMessage)- Validates the server’s response
- isResponseValid()- Indicates if the response passed validation
- midpoint()and- radius()- Returns the server’s provided time value (midpoint) and uncertainty (radius) if the response was valid
- invalidResponseCause()- If the response is invalid, provides the reason why validation failed
The constructor requires one argument: the remote Roughtime server’s long-term key. We’ll be interacting
with Google’s server (roughtime.sandbox.google.com) using the long-term public key found in the project’s
list of servers.
  // Long-term public key of Google's Roughtime server
  byte[] GOOGLE_SERVER_PUBKEY = hexToBytes(
      "7ad3da688c5c04c635a14786a70bcf30224cc25455371bf9d4a2bfb64b682534"
  );
  
  // Create a new RoughtimeClient instance using Nearenough client API
  RoughtimeClient client = new RoughtimeClient(GOOGLE_SERVER_PUBKEY);
The single-argument constructor uses
SecureRandom
to generate 64 random bytes for the nonce and should be sufficient for the majority of users.
For expert use, constructor overloads exist if controlling the random
number source or explicitly providing the nonce value are required.
Client Requests
All Roughtime operations are initiated by a client request. Requests must be >=1024 bytes long and all have two tags:
- NONCwith the 64-byte client nonce.
- PADto pad the request up to the 1024 byte minimum size.
Creating a Request
Creating a request is a one-liner:
  RtMessage request = client.createRequest();
Pretty-Printing
RtMessage overrides toString for convenient pretty-printing, so System.out.println(request) outputs:
RtMessage|2|{
  NONC(64) = aaacc1a6de530026f2500721b078967107734e173755f3dc6019218bffb1ce8bcfb1a87144386f45af0f1c5ce41bca4ebfeb727d27fe7a7d6baa9b08a3b50f68
  PAD(944) = 000...000
}
How to read the output:
- RtMessage|2|indicates the message has two tags.
- NONC(64)tag with a 64 byte long nonce value (- aaacc1a6...)
- PAD(944)tag with 944 bytes of zeros
Encoding for Transmission
Nearenough uses Netty to implement the network
layer. Netty’s “buffer” abstraction is the ByteBuf.
Static methods on RtWire encode an RtMessage into a ByteBuf for network transmission.
  ByteBuf buf = RtWire.toWire(request);
  // ready to send 'buf', e.g. ctx.writeAndFlush(buf)
  // Or, if you're in the NIO world and need a Java ByteBuffer
  ByteBuffer nioBuf = buf.nioBuffer();
On-The-Wire Bytes
To view the request’s bytes as they’ll be sent on-the-wire, use Netty’s ByteBufUtil.prettyHexDump():
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 02 00 00 00 40 00 00 00 4e 4f 4e 43 50 41 44 ff |....@...NONCPAD.|
|00000010| aa ac c1 a6 de 53 00 26 f2 50 07 21 b0 78 96 71 |.....S.&.P.!.x.q|
|00000020| 07 73 4e 17 37 55 f3 dc 60 19 21 8b ff b1 ce 8b |.sN.7U..`.!.....|
|00000030| cf b1 a8 71 44 38 6f 45 af 0f 1c 5c e4 1b ca 4e |...qD8oE...\...N|
|00000040| bf eb 72 7d 27 fe 7a 7d 6b aa 9b 08 a3 b5 0f 68 |..r}'.z}k......h|
|00000050| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|00000060| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
...
|000003f0| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+--------+-------------------------------------------------+----------------+
Walking through the bytes (keep in mind that integers are in little-endian byte order):
| Byte Range | Value | Description | 
|---|---|---|
| 0x00-0x03 | 02 00 00 00 | Number of tags in the message; 2 in decimal | 
| 0x04-0x07 | 40 00 00 00 | Offset to start of values; 64 in decimal | 
| 0x08-0x0b | 4e 4f 4e 43 | NONCtag marker | 
| 0x0c-0x0f | 50 41 44 ff | PADtag marker | 
| 0x10-0x4f | aa ac c1 a6... | Client nonce value (64 bytes long) | 
| 0x50-0x3ff | 00 00 00 00... | Padding zero bytes (944 bytes long) | 
Server Response
A Roughtime server response is a message with five tags. The values of some tags are themselves Roughtime messages.
Response Structure
RtMessage|5|{
  SIG(64) = fd06a4fb305f4df36e4e6f19941d0e4108d79d2879261ba03acbf48ae3d9fd60525dbfd21534c99f45145fa614afbbdad026437a6f6f6670452dee6766dd8003
  PATH(0) = 
  SREP(100) = RtMessage|3|{
    RADI(4) = 40420f00
    MIDP(8) = e36344212d4e0500
    ROOT(64) = 0e321361f19c96319484f7b7a5915f5f312702e4dd962cc6183361bfd32b4c096f75e8a254aecb612eb9b9c6aefdb1ed609884af19d6dff18cc091aaf2b79d86
  }
  CERT(152) = RtMessage|2|{
    SIG(64) = 29d589e9aaee25e00a2cdf019dcf848a99280fdf03310e00decb36c02535f8d66f79f3c12f69ccd93cf9978dc4c23f2c06b7ebc674c153c4452a42386dc4290f
    DELE(72) = RtMessage|3|{
      PUBK(32) = b411a29d262537cf175c55af4ad2f01155cc9e7bf37ac6502739124acb6bcf25
      MINT(8) = 00e02fe2284e0500
      MAXT(8) = 00c064778d4e0500
    }
  }
  INDX(4) = 00000000
}
Working through the response:
- SIGa 64 byte Ed25519 signature of the- SREPmessage using the- PUBKkey in the- CERTmessage.
- PATHempty here, but potentially contains the Merkle Tree path needed to verify that the client nonce was included in the response.
- SREP(“signed response”) a nested message containing:- ROOTroot of the Merkle Tree
- MIDP(“midpoint”) remote server’s time
- RADI(“radius”) width of the valid time span
 
- CERTa nested message:- SIGa 64 byte Ed25519 signature of the- DELEmessage using the server’s long-term public key.
- DELEa nested message:- PUBK(“public key”) a time-limited 32 byte Ed25519 public key
- MINT(“minimum time”) start of the time period when- PUBKis valid
- MAXT(“maximum time”) end of the time period when- PUBKis valid
 
 
- INDXindex locating the nonce in the Merkle Tree- PATH; zero here as- PATHis empty.
Response Validation
The processResponse(RtMessage) method will validate the server’s response. It checks that:
- The message is well-formed and obeys the Roughtime protocol spec.
- The signature of the DELEmessage, using the server’s long-term key, is valid.
- The top-level signature on SREP, using thePUBKkey in theDELEmessage, is valid.
- The request’s nonce is included in the response’s Merkle tree.
- The midpoint MIDPlies within the delegation time bounds (in betweenMINTandMAXT).
Use isResponseValid() to check the validation result:
  if (client.isResponseValid()) {
    // Validation passed, the response is good
  } else {
    // Validation failed. Print out the reason why.
    System.out.println("Response INVALID: " + client.invalidResponseCause().getMessage());
  }
The Midpoint and Radius
In a valid response, the midpoint is the Roughtime server’s reported UTC timestamp (in microseconds). The radius is a span of uncertainty around that midpoint. A Roughtime server asserts that its “true time” lies within the span.
  Instant midpoint = Instant.ofEpochMilli(client.midpoint() / 1_000L);
  int radiusSec = client.radius() / 1_000_000;
  System.out.println("midpoint: " + midpoint + " (radius " + radiusSec + " sec)");
  // midpoint: 2017-04-27T22:03:42.178Z (radius 1 sec)
- Using commit 523ff4a[return]