> For the complete documentation index, see [llms.txt](https://bloqly.gitbook.io/bloqly/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bloqly.gitbook.io/bloqly/api/transaction-signing.md).

# Transaction signing

Let `privateKey` is 32 bytes EdDSA private key

Concatenate all bytes of transaction fields (for strings use `utf8` encoding, for numbers such as `nonce` and `timestamp` - 8 bytes big-endian unsigned representation):

```
tags_bytes = concat(sort(tags))
```

```
data = concat(
    space
    key,
    bytes(nonce), // 8 bytes big-endian
    bytes(timestamp), // 8 bytes big-endian
    memo,
    tags_bytes, // concatenated array of tags
    value
)
```

Calculate `hash = sha256(data)`

Sign message `signature = EdDSA.sign(privateKey, hash)`

Create JSON object:

```
signedTransaction = {
    'space': space,
    'key': key,
    'nonce': nonce,
    'timestamp': timestamp,
    'tags': tags,
    'memo': memo,
    'value': value,
    'hash': encodeHex(tx_hash),
    'signature': encodeBase64(signature),
    'public_key': encodeBase64(public_key)
}
```

Create encoded transaction `encodedTransaction=encodeBase64(signedTransaction)`

Call API method `/api/events/add?event={encodedTransaction}`

If transaction is constructed and signed properly this endpoint will return `200 OK` HTTP response. It doesn't mean transaction is immediately add to blockchain as processing is asynchronous. In order to check if transaction succeeded  use provided in documentation API methods.

Here is the reference implementation of transaction signing in NodeJS

```javascript
const axios = require('axios');
const nacl = require('tweetnacl');
const crypto = require('crypto');

const UTF8 = 'utf8';

const EVENT_PATH = '/api/events/add';

const LAST_ID_PATH = '/api/events/last-id';

async function getLastId(nodeUrl, space, key) {
    const params = { space, key };
    const res = await axios.get(nodeUrl + LAST_ID_PATH, { params });
    return res.data;
}

async function sendSignedTransaction(nodeUrl, event) {
    const params = { event };
    const res = await axios.get(nodeUrl + EVENT_PATH, { params });
    return res.data;
}

function signTransaction(privateKey, space, key, nonce, timestamp, value, memo = '', tags = []) {

    const secretKeyBytes = Buffer.from(privateKey, 'base64');

    const keyPair = nacl.sign.keyPair.fromSeed(secretKeyBytes);

    const tagsBuffersList = [];

    tags.forEach(tag => {
        tagsBuffersList.push(Buffer.from(tag, UTF8));
    });

    const tagsBuffer = Buffer.concat(tagsBuffersList);

    const nonceBuffer = Buffer.alloc(8);
    nonceBuffer.writeBigInt64BE(BigInt(nonce));

    const tsBuffer = Buffer.alloc(8);
    tsBuffer.writeBigInt64BE(BigInt(timestamp));

    const messageBuffer = Buffer.concat([
        Buffer.from(space, UTF8),
        Buffer.from(key, UTF8),
        nonceBuffer,
        tsBuffer,
        Buffer.from(memo, UTF8),
        tagsBuffer,
        Buffer.from(value, UTF8)
    ]);

    const hash = crypto.createHash('sha256').update(messageBuffer).digest();

    const signature = nacl.sign.detached(hash, keyPair.secretKey);

    const encodedSignature =  Buffer.from(signature).toString('base64');

    const signedTransaction = {
        'space': space,
        'key': key,
        'nonce': nonce,
        'timestamp': timestamp,
        'tags': tags,
        'memo': memo,
        'value': value,
        'hash': Buffer.from(hash).toString('hex').toUpperCase(),
        'signature': encodedSignature,
        'public_key': Buffer.from(keyPair.publicKey).toString('base64')
    };

    return signedTransaction;

}

function encodeTransaction(signedTransaction) {
    const json = JSON.stringify(signedTransaction);

    return Buffer.from(json).toString('base64')
}

module.exports = {
    signTransaction,
    encodeTransaction,
    getLastId,
    sendSignedTransaction
};
```
