Solana/Transactions/Signing and Verification

Example Transaction

To avoid readers constantly switching pages, I will paste the analyzed transaction at the beginning of each section.

{
    "signatures": [
        "3NPdLTf2Xp1XUu82VVVKgQoHfiUau3wGPTKAhbNzm8Rx5ebNQfHBzCGVsagXyQxRCeEiGr1jgr4Vn32UEAx1Aov3"
    ],
    "message": {
        "header": [
            1,
            0,
            1
        ],
        "account_keys": [
            "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt",
            "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH",
            "11111111111111111111111111111111"
        ],
        "recent_blockhash": "6vAwzjtGMrN3mJ8o7iGVDjMM46e2AnctqmjvLbqtESrx",
        "instructions": [
            {
                "program": 2,
                "account": [
                    0,
                    1
                ],
                "data": "3Bxs3zzLZLuLQEYX"
            }
        ]
    }
}

Signing

A typical Solana transaction consists of two core parts: signature and message. The signature is the "pass" of the transaction. The signature is generated by the private key to ensure the authenticity and immutability of the transaction. The message part clearly specifies the operation details. Through this separation, Solana can process a large number of transactions in parallel, greatly improving throughput.

Each signature has a length of 64 bytes, with the number of signatures determined by the tx.message.header's specified number (explained later). In this example, only one signature indicates that the transaction is initiated by a single account and validated.

Multi-signature transactions (i.e. requiring authorization from multiple accounts) will contain multiple signatures.

The signature is generated by signing the "message" part of the serialized transaction with the account's private key.

Q: Using the Ada's private key 0x01 to sign the above transaction and verify its signature match with the transaction.

A:

import pxsol

tx = pxsol.core.Transaction.serialize_decode(bytearray([
    0x01, 0x76, 0x7a, 0xe2, 0x66, 0x60, 0xc1, 0x42, 0x94, 0x1a, 0x59, 0x61, 0xf6, 0xde, 0xc7, 0x23,
    0x7c, 0xae, 0x73, 0x3e, 0xdf, 0xe6, 0x51, 0x7c, 0x37, 0xfb, 0xb8, 0x48, 0x1f, 0x46, 0xbb, 0xb5,
    0x3c, 0xe3, 0x00, 0xe7, 0x14, 0xb4, 0x78, 0x40, 0x14, 0x2c, 0x93, 0xa4, 0xe6, 0x60, 0x0c, 0x50,
    0xfd, 0xa9, 0x75, 0x60, 0xab, 0x64, 0x1d, 0xb0, 0xce, 0x19, 0x55, 0x9b, 0x25, 0x1d, 0x66, 0xdf,
    0x04, 0x01, 0x00, 0x01, 0x03, 0x4c, 0xb5, 0xab, 0xf6, 0xad, 0x79, 0xfb, 0xf5, 0xab, 0xbc, 0xca,
    0xfc, 0xc2, 0x69, 0xd8, 0x5c, 0xd2, 0x65, 0x1e, 0xd4, 0xb8, 0x85, 0xb5, 0x86, 0x9f, 0x24, 0x1a,
    0xed, 0xf0, 0xa5, 0xba, 0x29, 0x74, 0x22, 0xb9, 0x88, 0x75, 0x98, 0x06, 0x8e, 0x32, 0xc4, 0x44,
    0x8a, 0x94, 0x9a, 0xdb, 0x29, 0x0d, 0x0f, 0x4e, 0x35, 0xb9, 0xe0, 0x1b, 0x0e, 0xe5, 0xf1, 0xa1,
    0xe6, 0x00, 0xfe, 0x26, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xe9, 0x77, 0x4a, 0x3c, 0xad, 0x5c, 0x33, 0xf1, 0xfb, 0x6b,
    0x37, 0xa0, 0x3d, 0x4f, 0x00, 0x9a, 0x31, 0x09, 0x81, 0x18, 0xd2, 0xce, 0xae, 0xbf, 0x43, 0x0a,
    0xf3, 0x01, 0xad, 0x25, 0x0d, 0x01, 0x02, 0x02, 0x00, 0x01, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00,
    0xca, 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00,
]))

s = pxsol.eddsa.sign(bytearray([0x00] * 31 + [0x01]),  tx.message.serialize())
assert s == tx.signatures[0]

Verification

When a transaction is submitted to the Solana network, the validator first checks if the number of signatures matches the tx.message.header, and then verifies that each signature is valid using the account's public key (from tx.message.account_keys).

In our example, the signature corresponds to the first account in tx.message.account_keys: 6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt. This account is the initiator of the transaction and paid the transaction fee.

Q: Verify that the signature of the above transaction is valid.

A:

import pxsol

tx = pxsol.core.Transaction.serialize_decode(bytearray([
    0x01, 0x76, 0x7a, 0xe2, 0x66, 0x60, 0xc1, 0x42, 0x94, 0x1a, 0x59, 0x61, 0xf6, 0xde, 0xc7, 0x23,
    0x7c, 0xae, 0x73, 0x3e, 0xdf, 0xe6, 0x51, 0x7c, 0x37, 0xfb, 0xb8, 0x48, 0x1f, 0x46, 0xbb, 0xb5,
    0x3c, 0xe3, 0x00, 0xe7, 0x14, 0xb4, 0x78, 0x40, 0x14, 0x2c, 0x93, 0xa4, 0xe6, 0x60, 0x0c, 0x50,
    0xfd, 0xa9, 0x75, 0x60, 0xab, 0x64, 0x1d, 0xb0, 0xce, 0x19, 0x55, 0x9b, 0x25, 0x1d, 0x66, 0xdf,
    0x04, 0x01, 0x00, 0x01, 0x03, 0x4c, 0xb5, 0xab, 0xf6, 0xad, 0x79, 0xfb, 0xf5, 0xab, 0xbc, 0xca,
    0xfc, 0xc2, 0x69, 0xd8, 0x5c, 0xd2, 0x65, 0x1e, 0xd4, 0xb8, 0x85, 0xb5, 0x86, 0x9f, 0x24, 0x1a,
    0xed, 0xf0, 0xa5, 0xba, 0x29, 0x74, 0x22, 0xb9, 0x88, 0x75, 0x98, 0x06, 0x8e, 0x32, 0xc4, 0x44,
    0x8a, 0x94, 0x9a, 0xdb, 0x29, 0x0d, 0x0f, 0x4e, 0x35, 0xb9, 0xe0, 0x1b, 0x0e, 0xe5, 0xf1, 0xa1,
    0xe6, 0x00, 0xfe, 0x26, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xe9, 0x77, 0x4a, 0x3c, 0xad, 0x5c, 0x33, 0xf1, 0xfb, 0x6b,
    0x37, 0xa0, 0x3d, 0x4f, 0x00, 0x9a, 0x31, 0x09, 0x81, 0x18, 0xd2, 0xce, 0xae, 0xbf, 0x43, 0x0a,
    0xf3, 0x01, 0xad, 0x25, 0x0d, 0x01, 0x02, 0x02, 0x00, 0x01, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00,
    0xca, 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00,
]))

assert pxsol.eddsa.verify(tx.message.account_keys[0].p, tx.message.serialize(), tx.signatures[0])