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:

struct Asset {
    // an enum representing the type of the asset
    AssetType assetType,
    // a 160-bit address
    address assetAddr,
    // the "ID" of the asset. This is only relevant for ERC721 and ERC1155
    uint256 assetId,
}

enum AssetType {
    // maps to integer value `0`
    ERC20,
    // maps to integer value `1`
    ERC721,
    // maps to integer value `2`
    ERC1155
}

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

struct EncodedAsset {
    // the asset address with the top-3 bits of `assetId`
    // and `assetType` packed into it
    uint254 encodedAssetAddr;
    // the bottom 253 bits of `assetId`
    uint254 encodedAssetId;
}
  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:

struct Note {
    // an anonymous stealth address for the note's owner
    CompressedStealthAddress owner;
    // a nonce that must be a valid element of the BN254 Scalar field
    uint256 nonce;
    // the asset the note is for
    Asset asset;
    // the amount of value in `asset` the note "holds"
    // this must be less than 2^252
    uint256 value;
}

struct CompressedStealthAddress {
    // the compressed encoding of the first component of the stealth address
    uint256 h1;
    // the compressed encoding of the second component of the stealth address
    uint256 h2;
}

struct StealthAddress {
  // the X coordinate of the first component of the stealth address
  uint256 h1X;
  // the Y coordinate of the first component of the stealth address
  uint256 h1Y;
  // the X coordinate of the second component of the stealth address
  uint256 h2X;
  // the Y coordinate of the second component of the stealth address
  uint256 h2Y
}

struct EncodedNote {
    StealthAddress owner;
    // same as above
    uint256 nonce;
    // pull out from EncodedAsset
    uint256 encodedAssetAddr;
    // pull out from EncodedAsset
    uint256 encodedAssetId;
    // same as above
    uint256 value;
}

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