Encodings

Details about encodings used to represent core data structures using both field elements and bits.

Compressed Point Encoding

We often represent Baby Jubjub curve points in a "compressed" 255-bit encoding to save on hashing, bandwidth, and calldata. Our encoding scheme is identical to the scheme used in circomlib's PointBits templates and circomlibjs's packPoint and unpackPoint methods.

For a given point P=(X,Y)P = (X, Y), the compressed encoding is the pair (s,Y)(s, Y), where ss is a single bit representing the "sign" (11 means negative) of XX, and a field element xFpx \in \mathbb{F}_p is considered "negative" if x>p12x \gt \frac{p - 1}{2}.

To decompress a pair (s,Y)(s, Y), we do the following:

  1. check that ss and YY are well-formed.

  2. compute X2=1Y2ADY2X^2 = \frac{1 - Y^2}{A - DY^2}, where AA and DD are Baby Jubjub's curve parameters

  3. check that the square root exists. If it doesn't, the pair does not represent a valid curve point.

  4. if the square root is 00, assert that ss is also 00. Otherwise, the pair is an invalid encoding

  5. otherwise, there will be two square roots - return the one whose "sign" matches ss:

    1. compute one of them and call it XX.

    2. compute the sign ss' of XX by comparing with p12\frac{p-1}{2}

    3. if s=ss' = s, return XX. Otherwise, return pXp-X.

We encode (s,Y)(s, Y) pairs into a 255-bit value as the sign bit followed by the binary representation of YY, which is 254-bits. In code:

function encodePair(signBit: bool, y: uint256) returns (uint256) {
    return uint256(signBit) << 254 | y
}

Note that the compressed encoding does not fit in a field element, even though it does fit in a uint256. Therefore it is (for the most part) not used in-circuit.

Asset Encoding

We define an Asset by the following struct:

To represent the asset using only valid elements of Fp\mathbb{F}_p, we transform it into the following form:

  1. encodedAssetId is the number represented by the 253 least-significant bits of assetId.

  2. encodedAssetAddr is defined as the number represented by the following bits concatenated together, read from most-significant to least-significant (i.e. in big-endian order):

    • 3 0 bits

    • the 3 most-significant bits of assetId

    • 88 bits that are left unspecified (currently they are ignored)

    • 2 bits representing assetType - 00 for ERC20, 01 for ERC721, 10 for ERC1155.

    • 160 bits representing assetAddr.

Note Encoding

We define the Note and EncodedNote structs as follows:

Within Nocturne, all amounts and balances are forced by the contracts and circuits to be 252-bit integers. This ensures it's impossible to overflow the field.

The StealthAddress struct is a "flattened" form of a stealth address. If we recall that a stealth address is pair of Baby Jubjub curve elements (H1,H2)G2(H_1, H_2) \in \mathbb{G}^2, h1X, h1Y, h2X, and h2Y are the XX and YY coordinates of H1H_1 and H2H_2 respectively. The CompressedStealthAddress struct is the "compressed" form of a stealth address, where the two components H1,H2H_1, H_2 are represented using compressed point encoding (see above).

The encoding process for a Note is:

  1. encode the asset

  2. decompress the stealthAddress field

  3. pull out the encodedAssetAddr and encodedAssetId fields.

Last updated