2021SC@SDUSC

The domain elements are defined in libsecp256k1, where Field defines the finite domain of secp256k1 G F ( p ) GF(p) GF(p) element, but the Field element defined in libsecp256k1 library is 320 bits, which is not the common 256 bits. Compact domain element storage is realized in FieldStorage, and the elements in the Field are compressed into 256 bits for easy storage.

# FieldStorage domain element compact storage

Each large number array is stored in libsecp256k1. Each array element is u32, that is, 32 bits. In FieldStorage, each Field element is 256 bits, which can be represented by 8 u32.

impl FieldStorage { pub const fn new( d7: u32, d6: u32, d5: u32, d4: u32, d3: u32, d2: u32, d1: u32, d0: u32, ) -> Self { Self([d0, d1, d2, d3, d4, d5, d6, d7]) }

The following code implements the process of compressing 320 bit domain elements into 256 bit storage. There is nothing to say about the compression process. It mainly introduces the syntax of the t rust language:

|Indicates a bit or. As long as one of the same bits is 1, it returns 1, otherwise it returns 0;

Shift left < < all bits in the operand move the specified number of bits to the left, and the bits on the right complement 0;

Shift right > > all bits in the operand move the specified number of bits to the right, and the bits on the left complement 0.

With the above syntax, it's easy to understand the compression process. Just do the corresponding operation on the 0-1 bit string of the corresponding array element. You can see that there are 10 array elements on the right side of the assignment number = and only 8 on the left side of the assignment number, which implements the stored procedure of compressing 320 bit domain elements into 256 bits.

impl Into<FieldStorage> for Field { fn into(self) -> FieldStorage { debug_assert!(self.normalized); let mut r = FieldStorage::default(); r.0[0] = self.n[0] | self.n[1] << 26; r.0[1] = self.n[1] >> 6 | self.n[2] << 20; r.0[2] = self.n[2] >> 12 | self.n[3] << 14; r.0[3] = self.n[3] >> 18 | self.n[4] << 8; r.0[4] = self.n[4] >> 24 | self.n[5] << 2 | self.n[6] << 28; r.0[5] = self.n[6] >> 4 | self.n[7] << 22; r.0[6] = self.n[7] >> 10 | self.n[8] << 16; r.0[7] = self.n[8] >> 16 | self.n[9] << 10; r } }

Since the Field elements can be compressed and stored, it is necessary to recover the compressed results. The recovery process is shown in the following code. It is the same 0-1 bit string operation, in which the & symbol represents bit and, that is, if the same bits are 1, it returns 1, otherwise it returns 0.

impl From<FieldStorage> for Field { fn from(a: FieldStorage) -> Field { let mut r = Field::default(); r.n[0] = a.0[0] & 0x3FFFFFF; r.n[1] = a.0[0] >> 26 | ((a.0[1] << 6) & 0x3FFFFFF); r.n[2] = a.0[1] >> 20 | ((a.0[2] << 12) & 0x3FFFFFF); r.n[3] = a.0[2] >> 14 | ((a.0[3] << 18) & 0x3FFFFFF); r.n[4] = a.0[3] >> 8 | ((a.0[4] << 24) & 0x3FFFFFF); r.n[5] = (a.0[4] >> 2) & 0x3FFFFFF; r.n[6] = a.0[4] >> 28 | ((a.0[5] << 4) & 0x3FFFFFF); r.n[7] = a.0[5] >> 22 | ((a.0[6] << 10) & 0x3FFFFFF); r.n[8] = a.0[6] >> 16 | ((a.0[7] << 16) & 0x3FFFFFF); r.n[9] = a.0[7] >> 10; r.magnitude = 1; r.normalized = true; r } }

# Field field element

Each Field element has 320 bits and is represented by 10 u32 array elements.

impl Field { pub const fn new_raw( d9: u32, d8: u32, d7: u32, d6: u32, d5: u32, d4: u32, d3: u32, d2: u32, d1: u32, d0: u32, ) -> Self { Self { n: [d0, d1, d2, d3, d4, d5, d6, d7, d8, d9], magnitude: 1, normalized: false, } }

The following contents will cover large end order, small end order, addition and multiplication of large numbers and normalization of field elements. I'll write it back soon