HaLo
NDEF Record Structure

NDEF Record Structure

The HaLo URL NDEF record contains information about the chip's unique public keys as well as information about the last issued command and last returned response for that command. There is a proprietary interface allowing the user to issue arbitrary commands against the chip using the NDEF layer.

⚠️

Parameters are expected to change in version c4 and higher.

Example URL

https://eth.vrfy.ch/?v=c3&static=4104CB18C1B56949A13EFA4468F50D81006BCD2B9009E3F7B83AF50F9B474537405FF34B6362F43ECA60F28FDC1ECC4488E6DE1A19C638A7E3F0D92ABD931A61AB434104295CA8CB0476091B242D8C990F9E34638FF7969D83014BCD4F9BD8B78D0AC25CBEA6A6CF5BBECD88CEBE994F6070E708518D0D9393968008C946B42E16987DB3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&latch1=0000000000000000000000000000000000000000000000000000000000000000&latch2=0000000000000000000000000000000000000000000000000000000000000000&cmd=810200000009DC18D3F30E06E9A3C10D41EDDA09404654C502F70E67E80D781876D800&res=3045022100FCAAEFFCAB25E4B78BABB68C733DD2827FDC76E927F87C50A392E257583797DD02201A9CA7012FF3CC1C3380E1832BC733C69D743445A8DA4F664095F134927DA9170000000000&

v parameter

The v parameter is a single byte indicating the version of the chip.

static parameter

The static parameter contains the public key information corresponding to the secp256k1 keys stored within the chip.

From the factory this field contains two public keys corresponding to the key slots #1 and #2. If the HaLo is instructed to generate the key in slot #3, the corresponding public key will be added to this field as well.

The structure is encoded as follows:

[key size - 1 byte] [public key - N bytes] [key size - 1 byte] [...]

Although current HaLo chips store public keys in an uncompressed format starting with the 04 byte, this may change and as such you should always use the key size byte to determine the nature of the key.

The parsing algorithm for that byte should be implemented as follows:

  1. Set i = 1.
  2. Read 1 byte from the buffer and store it in the key_size variable. If the read has failed because there are no bytes left in the buffer - stop algorithm.
  3. If key_size is equal to 0, stop the algorithm.
  4. Read key_size number of bytes from the buffer and store them as public_key[i].
  5. Increment i by 1.
  6. Return to step 2 (and continue until there are no bytes remaining in the buffer).

Example input:

4104CB18C1B56949A13EFA4468F50D81006BCD2B9009E3F7B83AF50F9B474537405FF34B6362F43ECA60F28FDC1ECC4488E6DE1A19C638A7E3F0D92ABD931A61AB434104295CA8CB0476091B242D8C990F9E34638FF7969D83014BCD4F9BD8B78D0AC25CBEA6A6CF5BBECD88CEBE994F6070E708518D0D9393968008C946B42E16987DB3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Example parsed result:

  • [1] 04CB18C1B56949A13EFA4468F50D81006BCD2B9009E3F7B83AF50F9B474537405FF34B6362F43ECA60F28FDC1ECC4488E6DE1A19C638A7E3F0D92ABD931A61AB43
  • [2] 04295CA8CB0476091B242D8C990F9E34638FF7969D83014BCD4F9BD8B78D0AC25CBEA6A6CF5BBECD88CEBE994F6070E708518D0D9393968008C946B42E16987DB3

If the size of the element is 65 - the key is a secp256k1 uncompressed public key. If the size of the element is 33 - the key is a secp256k1 compressed public key. The count of array indexes determine the number of corresponding key slots.

latch parameters

Later editions of the chip (version c2 and higher) noted with the also contain two one-time writeable 32 byte slots, called latches.

Once a non-zero buffer is written to any of the slots, that slot is locked against any further modification. The latch1 and latch2 parameters contain hex encoded values of the corresponding latch slots.

cmd parameter

The cmd or command field is always composed as follows:

  • command code: 2 bytes (always: 8102).
  • challenge: 32 bytes (e.g. 00000009DC18…), which consists of:
    • Big-endian UInt32: incremental counter, automatically incremented by 1 on each tap.
    • Random 28 bytes: randomly generated by the tag on each tap.
    • Zero-padding: 1 byte (always: 00).

Example cmd field value:

8102 00000009DC18D3F30E06E9A3C10D41EDDA09404654C502F70E67E80D781876D8 00

res parameter

The res or response field is always composed as follows:

  • The DER encoded ECDSA raw signature created over the challenge.
  • Zero-padding to the length of 76 bytes.

Note that no SHA1/SHA256 hashing is applied before the challenge is signed.

Example res field value:

3045022100FCAAEFFCAB25E4B78BABB68C733DD2827FDC76E927F87C50A392E257583797DD02201A9CA7012FF3CC1C3380E1832BC733C69D743445A8DA4F664095F134927DA917 0000000000

The exact length of the signature is variable. The length can be found by reading 2nd byte of res field and adding 2 to it.

In the example above: 0x45 + 2 = signature length is 71 bytes.

Note that you should discard excess zero bytes before validating the signature with a cryptographic library.

Last updated on March 17, 2023