NDEF Schema v2.x
☝️ The v2.0.0 Item System applets support the elliptic curves secp256r1 and secp256r1, thus when receiving a NDEF message from an item, the backend must know which curve is used on that specific item in order to verify the signature.
Reading the NdefApplet's Augmented Data
The NdefApplet will generate data that is appended to the base URL stored on the item. That data is made up of:
- Public Key; 65 bytes
- Nonce; by default 5 bytes, but can be defined at card setup-time, thus check with whom has set up the item
- Signature; 70, 71, or 72 bytes long
All three values are byte arrays concatenated together and then Base64-encoded. Additionally, the characters +, /
and = in the Base64 string are replaced with ., _ and -, respectively.
Thus, to read the values, take the query parameter, replace ., _ and - with +, / and =, then Base64-decode
that string and then extract bytes as follows:
- [0, 64] for the public key
- [65, 69] for the nonce
- [69, 138] or [69, 139] or [97, 140] for the signature
Public Key format
The public key is in uncompressed ANSI X9.62 format. It starts with a 0x04 byte, followed by two unsigned integers
each of size 32 bytes representing the X and Y coordinates of the point.
For example:
04
abd18a6b15edb80d843d42d5e1d7ae1bad8d95aeff0a5df10ec662e25b43790d
6178e73a033f166825842015d83a90bc67eee40794e105b9c453169bab40524b
Signature Format
The signature is in ASN.1/DER format:
SEQUENCE (30) | <length> | INTEGER (02) | <length> | <sig r value> | INTEGER (02) | <length> | <sig s value> |
Which could look like this:
30 45
02 21
00fe91a4884844b0335fcc39d89c4c8d0a6d47a37c73b47f01ce5025ee0cb40ae5
02 20
67ecb283a5250043ea638265190567689c88ef78d67e3b058bc4560a896296a1
Here's JavaScript code that parses the signature:
import { u } from "@cityofzion/neon-core";
let pubKey = "04f3f2d45a52be616cd9ccdca8238e16a0dc444618c0b2f09e593c68f73a78301fa71b25802e8efec800611991faa1300a70a4c3917dffa7471c50edef83731444"
let msg = "0000000002"
let sigDER = "3046022100c2780213a83d9eb83a6b8bf6c60ad4dff80543dec5ec41d876f892b40f0c5eba022100c2800794dd2e7cc013a893ec292020710baba0825c1aaf7fce0ddee3bdc3e107"
let extractedSig = processDERSignature(u.hexstring2ab(sigDER))
console.log(u.ab2hexstring(extractedSig))
function processDERSignature(sigBytes) {
// Drop the first three bytes. They are always `30 46 02`
let truncated = sigBytes.slice(3);
// Check length of r coordinate
if (truncated[0] === 0x21) {
truncated = truncated.slice(2) // skip the size byte and the zero byte that was prepended to the r coordinate
} else {// truncated [0] === 20
truncated = truncated.slice(1) // skip only the size byte
}
const r = truncated.slice(0, 32); // Read the r coordinate
// Skip the r coordinate and type byte
truncated = truncated.slice(32 + 1)
// Check length of s coordinate
if (truncated[0] === 0x21) {
truncated = truncated.slice(2) // skip the size byte and the zero byte that was prepended to the s coordinate
} else { // truncated [0] === 20
truncated = truncated.slice(1) // skip only the size byte
}
const s = truncated.slice(0, 32); // Read the s coordinate
const concat = new Uint8Array(r.length + s.length);
concat.set(r)
concat.set(s, r.length)
return concat
}
Example Augmented Data
An example of how the augmented data coming from the NDEF applet might look from it's Base64-encoded to "extracted" form:
Base64-encoded first with
.,_and-and second the real Base64 string.BPPy1FpSvmFs2czcqCOOFqDcREYYwLLwnlk8aPc6eDAfpxslgC6O_sgAYRmR.qEwCnCkw5F9_6dHHFDt74NzFEQAAAAAAjBGAiEAwngCE6g9nrg6a4v2xgrU3_gFQ97F7EHYdviStA8MXroCIQDCgAeU3S58wBOok.wpICBxC6ugglwar3_ODd7jvcPhBw--
BPPy1FpSvmFs2czcqCOOFqDcREYYwLLwnlk8aPc6eDAfpxslgC6O/sgAYRmR+qEwCnCkw5F9/6dHHFDt74NzFEQAAAAAAjBGAiEAwngCE6g9nrg6a4v2xgrU3/gFQ97F7EHYdviStA8MXroCIQDCgAeU3S58wBOok+wpICBxC6ugglwar3/ODd7jvcPhBw==Public Key:
04f3f2d45a52be616cd9ccdca8238e16a0dc444618c0b2f09e593c68f73a78301fa71b25802e8efec800611991faa1300a70a4c3917dffa7471c50edef83731444Nonce:
0000000002Signature:
3046022100c2780213a83d9eb83a6b8bf6c60ad4dff80543dec5ec41d876f892b40f0c5eba022100c2800794dd2e7cc013a893ec292020710baba0825c1aaf7fce0ddee3bdc3e107
Verifying the Signature
To verify the signature we have to use the ECDSA signature algorithm with hashing included. I.e., the nonce - which is
the signed "message" - has to be hashed with SHA-256 before verifying.
In Java, using the item-sdk-java SDK, signature verification would look
something like the following. Note that we have to mention which curve the public key is based on. This depends on the
keys installed and selected on the item.
ECPublicKey pubKey = Crypto.decodeEcPoint(publicKeyBytes, SupportedECCurves.SECP256R1);
Signature sigAlgo = Signature.getInstance("SHA256withECDSA");
sigAlgo.initVerify(pubKey);
sigAlgo.update(nonce);
sigAlgo.verify(signatureBytes) // Signature bytes DER-encoded
The NdefApplet's NDEF Message
The NdefApplet's response (the NDEF message) contains:
NDEF record header
The NDEF record header is only 4 bytes long in our case. We set the SR bit and unset the IL bit in the NDEF record header, meaning this is a "short record" containing Type Length, Payload Length, Type and Payload. The Type Name Format is set to "Well-known".
The NDEF payload
- The NDEF payload is prepended with one byte that encodes the URL scheme, which is
httpsin our case. - The base URL as defined at setup-time, e.g.,
item.systems/item/i= - The augmented data made up of:
- Public Key (65 byte)
- Nonce which is by default (5 bytes) but can be set at setup-time
- Signature which looks as followsThe length of the integers
SEQUENCE (30) | <length> | INTEGER (02) | <length> | <sig r value> | INTEGER (02) | <length> | <sig s value> |randsare either32or33bytes. That's because the most significant bit of the integer can be1, in which case the integer is extended by one byte to make sure it is interpreted as a positive number in the 2's complement system. Thus, the max length of the signature is1 + 1 + 1 + 1 + 33 + 1 + 1 + 33 = 72 bytes.
- The NDEF payload is prepended with one byte that encodes the URL scheme, which is
The augmented data is concatenated and Base64-encoded. Base64-encoding has the following effect on the augmented data's size:
- Base64 encodes each set of three bytes into four bytes. In addition the output is padded to always be a multiple of four.
- This means that the size of the base-64 representation of bytes of size n is: $ceil(n/3)*4$
- For our augmented data holding a 65 bytes public key, 5 bytes nonce, and 72 bytes signature this results in $n = 65 + 5 + 72 : ceil(142/3)*4 = 192$
Base URL Max Size
Without using extended length APDUs, one APDU allows for 255 bytes of data. Note, that it doesn't allow for 256 bytes because only one byte is used to denote the expected number of bytes, which can have a max value of 255. In a response APDU, two bytes are used for the Status Word (SW), leaving 253 bytes for our NDEF message.
With a max size of the NdefApplet’s augmented data of 192 bytes, 1 byte for the URL scheme, and 4 bytes for the NDEF record header there are 253 - 192 - 1 - 4 = 56 bytes left in the APDU response for the base URL.
☝️ Note, the base URL max size changes when the nonce length deviates from the default.