§ 01 · Bytes & numbers
Bytes
Eight bits, one number from 0 to 255
On this page 7 sections
A byteByte: Eight bits, enough to hold a whole number from 0 to 255. Written as two hexadecimal characters, from 00 to ff. is a group of eight bits. It can hold one whole number from 0 to 255.
That’s the basic unit of everything in Pearl. A private key is 32 bytes. A signature is 64. A block header is 108. Even the matrices miners multiply are stored as long rows of bytes.
What can one byte hold?
Each of the eight bits is a 0 or a 1, and each is worth twice the one to its right. Add up the bits that are on and you get the byte’s value.
All eight bits off is 0. All eight on is 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255. That gives 256 possible values, which is exactly two hexHexadecimal: A way of writing numbers with sixteen digits, 0–9 then a–f. One byte is always exactly two hex characters, which is why keys and hashes are shown in hex. characters’ worth.
How do bytes make bigger numbers?
By working in teams. Two bytes side by side act like a two-digit number in base 256.
| Bytes | Largest value | Where Pearl uses it |
|---|---|---|
| 1 | 255 | short lengths and counts |
| 2 | 65,535 | the noise rank in a certificate |
| 4 | 4,294,967,295 | version, timestamp, bits, output index, locktime |
| 8 | about 1.8 × 10¹⁹ | amounts in grains |
| 32 | about 1.2 × 10⁷⁷ | hashes, private keys, x-only public keys |
Which byte of the team comes first is a real question with a surprising answer. It has its own page: byte order.
How big are Pearl’s pieces?
Most things in Pearl have a fixed size, which is what makes raw data readable at all. If you know a hash is 32 bytes, you know exactly where the next field starts.
- Block header
- 108 bytes
- Bitcoin: 80
- Hash
- 32 bytes
- block hash, txid, Merkle root
- Public key
- 32 bytes
- x-only, as in Taproot
- Schnorr signature
- 64 bytes
- 65 with a sighash byte
- Amount
- 8 bytes
- a whole number of grains
- Certificate
- 59,768 bytes
- in front of every block today
- Public data
- 164 bytes
- what the proof is about
- XMSS signature
- 2,340 bytes
- optional, post-quantum
- Matrix entry
- 1 byte
- a whole number in [−64, 64]
Things that vary in length, like a list of inputs or a script, get a small length prefix in front. That prefix is a CompactSize.
How big is a kilobyte here?
This site uses KB for 1,000 bytes: the certificate in front of every block is about 59.8 KB. A few places use powers of two instead, written KiB (1,024 bytes) and GiB (2³⁰ bytes):
- BLAKE3 hashes data in 1 KiB chunks, and Pearl pads mining matrices to a multiple of 1,024 bytes before fingerprinting them.
- A matrix of 131,072 × 8,192 one-byte entries, the size of matrix A in block 130,825’s certificate, is exactly 1 GiB.
Block size is counted differently again, in vbytes: at most 1,000,000 per block, with the certificate not counted at all.
How are negative numbers stored?
A byte only holds 0 to 255, but sometimes you need negatives. The standard trick is two’s complementTwo's complement: The standard way to store negative whole numbers in bytes: −1 is all ones (0xff in one byte), −128 is 0x80. Pearl’s matrix entries are stored this way, one signed byte each.: the top half of the range stands for negative numbers.
| Value | −128 | −64 | −1 | 0 | 1 | 63 | 64 | 127 |
|---|---|---|---|---|---|---|---|---|
| Byte | 80 |
c0 |
ff |
00 |
01 |
3f |
40 |
7f |
The rule: to store −x, take 256 − x. So −1 becomes 255 (ff) and −64 becomes 192 (c0). The clever part is that ordinary addition still works. Add 1 to ff and it wraps around to 00, just as −1 + 1 = 0.
Pearl’s mining uses this directly. Every entry of a miner’s matrices is a small whole number between −64 and 64, stored as one signed byte. Here is the first row of the toy matrix A from Pearl’s toy mining example, and the 16 bytes that represent it:
| Field | Bytes | Value |
|---|---|---|
| −3, 1, 4, 4 | 4 @0 | −3 is 0xfd (256 − 3 = 253) |
| −3, −1, 4, 2 | 4 @4 | −1 is 0xff |
| 4, 3, 3, −1 | 4 @8 | 040303ff |
| −4, −3, −3, 0 | 4 @12 | −4 is 0xfc |
The integers page explains why Pearl picks the range −64 to 64, and why the sums use 4-byte numbers.
How is text stored?
With a lookup table. ASCIIASCII: A table that gives each letter, digit and symbol a number from 0 to 127, so text can be stored as bytes. "P" is 0x50, "R" is 0x52, "L" is 0x4c. gives every common character a number below 128, so each character fits in one byte.
Pearl’s mainnet network magic is the constant 0x50524C4D. Written out, its bytes are 50 52 4c 4d, and in ASCII that’s “P”, “R”, “L”, “M”. (Stored smallest byte first, it appears as 4d4c5250 in Pearl’s block files. Pearl’s peer-to-peer traffic is always encrypted, so it never shows up in the clear on the network.)
Text also appears inside real blocks. The coinbase of block 130,825 carries the bytes 2f706f6f6c2e6b7279707465782e636f6d2f, which read as “/pool.kryptex.com/”, the name of the pool that mined it.
What does a real transaction look like as bytes?
Here is a real Pearl payment, every byte colored by its field. Hover to name each one. It is one input, two outputs and one 64-byte signature.
| Field | Bytes | Value |
|---|---|---|
| Version version | 4 @0 | 1 int32 little-endian. Pearl wallets and pearld coinbases use version 1; relay policy accepts 1-2. |
| Marker marker | 1 @4 | 0x00 Segwit marker (BIP-144): says witness data follows the outputs. |
| Flag flag | 1 @5 | 0x01 Witness flag; 0x01 is the only value Pearl accepts. |
| Input count vin.length | 1 @6 | 1 CompactSize number of inputs. |
| Previous txid vin[0].txid | 32 @7 | 33218153e215d1a1e80941850a696af178fc83bd6cf266be92e7ba9170553959 The transaction being spent, stored byte-reversed (internal order); shown here in normal display order. |
| Output index vin[0].vout | 4 @39 | 683 Which output of that transaction is spent (uint32 LE). |
| scriptSig length vin[0].scriptSig.length | 1 @43 | 0 Always 0 for non-coinbase inputs in Pearl: every spend uses the witness instead. |
| Sequence vin[0].sequence | 4 @44 | 0xffffffff Final (0xffffffff): no RBF signal, no relative locktime, and locktime is ignored for this input. Pearl wallets use this. |
| Output count vout.length | 1 @48 | 2 CompactSize number of outputs. |
| Amount vout[0].value | 8 @49 | 295,646,640 grains (2.95646640 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains). |
| Script length vout[0].scriptPubKey.length | 1 @57 | 34 CompactSize length of the locking script. |
| Locking script vout[0].scriptPubKey | 34 @58 | prl1p674s9jt2xs4amk6aq7ul867kcp8qwll5zsvpzzu6revewsu5z6eqv9d5z4 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output. |
| Amount vout[1].value | 8 @92 | 200,000,000 grains (2.00000000 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains). |
| Script length vout[1].scriptPubKey.length | 1 @100 | 34 CompactSize length of the locking script. |
| Locking script vout[1].scriptPubKey | 34 @101 | prl1pvt5a5d5z42egswfv7uscht7rv2rj3z4m8n3qp3w35592ydq88v4seuhn70 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output. |
| Witness items witness[0].length | 1 @135 | 1 Number of witness stack items for input 0. |
| Schnorr signature length witness[0][0].length | 1 @136 | 64 CompactSize length of this witness item. |
| Schnorr signature witness[0][0] | 64 @137 | 4686c7014749649b25555874c7772ecb7a44e85454893ed5888db11ebcdc82a22d74ce1a1671d02810f5ba1dca3277409e95a653ba9644099f7da26be7a86ba2 64-byte BIP-340 signature (R.x ‖ s) with SIGHASH_DEFAULT: a Taproot key-path spend. |
| Locktime locktime | 4 @201 | 0 0: no time lock. |
Read it from left to right and you can see the sizes from the table above: 4 bytes of version, a 32-byte previous txid, 8-byte amounts, a 64-byte signature. The small one-byte fields between them (01, 02, 22, 40) are counts and lengths.
Code: bytes in JavaScript
Browsers handle raw bytes with Uint8Array. Signed bytes use Int8Array, which applies two’s complement for you:
const signed = Int8Array.of(-3, 1, 4, 4);
new Uint8Array(signed.buffer); // Uint8Array [253, 1, 4, 4], i.e. fd 01 04 04
new TextEncoder().encode('PRLM'); // Uint8Array [80, 82, 76, 77] = 50 52 4c 4d