diff --git a/aiken.toml b/aiken.toml index 8333752..5b312cb 100644 --- a/aiken.toml +++ b/aiken.toml @@ -1,6 +1,6 @@ name = "aiken-lang/stdlib" version = "main" -compiler = "v1.1.11" +compiler = "v1.1.12" 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..8da6da2 --- /dev/null +++ b/lib/aiken/crypto/bitwise.ak @@ -0,0 +1,134 @@ +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 alot 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..f011b9f --- /dev/null +++ b/lib/aiken/crypto/int224.ak @@ -0,0 +1,165 @@ +//// 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.{State} + +pub type Hash224 = + ByteArray + +/// The prime defining the 224-bit integer field (2^224) +pub const hash224_field = + builtin.replicate_byte(28, 0) + |> builtin.cons_bytearray(1, _) + |> builtin.bytearray_to_integer(True, _) + +pub const field_size = 28 + +// ## Constructing + +/// Constructs a new `Hash224` element from a Big-Endian (most-significant bits first) `ByteArray`. +pub fn from_bytes(bytes: Hash224) -> State { + bytes + |> builtin.bytearray_to_integer(True, _) + |> bitwise.from_int(hash224_field) +} + +/// Constructs a new `Hash224` 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(hash224_field) +} + +/// Constructs a new `Hash224` 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, hash224_field) +} + +type Bitwise224Bytes = + fn(State, ByteArray) -> State + +type Bitwise224Int = + fn(State, Int) -> State + +type Bitwise224State = + fn(State, State) -> State + +// ## Modifying + +/// Exponentiates a `Hash224` 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_hash224: Bitwise224State = bitwise.add_state(hash224_field) + +/// Adds two `Hash224` elements, ensuring the result stays within the finite field range. +pub fn add(left: State, right: State) -> State { + add_s_hash224(left, right) +} + +const add_bit224: Bitwise224Bytes = bitwise.add_bits(hash224_field, True) + +/// Adds a ByteArray to a `Hash224` element, interpreting bytes as a big-endian number. +pub fn add_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + add_bit224(intermediate, bytes) +} + +const add_i224: Bitwise224Int = bitwise.add_int(hash224_field) + +/// Adds an integer to a `Hash224` element. +pub fn add_int(intermediate: State, int: Int) -> State { + add_i224(intermediate, int) +} + +const mul_s_hash224: Bitwise224State = bitwise.mul_state(hash224_field) + +/// Multiplies two `Hash224` elements, with the result constrained within the finite field. +pub fn mul(left: State, right: State) -> State { + mul_s_hash224(left, right) +} + +const mul_bit224: Bitwise224Bytes = bitwise.mul_bits(hash224_field, True) + +/// Multiplies a `Hash224` element by a ByteArray, interpreting bytes as a big-endian number. +pub fn mul_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + mul_bit224(intermediate, bytes) +} + +const mul_i224: Bitwise224Int = bitwise.mul_int(hash224_field) + +/// Multiplies a `Hash224` element by an integer. +pub fn mul_int(intermediate: State, int: Int) -> State { + mul_i224(intermediate, int) +} + +const neg224: fn(State) -> State = bitwise.neg(hash224_field) + +/// Calculates the additive inverse of a `Hash224` element. +pub fn neg(intermediate: State) -> State { + neg224(intermediate) +} + +const sub_s_hash224: Bitwise224State = bitwise.sub_state(hash224_field) + +/// Subtracts one `Hash224` element from another, with the result wrapped within the finite field range. +pub fn sub(left: State, right: State) -> State { + sub_s_hash224(left, right) +} + +const sub_bit224: Bitwise224Bytes = bitwise.sub_bits(hash224_field, True) + +/// Subtracts a ByteArray from a `Hash224` element, interpreting bytes as a big-endian number. +pub fn sub_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + sub_bit224(intermediate, bytes) +} + +const sub_i224: Bitwise224Int = bitwise.sub_int(hash224_field) + +/// Subtracts an integer from a `Hash224` element. +pub fn sub_int(intermediate: State, int: Int) -> State { + sub_i224(intermediate, int) +} + +// ## Transforming + +/// Converts a `Hash224` element back to its integer representation. +pub fn to_int(intermediate: State) -> Int { + bitwise.to_int(intermediate) +} + +/// Converts a `Hash224` element to a Big-Endian (most-significant bits first) `ByteArray`. +pub fn to_bytes(intermediate: State) -> ByteArray { + bitwise.to_int(intermediate) + |> builtin.integer_to_bytearray(True, field_size, _) +} + +/// Converts a `Hash224` element to a Little-Endian (least-significant bits first) `ByteArray`. +pub fn to_bytes_little_endian(intermediate: State) -> ByteArray { + bitwise.to_int(intermediate) + |> builtin.integer_to_bytearray(False, field_size, _) +} diff --git a/lib/aiken/crypto/int256.ak b/lib/aiken/crypto/int256.ak new file mode 100644 index 0000000..867bbfe --- /dev/null +++ b/lib/aiken/crypto/int256.ak @@ -0,0 +1,165 @@ +//// 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.{State} + +/// The prime defining the 256-bit integer field (2^256) +pub const hash256_field = + builtin.replicate_byte(32, 0) + |> builtin.cons_bytearray(1, _) + |> builtin.bytearray_to_integer(True, _) + +pub const field_size = 32 + +pub type Hash256 = + ByteArray + +// ## Constructing + +/// Constructs a new `Hash256` element from a Big-Endian (most-significant bits first) `ByteArray`. +pub fn from_bytes(bytes: Hash256) -> State { + bytes + |> builtin.bytearray_to_integer(True, _) + |> bitwise.from_int(hash256_field) +} + +/// Constructs a new `Hash256` 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(hash256_field) +} + +/// Constructs a new `Hash256` 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, hash256_field) +} + +type Bitwise256Bytes = + fn(State, ByteArray) -> State + +type Bitwise256Int = + fn(State, Int) -> State + +type Bitwise256State = + fn(State, State) -> State + +// ## Modifying + +/// Exponentiates a `Hash256` 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: Bitwise256State = bitwise.add_state(hash256_field) + +/// Adds two `Hash256` 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: Bitwise256Bytes = bitwise.add_bits(hash256_field, True) + +/// Adds a ByteArray to a `Hash256` element, interpreting bytes as a big-endian number. +pub fn add_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + add_bit256(intermediate, bytes) +} + +const add_i256: Bitwise256Int = bitwise.add_int(hash256_field) + +/// Adds an integer to a `Hash256` element. +pub fn add_int(intermediate: State, int: Int) -> State { + add_i256(intermediate, int) +} + +const mul_s_hash256: Bitwise256State = bitwise.mul_state(hash256_field) + +/// Multiplies two `Hash256` 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: Bitwise256Bytes = bitwise.mul_bits(hash256_field, True) + +/// Multiplies a `Hash256` element by a ByteArray, interpreting bytes as a big-endian number. +pub fn mul_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + mul_bit256(intermediate, bytes) +} + +const mul_i256: Bitwise256Int = bitwise.mul_int(hash256_field) + +/// Multiplies a `Hash256` element by an integer. +pub fn mul_int(intermediate: State, int: Int) -> State { + mul_i256(intermediate, int) +} + +const neg256: fn(State) -> State = bitwise.neg(hash256_field) + +/// Calculates the additive inverse of a `Hash256` element. +pub fn neg(intermediate: State) -> State { + neg256(intermediate) +} + +const sub_s_hash256: Bitwise256State = bitwise.sub_state(hash256_field) + +/// Subtracts one `Hash256` 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: Bitwise256Bytes = bitwise.sub_bits(hash256_field, True) + +/// Subtracts a ByteArray from a `Hash256` element, interpreting bytes as a big-endian number. +pub fn sub_bytes( + intermediate: State, + bytes: ByteArray, +) -> State { + sub_bit256(intermediate, bytes) +} + +const sub_i256: Bitwise256Int = bitwise.sub_int(hash256_field) + +/// Subtracts an integer from a `Hash256` element. +pub fn sub_int(intermediate: State, int: Int) -> State { + sub_i256(intermediate, int) +} + +// ## Transforming + +/// Converts a `Hash256` element back to its integer representation. +pub fn to_int(intermediate: State) -> Int { + bitwise.to_int(intermediate) +} + +/// Converts a `Hash256` element to a Big-Endian (most-significant bits first) `ByteArray`. +pub fn to_bytes(intermediate: State) -> ByteArray { + bitwise.to_int(intermediate) + |> builtin.integer_to_bytearray(True, field_size, _) +} + +/// Converts a `Hash256` element to a Little-Endian (least-significant bits first) `ByteArray`. +pub fn to_bytes_little_endian(intermediate: State) -> ByteArray { + bitwise.to_int(intermediate) + |> builtin.integer_to_bytearray(False, field_size, _) +} diff --git a/lib/aiken/crypto/int256.tests.ak b/lib/aiken/crypto/int256.tests.ak new file mode 100644 index 0000000..96c3738 --- /dev/null +++ b/lib/aiken/crypto/int256.tests.ak @@ -0,0 +1,136 @@ +use aiken/crypto/int256.{add_bytes, from_bytes, to_bytes} + +test equal_pad_for_addition() { + let a: ByteArray = #"acab" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytes + |> add_bytes(b) + |> to_bytes + + x == #"00000000000000000000000000000000000000000000000000000000000177a9" +} + +test unequal_pad_for_addition() { + let a: ByteArray = #"acabbeefface" + let b: ByteArray = #"cafe" + + let x = + a + |> from_bytes + |> add_bytes(b) + |> to_bytes + + x == #"0000000000000000000000000000000000000000000000000000acabbef0c5cc" +} +// test unequal_pad_for_addition1() { +// let a: ByteArray = #"acabbeefface" +// let b: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_addition(a, b) +// and { +// new_a == #"00acabbeefface", +// new_b == #"0000000000cafe", +// } +// } + +// test unequal_pad_for_addition2() { +// let b: ByteArray = #"acabbeefface" +// let a: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_addition(a, b) +// and { +// new_a == #"0000000000cafe", +// new_b == #"00acabbeefface", +// } +// } + +// test pad_for_addition_does_works() { +// let b: ByteArray = #"acab" +// let a: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_addition(a, b) +// and { +// add(a, b) == #"77a9", +// add(new_a, new_b) == #"0177a9", +// } +// } + +// test emptiness_is_empty() { +// add(#"", #"") == #"" +// } + +// test adding_not_equal_length_does_not_work() fail { +// add(#"00", #"acab") == #"acab" +// } + +// test add_communitive() { +// add(#"acab", #"cafe") == add(#"cafe", #"acab") +// } + +// test add_associativity() { +// ( add(#"0101", #"0202") |> add(#"0303") ) == ( +// add(#"0202", #"0303") |> add(#"0101") +// ) +// } + +// test add_identity() { +// add(#"00", #"01") == #"01" +// } + +// test subtracting_does_work1() { +// subtract(#"0177a9", #"00cafe") == #"00acab" +// } + +// test subtracting_does_work2() { +// subtract(#"77a9", #"cafe") == #"acab" +// } + +// test subtracting_not_equal_length_does_not_work() fail { +// subtract(#"0177a9", #"cafe") == #"acab" +// } + +// test subtract_is_not_communitive() fail { +// subtract(#"acab", #"cafe") == add(#"cafe", #"acab") +// } + +// test subtract_identity() { +// subtract(#"10", #"00") == #"10" +// } + +// test equal_pad_for_multiply() { +// let a: ByteArray = #"acab" +// let b: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_multiply(a, b) +// and { +// new_a == #"0000acab", +// new_b == #"0000cafe", +// } +// } + +// test unequal_pad_for_multiply1() { +// let a: ByteArray = #"acabbeefface" +// let b: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_multiply(a, b) +// and { +// new_a == #"000000000000acabbeefface", +// new_b == #"00000000000000000000cafe", +// } +// } + +// test unequal_pad_for_multiply2() { +// let b: ByteArray = #"acabbeefface" +// let a: ByteArray = #"cafe" +// let (new_a, new_b) = pad_for_multiply(a, b) +// and { +// new_a == #"00000000000000000000cafe", +// new_b == #"000000000000acabbeefface", +// } +// } + +// test simple_multiply1() { +// multiply(#"0000ffff", #"0000ffff") == #"fffe0001" +// } + +// test multiply_communitive() { +// multiply(#"0000acab", #"0000cafe") == multiply(#"0000cafe", #"0000acab") +// } 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) +}