Worked example and test vector
A published test key, one signed PUT, and every intermediate value, so you can check your NK-SIG-1 implementation byte for byte.
This page signs one request with a published test key and prints every intermediate value. Run your own implementation on the same inputs: Ed25519 is deterministic, so your digest, base and signature must equal these bytes exactly.
The test key#
The private key is the Ed25519 key derived from this 32-byte seed (hex):
a07a875fdf7605bf41fccf573c35d0e0dadf7f70bf5f5a28375ed5fa8231a8bdIn Node, wrap the seed in the fixed 16-byte PKCS#8 header for Ed25519 (302e020100300506032b657004220420) and load it as DER:
import { createPrivateKey } from 'node:crypto'
const seed = Buffer.from('a07a875fdf7605bf41fccf573c35d0e0dadf7f70bf5f5a28375ed5fa8231a8bd', 'hex')
const privateKey = createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]), format: 'der', type: 'pkcs8' })Other libraries call this "Ed25519 private key from a 32-byte seed" (libsodium: crypto_sign_seed_keypair; Python cryptography: Ed25519PrivateKey.from_private_bytes). With OpenSSL, the same 48 bytes (header plus seed) are a DER private key.
The derived public key, as raw base64 (32 bytes):
RXFUSrDp7RaeGIkOOA8m0QFuuGeC3W+Ks1xqNQLglU4=and as PEM (SPKI):
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEARXFUSrDp7RaeGIkOOA8m0QFuuGeC3W+Ks1xqNQLglU4=
-----END PUBLIC KEY-----Key id used below: nkp_sandbox_0123456789abcdef.
The request#
Method and URL:
PUT https://novikvadrat.rs/api/partners/v1/connections/9b1d0c2e-4a6f-4c1e-9a53-3f2b7d8e1a10/listings/REAA-12345Timestamp 1791792000 and nonce bmstc2lnLTEtdGVzdC12ZWN0b3Itbm9uY2U.
The body — exactly these bytes, UTF-8, one line (the sale listing of Payload v1 with three photos):
{"external_id":"REAA-12345","transaction":"sale","category":"apartment","state":"active","title":{"sr":"Trosoban stan, Vračar","en":"Three-room flat, Vračar"},"description":{"sr":"Svetao trosoban stan na Vračaru, renoviran 2024, na pet minuta od Hrama Svetog Save.","en":"A bright three-room flat in Vračar, renovated in 2024, five minutes from the Temple of Saint Sava."},"price":{"amount":245000,"currency":"EUR","negotiable":false},"location":{"place_id":"b2c0d6a4-3f1e-4d2a-9c8b-7e6f5a4b3c2d","address_line":"Njegoševa 10","neighbourhood":"Crveni krst","lat":44.8012,"lng":20.4722},"structure":{"area_sqm":78.5,"rooms":3,"bedrooms":2,"bathrooms":1,"floor":3,"total_floors":5,"levels":1,"year_built":1968,"building_group":"old","condition":"renovated","furnished":"semi_furnished","heating":"district","elevator":true,"parking":false,"garage":false,"balcony":true,"terrace":false,"air_conditioning":true,"basement":false},"legal":{"ownership_status":"uknjizen","legalization_status":"legalizovan","mortgage_eligible":true,"mandate_attested":true,"exclusive":false,"broker_license_no":null,"tapija_note":null},"commission":{"buyer_pct":2,"seller_pct":2},"amenities":["built_in_wardrobes","intercom"],"attributes":{},"photos":[{"url":"https://cdn.reaa.rs/listings/12345/1.jpg","caption":null,"room_type":"living_room","digest":null},{"url":"https://cdn.reaa.rs/listings/12345/2.jpg","caption":null,"room_type":"kitchen","digest":null},{"url":"https://cdn.reaa.rs/listings/12345/3.jpg","caption":null,"room_type":"bedroom","digest":null}],"video_url":null,"virtual_tour_url":null,"agent":{"email":"milan@agencija.rs","name":"Milan Petrović","phone":"+381601234567"},"source":{"updated_at":"2026-10-12T08:00:00Z","url":"https://agencija.rs/nekretnine/12345"},"digest":null}Step 1: the content digest#
SHA-256 of the body bytes, base64, in the RFC 9530 form:
sha-256=:xKAutGVNI0q8QuF3kGZQr7r1CDddCdtFRvSXTahZyHY=:Step 2: the signature base#
Seven lines joined with \n, no trailing newline. There is no query, so the fourth line is the path alone:
NK-SIG-1
PUT
novikvadrat.rs
/api/partners/v1/connections/9b1d0c2e-4a6f-4c1e-9a53-3f2b7d8e1a10/listings/REAA-12345
1791792000
bmstc2lnLTEtdGVzdC12ZWN0b3Itbm9uY2U
sha-256=:xKAutGVNI0q8QuF3kGZQr7r1CDddCdtFRvSXTahZyHY=:Step 3: the signature#
Ed25519 over the UTF-8 bytes of the base, base64url without padding, prefixed with v1=:
v1=VETjUsgvkz_OA_VEJmCN-yI00JZsDW3apSbFhNZbQi8yKRKY_wHNVR-nGnTkizb_c5lRRATle5WR22a_yJdXCwStep 4: the headers#
NK-Key-Id: nkp_sandbox_0123456789abcdef
NK-Timestamp: 1791792000
NK-Nonce: bmstc2lnLTEtdGVzdC12ZWN0b3Itbm9uY2U
Content-Digest: sha-256=:xKAutGVNI0q8QuF3kGZQr7r1CDddCdtFRvSXTahZyHY=:
NK-Signature: v1=VETjUsgvkz_OA_VEJmCN-yI00JZsDW3apSbFhNZbQi8yKRKY_wHNVR-nGnTkizb_c5lRRATle5WR22a_yJdXCw
Content-Type: application/jsonIf yours differs#
- Digest differs: you hashed different bytes — another serialisation, a trailing newline, or a different encoding of
č,š,ć(they must be UTF-8). - Base differs: compare line by line — upper-case method, lower-case host without port, no trailing newline,
\nnot\r\n. - Signature differs but the base matches: the key is not derived from the seed above, or your library signs a hash of the message (Ed25519ph) instead of the message itself.
The reference client reproduces this example: signRequest with this key, method, URL and body, at this timestamp and nonce, returns these headers.
Last updated