diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ca450f0..25ddbf9 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -31,9 +31,9 @@ jobs: uses: actions/configure-pages@v5 - name: 🧰 Install Aiken - uses: aiken-lang/setup-aiken@v1 + uses: aiken-lang/setup-aiken@v1.0.3 with: - version: v1.1.11 + version: v1.1.15 - name: 📝 Run fmt run: aiken fmt --check diff --git a/aiken.toml b/aiken.toml index 8333752..1c11467 100644 --- a/aiken.toml +++ b/aiken.toml @@ -1,6 +1,6 @@ name = "aiken-lang/stdlib" version = "main" -compiler = "v1.1.11" +compiler = "v1.1.15" plutus = "v3" description = "The Aiken Standard Library" diff --git a/lib/aiken/crypto/bitwise.ak b/lib/aiken/crypto/bitwise.ak new file mode 100644 index 0000000..8a5eadb --- /dev/null +++ b/lib/aiken/crypto/bitwise.ak @@ -0,0 +1,136 @@ +//// @hidden + +use aiken/builtin + +pub opaque type State { + inner: Int, +} + +pub const zero = State { inner: 0 } + +pub const one = State { inner: 1 } + +pub fn add_bits(field: Int, big_endian: Bool) { + fn(state: State, bytes: ByteArray) -> State { + builtin.bytearray_to_integer(big_endian, bytes) + |> builtin.add_integer(state.inner, _) + |> builtin.mod_integer(field) + |> State + } +} + +pub fn add_int(field: Int) { + fn(state: State, int: Int) -> State { + state.inner + int + |> builtin.mod_integer(field) + |> State + } +} + +pub fn add_state(field: Int) { + fn(state: State, other: State) -> State { + state.inner + other.inner + |> builtin.mod_integer(field) + |> State + } +} + +pub fn sub_bits(field: Int, big_endian: Bool) { + fn(state: State, bytes: ByteArray) -> State { + builtin.bytearray_to_integer(big_endian, bytes) + |> builtin.subtract_integer(state.inner, _) + |> builtin.mod_integer(field) + |> State + } +} + +pub fn sub_int(field: Int) { + fn(state: State, int: Int) -> State { + state.inner - int + |> builtin.mod_integer(field) + |> State + } +} + +pub fn sub_state(field: Int) { + fn(state: State, other: State) -> State { + state.inner - other.inner + |> builtin.mod_integer(field) + |> State + } +} + +pub fn mul_bits(field: Int, big_endian: Bool) { + fn(state: State, bytes: ByteArray) -> State { + builtin.bytearray_to_integer(big_endian, bytes) + |> builtin.multiply_integer(state.inner, _) + |> builtin.mod_integer(field) + |> State + } +} + +pub fn mul_int(field: Int) { + fn(state: State, int: Int) -> State { + state.inner * int + |> builtin.mod_integer(field) + |> State + } +} + +pub fn mul_state(field: Int) { + fn(state: State, other: State) -> State { + state.inner * other.inner + |> builtin.mod_integer(field) + |> State + } +} + +pub fn scale( + self: State, + e: Int, + mul: fn(State, State) -> State, +) -> State { + if e < 0 { + zero + } else if e == 0 { + one + } else if e % 2 == 0 { + scale(mul(self, self), e / 2, mul) + } else { + mul(self, scale(mul(self, self), ( e - 1 ) / 2, mul)) + } +} + +/// A faster version of `scale` for the case where the exponent is a power of two. +/// That is, the exponent $e = 2^k$ for some non-negative integer $k$. Which is used a lot in zk-SNARKs. +pub fn scale2(self: State, k: Int, mul: fn(State, State) -> State) { + if k < 0 { + zero + } else { + do_scale2(self, k, mul) + } +} + +fn do_scale2(self: State, k: Int, mul) -> State { + if k == 0 { + self + } else { + do_scale2(mul(self, self), k - 1, mul) + } +} + +pub fn neg(field: Int) { + fn(state: State) -> State { + ( field - state.inner ) % field + |> State + } +} + +pub fn to_int(state: State) -> Int { + state.inner +} + +pub fn from_int(int: Int, field: Int) -> State { + int % field + |> State +} diff --git a/lib/aiken/crypto/bls12_381/g1.ak b/lib/aiken/crypto/bls12_381/g1.ak index d7b4cc1..2d3c920 100644 --- a/lib/aiken/crypto/bls12_381/g1.ak +++ b/lib/aiken/crypto/bls12_381/g1.ak @@ -11,6 +11,7 @@ //// This module ensures that all operations respect the properties of the BLS12-381 curve and the mathematical structure of the G1 group. use aiken/builtin +use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/scalar.{Scalar} /// The compressed generator of the G1 group of the BLS12-381 curve. @@ -95,12 +96,12 @@ test sub_1() { /// Exponentiates a point in the G1 group with a `scalar`. /// This operation is equivalent to the repeated addition of the point with itself `e` times. -pub fn scale(point, e: Scalar) { +pub fn scale(point, e: State) { builtin.bls12_381_g1_scalar_mul(scalar.to_int(e), point) } test scale_1() { - expect Some(x) = scalar.new(2) + let x = scalar.from_int(2) builtin.bls12_381_g1_add(generator, generator) == scale(generator, x) } diff --git a/lib/aiken/crypto/bls12_381/g2.ak b/lib/aiken/crypto/bls12_381/g2.ak index 7a2013d..37b8fd4 100644 --- a/lib/aiken/crypto/bls12_381/g2.ak +++ b/lib/aiken/crypto/bls12_381/g2.ak @@ -11,6 +11,7 @@ //// This module ensures that all operations respect the properties of the BLS12-381 curve and the mathematical structure of the G2 group. use aiken/builtin +use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/scalar.{Scalar} /// The compressed generator of the G2 group of the BLS12-381 curve. @@ -104,12 +105,12 @@ test sub_1() { /// Exponentiates a point in the G2 group with a `scalar`. /// This operation is equivalent to the repeated addition of the point with itself `e` times. -pub fn scale(point, e: Scalar) { +pub fn scale(point, e: State) { builtin.bls12_381_g2_scalar_mul(scalar.to_int(e), point) } test scale_1() { - expect Some(x) = scalar.new(2) + let x = scalar.from_int(2) builtin.bls12_381_g2_add(generator, generator) == scale(generator, x) } diff --git a/lib/aiken/crypto/bls12_381/scalar.ak b/lib/aiken/crypto/bls12_381/scalar.ak index cf028ad..24ef3a8 100644 --- a/lib/aiken/crypto/bls12_381/scalar.ak +++ b/lib/aiken/crypto/bls12_381/scalar.ak @@ -17,136 +17,166 @@ //// Additionally, it includes advanced operations such as exponentiation and calculation of multiplicative inverses, tailored for cryptographic applications. use aiken/builtin +use aiken/crypto/bitwise.{State, one, zero} /// The prime number defining the scalar field of the BLS12-381 curve. pub const field_prime = 52435875175126190479447740508185965837690552500527637822603658699938581184513 -/// Represents the additive identity (zero) in the `Scalar` field. -pub const zero: Scalar = Scalar(0) +pub const field_size = 32 -/// Represents the multiplicative identity (one) in the `Scalar` field. -pub const one: Scalar = Scalar(1) +pub type Scalar = + ByteArray -/// Opaque type representing an element of the finite field `Scalar`. -pub opaque type Scalar { - integer: Int, +test field_prime_1() { + builtin.integer_to_bytearray(True, 32, field_prime) == #"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" } // ## Constructing +/// Constructs a new `Scalar` element from a Big-Endian (most-significant bits first) `ByteArray`. +pub fn from_bytes(b: ByteArray) -> State { + b + |> builtin.bytearray_to_integer(True, _) + |> bitwise.from_int(field_prime) +} + +/// Constructs a new `Scalar` element from a Little-Endian (least-significant bits first) `ByteArray`. +pub fn from_bytes_little_endian(bytes: ByteArray) -> State { + bytes + |> builtin.bytearray_to_integer(False, _) + |> bitwise.from_int(field_prime) +} /// Constructs a new `Scalar` element from an integer, ensuring it's within the valid range of the field. -/// Returns `None` if the integer is negative or greater than the prime number defining the field. -pub fn new(n: Int) -> Option { - if n >= 0 && n < field_prime { - Some(Scalar(n)) - } else { - None - } +pub fn from_int(n: Int) -> State { + bitwise.from_int(n, field_prime) } test new_1() { + trace from_int(-1) and { - new(-1) == None, - new(field_prime) == None, - new(834884848) == Some(Scalar(834884848)), + ( from_int(-1) |> to_int ) == field_prime - 1, + ( from_int(field_prime) |> to_int ) == 0, + ( from_int(834884848) |> to_int ) == 834884848, } } -/// Constructs a new `Scalar` element from a Big-Endian (most-significant bits first) `ByteArray`. -pub fn from_bytearray_big_endian(bytes: ByteArray) -> Option { - new(builtin.bytearray_to_integer(True, bytes)) -} - test from_bytearray_big_endian_1() { - from_bytearray_big_endian(#"ffff00") == Some(Scalar(16776960)) -} - -/// Constructs a new `Scalar` element from a Little-Endian (least-significant bits first) `ByteArray`. -pub fn from_bytearray_little_endian(bytes: ByteArray) -> Option { - new(builtin.bytearray_to_integer(False, bytes)) + ( from_bytes(#"ffff00") |> to_int ) == 16776960 } test from_bytearray_little_endian_1() { - from_bytearray_little_endian(#"ffff00") == Some(Scalar(65535)) + ( from_bytes_little_endian(#"ffff00") |> to_int ) == 65535 } +type BitwiseScalarBytes = + fn(State, ByteArray) -> State + +type BitwiseScalarInt = + fn(State, Int) -> State + +type BitwiseScalarState = + fn(State, State) -> State + // ## Modifying /// Exponentiates an `Scalar` element by a non-negative integer exponent, using repeated squaring. /// Note that this function returns `scalar.zero` for negative exponents. /// A dedicated builtin function for this is in the making, see CIP 109. -pub fn scale(self: Scalar, e: Int) -> Scalar { - if e < 0 { - zero - } else if e == 0 { - one - } else if e % 2 == 0 { - scale(mul(self, self), e / 2) - } else { - mul(self, scale(mul(self, self), ( e - 1 ) / 2)) - } +pub fn scale(self: State, e: Int) -> State { + bitwise.scale(self, e, mul) } test scale_1() { + let x = from_int(834884848) + and { - scale(Scalar(834884848), -1) == zero, - scale(Scalar(834884848), 0) == one, - scale(Scalar(834884848), 1) == Scalar(834884848), - scale(Scalar(834884848), 2) == Scalar(697032709419983104), - scale(Scalar(834884848), 3) == Scalar(581942047655130761945608192), - scale(Scalar(field_prime - 4), 200) == Scalar( - 12843927705572658539565969578937286576443167978938369866871449552629978143484, - ), + ( x |> scale(-1) ) == zero, + ( x |> scale(0) ) == one, + ( x |> scale(1) ) == x, + ( x |> scale(2) |> to_int ) == 697032709419983104, + ( x |> scale(3) |> to_int ) == 581942047655130761945608192, + ( + from_int(field_prime - 4) + |> scale(200) + |> to_int + ) == 12843927705572658539565969578937286576443167978938369866871449552629978143484, } } /// A faster version of `scale` for the case where the exponent is a power of two. /// That is, the exponent `e = 2^k` for some non-negative integer `k`. Which is used alot in zk-SNARKs. -pub fn scale2(self: Scalar, k: Int) -> Scalar { - if k < 0 { - zero - } else { - do_scale2(self, k) - } -} - -fn do_scale2(self: Scalar, k: Int) -> Scalar { - if k == 0 { - self - } else { - do_scale2(mul(self, self), k - 1) - } +pub fn scale2(self: State, k: Int) -> State { + bitwise.scale2(self, k, mul) } test scale2_1() { + let x = from_int(834884848) + and { - scale2(Scalar(834884848), -1) == zero, - scale2(Scalar(834884848), 0) == scale(Scalar(834884848), 1), - scale2(Scalar(834884848), 1) == scale(Scalar(834884848), 2), - scale2(Scalar(834884848), 2) == scale(Scalar(834884848), 4), - scale2(Scalar(834884848), 3) == scale(Scalar(834884848), 8), - scale2(Scalar(834884848), 4) == scale(Scalar(834884848), 16), + scale2(x, -1) == zero, + scale2(x, 0) == scale(x, 1), + scale2(x, 1) == scale(x, 2), + scale2(x, 2) == scale(x, 4), + scale2(x, 3) == scale(x, 8), + scale2(x, 4) == scale(x, 16), } } // ## Combining +const add_s_scalar: BitwiseScalarState = bitwise.add_state(field_prime) + /// Adds two `Scalar` elements, ensuring the result stays within the finite field range. -pub fn add(left: Scalar, right: Scalar) -> Scalar { - Scalar(( left.integer + right.integer ) % field_prime) +pub fn add(left: State, right: State) -> State { + add_s_scalar(left, right) +} + +const add_bit_scalar: BitwiseScalarBytes = bitwise.add_bits(field_prime, True) + +pub fn add_bytes(intermediate: State, bytes: ByteArray) -> State { + add_bit_scalar(intermediate, bytes) +} + +const add_i_scalar: BitwiseScalarInt = bitwise.add_int(field_prime) + +pub fn add_int(intermediate: State, int: Int) -> State { + add_i_scalar(intermediate, int) } test add_1() { + let x = from_int(834884848) + let y = from_int(field_prime - 1) + let z = from_int(3) + and { - (add(Scalar(834884848), Scalar(834884848)) == Scalar(1669769696))?, - (add(Scalar(field_prime - 1), Scalar(1)) == Scalar(0))?, - (add(Scalar(3), Scalar(field_prime)) == Scalar(3))?, + (( add(x, x) |> to_int ) == 1669769696)?, + (add(y, one) == zero)?, + (add(z, add(y, one)) == z)?, } } /// Divides one `Scalar` element by another, returning `None` if the divisor is zero. -pub fn div(left: Scalar, right: Scalar) -> Option { +pub fn div(left: State, right: State) -> Option> { + if right == zero { + None + } else { + Some(mul(left, scale(right, field_prime - 2))) + } +} + +pub fn div_int(left: State, right: Int) -> Option> { + let right = from_int(right) + if right == zero { + None + } else { + Some(mul(left, scale(right, field_prime - 2))) + } +} + +pub fn div_bytes(left: State, right: ByteArray) -> Option> { + let right = from_bytes(right) + if right == zero { None } else { @@ -155,61 +185,77 @@ pub fn div(left: Scalar, right: Scalar) -> Option { } test div_1() { + let x = from_int(834884848) + and { - div(Scalar(834884848), Scalar(834884848)) == Some(Scalar(1)), - div(Scalar(834884848), zero) == None, - div(Scalar(field_prime - 1), Scalar(2)) == Some( - Scalar( + div(x, x) == Some(one), + div(x, zero) == None, + div(from_int(field_prime - 1), from_int(2)) == Some( + from_int( 26217937587563095239723870254092982918845276250263818911301829349969290592256, ), ), } } +const mul_s_scalar: BitwiseScalarState = bitwise.mul_state(field_prime) + /// Multiplies two `Scalar` elements, with the result constrained within the finite field. -pub fn mul(left: Scalar, right: Scalar) -> Scalar { - Scalar(left.integer * right.integer % field_prime) +pub fn mul(left: State, right: State) -> State { + mul_s_scalar(left, right) +} + +const mul_bit_scalar: BitwiseScalarBytes = bitwise.mul_bits(field_prime, True) + +pub fn mul_bytes(intermediate: State, bytes: ByteArray) -> State { + mul_bit_scalar(intermediate, bytes) +} + +const mul_i_scalar: BitwiseScalarInt = bitwise.mul_int(field_prime) + +pub fn mul_int(intermediate: State, int: Int) -> State { + mul_i_scalar(intermediate, int) } test mul_1() { + let x = from_int(834884848) and { - mul(Scalar(834884848), Scalar(834884848)) == Scalar(697032709419983104), - mul(zero, Scalar(834884848)) == zero, - mul(Scalar(field_prime - 1), Scalar(2)) == Scalar( + mul(x, x) == from_int(697032709419983104), + mul(zero, x) == zero, + mul(from_int(field_prime - 1), from_int(2)) == from_int( 52435875175126190479447740508185965837690552500527637822603658699938581184511, ), } } +const neg_scalar: fn(State) -> State = bitwise.neg(field_prime) + /// Calculates the additive inverse of a `Scalar` element. -pub fn neg(self: Scalar) -> Scalar { - // this is basicly sub(zero, self), but more efficient as it saves one modulo operation - if self.integer == 0 { - self - } else { - Scalar(field_prime - self.integer) - } +pub fn neg(intermediate: State) -> State { + neg_scalar(intermediate) } test neg_1() { + trace neg(zero) + and { - neg(Scalar(834884848)) == Scalar( + neg(from_int(834884848)) == from_int( 52435875175126190479447740508185965837690552500527637822603658699937746299665, ), neg(zero) == zero, - neg(one) == Scalar(field_prime - 1), + neg(one) == from_int(field_prime - 1), } } /// Calculates the multiplicative inverse of an `Scalar` element, returning `None` if the element is zero. -pub fn recip(self: Scalar) -> Option { +pub fn recip(self: State) -> Option> { div(one, self) } test recip_1() { and { - recip(Scalar(834884848)) == Some( - Scalar( + recip(from_int(834884848)) == Some( + from_int( 35891248691642227249400403463796410930702563777316955162085759263735363466421, ), ), @@ -217,39 +263,55 @@ test recip_1() { } } +const sub_s_scalar: BitwiseScalarState = bitwise.sub_state(field_prime) + /// Subtracts one `Scalar` element from another, with the result wrapped within the finite field range. -pub fn sub(left: Scalar, right: Scalar) -> Scalar { - Scalar(( left.integer - right.integer ) % field_prime) +pub fn sub(left: State, right: State) -> State { + sub_s_scalar(left, right) +} + +const sub_bit_scalar: BitwiseScalarBytes = bitwise.sub_bits(field_prime, True) + +pub fn sub_bytes(intermediate: State, bytes: ByteArray) -> State { + sub_bit_scalar(intermediate, bytes) +} + +const sub_i_scalar: BitwiseScalarInt = bitwise.sub_int(field_prime) + +pub fn sub_int(intermediate: State, int: Int) -> State { + sub_i_scalar(intermediate, int) } test sub_1() { + let x = from_int(834884848) + and { - (sub(Scalar(834884848), Scalar(834884848)) == zero)?, - (sub(zero, Scalar(5)) == Scalar(field_prime - 5))?, + (sub(x, x) == zero)?, + (sub(zero, from_int(5)) == from_int(field_prime - 5))?, } } // ## Transforming /// Converts a `Scalar` element back to its integer representation. -pub fn to_int(self: Scalar) -> Int { - self.integer -} - -test to_int_1() { - to_int(Scalar(834884848)) == 834884848 +pub fn to_int(s: State) -> Int { + bitwise.to_int(s) } /// Converts a `Scalar` element to a Big-Endian (most-significant bits first) `ByteArray`. -pub fn to_bytearray_big_endian(self: Scalar, size: Int) -> ByteArray { - builtin.integer_to_bytearray(True, size, self.integer) +pub fn to_bytes(s: State) -> ByteArray { + s |> bitwise.to_int |> builtin.integer_to_bytearray(True, field_size, _) } /// Converts a `Scalar` element to a Little-Endian (least-significant bits first) `ByteArray`. -pub fn to_bytearray_little_endian(self: Scalar, size: Int) -> ByteArray { - builtin.integer_to_bytearray(False, size, self.integer) +pub fn to_bytes_little_endian(s: State) -> ByteArray { + s |> bitwise.to_int |> builtin.integer_to_bytearray(False, field_size, _) +} + +test to_int_1() { + to_int(from_int(834884848)) == 834884848 } test to_bytearray_1() { - to_bytearray_big_endian(Scalar(16777215), 3) == #"ffffff" + ( to_bytes(from_int(16777215)) |> builtin.slice_bytearray(29, 32, _) ) == #"ffffff" } diff --git a/lib/aiken/crypto/int224.ak b/lib/aiken/crypto/int224.ak new file mode 100644 index 0000000..8befc41 --- /dev/null +++ b/lib/aiken/crypto/int224.ak @@ -0,0 +1,155 @@ +//// This module implements arithmetic operations in a constrained 224-bit integer field. +//// Operations are performed modulo $2^{224}$, providing a field for cryptographic operations +//// that require 28-byte values. +//// +//// The module provides functionality for basic arithmetic operations (addition, subtraction, +//// multiplication) within this constrained field, as well as conversion functions between +//// different representations. + +use aiken/builtin +use aiken/crypto/bitwise + +pub opaque type Bits224 { + Bits224 +} + +pub type State = + bitwise.State + +type Over = + fn(State, a) -> State + +/// The prime defining the 224-bit integer field $2^{224}$ +pub const field = + builtin.replicate_byte(28, 0) + |> builtin.cons_bytearray(1, _) + |> builtin.bytearray_to_integer(True, _) + +/// The field size, in **bytes**. +pub const field_size = 28 + +// ## Constructing + +/// Constructs a new `Bits224` element from a Big-Endian (most-significant bits first) `ByteArray`. +pub fn from_bytearray_big_endian(bytes: ByteArray) -> State { + bytes + |> builtin.bytearray_to_integer(True, _) + |> bitwise.from_int(field) +} + +/// Constructs a new `Bits224` element from a Little-Endian (least-significant bits first) `ByteArray`. +pub fn from_bytearray_little_endian(bytes: ByteArray) -> State { + bytes + |> builtin.bytearray_to_integer(False, _) + |> bitwise.from_int(field) +} + +/// Constructs a new `Bits224` element from an integer, ensuring it's within the valid range of the field. +pub fn from_int(int: Int) -> State { + bitwise.from_int(int, field) +} + +// ## Modifying + +/// Exponentiates a `Bits224` element by a non-negative integer exponent, using repeated squaring. +/// Note that this function returns `zero` for negative exponents. +pub fn scale(self: State, e: Int) -> State { + bitwise.scale(self, e, mul) +} + +/// A faster version of `scale` for the case where the exponent is a power of two. +/// That is, the exponent $e = 2^k$ for some non-negative integer $k$. +pub fn scale2(self: State, k: Int) -> State { + bitwise.scale2(self, k, mul) +} + +// ## Combining + +const add_s_bits224: Over = bitwise.add_state(field) + +/// Adds two `Bits224` elements, ensuring the result stays within the finite field range. +pub fn add(left: State, right: State) -> State { + add_s_bits224(left, right) +} + +const add_bit224: Over = bitwise.add_bits(field, True) + +/// Adds a ByteArray to a `Bits224` element, interpreting bytes as a big-endian number. +pub fn add_bytes(self: State, bytes: ByteArray) -> State { + add_bit224(self, bytes) +} + +const add_i224: Over = bitwise.add_int(field) + +/// Adds an integer to a `Bits224` element. +pub fn add_int(self: State, int: Int) -> State { + add_i224(self, int) +} + +const mul_s_bits224: Over = bitwise.mul_state(field) + +/// Multiplies two `Bits224` elements, with the result constrained within the finite field. +pub fn mul(left: State, right: State) -> State { + mul_s_bits224(left, right) +} + +const mul_bit224: Over = bitwise.mul_bits(field, True) + +/// Multiplies a `Bits224` element by a ByteArray, interpreting bytes as a big-endian number. +pub fn mul_bytes(self: State, bytes: ByteArray) -> State { + mul_bit224(self, bytes) +} + +const mul_i224: Over = bitwise.mul_int(field) + +/// Multiplies a `Bits224` element by an integer. +pub fn mul_int(self: State, int: Int) -> State { + mul_i224(self, int) +} + +const neg224: fn(State) -> State = bitwise.neg(field) + +/// Calculates the additive inverse of a `Bits224` element. +pub fn neg(self: State) -> State { + neg224(self) +} + +const sub_s_bits224: Over = bitwise.sub_state(field) + +/// Subtracts one `Bits224` element from another, with the result wrapped within the finite field range. +pub fn sub(left: State, right: State) -> State { + sub_s_bits224(left, right) +} + +const sub_bit224: Over = bitwise.sub_bits(field, True) + +/// Subtracts a `ByteArray` from a `Bits224` element, interpreting bytes as a big-endian number. +pub fn sub_bytes(self: State, bytes: ByteArray) -> State { + sub_bit224(self, bytes) +} + +const sub_i224: Over = bitwise.sub_int(field) + +/// Subtracts an integer from a `Bits224` element. +pub fn sub_int(self: State, int: Int) -> State { + sub_i224(self, int) +} + +// ## Transforming + +/// Converts a `Bits224` element to a Big-Endian (most-significant bits first) `ByteArray`. +pub fn to_bytearray_big_endian(self: State) -> ByteArray { + bitwise.to_int(self) + |> builtin.integer_to_bytearray(True, field_size, _) +} + +/// Converts a `Bits224` element to a Little-Endian (least-significant bits first) `ByteArray`. +pub fn to_bytearray_little_endian(self: State) -> ByteArray { + bitwise.to_int(self) + |> builtin.integer_to_bytearray(False, field_size, _) +} + +/// Converts a `Bits224` element back to its integer representation. +pub fn to_int(self: State) -> Int { + bitwise.to_int(self) +} diff --git a/lib/aiken/crypto/int224.tests.ak b/lib/aiken/crypto/int224.tests.ak new file mode 100644 index 0000000..ba31445 --- /dev/null +++ b/lib/aiken/crypto/int224.tests.ak @@ -0,0 +1,385 @@ +use aiken/builtin +use aiken/crypto/bitwise.{one, zero} +use aiken/crypto/int224.{ + add, add_bytes, add_int, field, from_bytearray_big_endian, + from_bytearray_little_endian, from_int, mul, mul_bytes, mul_int, neg, scale, + scale2, sub, sub_bytes, sub_int, to_bytearray_big_endian, + to_bytearray_little_endian, to_int, +} + +test equal_pad_for_addition() { + let a: ByteArray = #"acab" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytearray_big_endian + |> add_bytes(b) + |> to_bytearray_big_endian + + x == #"000000000000000000000000000000000000000000000000000177a9" +} + +test unequal_pad_for_addition() { + let a: ByteArray = #"acabbeefface" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytearray_big_endian + |> add_bytes(b) + |> to_bytearray_big_endian + + x == #"00000000000000000000000000000000000000000000acabbef0c5cc" +} + +// ## Test for constructing Hash224 elements + +test from_int_1() { + and { + ( from_int(-1) |> to_int ) == field - 1, + ( from_int(field) |> to_int ) == 0, + ( from_int(834884848) |> to_int ) == 834884848, + } +} + +test from_bytes_big_endian_1() { + ( from_bytearray_big_endian(#"ffff00") |> to_int ) == 16776960 +} + +test from_bytes_little_endian_1() { + ( from_bytearray_little_endian(#"ffff00") |> to_int ) == 65535 +} + +// ## Tests for modifying Hash224 elements + +test scale_1() { + let x = from_int(834884848) + + and { + ( x |> scale(-1) ) == zero, + ( x |> scale(0) ) == one, + ( x |> scale(1) ) == x, + ( x |> scale(2) |> to_int ) == 697032709419983104, + ( x |> scale(3) |> to_int ) == 581942047655130761945608192, + ( + from_int(field - 5) + |> scale(200) + |> to_int + ) != 0, + } +} + +test scale2_1() { + let a = from_int(2) + + and { + ( scale2(a, 0) |> to_int ) == 2, + ( scale2(a, 1) |> to_int ) == 4, + ( scale2(a, 2) |> to_int ) == 16, + ( scale2(a, 3) |> to_int ) == 256, + } +} + +// ## Tests for combining Hash224 elements + +test add_1() { + let x = from_int(834884848) + let y = from_int(field - 1) + let z = from_int(3) + + and { + (( add(x, x) |> to_int ) == 1669769696)?, + (add(y, one) == zero)?, + (add(z, add(y, one)) == z)?, + } +} + +test add_overflow_1() { + let a = from_int(field - 1) + let b = from_int(1) + + ( add(a, b) |> to_int ) == 0 +} + +test mul_1() { + let x = from_int(834884848) + and { + mul(x, x) == from_int(697032709419983104), + mul(zero, x) == zero, + mul(from_int(field - 1), from_int(2)) == from_int(field - 2), + } +} + +test neg_1() { + and { + neg(from_int(834884848)) == from_int(field - 834884848), + neg(zero) == zero, + neg(one) == from_int(field - 1), + } +} + +test sub_1() { + let x = from_int(834884848) + + and { + (sub(x, x) == zero)?, + (sub(zero, from_int(5)) == from_int(field - 5))?, + } +} + +// ## Tests for transforming Hash224 elements + +test to_int_1() { + to_int(from_int(834884848)) == 834884848 +} + +test to_bytes_1() { + ( + to_bytearray_big_endian(from_int(16777215)) + |> builtin.slice_bytearray(25, 28, _) + ) == #"ffffff" +} + +test to_bytes_little_endian_1() { + let value = 16777215 + let bytes = to_bytearray_little_endian(from_int(value)) + and { + ( bytes |> builtin.slice_bytearray(0, 3, _) ) == #"ffffff", + ( bytes |> builtin.bytearray_to_integer(False, _) ) == value, + } +} + +// ## Tests for various operations and edge cases + +test add_bytes_1() { + let x = from_int(100) + let result = add_bytes(x, #"0a") |> to_int + result == 110 +} + +test add_int_1() { + let x = from_int(100) + let result = add_int(x, 50) |> to_int + result == 150 +} + +test mul_bytes_1() { + let x = from_int(10) + let result = mul_bytes(x, #"0a") |> to_int + result == 100 +} + +test mul_int_1() { + let x = from_int(10) + let result = mul_int(x, 5) |> to_int + result == 50 +} + +test sub_bytes_1() { + let x = from_int(100) + let result = sub_bytes(x, #"0a") |> to_int + result == 90 +} + +test sub_int_1() { + let x = from_int(100) + let result = sub_int(x, 60) |> to_int + result == 40 +} + +test sub_underflow_1() { + let a = from_int(0) + let b = from_int(1) + + ( sub(a, b) |> to_int ) == field - 1 +} + +test overflow_handling() { + let max = from_int(field - 1) + and { + (add_int(max, 1) == zero)?, + (( add_int(max, 2) |> to_int ) == 1)?, + } +} + +test wraparound_behavior() { + let x = from_int(10) + let y = from_int(15) + and { + ( sub(x, y) |> to_int ) == field - 5, + (add(sub(x, y), y) == x)?, + } +} + +test from_bytes_tests() { + let a = #"acabbeefface" + let b = #"cafe" + + and { + ( from_bytearray_big_endian(a) |> to_int ) == 0xacabbeefface, + ( from_bytearray_big_endian(b) |> to_int ) == 0xcafe, + ( from_bytearray_little_endian(a) |> to_int ) == 0xcefaefbeabac, + ( from_bytearray_little_endian(b) |> to_int ) == 0xfeca, + } +} + +test add_tests() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + and { + ( + add(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(25, 28, _) + ) == #"0177a9", + add(from_int(0), from_bytearray_big_endian(#"01")) == from_bytearray_big_endian( + #"01", + ), + } +} + +test add_communitive() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + add(a, b) == add(b, a) +} + +test add_associativity() { + let a = from_bytearray_big_endian(#"0101") + let b = from_bytearray_big_endian(#"0202") + let c = from_bytearray_big_endian(#"0303") + + add(add(a, b), c) == add(a, add(b, c)) +} + +test add_identity() { + let a = from_bytearray_big_endian(#"01") + + add(zero, a) == a +} + +test add_bytes_tests() { + let a = from_int(0) + let b = from_bytearray_big_endian(#"acab") + + and { + ( + add_bytes(a, #"cafe") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(26, 28, _) + ) == #"cafe", + ( + add_bytes(b, #"cafe") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(25, 28, _) + ) == #"0177a9", + } +} + +test sub_tests() { + let a = from_bytearray_big_endian(#"0177a9") + let b = from_bytearray_big_endian(#"cafe") + + and { + ( + sub(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(26, 28, _) + ) == #"acab", + ( + sub( + from_bytearray_big_endian(#"77a9"), + from_bytearray_big_endian(#"cafe"), + ) + |> to_bytearray_big_endian + |> builtin.slice_bytearray(26, 28, _) + ) == #"acab", + } +} + +test sub_identity() { + let a = from_bytearray_big_endian(#"10") + + sub(a, zero) == a +} + +test mul_tests() { + let a = from_bytearray_big_endian(#"ffff") + let b = from_bytearray_big_endian(#"ffff") + + ( mul(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(24, 28, _) ) == #"fffe0001" +} + +test mul_communitive() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + mul(a, b) == mul(b, a) +} + +test mul_associativity() { + let a = from_bytearray_big_endian(#"33") + let b = from_bytearray_big_endian(#"44") + let c = from_bytearray_big_endian(#"55") + + mul(mul(a, b), c) == mul(a, mul(b, c)) +} + +test mul_identity() { + let a = from_bytearray_big_endian(#"acab") + + mul(one, a) == a +} + +test mul_bytes_tests() { + let a = from_int(0) + let b = from_bytearray_big_endian(#"0002") + + and { + mul_bytes(a, #"cafe") == zero, + ( + mul_bytes(b, #"0003") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(26, 28, _) + ) == #"0006", + } +} + +test bytes_conversion_roundtrip() { + let original = #"acabbeefface" + + and { + ( + to_bytearray_big_endian(from_bytearray_big_endian(original)) + |> builtin.slice_bytearray( + 28 - builtin.length_of_bytearray(original), + 28, + _, + ) + ) == original, + ( + to_bytearray_little_endian(from_bytearray_little_endian(original)) + |> builtin.slice_bytearray(0, builtin.length_of_bytearray(original), _) + ) == original, + } +} + +test operation_with_zero() { + and { + add(zero, zero) == zero, + mul(zero, from_int(123456)) == zero, + sub(zero, zero) == zero, + neg(zero) == zero, + } +} + +test operation_with_large_numbers() { + let almost_field_size = from_int(field - 1) + let large_num = from_int(0xffffffffffffffffffffff) + + and { + add(almost_field_size, one) == zero, + mul(large_num, large_num) != zero, + // Should be non-zero due to modular arithmetic + neg(almost_field_size) == one, + } +} diff --git a/lib/aiken/crypto/int256.ak b/lib/aiken/crypto/int256.ak new file mode 100644 index 0000000..99fe559 --- /dev/null +++ b/lib/aiken/crypto/int256.ak @@ -0,0 +1,154 @@ +//// This module implements arithmetic operations in a constrained 256-bit integer field. +//// Operations are performed modulo $2^{256}$, providing a field for cryptographic operations +//// that require 32-byte values. +//// +//// The module provides functionality for basic arithmetic operations (addition, subtraction, +//// multiplication) within this constrained field, as well as conversion functions between +//// different representations. + +use aiken/builtin +use aiken/crypto/bitwise + +/// The prime defining the 256-bit integer field $2^{256}$ +pub const field = + builtin.replicate_byte(32, 0) + |> builtin.cons_bytearray(1, _) + |> builtin.bytearray_to_integer(True, _) + +pub const field_size = 32 + +pub opaque type Bits256 { + Bits256 +} + +pub type State = + bitwise.State + +type Over = + fn(State, a) -> State + +// ## Constructing + +/// Constructs a new `Bits256` element from a Big-Endian (most-significant bits first) `ByteArray`. +pub fn from_bytearray_big_endian(bytes: ByteArray) -> State { + bytes + |> builtin.bytearray_to_integer(True, _) + |> bitwise.from_int(field) +} + +/// Constructs a new `Bits256` element from a Little-Endian (least-significant bits first) `ByteArray`. +pub fn from_bytearray_little_endian(bytes: ByteArray) -> State { + bytes + |> builtin.bytearray_to_integer(False, _) + |> bitwise.from_int(field) +} + +/// Constructs a new `Bits256` element from an integer, ensuring it's within the valid range of the field. +pub fn from_int(int: Int) -> State { + bitwise.from_int(int, field) +} + +// ## Modifying + +/// Exponentiates a `Bits256` element by a non-negative integer exponent, using repeated squaring. +/// Note that this function returns `zero` for negative exponents. +pub fn scale(self: State, e: Int) -> State { + bitwise.scale(self, e, mul) +} + +/// A faster version of `scale` for the case where the exponent is a power of two. +/// That is, the exponent $e = 2^k$ for some non-negative integer $k$. +pub fn scale2(self: State, k: Int) -> State { + bitwise.scale2(self, k, mul) +} + +// ## Combining + +const add_s_hash256: Over = bitwise.add_state(field) + +/// Adds two `Bits256` elements, ensuring the result stays within the finite field range. +pub fn add(left: State, right: State) -> State { + add_s_hash256(left, right) +} + +const add_bit256: Over = bitwise.add_bits(field, True) + +/// Adds a ByteArray to a `Bits256` element, interpreting bytes as a big-endian number. +pub fn add_bytes(self: State, bytes: ByteArray) -> State { + add_bit256(self, bytes) +} + +const add_i256: Over = bitwise.add_int(field) + +/// Adds an integer to a `Bits256` element. +pub fn add_int(self: State, int: Int) -> State { + add_i256(self, int) +} + +const mul_s_hash256: Over = bitwise.mul_state(field) + +/// Multiplies two `Bits256` elements, with the result constrained within the finite field. +pub fn mul(left: State, right: State) -> State { + mul_s_hash256(left, right) +} + +const mul_bit256: Over = bitwise.mul_bits(field, True) + +/// Multiplies a `Bits256` element by a ByteArray, interpreting bytes as a big-endian number. +pub fn mul_bytes(self: State, bytes: ByteArray) -> State { + mul_bit256(self, bytes) +} + +const mul_i256: Over = bitwise.mul_int(field) + +/// Multiplies a `Bits256` element by an integer. +pub fn mul_int(self: State, int: Int) -> State { + mul_i256(self, int) +} + +const neg256: fn(State) -> State = bitwise.neg(field) + +/// Calculates the additive inverse of a `Bits256` element. +pub fn neg(self: State) -> State { + neg256(self) +} + +const sub_s_hash256: Over = bitwise.sub_state(field) + +/// Subtracts one `Bits256` element from another, with the result wrapped within the finite field range. +pub fn sub(left: State, right: State) -> State { + sub_s_hash256(left, right) +} + +const sub_bit256: Over = bitwise.sub_bits(field, True) + +/// Subtracts a `ByteArray` from a `Bits256` element, interpreting bytes as a big-endian number. +pub fn sub_bytes(self: State, bytes: ByteArray) -> State { + sub_bit256(self, bytes) +} + +const sub_i256: Over = bitwise.sub_int(field) + +/// Subtracts an integer from a `Bits256` element. +pub fn sub_int(self: State, int: Int) -> State { + sub_i256(self, int) +} + +// ## Transforming + +/// Converts a `Bits256` element to a Big-Endian (most-significant bits first) `ByteArray`. +pub fn to_bytearray_big_endian(self: State) -> ByteArray { + bitwise.to_int(self) + |> builtin.integer_to_bytearray(True, field_size, _) +} + +/// Converts a `Bits256` element to a Little-Endian (least-significant bits first) `ByteArray`. +pub fn to_bytearray_little_endian(self: State) -> ByteArray { + bitwise.to_int(self) + |> builtin.integer_to_bytearray(False, field_size, _) +} + +/// Converts a `Bits256` element back to its integer representation. +pub fn to_int(self: State) -> Int { + bitwise.to_int(self) +} diff --git a/lib/aiken/crypto/int256.tests.ak b/lib/aiken/crypto/int256.tests.ak new file mode 100644 index 0000000..6fe7eb7 --- /dev/null +++ b/lib/aiken/crypto/int256.tests.ak @@ -0,0 +1,391 @@ +use aiken/builtin +use aiken/crypto/bitwise.{one, zero} +use aiken/crypto/int256.{ + add, add_bytes, add_int, field, from_bytearray_big_endian, + from_bytearray_little_endian, from_int, mul, mul_bytes, mul_int, neg, scale, + scale2, sub, sub_bytes, sub_int, to_bytearray_big_endian, + to_bytearray_little_endian, to_int, +} + +test equal_pad_for_addition() { + let a: ByteArray = #"acab" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytearray_big_endian + |> add_bytes(b) + |> to_bytearray_big_endian + + x == #"00000000000000000000000000000000000000000000000000000000000177a9" +} + +test unequal_pad_for_addition() { + let a: ByteArray = #"acabbeefface" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytearray_big_endian + |> add_bytes(b) + |> to_bytearray_big_endian + + x == #"0000000000000000000000000000000000000000000000000000acabbef0c5cc" +} + +// ## Test for constructing Hash256 elements + +test from_int_1() { + and { + ( from_int(-1) |> to_int ) == field - 1, + ( from_int(field) |> to_int ) == 0, + ( from_int(834884848) |> to_int ) == 834884848, + } +} + +test from_bytes_big_endian_1() { + ( from_bytearray_big_endian(#"ffff00") |> to_int ) == 16776960 +} + +test from_bytes_little_endian_1() { + ( from_bytearray_little_endian(#"ffff00") |> to_int ) == 65535 +} + +// ## Tests for modifying Hash256 elements + +test scale_1() { + let x = from_int(834884848) + + and { + ( x |> scale(-1) ) == zero, + ( x |> scale(0) ) == one, + ( x |> scale(1) ) == x, + ( x |> scale(2) |> to_int ) == 697032709419983104, + ( x |> scale(3) |> to_int ) == 581942047655130761945608192, + ( + from_int(field - 5) + |> scale(200) + |> to_int + ) != 0, + } +} + +test scale2_1() { + let a = from_int(2) + + and { + ( scale2(a, 0) |> to_int ) == 2, + ( scale2(a, 1) |> to_int ) == 4, + ( scale2(a, 2) |> to_int ) == 16, + ( scale2(a, 3) |> to_int ) == 256, + } +} + +// ## Tests for combining Hash256 elements + +test add_1() { + let x = from_int(834884848) + let y = from_int(field - 1) + let z = from_int(3) + + and { + (( add(x, x) |> to_int ) == 1669769696)?, + (add(y, one) == zero)?, + (add(z, add(y, one)) == z)?, + } +} + +test add_overflow_1() { + let a = from_int(field - 1) + let b = from_int(1) + + ( add(a, b) |> to_int ) == 0 +} + +test mul_1() { + let x = from_int(834884848) + and { + mul(x, x) == from_int(697032709419983104), + mul(zero, x) == zero, + mul(from_int(field - 1), from_int(2)) == from_int(field - 2), + } +} + +test neg_1() { + and { + neg(from_int(834884848)) == from_int(field - 834884848), + neg(zero) == zero, + neg(one) == from_int(field - 1), + } +} + +test sub_1() { + let x = from_int(834884848) + + and { + (sub(x, x) == zero)?, + (sub(zero, from_int(5)) == from_int(field - 5))?, + } +} + +// ## Tests for transforming Hash256 elements + +test to_int_1() { + to_int(from_int(834884848)) == 834884848 +} + +test to_bytes_1() { + ( + to_bytearray_big_endian(from_int(16777215)) + |> builtin.slice_bytearray(29, 32, _) + ) == #"ffffff" +} + +test to_bytes_little_endian_1() { + let value = 16777215 + let bytes = to_bytearray_little_endian(from_int(value)) + and { + ( bytes |> builtin.slice_bytearray(0, 3, _) ) == #"ffffff", + ( bytes |> builtin.bytearray_to_integer(False, _) ) == value, + } +} + +// ## Tests for various operations and edge cases + +test add_bytes_1() { + let x = from_int(100) + let result = add_bytes(x, #"0a") |> to_int + result == 110 +} + +test add_int_1() { + let x = from_int(100) + let result = add_int(x, 50) |> to_int + result == 150 +} + +test mul_bytes_1() { + let x = from_int(10) + let result = mul_bytes(x, #"0a") |> to_int + result == 100 +} + +test mul_int_1() { + let x = from_int(10) + let result = mul_int(x, 5) |> to_int + result == 50 +} + +test sub_bytes_1() { + let x = from_int(100) + + trace builtin.bytearray_to_integer(True, #"0a") + + let result = sub_bytes(x, #"0a") |> to_int + result == 90 +} + +test sub_int_1() { + let x = from_int(100) + let result = sub_int(x, 60) |> to_int + result == 40 +} + +test sub_underflow_1() { + let a = from_int(0) + let b = from_int(1) + + ( sub(a, b) |> to_int ) == field - 1 +} + +test overflow_handling() { + let max = from_int(field - 1) + and { + (add_int(max, 1) == zero)?, + (( add_int(max, 2) |> to_int ) == 1)?, + } +} + +test wraparound_behavior() { + let x = from_int(10) + let y = from_int(15) + and { + ( sub(x, y) |> to_int ) == field - 5, + (add(sub(x, y), y) == x)?, + } +} + +test from_bytes_tests() { + let a = #"acabbeefface" + let b = #"cafe" + + trace from_bytearray_little_endian(a) + trace 0xfaceefbeabac + + and { + ( from_bytearray_big_endian(a) |> to_int ) == 0xacabbeefface, + ( from_bytearray_big_endian(b) |> to_int ) == 0xcafe, + ( from_bytearray_little_endian(a) |> to_int ) == 0xcefaefbeabac, + ( from_bytearray_little_endian(b) |> to_int ) == 0xfeca, + } +} + +test add_tests() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + and { + ( + add(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(29, 32, _) + ) == #"0177a9", + add(from_int(0), from_bytearray_big_endian(#"01")) == from_bytearray_big_endian( + #"01", + ), + } +} + +test add_communitive() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + add(a, b) == add(b, a) +} + +test add_associativity() { + let a = from_bytearray_big_endian(#"0101") + let b = from_bytearray_big_endian(#"0202") + let c = from_bytearray_big_endian(#"0303") + + add(add(a, b), c) == add(a, add(b, c)) +} + +test add_identity() { + let a = from_bytearray_big_endian(#"01") + + add(zero, a) == a +} + +test add_bytes_tests() { + let a = from_int(0) + let b = from_bytearray_big_endian(#"acab") + + and { + ( + add_bytes(a, #"cafe") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(30, 32, _) + ) == #"cafe", + ( + add_bytes(b, #"cafe") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(29, 32, _) + ) == #"0177a9", + } +} + +test sub_tests() { + let a = from_bytearray_big_endian(#"0177a9") + let b = from_bytearray_big_endian(#"cafe") + + and { + ( + sub(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(30, 32, _) + ) == #"acab", + ( + sub( + from_bytearray_big_endian(#"77a9"), + from_bytearray_big_endian(#"cafe"), + ) + |> to_bytearray_big_endian + |> builtin.slice_bytearray(30, 32, _) + ) == #"acab", + } +} + +test sub_identity() { + let a = from_bytearray_big_endian(#"10") + + sub(a, zero) == a +} + +test mul_tests() { + let a = from_bytearray_big_endian(#"ffff") + let b = from_bytearray_big_endian(#"ffff") + + ( mul(a, b) |> to_bytearray_big_endian |> builtin.slice_bytearray(28, 32, _) ) == #"fffe0001" +} + +test mul_communitive() { + let a = from_bytearray_big_endian(#"acab") + let b = from_bytearray_big_endian(#"cafe") + + mul(a, b) == mul(b, a) +} + +test mul_associativity() { + let a = from_bytearray_big_endian(#"33") + let b = from_bytearray_big_endian(#"44") + let c = from_bytearray_big_endian(#"55") + + mul(mul(a, b), c) == mul(a, mul(b, c)) +} + +test mul_identity() { + let a = from_bytearray_big_endian(#"acab") + + mul(one, a) == a +} + +test mul_bytes_tests() { + let a = from_int(0) + let b = from_bytearray_big_endian(#"0002") + + and { + mul_bytes(a, #"cafe") == zero, + ( + mul_bytes(b, #"0003") + |> to_bytearray_big_endian + |> builtin.slice_bytearray(30, 32, _) + ) == #"0006", + } +} + +test bytes_conversion_roundtrip() { + let original = #"acabbeefface" + + and { + ( + to_bytearray_big_endian(from_bytearray_big_endian(original)) + |> builtin.slice_bytearray( + 32 - builtin.length_of_bytearray(original), + 32, + _, + ) + ) == original, + ( + to_bytearray_little_endian(from_bytearray_little_endian(original)) + |> builtin.slice_bytearray(0, builtin.length_of_bytearray(original), _) + ) == original, + } +} + +test operation_with_zero() { + and { + add(zero, zero) == zero, + mul(zero, from_int(123456)) == zero, + sub(zero, zero) == zero, + neg(zero) == zero, + } +} + +test operation_with_large_numbers() { + let almost_field_size = from_int(field - 1) + let large_num = from_int(0xffffffffffffffffffffffffffffff) + + and { + add(almost_field_size, one) == zero, + mul(large_num, large_num) != zero, + // Should be non-zero due to modular arithmetic + neg(almost_field_size) == one, + } +} diff --git a/lib/aiken/primitive/bytearray.ak b/lib/aiken/primitive/bytearray.ak index d2f125f..1dc2414 100644 --- a/lib/aiken/primitive/bytearray.ak +++ b/lib/aiken/primitive/bytearray.ak @@ -666,3 +666,15 @@ test starts_with_5() { test starts_with_6() { !starts_with("foo", "foo_") } + +pub fn and_bytes(left: ByteArray, right: ByteArray, pad_end: Bool) -> ByteArray { + builtin.and_bytearray(pad_end, left, right) +} + +pub fn or_bytes(left: ByteArray, right: ByteArray, pad_end: Bool) -> ByteArray { + builtin.or_bytearray(pad_end, left, right) +} + +pub fn xor_bytes(left: ByteArray, right: ByteArray, pad_end: Bool) -> ByteArray { + builtin.xor_bytearray(pad_end, left, right) +}