├── .gitignore ├── .rustfmt.toml ├── src ├── serde_test.rs ├── serde.rs ├── hash_test.rs ├── hash.rs ├── error.rs ├── lib.rs ├── ecdsa.rs ├── ecdsa_test.rs ├── types_test.rs ├── types.rs ├── utils.rs └── bn256.json ├── Cargo.toml ├── LICENSE ├── examples └── bn254.rs ├── .github └── workflows │ └── push.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea 4 | Cargo.lock 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Stable Rustfmt Flags 2 | edition = "2021" 3 | max_width = 120 4 | merge_derives = true 5 | newline_style = "Unix" 6 | use_field_init_shorthand = true 7 | 8 | # Unstable Rustfmt Flags 9 | condense_wildcard_suffixes = true 10 | enum_discrim_align_threshold = 120 11 | error_on_unformatted = true 12 | format_macro_matchers = true 13 | group_imports = "StdExternalCrate" 14 | imports_granularity = "Crate" 15 | imports_layout = "HorizontalVertical" 16 | reorder_impl_items = true 17 | struct_field_align_threshold = 120 18 | style_edition = "2021" 19 | wrap_comments = true 20 | -------------------------------------------------------------------------------- /src/serde_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | pub fn test_serde_private_key() { 5 | let pk = PrivateKey::random(&mut rand::rngs::OsRng); 6 | let json = serde_json::to_value(&pk).unwrap(); 7 | let pk_from_json: PrivateKey = serde_json::from_value(json).unwrap(); 8 | assert_eq!(pk_from_json, pk); 9 | } 10 | 11 | #[test] 12 | pub fn test_serde_public_key() { 13 | let pk = PrivateKey::random(&mut rand::rngs::OsRng); 14 | let pubk = PublicKey::from_private_key(&pk); 15 | let json = serde_json::to_value(pubk).unwrap(); 16 | let pubk_from_json: PublicKey = serde_json::from_value(json).unwrap(); 17 | assert_eq!(pubk_from_json, pubk); 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bn254" 3 | version = "0.0.1" 4 | description = "Open source implementation of Barreto-Naehrig (BN) aggregate signatures written in Rust" 5 | keywords = ["bn254", "bn", "bn256", "bn128", "bls"] 6 | categories = ["cryptography", "crypto", "algorithms"] 7 | 8 | license = "MIT" 9 | edition = "2021" 10 | homepage = "https://github.com/sedaprotocol/bn254" 11 | documentation = "https://docs.rs/bn254/" 12 | repository = "https://github.com/sedaprotocol/bn254" 13 | readme = "README.md" 14 | 15 | [features] 16 | default = [] 17 | serde = [] 18 | 19 | [package.metadata.docs.rs] 20 | features = ["serde"] 21 | rustdoc-args = ["--cfg", "docsrs"] 22 | 23 | [dependencies] 24 | bn = { package = "zeropool-bn", features = [], version = "0.5.11" } 25 | borsh = "0.9" 26 | byteorder = "1.4" 27 | hex = "0.4" 28 | rand = { version = "0.8", default-features = false } 29 | serde = { version = "1.0", optional = false } 30 | sha2 = "0.10" 31 | thiserror = "1.0" 32 | 33 | [dev-dependencies] 34 | rand = { version = "0.8", default-features = false, features = ["getrandom"] } 35 | serde_json = "1.0.94" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Open Oracle Association, Switzerland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/bn254.rs: -------------------------------------------------------------------------------- 1 | use bn254::{PrivateKey, PublicKey, ECDSA}; 2 | 3 | fn main() { 4 | // Inputs: Secret Key, Public Key (derived) & Message 5 | 6 | // Secret key one 7 | let private_key_1 = 8 | PrivateKey::try_from("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap(); 9 | 10 | // Secret key two 11 | let private_key_2 = 12 | PrivateKey::try_from("a55e93edb1350916bf5beea1b13d8f198ef410033445bcb645b65be5432722f1").unwrap(); 13 | 14 | // Derive public keys from secret key 15 | let public_key_1 = PublicKey::from_private_key(&private_key_1); 16 | let public_key_2 = PublicKey::from_private_key(&private_key_2); 17 | 18 | let message: &[u8] = b"sample"; 19 | 20 | // Sign identical message with two different secret keys 21 | let signature_1 = ECDSA::sign(message, &private_key_1).unwrap(); 22 | let signature_2 = ECDSA::sign(message, &private_key_2).unwrap(); 23 | 24 | // Aggregate public keys 25 | let aggregate_pub_key = public_key_1 + public_key_2; 26 | 27 | // Aggregate signatures 28 | let aggregate_sig = signature_1 + signature_2; 29 | 30 | // Check whether the aggregate signature corresponds to the aggregated 31 | // public_key 32 | ECDSA::verify(message, &aggregate_sig, &aggregate_pub_key).unwrap(); 33 | println!("Successful aggregate signature verification"); 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Push 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | env: 8 | RUST_BACKTRACE: 1 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | fmt: 13 | name: Fmt Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Install Rust Nightly 20 | uses: dtolnay/rust-toolchain@nightly 21 | with: 22 | toolchain: nightly 23 | components: rustfmt 24 | 25 | - name: Format Check 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: fmt 29 | args: --all -- --check 30 | 31 | clippy_and_test: 32 | name: Clippy and Test 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v3 37 | 38 | - name: Install Rust Stable 39 | uses: dtolnay/rust-toolchain@stable 40 | with: 41 | toolchain: stable 42 | components: clippy 43 | 44 | - name: Cache cargo registry 45 | uses: actions/cache@v3 46 | continue-on-error: false 47 | with: 48 | path: | 49 | ~/.cargo/registry 50 | ~/.cargo/git 51 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 52 | restore-keys: | 53 | ${{ runner.os }}-cargo- 54 | 55 | - name: Clippy Check 56 | env: 57 | RUSTFLAGS: '-D warnings' 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: --all-features 62 | 63 | - name: Test 64 | uses: actions-rs/cargo@v1 65 | with: 66 | command: test 67 | args: --all-features -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::Error as DeError, 3 | ser::{Error, SerializeSeq}, 4 | Deserialize, 5 | Serialize, 6 | }; 7 | 8 | use crate::{PrivateKey, PublicKey}; 9 | 10 | impl Serialize for PrivateKey { 11 | fn serialize(&self, serializer: S) -> Result 12 | where 13 | S: serde::Serializer, 14 | { 15 | let bytes = self.to_bytes().map_err(S::Error::custom)?; 16 | let mut seq = serializer.serialize_seq(Some(bytes.len()))?; 17 | for byte in bytes { 18 | seq.serialize_element(&byte)?; 19 | } 20 | seq.end() 21 | } 22 | } 23 | 24 | impl<'de> Deserialize<'de> for PrivateKey { 25 | fn deserialize(deserializer: D) -> Result 26 | where 27 | D: serde::Deserializer<'de>, 28 | { 29 | let bytes = <[u8; 32]>::deserialize(deserializer)?; 30 | PrivateKey::try_from(bytes.as_slice()).map_err(D::Error::custom) 31 | } 32 | } 33 | 34 | impl Serialize for PublicKey { 35 | fn serialize(&self, serializer: S) -> Result 36 | where 37 | S: serde::Serializer, 38 | { 39 | let bytes = self.to_compressed().map_err(S::Error::custom)?; 40 | let mut seq = serializer.serialize_seq(Some(bytes.len()))?; 41 | for byte in bytes { 42 | seq.serialize_element(&byte)?; 43 | } 44 | seq.end() 45 | } 46 | } 47 | 48 | impl<'de> Deserialize<'de> for PublicKey { 49 | fn deserialize(deserializer: D) -> Result 50 | where 51 | D: serde::Deserializer<'de>, 52 | { 53 | let bytes = Vec::::deserialize(deserializer)?; 54 | PublicKey::from_compressed(bytes).map_err(D::Error::custom) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/hash_test.rs: -------------------------------------------------------------------------------- 1 | use bn::{arith, Fq}; 2 | 3 | use crate::{ 4 | hash::{hash_to_try_and_increment, LAST_MULTIPLE_OF_FQ_MODULUS_LOWER_THAN_2_256}, 5 | utils, 6 | }; 7 | 8 | /// Test for the `hash_to_try_and_increment` function with own test vector 9 | #[test] 10 | fn test_hash_to_try_and_increment_1() { 11 | // Data to be hashed with TAI (ASCII "sample") 12 | let data = hex::decode("73616d706c65").unwrap(); 13 | let hash_point = hash_to_try_and_increment(data).unwrap(); 14 | let hash_bytes = utils::g1_to_compressed(hash_point).unwrap(); 15 | 16 | let expected_hash = "0211e028f08c500889891cc294fe758a60e84495ec1e2d0bce208c9fc67b6486fd"; 17 | assert_eq!(hex::encode(hash_bytes), expected_hash); 18 | } 19 | 20 | /// Test for the `hash_to_try_and_increment` function with own test vector 21 | #[test] 22 | fn test_hash_to_try_and_increment_2() { 23 | // Data to be hashed with TAI (ASCII "hello") 24 | let data = hex::decode("68656c6c6f").unwrap(); 25 | let hash_point = hash_to_try_and_increment(data).unwrap(); 26 | let hash_bytes = utils::g1_to_compressed(hash_point).unwrap(); 27 | 28 | let expected_hash = "0200b201235f522abbd3863b7496dfa213be0ed1f4c7a22196d8afddec7e64c8ec"; 29 | assert_eq!(hex::encode(hash_bytes), expected_hash); 30 | } 31 | 32 | /// Test for the `hash_to_try_and_increment` valid range 33 | #[test] 34 | fn test_hash_to_try_valid_range() { 35 | let modulus = Fq::modulus(); 36 | let mut last_multiple = arith::U256([5, 0]); 37 | let mut overflow_multiple = arith::U256([6, 0]); 38 | let max_value = arith::U256([0xffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff]); 39 | last_multiple.mul(&modulus, &max_value, 1); 40 | assert_eq!(last_multiple, LAST_MULTIPLE_OF_FQ_MODULUS_LOWER_THAN_2_256); 41 | overflow_multiple.mul(&modulus, &max_value, 1); 42 | assert!(overflow_multiple < modulus) 43 | } 44 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | use bn::{arith, Fq, G1}; 2 | use sha2::{Digest, Sha256}; 3 | 4 | use crate::{ 5 | error::{Error, Result}, 6 | utils, 7 | }; 8 | 9 | /// This is 0xf1f5883e65f820d099915c908786b9d3f58714d70a38f4c22ca2bc723a70f263, 10 | /// the last mulitple of the modulus before 2^256 11 | pub(crate) const LAST_MULTIPLE_OF_FQ_MODULUS_LOWER_THAN_2_256: arith::U256 = arith::U256([ 12 | 0xf587_14d7_0a38_f4c2_2ca2_bc72_3a70_f263, 13 | 0xf1f5_883e_65f8_20d0_9991_5c90_8786_b9d3, 14 | ]); 15 | 16 | /// Function to convert a `Hash(DATA|COUNTER)` to a point in the curve. 17 | /// Similar to [VRF-draft-05](https://tools.ietf.org/pdf/draft-irtf-cfrg-vrf-05) (section 5.4.1.1). 18 | /// 19 | /// Point multiplication by the cofactor is not required for curve `bn256` as it 20 | /// has cofactor 1. 21 | /// 22 | /// # Arguments 23 | /// 24 | /// * `msg` - A slice containing the input data. 25 | /// 26 | /// # Returns 27 | /// 28 | /// * If successful, a point in the [G1] group representing the hashed point. 29 | pub(crate) fn hash_to_try_and_increment>(message: T) -> Result { 30 | // Add counter suffix 31 | // This message should be: ciphersuite || 0x01 || message || ctr 32 | // For the moment we work with message || ctr until a tag is decided 33 | let mut v = [message.as_ref(), &[0x00]].concat(); 34 | let position = v.len() - 1; 35 | 36 | // `Hash(data||ctr)` 37 | // The modulus of bn256 is low enough to trigger several iterations of this loop 38 | // We instead compute attempted_hash = `Hash(data||ctr)` mod Fq::modulus 39 | // This should trigger less iterations of the loop 40 | let point = (0..255).find_map(|ctr| { 41 | v[position] = ctr; 42 | let hash = Sha256::digest(&v); 43 | // this should never fail as the length of sha256 is max 256 44 | let attempted_hash = arith::U256::from_slice(&hash).unwrap(); 45 | 46 | // Reducing the hash modulo the field modulus biases point odds 47 | // As a prevention, we should discard hashes above the highest multiple of the 48 | // modulo 49 | if attempted_hash >= LAST_MULTIPLE_OF_FQ_MODULUS_LOWER_THAN_2_256 { 50 | return None; 51 | } 52 | 53 | let module_hash = utils::mod_u256(attempted_hash, Fq::modulus()); 54 | let mut s = [0u8; 32]; 55 | module_hash 56 | .to_big_endian(&mut s) 57 | .ok() 58 | .and_then(|_| utils::arbitrary_string_to_g1(&s).ok()) 59 | }); 60 | 61 | // Return an error if no valid point was found 62 | point.ok_or(Error::HashToPointError) 63 | } 64 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Errors returned by the bn256 library 2 | use bn::{CurveError, FieldError, GroupError}; 3 | use thiserror::Error as ThisError; 4 | 5 | #[derive(ThisError, Debug)] 6 | pub enum Error { 7 | #[error("errored to find a valid point while converting hash to point")] 8 | HashToPointError, 9 | #[error("errored to get data from an index out of bounds")] 10 | IndexOutOfBounds, 11 | #[error("errored to create group or field due to invalid input encoding")] 12 | InvalidEncoding, 13 | #[error("errored to map point to a curve")] 14 | InvalidGroupPoint, 15 | #[error("errored to create group or field due to invalid input length")] 16 | InvalidLength, 17 | #[error("errored to create a field element")] 18 | NotMemberError, 19 | #[error("errored to convert to affine coordinates")] 20 | ToAffineConversion, 21 | #[error("Point was already in affine coordinates (division-by-zero)")] 22 | PointInJacobian, 23 | #[error("Bn254 verification failed")] 24 | VerificationFailed, 25 | #[error("Serialization failed")] 26 | SerializationError, 27 | #[error(transparent)] 28 | HexDecodeFailed(#[from] hex::FromHexError), 29 | } 30 | 31 | impl From for Error { 32 | fn from(error: CurveError) -> Self { 33 | match error { 34 | CurveError::InvalidEncoding => Error::InvalidEncoding, 35 | CurveError::NotMember => Error::NotMemberError, 36 | CurveError::Field(field_error) => field_error.into(), 37 | CurveError::ToAffineConversion => Error::ToAffineConversion, 38 | } 39 | } 40 | } 41 | 42 | impl From for Error { 43 | fn from(error: FieldError) -> Self { 44 | match error { 45 | FieldError::NotMember => Error::NotMemberError, 46 | FieldError::InvalidSliceLength => Error::InvalidLength, 47 | FieldError::InvalidU512Encoding => Error::InvalidEncoding, 48 | } 49 | } 50 | } 51 | 52 | impl From for Error { 53 | fn from(_error: GroupError) -> Self { 54 | Error::InvalidGroupPoint 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(_error: bn::arith::Error) -> Self { 60 | Error::InvalidLength 61 | } 62 | } 63 | 64 | impl From> for Error { 65 | fn from(_error: std::vec::Vec) -> Self { 66 | Error::SerializationError 67 | } 68 | } 69 | 70 | impl From for Error { 71 | fn from(_error: std::io::Error) -> Self { 72 | Error::SerializationError 73 | } 74 | } 75 | 76 | pub type Result = core::result::Result; 77 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Elliptic Curve Digital Signature Algorithm (ECDSA) using the `bn254` curve, 2 | //! also known as `bn128` or `bn256`. 3 | //! 4 | //! This module has been designed with the goal of being compatible with the 5 | //! bn256Add(G1), bn256ScalarMul(G1) and bn256Pairing provided by precompiled 6 | //! contracts on the Ethereum Virtual Machine (EVM). 7 | //! 8 | //! Signature verification: e(H(m), PubKey) = e(Signature, 9 | //! G2::one) 10 | //! 11 | //! This module handles public keys in G2 in order to avoid performing the 12 | //! hashing to G2, which involves a costly multiplication with the cofactor. 13 | //! 14 | //!Test vectors: the following resources have been used for testing 15 | //! BN256 functionalities 16 | //! - test vectors from Ethereum 17 | //! - test vectors from Asecurity 18 | //! 19 | //! Hashing to G1: In order to hash a specific message to G1 this module 20 | //! uses the try and increment algorithm. The running time of this algorithm is 21 | //! dependant on the input message, so it should be used only with public 22 | //! inputs. Alternatively different hashing methods can be implemented as 23 | //! specified in: 24 | //! - hash_to_ algorithms 25 | //! 26 | //!Resources: The following resources have been used as a reference 27 | //! to implement aggregate signatures: 28 | //! 29 | //! - BLS IRTF draft 30 | //! - 31 | //! BLSmultisig 32 | //! - bls-signatures-better-than-schnorr 33 | //! 34 | //! # Disclaimer 35 | //! 36 | //! This module does not implement a defense against Rogue-key attacks, which 37 | //! means it should be used in protocols where the possession of the private key 38 | //! of each individual has been proven (i.e., by signing a message). 39 | 40 | mod ecdsa; 41 | mod error; 42 | mod hash; 43 | #[cfg(feature = "serde")] 44 | #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 45 | mod serde; 46 | mod types; 47 | mod utils; 48 | 49 | #[cfg(test)] 50 | #[path = ""] 51 | mod test { 52 | use super::*; 53 | mod ecdsa_test; 54 | mod hash_test; 55 | #[cfg(feature = "serde")] 56 | mod serde_test; 57 | mod types_test; 58 | } 59 | 60 | pub use ecdsa::{check_public_keys, ECDSA}; 61 | pub use error::{Error, Result}; 62 | pub use types::{PrivateKey, PublicKey, PublicKeyG1, Signature}; 63 | pub use utils::{format_pairing_check_uncompressed_values, format_pairing_check_values}; 64 | -------------------------------------------------------------------------------- /src/ecdsa.rs: -------------------------------------------------------------------------------- 1 | use bn::{pairing_batch, Group, Gt, G1, G2}; 2 | 3 | use crate::{ 4 | error::{Error, Result}, 5 | hash, 6 | PrivateKey, 7 | PublicKey, 8 | PublicKeyG1, 9 | Signature, 10 | }; 11 | 12 | /// ECDSA with curve `bn254`. 13 | pub struct ECDSA; 14 | 15 | impl ECDSA { 16 | /// Function to sign a message given a private key (as a point in [G1]). 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `message` - The message bytes 21 | /// * `private_key` - The private key 22 | /// 23 | /// # Returns 24 | /// 25 | /// * If successful, the signature as a [G1] point 26 | pub fn sign>(message: T, private_key: &PrivateKey) -> Result { 27 | // 1. Hash_to_try_and_increment --> H(m) as point in G1 (only if it exists) 28 | let hash_point = hash::hash_to_try_and_increment(message)?; 29 | 30 | // 2. Multiply hash_point times private_key --> Signature in G1 31 | let g1_point = hash_point * private_key.into(); 32 | 33 | // 3. Return signature 34 | Ok(Signature(g1_point)) 35 | } 36 | 37 | /// Function to verify a signature (point in [G1]) given a public key 38 | /// (point in [G2]). 39 | /// 40 | /// # Arguments 41 | /// 42 | /// * `message` - The message to be signed 43 | /// * `signature` - The signature 44 | /// * `public_key` - The public key 45 | /// 46 | /// # Returns 47 | /// 48 | /// * If successful, `Ok(())`; otherwise [Error] 49 | pub fn verify>(message: T, signature: &Signature, public_key: &PublicKey) -> Result<()> { 50 | // Pairing batch with one negated point 51 | let mut vals = Vec::new(); 52 | // First pairing input: e(H(m), PubKey) 53 | let hash_point = hash::hash_to_try_and_increment(message)?; 54 | vals.push((hash_point, public_key.into())); 55 | // Second pairing input: e(-Signature,G2::one()) 56 | vals.push((signature.into(), -G2::one())); 57 | let mul = pairing_batch(&vals); 58 | 59 | if mul == Gt::one() { 60 | Ok(()) 61 | } else { 62 | Err(Error::VerificationFailed) 63 | } 64 | } 65 | } 66 | 67 | /// Function to check if 2 Public Keys in [G1] and [G2] are valid 68 | /// 69 | /// # Arguments 70 | /// 71 | /// * `message` - The message to be signed 72 | /// * `signature` - The signature 73 | /// * `public_key` - The public key 74 | /// 75 | /// # Returns 76 | /// 77 | /// * If successful, `Ok(())`; otherwise [Error] 78 | pub fn check_public_keys(public_key_g2: &PublicKey, public_key_g1: &PublicKeyG1) -> Result<()> { 79 | // Pairing batch with one negated point 80 | let vals = vec![ 81 | // First pairing input: e(H(m), PubKey) 82 | (G1::one(), public_key_g2.into()), 83 | // Second pairing input: e(PubKey_G1, G2::one()) 84 | (public_key_g1.into(), -G2::one()), 85 | ]; 86 | let mul = pairing_batch(&vals); 87 | 88 | if mul == Gt::one() { 89 | Ok(()) 90 | } else { 91 | Err(Error::VerificationFailed) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bn254 2 | [![](https://img.shields.io/crates/v/bn254.svg)](https://crates.io/crates/bn254) [![](https://docs.rs/bn254/badge.svg)](https://docs.rs/bn254) 3 | 4 | `bn254` is an open-source Rust implementation of aggregate signatures over the pairing-friendly elliptic curve BN254 ([Barreto-Naehrig (BN)](https://www.cryptojedi.org/papers/pfcpo.pdf)). 5 | 6 | This curve is also known as `bn256` or `bn128` (`alt-bn128`) referred to the bits of security. The bits of security of `bn254` dropped from 128 to around 100 after the new algorithms of [Kim-Barbulescu](https://eprint.iacr.org/2015/1027.pdf). 7 | 8 | _DISCLAIMER_: This is experimental software. Be careful! 9 | 10 | ## Usage 11 | 12 | This module uses the [substrate-bn](https://github.com/paritytech/bn) library to perform elliptic curve operations over the appropriate fields. It provides the following functionalities: 13 | 14 | * `sign`: Sign a message given a secret key. 15 | * `verify`: Given a public key, a signature, and a message it verifies whether the signature is valid. 16 | 17 | Signature and public aggregation can be done directly with the `+` or `-` operators. 18 | 19 | ## Hashing to G1 20 | 21 | The algorithm used for hashing a given message into a point in G1 follows the "try-and-increment" method. We discourage its usage in the cases of hashing secret messages since its running time leaks information about the input. 22 | 23 | In other cases, where the message to be hashed is public, "try-and-increment" should be safe. The hashing algorithm utilized is `sha256`. 24 | 25 | ## Example 26 | 27 | Sign, aggregate and verify by using the `bn254` curve: 28 | 29 | ```rust 30 | use bn254::{PrivateKey, PublicKey, ECDSA}; 31 | 32 | fn main() { 33 | // Inputs: Secret Key, Public Key (derived) & Message 34 | 35 | // Secret key one 36 | let private_key_1_bytes = hex::decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap(); 37 | let private_key_1 = PrivateKey::try_from(private_key_1_bytes.as_ref()).unwrap(); 38 | 39 | // Secret key two 40 | let private_key_2_bytes = hex::decode("a55e93edb1350916bf5beea1b13d8f198ef410033445bcb645b65be5432722f1").unwrap(); 41 | let private_key_2 = PrivateKey::try_from(private_key_2_bytes.as_ref()).unwrap(); 42 | 43 | // Derive public keys from secret key 44 | let public_key_1 = PublicKey::from_private_key(&private_key_1); 45 | let public_key_2 = PublicKey::from_private_key(&private_key_2); 46 | 47 | let message: &[u8] = b"sample"; 48 | 49 | // Sign identical message with two different secret keys 50 | let signature_1 = ECDSA::sign(&message, &private_key_1).unwrap(); 51 | let signature_2 = ECDSA::sign(&message, &private_key_2).unwrap(); 52 | 53 | // Aggregate public keys 54 | let aggregate_pub_key = public_key_1 + public_key_2; 55 | 56 | // Aggregate signatures 57 | let aggregate_sig = signature_1 + signature_2; 58 | 59 | // Check whether the aggregate signature corresponds to the aggregated 60 | // public_key 61 | ECDSA::verify(&message, &aggregate_sig, &aggregate_pub_key).unwrap(); 62 | println!("Successful aggregate signature verification"); 63 | } 64 | ``` 65 | 66 | ## License 67 | 68 | `bn254` is published under the [MIT license](https://github.com/sedaprotocol/bn254/blob/main/LICENSE.md) -------------------------------------------------------------------------------- /src/ecdsa_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::ecdsa::check_public_keys; 3 | 4 | /// Test for the `ECDSA::sign` function with own test vector 5 | #[test] 6 | fn test_sign_1() { 7 | // Inputs: private key and message "sample" in ASCII 8 | let data = hex::decode("73616d706c65").unwrap(); 9 | 10 | let private_key = PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 11 | 12 | // Sign data with private key 13 | let signature = ECDSA::sign(data, &private_key).unwrap(); 14 | 15 | let expected_signature = "020f047a153e94b5f109e4013d1bd078112817cf0d58cdf6ba8891f9849852ba5b"; 16 | assert_eq!(hex::encode(signature.to_compressed().unwrap()), expected_signature); 17 | } 18 | 19 | /// Test `ECDSA::verify` function with own signed message 20 | #[test] 21 | fn test_verify_signed_msg() { 22 | // Public key 23 | let private_key = PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 24 | let public_key = PublicKey::from_private_key(&private_key); 25 | 26 | // Signature 27 | let signature_vec = hex::decode("020f047a153e94b5f109e4013d1bd078112817cf0d58cdf6ba8891f9849852ba5b").unwrap(); 28 | let signature = Signature::from_compressed(signature_vec).unwrap(); 29 | 30 | // Message signed 31 | let msg = hex::decode("73616d706c65").unwrap(); 32 | 33 | // Verify signature 34 | assert!( 35 | ECDSA::verify(msg, &signature, &public_key).is_ok(), 36 | "Verification failed" 37 | ); 38 | } 39 | 40 | /// Test aggregate signature verification 41 | #[test] 42 | fn test_verify_aggregate_signatures() { 43 | // Message 44 | let msg = hex::decode("73616d706c65").unwrap(); 45 | 46 | // Signature 1 47 | let private_key_1 = 48 | PrivateKey::try_from("1ab1126ff2e37c6e6eddea943ccb3a48f83b380b856424ee552e113595525565").unwrap(); 49 | let sign_1 = ECDSA::sign(&msg, &private_key_1).unwrap(); 50 | 51 | let public_key_1 = PublicKey::from_private_key(&private_key_1); 52 | 53 | // Signature 2 54 | let private_key_2 = 55 | PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 56 | let sign_2 = ECDSA::sign(&msg, &private_key_2).unwrap(); 57 | 58 | let public_key_2 = PublicKey::from_private_key(&private_key_2); 59 | 60 | // Public Key and Signature aggregation 61 | let agg_public_key = public_key_1 + public_key_2; 62 | let agg_signature = sign_1 + sign_2; 63 | 64 | // Verification single signatures 65 | assert!( 66 | ECDSA::verify(&msg, &sign_1, &public_key_1).is_ok(), 67 | "Signature 1 verification failed" 68 | ); 69 | assert!( 70 | ECDSA::verify(&msg, &sign_2, &public_key_2).is_ok(), 71 | "Signature 2 signature verification failed" 72 | ); 73 | 74 | // Aggregate signature verification 75 | assert!( 76 | ECDSA::verify(&msg, &agg_signature, &agg_public_key).is_ok(), 77 | "Aggregated signature verification failed" 78 | ); 79 | } 80 | 81 | /// Test if PubKey in G1 -> PubKey in G2: e(G1, P2) = e(P1, G2) 82 | #[test] 83 | fn test_verify_valid_public_keys_in_g1_g2() { 84 | let private_key = PrivateKey::try_from("1ab1126ff2e37c6e6eddea943ccb3a48f83b380b856424ee552e113595525565").unwrap(); 85 | 86 | // Get public keys in G1 and G2 87 | let public_g2 = PublicKey::from_private_key(&private_key); 88 | let public_g1 = PublicKeyG1::from_private_key(&private_key); 89 | 90 | // Check if valid 91 | assert!( 92 | check_public_keys(&public_g2, &public_g1).is_ok(), 93 | "Public Key in G1 DOES NOT correspond to Public Key in G2" 94 | ); 95 | } 96 | 97 | /// Test (false-positive) if PubKey in G1 -> PubKey in G2: e(G1, P2) = e(P1, G2) 98 | #[test] 99 | fn test_verify_invalid_public_keys_in_g1_g2() { 100 | // Get public keys in G1 and G2 (from different private keys) 101 | let private_key_1 = 102 | PrivateKey::try_from("1ab1126ff2e37c6e6eddea943ccb3a48f83b380b856424ee552e113595525565").unwrap(); 103 | let public_g2 = PublicKey::from_private_key(&private_key_1); 104 | 105 | let private_key_2 = 106 | PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 107 | let public_g1 = PublicKeyG1::from_private_key(&private_key_2); 108 | 109 | // Check if valid 110 | let result = check_public_keys(&public_g2, &public_g1); 111 | assert!(matches!(result, Err(Error::VerificationFailed))); 112 | } 113 | 114 | /// Test 'PublicKeyG1::from_uncompressed' and 'PublicKeyG1::to_uncompressed' 115 | #[test] 116 | fn test_public_key_g1_from_uncompressed() { 117 | let private_key = PrivateKey::try_from("1ab1126ff2e37c6e6eddea943ccb3a48f83b380b856424ee552e113595525565").unwrap(); 118 | 119 | // Get public keys in G1 and G2 120 | let public_g2 = PublicKey::from_private_key(&private_key); 121 | let public_g1 = PublicKeyG1::from_private_key(&private_key); 122 | 123 | let pk_g1_uncompressed = public_g1.to_uncompressed().unwrap(); 124 | let public_g1_again = PublicKeyG1::from_uncompressed(pk_g1_uncompressed).unwrap(); 125 | 126 | // Check if valid 127 | assert!( 128 | check_public_keys(&public_g2, &public_g1_again).is_ok(), 129 | "Public Key in G1 DOES NOT correspond to Public Key in G2" 130 | ); 131 | } 132 | 133 | /// Test `Signature::from_uncompressed()` and `Signature::to_uncompressed()` 134 | #[test] 135 | fn test_sig_from_uncompressed() { 136 | // Public key 137 | let private_key = PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 138 | let public_key = PublicKey::from_private_key(&private_key); 139 | 140 | // Signature 141 | let signature_vec = hex::decode("020f047a153e94b5f109e4013d1bd078112817cf0d58cdf6ba8891f9849852ba5b").unwrap(); 142 | let signature = Signature::from_compressed(signature_vec).unwrap(); 143 | let uncompressed_sig = signature.to_uncompressed().unwrap(); 144 | let signature_again = Signature::from_uncompressed(uncompressed_sig).unwrap(); 145 | 146 | // Message signed 147 | let msg = hex::decode("73616d706c65").unwrap(); 148 | 149 | // Verify signature 150 | assert!( 151 | ECDSA::verify(msg, &signature_again, &public_key).is_ok(), 152 | "Verification failed" 153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /src/types_test.rs: -------------------------------------------------------------------------------- 1 | use bn::{Group, G2}; 2 | 3 | /// Test vectors taken from https://asecuritysite.com/encryption/go_bn256. 4 | /// The public keys in G2 are changed in order in the website, i.e., imaginary 5 | /// goes first. 6 | /// 7 | /// In order to construct the test vectors we need to do the following: 8 | /// - Get the modulus of Fq 9 | /// - Get the components (real, imaginary) of x and y 10 | /// - Perform (imaginary*modulus) + real 11 | /// - Compress with 0x0a or 0x0b depending on the value of y 12 | use super::*; 13 | 14 | #[test] 15 | fn test_valid_private_key() { 16 | let compressed = hex::decode("023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a").unwrap(); 17 | let private_key = PrivateKey::try_from(compressed.as_slice()); 18 | assert_eq!(private_key.unwrap().to_bytes().unwrap(), compressed); 19 | } 20 | 21 | #[test] 22 | fn test_valid_private_key_hex() { 23 | let hex = "023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a"; 24 | let private_key = PrivateKey::try_from(hex).unwrap(); 25 | let hex_again: String = private_key.try_into().unwrap(); 26 | assert_eq!(hex, &hex_again); 27 | } 28 | 29 | #[test] 30 | #[should_panic(expected = "InvalidLength")] 31 | fn test_invalid_private_key_1() { 32 | let compressed = hex::decode( 33 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 34 | ) 35 | .unwrap(); 36 | let private_key = PrivateKey::try_from(compressed.as_slice()); 37 | private_key.unwrap(); 38 | } 39 | 40 | #[test] 41 | #[should_panic(expected = "InvalidLength")] 42 | fn test_invalid_private_key_2() { 43 | let compressed = hex::decode("aaaa").unwrap(); 44 | let private_key = PrivateKey::try_from(compressed.as_slice()); 45 | private_key.unwrap(); 46 | } 47 | 48 | #[test] 49 | fn test_compressed_public_key_1() { 50 | let compressed = hex::decode("0a023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a").unwrap(); 51 | let public_key = PublicKey::from_compressed(&compressed).unwrap(); 52 | let compressed_again = public_key.to_compressed().unwrap(); 53 | assert_eq!(compressed, compressed_again); 54 | } 55 | 56 | #[test] 57 | fn test_uncompressed_public_key() { 58 | let uncompressed = hex::decode( 59 | "28fe26becbdc0384aa67bf734d08ec78ecc2330f0aa02ad9da00f56c37907f78\ 60 | 2cd080d897822a95a0fb103c54f06e9bf445f82f10fe37efce69ecb59514abc8\ 61 | 237faeb0351a693a45d5d54aa9759f52a71d76edae2132616d6085a9b2228bf9\ 62 | 0f46bd1ef47552c3089604c65a3e7154e3976410be01149b60d5a41a6053e6c2", 63 | ) 64 | .unwrap(); 65 | let public_key = PublicKey::from_uncompressed(&uncompressed).unwrap(); 66 | 67 | let uncompressed_again = public_key.to_uncompressed().unwrap(); 68 | assert_eq!(uncompressed_again, uncompressed); 69 | } 70 | 71 | #[test] 72 | fn test_to_public_key_1() { 73 | let expected = hex::decode( 74 | "28fe26becbdc0384aa67bf734d08ec78ecc2330f0aa02ad9da00f56c37907f78\ 75 | 2cd080d897822a95a0fb103c54f06e9bf445f82f10fe37efce69ecb59514abc8\ 76 | 237faeb0351a693a45d5d54aa9759f52a71d76edae2132616d6085a9b2228bf9\ 77 | 0f46bd1ef47552c3089604c65a3e7154e3976410be01149b60d5a41a6053e6c2", 78 | ) 79 | .unwrap(); 80 | let expected_public_key = PublicKey::from_uncompressed(expected).unwrap(); 81 | let private_key = PrivateKey::try_from("1ab1126ff2e37c6e6eddea943ccb3a48f83b380b856424ee552e113595525565").unwrap(); 82 | let public_key = PublicKey::from_private_key(&private_key); 83 | assert_eq!(public_key.0, expected_public_key.0); 84 | } 85 | 86 | #[test] 87 | fn test_to_public_key_2() { 88 | let expected = hex::decode( 89 | "1cd5df38ed2f184b9830bfd3c2175d53c1455352307ead8cbd7c6201202f4aa8\ 90 | 02ce1c4241143cc61d82589c9439c6dd60f81fa6f029625d58bc0f2e25e4ce89\ 91 | 0ba19ae3b5a298b398b3b9d410c7e48c4c8c63a1d6b95b098289fbe1503d00fb\ 92 | 2ec596e93402de0abc73ce741f37ed4984a0b59c96e20df8c9ea1c4e6ec04556", 93 | ) 94 | .unwrap(); 95 | let expected_public_key = PublicKey::from_uncompressed(expected).unwrap(); 96 | let private_key = PrivateKey::try_from("2009da7287c158b126123c113d1c85241b6e3294dd75c643588630a8bc0f934c").unwrap(); 97 | let public_key = PublicKey::from_private_key(&private_key); 98 | assert_eq!(public_key.0, expected_public_key.0); 99 | } 100 | 101 | #[test] 102 | fn test_to_public_key_3() { 103 | let expected = hex::decode( 104 | "077dfcf14e940b69bf88fa1ad99b6c7e1a1d6d2cb8813ac53383bf505a17f8ff\ 105 | 2d1a9b04a2c5674373353b5a25591292e69c37c0b84d9ef1c780a57bb98638e6\ 106 | 2dc52f109b333c4125bccf55bc3a839ce57676514405656c79e577e231519273\ 107 | 2410eee842807d9325f22d087fa6bc79d9bbea07f5fa8c345e1e57b28ad54f84", 108 | ) 109 | .unwrap(); 110 | let expected_public_key = PublicKey::from_uncompressed(expected).unwrap(); 111 | let private_key = PrivateKey::try_from("26fb4d661491b0a623637a2c611e34b6641cdea1743bee94c17b67e5ef14a550").unwrap(); 112 | let public_key = PublicKey::from_private_key(&private_key); 113 | assert_eq!(public_key.0, expected_public_key.0); 114 | } 115 | 116 | #[test] 117 | fn test_to_public_key_4() { 118 | let expected = hex::decode( 119 | "270567a05b56b02e813281d554f46ce0c1b742b622652ef5a41d69afb6eb8338\ 120 | 1bab5671c5107de67fe06007dde240a84674c8ff13eeac6d64bad0caf2cfe53e\ 121 | 0142f4e04fc1402e17ae7e624fd9bd15f1eae0a1d8eda4e26ab70fd4cd793338\ 122 | 02b54a5deaaf86dc7f03d080c8373d62f03b3be06dac42b2d9426a8ebd0caf4a", 123 | ) 124 | .unwrap(); 125 | let expected_public_key = PublicKey::from_uncompressed(expected).unwrap(); 126 | let private_key = PrivateKey::try_from("0f6b8785374476a3b3e4bde2c64dfb12964c81c7930d32367c8e318609387872").unwrap(); 127 | let public_key = PublicKey::from_private_key(&private_key); 128 | assert_eq!(public_key.0, expected_public_key.0); 129 | } 130 | 131 | /// Test `aggregate_public_keys` 132 | #[test] 133 | fn test_aggregate_public_keys_1() { 134 | // Public keys 135 | let public_key_1 = PublicKey(G2::one()); 136 | let public_key_2 = PublicKey(G2::one()); 137 | 138 | // Aggregation 139 | let agg_public_key = public_key_1 + public_key_2; 140 | 141 | // Check 142 | let expected = hex::decode("0b061848379c6bccd9e821e63ff6932738835b78e1e10079a0866073eba5b8bb444afbb053d16542e2b839477434966e5a9099093b6b3351f84ac19fe28f096548").unwrap(); 143 | assert_eq!(agg_public_key.to_compressed().unwrap(), expected); 144 | } 145 | 146 | /// Test `aggregate_signatures` 147 | #[test] 148 | fn test_aggregate_signatures_1() { 149 | // Signatures (as valid points on G1) 150 | let sign_1 = Signature(bn::G1::one()); 151 | let sign_2 = Signature(bn::G1::one()); 152 | 153 | // Aggregation 154 | let aggregate_signature = sign_1 + sign_2; 155 | 156 | // Check 157 | let expected = hex::decode("02030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3").unwrap(); 158 | assert_eq!(aggregate_signature.to_compressed().unwrap(), expected); 159 | } 160 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Neg, Sub}; 2 | 3 | use bn::{Fr, Group, G1, G2}; 4 | use rand::Rng; 5 | 6 | use crate::{ 7 | error::{Error, Result}, 8 | utils, 9 | }; 10 | 11 | /// The Private Key as an element of [Fr] 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct PrivateKey(pub Fr); 14 | 15 | impl PrivateKey { 16 | /// Function to create a random [PrivateKey]. 17 | pub fn random(rng: &mut R) -> Self 18 | where 19 | R: Rng, 20 | { 21 | // This function throws an error if the slice does not have a proper length. 22 | let private_key = Fr::random(rng); 23 | 24 | Self(private_key) 25 | } 26 | 27 | /// Function to obtain a private key in bytes. 28 | pub fn to_bytes(&self) -> Result> { 29 | utils::fr_to_bytes(self.into()) 30 | } 31 | } 32 | 33 | impl TryFrom<&[u8]> for PrivateKey { 34 | type Error = Error; 35 | 36 | fn try_from(private_key: &[u8]) -> Result { 37 | Ok(PrivateKey(Fr::from_slice(private_key)?)) 38 | } 39 | } 40 | 41 | impl TryFrom<&str> for PrivateKey { 42 | type Error = Error; 43 | 44 | fn try_from(value: &str) -> std::result::Result { 45 | let bytes = hex::decode(value)?; 46 | Self::try_from(bytes.as_slice()) 47 | } 48 | } 49 | 50 | impl TryFrom for PrivateKey { 51 | type Error = Error; 52 | 53 | fn try_from(value: String) -> std::result::Result { 54 | Self::try_from(value.as_str()) 55 | } 56 | } 57 | 58 | impl TryFrom for String { 59 | type Error = Error; 60 | 61 | fn try_from(value: PrivateKey) -> std::result::Result { 62 | let bytes = value.to_bytes()?; 63 | Ok(hex::encode(bytes)) 64 | } 65 | } 66 | 67 | impl From for Fr { 68 | fn from(private_key: PrivateKey) -> Self { 69 | private_key.0 70 | } 71 | } 72 | 73 | impl From<&PrivateKey> for Fr { 74 | fn from(private_key: &PrivateKey) -> Self { 75 | private_key.0 76 | } 77 | } 78 | 79 | /// The Public Key as a point in [G2] 80 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 81 | pub struct PublicKey(pub G2); 82 | 83 | impl PublicKey { 84 | /// Function to derive the `bn254` public key from the [PrivateKey]. 85 | pub fn from_private_key(private_key: &PrivateKey) -> Self { 86 | Self(G2::one() * private_key.into()) 87 | } 88 | 89 | /// Function to create a [PublicKey] from bytes representing a [G2] point in 90 | /// compressed format. 91 | pub fn from_compressed>(bytes: T) -> Result { 92 | Ok(Self(G2::from_compressed(bytes.as_ref())?)) 93 | } 94 | 95 | /// Function to create a [PublicKey] from bytes representing a [G2] point in 96 | /// uncompressed format. 97 | pub fn from_uncompressed>(bytes: T) -> Result { 98 | Ok(Self(utils::from_uncompressed_to_g2(bytes.as_ref())?)) 99 | } 100 | 101 | /// Function to serialize the [PublicKey] to vector of bytes in compressed 102 | /// format. 103 | pub fn to_compressed(&self) -> Result> { 104 | utils::g2_to_compressed(self.into()) 105 | } 106 | 107 | /// Function to serialize the [PublicKey] to vector of bytes in uncompressed 108 | /// format. 109 | pub fn to_uncompressed(&self) -> Result> { 110 | utils::g2_to_uncompressed(self.into()) 111 | } 112 | } 113 | 114 | impl From for G2 { 115 | fn from(public_key: PublicKey) -> Self { 116 | public_key.0 117 | } 118 | } 119 | 120 | impl From<&PublicKey> for G2 { 121 | fn from(public_key: &PublicKey) -> Self { 122 | public_key.0 123 | } 124 | } 125 | 126 | impl Add for PublicKey { 127 | type Output = Self; 128 | 129 | fn add(self, other: Self) -> Self::Output { 130 | Self(self.0.add(other.0)) 131 | } 132 | } 133 | 134 | impl Sub for PublicKey { 135 | type Output = Self; 136 | 137 | fn sub(self, other: Self) -> Self { 138 | Self(self.0 - other.0) 139 | } 140 | } 141 | 142 | impl Neg for PublicKey { 143 | type Output = Self; 144 | 145 | fn neg(self) -> Self { 146 | Self(-self.0) 147 | } 148 | } 149 | 150 | /// The Public Key as a point in [G1] 151 | pub struct PublicKeyG1(pub G1); 152 | 153 | impl PublicKeyG1 { 154 | /// Function to derive the `bn254` public key from the [PrivateKey]. 155 | pub fn from_private_key(private_key: &PrivateKey) -> Self { 156 | Self(G1::one() * private_key.into()) 157 | } 158 | 159 | /// Function to serialize the [PublicKeyG1] to vector of bytes in compressed 160 | /// format. 161 | pub fn to_compressed(&self) -> Result> { 162 | utils::g1_to_compressed(self.0) 163 | } 164 | 165 | /// Function to create a [PublicKeyG1] from bytes representing a [G1] point 166 | /// in compressed format. 167 | pub fn from_compressed>(bytes: T) -> Result { 168 | Ok(Self(G1::from_compressed(bytes.as_ref())?)) 169 | } 170 | 171 | /// Function to create a [Signature] from bytes representing a [G1] point in 172 | /// uncompressed format. 173 | pub fn from_uncompressed>(bytes: T) -> Result { 174 | Ok(Self(utils::from_uncompressed_to_g1(bytes.as_ref())?)) 175 | } 176 | 177 | /// Function to serialize the [Signature] to vector of bytes in uncompressed 178 | /// format. 179 | pub fn to_uncompressed(&self) -> Result> { 180 | utils::g1_to_uncompressed(self.into()) 181 | } 182 | } 183 | 184 | impl From for G1 { 185 | fn from(public_key: PublicKeyG1) -> Self { 186 | public_key.0 187 | } 188 | } 189 | 190 | impl From<&PublicKeyG1> for G1 { 191 | fn from(public_key: &PublicKeyG1) -> Self { 192 | public_key.0 193 | } 194 | } 195 | 196 | impl Add for PublicKeyG1 { 197 | type Output = Self; 198 | 199 | fn add(self, other: Self) -> Self::Output { 200 | Self(self.0.add(other.0)) 201 | } 202 | } 203 | 204 | impl Sub for PublicKeyG1 { 205 | type Output = Self; 206 | 207 | fn sub(self, other: Self) -> Self { 208 | Self(self.0 - other.0) 209 | } 210 | } 211 | 212 | impl Neg for PublicKeyG1 { 213 | type Output = Self; 214 | 215 | fn neg(self) -> Self { 216 | Self(-self.0) 217 | } 218 | } 219 | 220 | /// The Signature as a point in [G1] 221 | #[derive(Copy, Clone, Debug)] 222 | pub struct Signature(pub G1); 223 | 224 | impl Signature { 225 | /// Function to serialize the [Signature] to vector of bytes in compressed 226 | /// format. 227 | pub fn to_compressed(&self) -> Result> { 228 | utils::g1_to_compressed(self.0) 229 | } 230 | 231 | /// Function to create a [Signature] from bytes representing a [G1] point in 232 | /// compressed format. 233 | pub fn from_compressed>(bytes: T) -> Result { 234 | let uncompressed = G1::from_compressed(bytes.as_ref())?; 235 | 236 | Ok(Self(uncompressed)) 237 | } 238 | 239 | /// Function to create a [Signature] from bytes representing a [G1] point in 240 | /// uncompressed format. 241 | pub fn from_uncompressed>(bytes: T) -> Result { 242 | Ok(Self(utils::from_uncompressed_to_g1(bytes.as_ref())?)) 243 | } 244 | 245 | /// Function to serialize the [Signature] to vector of bytes in uncompressed 246 | /// format. 247 | pub fn to_uncompressed(&self) -> Result> { 248 | utils::g1_to_uncompressed(self.into()) 249 | } 250 | } 251 | 252 | impl From for G1 { 253 | fn from(signature: Signature) -> Self { 254 | signature.0 255 | } 256 | } 257 | 258 | impl From<&Signature> for G1 { 259 | fn from(signature: &Signature) -> Self { 260 | signature.0 261 | } 262 | } 263 | 264 | impl Add for Signature { 265 | type Output = Self; 266 | 267 | fn add(self, other: Self) -> Self::Output { 268 | Self(self.0.add(other.0)) 269 | } 270 | } 271 | 272 | impl Sub for Signature { 273 | type Output = Self; 274 | 275 | fn sub(self, other: Self) -> Self { 276 | Self(self.0 - other.0) 277 | } 278 | } 279 | 280 | impl Neg for Signature { 281 | type Output = Self; 282 | 283 | fn neg(self) -> Self { 284 | Self(-self.0) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use bn::{ 2 | arith::{U256, U512}, 3 | G1, 4 | G2, 5 | *, 6 | }; 7 | use borsh::BorshSerialize; 8 | use byteorder::ByteOrder; 9 | 10 | use crate::{ 11 | error::{Error, Result}, 12 | hash::hash_to_try_and_increment, 13 | PublicKey, 14 | Signature, 15 | }; 16 | 17 | /// Function to calculate the modulus of a [U256]. 18 | /// 19 | /// # Arguments 20 | /// 21 | /// * `num` - the number we want to reduce. 22 | /// * `modulus` - the modulus we want to apply. 23 | /// 24 | /// # Returns 25 | /// 26 | /// * If successful, a [U256] representing num % modulus. 27 | pub(crate) fn mod_u256(num: U256, modulus: U256) -> U256 { 28 | let mut reduced = num; 29 | // the library does not provide a function to do a modulo reduction 30 | // we use the provided add function adding a 0 31 | // we also need to iterate here as the library does the modulus only once 32 | while reduced > modulus { 33 | reduced.add(&U256::zero(), &modulus); 34 | } 35 | 36 | reduced 37 | } 38 | 39 | /// Function to convert a complex coordinate ([Fq2]) to [U512]. 40 | pub(crate) fn to_u512(coord: Fq2) -> U512 { 41 | let c0: U256 = (coord.real()).into_u256(); 42 | let c1: U256 = (coord.imaginary()).into_u256(); 43 | 44 | U512::new(&c1, &c0, &Fq::modulus()) 45 | } 46 | 47 | /// Function to convert an arbitrary string to a point in the curve [G1]. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// * `data` - A slice representing the data to be converted to a [G1] point. 52 | /// 53 | /// # Returns 54 | /// 55 | /// * If successful, a [G1] representing the converted point. 56 | pub(crate) fn arbitrary_string_to_g1(data: &[u8; 32]) -> Result { 57 | let mut v = vec![0x02]; 58 | v.extend(data); 59 | 60 | let point = G1::from_compressed(&v)?; 61 | 62 | Ok(point) 63 | } 64 | 65 | /// Function to obtain a private key in bytes. 66 | pub(crate) fn fr_to_bytes(fr: bn::Fr) -> Result> { 67 | let mut result: [u8; 32] = [0; 32]; 68 | // to_big_endian from Fr does not work here. 69 | fr.into_u256().to_big_endian(&mut result)?; 70 | 71 | Ok(result.to_vec()) 72 | } 73 | 74 | /// Function to convert [G1] point into compressed form (`0x02` if Y is even and 75 | /// `0x03` if Y is odd). 76 | /// 77 | /// # Arguments 78 | /// 79 | /// * `point` - A [G1] point. 80 | /// 81 | /// # Returns 82 | /// 83 | /// * If successful, a `Vec` with the compressed [G1] point. 84 | pub(crate) fn g1_to_compressed(point: G1) -> Result> { 85 | // From Jacobian to Affine first! 86 | let affine_coords = AffineG1::from_jacobian(point).ok_or(Error::PointInJacobian)?; 87 | // Get X coordinate 88 | let x = Fq::into_u256(affine_coords.x()); 89 | // Get Y coordinate 90 | let y = Fq::into_u256(affine_coords.y()); 91 | // Get parity of Y 92 | let parity = y.get_bit(0).ok_or(Error::IndexOutOfBounds)?; 93 | 94 | // Take x as big endian into slice 95 | let mut s = [0u8; 32]; 96 | x.to_big_endian(&mut s)?; 97 | let mut result: Vec = Vec::new(); 98 | // Push 0x02 or 0x03 depending on parity 99 | result.push(if parity { 3 } else { 2 }); 100 | // Append x 101 | result.append(&mut s.to_vec()); 102 | 103 | Ok(result) 104 | } 105 | 106 | /// Function to create a [G2] from bytes in uncompressed format. 107 | pub(crate) fn from_uncompressed_to_g2(bytes: &[u8]) -> Result { 108 | if bytes.len() != 128 { 109 | return Err(Error::InvalidLength {}); 110 | } 111 | let x = Fq2::new(Fq::from_slice(&bytes[0..32])?, Fq::from_slice(&bytes[32..64])?); 112 | let y = Fq2::new(Fq::from_slice(&bytes[64..96])?, Fq::from_slice(&bytes[96..128])?); 113 | let g2_point = AffineG2::new(x, y)?; 114 | 115 | Ok(g2_point.into()) 116 | } 117 | 118 | /// Function to create a [G1] from bytes in uncompressed format. 119 | pub(crate) fn from_uncompressed_to_g1(bytes: &[u8]) -> Result { 120 | if bytes.len() != 64 { 121 | return Err(Error::InvalidLength {}); 122 | } 123 | let x = Fq::from_slice(&bytes[0..32])?; 124 | let y = Fq::from_slice(&bytes[32..64])?; 125 | let g1_point = AffineG1::new(x, y)?; 126 | Ok(g1_point.into()) 127 | } 128 | 129 | /// Function to serialize the [G2] to vector of bytes in compressed format. 130 | pub(crate) fn g2_to_compressed(g2: G2) -> Result> { 131 | let modulus = Fq::modulus(); 132 | // From Jacobian to Affine first! 133 | let affine_coords = AffineG2::from_jacobian(g2).ok_or(Error::PointInJacobian)?; 134 | 135 | // Get X real coordinate 136 | let x_real = Fq::into_u256(affine_coords.x().real()); 137 | // Get X imaginary coordinate 138 | let x_imaginary = Fq::into_u256(affine_coords.x().imaginary()); 139 | // Get Y and get sign 140 | let y = affine_coords.y(); 141 | let y_neg = -y; 142 | let sign: u8 = if to_u512(y) > to_u512(y_neg) { 0x0b } else { 0x0a }; 143 | 144 | // To U512 and its compressed representation 145 | let compressed = U512::new(&x_imaginary, &x_real, &modulus); 146 | // To slice 147 | let mut buf: [u8; 64] = [0; (4 * 16)]; 148 | for (l, i) in (0..4).rev().zip((0..4).map(|i| i * 16)) { 149 | byteorder::BigEndian::write_u128(&mut buf[i..], compressed.0[l]); 150 | } 151 | 152 | // Result = sign || compressed 153 | let mut result: Vec = Vec::new(); 154 | result.push(sign); 155 | result.append(&mut buf.to_vec()); 156 | 157 | Ok(result) 158 | } 159 | 160 | /// Function to serialize the [G2] to vector of bytes in uncompressed format. 161 | pub(crate) fn g2_to_uncompressed(g2: G2) -> Result> { 162 | // From Jacobian to Affine first! 163 | let affine_coords = AffineG2::from_jacobian(g2).ok_or(Error::PointInJacobian)?; 164 | let mut result: [u8; 32 * 4] = [0; (4 * 32)]; 165 | 166 | // Get X real coordinate 167 | Fq::into_u256(affine_coords.x().real()).to_big_endian(&mut result[0..32])?; 168 | 169 | // Get X imaginary coordinate 170 | Fq::into_u256(affine_coords.x().imaginary()).to_big_endian(&mut result[32..64])?; 171 | 172 | // Get Y real coordinate 173 | Fq::into_u256(affine_coords.y().real()).to_big_endian(&mut result[64..96])?; 174 | 175 | // Get Y imaginary coordinate 176 | Fq::into_u256(affine_coords.y().imaginary()).to_big_endian(&mut result[96..128])?; 177 | 178 | Ok(result.to_vec()) 179 | } 180 | 181 | /// Function to serialize the [G1] to vector of bytes in uncompressed format. 182 | pub(crate) fn g1_to_uncompressed(g1: G1) -> Result> { 183 | // From Jacobian to Affine first! 184 | let affine_coords = AffineG1::from_jacobian(g1).ok_or(Error::PointInJacobian)?; 185 | let mut result: [u8; 32 * 2] = [0; (2 * 32)]; 186 | 187 | // Get X coordinate 188 | Fq::into_u256(affine_coords.x()).to_big_endian(&mut result[0..32])?; 189 | 190 | // Get Y coordinate 191 | Fq::into_u256(affine_coords.y()).to_big_endian(&mut result[32..64])?; 192 | 193 | Ok(result.to_vec()) 194 | } 195 | 196 | /// Function to format the inputs using Borsh for a pairing check 197 | pub fn format_pairing_check_values( 198 | message: Vec, 199 | signature: Vec, 200 | public_key: Vec, 201 | ) -> Result<[([u8; 64], [u8; 128]); 2]> { 202 | // First pairing input: e(Uncompressed H(m) on G1, Uncompressed PubKey on G2) 203 | let msg_hash_point = hash_to_try_and_increment(message)?; 204 | let msg_hash_arr: [u8; 64] = msg_hash_point.try_to_vec()?.try_into()?; 205 | let pk_point = PublicKey::from_compressed(public_key)?; 206 | let pk_arr: [u8; 128] = pk_point.0.try_to_vec()?.try_into()?; 207 | 208 | // Second pairing input: e(Uncompressed Signature on G1,-G2::one()) 209 | let sig_point = Signature::from_compressed(signature)?; 210 | let sig_arr: [u8; 64] = sig_point.0.try_to_vec()?.try_into()?; 211 | let n_g2: [u8; 128] = (-G2::one()).try_to_vec()?.try_into()?; 212 | 213 | Ok([(msg_hash_arr, pk_arr), (sig_arr, n_g2)]) 214 | } 215 | 216 | pub fn format_pairing_check_uncompressed_values( 217 | message: Vec, 218 | mut signature: Vec, 219 | mut public_key: Vec, 220 | ) -> Result<[([u8; 64], [u8; 128]); 2]> { 221 | // convert to little endian 222 | for i in (0..=32).step_by(32) { 223 | signature[i..i + 32].reverse() 224 | } 225 | for i in (0..=96).step_by(32) { 226 | public_key[i..i + 32].reverse() 227 | } 228 | 229 | // First pairing input: e(Uncompressed H(m) on G1, Uncompressed PubKey on G2) 230 | let msg_hash_point = hash_to_try_and_increment(message)?; 231 | let msg_hash_arr: [u8; 64] = msg_hash_point.try_to_vec()?.try_into()?; 232 | let pk_arr: [u8; 128] = public_key.try_into()?; 233 | 234 | // Second pairing input: e(Uncompressed Signature on G1,-G2::one()) 235 | let sig_arr: [u8; 64] = signature.try_into()?; 236 | let n_g2: [u8; 128] = (-G2::one()).try_to_vec()?.try_into()?; 237 | 238 | Ok([(msg_hash_arr, pk_arr), (sig_arr, n_g2)]) 239 | } 240 | -------------------------------------------------------------------------------- /src/bn256.json: -------------------------------------------------------------------------------- 1 | { 2 | "add": 3 | [ 4 | { 5 | "x1": "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9", 6 | "y1": "063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266", 7 | "x2": "07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed", 8 | "y2": "06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", 9 | "result": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915" 10 | }, 11 | { 12 | "x1": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703", 13 | "y1": "301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915", 14 | "x2": "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9", 15 | "y2": "063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266", 16 | "result": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204" 17 | }, 18 | { 19 | "x1": "0000000000000000000000000000000000000000000000000000000000000000", 20 | "y1": "0000000000000000000000000000000000000000000000000000000000000000", 21 | "x2": "0000000000000000000000000000000000000000000000000000000000000000", 22 | "y2": "0000000000000000000000000000000000000000000000000000000000000000", 23 | "result": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 24 | }, 25 | { 26 | "x1": "0000000000000000000000000000000000000000000000000000000000000000", 27 | "y1": "0000000000000000000000000000000000000000000000000000000000000000", 28 | "x2": "0000000000000000000000000000000000000000000000000000000000000001", 29 | "y2": "0000000000000000000000000000000000000000000000000000000000000002", 30 | "result": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" 31 | }, 32 | { 33 | "x1": "0000000000000000000000000000000000000000000000000000000000000001", 34 | "y1": "0000000000000000000000000000000000000000000000000000000000000002", 35 | "x2": "0000000000000000000000000000000000000000000000000000000000000001", 36 | "y2": "0000000000000000000000000000000000000000000000000000000000000002", 37 | "result": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4" 38 | }, 39 | { 40 | "x1": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 41 | "y1": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 42 | "x2": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 43 | "y2": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 44 | "result": "15bf2bb17880144b5d1cd2b1f46eff9d617bffd1ca57c37fb5a49bd84e53cf66049c797f9ce0d17083deb32b5e36f2ea2a212ee036598dd7624c168993d1355f" 45 | }, 46 | { 47 | "x1": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 48 | "y1": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 49 | "x2": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 50 | "y2": "2e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb", 51 | "result": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 52 | } 53 | ], 54 | "mul": 55 | [ 56 | { 57 | "x": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb7", 58 | "y": "21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204", 59 | "scalar": "00000000000000000000000000000000000000000000000011138ce750fa15c2", 60 | "result": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc" 61 | }, 62 | { 63 | "x": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c", 64 | "y": "031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc", 65 | "scalar": "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46", 66 | "result": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e" 67 | }, 68 | { 69 | "x": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb", 70 | "y": "2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e", 71 | "scalar": "183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3", 72 | "result": "14789d0d4a730b354403b5fac948113739e276c23e0258d8596ee72f9cd9d3230af18a63153e0ec25ff9f2951dd3fa90ed0197bfef6e2a1a62b5095b9d2b4a27" 73 | }, 74 | { 75 | "x": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3", 76 | "y": "1a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", 77 | "scalar": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 78 | "result": "2cde5879ba6f13c0b5aa4ef627f159a3347df9722efce88a9afbb20b763b4c411aa7e43076f6aee272755a7f9b84832e71559ba0d2e0b17d5f9f01755e5b0d11" 79 | }, 80 | { 81 | "x": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3", 82 | "y": "1a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", 83 | "scalar": "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", 84 | "result": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3163511ddc1c3f25d396745388200081287b3fd1472d8339d5fecb2eae0830451" 85 | }, 86 | { 87 | "x": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3", 88 | "y": "1a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", 89 | "scalar": "0000000000000000000000000000000100000000000000000000000000000000", 90 | "result": "1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b" 91 | }, 92 | { 93 | "x": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3", 94 | "y": "1a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", 95 | "scalar": "0000000000000000000000000000000000000000000000000000000000000009", 96 | "result": "1dbad7d39dbc56379f78fac1bca147dc8e66de1b9d183c7b167351bfe0aeab742cd757d51289cd8dbd0acf9e673ad67d0f0a89f912af47ed1be53664f5692575" 97 | }, 98 | { 99 | "x": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 100 | "y": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 101 | "scalar": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 102 | "result": "29e587aadd7c06722aabba753017c093f70ba7eb1f1c0104ec0564e7e3e21f6022b1143f6a41008e7755c71c3d00b6b915d386de21783ef590486d8afa8453b1" 103 | }, 104 | { 105 | "x": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 106 | "y": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 107 | "scalar": "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", 108 | "result": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb" 109 | }, 110 | { 111 | "x": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 112 | "y": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 113 | "scalar": "0000000000000000000000000000000100000000000000000000000000000000", 114 | "result": "221a3577763877920d0d14a91cd59b9479f83b87a653bb41f82a3f6f120cea7c2752c7f64cdd7f0e494bff7b60419f242210f2026ed2ec70f89f78a4c56a1f15" 115 | }, 116 | { 117 | 118 | "x": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 119 | "y": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 120 | "scalar": "0000000000000000000000000000000000000000000000000000000000000009", 121 | "result": "228e687a379ba154554040f8821f4e41ee2be287c201aa9c3bc02c9dd12f1e691e0fd6ee672d04cfd924ed8fdc7ba5f2d06c53c1edc30f65f2af5a5b97f0a76a" 122 | }, 123 | { 124 | "x": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa9", 125 | "y": "01e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", 126 | "scalar": "0000000000000000000000000000000000000000000000000000000000000001", 127 | "result": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c" 128 | }, 129 | { 130 | "x": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 131 | "y": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 132 | "scalar": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 133 | "result": "00a1a234d08efaa2616607e31eca1980128b00b415c845ff25bba3afcb81dc00242077290ed33906aeb8e42fd98c41bcb9057ba03421af3f2d08cfc441186024" 134 | }, 135 | { 136 | "x": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 137 | "y": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 138 | "scalar": "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", 139 | "result": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b8692929ee761a352600f54921df9bf472e66217e7bb0cee9032e00acc86b3c8bfaf" 140 | }, 141 | { 142 | "x": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 143 | "y": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 144 | "scalar": "0000000000000000000000000000000100000000000000000000000000000000", 145 | "result": "1071b63011e8c222c5a771dfa03c2e11aac9666dd097f2c620852c3951a4376a2f46fe2f73e1cf310a168d56baa5575a8319389d7bfa6b29ee2d908305791434" 146 | }, 147 | { 148 | "x": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 149 | "y": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 150 | "scalar": "0000000000000000000000000000000000000000000000000000000000000009", 151 | "result": "19f75b9dd68c080a688774a6213f131e3052bd353a304a189d7a2ee367e3c2582612f545fb9fc89fde80fd81c68fc7dcb27fea5fc124eeda69433cf5c46d2d7f" 152 | }, 153 | { 154 | "x": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869", 155 | "y": "073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", 156 | "scalar": "0000000000000000000000000000000000000000000000000000000000000001", 157 | "result": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98" 158 | } 159 | ] 160 | } --------------------------------------------------------------------------------