├── .gitignore ├── src ├── lib.rs ├── perf_test.rs ├── scalar.rs ├── secp256k1.rs ├── field.rs ├── biguint.rs └── schnorr.rs ├── Cargo.toml ├── README.md ├── tests └── test_vectors.csv └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod field; 2 | pub mod scalar; 3 | pub mod secp256k1; 4 | pub mod schnorr; 5 | pub mod perf_test; 6 | pub mod biguint; -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "key-protocol" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tfhe = { version = "*", features = ["boolean", "shortint", "integer", "seeder_unix"] } 10 | sha2 = "0.10" 11 | rand = "0.8" 12 | hex = "0.4" 13 | num-bigint = { version = "0.4", features = ["rand"] } 14 | -------------------------------------------------------------------------------- /src/perf_test.rs: -------------------------------------------------------------------------------- 1 | use sha2::{Sha256, Digest}; 2 | use rand::Rng; 3 | use std::time::Instant; 4 | use tfhe::prelude::*; 5 | use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, FheUint8, ClientKey, FheBool}; 6 | 7 | pub fn perf_test() { 8 | // Basic configuration to use homomorphic integers 9 | let config = ConfigBuilder::default().build(); 10 | 11 | // Key generation 12 | let (client_key, server_keys) = generate_keys(config); 13 | 14 | let clear_a = 1344u32; 15 | let clear_b = 5u32; 16 | let clear_c = 7u8; 17 | 18 | // Encrypting the input data using the (private) client_key 19 | let encrypted_a = FheUint32::try_encrypt(clear_a, &client_key).unwrap(); 20 | let encrypted_b = FheUint32::try_encrypt(clear_b, &client_key).unwrap(); 21 | let encrypted_c = FheUint8::try_encrypt(clear_c, &client_key).unwrap(); 22 | 23 | // On the server side: 24 | set_server_key(server_keys); 25 | 26 | // Perform homomorphic operations 27 | let start_add = Instant::now(); 28 | let encrypted_res_add = &encrypted_a + &encrypted_b; // 1344 * 5 29 | let end_add = Instant::now(); 30 | println!("Time taken for add: {:?}", end_add.duration_since(start_add)); 31 | let start_mul = Instant::now(); 32 | let encrypted_res_mul = &encrypted_a * &encrypted_b; // 1344 * 5 33 | let end_mul = Instant::now(); 34 | println!("Time taken for mul: {:?}", end_mul.duration_since(start_mul)); 35 | let start_shift = Instant::now(); 36 | let shifted_a = &encrypted_a >> &encrypted_b; // 6720 >> 5 37 | let end_shift = Instant::now(); 38 | println!("Time taken for shift: {:?}", end_shift.duration_since(start_shift)); 39 | let start_cast = Instant::now(); 40 | let casted_a: FheUint8 = shifted_a.cast_into(); // Cast to u8 41 | let end_cast = Instant::now(); 42 | println!("Time taken for cast: {:?}", end_cast.duration_since(start_cast)); 43 | let start_min = Instant::now(); 44 | let encrypted_res_min = &casted_a.min(&encrypted_c); // min(210, 7) 45 | let end_min = Instant::now(); 46 | println!("Time taken for min: {:?}", end_min.duration_since(start_min)); 47 | let start_and = Instant::now(); 48 | let encrypted_res = encrypted_res_min & 1_u8; // 7 & 1 49 | let end_and = Instant::now(); 50 | println!("Time taken for and: {:?}", end_and.duration_since(start_and)); 51 | 52 | // Keep original encrypted_a for later use 53 | let start_div = Instant::now(); 54 | let encrypted_res_div = &encrypted_a / 5; // 1344 / 5 = 268 55 | let end_div = Instant::now(); 56 | println!("Time taken for div(encrypted/clear): {:?}", end_div.duration_since(start_div)); 57 | 58 | // Decrypting on the client side: 59 | let start_decrypt = Instant::now(); 60 | let clear_res: u8 = encrypted_res.decrypt(&client_key); 61 | let end_decrypt = Instant::now(); 62 | println!("Time taken for decrypt: {:?}", end_decrypt.duration_since(start_decrypt)); 63 | assert_eq!(clear_res, 1_u8); 64 | 65 | println!("Decrypted result: {}", clear_res); 66 | 67 | // Get division result 68 | let start_decrypt_div = Instant::now(); 69 | let decrypted_div: u32 = encrypted_res_div.decrypt(&client_key); 70 | let end_decrypt_div = Instant::now(); 71 | println!("Time taken for decrypt div: {:?}", end_decrypt_div.duration_since(start_decrypt_div)); 72 | let clear_div = clear_a / clear_b; 73 | println!("Clear division result: {}", clear_div); 74 | println!("Decrypted division result: {}", decrypted_div); 75 | assert_eq!(decrypted_div, clear_div); 76 | // Compare with float division 77 | let start_float_div = Instant::now(); 78 | let clear_div_f = clear_a as f32 / clear_b as f32; 79 | let end_float_div = Instant::now(); 80 | println!("Time taken for float div: {:?}", end_float_div.duration_since(start_float_div)); 81 | println!("Float division result: {}", clear_div_f); 82 | // Time taken for add: 83.633613486s 83 | // Time taken for mul: 722.108276373s 84 | // Time taken for shift: 330.283779171s 85 | // Time taken for cast: 46.829µs 86 | // Time taken for min: 38.78299005s 87 | // Time taken for and: 8.523563505s 88 | // Time taken for div: 4211.997434488s 89 | // Time taken for decrypt: 365.956µs 90 | // Time taken for decrypt div: 912.233µs 91 | // Time taken for float div: 78ns 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | 98 | #[test] 99 | fn test_perf_test() { 100 | perf_test(); 101 | } 102 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FHE-Sign: Fully Homomorphic Encryption Signature Library 2 | 3 | A Rust implementation of cryptographic signing operations using Fully Homomorphic Encryption (FHE), with a focus on secp256k1 and Schnorr signatures currently. This project is only for educational purpose. 4 | 5 | ## Overview 6 | 7 | This library provides implementations for: 8 | - Secp256k1 elliptic curve operations 9 | - Field arithmetic in prime fields 10 | - Homomorphic operations on big integers 11 | - Performance testing utilities 12 | - Schnorr signature scheme 13 | 14 | ## Features 15 | 16 | - **Secp256k1 Implementation**: Basic implementation of the secp256k1 elliptic curve, including point addition, doubling, and scalar multiplication. 17 | - **Field Arithmetic**: Generic finite field arithmetic implementation for prime fields. 18 | - **FHE Operations**: Homomorphic operations on encrypted integers using the TFHE library. 19 | - **BigUint FHE**: Custom implementation for handling large encrypted integers. 20 | - **Schnorr Signatures**: Implementation of the Schnorr signature scheme with both standard and FHE variants. 21 | - **BIP340 Test Vectors**: Comprehensive test suite using official test vectors. 22 | 23 | ## Dependencies 24 | 25 | ```toml 26 | [dependencies] 27 | tfhe = { version = "*", features = ["boolean", "shortint", "integer", "seeder_unix"] } 28 | sha2 = "0.10" 29 | rand = "0.8" 30 | hex = "0.4" 31 | num-bigint = { version = "0.4", features = ["rand"] } 32 | ``` 33 | 34 | ## Core Components 35 | 36 | ### 1. Field Operations (`field.rs`) 37 | - Implementation of finite field arithmetic 38 | - Support for addition, subtraction, multiplication, and division 39 | - Modular arithmetic operations 40 | 41 | ### 2. Scalar Operations (`scalar.rs`) 42 | - Scalar field arithmetic for secp256k1 43 | - Conversion between different formats 44 | - Basic arithmetic operations in the scalar field 45 | 46 | ### 3. Secp256k1 (`secp256k1.rs`) 47 | - Basic elliptic curve implementation 48 | - Point arithmetic (addition, doubling, multiplication) 49 | - Generator point and curve parameters 50 | 51 | ### 4. BigUint FHE (`biguint.rs`) 52 | - Homomorphic operations on large integers 53 | - Support for basic arithmetic operations 54 | - Conversion between encrypted and plain formats 55 | 56 | ### 5. Schnorr Signatures (`schnorr.rs`) 57 | - Implementation of FHE-based Schnorr signature scheme 58 | - BIP340-compatible implementation 59 | 60 | ### 6. Performance Testing (`perf_test.rs`) 61 | - Benchmarking utilities for FHE operations 62 | - Comparison of different operation timings 63 | 64 | ## Usage Examples 65 | 66 | ### Basic Schnorr Signature 67 | ```rust 68 | let config = ConfigBuilder::default().build(); 69 | let (client_key, server_keys) = generate_keys(config); 70 | set_server_key(server_keys); 71 | 72 | // Test vector from BIP-340 73 | let seckey_bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000003").unwrap(); 74 | let message = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 75 | let aux_rand = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 76 | 77 | let privkey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 78 | let privkey_fhe = BigUintFHE::new(privkey.value().clone(), &client_key).unwrap(); 79 | let pubkey = get_public_key_with_even_y(&privkey); 80 | 81 | let schnorr = Schnorr::new(); 82 | let k0 = compute_nonce(&privkey.value(), &pubkey, &message, &aux_rand); 83 | // sign with k0 84 | let sig_with_k0 = schnorr.sign_with_k0(&message, &k0, &privkey).unwrap(); 85 | // sign fhe with k0 86 | let sig_fhe_with_k0 = schnorr.sign_fhe_with_k0(&message, &k0, &privkey, &privkey_fhe, &client_key).unwrap(); 87 | 88 | assert_eq!(sig_with_k0.to_bytes(), sig_fhe_with_k0.to_bytes()); 89 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &sig_with_k0.to_bytes())); 90 | ``` 91 | 92 | ## Testing 93 | 94 | The library includes test suites for all components: 95 | 96 | ```bash 97 | cargo test 98 | ``` 99 | 100 | Special test vectors for Schnorr signatures are included in `tests/test_vectors.csv` corresponded to BIP-314 specification. 101 | 102 | ## Performance Considerations 103 | 104 | FHE operations are computationally intensive. The entire signing time of Schnorr signature takes 4269 seconds (about 71 minutes). While this might seem long, it's important to note that this is a proof-of-concept implementation focusing on exploration rather than performance optimization. 105 | 106 | To better understand the time breakdown, here are the single operations: 107 | 108 | - add: 25.965747001s 109 | - mul: 76.051254698s 110 | - shift: 45.566019345s 111 | - cast: 135.023µs 112 | - min: 25.71097148s 113 | - and: 6.418014644s 114 | - div: 1121.134781795s 115 | - decrypt: 186.764µs 116 | - decrypt div: 529.511µs 117 | - float div: 30ns 118 | 119 | These measurements were taken on AWS c5.24xlarge (96 vCPU, 192 GB memory), providing a robust environment for FHE computations. Even with such powerful hardware, the operations remain time-intensive, highlighting both the current limitations and the potential for optimization in FHE technology. 120 | 121 | Note we did not enable the configuration as Zama FHEVM paper did, so the timing is not as good as them. 122 | 123 | 124 | ## License 125 | 126 | MIT 127 | 128 | ## Contributing 129 | 130 | Contributions are welcome! Please follow these steps: 131 | 132 | 1. Fork the repository 133 | 2. Create a new branch for your feature (`git checkout -b feature/amazing-feature`) 134 | 3. Make your changes 135 | 4. Run tests to ensure everything works (`cargo test`) 136 | 5. Commit your changes (`git commit -m 'Add some amazing feature'`) 137 | 6. Push to the branch (`git push origin feature/amazing-feature`) 138 | 7. Open a Pull Request 139 | 140 | ### Development Guidelines 141 | 142 | - Write clear commit messages 143 | - Add tests for new functionality 144 | - Update documentation as needed 145 | - Follow Rust coding standards and best practices 146 | - Ensure all tests pass before submitting PR 147 | 148 | ### Reporting Issues 149 | 150 | If you find a bug or have a feature request, please open an issue: 151 | 152 | 1. Use the GitHub issue tracker 153 | 2. Describe the bug or feature request in detail 154 | 3. Include relevant code examples if applicable 155 | 4. For bugs, include steps to reproduce 156 | -------------------------------------------------------------------------------- /tests/test_vectors.csv: -------------------------------------------------------------------------------- 1 | index,secret key,public key,aux_rand,message,signature,verification result,comment 2 | 0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE, 3 | 1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE, 4 | 2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE, 5 | 3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n 6 | 4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE, 7 | 5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve 8 | 6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false 9 | 7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message 10 | 8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value 11 | 9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0 12 | 10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1 13 | 11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve 14 | 12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size 15 | 13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order 16 | 14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size 17 | 15,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,,71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63,TRUE,message of size 0 (added 2022-12) 18 | 16,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,11,08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF,TRUE,message of size 1 (added 2022-12) 19 | 17,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,0102030405060708090A0B0C0D0E0F1011,5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5,TRUE,message of size 17 (added 2022-12) 20 | 18,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367,TRUE,message of size 100 (added 2022-12) 21 | -------------------------------------------------------------------------------- /src/scalar.rs: -------------------------------------------------------------------------------- 1 | use num_bigint::BigUint; 2 | use crate::field::FieldElement; 3 | 4 | /// The prime field size (p) for secp256k1 curve 5 | const FIELD_SIZE: &str = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"; 6 | 7 | /// The curve order (n) for secp256k1 curve 8 | const CURVE_ORDER: &str = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"; 9 | 10 | /// Returns the field size as a BigUint 11 | pub fn get_field_size() -> BigUint { 12 | BigUint::parse_bytes(&FIELD_SIZE[2..].as_bytes(), 16).unwrap() 13 | } 14 | 15 | /// Returns the curve order as a BigUint 16 | pub fn get_curve_order() -> BigUint { 17 | BigUint::parse_bytes(&CURVE_ORDER[2..].as_bytes(), 16).unwrap() 18 | } 19 | 20 | /// Creates a new field element in the base field (mod p). 21 | pub fn new_base_field(value: BigUint) -> FieldElement { 22 | FieldElement::new(value, get_field_size()) 23 | } 24 | 25 | /// Creates a new field element in the scalar field (mod n). 26 | pub fn new_scalar_field(value: BigUint) -> FieldElement { 27 | FieldElement::new(value, get_curve_order()) 28 | } 29 | 30 | /// Represents a scalar value in the secp256k1 curve's scalar field. 31 | /// This is specifically for scalar multiplication operations in ECC. 32 | #[derive(Clone, Debug, PartialEq)] 33 | pub struct Scalar(FieldElement); 34 | 35 | impl Scalar { 36 | /// Creates a new scalar value, automatically reducing it modulo the curve order. 37 | pub fn new(value: BigUint) -> Self { 38 | Self(new_scalar_field(value)) 39 | } 40 | 41 | /// Creates a scalar from bytes in big-endian format. 42 | /// The bytes are interpreted as an unsigned integer and reduced modulo the curve order. 43 | pub fn from_bytes_be(bytes: &[u8]) -> Self { 44 | let value = BigUint::from_bytes_be(bytes); 45 | Self::new(value) 46 | } 47 | 48 | /// Creates a scalar from bytes in little-endian format. 49 | /// The bytes are interpreted as an unsigned integer and reduced modulo the curve order. 50 | pub fn from_bytes_le(bytes: &[u8]) -> Self { 51 | let value = BigUint::from_bytes_le(bytes); 52 | Self::new(value) 53 | } 54 | 55 | /// Returns the scalar value as a big-endian byte array. 56 | pub fn to_bytes_be(&self) -> Vec { 57 | self.value().to_bytes_be() 58 | } 59 | 60 | /// Returns the scalar value as a little-endian byte array. 61 | pub fn to_bytes_le(&self) -> Vec { 62 | self.value().to_bytes_le() 63 | } 64 | 65 | /// Returns the zero scalar. 66 | pub fn zero() -> Self { 67 | Self::new(BigUint::from(0u32)) 68 | } 69 | 70 | /// Returns the one scalar (multiplicative identity). 71 | pub fn one() -> Self { 72 | Self::new(BigUint::from(1u32)) 73 | } 74 | 75 | /// Creates a scalar from a signed integer. 76 | pub fn from_i32(value: i32) -> Self { 77 | let order = get_curve_order(); 78 | let value = if value < 0 { 79 | let abs_val = (-value) as u32; 80 | let abs_big = BigUint::from(abs_val); 81 | &order - (abs_big % &order) 82 | } else { 83 | BigUint::from(value as u32) % &order 84 | }; 85 | Self::new(value) 86 | } 87 | 88 | /// Returns the underlying field element. 89 | pub fn as_field_element(&self) -> &FieldElement { 90 | &self.0 91 | } 92 | 93 | /// Returns the value of the scalar. 94 | pub fn value(&self) -> &BigUint { 95 | self.0.value() 96 | } 97 | 98 | /// Adds two scalars modulo the curve order. 99 | pub fn add(&self, other: &Scalar) -> Scalar { 100 | Self(self.0.clone() + &other.0) 101 | } 102 | 103 | /// Subtracts two scalars modulo the curve order. 104 | pub fn sub(&self, other: &Scalar) -> Scalar { 105 | Self(self.0.clone() - &other.0) 106 | } 107 | 108 | /// Multiplies two scalars modulo the curve order. 109 | pub fn mul(&self, other: &Scalar) -> Scalar { 110 | Self(self.0.clone() * &other.0) 111 | } 112 | 113 | /// Computes the additive inverse of the scalar. 114 | pub fn neg(&self) -> Scalar { 115 | Self(-self.0.clone()) 116 | } 117 | 118 | /// Computes the multiplicative inverse of the scalar. 119 | pub fn inverse(&self) -> Scalar { 120 | Self(self.0.inverse()) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | #[test] 129 | fn test_scalar_addition() { 130 | let a = Scalar::new(BigUint::from(5u32)); 131 | let b = Scalar::new(BigUint::from(3u32)); 132 | let c = a.add(&b); 133 | assert_eq!(c.value(), &BigUint::from(8u32)); 134 | } 135 | 136 | #[test] 137 | fn test_scalar_subtraction() { 138 | let a = Scalar::new(BigUint::from(5u32)); 139 | let b = Scalar::new(BigUint::from(3u32)); 140 | let c = a.sub(&b); 141 | assert_eq!(c.value(), &BigUint::from(2u32)); 142 | } 143 | 144 | #[test] 145 | fn test_scalar_multiplication() { 146 | let a = Scalar::new(BigUint::from(5u32)); 147 | let b = Scalar::new(BigUint::from(3u32)); 148 | let c = a.mul(&b); 149 | assert_eq!(c.value(), &BigUint::from(15u32)); 150 | } 151 | 152 | #[test] 153 | fn test_scalar_negation() { 154 | let a = Scalar::new(BigUint::from(5u32)); 155 | let neg_a = a.neg(); 156 | let sum = a.add(&neg_a); 157 | assert_eq!(sum.value(), &BigUint::from(0u32)); 158 | } 159 | 160 | #[test] 161 | fn test_scalar_inverse() { 162 | let a = Scalar::new(BigUint::from(5u32)); 163 | let a_inv = a.inverse(); 164 | let product = a.mul(&a_inv); 165 | assert_eq!(product.value(), &BigUint::from(1u32)); 166 | } 167 | 168 | #[test] 169 | fn test_scalar_from_bytes() { 170 | let bytes = [0x12, 0x34, 0x56, 0x78]; 171 | let scalar = Scalar::from_bytes_be(&bytes); 172 | assert_eq!(scalar.to_bytes_be(), bytes); 173 | 174 | let scalar_le = Scalar::from_bytes_le(&bytes); 175 | assert_eq!(scalar_le.to_bytes_le(), bytes); 176 | } 177 | 178 | #[test] 179 | fn test_scalar_zero_and_one() { 180 | let zero = Scalar::zero(); 181 | let one = Scalar::one(); 182 | 183 | assert_eq!(zero.value(), &BigUint::from(0u32)); 184 | assert_eq!(one.value(), &BigUint::from(1u32)); 185 | 186 | // Test that 0 + 1 = 1 187 | assert_eq!(zero.add(&one), one); 188 | 189 | // Test that 1 * 1 = 1 190 | assert_eq!(one.mul(&one), one); 191 | 192 | // Test that 0 * x = 0 for some arbitrary x 193 | let x = Scalar::new(BigUint::from(123u32)); 194 | assert_eq!(zero.mul(&x), zero); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/secp256k1.rs: -------------------------------------------------------------------------------- 1 | use crate::field::FieldElement; 2 | use crate::scalar::{Scalar, new_base_field, get_field_size}; 3 | use std::{clone::Clone, fmt::{Debug, Display}, ops::{Sub, Neg}}; 4 | use num_bigint::BigUint; 5 | 6 | /// Implementation of the secp256k1 elliptic curve: y^2 = x^3 + 7 (mod p) 7 | /// The curve is defined over the prime field GF(p) where p is the field size. 8 | 9 | // Curve parameters 10 | const A: u32 = 0; // Coefficient of x term 11 | const B: u32 = 7; // Constant term 12 | 13 | /// A point on the secp256k1 curve. 14 | /// Points are represented in affine coordinates (x, y). 15 | /// The point at infinity is represented by a special flag. 16 | #[derive(Clone, Debug, PartialEq)] 17 | pub struct Point { 18 | pub x: FieldElement, 19 | pub y: FieldElement, 20 | pub is_infinity: bool, 21 | } 22 | 23 | impl Point { 24 | /// Creates a new point on the curve. 25 | /// Verifies that the point satisfies the curve equation y^2 = x^3 + 7 (mod p). 26 | pub fn new(x: FieldElement, y: FieldElement, is_infinity: bool) -> Self { 27 | if !is_infinity { 28 | // Verify that the point is on the curve 29 | let y2 = &y * &y; 30 | let x3 = &x * &x * &x; 31 | let rhs = x3 + new_base_field(BigUint::from(B)); 32 | if y2 != rhs { 33 | println!("Point is not on the curve"); 34 | return Self::infinity(); 35 | } 36 | } 37 | Self { x, y, is_infinity } 38 | } 39 | 40 | /// Creates a new point at infinity (the identity element of the curve). 41 | pub fn infinity() -> Self { 42 | Self { 43 | x: new_base_field(BigUint::from(0u32)), 44 | y: new_base_field(BigUint::from(0u32)), 45 | is_infinity: true, 46 | } 47 | } 48 | 49 | /// Adds two points on the curve using the standard elliptic curve addition formulas. 50 | pub fn add(&self, other: &Point) -> Self { 51 | // Handle special cases involving point at infinity 52 | if self.is_infinity { 53 | return other.clone(); 54 | } 55 | if other.is_infinity { 56 | return self.clone(); 57 | } 58 | 59 | // If points are inverses of each other, return point at infinity 60 | if self.x == other.x { 61 | if self.y == other.y { 62 | // Point doubling case - handle it directly here 63 | let three = new_base_field(BigUint::from(3u32)); 64 | let two = new_base_field(BigUint::from(2u32)); 65 | let a = new_base_field(BigUint::from(A)); 66 | 67 | let numerator = &(&three * &self.x * &self.x) + &a; 68 | let denominator = &two * &self.y; 69 | let lambda = &numerator / &denominator; 70 | 71 | // x3 = lambda^2 - 2x 72 | let x3 = &(&lambda * &lambda) - &(&two * &self.x); 73 | 74 | // y3 = lambda(x - x3) - y 75 | let y3 = &(&lambda * &(&self.x - &x3)) - &self.y; 76 | 77 | return Self::new(x3, y3, false); 78 | } 79 | if &self.y == &(-other.y.clone()) { 80 | return Self::infinity(); 81 | } 82 | } 83 | 84 | // Different points addition: lambda = (y2 - y1) / (x2 - x1) 85 | let numerator = &other.y - &self.y; 86 | let denominator = &other.x - &self.x; 87 | let lambda = &numerator / &denominator; 88 | 89 | // Calculate new point coordinates 90 | // x3 = lambda^2 - x1 - x2 91 | let x3 = &(&lambda * &lambda) - &self.x - &other.x; 92 | 93 | // y3 = lambda(x1 - x3) - y1 94 | let y3 = &(&lambda * &(&self.x - &x3)) - &self.y; 95 | 96 | Self::new(x3, y3, false) 97 | } 98 | 99 | /// Doubles a point on the curve (adds it to itself). 100 | pub fn double(&self) -> Self { 101 | self.add(self) // Now safe to use add since we handle doubling directly in add 102 | } 103 | 104 | /// Multiplies a point by a scalar using the double-and-add algorithm. 105 | /// This is an optimized version that avoids recursive calls and minimizes cloning. 106 | pub fn scalar_mul(&self, scalar: &Scalar) -> Self { 107 | if self.is_infinity { 108 | return Self::infinity(); 109 | } 110 | 111 | let mut result = Self::infinity(); 112 | let mut current = self.clone(); 113 | let mut scalar_bits = scalar.value().clone(); 114 | let zero = BigUint::from(0u32); 115 | let one = BigUint::from(1u32); 116 | 117 | // Double-and-add algorithm 118 | while scalar_bits > zero { 119 | if &scalar_bits & &one == one { 120 | result = result.add(¤t); 121 | } 122 | current = current.double(); 123 | scalar_bits >>= 1; 124 | } 125 | 126 | result 127 | } 128 | 129 | /// Returns the base point G of the secp256k1 curve. 130 | pub fn get_generator() -> Self { 131 | // Generator point coordinates from the secp256k1 specification 132 | let gx = FieldElement::new( 133 | BigUint::parse_bytes(b"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16).unwrap(), 134 | get_field_size() 135 | ); 136 | let gy = FieldElement::new( 137 | BigUint::parse_bytes(b"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16).unwrap(), 138 | get_field_size() 139 | ); 140 | Point::new(gx, gy, false) // Not a point at infinity 141 | } 142 | } 143 | 144 | impl Display for Point { 145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 146 | if self.is_infinity { 147 | write!(f, "Point at infinity") 148 | } else { 149 | write!(f, "({}, {})", self.x, self.y) 150 | } 151 | } 152 | } 153 | 154 | impl Sub for Point { 155 | type Output = Self; 156 | 157 | fn sub(self, other: Self) -> Self::Output { 158 | self.add(&(-other)) 159 | } 160 | } 161 | 162 | impl Sub<&Point> for Point { 163 | type Output = Self; 164 | 165 | fn sub(self, other: &Point) -> Self::Output { 166 | self.add(&(-other.clone())) 167 | } 168 | } 169 | 170 | impl Neg for Point { 171 | type Output = Self; 172 | 173 | fn neg(self) -> Self::Output { 174 | if self.is_infinity { 175 | return Self::infinity(); 176 | } 177 | Self { 178 | x: self.x.clone(), 179 | y: -self.y, 180 | is_infinity: false, 181 | } 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use super::*; 188 | 189 | /// Returns the generator point of the secp256k1 curve 190 | fn get_generator() -> Point { 191 | let x = BigUint::parse_bytes(b"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16).unwrap(); 192 | let y = BigUint::parse_bytes(b"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16).unwrap(); 193 | 194 | Point::new( 195 | new_base_field(x), 196 | new_base_field(y), 197 | false 198 | ) 199 | } 200 | 201 | #[test] 202 | fn test_generator_point() { 203 | let g = get_generator(); 204 | 205 | // Verify that G is on the curve 206 | let y2 = &g.y * &g.y; 207 | let x3 = &g.x * &g.x * &g.x; 208 | let rhs = x3 + new_base_field(BigUint::from(B)); 209 | assert_eq!(y2, rhs, "Generator point is not on the curve"); 210 | } 211 | 212 | #[test] 213 | fn test_point_at_infinity() { 214 | let point = Point::infinity(); 215 | assert!(point.is_infinity); 216 | } 217 | 218 | #[test] 219 | fn test_point_addition() { 220 | let g = get_generator(); 221 | let g2 = g.double(); 222 | 223 | // Verify G + G = 2G is on the curve 224 | let y2 = &g2.y * &g2.y; 225 | let x3 = &g2.x * &g2.x * &g2.x; 226 | let rhs = x3 + new_base_field(BigUint::from(B)); 227 | assert_eq!(y2, rhs, "2G is not on the curve"); 228 | } 229 | 230 | #[test] 231 | fn test_scalar_multiplication() { 232 | let g = get_generator(); 233 | let scalar = Scalar::new(BigUint::from(2u32)); 234 | let g2 = g.scalar_mul(&scalar); 235 | 236 | // Verify 2G is on the curve 237 | let y2 = &g2.y * &g2.y; 238 | let x3 = &g2.x * &g2.x * &g2.x; 239 | let rhs = x3 + new_base_field(BigUint::from(B)); 240 | assert_eq!(y2, rhs, "2G is not on the curve"); 241 | 242 | // Verify that scalar multiplication matches repeated addition 243 | let g2_add = g.add(&g); 244 | assert_eq!(g2, g2_add); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/field.rs: -------------------------------------------------------------------------------- 1 | use std::{clone::Clone, fmt::{Debug, Display}, ops::{Add, Div, Mul, Neg, Sub}}; 2 | use num_bigint::{BigUint, BigInt}; 3 | 4 | /// Represents an element in a prime field GF(p). 5 | /// All operations are performed modulo the field characteristic p. 6 | #[derive(Clone, Debug, PartialEq)] 7 | pub struct FieldElement { 8 | value: BigUint, 9 | order: BigUint, 10 | } 11 | 12 | impl FieldElement { 13 | /// Creates a new field element with the given value and order. 14 | /// The value is automatically reduced modulo the order. 15 | pub fn new(value: BigUint, order: BigUint) -> Self { 16 | let reduced_value = value % ℴ 17 | Self { value: reduced_value, order } 18 | } 19 | 20 | /// Returns the value of the field element. 21 | pub fn value(&self) -> &BigUint { 22 | &self.value 23 | } 24 | 25 | /// Returns the order of the field. 26 | pub fn order(&self) -> &BigUint { 27 | &self.order 28 | } 29 | 30 | /// Computes the multiplicative inverse using the Extended Euclidean Algorithm. 31 | pub fn inverse(&self) -> Self { 32 | if self.value == BigUint::from(0u32) { 33 | panic!("Cannot compute multiplicative inverse of zero"); 34 | } 35 | 36 | let mut t = BigInt::from(0); 37 | let mut newt = BigInt::from(1); 38 | let mut r = BigInt::from(self.order.clone()); 39 | let mut newr = BigInt::from(self.value.clone()); 40 | 41 | while newr != BigInt::from(0) { 42 | let quotient = &r / &newr; 43 | let t_next = newt.clone(); 44 | let r_next = newr.clone(); 45 | newt = t - "ient * &newt; 46 | newr = r - "ient * &newr; 47 | t = t_next; 48 | r = r_next; 49 | } 50 | 51 | if r > BigInt::from(1) { 52 | panic!("Value and field order are not coprime"); 53 | } 54 | 55 | // Make positive 56 | while t < BigInt::from(0) { 57 | t = t + BigInt::from(self.order.clone()); 58 | } 59 | 60 | // Convert back to BigUint and reduce mod n 61 | let value = match t.to_biguint() { 62 | Some(v) => v % &self.order, 63 | None => panic!("Failed to convert to unsigned"), 64 | }; 65 | 66 | Self { 67 | value, 68 | order: self.order.clone(), 69 | } 70 | } 71 | 72 | /// Compute the square root of this field element using the Tonelli-Shanks algorithm 73 | pub fn sqrt(&self) -> Self { 74 | // For secp256k1's prime p, we know that p ≡ 3 (mod 4) 75 | // So we can use the formula: sqrt(a) = a^((p+1)/4) mod p 76 | let p = &self.order; 77 | let exp = (p + BigUint::from(1u32)) >> 2; 78 | self.pow(&exp) 79 | } 80 | 81 | /// Compute self^exp in the field 82 | pub fn pow(&self, exp: &BigUint) -> Self { 83 | let mut base = self.clone(); 84 | let mut result = FieldElement::new(BigUint::from(1u32), self.order.clone()); 85 | let mut exp = exp.clone(); 86 | 87 | while exp > BigUint::from(0u32) { 88 | if &exp % BigUint::from(2u32) == BigUint::from(1u32) { 89 | result = &result * &base; 90 | } 91 | base = &base * &base; 92 | exp >>= 1; 93 | } 94 | result 95 | } 96 | } 97 | 98 | impl Add for FieldElement { 99 | type Output = Self; 100 | 101 | fn add(self, other: Self) -> Self::Output { 102 | assert_eq!(self.order, other.order, "Cannot add elements from different fields"); 103 | Self { 104 | value: (self.value + other.value) % &self.order, 105 | order: self.order, 106 | } 107 | } 108 | } 109 | 110 | impl<'a> Add<&'a FieldElement> for FieldElement { 111 | type Output = Self; 112 | 113 | fn add(self, other: &'a FieldElement) -> Self::Output { 114 | assert_eq!(self.order, other.order, "Cannot add elements from different fields"); 115 | Self { 116 | value: (self.value + &other.value) % &self.order, 117 | order: self.order, 118 | } 119 | } 120 | } 121 | 122 | impl<'a> Add<&'a FieldElement> for &'a FieldElement { 123 | type Output = FieldElement; 124 | 125 | fn add(self, other: &'a FieldElement) -> Self::Output { 126 | assert_eq!(self.order, other.order, "Cannot add elements from different fields"); 127 | FieldElement { 128 | value: (&self.value + &other.value) % &self.order, 129 | order: self.order.clone(), 130 | } 131 | } 132 | } 133 | 134 | impl Neg for FieldElement { 135 | type Output = Self; 136 | 137 | fn neg(self) -> Self::Output { 138 | Self { 139 | value: if self.value == BigUint::from(0u32) { 140 | BigUint::from(0u32) 141 | } else { 142 | &self.order - &self.value 143 | }, 144 | order: self.order, 145 | } 146 | } 147 | } 148 | 149 | impl Sub for FieldElement { 150 | type Output = Self; 151 | 152 | fn sub(self, other: Self) -> Self::Output { 153 | assert_eq!(self.order, other.order, "Cannot subtract elements from different fields"); 154 | let mut result = self.value; 155 | if result < other.value { 156 | result = result + &self.order; 157 | } 158 | Self { 159 | value: (result - other.value) % &self.order, 160 | order: self.order, 161 | } 162 | } 163 | } 164 | 165 | impl<'a> Sub<&'a FieldElement> for FieldElement { 166 | type Output = Self; 167 | 168 | fn sub(self, other: &'a FieldElement) -> Self::Output { 169 | assert_eq!(self.order, other.order, "Cannot subtract elements from different fields"); 170 | let mut result = self.value; 171 | if result < other.value { 172 | result = result + &self.order; 173 | } 174 | Self { 175 | value: (result - &other.value) % &self.order, 176 | order: self.order, 177 | } 178 | } 179 | } 180 | 181 | impl<'a> Sub<&'a FieldElement> for &'a FieldElement { 182 | type Output = FieldElement; 183 | 184 | fn sub(self, other: &'a FieldElement) -> Self::Output { 185 | assert_eq!(self.order, other.order, "Cannot subtract elements from different fields"); 186 | let mut result = self.value.clone(); 187 | if result < other.value { 188 | result = result + &self.order; 189 | } 190 | FieldElement { 191 | value: (result - &other.value) % &self.order, 192 | order: self.order.clone(), 193 | } 194 | } 195 | } 196 | 197 | impl Mul for FieldElement { 198 | type Output = Self; 199 | 200 | fn mul(self, other: Self) -> Self::Output { 201 | assert_eq!(self.order, other.order, "Cannot multiply elements from different fields"); 202 | Self { 203 | value: (self.value * other.value) % &self.order, 204 | order: self.order, 205 | } 206 | } 207 | } 208 | 209 | impl<'a> Mul<&'a FieldElement> for FieldElement { 210 | type Output = Self; 211 | 212 | fn mul(self, other: &'a FieldElement) -> Self::Output { 213 | assert_eq!(self.order, other.order, "Cannot multiply elements from different fields"); 214 | Self { 215 | value: (self.value * &other.value) % &self.order, 216 | order: self.order, 217 | } 218 | } 219 | } 220 | 221 | impl<'a> Mul<&'a FieldElement> for &'a FieldElement { 222 | type Output = FieldElement; 223 | 224 | fn mul(self, other: &'a FieldElement) -> Self::Output { 225 | assert_eq!(self.order, other.order, "Cannot multiply elements from different fields"); 226 | FieldElement { 227 | value: (&self.value * &other.value) % &self.order, 228 | order: self.order.clone(), 229 | } 230 | } 231 | } 232 | 233 | impl Div for FieldElement { 234 | type Output = Self; 235 | 236 | fn div(self, other: Self) -> Self::Output { 237 | assert_eq!(self.order, other.order, "Cannot divide elements from different fields"); 238 | if other.value == BigUint::from(0u32) { 239 | panic!("Division by zero"); 240 | } 241 | self * other.inverse() 242 | } 243 | } 244 | 245 | impl<'a> Div<&'a FieldElement> for FieldElement { 246 | type Output = Self; 247 | 248 | fn div(self, other: &'a FieldElement) -> Self::Output { 249 | assert_eq!(self.order, other.order, "Cannot divide elements from different fields"); 250 | if other.value == BigUint::from(0u32) { 251 | panic!("Division by zero"); 252 | } 253 | self * other.inverse() 254 | } 255 | } 256 | 257 | impl<'a> Div<&'a FieldElement> for &'a FieldElement { 258 | type Output = FieldElement; 259 | 260 | fn div(self, other: &'a FieldElement) -> Self::Output { 261 | assert_eq!(self.order, other.order, "Cannot divide elements from different fields"); 262 | if other.value == BigUint::from(0u32) { 263 | panic!("Division by zero"); 264 | } 265 | self * &other.inverse() 266 | } 267 | } 268 | 269 | impl Display for FieldElement { 270 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 271 | write!(f, "{}", self.value) 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod tests { 277 | use super::*; 278 | 279 | #[test] 280 | fn test_field_element_creation() { 281 | let order = BigUint::from(17u32); 282 | let value = BigUint::from(20u32); 283 | let element = FieldElement::new(value, order.clone()); 284 | assert_eq!(element.value, BigUint::from(3u32)); 285 | assert_eq!(element.order, order); 286 | } 287 | 288 | #[test] 289 | fn test_field_element_addition() { 290 | let order = BigUint::from(17u32); 291 | let a = FieldElement::new(BigUint::from(15u32), order.clone()); 292 | let b = FieldElement::new(BigUint::from(5u32), order.clone()); 293 | let sum = &a + &b; 294 | assert_eq!(sum.value, BigUint::from(3u32)); 295 | } 296 | 297 | #[test] 298 | fn test_field_element_subtraction() { 299 | let order = BigUint::from(17u32); 300 | let a = FieldElement::new(BigUint::from(5u32), order.clone()); 301 | let b = FieldElement::new(BigUint::from(10u32), order.clone()); 302 | let diff = &a - &b; 303 | assert_eq!(diff.value, BigUint::from(12u32)); 304 | } 305 | 306 | #[test] 307 | fn test_field_element_multiplication() { 308 | let order = BigUint::from(17u32); 309 | let a = FieldElement::new(BigUint::from(5u32), order.clone()); 310 | let b = FieldElement::new(BigUint::from(10u32), order.clone()); 311 | let product = &a * &b; 312 | assert_eq!(product.value, BigUint::from(16u32)); 313 | } 314 | 315 | #[test] 316 | fn test_field_element_division() { 317 | let order = BigUint::from(17u32); 318 | let a = FieldElement::new(BigUint::from(5u32), order.clone()); 319 | let b = FieldElement::new(BigUint::from(10u32), order.clone()); 320 | let quotient = &a / &b; 321 | let product = "ient * &b; 322 | assert_eq!(product.value, a.value); 323 | } 324 | 325 | #[test] 326 | fn test_field_element_inverse() { 327 | let order = BigUint::from(17u32); 328 | let a = FieldElement::new(BigUint::from(5u32), order.clone()); 329 | let a_inv = a.inverse(); 330 | let product = &a * &a_inv; 331 | assert_eq!(product.value, BigUint::from(1u32)); 332 | } 333 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.8.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | ] 15 | 16 | [[package]] 17 | name = "aligned-vec" 18 | version = "0.5.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" 21 | dependencies = [ 22 | "serde", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.4.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 30 | 31 | [[package]] 32 | name = "bincode" 33 | version = "1.3.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 36 | dependencies = [ 37 | "serde", 38 | ] 39 | 40 | [[package]] 41 | name = "block-buffer" 42 | version = "0.10.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 45 | dependencies = [ 46 | "generic-array", 47 | ] 48 | 49 | [[package]] 50 | name = "bumpalo" 51 | version = "3.16.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 54 | 55 | [[package]] 56 | name = "bytemuck" 57 | version = "1.21.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" 60 | 61 | [[package]] 62 | name = "byteorder" 63 | version = "1.5.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "cipher" 75 | version = "0.4.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 78 | dependencies = [ 79 | "crypto-common", 80 | "inout", 81 | ] 82 | 83 | [[package]] 84 | name = "concrete-csprng" 85 | version = "0.4.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "90518357249582c16a6b64d7410243dfb3109d5bf0ad1665c058c9a59f2fc4cc" 88 | dependencies = [ 89 | "aes", 90 | "libc", 91 | "rayon", 92 | ] 93 | 94 | [[package]] 95 | name = "concrete-fft" 96 | version = "0.5.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "86ff7397e00e903afb03f0adca6a5f3bec3a6e96a7cdb70bdc088e01b125e170" 99 | dependencies = [ 100 | "aligned-vec", 101 | "bytemuck", 102 | "dyn-stack", 103 | "js-sys", 104 | "num-complex", 105 | "pulp", 106 | "serde", 107 | ] 108 | 109 | [[package]] 110 | name = "concrete-ntt" 111 | version = "0.2.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "bea708a14b4cfe650eec644eac11889a187404a3a0738a41a5524b0f548850a1" 114 | dependencies = [ 115 | "aligned-vec", 116 | "pulp", 117 | ] 118 | 119 | [[package]] 120 | name = "cpufeatures" 121 | version = "0.2.16" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" 124 | dependencies = [ 125 | "libc", 126 | ] 127 | 128 | [[package]] 129 | name = "crossbeam-deque" 130 | version = "0.8.6" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 133 | dependencies = [ 134 | "crossbeam-epoch", 135 | "crossbeam-utils", 136 | ] 137 | 138 | [[package]] 139 | name = "crossbeam-epoch" 140 | version = "0.9.18" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 143 | dependencies = [ 144 | "crossbeam-utils", 145 | ] 146 | 147 | [[package]] 148 | name = "crossbeam-utils" 149 | version = "0.8.21" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 152 | 153 | [[package]] 154 | name = "crypto-common" 155 | version = "0.1.6" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 158 | dependencies = [ 159 | "generic-array", 160 | "typenum", 161 | ] 162 | 163 | [[package]] 164 | name = "digest" 165 | version = "0.10.7" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 168 | dependencies = [ 169 | "block-buffer", 170 | "crypto-common", 171 | ] 172 | 173 | [[package]] 174 | name = "dyn-stack" 175 | version = "0.10.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b" 178 | dependencies = [ 179 | "bytemuck", 180 | "reborrow", 181 | ] 182 | 183 | [[package]] 184 | name = "either" 185 | version = "1.13.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 188 | 189 | [[package]] 190 | name = "generic-array" 191 | version = "0.14.7" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 194 | dependencies = [ 195 | "typenum", 196 | "version_check", 197 | ] 198 | 199 | [[package]] 200 | name = "getrandom" 201 | version = "0.2.15" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 204 | dependencies = [ 205 | "cfg-if", 206 | "libc", 207 | "wasi", 208 | ] 209 | 210 | [[package]] 211 | name = "hex" 212 | version = "0.4.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 215 | 216 | [[package]] 217 | name = "inout" 218 | version = "0.1.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 221 | dependencies = [ 222 | "generic-array", 223 | ] 224 | 225 | [[package]] 226 | name = "itertools" 227 | version = "0.11.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 230 | dependencies = [ 231 | "either", 232 | ] 233 | 234 | [[package]] 235 | name = "js-sys" 236 | version = "0.3.76" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 239 | dependencies = [ 240 | "once_cell", 241 | "wasm-bindgen", 242 | ] 243 | 244 | [[package]] 245 | name = "keccak" 246 | version = "0.1.5" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" 249 | dependencies = [ 250 | "cpufeatures", 251 | ] 252 | 253 | [[package]] 254 | name = "key-protocol" 255 | version = "0.0.1" 256 | dependencies = [ 257 | "hex", 258 | "num-bigint", 259 | "rand", 260 | "sha2", 261 | "tfhe", 262 | ] 263 | 264 | [[package]] 265 | name = "libc" 266 | version = "0.2.169" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 269 | 270 | [[package]] 271 | name = "libm" 272 | version = "0.2.11" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 275 | 276 | [[package]] 277 | name = "log" 278 | version = "0.4.22" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 281 | 282 | [[package]] 283 | name = "num-bigint" 284 | version = "0.4.6" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 287 | dependencies = [ 288 | "num-integer", 289 | "num-traits", 290 | "rand", 291 | ] 292 | 293 | [[package]] 294 | name = "num-complex" 295 | version = "0.4.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 298 | dependencies = [ 299 | "bytemuck", 300 | "num-traits", 301 | "serde", 302 | ] 303 | 304 | [[package]] 305 | name = "num-integer" 306 | version = "0.1.46" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 309 | dependencies = [ 310 | "num-traits", 311 | ] 312 | 313 | [[package]] 314 | name = "num-traits" 315 | version = "0.2.19" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 318 | dependencies = [ 319 | "autocfg", 320 | ] 321 | 322 | [[package]] 323 | name = "once_cell" 324 | version = "1.20.2" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 327 | 328 | [[package]] 329 | name = "paste" 330 | version = "1.0.15" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 333 | 334 | [[package]] 335 | name = "ppv-lite86" 336 | version = "0.2.20" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 339 | dependencies = [ 340 | "zerocopy", 341 | ] 342 | 343 | [[package]] 344 | name = "proc-macro2" 345 | version = "1.0.92" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 348 | dependencies = [ 349 | "unicode-ident", 350 | ] 351 | 352 | [[package]] 353 | name = "pulp" 354 | version = "0.18.22" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" 357 | dependencies = [ 358 | "bytemuck", 359 | "libm", 360 | "num-complex", 361 | "reborrow", 362 | ] 363 | 364 | [[package]] 365 | name = "quote" 366 | version = "1.0.38" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 369 | dependencies = [ 370 | "proc-macro2", 371 | ] 372 | 373 | [[package]] 374 | name = "rand" 375 | version = "0.8.5" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 378 | dependencies = [ 379 | "libc", 380 | "rand_chacha", 381 | "rand_core", 382 | ] 383 | 384 | [[package]] 385 | name = "rand_chacha" 386 | version = "0.3.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 389 | dependencies = [ 390 | "ppv-lite86", 391 | "rand_core", 392 | ] 393 | 394 | [[package]] 395 | name = "rand_core" 396 | version = "0.6.4" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 399 | dependencies = [ 400 | "getrandom", 401 | ] 402 | 403 | [[package]] 404 | name = "rayon" 405 | version = "1.10.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 408 | dependencies = [ 409 | "either", 410 | "rayon-core", 411 | ] 412 | 413 | [[package]] 414 | name = "rayon-core" 415 | version = "1.12.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 418 | dependencies = [ 419 | "crossbeam-deque", 420 | "crossbeam-utils", 421 | ] 422 | 423 | [[package]] 424 | name = "reborrow" 425 | version = "0.5.5" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" 428 | 429 | [[package]] 430 | name = "serde" 431 | version = "1.0.216" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 434 | dependencies = [ 435 | "serde_derive", 436 | ] 437 | 438 | [[package]] 439 | name = "serde_derive" 440 | version = "1.0.216" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 443 | dependencies = [ 444 | "proc-macro2", 445 | "quote", 446 | "syn", 447 | ] 448 | 449 | [[package]] 450 | name = "sha2" 451 | version = "0.10.8" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 454 | dependencies = [ 455 | "cfg-if", 456 | "cpufeatures", 457 | "digest", 458 | ] 459 | 460 | [[package]] 461 | name = "sha3" 462 | version = "0.10.8" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" 465 | dependencies = [ 466 | "digest", 467 | "keccak", 468 | ] 469 | 470 | [[package]] 471 | name = "syn" 472 | version = "2.0.92" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" 475 | dependencies = [ 476 | "proc-macro2", 477 | "quote", 478 | "unicode-ident", 479 | ] 480 | 481 | [[package]] 482 | name = "tfhe" 483 | version = "0.10.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "7190b3967c9322674bae8f247380c8a90d38448fbd8cd35b17a1958f39190ec2" 486 | dependencies = [ 487 | "aligned-vec", 488 | "bincode", 489 | "bytemuck", 490 | "concrete-csprng", 491 | "concrete-fft", 492 | "concrete-ntt", 493 | "dyn-stack", 494 | "itertools", 495 | "paste", 496 | "pulp", 497 | "rand_core", 498 | "rayon", 499 | "serde", 500 | "sha3", 501 | "tfhe-versionable", 502 | ] 503 | 504 | [[package]] 505 | name = "tfhe-versionable" 506 | version = "0.3.2" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "32111f6df1b4ced57bea0bc548a35eb8130e5b9f2084378299eeacf4148cb8a5" 509 | dependencies = [ 510 | "aligned-vec", 511 | "num-complex", 512 | "serde", 513 | "tfhe-versionable-derive", 514 | ] 515 | 516 | [[package]] 517 | name = "tfhe-versionable-derive" 518 | version = "0.3.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "a463428890873548472daba5bdcecfe34b89c98518b4bd6cbd8595ac48fc0771" 521 | dependencies = [ 522 | "proc-macro2", 523 | "quote", 524 | "syn", 525 | ] 526 | 527 | [[package]] 528 | name = "typenum" 529 | version = "1.17.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 532 | 533 | [[package]] 534 | name = "unicode-ident" 535 | version = "1.0.14" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 538 | 539 | [[package]] 540 | name = "version_check" 541 | version = "0.9.5" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 544 | 545 | [[package]] 546 | name = "wasi" 547 | version = "0.11.0+wasi-snapshot-preview1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 550 | 551 | [[package]] 552 | name = "wasm-bindgen" 553 | version = "0.2.99" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 556 | dependencies = [ 557 | "cfg-if", 558 | "once_cell", 559 | "wasm-bindgen-macro", 560 | ] 561 | 562 | [[package]] 563 | name = "wasm-bindgen-backend" 564 | version = "0.2.99" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 567 | dependencies = [ 568 | "bumpalo", 569 | "log", 570 | "proc-macro2", 571 | "quote", 572 | "syn", 573 | "wasm-bindgen-shared", 574 | ] 575 | 576 | [[package]] 577 | name = "wasm-bindgen-macro" 578 | version = "0.2.99" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 581 | dependencies = [ 582 | "quote", 583 | "wasm-bindgen-macro-support", 584 | ] 585 | 586 | [[package]] 587 | name = "wasm-bindgen-macro-support" 588 | version = "0.2.99" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 591 | dependencies = [ 592 | "proc-macro2", 593 | "quote", 594 | "syn", 595 | "wasm-bindgen-backend", 596 | "wasm-bindgen-shared", 597 | ] 598 | 599 | [[package]] 600 | name = "wasm-bindgen-shared" 601 | version = "0.2.99" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 604 | 605 | [[package]] 606 | name = "zerocopy" 607 | version = "0.7.35" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 610 | dependencies = [ 611 | "byteorder", 612 | "zerocopy-derive", 613 | ] 614 | 615 | [[package]] 616 | name = "zerocopy-derive" 617 | version = "0.7.35" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 620 | dependencies = [ 621 | "proc-macro2", 622 | "quote", 623 | "syn", 624 | ] 625 | -------------------------------------------------------------------------------- /src/biguint.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul}; 2 | use std::fmt; 3 | use tfhe::prelude::*; 4 | use tfhe::{FheUint32, FheUint64, ClientKey}; 5 | use num_bigint::BigUint; 6 | use std::time::Instant; 7 | 8 | #[derive(Clone)] 9 | pub struct BigUintFHE { 10 | // Represent the number as a vector of encrypted u32 digits, least significant digit first 11 | digits: Vec, 12 | client_key: ClientKey, 13 | } 14 | 15 | impl BigUintFHE { 16 | /// Creates a new BigUintFHE from a BigUint value 17 | pub fn new(value: BigUint, client_key: &ClientKey) -> Result { 18 | if value == BigUint::from(0u32) { 19 | Ok(Self { digits: vec![], client_key: client_key.clone() }) 20 | } else { 21 | // Convert BigUint to a vector of u32 digits 22 | let digits: Vec = value.to_u32_digits(); 23 | 24 | // Encrypt each digit 25 | let encrypted_digits = digits.into_iter() 26 | .map(|d| FheUint32::try_encrypt(d, client_key)) 27 | .collect::, _>>()?; 28 | 29 | Ok(Self { digits: encrypted_digits, client_key: client_key.clone() }) 30 | } 31 | } 32 | 33 | /// Creates a new BigUintFHE from a u32 value 34 | pub fn from_u32(value: u32, client_key: &ClientKey) -> Result { 35 | Self::new(BigUint::from(value), client_key) 36 | } 37 | 38 | /// Normalize the digits vector by removing trailing zeros 39 | fn normalize(&mut self) { 40 | // Note: We can't easily check for zeros in encrypted form 41 | // This would require decryption which we don't want to do during computation 42 | // So we'll keep all digits for now 43 | } 44 | 45 | /// Creates a BigUint from a vector of encrypted u32 digits 46 | pub fn from_encrypted_digits(digits: Vec, client_key: &ClientKey) -> Self { 47 | Self { digits, client_key: client_key.clone() } 48 | } 49 | 50 | /// Returns zero 51 | pub fn zero(client_key: &ClientKey) -> Result { 52 | Ok(Self { digits: Vec::::new(), client_key: client_key.clone() }) 53 | } 54 | 55 | /// Returns one 56 | pub fn one(client_key: &ClientKey) -> Result { 57 | Self::from_u32(1, client_key) 58 | } 59 | 60 | /// Decrypts the BigUintFHE to a BigUint 61 | pub fn to_biguint(&self, client_key: &ClientKey) -> BigUint { 62 | if self.digits.is_empty() { 63 | return BigUint::from(0u32); 64 | } 65 | 66 | let mut result = BigUint::from(0u32); 67 | let mut base = BigUint::from(1u32); 68 | 69 | for digit in &self.digits { 70 | let decrypted: u32 = digit.decrypt(client_key); 71 | result += base.clone() * decrypted; 72 | base *= BigUint::from(0x100000000u64); 73 | } 74 | 75 | result 76 | } 77 | 78 | /// Decrypts the BigUintFHE to a u32 if possible 79 | pub fn decrypt_to_u32(&self, client_key: &ClientKey) -> Option { 80 | match self.digits.len() { 81 | 0 => Some(0u32), 82 | 1 => { 83 | let decrypted: u32 = self.digits[0].decrypt(client_key); 84 | Some(decrypted) 85 | }, 86 | _ => None, 87 | } 88 | } 89 | 90 | /// Decrypts the BigUintFHE to a u64 if possible 91 | pub fn decrypt_to_u64(&self, client_key: &ClientKey) -> Option { 92 | match self.digits.len() { 93 | 0 => Some(0u64), 94 | 1 => { 95 | let decrypted: u32 = self.digits[0].decrypt(client_key); 96 | Some(decrypted as u64) 97 | }, 98 | 2 => { 99 | let low: u32 = self.digits[0].decrypt(client_key); 100 | let high: u32 = self.digits[1].decrypt(client_key); 101 | Some((low as u64) | ((high as u64) << 32)) 102 | }, 103 | _ => None, 104 | } 105 | } 106 | 107 | /// Extract upper 32 bits from a sum 108 | fn extract_upper_bits(sum: &FheUint64) -> FheUint32 { 109 | // Right shift by 32 bits to get the upper bits 110 | FheUint32::cast_from(sum >> 32u64) 111 | } 112 | 113 | /// Extract lower 32 bits from a sum 114 | fn extract_lower_bits(sum: &FheUint64) -> FheUint32 { 115 | // Use bitwise AND with mask 0xFFFFFFFF to get lower 32 bits 116 | FheUint32::cast_from(sum & 0xFFFFFFFFu64) 117 | } 118 | } 119 | 120 | impl Add for BigUintFHE { 121 | type Output = Self; 122 | 123 | fn add(self, other: Self) -> Self { 124 | let mut result = Vec::new(); 125 | let max_len = std::cmp::max(self.digits.len(), other.digits.len()); 126 | let mut carry: Option = None; 127 | 128 | for i in 0..max_len { 129 | let a = if i < self.digits.len() { Some(&self.digits[i]) } else { None }; 130 | let b = if i < other.digits.len() { Some(&other.digits[i]) } else { None }; 131 | 132 | let sum = match (a, b, carry.as_ref()) { 133 | (Some(a), Some(b), Some(c)) => { 134 | // Convert to u64 for the sum 135 | let a64 = FheUint64::cast_from(a.clone()); 136 | let b64 = FheUint64::cast_from(b.clone()); 137 | let c64 = FheUint64::cast_from(c.clone()); 138 | let temp_sum = a64 + b64 + c64; 139 | 140 | // Extract carry and result 141 | let next_carry = FheUint32::cast_from(&temp_sum >> 32u64); 142 | carry = Some(next_carry); 143 | FheUint32::cast_from(&temp_sum & 0xFFFFFFFFu64) 144 | }, 145 | (Some(a), Some(b), None) => { 146 | let a64 = FheUint64::cast_from(a.clone()); 147 | let b64 = FheUint64::cast_from(b.clone()); 148 | let temp_sum = a64 + b64; 149 | 150 | let next_carry = FheUint32::cast_from(&temp_sum >> 32u64); 151 | carry = Some(next_carry); 152 | FheUint32::cast_from(&temp_sum & 0xFFFFFFFFu64) 153 | }, 154 | (Some(a), None, Some(c)) => { 155 | let a64 = FheUint64::cast_from(a.clone()); 156 | let c64 = FheUint64::cast_from(c.clone()); 157 | let temp_sum = a64 + c64; 158 | 159 | let next_carry = FheUint32::cast_from(&temp_sum >> 32u64); 160 | carry = Some(next_carry); 161 | FheUint32::cast_from(&temp_sum & 0xFFFFFFFFu64) 162 | }, 163 | (Some(a), None, None) => { 164 | a.clone() 165 | }, 166 | (None, Some(b), Some(c)) => { 167 | let b64 = FheUint64::cast_from(b.clone()); 168 | let c64 = FheUint64::cast_from(c.clone()); 169 | let temp_sum = b64 + c64; 170 | 171 | let next_carry = FheUint32::cast_from(&temp_sum >> 32u64); 172 | carry = Some(next_carry); 173 | FheUint32::cast_from(&temp_sum & 0xFFFFFFFFu64) 174 | }, 175 | (None, Some(b), None) => { 176 | b.clone() 177 | }, 178 | (None, None, Some(c)) => { 179 | c.clone() 180 | }, 181 | (None, None, None) => break, 182 | }; 183 | result.push(sum); 184 | } 185 | 186 | if let Some(c) = carry { 187 | result.push(c); 188 | } 189 | 190 | Self { digits: result, client_key: self.client_key.clone() } 191 | } 192 | } 193 | 194 | impl Mul for BigUintFHE { 195 | type Output = Self; 196 | 197 | fn mul(self, other: Self) -> Self { 198 | if self.digits.is_empty() || other.digits.len() == 0 { 199 | return Self { digits: Vec::new(), client_key: self.client_key.clone() }; 200 | } 201 | 202 | let start_total = Instant::now(); 203 | 204 | // Initialize result vector with zeros 205 | let start_init = Instant::now(); 206 | let mut result = vec![ 207 | FheUint32::try_encrypt(0u32, &self.client_key).unwrap(); 208 | self.digits.len() + other.digits.len() 209 | ]; 210 | println!("Init time: {:?}", start_init.elapsed()); 211 | 212 | // Compute each partial product and add to the appropriate position 213 | let start_products = Instant::now(); 214 | for (i, a) in self.digits.iter().enumerate() { 215 | for (j, b) in other.digits.iter().enumerate() { 216 | let start_iter = Instant::now(); 217 | let idx = i + j; 218 | 219 | let start_mul = Instant::now(); 220 | // Convert to FheUint64 for multiplication 221 | let a64 = FheUint64::cast_from(a.clone()); 222 | let b64 = FheUint64::cast_from(b.clone()); 223 | let product = a64 * b64; 224 | println!("Multiplication time: {:?}", start_mul.elapsed()); 225 | 226 | // Split product into lower and upper 32 bits 227 | let start_split = Instant::now(); 228 | let lower = BigUintFHE::extract_lower_bits(&product); 229 | let upper = BigUintFHE::extract_upper_bits(&product); 230 | println!("Split time: {:?}", start_split.elapsed()); 231 | 232 | let start_add = Instant::now(); 233 | // Add lower part and handle potential carry 234 | let current_pos = FheUint64::cast_from(result[idx].clone()); 235 | let lower64 = FheUint64::cast_from(lower); 236 | let sum = current_pos + lower64; 237 | result[idx] = BigUintFHE::extract_lower_bits(&sum); 238 | 239 | // Add upper part plus any carry from lower addition 240 | let next_pos = FheUint64::cast_from(result[idx + 1].clone()); 241 | let upper64 = FheUint64::cast_from(upper); 242 | let carry64 = FheUint64::cast_from(BigUintFHE::extract_upper_bits(&sum)); 243 | let sum = next_pos + upper64 + carry64; 244 | result[idx + 1] = BigUintFHE::extract_lower_bits(&sum); 245 | 246 | // Handle potential new carry 247 | if idx + 2 < result.len() { 248 | result[idx + 2] = result[idx + 2].clone() + BigUintFHE::extract_upper_bits(&sum); 249 | } 250 | println!("Addition time: {:?}", start_add.elapsed()); 251 | 252 | println!("Total iteration time for {:?} / {:?}: {:?}", i + j + 1, result.len() - 1, start_iter.elapsed()); 253 | } 254 | } 255 | println!("Total products time: {:?}", start_products.elapsed()); 256 | 257 | let result = Self { 258 | digits: result, 259 | client_key: self.client_key.clone() 260 | }; 261 | 262 | println!("Total multiplication time: {:?}", start_total.elapsed()); 263 | result 264 | } 265 | } 266 | 267 | #[cfg(test)] 268 | mod tests { 269 | use super::*; 270 | use tfhe::ConfigBuilder; 271 | use tfhe::prelude::FheDecrypt; 272 | 273 | #[test] 274 | fn test_mul_with_carry_small_numbers() { 275 | println!("starting test"); 276 | let config = ConfigBuilder::default().build(); 277 | let (client_key, server_key) = tfhe::generate_keys(config); 278 | tfhe::set_server_key(server_key); 279 | 280 | println!("finished generating keys"); 281 | // Use small numbers for initial testing 282 | let a = BigUintFHE::from_u32(2u32, &client_key).unwrap(); 283 | println!("finished from_u32 a"); 284 | let b = BigUintFHE::from_u32(3u32, &client_key).unwrap(); 285 | println!("finished from_u32 b"); 286 | let result = a * b; 287 | println!("finished mul"); 288 | 289 | // Decrypt the result to verify correctness 290 | let decrypted = result.to_biguint(&client_key); 291 | assert_eq!(decrypted, BigUint::from(6u32)); 292 | } 293 | 294 | #[test] 295 | fn test_biguint_conversion() { 296 | let config = ConfigBuilder::default().build(); 297 | let (client_key, server_key) = tfhe::generate_keys(config); 298 | tfhe::set_server_key(server_key); 299 | 300 | // Test with a large number that requires multiple u32 digits 301 | let large_num = BigUint::parse_bytes(b"123456789123456789", 10).unwrap(); 302 | let encrypted = BigUintFHE::new(large_num.clone(), &client_key).unwrap(); 303 | let decrypted = encrypted.to_biguint(&client_key); 304 | assert_eq!(decrypted, large_num); 305 | } 306 | 307 | #[test] 308 | fn test_add_with_carry() { 309 | let a_biguint = BigUint::from(0xFFFFFFFFu32); 310 | let b_biguint = BigUint::from(1u32); 311 | let result_biguint = a_biguint + b_biguint; 312 | let low_biguint: u32 = result_biguint.to_u32_digits()[0]; 313 | let high_biguint: u32 = result_biguint.to_u32_digits()[1]; 314 | assert_eq!(result_biguint, BigUint::from(0x100000000u64)); 315 | assert_eq!(low_biguint, 0); 316 | assert_eq!(high_biguint, 1); 317 | 318 | let config = ConfigBuilder::default().build(); 319 | let (client_key, server_key) = tfhe::generate_keys(config); 320 | tfhe::set_server_key(server_key); 321 | 322 | let a = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 323 | let b = BigUintFHE::from_u32(1u32, &client_key).unwrap(); 324 | let result = a + b; 325 | 326 | assert_eq!(result.digits.len(), 2); 327 | let low: u32 = result.digits[0].decrypt(&client_key); 328 | let high: u32 = result.digits[1].decrypt(&client_key); 329 | println!("low: {}", low); 330 | println!("high: {}", high); 331 | assert_eq!(low, 0); 332 | assert_eq!(high, 1); 333 | } 334 | 335 | #[test] 336 | fn test_mul_with_carry() { 337 | let config = ConfigBuilder::default().build(); 338 | let (client_key, server_key) = tfhe::generate_keys(config); 339 | tfhe::set_server_key(server_key); 340 | 341 | // Test case 1: 0xFFFFFFFF * 2 = 0x1FFFFFFFE 342 | let a = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 343 | let b = BigUintFHE::from_u32(2u32, &client_key).unwrap(); 344 | let result = a * b; 345 | 346 | assert_eq!(result.digits.len(), 2); 347 | let low: u32 = result.digits[0].decrypt(&client_key); 348 | let high: u32 = result.digits[1].decrypt(&client_key); 349 | assert_eq!(low, 0xFFFFFFFEu32); 350 | assert_eq!(high, 1); 351 | } 352 | 353 | #[test] 354 | fn test_mul_with_carry_2() { 355 | let config = ConfigBuilder::default().build(); 356 | let (client_key, server_key) = tfhe::generate_keys(config); 357 | tfhe::set_server_key(server_key); 358 | 359 | // Test case 2: 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001 360 | let a = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 361 | let b = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 362 | let result = a * b; 363 | 364 | assert_eq!(result.digits.len(), 2); 365 | let low: u32 = result.digits[0].decrypt(&client_key); 366 | let high: u32 = result.digits[1].decrypt(&client_key); 367 | assert_eq!(low, 1); 368 | assert_eq!(high, 0xFFFFFFFEu32); 369 | } 370 | 371 | #[test] 372 | fn test_add_multiple_carries() { 373 | let config = ConfigBuilder::default().build(); 374 | let (client_key, server_key) = tfhe::generate_keys(config); 375 | tfhe::set_server_key(server_key); 376 | 377 | let a = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 378 | let b = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 379 | let result = a + b; 380 | 381 | assert_eq!(result.digits.len(), 2); 382 | let low: u32 = result.digits[0].decrypt(&client_key); 383 | let high: u32 = result.digits[1].decrypt(&client_key); 384 | assert_eq!(low, 0xFFFFFFFEu32); 385 | assert_eq!(high, 1); 386 | } 387 | 388 | #[test] 389 | fn test_mul_multiple_carries() { 390 | let config = ConfigBuilder::default().build(); 391 | let (client_key, server_key) = tfhe::generate_keys(config); 392 | tfhe::set_server_key(server_key); 393 | 394 | // Test case: 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001 395 | let a = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 396 | let b = BigUintFHE::from_u32(0xFFFFFFFFu32, &client_key).unwrap(); 397 | let result = a * b; 398 | 399 | assert_eq!(result.digits.len(), 2); 400 | let low: u32 = result.digits[0].decrypt(&client_key); 401 | let high: u32 = result.digits[1].decrypt(&client_key); 402 | assert_eq!(low, 1); 403 | assert_eq!(high, 0xFFFFFFFEu32); 404 | } 405 | 406 | #[test] 407 | fn test_large_number_operations() { 408 | let config = ConfigBuilder::default().build(); 409 | let (client_key, server_key) = tfhe::generate_keys(config); 410 | tfhe::set_server_key(server_key); 411 | 412 | // Test with large numbers 413 | let a = BigUint::parse_bytes(b"123456789123456789", 10).unwrap(); 414 | let b = BigUint::parse_bytes(b"987654321987654321", 10).unwrap(); 415 | 416 | let a_enc = BigUintFHE::new(a.clone(), &client_key).unwrap(); 417 | let b_enc = BigUintFHE::new(b.clone(), &client_key).unwrap(); 418 | 419 | // Test addition 420 | let sum = a_enc.clone() + b_enc.clone(); 421 | assert_eq!(sum.to_biguint(&client_key), a.clone() + b.clone()); 422 | 423 | // Test multiplication 424 | let product = a_enc * b_enc; 425 | assert_eq!(product.to_biguint(&client_key), a * b); 426 | } 427 | 428 | #[test] 429 | fn test_extract_carry_and_lower_bits() { 430 | let config = ConfigBuilder::default().build(); 431 | let (client_key, server_key) = tfhe::generate_keys(config); 432 | tfhe::set_server_key(server_key); 433 | 434 | // Test case 1: Simple addition without carry 435 | let num1 = FheUint64::try_encrypt(5u64, &client_key).unwrap(); 436 | let num2 = FheUint64::try_encrypt(3u64, &client_key).unwrap(); 437 | let sum = num1 + num2; // 8 438 | 439 | let carry = BigUintFHE::extract_upper_bits(&sum); 440 | let lower = BigUintFHE::extract_lower_bits(&sum); 441 | 442 | assert_eq!(FheDecrypt::::decrypt(&carry, &client_key), 0u32); 443 | assert_eq!(FheDecrypt::::decrypt(&lower, &client_key), 8u32); 444 | 445 | // Test case 2: Addition with carry 446 | let max = FheUint64::try_encrypt(0xFFFFFFFFu64, &client_key).unwrap(); 447 | let one = FheUint64::try_encrypt(1u64, &client_key).unwrap(); 448 | let sum_with_carry = max + one; // 0x100000000 449 | 450 | let carry = BigUintFHE::extract_upper_bits(&sum_with_carry); 451 | let lower = BigUintFHE::extract_lower_bits(&sum_with_carry); 452 | 453 | assert_eq!(FheDecrypt::::decrypt(&carry, &client_key), 1u32); 454 | assert_eq!(FheDecrypt::::decrypt(&lower, &client_key), 0u32); 455 | 456 | // Test case 3: Large numbers 457 | let large1 = FheUint64::try_encrypt(0xFFFFFFFFu64, &client_key).unwrap(); 458 | let large2 = FheUint64::try_encrypt(0xFFFFFFFFu64, &client_key).unwrap(); 459 | let large_sum = large1 + large2; // 0x1FFFFFFFE 460 | 461 | let carry = BigUintFHE::extract_upper_bits(&large_sum); 462 | let lower = BigUintFHE::extract_lower_bits(&large_sum); 463 | 464 | assert_eq!(FheDecrypt::::decrypt(&carry, &client_key), 1u32); 465 | assert_eq!(FheDecrypt::::decrypt(&lower, &client_key), 0xFFFFFFFEu32); 466 | } 467 | 468 | #[test] 469 | fn test_uint32_overflow_behavior() { 470 | let config = ConfigBuilder::default().build(); 471 | let (client_key, server_key) = tfhe::generate_keys(config); 472 | tfhe::set_server_key(server_key); 473 | 474 | // Test maximum u32 value plus one 475 | let max = FheUint32::try_encrypt(0xFFFFFFFFu32, &client_key).unwrap(); 476 | let one = FheUint32::try_encrypt(1u32, &client_key).unwrap(); 477 | let sum = &max + &one; 478 | 479 | // Extract carry and lower bits 480 | let carry = &sum >> 32u32; 481 | let lower = &sum & 0xFFFFFFFFu32; 482 | // TFHE does not support overflow checking, here the carry is 0, which should be 1 if computed in plaintext 483 | println!("carry: {}", FheDecrypt::::decrypt(&carry, &client_key)); 484 | println!("lower: {}", FheDecrypt::::decrypt(&lower, &client_key)); 485 | 486 | // Test maximum u32 value plus itself 487 | let sum2 = &max + &max; 488 | let carry2 = &sum2 >> 32u32; 489 | let lower2 = &sum2 & 0xFFFFFFFFu32; 490 | 491 | // TFHE does not support overflow checking, here the carry is 0, which should be 1 if computed in plaintext 492 | println!("carry2: {}", FheDecrypt::::decrypt(&carry2, &client_key)); 493 | println!("lower2: {}", FheDecrypt::::decrypt(&lower2, &client_key)); 494 | // Expected output(which is wrong because of overflow): 495 | // carry: 0 496 | // lower: 0 497 | // carry2: 4294967294 498 | // lower2: 4294967294 499 | } 500 | 501 | #[test] 502 | fn test_uint64_carry_behavior() { 503 | let config = ConfigBuilder::default().build(); 504 | let (client_key, server_key) = tfhe::generate_keys(config); 505 | tfhe::set_server_key(server_key); 506 | 507 | // Test maximum u32 value plus one 508 | let max = FheUint64::try_encrypt(0xFFFFFFFFu64, &client_key).unwrap(); 509 | let one = FheUint64::try_encrypt(1u64, &client_key).unwrap(); 510 | let sum = &max + &one; 511 | 512 | // Extract carry and lower bits 513 | let carry = &sum >> 32u64; 514 | let lower = &sum & 0xFFFFFFFFu64; 515 | // TFHE does not support overflow checking, here the carry is 0, which should be 1 if computed in plaintext 516 | assert_eq!(FheDecrypt::::decrypt(&carry, &client_key), 1u64); 517 | assert_eq!(FheDecrypt::::decrypt(&lower, &client_key), 0u64); 518 | 519 | // Test maximum u32 value plus itself 520 | let sum2 = &max + &max; 521 | let carry2 = &sum2 >> 32u64; 522 | let lower2 = &sum2 & 0xFFFFFFFFu64; 523 | 524 | // TFHE does not support overflow checking, here the carry is 0, which should be 1 if computed in plaintext 525 | assert_eq!(FheDecrypt::::decrypt(&carry2, &client_key), 1u64); 526 | assert_eq!(FheDecrypt::::decrypt(&lower2, &client_key), 0xFFFFFFFEu64); 527 | } 528 | 529 | } 530 | 531 | -------------------------------------------------------------------------------- /src/schnorr.rs: -------------------------------------------------------------------------------- 1 | use sha2::{Sha256, Digest}; 2 | use crate::scalar::{get_curve_order, Scalar, get_field_size}; 3 | use crate::field::FieldElement; 4 | use crate::secp256k1::Point; 5 | use num_bigint::BigUint; 6 | use tfhe::prelude::*; 7 | use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, ClientKey}; 8 | use crate::biguint::BigUintFHE; 9 | use std::time::Instant; 10 | 11 | // https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki 12 | 13 | // Input: 14 | // - The secret key sk: a 32-byte array 15 | // - The message m: a byte array 16 | // - Auxiliary random data a: a 32-byte array 17 | 18 | // The algorithm Sign(sk, m) is defined as: 19 | // - Let d' = int(sk) 20 | // - Fail if d' = 0 or d' ≥ n 21 | // - Let P = d'⋅G 22 | // - Let d = d' if has_even_y(P), otherwise let d = n - d' . 23 | // - Let t be the byte-wise xor of bytes(d) and hashBIP0340/aux(a)[11]. 24 | // - Let rand = hashBIP0340/nonce(t || bytes(P) || m)[12]. 25 | // - Let k' = int(rand) mod n[13]. 26 | // - Fail if k' = 0. 27 | // - Let R = k'⋅G. 28 | // - Let k = k' if has_even_y(R), otherwise let k = n - k' . 29 | // - Let e = int(hashBIP0340/challenge(bytes(R) || bytes(P) || m)) mod n. 30 | // - Let sig = bytes(R) || bytes((k + ed) mod n). 31 | // - If Verify(bytes(P), m, sig) (see below) returns failure, abort[14]. 32 | // - Return the signature sig. 33 | 34 | /// BIP-340 tag constants for domain separation 35 | const AUX_TAG: &[u8] = b"BIP0340/aux"; 36 | const NONCE_TAG: &[u8] = b"BIP0340/nonce"; 37 | const CHALLENGE_TAG: &[u8] = b"BIP0340/challenge"; 38 | 39 | /// Represents a Schnorr signature according to BIP-340 40 | #[derive(Debug, Clone, PartialEq)] 41 | pub struct Signature { 42 | pub r_x: FieldElement, // x-coordinate of R 43 | pub s: Scalar, // scalar s 44 | } 45 | 46 | impl Signature { 47 | /// Serializes the signature to bytes according to BIP-340: R.x || s 48 | pub fn to_bytes(&self) -> Vec { 49 | let mut sig = Vec::with_capacity(64); 50 | sig.extend_from_slice(&bytes_from_int(&self.r_x.value())); 51 | sig.extend_from_slice(&bytes_from_int(self.s.value())); 52 | sig 53 | } 54 | } 55 | 56 | /// The Schnorr signature scheme implementation following BIP-340 57 | pub struct Schnorr { 58 | } 59 | 60 | impl Schnorr { 61 | /// Creates a new Schnorr instance with the given private key. 62 | pub fn new() -> Self { 63 | Self {} 64 | } 65 | 66 | /// Signs a message using the Schnorr signature scheme according to BIP-340. 67 | /// 68 | /// Arguments: 69 | /// * `message` - The message to be signed as a byte slice 70 | /// * `aux_rand` - The auxiliary random data as a byte slice 71 | /// * `privkey` - The caller/owner's private key as a Scalar 72 | /// 73 | /// Returns: 74 | /// * `Result>` - The Schnorr signature if successful, or an error if the operation fails 75 | pub fn sign(&self, message: &[u8], aux_rand: &[u8], privkey: &Scalar) -> Result> { 76 | println!("Starting `sign` operation"); 77 | let start_total = Instant::now(); 78 | let pubkey = get_public_key_with_even_y(privkey); 79 | let curve_order = get_curve_order(); 80 | // Generate deterministic nonce k0 according to BIP-340 81 | let k0 = compute_nonce(privkey.value(), &pubkey, message, aux_rand); 82 | let generator = Point::get_generator(); 83 | let r = generator.scalar_mul(&Scalar::new(k0.clone())); 84 | 85 | // Adjust k based on R's y-coordinate parity 86 | let k = if r.y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 87 | &curve_order - k0 88 | } else { 89 | k0 90 | }; 91 | 92 | // Compute challenge e = hash(R || P || message) 93 | let e = compute_challenge(&r, &pubkey, message); 94 | 95 | // Compute s = (k + e * privkey) % n 96 | let s = (k + e * privkey.value()) % get_curve_order(); 97 | println!("`sign` operation time: {:?}", start_total.elapsed()); 98 | 99 | Ok(Signature { 100 | r_x: r.x, 101 | s: Scalar::new(s), 102 | }) 103 | } 104 | 105 | /// Signs a message using the Schnorr signature scheme according to BIP-340. 106 | /// 107 | /// Arguments: 108 | /// * `message` - The message to be signed as a byte slice 109 | /// * `k0` - The pre-computed nonce value as a BigUint 110 | /// * `privkey` - The caller/owner's private key as a Scalar 111 | /// 112 | /// Returns: 113 | /// * `Result>` - The Schnorr signature if successful, or an error if the operation fails 114 | pub fn sign_with_k0(&self, message: &[u8], k0: &BigUint, privkey: &Scalar) -> Result> { 115 | println!("Starting `sign` operation"); 116 | let start_total = Instant::now(); 117 | let pubkey = get_public_key_with_even_y(privkey); 118 | let curve_order = get_curve_order(); 119 | // Generate deterministic nonce k0 according to BIP-340 120 | let generator = Point::get_generator(); 121 | let r = generator.scalar_mul(&Scalar::new(k0.clone())); 122 | 123 | // Adjust k based on R's y-coordinate parity 124 | let k = if r.y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 125 | &curve_order - k0 126 | } else { 127 | k0.clone() 128 | }; 129 | 130 | // Compute challenge e = hash(R || P || message) 131 | let e = compute_challenge(&r, &pubkey, message); 132 | 133 | // Compute s = (k + e * privkey) % n 134 | let s = (k + e * privkey.value()) % get_curve_order(); 135 | println!("`sign` operation time: {:?}", start_total.elapsed()); 136 | 137 | Ok(Signature { 138 | r_x: r.x, 139 | s: Scalar::new(s), 140 | }) 141 | } 142 | 143 | 144 | /// Signs a message using the Schnorr signature scheme according to BIP-340 with FHE. 145 | /// 146 | /// # Arguments 147 | /// * `message` - The message to be signed as a byte slice 148 | /// * `aux_rand` - The auxiliary random data as a byte slice 149 | /// * `privkey` - The caller/owner's private key as a Scalar 150 | /// * `client_key` - The FHE client key used for encryption/decryption 151 | /// 152 | /// # Returns 153 | /// * `Result` - The Schnorr signature if successful, or a TFHE error if encryption operations fail 154 | pub fn sign_fhe(&self, message: &[u8], aux_rand: &[u8], privkey: &Scalar, client_key: &ClientKey) -> Result { 155 | let start_total = Instant::now(); 156 | println!("Starting `sign_fhe` operation"); 157 | 158 | // Step 1: Get Public Key 159 | let start_public_key = Instant::now(); 160 | let pubkey = get_public_key_with_even_y(privkey); 161 | println!("`get_public_key` time: {:?}", start_public_key.elapsed()); 162 | 163 | let curve_order = get_curve_order(); 164 | 165 | // Step 2: Compute Nonce 166 | let start_nonce = Instant::now(); 167 | let k0 = compute_nonce(privkey.value(), &pubkey, message, aux_rand); 168 | println!("`compute_nonce` time: {:?}", start_nonce.elapsed()); 169 | 170 | // Step 3: Compute R = kG 171 | let generator = Point::get_generator(); 172 | let start_r = Instant::now(); 173 | let r = generator.scalar_mul(&Scalar::new(k0.clone())); 174 | println!("`scalar_mul` (computing R) time: {:?}", start_r.elapsed()); 175 | 176 | // Step 4: Adjust k based on R's y-coordinate parity 177 | let start_adjust_k = Instant::now(); 178 | let k = if r.y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 179 | &curve_order - k0 180 | } else { 181 | k0 182 | }; 183 | println!("`adjust_k` time: {:?}", start_adjust_k.elapsed()); 184 | 185 | // Step 5: Compute Challenge e = H(R || P || m) 186 | let start_challenge = Instant::now(); 187 | let e = compute_challenge(&r, &pubkey, message); 188 | println!("`compute_challenge` time: {:?}", start_challenge.elapsed()); 189 | 190 | // Step 6: Compute s = (k + e * privkey) mod n using FHE operations 191 | let start_fhe_operations = Instant::now(); 192 | let privkey_fhe = BigUintFHE::new(privkey.value().clone(), client_key)?; 193 | let e_fhe = BigUintFHE::new(e.clone(), client_key)?; 194 | let k_fhe = BigUintFHE::new(k.clone(), client_key)?; 195 | let s_fhe_without_mod = k_fhe + (e_fhe * privkey_fhe); 196 | let s_without_mod = s_fhe_without_mod.to_biguint(client_key); 197 | let s = s_without_mod % curve_order; 198 | println!("FHE operations (`k + e * privkey mod n`) time: {:?}", start_fhe_operations.elapsed()); 199 | 200 | // Step 7: Construct the Signature 201 | let start_construct_signature = Instant::now(); 202 | let signature = Signature { 203 | r_x: r.x, 204 | s: Scalar::new(s), 205 | }; 206 | println!("`construct_signature` time: {:?}", start_construct_signature.elapsed()); 207 | 208 | println!("Total `sign_fhe` operation time: {:?}", start_total.elapsed()); 209 | 210 | Ok(signature) 211 | } 212 | 213 | /// Signs a message using the Schnorr signature scheme according to BIP-340 with FHE. 214 | /// problem: we do not want to involve private key in nonce computation, because it uses hash operation which is very expensive 215 | /// solution exploration: 216 | /// 1. since k0 is used as a random number, which is computed with private key and other message. this can generate a unique random number for each message. 217 | /// 2. if we can use other number rather than private key to generate k0, we can avoid the expensive hash operation. so we only need to calculate signature with private key(encrypted form) involved in the formula s = (k + e * privkey) mod n 218 | /// 3. considering our use case, alice store the main private key in encrypted form, then set access control rules, allow alice's working device to call the sign function, the working device also has a key pair which is visible to alice due to alice owns the working device. so we can use the working device's private key to generate k0. this avoid the expensive hash operation. 219 | /// 4. remind that we want to avoid the expensive hash operation, we need to use the working device's private key to in clear/unencrypted form, but we can not pass the private key as parameter to the sign function which happens onchain and publically visible to all. so we need the device to generate k0 with its private key offchain privately, and then pass k0 to the sign function. 220 | /// 5. addtional work for security reason 221 | /// 5.1 if the working device is compromised, the private key can be leaked. so we need to ensure the sign function is still secure, which means cracker can not get alice's private key from s = (k + e * privkey) mod n, which means k0 should be unique for each signature. 222 | /// 5.2 so we need to store the k0 values on chain, each sign operation need to check if the k0 is already used before. 223 | 224 | /// Signs a message using the Schnorr signature scheme according to BIP-340 with FHE and a pre-computed nonce k0. 225 | /// 226 | /// # Arguments 227 | /// * `message` - The message to be signed as a byte slice 228 | /// * `k0` - The pre-computed nonce value as a BigUint 229 | /// * `privkey` - The caller's private key as a Scalar 230 | /// * `privkey_fhe` - The owner's private key encrypted as a BigUintFHE 231 | /// * `client_key` - The FHE client key used for encryption/decryption 232 | /// 233 | /// # Returns 234 | /// * `Result` - The Schnorr signature if successful, or a TFHE error if encryption operations fail 235 | pub fn sign_fhe_with_k0(&self, message: &[u8], k0: &BigUint, privkey: &Scalar, privkey_fhe: &BigUintFHE, client_key: &ClientKey) -> Result { 236 | let start_total = Instant::now(); 237 | println!("Starting `sign_fhe` operation"); 238 | 239 | // Step 1: Get Public Key 240 | let start_public_key = Instant::now(); 241 | let pubkey = get_public_key_with_even_y(privkey); 242 | println!("`get_public_key` time: {:?}", start_public_key.elapsed()); 243 | 244 | let curve_order = get_curve_order(); 245 | 246 | // Step 2: Compute Nonce 247 | let start_nonce = Instant::now(); 248 | println!("`compute_nonce` time: {:?}", start_nonce.elapsed()); 249 | 250 | // Step 3: Compute R = kG 251 | let generator = Point::get_generator(); 252 | let start_r = Instant::now(); 253 | let r = generator.scalar_mul(&Scalar::new(k0.clone())); 254 | println!("`scalar_mul` (computing R) time: {:?}", start_r.elapsed()); 255 | 256 | // Step 4: Adjust k based on R's y-coordinate parity 257 | let start_adjust_k = Instant::now(); 258 | let k = if r.y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 259 | &curve_order - k0.clone() 260 | } else { 261 | k0.clone() 262 | }; 263 | println!("`adjust_k` time: {:?}", start_adjust_k.elapsed()); 264 | 265 | // Step 5: Compute Challenge e = H(R || P || m) 266 | let start_challenge = Instant::now(); 267 | let e = compute_challenge(&r, &pubkey, message); 268 | println!("`compute_challenge` time: {:?}", start_challenge.elapsed()); 269 | 270 | // Step 6: Compute s = (k + e * privkey) mod n using FHE operations 271 | let start_fhe_operations = Instant::now(); 272 | let e_fhe = BigUintFHE::new(e.clone(), client_key)?; 273 | let k_fhe = BigUintFHE::new(k.clone(), client_key)?; 274 | let s_fhe_without_mod = k_fhe + (e_fhe * privkey_fhe.clone()); 275 | let s_without_mod = s_fhe_without_mod.to_biguint(client_key); 276 | let s = s_without_mod % curve_order; 277 | println!("FHE operations (`k + e * privkey mod n`) time: {:?}", start_fhe_operations.elapsed()); 278 | 279 | // Step 7: Construct the Signature 280 | let start_construct_signature = Instant::now(); 281 | let signature = Signature { 282 | r_x: r.x, 283 | s: Scalar::new(s), 284 | }; 285 | println!("`construct_signature` time: {:?}", start_construct_signature.elapsed()); 286 | 287 | println!("Total `sign_fhe` operation time: {:?}", start_total.elapsed()); 288 | 289 | Ok(signature) 290 | } 291 | 292 | /// Verifies a Schnorr signature according to BIP-340. 293 | /// 294 | /// Arguments: 295 | /// * `message` - The message to be signed as a byte slice 296 | /// * `pubkey_bytes` - The public key as a byte slice 297 | /// * `sig_bytes` - The signature as a byte slice 298 | /// 299 | /// Returns: 300 | /// * `bool` - True if the signature is valid, false otherwise 301 | pub fn verify(message: &[u8], pubkey_bytes: &[u8], sig_bytes: &[u8]) -> bool { 302 | // Check input lengths 303 | if pubkey_bytes.len() != 32 || sig_bytes.len() != 64 { 304 | return false; 305 | } 306 | 307 | // Parse signature and public key 308 | let sig = Signature { 309 | r_x: FieldElement::new(BigUint::from_bytes_be(&sig_bytes[0..32]), get_field_size()), 310 | s: Scalar::new(BigUint::from_bytes_be(&sig_bytes[32..64])), 311 | }; 312 | 313 | // Lift x coordinates to curve points 314 | let pubkey = FieldElement::new(BigUint::from_bytes_be(pubkey_bytes), get_field_size()); 315 | let pubkey_point = lift_x(&pubkey); 316 | if pubkey_point.is_infinity { 317 | return false; 318 | } 319 | 320 | // Reconstruct R point from x-coordinate 321 | let r_point = { 322 | let x3 = &sig.r_x * &sig.r_x * &sig.r_x; 323 | let r_y_squared = x3 + FieldElement::new(BigUint::from(7u32), get_field_size()); 324 | let mut r_y = r_y_squared.sqrt(); 325 | if r_y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 326 | r_y = FieldElement::new(get_field_size() - r_y.value(), get_field_size()); 327 | } 328 | Point::new(sig.r_x.clone(), r_y, false) 329 | }; 330 | 331 | // Verify signature bounds 332 | if r_point.x.value() >= &get_curve_order() || sig.s.value() >= &get_curve_order() { 333 | return false; 334 | } 335 | 336 | // Compute sG and eP 337 | let generator = Point::get_generator(); 338 | let s_g = generator.scalar_mul(&sig.s); 339 | let e = Scalar::new(compute_challenge(&r_point, &pubkey_point, message)); 340 | let e_p = pubkey_point.scalar_mul(&e); 341 | 342 | // Verify R = sG - eP and has even y-coordinate 343 | let r_computed = s_g - e_p; 344 | !(r_computed.is_infinity 345 | || r_computed.y.value() % BigUint::from(2u32) == BigUint::from(1u32) 346 | || r_computed.x.value() != sig.r_x.value()) 347 | } 348 | } 349 | 350 | 351 | /// Gets the public key with BIP-340 y-coordinate conventions which is even 352 | fn get_public_key_with_even_y(privkey: &Scalar) -> Point { 353 | let generator = Point::get_generator(); 354 | let pubkey = generator.scalar_mul(&privkey); 355 | 356 | // Ensure the public key has an even y-coordinate as per BIP-340 357 | if pubkey.y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 358 | Point::new( 359 | pubkey.x.clone(), 360 | FieldElement::new(get_field_size() - pubkey.y.value(), get_field_size()), 361 | false 362 | ) 363 | } else { 364 | pubkey 365 | } 366 | } 367 | 368 | /// Computes the tagged hash according to BIP-340 specification. 369 | /// tagged_hash = SHA256(SHA256(tag) || SHA256(tag) || msg) 370 | fn tagged_hash(tag: &[u8], msg: &[u8]) -> Vec { 371 | let tag_hash = Sha256::digest(tag); 372 | let mut hasher = Sha256::new(); 373 | hasher.update(&tag_hash); 374 | hasher.update(&tag_hash); 375 | hasher.update(msg); 376 | hasher.finalize().to_vec() 377 | } 378 | 379 | /// Converts a BigUint to a 32-byte array in big-endian format 380 | fn bytes_from_int(n: &BigUint) -> [u8; 32] { 381 | let bytes = n.to_bytes_be(); 382 | let mut result = [0u8; 32]; 383 | let start = 32 - bytes.len(); 384 | result[start..].copy_from_slice(&bytes); 385 | result 386 | } 387 | 388 | /// Converts a Point to a 32-byte array by taking its x-coordinate 389 | fn bytes_from_point(p: &Point) -> [u8; 32] { 390 | bytes_from_int(&p.x.value()) 391 | } 392 | 393 | /// Computes the nonce according to BIP-340 specification 394 | fn compute_nonce(privkey: &BigUint, pubkey: &Point, message: &[u8], aux_rand: &[u8]) -> BigUint { 395 | let t = xor_bytes(&bytes_from_int(privkey), &tagged_hash(AUX_TAG, aux_rand)); 396 | let mut nonce_input = Vec::new(); 397 | nonce_input.extend_from_slice(&t); 398 | nonce_input.extend_from_slice(&bytes_from_point(pubkey)); 399 | nonce_input.extend_from_slice(message); 400 | BigUint::from_bytes_be(&tagged_hash(NONCE_TAG, &nonce_input)) % get_curve_order() 401 | } 402 | 403 | /// Computes the challenge according to BIP-340 specification 404 | fn compute_challenge(r: &Point, pubkey: &Point, message: &[u8]) -> BigUint { 405 | let mut challenge_input = Vec::new(); 406 | challenge_input.extend_from_slice(&bytes_from_point(r)); 407 | challenge_input.extend_from_slice(&bytes_from_point(pubkey)); 408 | challenge_input.extend_from_slice(message); 409 | BigUint::from_bytes_be(&tagged_hash(CHALLENGE_TAG, &challenge_input)) % get_curve_order() 410 | } 411 | 412 | /// Performs XOR operation on two 32-byte slices 413 | fn xor_bytes(a: &[u8], b: &[u8]) -> [u8; 32] { 414 | let mut result = [0u8; 32]; 415 | for i in 0..32 { 416 | result[i] = a[i] ^ b[i]; 417 | } 418 | result 419 | } 420 | 421 | /// Lifts an x-coordinate to a point on the curve with even y-coordinate 422 | fn lift_x(x: &FieldElement) -> Point { 423 | if x.value() >= &get_curve_order() { 424 | return Point::infinity(); 425 | } 426 | let y_squared = x.pow(&BigUint::from(3u32)) + FieldElement::new(BigUint::from(7u32), x.order().clone()); 427 | let mut y = y_squared.sqrt(); 428 | if y.value() % BigUint::from(2u32) == BigUint::from(1u32) { 429 | y = FieldElement::new(x.order().clone() - y.value(), x.order().clone()); 430 | } 431 | Point::new(x.clone(), y, false) 432 | } 433 | 434 | #[cfg(test)] 435 | mod tests { 436 | use super::*; 437 | use hex; 438 | 439 | #[test] 440 | fn test_schnorr_fhe() { 441 | let config = ConfigBuilder::default().build(); 442 | let (client_key, server_keys) = generate_keys(config); 443 | set_server_key(server_keys); 444 | 445 | // Test vector from BIP-340 446 | let seckey_bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000003").unwrap(); 447 | let message = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 448 | let aux_rand = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 449 | let expected_sig = hex::decode("E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0").unwrap(); 450 | 451 | let seckey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 452 | let schnorr = Schnorr::new(); 453 | let sig = schnorr.sign(&message, &aux_rand, &seckey); 454 | let pubkey = get_public_key_with_even_y(&seckey); 455 | 456 | assert!(sig.is_ok()); 457 | let sig = sig.unwrap(); 458 | assert_eq!(sig.to_bytes(), expected_sig); 459 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &expected_sig)); 460 | 461 | let sig_fhe = schnorr.sign_fhe(&message, &aux_rand, &seckey, &client_key); 462 | assert!(sig_fhe.is_ok()); 463 | let sig_fhe = sig_fhe.unwrap(); 464 | assert_eq!(sig_fhe.to_bytes(), expected_sig); 465 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &expected_sig)); 466 | } 467 | 468 | #[test] 469 | fn test_schnorr_fhe_with_k0() { 470 | let config = ConfigBuilder::default().build(); 471 | let (client_key, server_keys) = generate_keys(config); 472 | set_server_key(server_keys); 473 | 474 | // Test vector from BIP-340 475 | let seckey_bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000003").unwrap(); 476 | let message = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 477 | let aux_rand = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 478 | 479 | let privkey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 480 | let privkey_fhe = BigUintFHE::new(privkey.value().clone(), &client_key).unwrap(); 481 | let pubkey = get_public_key_with_even_y(&privkey); 482 | 483 | let schnorr = Schnorr::new(); 484 | let k0 = compute_nonce(&privkey.value(), &pubkey, &message, &aux_rand); 485 | // sign with k0 486 | let sig_with_k0 = schnorr.sign_with_k0(&message, &k0, &privkey).unwrap(); 487 | // sign fhe with k0 488 | let sig_fhe_with_k0 = schnorr.sign_fhe_with_k0(&message, &k0, &privkey, &privkey_fhe, &client_key).unwrap(); 489 | 490 | assert_eq!(sig_with_k0.to_bytes(), sig_fhe_with_k0.to_bytes()); 491 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &sig_with_k0.to_bytes())); 492 | } 493 | 494 | #[test] 495 | fn test_schnorr_bip340() { 496 | // Test vector from BIP-340 497 | let seckey_bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000003").unwrap(); 498 | let message = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 499 | let aux_rand = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 500 | let expected_sig = hex::decode("E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0").unwrap(); 501 | 502 | let seckey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 503 | let schnorr = Schnorr::new(); 504 | let sig = schnorr.sign(&message, &aux_rand, &seckey); 505 | assert!(sig.is_ok()); 506 | let sig = sig.unwrap(); 507 | let pubkey = get_public_key_with_even_y(&seckey); 508 | 509 | assert_eq!(sig.to_bytes(), expected_sig); 510 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &expected_sig)); 511 | } 512 | 513 | #[test] 514 | fn test_schnorr_with_k0() { 515 | let seckey_bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000003").unwrap(); 516 | let message = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 517 | let aux_rand = hex::decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); 518 | let privkey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 519 | let schnorr = Schnorr::new(); 520 | let pubkey = get_public_key_with_even_y(&privkey); 521 | let k0 = compute_nonce(&privkey.value(), &pubkey, &message, &aux_rand); 522 | let sig_with_k0 = schnorr.sign_with_k0(&message, &k0, &privkey).unwrap(); 523 | 524 | let sig = schnorr.sign(&message, &aux_rand, &privkey).unwrap(); 525 | 526 | assert_eq!(sig.to_bytes(), sig_with_k0.to_bytes()); 527 | assert!(Schnorr::verify(&message, &pubkey.x.value().to_bytes_be(), &sig_with_k0.to_bytes())); 528 | } 529 | 530 | #[test] 531 | fn test_schnorr_vectors() { 532 | let csv_content = include_str!("../tests/test_vectors.csv"); 533 | let mut all_passed = true; 534 | 535 | for (i, line) in csv_content.lines().skip(1).enumerate() { 536 | let fields: Vec<&str> = line.split(',').collect(); 537 | if fields.len() < 7 { continue; } 538 | 539 | let (index, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str) = 540 | (fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6]); 541 | let expected_result = result_str == "TRUE"; 542 | 543 | let pubkey_bytes = hex::decode(pubkey_hex).unwrap(); 544 | let message = hex::decode(msg_hex).unwrap(); 545 | let expected_sig = hex::decode(sig_hex).unwrap(); 546 | 547 | if !seckey_hex.is_empty() { 548 | let seckey_bytes = hex::decode(seckey_hex).unwrap(); 549 | let aux_rand = hex::decode(aux_rand_hex).unwrap(); 550 | let seckey = Scalar::new(BigUint::from_bytes_be(&seckey_bytes)); 551 | let schnorr = Schnorr::new(); 552 | 553 | let sig = schnorr.sign(&message, &aux_rand, &seckey); 554 | assert!(sig.is_ok()); 555 | let sig = sig.unwrap(); 556 | if sig.to_bytes() != expected_sig { 557 | println!("Failed signing test for vector #{}", index); 558 | all_passed = false; 559 | continue; 560 | } 561 | } 562 | 563 | let result = Schnorr::verify(&message, &pubkey_bytes, &expected_sig); 564 | if result != expected_result { 565 | println!("Failed verification test for vector #{}", index); 566 | all_passed = false; 567 | continue; 568 | } 569 | } 570 | 571 | assert!(all_passed, "Some test vectors failed"); 572 | } 573 | 574 | #[test] 575 | fn test_fheu32_mul_u32() { 576 | let config = ConfigBuilder::default().build(); 577 | let (client_key, server_keys) = generate_keys(config); 578 | set_server_key(server_keys); 579 | 580 | // Test FHE multiplication with plain value 581 | let value = 123u32; 582 | let multiplier = 456u32; 583 | let expected = value * multiplier; 584 | // Encrypt the value 585 | let encrypted = FheUint32::try_encrypt(value, &client_key).unwrap(); 586 | 587 | // Multiply encrypted value with plain value 588 | let result_fhe = encrypted * multiplier; 589 | // Decrypt and verify 590 | let decrypted: u32 = result_fhe.decrypt(&client_key); 591 | assert_eq!(decrypted, expected); 592 | } 593 | 594 | #[test] 595 | fn test_fheu32_add_u32() { 596 | let config = ConfigBuilder::default().build(); 597 | let (client_key, server_keys) = generate_keys(config); 598 | set_server_key(server_keys); 599 | 600 | let value = 123u32; 601 | let addend = 456u32; 602 | let expected = value + addend; 603 | let encrypted = FheUint32::try_encrypt(value, &client_key).unwrap(); 604 | let result_fhe = encrypted + addend; 605 | let decrypted: u32 = result_fhe.decrypt(&client_key); 606 | assert_eq!(decrypted, expected); 607 | } 608 | } 609 | 610 | 611 | --------------------------------------------------------------------------------