├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── protocols.rs ├── docs ├── images │ ├── dark │ │ └── dependencies.svg │ └── light │ │ └── dependencies.svg ├── intro.md ├── key-generation.md ├── orchestration.md ├── proofs.md ├── signing.md └── triples.md ├── examples └── network-benches.rs ├── logo.png └── src ├── compat.rs ├── constants.rs ├── crypto.rs ├── keyshare.rs ├── lib.rs ├── math.rs ├── participants.rs ├── presign.rs ├── proofs ├── dlog.rs ├── dlogeq.rs └── mod.rs ├── protocol ├── internal.rs └── mod.rs ├── serde.rs ├── sign.rs ├── test.rs └── triples ├── batch_random_ot.rs ├── bits.rs ├── correlated_ot_extension.rs ├── generation.rs ├── mod.rs ├── mta.rs ├── multiplication.rs └── random_ot_extension.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.8.0 2 | 3 | - Added an extra requirement to Curve API for constant-time scalar sampling 4 | 5 | # 0.7.0 6 | 7 | - Remove triple setup interface, always doing a fresh setup, for security reasons. 8 | - Fix various security bugs. 9 | - Update dependencies. 10 | 11 | # 0.6.0 12 | 13 | - Modify specification to use a single threshold (turns out the code accidentally enforced this already) 14 | - Modify code to match simplified presigning protocol because of this threshold. 15 | - Modify specification to pre-commit to C polynomial in triple generation. 16 | - Modify code accordingly. 17 | 18 | # 0.5.0 19 | 20 | - Modify specification & implementation to use perfectly hiding commitments. 21 | - Update dependencies to recent Rust-Crypto ECDSA versions. 22 | - Support arbitrary curves and message hashes. 23 | - Add curve description (name) to transcript in keysharing and triple generation. 24 | 25 | # 0.4.0 26 | 27 | - Added key refresh and resharing protocols. 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cait-sith" 3 | description = "Threshold ECDSA via Triples" 4 | repository = "https://github.com/cronokirby/cait-sith" 5 | version = "0.8.0" 6 | edition = "2021" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | auto_ops = "0.3.0" 11 | ck-meow = "0.1.0" 12 | digest = "0.10.7" 13 | ecdsa = { version = "0.16.8", features = ["digest", "hazmat"] } 14 | elliptic-curve = { version = "0.13.5", features = ["serde"] } 15 | event-listener = "2.5.3" 16 | k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = true } 17 | magikitten = "0.2.0" 18 | rand_core = { version = "0.6.4", features = ["getrandom"] } 19 | rmp-serde = "1.1.2" 20 | serde = { version = "1.0.175", features = ["derive"] } 21 | smol = "1.3.0" 22 | subtle = "2.5.0" 23 | 24 | [dev-dependencies] 25 | criterion = "0.4" 26 | easy-parallel = "3.2.0" 27 | haisou-chan = { git = "https://github.com/cronokirby/haisou-chan", rev = "d28c46e51acfcb818236caae293f6e56dff41ad2" } 28 | structopt = "0.3.26" 29 | k256 = { version = "0.13.0", features = ["sha256", "ecdsa", "serde"], optional = false } 30 | 31 | [[bench]] 32 | name = "protocols" 33 | harness = false 34 | required-features = ["k256"] 35 | 36 | [features] 37 | k256 = ["dep:k256"] 38 | 39 | [[example]] 40 | name = "network-benches" 41 | path = "examples/network-benches.rs" 42 | required-features = ["k256"] 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Lúcás C. Meier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cait-Sith [![](https://img.shields.io/crates/v/cait-sith.svg)](https://crates.io/crates/cait-sith) [![](https://docs.rs/cait-sith/badge.svg)](https://docs.rs/cait-sith) 2 | 3 | Cait-Sith is a novel threshold ECDSA protocol (and implementation), 4 | which is both simpler and substantially more performant than 5 | popular alternatives. 6 | 7 | The protocol supports arbitrary numbers of parties and thresholds. 8 | 9 | 13 | 14 | # Warning 15 | 16 | This is experimental cryptographic software, unless you're a cat with 17 | a megaphone on top of a giant Moogle I would exercise caution. 18 | 19 | - The protocol does not have a formal proof of security. 20 | - This library has not undergone any form of audit. 21 | 22 | # Design 23 | 24 | The main design principle of Cait-Sith is offloading as much work 25 | to a key-independent preprocessing phase as possible. 26 | The advantage of this approach is that this preprocessing phase can be conducted 27 | in advance, before a signature is needed, and the results of this phase 28 | can even be peformed before the key that you need to sign with is decided. 29 | 30 | One potential scenario where this is useful is when running a threshold 31 | custody service over many keys, where these preprocessing results 32 | can be performed, and then used on demand regardless of which keys 33 | end up being used more often. 34 | 35 | A detailed specification is available [in this repo](./docs), 36 | but we'll also give a bit of detail here. 37 | 38 | The core of Cait-Sith's design involves a *committed* Beaver triple. 39 | These are of the form: 40 | 41 | $$ 42 | ([a], [b], [c]), (A = a \cdot G, B = b \cdot G, C = c \cdot G) 43 | $$ 44 | 45 | where $a, b, c$ are scalars such that $a \cdot b = c$, and are 46 | secret shared among several participants, so that no one knows their actual value. 47 | Furthermore, unlike standard Beaver triples, we also have a public commitment 48 | to the these secret values, which helps the online protocol. 49 | 50 | The flow of the protocol is first that the parties need a way to generate triples: 51 | 52 | - A setup protocol is run once, allowing parties to efficiently generate triples. 53 | - The parties can now generate an arbitrary number triples through a distributed protocol. 54 | 55 | Then, the parties need to generate a key pair so that they can sign messages: 56 | 57 | - The parties run a distributed key generation protocol to setup a new key pair, 58 | which can be used for many signatures. 59 | 60 | When the parties want to sign using a given key: 61 | 62 | - Using their shares of a private key, the parties can create a *presignature*, 63 | before knowing the message to sign. 64 | - Once they know this message, they can use the presignature to create a complete signature. 65 | 66 | It's important that presignatures and triples are **never** reused. 67 | 68 | ### Refresh and Resharing 69 | 70 | In addition to key generation, cait-sith also supports key *refresh*, 71 | and key *resharing*. 72 | 73 | Key refresh generates new shares for each party, while keeping the same list 74 | of participants and threshold. 75 | 76 | Key resharing does the same, but also allows changing the threshold, 77 | and the list of participants (as long as enough old participants are present 78 | to meet the old threshold). 79 | 80 | ## API Design 81 | 82 | Internally, the API tries to be as simple as possible abstracting away 83 | as many details as possible into a simple interface. 84 | 85 | This interface just has two methods: 86 | ```rust 87 | pub trait Protocol { 88 | type Output; 89 | 90 | fn poke(&mut self) -> Result, ProtocolError>; 91 | fn message(&mut self, from: Participant, data: MessageData); 92 | } 93 | ``` 94 | Given an instance of this trait, which represents a single party 95 | participating in a protocol, you can do two things: 96 | - You can provide a new message received from some other party. 97 | - You can "poke" the protocol to see if it has some kind of action it wants you to perform, or if an error happened. 98 | 99 | This action is either: 100 | - The protocol telling you it has finished, with a return value of type `Output`. 101 | - The protocol asking you to send a message to all other parties. 102 | - The protocol asking you to *privately* send a message to one party. 103 | - The protocol informing you that no more progress can be made until it receives new messages. 104 | 105 | In particular, details about rounds and message serialization are abstracted 106 | away, and all performed internally. 107 | In fact, the protocols aren't designed around "rounds", and can even have parallel 108 | threads of execution internally for some of the more complicated ones. 109 | 110 | # Benchmarks 111 | 112 | Here are some benchmarks, for the `Secp256k1` curve, performed on an Intel Core i5-4690K CPU. 113 | 114 | ``` 115 | > cargo bench -F k256 116 | 117 | setup 3 118 | time: [94.689 ms 95.057 ms 95.449 ms] 119 | 120 | triple generation (3, 3) 121 | time: [36.610 ms 36.682 ms 36.757 ms] 122 | 123 | keygen (3,3) 124 | time: [3.0901 ms 3.1095 ms 3.1297 ms] 125 | 126 | presign (3,3) 127 | time: [2.5531 ms 2.5640 ms 2.5761 ms] 128 | 129 | sign (3,3) 130 | time: [446.79 µs 447.89 µs 449.02 µs] 131 | ``` 132 | 133 | These were performed with 3 parties running on the same machine, 134 | with no communication cost. 135 | 136 | Note that triple generation needs to be performed *twice* for each signature. 137 | Also, triple generation is relatively bandwidth intensive compared to other 138 | protocols, which isn't reflected in these benchmarks, since network speed 139 | isn't constrained. 140 | Nonetheless, this cost isn't all that important, because it can be performed 141 | in advance, and independent of the key. 142 | 143 | Thus, the cost of presigning + signing should be considered instead. 144 | This cost is low enough to be bottlenecked by network performance, most likely. 145 | 146 | ## Networked Benchmarks 147 | 148 | The library also has an example which runs a benchmark simulating 149 | network latency and bandwidth constraints. 150 | Note that in these examples, multiple threads are used, so better 151 | reflect the fact that computation is parallelized across each node. 152 | However, the CPU I ran these benchmarks on only had 4 cores, 153 | so take the large party benchmarks with a grain of salt. 154 | 155 | Here's an example with 3 parties, with 100ms latency between them, 156 | and a 10 MB/s outgoing link each. 157 | 158 | ``` 159 | > cargo run --release -F k256 --example network-benches -- 3 100 10000000 160 | 161 | Triple Setup 3 [100 ms, 10000000 B/S] 162 | time: 304.884093ms 163 | up: 10322 B 164 | down: 10322 B 165 | 166 | Triple Gen 3 [100 ms, 10000000 B/S] 167 | time: 740.041888ms 168 | up: 106202 B 169 | down: 106202 B 170 | 171 | Keygen (3, 3) [100 ms, 10000000 B/S] 172 | time: 207.137969ms 173 | up: 1068 B 174 | down: 1068 B 175 | 176 | Presign (3, 3) [100 ms, 10000000 B/S] 177 | time: 104.090877ms 178 | up: 961 B 179 | down: 961 B 180 | 181 | Sign (3, 3) [100 ms, 10000000 B/S] 182 | time: 100.606562ms 183 | up: 151 B 184 | down: 151 B 185 | ``` 186 | 187 | Here's an extreme case, with 100 parties, 300ms of latency between them, 188 | and an outgoing 1 MB/s link each: 189 | 190 | ``` 191 | > cargo run --release --example network-benches -- 100 300 1000000 192 | 193 | Triple Setup 100 [300 ms, 1000000 B/S] 194 | time: 51.269278194s 195 | up: 510843 B 196 | down: 510843 B 197 | 198 | Triple Gen 100 [300 ms, 1000000 B/S] 199 | time: 32.959644915s 200 | up: 6765025 B 201 | down: 6765025 B 202 | 203 | Keygen (100, 100) [300 ms, 1000000 B/S] 204 | time: 5.871460998s 205 | up: 551527 B 206 | down: 551527 B 207 | 208 | Presign (100, 100) [300 ms, 1000000 B/S] 209 | time: 2.891458487s 210 | up: 546835 B 211 | down: 546835 B 212 | 213 | Sign (100, 100) [300 ms, 1000000 B/S] 214 | time: 359.795393ms 215 | up: 7859 B 216 | down: 7859 B 217 | ``` 218 | 219 | # Generic Curves 220 | 221 | The library has support for generic curves and hashes. 222 | 223 | The support for generic curves is done through a custom `CSCurve` trait, 224 | which can be easily implemented for any curve from the 225 | RustCrypto [elliptic-curves](https://github.com/RustCrypto/elliptic-curves) 226 | suite of libraries. 227 | 228 | This crate also provides implementations of some existing curves behind features, 229 | as per the following table: 230 | 231 | | Curve | Feature | 232 | |-------|---------| 233 | |Secp256k1|`k256`| 234 | 235 | For supporting any message hash, the API requires the user to supply 236 | the hash of a message when signing as a scalar directly. 237 | 238 | # Shortcomings 239 | 240 | The protocol and its implementation do have a few known disadvantages at the moment: 241 | 242 | - The protocol does require generating triples in advance, but these can be generated without knowledge of the private key. 243 | - The protocol does not attempt to provide identifiable aborts. 244 | 245 | We also don't really intend to add identifiable aborts to Cait-Sith itself. 246 | While these can be desirable in certain situations, we aren't satisfied 247 | with the way the property of identifiable aborts is modeled currently, 248 | and are working on improvements to this model. 249 | -------------------------------------------------------------------------------- /benches/protocols.rs: -------------------------------------------------------------------------------- 1 | use std::vec::Vec; 2 | 3 | use cait_sith::{ 4 | keygen, presign, 5 | protocol::{run_protocol, Participant, Protocol}, 6 | sign, 7 | triples::{ 8 | self, generate_triple, setup, Setup, TripleGenerationOutput, TriplePub, TripleShare, 9 | }, 10 | FullSignature, KeygenOutput, PresignArguments, PresignOutput, 11 | }; 12 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 13 | use k256::{AffinePoint, Secp256k1, Scalar}; 14 | use rand_core::OsRng; 15 | 16 | fn run_setup(participants: Vec) -> Vec<(Participant, Setup)> { 17 | let mut protocols: Vec<(Participant, Box>)> = 18 | Vec::with_capacity(participants.len()); 19 | 20 | for p in participants.iter() { 21 | let protocol = setup::(&participants, *p); 22 | assert!(protocol.is_ok()); 23 | let protocol = protocol.unwrap(); 24 | protocols.push((*p, Box::new(protocol))); 25 | } 26 | 27 | run_protocol(protocols).unwrap() 28 | } 29 | 30 | fn run_triple_generation( 31 | participants: Vec<(Participant, Setup)>, 32 | threshold: usize, 33 | ) -> Vec<(Participant, TripleGenerationOutput)> { 34 | let mut protocols: Vec<( 35 | Participant, 36 | Box>>, 37 | )> = Vec::with_capacity(participants.len()); 38 | 39 | let just_participants: Vec<_> = participants.iter().map(|(p, _)| *p).collect(); 40 | 41 | for (p, setup) in participants.into_iter() { 42 | let protocol = generate_triple(&just_participants, p, setup, threshold); 43 | assert!(protocol.is_ok()); 44 | let protocol = protocol.unwrap(); 45 | protocols.push((p, Box::new(protocol))); 46 | } 47 | 48 | run_protocol(protocols).unwrap() 49 | } 50 | 51 | fn run_keygen( 52 | participants: Vec, 53 | threshold: usize, 54 | ) -> Vec<(Participant, KeygenOutput)> { 55 | let mut protocols: Vec<(Participant, Box>>)> = 56 | Vec::with_capacity(participants.len()); 57 | 58 | for p in participants.iter() { 59 | let protocol = keygen(&participants, *p, threshold); 60 | assert!(protocol.is_ok()); 61 | let protocol = protocol.unwrap(); 62 | protocols.push((*p, Box::new(protocol))); 63 | } 64 | 65 | run_protocol(protocols).unwrap() 66 | } 67 | 68 | fn run_presign( 69 | participants: Vec<(Participant, KeygenOutput)>, 70 | shares0: Vec>, 71 | shares1: Vec>, 72 | pub0: &TriplePub, 73 | pub1: &TriplePub, 74 | threshold: usize, 75 | ) -> Vec<(Participant, PresignOutput)> { 76 | assert!(participants.len() == shares0.len()); 77 | assert!(participants.len() == shares1.len()); 78 | 79 | let mut protocols: Vec<(Participant, Box>>)> = 80 | Vec::with_capacity(participants.len()); 81 | 82 | let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); 83 | 84 | for (((p, keygen_out), share0), share1) in participants 85 | .into_iter() 86 | .zip(shares0.into_iter()) 87 | .zip(shares1.into_iter()) 88 | { 89 | let protocol = presign( 90 | &participant_list, 91 | p, 92 | PresignArguments { 93 | original_threshold: threshold, 94 | triple0: (share0, pub0.clone()), 95 | triple1: (share1, pub1.clone()), 96 | keygen_out, 97 | threshold, 98 | }, 99 | ); 100 | assert!(protocol.is_ok()); 101 | let protocol = protocol.unwrap(); 102 | protocols.push((p, Box::new(protocol))); 103 | } 104 | 105 | run_protocol(protocols).unwrap() 106 | } 107 | 108 | fn run_sign( 109 | participants: Vec<(Participant, PresignOutput)>, 110 | public_key: AffinePoint, 111 | msg: Scalar, 112 | ) -> Vec<(Participant, FullSignature)> { 113 | let mut protocols: Vec<(Participant, Box>>)> = 114 | Vec::with_capacity(participants.len()); 115 | 116 | let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); 117 | 118 | for (p, presign_out) in participants.into_iter() { 119 | let protocol = sign(&participant_list, p, public_key, presign_out, msg); 120 | assert!(protocol.is_ok()); 121 | let protocol = protocol.unwrap(); 122 | protocols.push((p, Box::new(protocol))); 123 | } 124 | 125 | run_protocol(protocols).unwrap() 126 | } 127 | 128 | pub fn criterion_benchmark(c: &mut Criterion) { 129 | let participants = vec![ 130 | Participant::from(0u32), 131 | Participant::from(1u32), 132 | Participant::from(2u32), 133 | ]; 134 | let t = 3; 135 | 136 | c.bench_function("setup 3", |b| b.iter(|| run_setup(participants.clone()))); 137 | 138 | let mut setup_result = run_setup(participants.clone()); 139 | setup_result.sort_by_key(|(p, _)| *p); 140 | 141 | c.bench_function("triple generation (3, 3)", |b| { 142 | b.iter(|| run_triple_generation(black_box(setup_result.clone()), t)) 143 | }); 144 | 145 | c.bench_function("keygen (3,3)", |b| { 146 | b.iter(|| run_keygen(black_box(participants.clone()), black_box(t))) 147 | }); 148 | 149 | let mut keygen_result = run_keygen(participants.clone(), t); 150 | keygen_result.sort_by_key(|(p, _)| *p); 151 | 152 | let public_key = keygen_result[0].1.public_key; 153 | 154 | let (pub0, shares0) = triples::deal(&mut OsRng, &participants, t); 155 | let (pub1, shares1) = triples::deal(&mut OsRng, &participants, t); 156 | 157 | c.bench_function("presign (3,3)", |b| { 158 | b.iter(|| { 159 | run_presign( 160 | black_box(keygen_result.clone()), 161 | black_box(shares0.clone()), 162 | black_box(shares1.clone()), 163 | black_box(&pub0), 164 | black_box(&pub1), 165 | black_box(t), 166 | ) 167 | }) 168 | }); 169 | 170 | let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, t); 171 | presign_result.sort_by_key(|(p, _)| *p); 172 | 173 | // DO NOT COPY THIS CODE FOR ACTUAL SIGNING 174 | let msg = Scalar::ONE; 175 | 176 | c.bench_function("sign (3,3)", |b| { 177 | b.iter(|| { 178 | run_sign( 179 | black_box(presign_result.clone()), 180 | black_box(public_key), 181 | black_box(msg), 182 | ) 183 | }) 184 | }); 185 | } 186 | 187 | criterion_group!(benches, criterion_benchmark); 188 | criterion_main!(benches); 189 | -------------------------------------------------------------------------------- /docs/images/dark/dependencies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 26 | 47 | 52 | 57 | 58 | 60 | 73 | 78 | 79 | 92 | 97 | 98 | 111 | 116 | 117 | 118 | 123 | Key-Gen 135 | Triples 146 | Triples Setup 158 | Presign 169 | Sign 180 | m 191 | 198 | 205 | 212 | 219 | 226 | 233 | 237 | 241 | 245 | 249 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /docs/images/light/dependencies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 26 | 47 | 52 | 57 | 58 | 60 | 73 | 78 | 79 | 92 | 97 | 98 | 111 | 116 | 117 | 118 | 123 | Key-Gen 135 | Triples 146 | Triples Setup 158 | Presign 169 | Sign 180 | m 191 | 198 | 205 | 212 | 219 | 226 | 233 | 237 | 241 | 245 | 249 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | These docs provide a lower level description of the protocols used in Cait-Sith. 2 | 3 | # Overview 4 | 5 | ## [Orchestration](./orchestration.md) 6 | 7 | Describes how the different protocols fit together. 8 | 9 | ## [Proofs](./proofs.md) 10 | 11 | Describes the conventions around transcripts and ZK Proofs. 12 | 13 | ## [Key Generation](./key-generation.md) 14 | 15 | This describes the distributed key generation protocol. 16 | 17 | ## [Triples](./triples.md) 18 | 19 | This describes the triple generation protocol: a pre-processing phase 20 | used to speed up signing. 21 | 22 | ## [Signing](./signing.md) 23 | 24 | This describes the signing protocol, which consists of a presignature phase 25 | and a final signature phase. 26 | 27 | # Security Analysis 28 | 29 | A security analysis of the protocol is available [here](https://cronokirby.com/notes/2023/04/cait-sith-security/). 30 | 31 | # Some Notation conventions 32 | 33 | Vectors / Matrices are denoted $x_i$ or $A_{ij}$, using indices. Operators behave pointwise. For example, $x_i \cdot y_i$ creates a new vector by multiplying the entries of $x$ and $y$ pointwise. $\langle A_{ij}, \ldots \rangle$ denotes summation, over the shared indices. For example $\langle A_{ij}, x_j \rangle_i$ would be $A x$ in more conventional matrix vector multiplication notation. 34 | 35 | $\lambda(\mathcal{P})_i$ denotes the Lagrange coefficient for participant $i$ 36 | in a group of participants $\mathcal{P}$, used for interpolating threshold 37 | shared values into linear shared values. 38 | -------------------------------------------------------------------------------- /docs/key-generation.md: -------------------------------------------------------------------------------- 1 | In this document, we describe a generalized version of key generation, 2 | allowing for key refresh and resharing, and then apply that to 3 | create two specific protocols: 4 | 1. A protocol for generating a fresh key. 5 | 2. A protocol for changing the threshold and set of participants but with the same key. 6 | 7 | 8 | Given a set of players $\mathcal{P} = \{P_1, \ldots, P_N\}$, 9 | and a desired threshold $t$, define 10 | the following protocol: 11 | 12 | ## KeyShare 13 | 14 | We assume that each participant in $\mathcal{P} := \\\{P_1, \ldots, P_n\\\}$ 15 | has a secret share $s_i$ (which can possibly be $0$), which sum to $s := \sum_i s_i$ and that they share a public value $S$, 16 | which is either $\bot$ (meaning no value), 17 | or should equal $s \cdot G$. 18 | 19 | The goal of this protocol is for each particapant to obtain a fresh 20 | threshold $t$ sharing of $s$, such that any $t$ participants 21 | can reconstruct the value, along with the value $s \cdot G$, 22 | if $S = \bot$. 23 | 24 | **Round 1:** 25 | 26 | 1. $\blacktriangle$ Each $P_i$ *asserts* that $|\mathcal{P}| \geq t$. 27 | 2. $T.\text{Add}(\mathbb{G}, \mathcal{P}, t)$ 28 | 3. Each $P_i$ samples $f \xleftarrow{\\\$} \mathbb{F}_ q[X]_ {\leq t - 1}$, 29 | subject to the constraint that $f(0) = s_i$. 30 | 4. Each $P_i$ sets $F_ i \gets f \cdot G$. 31 | 5. Each $P_i$ sets $(\text{Com}_i, r_i) \gets \text{Commit}(F_i)$. 32 | 6. $\star$ Each $P_i$ sends $\text{Com}_i$ to every other party. 33 | 34 | **Round 2:** 35 | 36 | 1. $\bullet$ Each $P_i$ waits to receive $\text{Com}_j$ from each other $P_j$. 37 | 2. Each $P_i$ sets $\text{Confirm}_i \gets H(\text{Com}_1, \ldots, \text{Com}_N)$. 38 | 3. $T.\text{Add}(\text{Confirm}_i)$ 39 | 4. $\star$ Each $P_i$ sends $\text{Confirm}_i$ to every other party. 40 | 5. Each $P_i$ generates the proof $\pi_i \gets \text{Prove}(T.\text{Cloned}(\texttt{dlog0}, i), \text{Mau}(- \cdot G, F_{i}(0); f(0)))$. 41 | 6. $\star$ Each $P_i$ sends $(F_i, r_i, \pi_i)$ to every other party. 42 | 7. $\textcolor{red}{\star}$ Each $P_i$ *privately* sends $x_i^j := f(j)$ to each other party $P_j$, and saves $x_i^i$ for itself. 43 | 44 | **Round 3:** 45 | 46 | 1. $\bullet$ Each $P_i$ waits to receive $\text{Confirm}_j$ from each other $P_j$. 47 | 2. $\blacktriangle$ Each $P_i$ *asserts* that $\forall j \in [N].\ \text{Confirm}_j = \text{Confirm}_i$, aborting otherwise. 48 | 3. $\bullet$ Each $P_i$ waits to receive $(F_j, r_j, \pi_j)$ from each other $P_j$. 49 | 4. $\blacktriangle$ Each $P_i$ *asserts* that $\forall j \in [N].\ \text{deg}(F_ j) = t -1 \land \text{CheckCommit}(\text{Com}_j, F_j, r_j) \land \text{Verify}(T.\text{Cloned}(\texttt{dlog0}, j), \pi_j, \text{Mau}({- \cdot G}, F_j(0)))$. 50 | 5. $\bullet$ Each $P_i$ waits to receive $x_j^i$ from each other $P_j$. 51 | 6. Each $P_i$ sets $x_i \gets \sum_j x^i_j$ and $X \gets \sum_j F_j(0)$. 52 | 7. $\blacktriangle$ Each $P_i$ asserts that $x_i \cdot G = (\sum_j F_j)(i)$. 53 | 8. (If $S \neq \bot$) $\blacktriangle$ Each $P_i$ asserts that $X = S$. 54 | 9. Each $P_i$ outputs $x_i$ and $X$. 55 | 56 | **Output** 57 | 58 | The value $x_i$ is $P_i$'s private share of the secret key $s$. 59 | 60 | $X$ is the public key shared by the group, which should be equal 61 | to the previous value $S$, if it was provided. 62 | 63 | ## Key Generation 64 | 65 | The key sharing protocol can be used for a standard key generation 66 | protocol, by having each party sample $s_i$ randomly, 67 | and setting $S = \bot$ (no expected public key). 68 | 69 | ## Key Refresh 70 | 71 | A key refresh protocol can be performed by first linearizing the 72 | shares $x_1, \ldots, x_n$, 73 | setting $s_i \gets \lambda(\mathcal{P})_i \cdot x_i$, 74 | and then using $S = X$, to check that the public key doesn't 75 | change. 76 | 77 | ## Key Resharing 78 | 79 | A key resharing protocol can be performed as well. 80 | This involves transitioning from $(\mathcal{P}, t)$ 81 | to $(\mathcal{P}', t')$, and can be performed as long 82 | as $|\mathcal{P} \cap \mathcal{P}'| \geq t$, 83 | i.e. there are enough old parties with a share. 84 | The end result is that the new set of parties 85 | hold threshold $t'$ shares of the same private key. 86 | 87 | This works by having each party in $\mathcal{P} \cap \mathcal{P}'$ linearize their share, 88 | setting $s_i \gets \lambda(\mathcal{P})_i \cdot x_i$. 89 | Each party in $\mathcal{P}' / \mathcal{P}$ (the new members), 90 | simply set $s_i \gets 0$. 91 | We also set $S = X$, to check that the same public key 92 | is generated. 93 | 94 | Key refresh can be seen as a natural case of 95 | key resharing, with $\mathcal{P} = \mathcal{P}'$, 96 | and $t = t'$. 97 | -------------------------------------------------------------------------------- /docs/orchestration.md: -------------------------------------------------------------------------------- 1 | Cait-Sith is composed of multiple protocols. 2 | For example, key generation is a separate protocol from signing. 3 | Some protocols are broken into sub-protocols. 4 | For example, signing is broken into three phases, the first can be 5 | done without any secret information, the second with just the shares of the 6 | secret key, and the final one after learning the message to sign. 7 | 8 | This document describes the high-level orchestration of the protocols. 9 | 10 | # Key Generation 11 | 12 | During key generation, a set of parties $\mathcal{P}$ of size $N$ come together 13 | to produce shares of a secret key. 14 | These shares are such that any set $\mathcal{S} \subseteq \mathcal{P}$ 15 | of size $\geq t$ can reconstruct the secret. 16 | We call $t$ our threshold. 17 | 18 | # Signing 19 | 20 | Signing is split into three phases: 21 | 22 | 1. Triple generation. 23 | 2. Presigning. 24 | 3. Signing. 25 | 26 | Each of these phases can potentially use a different set of parties, 27 | and these sets can have different sizes. 28 | Furthermore, each phase can have a different threshold describing 29 | how large the party set for the next phase needs to be. 30 | 31 | Concretely, we have the following situation: 32 | 33 | $$ 34 | \begin{matrix} 35 | &\scriptsize{\text{Key-Gen}} 36 | &&\scriptsize{\text{Triples}} 37 | &&\scriptsize{\text{Presigning}} 38 | &&\scriptsize{\text{Signing}} 39 | \cr 40 | &\mathcal{P} &\supseteq &\mathcal{P}_0 &\supseteq &\mathcal{P}_1 &\supseteq &\mathcal{P}_2\cr 41 | &N &\geq &N_0 &\geq &N_1 & \geq &N_2\cr 42 | &t &&t_0 &&t_1 &&t_2\cr 43 | \end{matrix} 44 | $$ 45 | 46 | Each phase has a different set of parties, with each subsequent phase 47 | having a subset of the parties present in the previous one. 48 | The size of each party set, $N_i$, can also vary. 49 | The thresholds can also change, subject to the following conditions: 50 | 51 | $$ 52 | \begin{aligned} 53 | &N_0 \geq t\cr 54 | &N_1 \geq t_0 \geq t\cr 55 | &N_2 \geq t_1 \geq t 56 | \end{aligned} 57 | $$ 58 | 59 | In other words, the first threshold $t$ is at most $N$, and every 60 | other threshold must be at least that large. 61 | Otherwise, the only constraint is that each subsequent party set must 62 | be at least as large as the previous threshold. 63 | 64 | ## Discarding information 65 | 66 | Each phase can be run many times in advance, recording the information 67 | public information produced, as well as the list of parties which produced it. 68 | Then, this output is consumed by having a set of parties use it 69 | for a subsequent phase. 70 | It's **critical** that the output is then destroyed, so that no other 71 | group of parties attempts to re-use that output for another phase. 72 | In particular, the parties need some way of agreeing on which 73 | outputs have been created and used. 74 | If the threshold $t_i$ is such that $N_{i} \leq 2t - 1$, then it's impossible 75 | to have two non-overlapping quorums, so if each party locally registers the 76 | fact that an output has been used, then agreement can be had not to 77 | use a certain output. 78 | Otherwise, you might have two independent groups of parties trying 79 | to use the same output, which is bad. 80 | 81 | # Graph 82 | 83 | Here's a figure describing the dependencies between the different phases: 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | The red boxes mean that the output of that phase can only be used once. 92 | -------------------------------------------------------------------------------- /docs/proofs.md: -------------------------------------------------------------------------------- 1 | Various protocols use ZK proofs using the Fiat-Shamir 2 | These proofs need to incorporate as much contextual information as necessary to avoid potential replay attacks. 3 | To accomplish this, we use a "transcript" abstraction, which allows absorbing information before then using it in a proof. 4 | 5 | # Transcript API 6 | 7 | - $T.\text{Add}(x_1, x_2, \ldots)$ absorbs new data $x_1, x_2, \ldots$, handling separation and padding. 8 | - $T.\text{Cloned}(\text{tag}, x_1, \ldots)$ produces a forked version of the transcript, by using a given tag, and additional data. This transcript will not modify the original transcript $T$, but will contain the information absorbed in it. 9 | 10 | You can also think of a transcript as essentially containing a complete 11 | list of all the operations performed on it. 12 | So adding $x$ and then $y$ is equivalent to having a transcript consisting 13 | of $[x, y]$. 14 | All this information will then be used when the transcript is passed 15 | to create or verify a proof. 16 | 17 | This transcript API is closely related to the implementation 18 | used in this library: [Magikitten](https://github.com/cronokirby/magikitten). 19 | Looking at the API of that library will likely make this 20 | API more understandable. 21 | 22 | # ZK Proofs 23 | 24 | The proofs we use in this library are all Maurer proofs. 25 | These are proofs of the form: 26 | "I know a secret $x$ such that $\varphi(x) = X$, with $X$ a public value", 27 | and $\varphi$ being a homomorphism, i.e. $\varphi(a + b) = \varphi(a) + \varphi(b)$. 28 | 29 | A common case of this is the Schnorr discrete logarithm proof, 30 | with $\varphi(x) = x \cdot G$. 31 | We would write this as $- \cdot G$ in the notation of our specifications. 32 | 33 | In general, we write $\text{Mau}(\varphi, X; x)$ 34 | to denote the relation "I know a private $x$ such that $\varphi(x) = X$. 35 | We also write $\text{Mau}(\varphi, X)$ to denote the verifier's 36 | view of this relation, where $x$ is not known. 37 | 38 | Using this notation, we write: 39 | - $$ 40 | \text{Prove}(T, \text{Mau}(\varphi, X; x)) 41 | $$ 42 | - $$ 43 | \text{Verify}(T, \pi, \text{Mau}(\varphi, X)) 44 | $$ 45 | for creating and verifying a proof, using a transcript for binding 46 | proofs to a given context. 47 | 48 | See [this blog post](https://cronokirby.com/posts/2022/08/the-paper-that-keeps-showing-up/) for more context on Maurer proofs. 49 | -------------------------------------------------------------------------------- /docs/signing.md: -------------------------------------------------------------------------------- 1 | This document specifies the signing protocol. 2 | The protocol is split into two main phases, a pre-signing phase 3 | 4 | # 1 Preliminaries 5 | 6 | Let $\mathbb{G}$ be a cryptographic group, with generator $G$, of prime order $q$. 7 | 8 | Let $\text{Hash} : \\{0, 1\\}^* \to \mathbb{F}_q$ denote a hash function used for hashing messages 9 | for signatures. 10 | Let $h : \mathbb{G} \to \mathbb{F}_q$ denote a different "hash function" used for converting points to scalars. 11 | Commonly, this is done by "simply" taking the x coordinate of the affine 12 | representation of a point. 13 | Let $H : \\{0, 1\\}^* \to \\{0, 1\\}^{2\lambda}$ be a generic hash function. 14 | 15 | # 2 ECDSA Recap 16 | 17 | ECDSA is defined by algorithms for key generation, signing, and verification: 18 | 19 | First, key generation: 20 | 21 | $$ 22 | \begin{aligned} 23 | &\underline{\texttt{Gen}():}\cr 24 | &\ x \xleftarrow{\$} \mathbb{F}_q\cr 25 | &\ X \gets x \cdot G\cr 26 | &\ \texttt{return } (x, X)\cr 27 | \end{aligned} 28 | $$ 29 | 30 | Next, signing: 31 | 32 | $$ 33 | \begin{aligned} 34 | &\underline{\texttt{Sign}(x : \mathbb{F}_q, m : \{0, 1\}^*):}\cr 35 | &\ k \xleftarrow{\$} \mathbb{F}_q\cr 36 | &\ R \gets \frac{1}{k} \cdot G\cr 37 | &\ r \gets h(R)\cr 38 | &\ \texttt{retry if } r = 0\cr 39 | &\ s \gets k (\texttt{Hash}(m) + rx)\cr 40 | &\ \texttt{return } (R, s) 41 | \end{aligned} 42 | $$ 43 | 44 | Note that we deviate slightly from ECDSA specifications by returning 45 | the entire point $R$ instead of just $r$. 46 | This makes it easier for downstream implementations to massage 47 | the result signature into whatever format they need for compatability. 48 | 49 | Finally, verification: 50 | 51 | $$ 52 | \begin{aligned} 53 | &\underline{\texttt{Verify}(X : \mathbb{G}, m : \{0, 1\}^*, (R, s) : \mathbb{G} \times \mathbb{F}_q):}\cr 54 | &\ r \gets h(R)\cr 55 | &\ \texttt{assert } r \neq 0, s \neq 0\cr 56 | &\ \hat{R} \gets \frac{\texttt{Hash}(m)}{s} \cdot G + \frac{r}{s} \cdot X\cr 57 | &\ \texttt{asssert } \hat{R} = R\cr 58 | \end{aligned} 59 | $$ 60 | 61 | # 3 Presigning 62 | 63 | In the setup phase, the parties generated a $t$ threshold sharing 64 | of the private key $x$, with the share of $\mathcal{P}_i$ being $x_i$. 65 | The parties also hold the public key $X = x \cdot G$. 66 | 67 | In two prior phases $\sigma \in \\{0, 1\\}$, a set of parties $\mathcal{P}_0^\sigma$ of size $N_0^\sigma$ 68 | came together to generate a $t$ threshold sharing of triples $a^\sigma$, $b^\sigma$, $c^\sigma = a^\sigma b^\sigma$ 69 | along with values $A^\sigma = a^\sigma \cdot G$, $B^\sigma = b^\sigma \cdot G$ and $C^\sigma = c^\sigma \cdot G$. 70 | 71 | In the current phase, a set of parties $\mathcal{P}_ 1 \subseteq \mathcal{P}_ 0^0 \cap \mathcal{P}^1_ 0$ 72 | of size $N_1 \geq t$ wish to generate a threshold $t' = t$ sharing 73 | of a pre-signature. 74 | 75 | **Round 1:** 76 | 77 | 1. Each $P_i$ checks that $\mathcal{P}_1 \subseteq \mathcal{P}_0^0 \cap \mathcal{P}_0^1$, and that $t' = t$. 78 | 2. Each $P_i$ renames: 79 | 80 | $$ 81 | \begin{aligned} 82 | &k_i \gets a^0_i, &d_i \gets b^0_i,\quad &\text{kd}_i \gets c^0_i\cr 83 | &K \gets A^0, &D \gets B^0,\quad &\text{KD} \gets C^0\cr 84 | &a \gets a^1_i, &b \gets b^1_i,\quad &c \gets c^1_i\cr 85 | &A \gets A^1, &B \gets B^1,\quad &C \gets C^1\cr 86 | \end{aligned} 87 | $$ 88 | 89 | 3. Then, each $P_i$ linearizes their shares, setting: 90 | 91 | $$ 92 | \begin{aligned} 93 | (k'_i, d_i, \text{kd}_i) &\gets \lambda(\mathcal{P}_1)_i \cdot (k_i, d_i, \text{kd}_i)\cr 94 | (a'_i, b'_i, c'_i) &\gets \lambda(\mathcal{P}_1)_i \cdot (a_i, b_i, c_i)\cr 95 | x'_i &\gets \lambda(\mathcal{P}_1)_i \cdot x_i\cr 96 | \end{aligned} 97 | $$ 98 | 99 | 4. $\star$ Each $P_i$ sends $\text{kd}_i$ to every other party. 100 | 5. Each $P_i$ sets: 101 | 102 | $$ 103 | \begin{aligned} 104 | &\text{ka}_i \gets k'_i + a'_i\cr 105 | &\text{xb}_i \gets x'_i + b'_i\cr 106 | \end{aligned} 107 | $$ 108 | 109 | 6. $\star$ Each $P_i$ sends $\text{ka}_i$ and $\text{xb}_i$ to every other party. 110 | 111 | **Round 2:** 112 | 113 | 1. $\bullet$ Each $P_i$ waits to receive $\text{kd}_j$ from each other $P_j$. 114 | 2. Each $P_i$ sets $\text{kd} \gets \sum_j \text{kd}_j$. 115 | 3. $\blacktriangle$ Each $P_i$ *asserts* that $\text{kd} \cdot G = \text{KD}$. 116 | 4. $\bullet$ Each $P_i$ waits to receive $\text{ka}_j$ and $\text{xb}_j$ from from every other party $P_j$. 117 | 5. Each $P_i$ sets $\text{ka} \gets \sum_j \text{ka}_j$ and $\text{xb} \gets \sum_j \text{xb}_j$. 118 | 6. $\blacktriangle$ Each $P_i$ asserts that: 119 | 120 | $$ 121 | \begin{aligned} 122 | \text{ka} \cdot G &= K + A\cr 123 | \text{xb} \cdot G &= X + B 124 | \end{aligned} 125 | $$ 126 | 127 | 7. Each $P_i$ sets: $R \gets \frac{1}{\text{kd}} \cdot D$. 128 | 8. Each $P_i$ sets $\sigma_i \gets \text{ka} \cdot x_i - \text{xb} \cdot a_i + c_i$, which is already threshold shared. 129 | 130 | **Output:** 131 | The output is the presignature $(R, k, \sigma)$, with $k$ and $\sigma$ 132 | threshold shared as $k_1, \ldots$ and $\sigma_1, \ldots$. 133 | 134 | # 4 Signing 135 | 136 | In the previous phase, a group of parties $\mathcal{P}_1$ 137 | generate a presignature $(R, k, \sigma)$, with the values 138 | $k$, $\sigma$ being shared with a threshold of $t$. 139 | 140 | In the signing phase, a group of parties $\mathcal{P}_2 \subseteq \mathcal{P}_1$ of size $\geq t$ consumes this presignature 141 | to sign a message $m$. 142 | 143 | **Round 1:** 144 | 145 | 1. Each $P_i$ linearizes their share of $k$, setting $k_i \gets \lambda(\mathcal{P}_2)_i \cdot k_i$. 146 | 2. Each $P_i$ linearizes their share of $\sigma$, setting $\sigma_i \gets \lambda(\mathcal{P}_2)_i \cdot \sigma_i$. 147 | 3. Each $P_i$ sets $s_i \gets \text{Hash}(M) \cdot k_i + h(R) \sigma_i$. 148 | 4. $\star$ Each $P_i$ sends $s_i$ to every other party. 149 | 150 | **Round 2:** 151 | 152 | 1. $\bullet$ Each $P_i$ waits to receive $s_j$ from every other party. 153 | 2. Each $P_i$ sets $s \gets \sum_{j \in [N]} s_j$. 154 | 3. $\blacktriangle$ Each $P_i$ *asserts* that $(R, s)$ is a valid ECDSA signature for $m$. 155 | 4. Each $P_i$ outputs $(R, s)$. 156 | 157 | **Output** 158 | 159 | The pair $(R, s)$ is the signature. 160 | 161 | -------------------------------------------------------------------------------- /docs/triples.md: -------------------------------------------------------------------------------- 1 | This document specifies the protocol for triple generation. 2 | Note that while the other specifications and parts of the protocol are intended 3 | to be strictly followed, this specification is less opinionated about generating 4 | triples. 5 | 6 | As long as valid triples are generated according to proces, they 7 | can be used in the subsequent presigning phase. 8 | The presigning phase is agnostic as to how these triples have been generated. 9 | 10 | This document only gives a very concrete suggestion as to how this might be implemented, 11 | and describes how this crate implements triple generation without a trusted 12 | dealer. 13 | 14 | Compared to the other parts of the protocol, triple generation is more complex, 15 | in that it involves the composition of several layers of protocols. 16 | We describe each of these from the bottom up. 17 | 18 | # Random Oblivious Transfer 19 | 20 | The first protocol we make use of is *random oblivious transfer*. 21 | This is a two-party protocol, involving a sender $\mathcal{S}$, and a receiver $\mathcal{R}$. 22 | The receiver has as input a bit $b$. 23 | 24 | The output of the protocol if a trusted dealer existed would be: 25 | 26 | $$ 27 | \begin{aligned} 28 | &k_ 0, k_ 1 \xleftarrow{\$} \\\{0, 1\\\}^\lambda\cr 29 | &\mathcal{R} \texttt{ gets } k_ b\cr 30 | &\mathcal{S} \texttt{ gets } k_ 0, k_ 1\cr 31 | \end{aligned} 32 | $$ 33 | 34 | The seed length $\lambda$ is the ambient security parameter. 35 | (e.g. 128 bits, in the usual case) 36 | 37 | In particular, we consider a *batched* version of this functionality, 38 | in which the receiver has $l$ bits $b_ 1, \ldots, b_ l$, and both parties. 39 | Receive the output $l$ times. 40 | 41 | We can also consider the result as being two matrices $K_{ij}^0$ and $K_{ij}^1$, 42 | with $i \in [l]$, $j \in [\lambda]$, 43 | with the receiver getting the matrix $K_ {ij}^{b_ i}$. 44 | 45 | ## "Simplest" OT Protocol 46 | 47 | For batch random OT, we use the so-called "simplest" OT Protocol 48 | [[CO15]](https://eprint.iacr.org/2015/267). 49 | 50 | Procotol `Batch-Random-OT`: 51 | 52 | 1. $\mathcal{S}$ samples $y \xleftarrow{R} \mathbb{F}_q$, and sets $Y \gets y \cdot G$, and $Z \gets y \cdot Y$. 53 | 2. $\star$ $\mathcal{S}$ sends $Y$ to $\mathcal{R}$. 54 | 3. $\bullet$ $\mathcal{R}$ waits to receive $Y$. 55 | 56 | In parallel, for $i \in [l]$: 57 | 58 | 4. $\mathcal{R}$ samples $x_i \xleftarrow{R} \mathbb{F}_q$, and computes $X_i \gets b_i \cdot Y + x_i \cdot G$. 59 | 5. $\mathcal{R}$ computes $K_{ij}^{b_ i} \gets H_{(i, Y, X_ i)}(x_ i \cdot Y)$ 60 | 6. $\star$ $\mathcal{R}$ sends $X_i$ to $\mathcal{S}$. 61 | 7. $\bullet$ $\mathcal{S}$ waits to receive $X_i$. 62 | 8. $\mathcal{S}$ computes $K_{ij}^b \gets H_{(i, Y, X_ i)}(y \cdot X_ i - b \cdot Z)_j$. 63 | 64 | Here $H$ is a hash function, parameterized by an integer $i$, as well 65 | as two points, providing a key derivation function $\mathbb{G} \to \mathbb{F}_2^{\lambda}$. 66 | 67 | # Setup Phase 68 | 69 | The goal of the setup phase is for each unordered pair of parties $P_a$ 70 | and $P_b$ to run a $\lambda$ batched random OT. 71 | Each pair will be run only once, so we need to agree on a canonical 72 | way to determine which of two parties $P_a$ and $P_b$ 73 | will act as the sender. 74 | We do this by imposing a total order $<$ on the parties, and $P_a$ 75 | is the sender in the $(P_a, P_b)$ pair, if $P_a < P_b$. 76 | 77 | The end result is that $P_a$ will learn $K_{ij}^0$ and $K_{ij}^1$, 78 | and $P_b$ will learn $K_{ij}^{\Delta_ i}$, for a randomly chosen $\Delta \in \mathbb{F}_2^{\lambda}$. 79 | 80 | In more detail: 81 | 82 | Protocol `Triples-Setup` 83 | 84 | In parallel, for each unordered pair of parties $P_a$ and $P_b$ 85 | $P_b$ samples $\Delta \xleftarrow{R} \mathbb{F}_2^\lambda$, 86 | and then $P_a$ and $P_b$ run `Batch-Random-OT` with a batch size 87 | of $\lambda$, and save the result. 88 | Note that communication in this subprotocol should be *private*. 89 | 90 | # Extended Oblivious Transfer 91 | 92 | The goal of the extended oblivious transfer protocol is for two parties 93 | to extend their joint setup, and use that setup to generate $\kappa$ oblivious 94 | transfers, using fast symmetric key primitives. 95 | 96 | We use the [KOS15](https://eprint.iacr.org/2015/546), 97 | specifically, the amended version using SoftspokenOT. 98 | 99 | ## Correlated OT Extension 100 | 101 | We start with the *correlated* extension protocol. 102 | 103 | The correlation comes from the $\Delta$ value used in the setup, controlled 104 | by the sender $\mathcal{S}$. 105 | Note that the sender was the receiver in the original setup. 106 | In this protocol $\mathcal{R}$ uses an input matrix $X_{ij}$, and learns a random boolean matrix 107 | $T_{ij} \in \mathbb{F}_ 2$, $i \in [\kappa], j \in [\lambda]$, 108 | and $\mathcal{S}$ learns $Q_{ij} = T_{ij} + X_{ij} \cdot \Delta_ j$ 109 | 110 | Protocol `Correlated-OT-Extension`: 111 | 112 | $\mathcal{R}$ has $K_{ij}^b$ from a prior setup phase, and $\mathcal{S}$ 113 | has $\Delta_i$ and $K_{ij}^{\Delta_i}$ from that setup phase. 114 | 115 | $\mathcal{R}$ has an input matrix $X_{ij}$, with $i \in [\kappa]$, and $j \in [\lambda]$. 116 | 117 | We also require a pseudo-random generator $\text{PRG} : \mathbb{F}_2^{\lambda} \to \mathbb{F}_2^{\kappa}$. 118 | This generator is parameterized by a session id $\text{sid}$, allowing the same 119 | setup to be used for multiple extensions, so long as $\text{sid}$ is **unique** 120 | for each execution. 121 | 122 | 1. $\mathcal{R}$ computes: $T_ {ij}^b \gets \text{PRG}_ {\text{sid}}(K^b_ {j \bullet})_ i$. 123 | 2. $\mathcal{S}$ computes: $T_ {ij}^{\Delta_ j} \gets \text{PRG}_ {\text{sid}}(K^{\Delta_ j}_ {j \bullet})_ i$. 124 | 3. $\mathcal{R}$ computes $U_{ij} = T_{ij}^0 + T_{ij}^1 + X_{ij}$. 125 | 4. $\star$ $\mathcal{R}$ sends $U_{ij}$ to $\mathcal{S}$ 126 | 5. $\bullet$ $\mathcal{S}$ waits to receive $U_{ij}$. 127 | 6. $\mathcal{S}$ computes $Q_{ij} = \Delta_j \cdot U_{ij} + T_{ij}^{\Delta_j}$. 128 | 7. $\square$ $\mathcal{R}$ returns $T_{ij}^0$, and $\mathcal{S}$ returns $Q_{ij}$. 129 | 130 | Note that since we're working in $\mathbb{F}_ 2$, we have $Q_ {ij} = T_ {ij}^0 + X_ {ij} \cdot \Delta_ j$. 131 | 132 | ## Random OT Extension 133 | 134 | Random OT extension also uses $K^b_{ij}$, and $\Delta_i$ from the setup phase. 135 | The output of this phase are $\kappa$ pairs of random field elements 136 | $v_1^b, \ldots, v_\kappa^b$ in $\mathbb{F}_q$ for the sender, and $v_i^{b_i}$ for the receiver, 137 | where $b_i$ is random bit for the $i$-th element. 138 | 139 | For the sake of this protocol, we can identifier vectors in $\mathbb{F}_ 2^\lambda$ 140 | with field elements in $\mathbb{F}_ {2^\lambda}$, and we write $\text{mul}$ for 141 | explicit multiplication in this field. 142 | 143 | Protocol `Random-OT-Extension`: 144 | 145 | $\mathcal{R}$ has $K^b_{ij}$ from a prior setup phase, and $\mathcal{S}$ has 146 | $\Delta_i$ and $K_{ij}^{\Delta_i}$ from that same setup. 147 | 148 | This protocol is also parameterized by a unique session id $\text{sid}$ 149 | 150 | 1. $\mathcal{R}$ generates a random vector $b_ i \in \mathbb{F}_ 2$, with $i \in [\kappa']$, and sets $X_ {ij} \gets b_ i 1_ j$. Where $1_ j$ is a vector filled with $\lambda$ ones. 151 | 2. $\mathcal{R}$ and $\mathcal{S}$ run `Correlated-OT-Extension`, with batch size $\kappa'$, and session id $\text{sid}$ with $\mathcal{R}$ 152 | using $X_{ij}$ as its input. 153 | The parties receive $T_ {ij}$ and $Q_ {ij}$ respectively. 154 | 3. $\mathcal{S}$ samples $s \xleftarrow{R} \mathbb{F}_2^\lambda$. 155 | 4. $\star$ $\mathcal{S}$ sends $s$ to $\mathcal{R}$. 156 | 5. $\bullet$ $\mathcal{R}$ waits to receive $s$. 157 | 6. Let $\mu \gets \lceil \kappa' / \lambda \rceil$, 158 | then, the parties set $\hat{T}_ {ij}$, $\hat{b}_ i$, $\hat{Q}_ {ij}$, 159 | with $i \in [\mu]$, $j \in [\lambda]$ by grouping adjacent bits 160 | into elements of the field $\mathbb{F}_ {2^\lambda}$. 161 | 162 | 7. The parties use a 163 | PRG to set $\chi_1, \ldots, \chi_\mu \gets \text{PRG}(s)$, 164 | where $\chi_i \in \mathbb{F}_{2^\lambda}$. 165 | 8. $\mathcal{R}$ computes $x \gets \langle \hat{b}_ i, \chi_ i \rangle$, 166 | and $t_ j \gets \langle \text{mul}(\hat{T}_ {i j}, \chi_i), 1_ i\rangle$. 167 | 9. $\star$ $\mathcal{R}$ sends $x$ and $t_ 1, \ldots, t_ {\lambda}$ to $\mathcal{S}$. 168 | 10. $\mathcal{S}$ calculates $q_j \gets \langle \text{mul}(\hat{Q}_{ij}, \chi_i), 1_i \rangle$. 169 | 11. $\bullet$ $\mathcal{S}$ waits to receive $x$ and $t_j$, and checks that 170 | $q_j = t_j + \Delta_j \cdot x$. 171 | 12. $\mathcal{S}$ sets $v^0_i \gets H_i(Q_{i\bullet})$ and $v^1_i \gets H_i(Q_{i \bullet} + \Delta_\bullet)$, for $i \in [\kappa]$ 172 | 13. $\mathcal{R}$ sets $v^{b_ i}_ i \gets H_ i(T_ {i\bullet})$, for $i \in [\kappa]$ 173 | 174 | # Multiplicative to Additive Conversion 175 | 176 | We follow [HMRT21](https://eprint.iacr.org/2021/1373). 177 | 178 | In this protocol, two parties $\mathcal{S}$ and $\mathcal{R}$ have values 179 | $a, b \in \mathbb{F}_q$ respectively. 180 | The output of this protocol has each party receiver $\alpha, \beta \in \mathbb{F}_q$ 181 | respectively, such that $\alpha + \beta = a \cdot b$. 182 | 183 | This protocol requires the parties to have a triple setup. 184 | Additionally, rather than describing the protocol as making a call to a random 185 | OT extension internally, we instead say that the participants must have done 186 | this prior to the protocol. 187 | This makes our description of using a single OT extension for multiple instances 188 | of MTA easier. 189 | 190 | Protocol `MTA`: 191 | 192 | Let $\kappa = \lceil \lg q \rceil + \lambda$. 193 | 194 | The parties have, in a previous phase, have generated correlated randomness 195 | of the following form: 196 | 197 | $$ 198 | \begin{aligned} 199 | &v_i^0, v_i^1 \xleftarrow{R} \mathbb{F}_q\ (i \in [\kappa])\cr 200 | &t_i \xleftarrow{R} \mathbb{F}_2\cr 201 | &\mathcal{S} \texttt{ receives } (v_i^0, v_i^1)\cr 202 | &\mathcal{R} \texttt{ receives } (t_i, v_i^{t_i})\cr 203 | \end{aligned} 204 | $$ 205 | 206 | 1. $\mathcal{S}$ samples random $\delta_1, \ldots, \delta_\kappa \xleftarrow{R} \mathbb{F}_q$. 207 | 2. $\star$ $\mathcal{S}$ sends $(-a + \delta_i + v_i^0, a + \delta_i + v_i^1)$ to $\mathcal{R}$. 208 | 3. $\bullet$ $\mathcal{R}$ waits to receive $(c^0_i, c^1_i)$ from $\mathcal{S}$, and 209 | sets $m_i \gets c^{t_i}_i - v_i^{t_i}$ 210 | 4. $\mathcal{R}$ samples $s \xleftarrow{R} \mathbb{F}_2^\lambda$, and 211 | extends this into $\chi_2, \ldots, \chi_\kappa \gets \text{PRG}(s)$. 212 | $\mathcal{S}$ then sets $\chi_ 1 \gets (-1)^{t_ 1}(b - \sum_{i \in [2\ldots \kappa]} \chi_ i \cdot (-1)^{t_ i})$. 213 | (This makes it so that $b = \langle \chi_ i, (-1)^{t_ i} \rangle$) 214 | 5. $\mathcal{R}$ saves $\beta = \langle \chi_ i, m_ i \rangle$. 215 | 6. $\star$ $\mathcal{R}$ sends $s$ and $\chi_ 1$ to $\mathcal{S}$. 216 | 7. $\bullet$ $\mathcal{S}$ waits to receive $s$ and $\chi_ 1$, and uses $s$ 217 | to expand $\chi_ 2, \ldots, \chi_\kappa \gets \text{PRG(s)}$. 218 | 8. $\square$ $\mathcal{S}$ outputs $\alpha \gets - \langle \chi_ i, \delta_ i \rangle$ 219 | 220 | In the presence of malicious parties, this protocol may return a result 221 | such that $\alpha + \beta$ is *not* $ab$, however, malicious parties 222 | cannot learn information about the other party's result, except with 223 | negligible probability. 224 | 225 | Our triple generation protocol will take care of multiplication potentially 226 | being wrong. 227 | 228 | # Multiplication 229 | 230 | This protocol involves $n$ parties $P_1, \ldots, P_n$. 231 | Each of them has a share $a_i$ and $b_i$, of global values $a$ and $b$ in $\mathbb{F}_q$. 232 | 233 | The goal is for each party to obtain a share $c_i$ of $c = ab$. 234 | 235 | The idea behind the protocol is to use the decomposition: 236 | 237 | $$ 238 | c = ab = (\sum_i a_i)(\sum_j b_j) = \sum_{ij} a_i b_j 239 | $$ 240 | 241 | We run the `MTA` protocol for each unordered pair of parties, giving each party 242 | two shares $\gamma^0_i$, and $\gamma^1_i$, which they then add to $a_{i} b_i$ to 243 | get their share $c_i$. 244 | 245 | The protocol is also parameterized by a unique session id $\text{sid}$, 246 | and requires the triple setup phase to have been performed. 247 | 248 | Protocol `Multiplication`: 249 | 250 | In parallel, for each order pair of parties $P_i < P_j$, 251 | with $\mathcal{S} = P_i$, being the sender, and $\mathcal{R} = P_j$ 252 | being the receiver. 253 | 254 | Let $\kappa = \lceil q \rceil + \lambda$. 255 | 256 | 1. $\mathcal{S}$ and $\mathcal{R}$ run `Random-OT-Extension` with $\text{sid}$ and a batch size of $2 \kappa$. 257 | $\mathcal{S}$ receives $v_i^0, v_i^1$, and $\mathcal{R}$ receives $t_i$ and $v_i^{t_i}$. 258 | 259 | In parallel, for $(a,b) = (a_i, b_j)$ and $(a, b) = (b_i, a_j)$: 260 | 261 | 2. $\mathcal{S}$ and $\mathcal{R}$ run `MTA` using the first (or last, in the second 262 | instance) $\kappa$ elements of the previous step, as well as their respective inputs 263 | $a$ and $b$. 264 | 265 | 3. $\mathcal{S}$ receives $\gamma_j^0, \gamma_j^1$, and $\mathcal{R}$ receives 266 | $\gamma_i^0, \gamma_i^1$. (Writing it this way means that each party has 267 | one instance of $\gamma$ for every other party they're interacting with). 268 | 269 | After all the p2p interactions are done: 270 | 271 | 4. Every party $P_i$ sets $c_i = a_i b_i + \sum_j (\gamma_j^0 + \gamma_j^1)$. 272 | 273 | # Triple Generation 274 | 275 | The goal of triple generation is to generate *threshold* shares 276 | of values $a, b, c$ such that $ab = c$. 277 | Additionally, the parties should also learn $A = a \cdot G$, $B = b \cdot G$, 278 | and $C = c \cdot G$. 279 | 280 | More concretely, we have a set of parties $\mathcal{P}$ of size $N$, 281 | which want to generate a triple with threshold $t$. 282 | 283 | **Round 1:** 284 | 285 | 1. $T.\text{Add}(\mathbb{G}, \mathcal{P}, t)$ 286 | 2. Each $P_ i$ samples $e, f, l \xleftarrow{R} \mathbb{F}_ q[X]_ {\leq (t - 1)}$. 287 | 3. Each $P_ i$ sets $l(0) = 0$. 288 | 3. Each $P_i$ sets $E_i \gets e \cdot G, F_i \gets f \cdot G, L_i \gets l \cdot G$. 289 | 4. Each $P_i$ sets $(\text{Com}_i, r_i) \gets \text{Commit}((E_i, F_i, L_i))$. 290 | 5. $\star$ Each $P_i$ sends $\text{Com}_i$ to all other parties. 291 | 292 | **Round 2:** 293 | 294 | 1. $\bullet$ Each $P_i$ waits to receive $\text{Com}_j$ from each other $P_j$. 295 | 2. Each $P_i$ sets $\text{Confirm}_i \gets H(\text{Com}_1, \ldots, \text{Com}_N)$. 296 | 3. $T.\text{Add}(\text{Confirm}_i)$ 297 | 4. In *parallel* to the following steps, the parties run `Multiplication` using 298 | $\text{Confirm}_i$ as the session id, and using $e(0)$ and $f(0)$ as their personal shares. 299 | 5. $\star$ Each $P_i$ sends $\text{Confirm}_i$ to every other party. 300 | 6. Each $P_i$ generates the proofs: 301 | 302 | $$ 303 | \begin{aligned} 304 | \pi^0_i &\gets \text{Prove}(T.\text{Cloned}(\texttt{dlog0}, i), \text{Mau}(- \cdot G, E_i(0); e(0)))\cr 305 | \pi^1_i &\gets \text{Prove}(T.\text{Cloned}(\texttt{dlog1}, i), \text{Mau}(- \cdot G, F_i(0); f(0)))\cr 306 | \end{aligned} 307 | $$ 308 | 309 | 7. $\star$ Each $P_i$ sends $(E_i, F_i, L_i, r_i, \pi^0_i, \pi^1_i)$ to every other party. 310 | 7. $\textcolor{red}{\star}$ Each $P_i$ *privately* sends $a_i^j = e(j)$ and $b_i^j$ = $f(j)$ to every other party $P_j$. 311 | 312 | **Round 3:** 313 | 314 | 1. $\bullet$ Each $P_i$ waits to receive $\text{Confirm}_j$ from each other $P_j$. 315 | 2. $\blacktriangle$ Each $P_i$ *asserts* that $\forall P_j.\ \text{Confirm}_j = \text{Confirm}_i$. 316 | 3. $\bullet$ Each $P_i$ waits to receive $(E_j, F_j, L_j, r_i, \pi^0_j, \pi^1_j)$ from each other $P_j$. 317 | 4. $\blacktriangle$ Each $P_i$ asserts that $\forall P_j$: 318 | 319 | $$ 320 | \begin{aligned} 321 | &\text{deg}(E_j) = \text{deg}(F_j) = \text{deg}(L_j) = t - 1\cr 322 | &\forall j. L_j(0) = 0\cr 323 | &\text{CheckCommit}(\text{Com}_j, (E_j, F_j, L_j), r_j)\cr 324 | &\text{Verify}(T.\text{Cloned}(\texttt{dlog0}, j), \pi^0_j, \text{Mau}(- \cdot G, E_j(0)))\cr 325 | &\text{Verify}(T.\text{Cloned}(\texttt{dlog1}, j), \pi^1_j, \text{Mau}(- \cdot G, F_j(0)))\cr 326 | \end{aligned} 327 | $$ 328 | 329 | 5. $\bullet$ Each $P_i$ waits to receive $a^i_j$ and $b^i_j$ from every other $P_j$. 330 | 6. Each $P_i$ sets $a_i \gets \sum_j a^i_j$, $b_i \gets \sum_j b^i_j$, $E \gets \sum_j E_j$, and $F \gets \sum_j F_j$. 331 | 7. $\blacktriangle$ Each $P_i$ *asserts* that $E(i) = a_i \cdot G$ and $F(i) = b_i \cdot G$. 332 | 8. Each $P_i$ sets $C_i \gets e(0) \cdot F(0)$. 333 | 9. Each $P_I$ generates the proof: 334 | 335 | $$ 336 | \pi_i \gets \text{Prove}(T.\text{Cloned}(\texttt{dlogeq0}, i), \text{Mau}((- \cdot G, - \cdot F(0)), (E_i(0), C_i); e(0)) 337 | $$ 338 | 339 | 10. $\star$ Each $P_i$ sends $(C_i, \pi_i)$ to every other party. 340 | 341 | **Round 4:** 342 | 343 | 1. $\bullet$ Each $P_i$ waits to receive $(C_j, \pi_j)$ from each other $P_j$. 344 | 2. $\blacktriangle$ Each $P_i$ *asserts* that $\forall P_j$: 345 | 346 | $$ 347 | \text{Verify}(T.\text{Cloned}(\texttt{dlogeq0}, j), \pi_j, \text{Mau}((- \cdot G, - \cdot F(0)), (E_j(0), C_j))) 348 | $$ 349 | 350 | 3. Each $P_i$ sets $C \gets \sum_i C_i$. 351 | 4. $\bullet$ Each $P_i$ waits to receive $l_0$ from the `Multiplication` protocol. 352 | 5. Each $P_i$ sets $\hat{C}_i = l_0 \cdot G$. 353 | 6. Each $P_i$ generates the proof: 354 | 355 | $$ 356 | \begin{aligned} 357 | \pi_i &\gets \text{Prove}(T.\text{Cloned}(\texttt{dlog2}, i), \text{Mau}(- \cdot G, \hat{C}_i; l_0)))\cr 358 | \end{aligned} 359 | $$ 360 | 361 | 7. $\star$ Each $P_i$ sends $(\hat{C}_i, \pi_i)$ to every other party. 362 | 8. $\textcolor{red}{\star}$ Each $P_i$ *privately* sends $c_i^j \gets l_0 + l_i(j)$ to every other $P_j$. 363 | 364 | **Round 5:** 365 | 366 | 1. $\bullet$ Each $P_i$ waits to receive $(\hat{C}_j, \pi_j)$ from every other party. 367 | 2. $\blacktriangle$ Each $P_i$ *asserts* that (for all $j$): 368 | 369 | $$ 370 | \begin{aligned} 371 | &\text{Verify}(T.\text{Cloned}(\texttt{dlog2}, j), \pi_j, \text{Mau}(- \cdot G, \hat{C}_j)\cr 372 | \end{aligned} 373 | $$ 374 | 375 | 3. Each $P_i$ sets $L \gets \sum_i \hat{C}_i + L_i$. 376 | 4. $\blacktriangle$ Each $P_i$ *asserts* that $C = L(0)$. 377 | 5. $\bullet$ Each $P_i$ waits to receive $c_j^i$ from every other $P_j$. 378 | 6. Each $P_i$ sets $c_i \gets \sum_j c_j^i$. 379 | 7. $\blacktriangle$ Each $P_i$ *asserts* that $L(i) = c_i \cdot G$. 380 | 8. Each $P_i$ sets $A \gets E(0)$, $B \gets F(0)$. 381 | 9. $\square$ Each $P_i$ returns $((a_i, b_i, c_i), (A, B, C))$. 382 | 383 | -------------------------------------------------------------------------------- /examples/network-benches.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | use cait_sith::{ 7 | keygen, presign, 8 | protocol::{Action, MessageData, Participant, Protocol}, 9 | sign, triples, PresignArguments, 10 | }; 11 | use digest::{Digest, FixedOutput}; 12 | use easy_parallel::Parallel; 13 | use ecdsa::hazmat::DigestPrimitive; 14 | use elliptic_curve::{ops::Reduce, Curve}; 15 | use haisou_chan::{channel, Bandwidth}; 16 | 17 | use k256::{FieldBytes, Scalar, Secp256k1}; 18 | use rand_core::OsRng; 19 | use structopt::StructOpt; 20 | 21 | fn scalar_hash(msg: &[u8]) -> Scalar { 22 | let digest = ::Digest::new_with_prefix(msg); 23 | let m_bytes: FieldBytes = digest.finalize_fixed(); 24 | ::Uint>>::reduce_bytes(&m_bytes) 25 | } 26 | 27 | #[derive(Debug, StructOpt)] 28 | struct Args { 29 | /// The number of parties to run the benchmarks with. 30 | parties: u32, 31 | /// The latency, in milliseconds. 32 | latency_ms: u32, 33 | /// The bandwidth, in bytes per second. 34 | bandwidth: u32, 35 | } 36 | 37 | #[derive(Debug, Clone, Copy)] 38 | struct Stats { 39 | sent: usize, 40 | received: usize, 41 | } 42 | 43 | fn run_protocol( 44 | latency: Duration, 45 | bandwidth: Bandwidth, 46 | participants: &[Participant], 47 | f: F, 48 | ) -> Vec<(Participant, Stats, T)> 49 | where 50 | F: Fn(Participant) -> P + Send + Sync, 51 | P: Protocol, 52 | T: Send, 53 | { 54 | // We create a link between each pair of parties, with a set amount of latency, 55 | // but no bandwidth constraints. 56 | let mut senders: HashMap<_, _> = participants.iter().map(|p| (p, HashMap::new())).collect(); 57 | let mut receivers: HashMap<_, _> = participants.iter().map(|p| (p, Vec::new())).collect(); 58 | 59 | for p in participants { 60 | for q in participants { 61 | if p >= q { 62 | continue; 63 | } 64 | let (sender0, mut receiver0) = channel(); 65 | let (sender1, mut receiver1) = channel(); 66 | receiver0.set_latency(latency); 67 | receiver1.set_latency(latency); 68 | senders.get_mut(p).unwrap().insert(q, sender0); 69 | senders.get_mut(q).unwrap().insert(p, sender1); 70 | receivers.get_mut(p).unwrap().push((q, receiver1)); 71 | receivers.get_mut(q).unwrap().push((p, receiver0)); 72 | } 73 | } 74 | 75 | let executor = smol::Executor::new(); 76 | 77 | // Next, we create a bottleneck link which every outgoing message passes through, 78 | // which limits how fast data can be transmitted away from the node. 79 | let mut outgoing = HashMap::new(); 80 | for (p, mut senders) in senders { 81 | let (mut bottleneck_s, bottleneck_r) = channel(); 82 | bottleneck_s.set_bandwidth(bandwidth); 83 | executor 84 | .spawn(async move { 85 | loop { 86 | let (to, msg): (Participant, MessageData) = match bottleneck_r.recv().await { 87 | Ok(x) => x, 88 | Err(_) => return, 89 | }; 90 | senders 91 | .get_mut(&to) 92 | .unwrap() 93 | .send(msg.len(), msg) 94 | .await 95 | .unwrap(); 96 | } 97 | }) 98 | .detach(); 99 | outgoing.insert(p, bottleneck_s); 100 | } 101 | 102 | // For convenience, we create a channel in order to receive the first 103 | // available message across any of the parties. 104 | let mut incoming = HashMap::new(); 105 | for (p, receivers) in receivers { 106 | let (sender, receiver) = smol::channel::unbounded(); 107 | for (q, r) in receivers { 108 | executor 109 | .spawn({ 110 | let sender = sender.clone(); 111 | async move { 112 | loop { 113 | let msg = match r.recv().await { 114 | Ok(msg) => msg, 115 | Err(_) => return, 116 | }; 117 | sender.send((*q, msg)).await.unwrap(); 118 | } 119 | } 120 | }) 121 | .detach(); 122 | } 123 | incoming.insert(p, receiver); 124 | } 125 | 126 | let setup = participants.iter().map(|p| { 127 | let incoming = incoming.remove(p).unwrap(); 128 | let outgoing = outgoing.remove(p).unwrap(); 129 | (p, outgoing, incoming) 130 | }); 131 | 132 | // Now we run all of the protocols in parallel, on a different thread. 133 | let mut out = Parallel::new() 134 | .each(setup, |(p, mut outgoing, incoming)| { 135 | smol::block_on(executor.run(async { 136 | let mut prot = f(*p); 137 | let mut stats = Stats { 138 | sent: 0, 139 | received: 0, 140 | }; 141 | loop { 142 | loop { 143 | let poked = prot.poke().unwrap(); 144 | match poked { 145 | Action::Wait => break, 146 | Action::SendMany(m) => { 147 | for q in participants { 148 | if p == q { 149 | continue; 150 | } 151 | stats.sent += m.len(); 152 | outgoing.send(m.len(), (*q, m.clone())).await.unwrap(); 153 | } 154 | } 155 | Action::SendPrivate(q, m) => { 156 | stats.sent += m.len(); 157 | outgoing.send(m.len(), (q, m.clone())).await.unwrap(); 158 | } 159 | Action::Return(r) => return (*p, stats, r), 160 | } 161 | } 162 | let (from, m) = incoming.recv().await.unwrap(); 163 | stats.received += m.len(); 164 | prot.message(from, m); 165 | } 166 | })) 167 | }) 168 | .run(); 169 | 170 | out.sort_by_key(|(p, _, _)| *p); 171 | 172 | out 173 | } 174 | 175 | fn report_stats(iter: I) 176 | where 177 | I: Iterator, 178 | { 179 | let mut count = 0; 180 | let mut avg_up = 0; 181 | let mut avg_down = 0; 182 | iter.for_each(|stats| { 183 | count += 1; 184 | avg_up += stats.sent; 185 | avg_down += stats.received; 186 | }); 187 | avg_up /= count; 188 | avg_down /= count; 189 | println!("up:\t {} B", avg_up); 190 | println!("down:\t {} B", avg_down); 191 | } 192 | 193 | fn main() { 194 | let args = Args::from_args(); 195 | let latency = Duration::from_millis(args.latency_ms as u64); 196 | let bandwidth = args.bandwidth; 197 | let participants: Vec<_> = (0..args.parties) 198 | .map(|p| Participant::from(p as u32)) 199 | .collect(); 200 | 201 | println!( 202 | "\nTriple Gen {} [{} ms, {} B/S]", 203 | args.parties, args.latency_ms, args.bandwidth 204 | ); 205 | let start = Instant::now(); 206 | let results = run_protocol(latency, bandwidth, &participants, |p| { 207 | triples::generate_triple::(&participants, p, args.parties as usize).unwrap() 208 | }); 209 | let stop = Instant::now(); 210 | println!("time:\t{:#?}", stop.duration_since(start)); 211 | report_stats(results.iter().map(|(_, stats, _)| *stats)); 212 | 213 | let triples: HashMap<_, _> = results.into_iter().map(|(p, _, out)| (p, out)).collect(); 214 | 215 | println!( 216 | "\nKeygen ({}, {}) [{} ms, {} B/S]", 217 | args.parties, args.parties, args.latency_ms, args.bandwidth 218 | ); 219 | let start = Instant::now(); 220 | let results = run_protocol(latency, bandwidth, &participants, |p| { 221 | keygen(&participants, p, args.parties as usize).unwrap() 222 | }); 223 | let stop = Instant::now(); 224 | println!("time:\t{:#?}", stop.duration_since(start)); 225 | report_stats(results.iter().map(|(_, stats, _)| *stats)); 226 | 227 | let shares: HashMap<_, _> = results.into_iter().map(|(p, _, out)| (p, out)).collect(); 228 | 229 | let (other_triples_pub, other_triples_share) = 230 | triples::deal(&mut OsRng, &participants, args.parties as usize); 231 | let other_triples: HashMap<_, _> = participants 232 | .iter() 233 | .zip(other_triples_share) 234 | .map(|(p, share)| (p, (share, other_triples_pub.clone()))) 235 | .collect(); 236 | 237 | println!( 238 | "\nPresign ({}, {}) [{} ms, {} B/S]", 239 | args.parties, args.parties, args.latency_ms, args.bandwidth 240 | ); 241 | let start = Instant::now(); 242 | let results = run_protocol(latency, bandwidth, &participants, |p| { 243 | presign( 244 | &participants, 245 | p, 246 | PresignArguments { 247 | triple0: triples[&p].clone(), 248 | triple1: other_triples[&p].clone(), 249 | keygen_out: shares[&p].clone(), 250 | threshold: args.parties as usize, 251 | }, 252 | ) 253 | .unwrap() 254 | }); 255 | let stop = Instant::now(); 256 | println!("time:\t{:#?}", stop.duration_since(start)); 257 | report_stats(results.iter().map(|(_, stats, _)| *stats)); 258 | 259 | let presignatures: HashMap<_, _> = results.into_iter().map(|(p, _, out)| (p, out)).collect(); 260 | 261 | println!( 262 | "\nSign ({}, {}) [{} ms, {} B/S]", 263 | args.parties, args.parties, args.latency_ms, args.bandwidth 264 | ); 265 | let start = Instant::now(); 266 | let results = run_protocol(latency, bandwidth, &participants, |p| { 267 | sign( 268 | &participants, 269 | p, 270 | shares[&p].public_key, 271 | presignatures[&p].clone(), 272 | scalar_hash(b"hello world"), 273 | ) 274 | .unwrap() 275 | }); 276 | let stop = Instant::now(); 277 | println!("time:\t{:#?}", stop.duration_since(start)); 278 | report_stats(results.iter().map(|(_, stats, _)| *stats)); 279 | } 280 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronokirby/cait-sith/8e6dc86d1a6c672315a7391c3f0cb3c58c990f3f/logo.png -------------------------------------------------------------------------------- /src/compat.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{ops::Reduce, point::AffineCoordinates, Curve, CurveArithmetic, PrimeCurve}; 2 | use rand_core::CryptoRngCore; 3 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 4 | 5 | /// Represents a curve suitable for use in cait-sith. 6 | /// 7 | /// This is the trait that any curve usable in this library must implement. 8 | /// This library does provide a few feature-gated implementations for curves 9 | /// itself, beyond that you'll need to implement this trait yourself. 10 | /// 11 | /// The bulk of the trait are the bounds requiring a curve according 12 | /// to RustCrypto's traits. 13 | /// 14 | /// Beyond that, we also require that curves have a name, for domain separation, 15 | /// and a way to serialize points with serde. 16 | pub trait CSCurve: PrimeCurve + CurveArithmetic { 17 | const NAME: &'static [u8]; 18 | 19 | const BITS: usize; 20 | /// Serialize a point with serde. 21 | fn serialize_point( 22 | point: &Self::AffinePoint, 23 | serializer: S, 24 | ) -> Result; 25 | 26 | /// Deserialize a point with serde. 27 | fn deserialize_point<'de, D: Deserializer<'de>>( 28 | deserializer: D, 29 | ) -> Result; 30 | 31 | /// A function to sample a random scalar, guaranteed to be constant-time. 32 | /// 33 | /// By this, it's meant that we will make pull a fixed amount of 34 | /// data from the rng. 35 | fn sample_scalar_constant_time(r: &mut R) -> Self::Scalar; 36 | } 37 | 38 | #[cfg(any(feature = "k256", test))] 39 | mod k256_impl { 40 | use super::*; 41 | 42 | use elliptic_curve::bigint::{Bounded, U512}; 43 | use k256::Secp256k1; 44 | 45 | impl CSCurve for Secp256k1 { 46 | const NAME: &'static [u8] = b"Secp256k1"; 47 | const BITS: usize = ::BITS; 48 | 49 | fn serialize_point( 50 | point: &Self::AffinePoint, 51 | serializer: S, 52 | ) -> Result { 53 | point.serialize(serializer) 54 | } 55 | 56 | fn deserialize_point<'de, D: Deserializer<'de>>( 57 | deserializer: D, 58 | ) -> Result { 59 | Self::AffinePoint::deserialize(deserializer) 60 | } 61 | 62 | fn sample_scalar_constant_time(r: &mut R) -> Self::Scalar { 63 | let mut data = [0u8; 64]; 64 | r.fill_bytes(&mut data); 65 | >::reduce_bytes(&data.into()) 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod test_scalar_hash { 72 | use super::*; 73 | 74 | use digest::{Digest, FixedOutput}; 75 | use ecdsa::hazmat::DigestPrimitive; 76 | use elliptic_curve::{ops::Reduce, Curve}; 77 | use k256::{FieldBytes, Scalar, Secp256k1}; 78 | 79 | #[cfg(test)] 80 | pub(crate) fn scalar_hash(msg: &[u8]) -> ::Scalar { 81 | let digest = ::Digest::new_with_prefix(msg); 82 | let m_bytes: FieldBytes = digest.finalize_fixed(); 83 | ::Uint>>::reduce_bytes(&m_bytes) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | pub(crate) use test_scalar_hash::scalar_hash; 89 | 90 | #[derive(Clone, Copy)] 91 | pub(crate) struct SerializablePoint(C::AffinePoint); 92 | 93 | impl SerializablePoint { 94 | pub fn to_projective(self) -> C::ProjectivePoint { 95 | self.0.into() 96 | } 97 | 98 | pub fn from_projective(point: &C::ProjectivePoint) -> Self { 99 | Self((*point).into()) 100 | } 101 | } 102 | 103 | impl Serialize for SerializablePoint { 104 | fn serialize(&self, serializer: S) -> Result 105 | where 106 | S: Serializer, 107 | { 108 | C::serialize_point(&self.0, serializer) 109 | } 110 | } 111 | 112 | impl<'de, C: CSCurve> Deserialize<'de> for SerializablePoint { 113 | fn deserialize(deserializer: D) -> Result 114 | where 115 | D: Deserializer<'de>, 116 | { 117 | let affine = C::deserialize_point(deserializer)?; 118 | Ok(Self(affine)) 119 | } 120 | } 121 | 122 | /// Get the x coordinate of a point, as a scalar 123 | pub(crate) fn x_coordinate(point: &C::AffinePoint) -> C::Scalar { 124 | ::Uint>>::reduce_bytes(&point.x()) 125 | } 126 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | /// The security parameter we use for different constructions 2 | pub const SECURITY_PARAMETER: usize = 128; 3 | -------------------------------------------------------------------------------- /src/crypto.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use ck_meow::Meow; 4 | use rand_core::CryptoRngCore; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::serde::encode_writer; 8 | 9 | const COMMIT_LABEL: &[u8] = b"cait-sith v0.8.0 commitment"; 10 | const COMMIT_LEN: usize = 32; 11 | const RANDOMIZER_LEN: usize = 32; 12 | const HASH_LABEL: &[u8] = b"cait-sith v0.8.0 generic hash"; 13 | const HASH_LEN: usize = 32; 14 | 15 | struct MeowWriter<'a>(&'a mut Meow); 16 | 17 | impl<'a> MeowWriter<'a> { 18 | fn init(meow: &'a mut Meow) -> Self { 19 | meow.ad(&[], false); 20 | Self(meow) 21 | } 22 | } 23 | 24 | impl<'a> Write for MeowWriter<'a> { 25 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 26 | self.0.ad(buf, true); 27 | Ok(buf.len()) 28 | } 29 | 30 | fn flush(&mut self) -> std::io::Result<()> { 31 | Ok(()) 32 | } 33 | } 34 | 35 | /// Represents the randomizer used to make a commit hiding. 36 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 37 | pub struct Randomizer([u8; RANDOMIZER_LEN]); 38 | 39 | impl Randomizer { 40 | /// Generate a new randomizer value by sampling from an RNG. 41 | fn random(rng: &mut R) -> Self { 42 | let mut out = [0u8; RANDOMIZER_LEN]; 43 | rng.fill_bytes(&mut out); 44 | Self(out) 45 | } 46 | } 47 | 48 | impl AsRef<[u8]> for Randomizer { 49 | fn as_ref(&self) -> &[u8] { 50 | &self.0 51 | } 52 | } 53 | 54 | /// Represents a commitment to some value. 55 | /// 56 | /// This commit is both binding, in that it can't be opened to a different 57 | /// value than the one committed, and hiding, in that it hides the value 58 | /// committed inside (perfectly). 59 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 60 | pub struct Commitment([u8; COMMIT_LEN]); 61 | 62 | impl Commitment { 63 | fn compute(val: &T, r: &Randomizer) -> Self { 64 | let mut meow = Meow::new(COMMIT_LABEL); 65 | 66 | meow.ad(r.as_ref(), false); 67 | meow.meta_ad(b"start data", false); 68 | encode_writer(&mut MeowWriter::init(&mut meow), val); 69 | 70 | let mut out = [0u8; COMMIT_LEN]; 71 | meow.prf(&mut out, false); 72 | 73 | Commitment(out) 74 | } 75 | 76 | /// Check that a value and a randomizer match this commitment. 77 | #[must_use] 78 | pub fn check(&self, val: &T, r: &Randomizer) -> bool { 79 | let actual = Self::compute(val, r); 80 | *self == actual 81 | } 82 | } 83 | 84 | /// Commit to an arbitrary serializable value. 85 | /// 86 | /// This also returns a fresh randomizer, which is used to make sure that the 87 | /// commitment perfectly hides the value contained inside. 88 | /// 89 | /// This value will need to be sent when opening the commitment to allow 90 | /// others to check that the opening is valid. 91 | pub fn commit(rng: &mut R, val: &T) -> (Commitment, Randomizer) { 92 | let r = Randomizer::random(rng); 93 | let c = Commitment::compute(val, &r); 94 | (c, r) 95 | } 96 | 97 | /// The output of a generic hash function. 98 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 99 | pub struct Digest([u8; HASH_LEN]); 100 | 101 | impl AsRef<[u8]> for Digest { 102 | fn as_ref(&self) -> &[u8] { 103 | &self.0 104 | } 105 | } 106 | 107 | /// Hash some value to produce a short digest. 108 | pub fn hash(val: &T) -> Digest { 109 | let mut meow = Meow::new(HASH_LABEL); 110 | encode_writer(&mut MeowWriter::init(&mut meow), val); 111 | 112 | let mut out = [0u8; HASH_LEN]; 113 | meow.prf(&mut out, false); 114 | 115 | Digest(out) 116 | } 117 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cait-Sith is a novel threshold ECDSA protocol (and implementation), 2 | //! which is both simpler and substantially more performant than 3 | //! popular alternatives. 4 | //! 5 | //! The protocol supports arbitrary numbers of parties and thresholds. 6 | //! 7 | //! # Warning 8 | //! 9 | //! This is experimental cryptographic software, unless you're a cat with 10 | //! a megaphone on top of a giant Moogle I would exercise caution. 11 | //! 12 | //! - The protocol does not have a formal proof of security. 13 | //! - This library has not undergone any form of audit. 14 | //! 15 | //! # Design 16 | //! 17 | //! The main design principle of Cait-Sith is offloading as much work 18 | //! to a key-independent preprocessing phase as possible. 19 | //! The advantage of this approach is that this preprocessing phase can be conducted 20 | //! in advance, before a signature is needed, and the results of this phase 21 | //! can even be peformed before the key that you need to sign with is decided. 22 | //! 23 | //! One potential scenario where this is useful is when running a threshold 24 | //! custody service over many keys, where these preprocessing results 25 | //! can be performed, and then used on demand regardless of which keys 26 | //! end up being used more often. 27 | //! 28 | //! A detailed specification is available [in this repo](./docs), 29 | //! but we'll also give a bit of detail here. 30 | //! 31 | //! The core of Cait-Sith's design involves a *committed* Beaver triple. 32 | //! These are of the form: 33 | //! ```ignore 34 | //! ([a], [b], [c]), (A = a * G, B = b * G, C = c * G) 35 | //! ``` 36 | //! where `a, b, c` are scalars such that `a * b = c`, and are 37 | //! secret shared among several participants, so that no one knows their actual value. 38 | //! Furthermore, unlike standard Beaver triples, we also have a public commitment 39 | //! to the these secret values, which helps the online protocol. 40 | //! 41 | //! The flow of the protocol is first that the parties need a way to generate triples: 42 | //! 43 | //! - A setup protocol is run once, allowing parties to efficiently generate triples. 44 | //! - The parties can now generate an arbitrary number triples through a distributed protocol. 45 | //! 46 | //! Then, the parties need to generate a key pair so that they can sign messages: 47 | //! 48 | //! - The parties run a distributed key generation protocol to setup a new key pair, 49 | //! which can be used for many signatures. 50 | //! 51 | //! When the parties want to sign using a given key: 52 | //! 53 | //! - Using their shares of a private key, the parties can create a *presignature*, 54 | //! before knowing the message to sign. 55 | //! - Once they know this message, they can use the presignature to create a complete signature. 56 | //! 57 | //! It's important that presignatures and triples are **never** reused. 58 | //! 59 | //! ## API Design 60 | //! 61 | //! Internally, the API tries to be as simple as possible abstracting away 62 | //! as many details as possible into a simple interface. 63 | //! 64 | //! This interface just has two methods: 65 | //! ```ignore 66 | //! pub trait Protocol { 67 | //! type Output; 68 | //! 69 | //! fn poke(&mut self) -> Result, ProtocolError>; 70 | //! fn message(&mut self, from: Participant, data: MessageData); 71 | //! } 72 | //! ``` 73 | //! Given an instance of this trait, which represents a single party 74 | //! participating in a protocol, you can do two things: 75 | //! - You can provide a new message received from some other party. 76 | //! - You can "poke" the protocol to see if it has some kind of action it wants you to perform, or if an error happened. 77 | //! 78 | //! This action is either: 79 | //! - The protocol telling you it has finished, with a return value of type `Output`. 80 | //! - The protocol asking you to send a message to all other parties. 81 | //! - The protocol asking you to *privately* send a message to one party. 82 | //! - The protocol informing you that no more progress can be made until it receives new messages. 83 | //! 84 | //! In particular, details about rounds and message serialization are abstracted 85 | //! away, and all performed internally. 86 | //! In fact, the protocols aren't designed around "rounds", and can even have parallel 87 | //! threads of execution internally for some of the more complicated ones. 88 | //! # Generic Curves 89 | //! 90 | //! The library has support for generic curves and hashes. 91 | //! 92 | //! The support for generic curves is done through a custom `CSCurve` trait, 93 | //! which can be easily implemented for any curve from the 94 | //! RustCrypto [elliptic-curves](https://github.com/RustCrypto/elliptic-curves) 95 | //! suite of libraries. 96 | //! 97 | //! This crate also provides implementations of some existing curves behind features, 98 | //! as per the following table: 99 | //! 100 | //! | Curve | Feature | 101 | //! |-------|---------| 102 | //! |Secp256k1|`k256`| 103 | //! 104 | //! For supporting any message hash, the API requires the user to supply 105 | //! the hash of a message when signing as a scalar directly. 106 | //! 107 | //! # Shortcomings 108 | //! 109 | //! The protocol and its implementation do have a few known disadvantages at the moment: 110 | //! 111 | //! - The protocol does require generating triples in advance, but these can be generated without knowledge of the private key. 112 | //! - The protocol does not attempt to provide identifiable aborts. 113 | //! 114 | //! We also don't really intend to add identifiable aborts to Cait-Sith itself. 115 | //! While these can be desirable in certain situations, we aren't satisfied 116 | //! with the way the property of identifiable aborts is modeled currently, 117 | //! and are working on improvements to this model. 118 | mod compat; 119 | mod constants; 120 | mod crypto; 121 | mod keyshare; 122 | mod math; 123 | mod participants; 124 | mod presign; 125 | mod proofs; 126 | pub mod protocol; 127 | mod serde; 128 | mod sign; 129 | #[cfg(test)] 130 | mod test; 131 | pub mod triples; 132 | 133 | pub use compat::CSCurve; 134 | pub use keyshare::{keygen, refresh, reshare, KeygenOutput}; 135 | pub use presign::{presign, PresignArguments, PresignOutput}; 136 | pub use sign::{sign, FullSignature}; 137 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Index, Mul, MulAssign}; 2 | 3 | use elliptic_curve::{Field, Group}; 4 | use rand_core::CryptoRngCore; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | compat::CSCurve, 9 | serde::{deserialize_projective_points, serialize_projective_points}, 10 | }; 11 | 12 | /// Represents a polynomial with coefficients in the scalar field of the curve. 13 | #[derive(Debug, Clone, PartialEq, Eq)] 14 | pub struct Polynomial { 15 | /// The coefficients of our polynomial, from 0..size-1. 16 | coefficients: Vec, 17 | } 18 | 19 | impl Polynomial { 20 | /// Generate a random polynomial with a certain number of coefficients. 21 | pub fn random(rng: &mut impl CryptoRngCore, size: usize) -> Self { 22 | let coefficients = (0..size).map(|_| C::Scalar::random(&mut *rng)).collect(); 23 | Self { coefficients } 24 | } 25 | 26 | /// Extend a constant to a random polynomial of a certain size. 27 | /// 28 | /// This is useful if you want the polynomial to have a certain value, but 29 | /// otherwise be random. 30 | pub fn extend_random(rng: &mut impl CryptoRngCore, size: usize, constant: &C::Scalar) -> Self { 31 | let mut coefficients = Vec::with_capacity(size); 32 | coefficients.push(*constant); 33 | for _ in 1..size { 34 | coefficients.push(C::Scalar::random(&mut *rng)); 35 | } 36 | Self { coefficients } 37 | } 38 | 39 | /// Modify this polynomial by adding another polynomial. 40 | pub fn add_mut(&mut self, other: &Self) { 41 | let new_len = self.coefficients.len().max(other.coefficients.len()); 42 | self.coefficients.resize(new_len, C::Scalar::ZERO); 43 | self.coefficients 44 | .iter_mut() 45 | .zip(other.coefficients.iter()) 46 | .for_each(|(a, b)| *a += b); 47 | } 48 | 49 | /// Return the addition of this polynomial with another. 50 | pub fn add(&self, other: &Self) -> Self { 51 | let mut out = self.clone(); 52 | out.add_mut(other); 53 | out 54 | } 55 | 56 | /// Scale this polynomial in place by a field element. 57 | pub fn scale_mut(&mut self, scale: &C::Scalar) { 58 | self.coefficients.iter_mut().for_each(|a| *a *= scale); 59 | } 60 | 61 | /// Return the result of scaling this polynomial by a field element. 62 | pub fn scale(&self, scale: &C::Scalar) -> Self { 63 | let mut out = self.clone(); 64 | out.scale_mut(scale); 65 | out 66 | } 67 | 68 | /// Evaluate this polynomial at 0. 69 | /// 70 | /// This is much more efficient than evaluating at other points. 71 | pub fn evaluate_zero(&self) -> C::Scalar { 72 | self.coefficients.get(0).cloned().unwrap_or_default() 73 | } 74 | 75 | /// Set the zero value of this polynomial to a new scalar 76 | pub fn set_zero(&mut self, v: C::Scalar) { 77 | if self.coefficients.is_empty() { 78 | self.coefficients.push(v) 79 | } else { 80 | self.coefficients[0] = v 81 | } 82 | } 83 | 84 | /// Evaluate this polynomial at a specific point. 85 | pub fn evaluate(&self, x: &C::Scalar) -> C::Scalar { 86 | let mut out = C::Scalar::ZERO; 87 | for c in self.coefficients.iter().rev() { 88 | out = out * x + c; 89 | } 90 | out 91 | } 92 | 93 | /// Commit to this polynomial by acting on the generator 94 | pub fn commit(&self) -> GroupPolynomial { 95 | let coefficients = self 96 | .coefficients 97 | .iter() 98 | .map(|x| C::ProjectivePoint::generator() * x) 99 | .collect(); 100 | GroupPolynomial { coefficients } 101 | } 102 | 103 | /// Return the length of this polynomial. 104 | pub fn len(&self) -> usize { 105 | self.coefficients.len() 106 | } 107 | } 108 | 109 | impl Index for Polynomial { 110 | type Output = C::Scalar; 111 | 112 | fn index(&self, i: usize) -> &Self::Output { 113 | &self.coefficients[i] 114 | } 115 | } 116 | 117 | impl Add for &Polynomial { 118 | type Output = Polynomial; 119 | 120 | fn add(self, rhs: Self) -> Self::Output { 121 | self.add(rhs) 122 | } 123 | } 124 | 125 | impl AddAssign<&Self> for Polynomial { 126 | fn add_assign(&mut self, rhs: &Self) { 127 | self.add_mut(rhs) 128 | } 129 | } 130 | 131 | impl Mul<&C::Scalar> for &Polynomial { 132 | type Output = Polynomial; 133 | 134 | fn mul(self, rhs: &C::Scalar) -> Self::Output { 135 | self.scale(rhs) 136 | } 137 | } 138 | 139 | impl MulAssign<&C::Scalar> for Polynomial { 140 | fn mul_assign(&mut self, rhs: &C::Scalar) { 141 | self.scale_mut(rhs) 142 | } 143 | } 144 | 145 | /// A polynomial with group coefficients. 146 | #[derive(Debug, Clone, Deserialize, Serialize)] 147 | pub struct GroupPolynomial { 148 | #[serde( 149 | serialize_with = "serialize_projective_points::", 150 | deserialize_with = "deserialize_projective_points::" 151 | )] 152 | coefficients: Vec, 153 | } 154 | 155 | impl GroupPolynomial { 156 | /// Modify this polynomial by adding another one. 157 | pub fn add_mut(&mut self, other: &Self) { 158 | self.coefficients 159 | .iter_mut() 160 | .zip(other.coefficients.iter()) 161 | .for_each(|(a, b)| *a += b) 162 | } 163 | 164 | /// The result of adding this polynomial with another. 165 | pub fn add(&self, other: &Self) -> Self { 166 | let coefficients = self 167 | .coefficients 168 | .iter() 169 | .zip(other.coefficients.iter()) 170 | .map(|(a, b)| *a + *b) 171 | .collect(); 172 | Self { coefficients } 173 | } 174 | 175 | /// Evaluate this polynomial at 0. 176 | /// 177 | /// This is more efficient than evaluating at an arbitrary point. 178 | pub fn evaluate_zero(&self) -> C::ProjectivePoint { 179 | self.coefficients.get(0).cloned().unwrap_or_default() 180 | } 181 | 182 | /// Evaluate this polynomial at a specific value. 183 | pub fn evaluate(&self, x: &C::Scalar) -> C::ProjectivePoint { 184 | let mut out = C::ProjectivePoint::identity(); 185 | for c in self.coefficients.iter().rev() { 186 | out = out * x + c; 187 | } 188 | out 189 | } 190 | 191 | /// Set the zero value of this polynomial to a new group value. 192 | pub fn set_zero(&mut self, v: C::ProjectivePoint) { 193 | if self.coefficients.is_empty() { 194 | self.coefficients.push(v) 195 | } else { 196 | self.coefficients[0] = v 197 | } 198 | } 199 | 200 | /// Return the length of this polynomial. 201 | pub fn len(&self) -> usize { 202 | self.coefficients.len() 203 | } 204 | } 205 | 206 | impl Add for &GroupPolynomial { 207 | type Output = GroupPolynomial; 208 | 209 | fn add(self, rhs: Self) -> Self::Output { 210 | self.add(rhs) 211 | } 212 | } 213 | 214 | impl AddAssign<&Self> for GroupPolynomial { 215 | fn add_assign(&mut self, rhs: &Self) { 216 | self.add_mut(rhs) 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod test { 222 | use super::*; 223 | use k256::{Scalar, Secp256k1}; 224 | 225 | #[test] 226 | fn test_addition() { 227 | let mut f = Polynomial:: { 228 | coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], 229 | }; 230 | let g = Polynomial { 231 | coefficients: vec![Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)], 232 | }; 233 | let h = Polynomial { 234 | coefficients: vec![Scalar::from(2u32), Scalar::from(4u32), Scalar::from(3u32)], 235 | }; 236 | assert_eq!(&f + &g, h); 237 | f += &g; 238 | assert_eq!(f, h); 239 | } 240 | 241 | #[test] 242 | fn test_scaling() { 243 | let s = Scalar::from(2u32); 244 | let mut f = Polynomial:: { 245 | coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], 246 | }; 247 | let h = Polynomial { 248 | coefficients: vec![Scalar::from(2u32), Scalar::from(4u32)], 249 | }; 250 | assert_eq!(&f * &s, h); 251 | f *= &s; 252 | assert_eq!(f, h); 253 | } 254 | 255 | #[test] 256 | fn test_evaluation() { 257 | let f = Polynomial:: { 258 | coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], 259 | }; 260 | assert_eq!(f.evaluate(&Scalar::from(1u32)), Scalar::from(3u32)); 261 | assert_eq!(f.evaluate(&Scalar::from(2u32)), Scalar::from(5u32)); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/participants.rs: -------------------------------------------------------------------------------- 1 | //! This module holds some utilities for working with participants. 2 | //! 3 | //! Often you need to do things like, storing one item for each participant, 4 | //! or getting the field values corresponding to each participant, etc. 5 | //! This module tries to provide useful data structures for doing that. 6 | 7 | use std::{collections::HashMap, mem, ops::Index}; 8 | 9 | use elliptic_curve::Field; 10 | use serde::Serialize; 11 | 12 | use crate::{compat::CSCurve, protocol::Participant}; 13 | 14 | /// Represents a sorted list of participants. 15 | /// 16 | /// The advantage of this data structure is that it can be hashed in the protocol transcript, 17 | /// since everybody will agree on its order. 18 | #[derive(Clone, Debug, Serialize)] 19 | pub struct ParticipantList { 20 | participants: Vec, 21 | /// This maps each participant to their index in the vector above. 22 | #[serde(skip_serializing)] 23 | indices: HashMap, 24 | } 25 | 26 | impl ParticipantList { 27 | // For optimization reasons, another method needs this. 28 | fn new_vec(mut participants: Vec) -> Option { 29 | participants.sort(); 30 | 31 | let indices: HashMap<_, _> = participants 32 | .iter() 33 | .enumerate() 34 | .map(|(p, x)| (*x, p)) 35 | .collect(); 36 | 37 | if indices.len() < participants.len() { 38 | return None; 39 | } 40 | 41 | Some(Self { 42 | participants, 43 | indices, 44 | }) 45 | } 46 | 47 | /// Create a participant list from a slice of participants. 48 | /// 49 | /// This will return None if the participants have duplicates. 50 | pub fn new(participants: &[Participant]) -> Option { 51 | Self::new_vec(participants.to_owned()) 52 | } 53 | 54 | pub fn len(&self) -> usize { 55 | self.participants.len() 56 | } 57 | 58 | /// Check if this list has a given participant. 59 | pub fn contains(&self, participant: Participant) -> bool { 60 | self.indices.contains_key(&participant) 61 | } 62 | 63 | /// Iterate over the other participants 64 | pub fn others(&self, me: Participant) -> impl Iterator + '_ { 65 | self.participants.iter().filter(move |x| **x != me).copied() 66 | } 67 | 68 | /// Return the index of a given participant. 69 | /// 70 | /// Basically, the order they appear in a sorted list 71 | pub fn index(&self, participant: Participant) -> usize { 72 | self.indices[&participant] 73 | } 74 | 75 | /// Get the lagrange coefficient for a participant, relative to this list. 76 | pub fn lagrange(&self, p: Participant) -> C::Scalar { 77 | let p_scalar = p.scalar::(); 78 | 79 | let mut top = C::Scalar::ONE; 80 | let mut bot = C::Scalar::ONE; 81 | for q in &self.participants { 82 | if p == *q { 83 | continue; 84 | } 85 | let q_scalar = q.scalar::(); 86 | top *= q_scalar; 87 | bot *= q_scalar - p_scalar; 88 | } 89 | 90 | top * bot.invert().unwrap() 91 | } 92 | 93 | /// Return the intersection of this list with another list. 94 | pub fn intersection(&self, others: &ParticipantList) -> Self { 95 | let mut out = Vec::new(); 96 | for &p in &self.participants { 97 | if others.contains(p) { 98 | out.push(p); 99 | } 100 | } 101 | // We know that no duplicates will be created, so unwrapping is safe 102 | Self::new_vec(out).unwrap() 103 | } 104 | } 105 | 106 | impl From for Vec { 107 | fn from(val: ParticipantList) -> Self { 108 | val.participants 109 | } 110 | } 111 | 112 | /// A map from participants to elements. 113 | /// 114 | /// The idea is that you have one element for each participant. 115 | #[derive(Debug, Clone, Serialize)] 116 | pub struct ParticipantMap<'a, T> { 117 | #[serde(skip_serializing)] 118 | participants: &'a ParticipantList, 119 | data: Vec>, 120 | #[serde(skip_serializing)] 121 | count: usize, 122 | } 123 | 124 | impl<'a, T> ParticipantMap<'a, T> { 125 | /// Create a new map from a list of participants. 126 | /// 127 | /// This map only lives as long as that list of participants. 128 | pub fn new(participants: &'a ParticipantList) -> Self { 129 | // We could also require a T: Clone bound instead of doing this initialization manually. 130 | let size = participants.participants.len(); 131 | let mut data = Vec::with_capacity(size); 132 | for _ in 0..size { 133 | data.push(None); 134 | } 135 | 136 | Self { 137 | participants, 138 | data, 139 | count: 0, 140 | } 141 | } 142 | 143 | /// Check if this map is full, i.e. if every participant has put something in. 144 | pub fn full(&self) -> bool { 145 | self.count == self.data.len() 146 | } 147 | 148 | /// Place the data for a participant in this map. 149 | /// 150 | /// This will do nothing if the participant is unknown, or already has a value 151 | pub fn put(&mut self, participant: Participant, data: T) { 152 | let i = self.participants.indices.get(&participant); 153 | if i.is_none() { 154 | return; 155 | } 156 | let i = *i.unwrap(); 157 | 158 | if self.data[i].is_some() { 159 | return; 160 | } 161 | 162 | self.data[i] = Some(data); 163 | self.count += 1; 164 | } 165 | } 166 | 167 | impl<'a, T> Index for ParticipantMap<'a, T> { 168 | type Output = T; 169 | 170 | fn index(&self, index: Participant) -> &Self::Output { 171 | self.data[self.participants.index(index)].as_ref().unwrap() 172 | } 173 | } 174 | 175 | /// A way to count participants. 176 | /// 177 | /// This is used when you want to process a message from each participant only once. 178 | /// This datastructure will let you put a participant in, and then tell you if this 179 | /// participant was newly inserted or not, allowing you to thus process the 180 | /// first message received from them. 181 | #[derive(Debug, Clone)] 182 | pub struct ParticipantCounter<'a> { 183 | participants: &'a ParticipantList, 184 | seen: Vec, 185 | counter: usize, 186 | } 187 | 188 | impl<'a> ParticipantCounter<'a> { 189 | /// Create a new participant counter from the list of all participants. 190 | pub fn new(participants: &'a ParticipantList) -> Self { 191 | Self { 192 | participants, 193 | seen: vec![false; participants.len()], 194 | counter: participants.len(), 195 | } 196 | } 197 | 198 | /// Put a new participant in this counter. 199 | /// 200 | /// This will return true if the participant was added, or false otherwise. 201 | /// 202 | /// The participant may not have been added because: 203 | /// - The participant is not part of our participant list. 204 | /// - The participant has already been added. 205 | /// 206 | /// This can be checked to not process a message twice. 207 | pub fn put(&mut self, participant: Participant) -> bool { 208 | let i = match self.participants.indices.get(&participant) { 209 | None => return false, 210 | Some(&i) => i, 211 | }; 212 | 213 | // Need the old value to be false. 214 | let inserted = !mem::replace(&mut self.seen[i], true); 215 | if inserted { 216 | self.counter -= 1; 217 | } 218 | inserted 219 | } 220 | 221 | /// Clear the contents of this counter. 222 | pub fn clear(&mut self) { 223 | for x in &mut self.seen { 224 | *x = false 225 | } 226 | self.counter = self.participants.len(); 227 | } 228 | 229 | /// Check if this counter contains all participants 230 | pub fn full(&self) -> bool { 231 | self.counter == 0 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/presign.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{Field, Group, ScalarPrimitive}; 2 | 3 | use crate::compat::CSCurve; 4 | use crate::participants::ParticipantCounter; 5 | use crate::protocol::internal::{make_protocol, Context, SharedChannel}; 6 | use crate::protocol::{InitializationError, Protocol}; 7 | use crate::triples::{TriplePub, TripleShare}; 8 | use crate::KeygenOutput; 9 | use crate::{ 10 | participants::ParticipantList, 11 | protocol::{Participant, ProtocolError}, 12 | }; 13 | 14 | /// The output of the presigning protocol. 15 | /// 16 | /// This output is basically all the parts of the signature that we can perform 17 | /// without knowing the message. 18 | #[derive(Debug, Clone)] 19 | pub struct PresignOutput { 20 | /// The public nonce commitment. 21 | pub big_r: C::AffinePoint, 22 | /// Our share of the nonce value. 23 | pub k: C::Scalar, 24 | /// Our share of the sigma value. 25 | pub sigma: C::Scalar, 26 | } 27 | 28 | /// The arguments needed to create a presignature. 29 | #[derive(Debug, Clone)] 30 | pub struct PresignArguments { 31 | /// The first triple's public information, and our share. 32 | pub triple0: (TripleShare, TriplePub), 33 | /// Ditto, for the second triple. 34 | pub triple1: (TripleShare, TriplePub), 35 | /// The output of key generation, i.e. our share of the secret key, and the public key. 36 | pub keygen_out: KeygenOutput, 37 | /// The desired threshold for the presignature, which must match the original threshold 38 | pub threshold: usize, 39 | } 40 | 41 | async fn do_presign( 42 | mut chan: SharedChannel, 43 | participants: ParticipantList, 44 | me: Participant, 45 | args: PresignArguments, 46 | ) -> Result, ProtocolError> { 47 | // Spec 1.2 + 1.3 48 | let big_k: C::ProjectivePoint = args.triple0.1.big_a.into(); 49 | let big_d = args.triple0.1.big_b; 50 | let big_kd = args.triple0.1.big_c; 51 | 52 | let big_x: C::ProjectivePoint = args.keygen_out.public_key.into(); 53 | 54 | let big_a: C::ProjectivePoint = args.triple1.1.big_a.into(); 55 | let big_b: C::ProjectivePoint = args.triple1.1.big_b.into(); 56 | 57 | let lambda = participants.lagrange::(me); 58 | 59 | let k_i = args.triple0.0.a; 60 | let k_prime_i = lambda * k_i; 61 | let kd_i: C::Scalar = lambda * args.triple0.0.c; 62 | 63 | let a_i = args.triple1.0.a; 64 | let b_i = args.triple1.0.b; 65 | let c_i = args.triple1.0.c; 66 | let a_prime_i = lambda * a_i; 67 | let b_prime_i = lambda * b_i; 68 | 69 | let x_prime_i = lambda * args.keygen_out.private_share; 70 | 71 | // Spec 1.4 72 | let wait0 = chan.next_waitpoint(); 73 | { 74 | let kd_i: ScalarPrimitive = kd_i.into(); 75 | chan.send_many(wait0, &kd_i).await; 76 | } 77 | 78 | // Spec 1.9 79 | let ka_i: C::Scalar = k_prime_i + a_prime_i; 80 | let xb_i: C::Scalar = x_prime_i + b_prime_i; 81 | 82 | // Spec 1.10 83 | let wait1 = chan.next_waitpoint(); 84 | { 85 | let ka_i: ScalarPrimitive = ka_i.into(); 86 | let xb_i: ScalarPrimitive = xb_i.into(); 87 | chan.send_many(wait1, &(ka_i, xb_i)).await; 88 | } 89 | 90 | // Spec 2.1 and 2.2 91 | let mut kd = kd_i; 92 | let mut seen = ParticipantCounter::new(&participants); 93 | seen.put(me); 94 | while !seen.full() { 95 | let (from, kd_j): (_, ScalarPrimitive) = chan.recv(wait0).await?; 96 | if !seen.put(from) { 97 | continue; 98 | } 99 | kd += C::Scalar::from(kd_j); 100 | } 101 | 102 | // Spec 2.3 103 | if big_kd != (C::ProjectivePoint::generator() * kd).into() { 104 | return Err(ProtocolError::AssertionFailed( 105 | "received incorrect shares of kd".to_string(), 106 | )); 107 | } 108 | 109 | // Spec 2.4 and 2.5 110 | let mut ka = ka_i; 111 | let mut xb = xb_i; 112 | seen.clear(); 113 | seen.put(me); 114 | while !seen.full() { 115 | let (from, (ka_j, xb_j)): (_, (ScalarPrimitive, ScalarPrimitive)) = 116 | chan.recv(wait1).await?; 117 | if !seen.put(from) { 118 | continue; 119 | } 120 | ka += C::Scalar::from(ka_j); 121 | xb += C::Scalar::from(xb_j); 122 | } 123 | 124 | // Spec 2.6 125 | if (C::ProjectivePoint::generator() * ka != big_k + big_a) 126 | || (C::ProjectivePoint::generator() * xb != big_x + big_b) 127 | { 128 | return Err(ProtocolError::AssertionFailed( 129 | "received incorrect shares of additive triple phase.".to_string(), 130 | )); 131 | } 132 | 133 | // Spec 2.7 134 | let kd_inv: Option = kd.invert().into(); 135 | let kd_inv = 136 | kd_inv.ok_or_else(|| ProtocolError::AssertionFailed("failed to invert kd".to_string()))?; 137 | let big_r = (C::ProjectivePoint::from(big_d) * kd_inv).into(); 138 | 139 | // Spec 2.8 140 | let sigma_i = ka * args.keygen_out.private_share - xb * a_i + c_i; 141 | 142 | Ok(PresignOutput { 143 | big_r, 144 | k: k_i, 145 | sigma: sigma_i, 146 | }) 147 | } 148 | 149 | /// The presignature protocol. 150 | /// 151 | /// This is the first phase of performing a signature, in which we perform 152 | /// all the work we can do without yet knowing the message to be signed. 153 | /// 154 | /// This work does depend on the private key though, and it's crucial 155 | /// that a presignature is never used. 156 | pub fn presign( 157 | participants: &[Participant], 158 | me: Participant, 159 | args: PresignArguments, 160 | ) -> Result>, InitializationError> { 161 | if participants.len() < 2 { 162 | return Err(InitializationError::BadParameters(format!( 163 | "participant count cannot be < 2, found: {}", 164 | participants.len() 165 | ))); 166 | }; 167 | // Spec 1.1 168 | if args.threshold > participants.len() { 169 | return Err(InitializationError::BadParameters( 170 | "threshold must be <= participant count".to_string(), 171 | )); 172 | } 173 | // NOTE: We omit the check that the new participant set was present for 174 | // the triple generation, because presumably they need to have been present 175 | // in order to have shares. 176 | 177 | // Also check that we have enough participants to reconstruct shares. 178 | if args.threshold != args.triple0.1.threshold || args.threshold != args.triple1.1.threshold { 179 | return Err(InitializationError::BadParameters( 180 | "New threshold must match the threshold of both triples".to_string(), 181 | )); 182 | } 183 | 184 | let participants = ParticipantList::new(participants).ok_or_else(|| { 185 | InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) 186 | })?; 187 | 188 | let ctx = Context::new(); 189 | let fut = do_presign(ctx.shared_channel(), participants, me, args); 190 | Ok(make_protocol(ctx, fut)) 191 | } 192 | 193 | #[cfg(test)] 194 | mod test { 195 | use super::*; 196 | use rand_core::OsRng; 197 | 198 | use crate::{math::Polynomial, protocol::run_protocol, triples}; 199 | 200 | use k256::{ProjectivePoint, Secp256k1}; 201 | 202 | #[test] 203 | fn test_presign() { 204 | let participants = vec![ 205 | Participant::from(0u32), 206 | Participant::from(1u32), 207 | Participant::from(2u32), 208 | Participant::from(3u32), 209 | ]; 210 | let original_threshold = 2; 211 | let f = Polynomial::::random(&mut OsRng, original_threshold); 212 | let big_x = (ProjectivePoint::GENERATOR * f.evaluate_zero()).to_affine(); 213 | let threshold = 2; 214 | 215 | let (triple0_pub, triple0_shares) = 216 | triples::deal(&mut OsRng, &participants, original_threshold); 217 | let (triple1_pub, triple1_shares) = 218 | triples::deal(&mut OsRng, &participants, original_threshold); 219 | 220 | #[allow(clippy::type_complexity)] 221 | let mut protocols: Vec<( 222 | Participant, 223 | Box>>, 224 | )> = Vec::with_capacity(participants.len()); 225 | 226 | for ((p, triple0), triple1) in participants 227 | .iter() 228 | .take(3) 229 | .zip(triple0_shares.into_iter()) 230 | .zip(triple1_shares.into_iter()) 231 | { 232 | let protocol = presign( 233 | &participants[..3], 234 | *p, 235 | PresignArguments { 236 | triple0: (triple0, triple0_pub.clone()), 237 | triple1: (triple1, triple1_pub.clone()), 238 | keygen_out: KeygenOutput { 239 | private_share: f.evaluate(&p.scalar::()), 240 | public_key: big_x, 241 | }, 242 | threshold, 243 | }, 244 | ); 245 | assert!(protocol.is_ok()); 246 | let protocol = protocol.unwrap(); 247 | protocols.push((*p, Box::new(protocol))); 248 | } 249 | 250 | let result = run_protocol(protocols); 251 | assert!(result.is_ok()); 252 | let result = result.unwrap(); 253 | 254 | assert!(result.len() == 3); 255 | assert_eq!(result[0].1.big_r, result[1].1.big_r); 256 | assert_eq!(result[1].1.big_r, result[2].1.big_r); 257 | 258 | let big_k = result[2].1.big_r; 259 | 260 | let participants = vec![result[0].0, result[1].0]; 261 | let k_shares = vec![result[0].1.k, result[1].1.k]; 262 | let sigma_shares = vec![result[0].1.sigma, result[1].1.sigma]; 263 | let p_list = ParticipantList::new(&participants).unwrap(); 264 | let k = p_list.lagrange::(participants[0]) * k_shares[0] 265 | + p_list.lagrange::(participants[1]) * k_shares[1]; 266 | assert_eq!(ProjectivePoint::GENERATOR * k.invert().unwrap(), big_k); 267 | let sigma = p_list.lagrange::(participants[0]) * sigma_shares[0] 268 | + p_list.lagrange::(participants[1]) * sigma_shares[1]; 269 | assert_eq!(sigma, k * f.evaluate_zero()); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/proofs/dlog.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{Field, Group}; 2 | use magikitten::Transcript; 3 | use rand_core::CryptoRngCore; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | compat::{CSCurve, SerializablePoint}, 8 | serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, 9 | }; 10 | 11 | /// The label we use for hashing the statement. 12 | const STATEMENT_LABEL: &[u8] = b"dlog proof statement"; 13 | /// The label we use for hashing the first prover message. 14 | const COMMITMENT_LABEL: &[u8] = b"dlog proof commitment"; 15 | /// The label we use for generating the challenge. 16 | const CHALLENGE_LABEL: &[u8] = b"dlog proof challenge"; 17 | 18 | /// The public statement for this proof. 19 | /// 20 | /// This statement claims knowledge of the discrete logarithm of some point. 21 | #[derive(Debug, Clone, Copy, Serialize)] 22 | pub struct Statement<'a, C: CSCurve> { 23 | #[serde(serialize_with = "serialize_projective_point::")] 24 | pub public: &'a C::ProjectivePoint, 25 | } 26 | 27 | impl<'a, C: CSCurve> Statement<'a, C> { 28 | /// Calculate the homomorphism we want to prove things about. 29 | fn phi(&self, x: &C::Scalar) -> C::ProjectivePoint { 30 | C::ProjectivePoint::generator() * x 31 | } 32 | } 33 | 34 | /// The private witness for this proof. 35 | /// 36 | /// This holds the scalar the prover needs to know. 37 | #[derive(Clone, Copy)] 38 | pub struct Witness<'a, C: CSCurve> { 39 | pub x: &'a C::Scalar, 40 | } 41 | 42 | /// Represents a proof of the statement. 43 | #[derive(Debug, Clone, Serialize, Deserialize)] 44 | pub struct Proof { 45 | #[serde( 46 | serialize_with = "serialize_scalar::", 47 | deserialize_with = "deserialize_scalar::" 48 | )] 49 | e: C::Scalar, 50 | #[serde( 51 | serialize_with = "serialize_scalar::", 52 | deserialize_with = "deserialize_scalar::" 53 | )] 54 | s: C::Scalar, 55 | } 56 | 57 | /// Prove that a witness satisfies a given statement. 58 | /// 59 | /// We need some randomness for the proof, and also a transcript, which is 60 | /// used for the Fiat-Shamir transform. 61 | pub fn prove<'a, C: CSCurve>( 62 | rng: &mut impl CryptoRngCore, 63 | transcript: &mut Transcript, 64 | statement: Statement<'a, C>, 65 | witness: Witness<'a, C>, 66 | ) -> Proof { 67 | transcript.message(STATEMENT_LABEL, &encode(&statement)); 68 | 69 | let k = C::Scalar::random(rng); 70 | let big_k = statement.phi(&k); 71 | 72 | transcript.message( 73 | COMMITMENT_LABEL, 74 | &encode(&SerializablePoint::::from_projective(&big_k)), 75 | ); 76 | 77 | let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); 78 | 79 | let s = k + e * witness.x; 80 | Proof { e, s } 81 | } 82 | 83 | /// Verify that a proof attesting to the validity of some statement. 84 | /// 85 | /// We use a transcript in order to verify the Fiat-Shamir transformation. 86 | #[must_use] 87 | pub fn verify( 88 | transcript: &mut Transcript, 89 | statement: Statement<'_, C>, 90 | proof: &Proof, 91 | ) -> bool { 92 | let statement_data = encode(&statement); 93 | transcript.message(STATEMENT_LABEL, &statement_data); 94 | 95 | let big_k: C::ProjectivePoint = statement.phi(&proof.s) - *statement.public * proof.e; 96 | 97 | transcript.message( 98 | COMMITMENT_LABEL, 99 | &encode(&SerializablePoint::::from_projective(&big_k)), 100 | ); 101 | 102 | let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); 103 | 104 | e == proof.e 105 | } 106 | 107 | #[cfg(test)] 108 | mod test { 109 | use rand_core::OsRng; 110 | 111 | use super::*; 112 | use k256::{ProjectivePoint, Scalar, Secp256k1}; 113 | 114 | #[test] 115 | fn test_valid_proof_verifies() { 116 | let x = Scalar::generate_biased(&mut OsRng); 117 | 118 | let statement = Statement:: { 119 | public: &(ProjectivePoint::GENERATOR * x), 120 | }; 121 | let witness = Witness { x: &x }; 122 | 123 | let transcript = Transcript::new(b"protocol"); 124 | 125 | let proof = prove( 126 | &mut OsRng, 127 | &mut transcript.forked(b"party", &[1]), 128 | statement, 129 | witness, 130 | ); 131 | 132 | let ok = verify(&mut transcript.forked(b"party", &[1]), statement, &proof); 133 | 134 | assert!(ok); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/proofs/dlogeq.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{Field, Group}; 2 | use magikitten::Transcript; 3 | use rand_core::CryptoRngCore; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | compat::{CSCurve, SerializablePoint}, 8 | serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, 9 | }; 10 | 11 | /// The label we use for hashing the statement. 12 | const STATEMENT_LABEL: &[u8] = b"dlogeq proof statement"; 13 | /// The label we use for hashing the first prover message. 14 | const COMMITMENT_LABEL: &[u8] = b"dlogeq proof commitment"; 15 | /// The label we use for generating the challenge. 16 | const CHALLENGE_LABEL: &[u8] = b"dlogeq proof challenge"; 17 | 18 | /// The public statement for this proof. 19 | /// 20 | /// This statement claims knowledge of a scalar that's the discrete logarithm 21 | /// of one point under the standard generator, and of another point under an alternate generator. 22 | #[derive(Debug, Clone, Copy, Serialize)] 23 | pub struct Statement<'a, C: CSCurve> { 24 | #[serde(serialize_with = "serialize_projective_point::")] 25 | pub public0: &'a C::ProjectivePoint, 26 | #[serde(serialize_with = "serialize_projective_point::")] 27 | pub generator1: &'a C::ProjectivePoint, 28 | #[serde(serialize_with = "serialize_projective_point::")] 29 | pub public1: &'a C::ProjectivePoint, 30 | } 31 | 32 | impl<'a, C: CSCurve> Statement<'a, C> { 33 | /// Calculate the homomorphism we want to prove things about. 34 | fn phi(&self, x: &C::Scalar) -> (C::ProjectivePoint, C::ProjectivePoint) { 35 | (C::ProjectivePoint::generator() * x, *self.generator1 * x) 36 | } 37 | } 38 | 39 | /// The private witness for this proof. 40 | /// 41 | /// This holds the scalar the prover needs to know. 42 | #[derive(Clone, Copy)] 43 | pub struct Witness<'a, C: CSCurve> { 44 | pub x: &'a C::Scalar, 45 | } 46 | 47 | /// Represents a proof of the statement. 48 | #[derive(Debug, Clone, Serialize, Deserialize)] 49 | pub struct Proof { 50 | #[serde( 51 | serialize_with = "serialize_scalar::", 52 | deserialize_with = "deserialize_scalar::" 53 | )] 54 | e: C::Scalar, 55 | #[serde( 56 | serialize_with = "serialize_scalar::", 57 | deserialize_with = "deserialize_scalar::" 58 | )] 59 | s: C::Scalar, 60 | } 61 | 62 | /// Prove that a witness satisfies a given statement. 63 | /// 64 | /// We need some randomness for the proof, and also a transcript, which is 65 | /// used for the Fiat-Shamir transform. 66 | pub fn prove<'a, C: CSCurve>( 67 | rng: &mut impl CryptoRngCore, 68 | transcript: &mut Transcript, 69 | statement: Statement<'a, C>, 70 | witness: Witness<'a, C>, 71 | ) -> Proof { 72 | transcript.message(STATEMENT_LABEL, &encode(&statement)); 73 | 74 | let k = C::Scalar::random(rng); 75 | let big_k = statement.phi(&k); 76 | 77 | transcript.message( 78 | COMMITMENT_LABEL, 79 | &encode(&( 80 | SerializablePoint::::from_projective(&big_k.0), 81 | SerializablePoint::::from_projective(&big_k.1), 82 | )), 83 | ); 84 | 85 | let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); 86 | 87 | let s = k + e * witness.x; 88 | Proof { e, s } 89 | } 90 | 91 | /// Verify that a proof attesting to the validity of some statement. 92 | /// 93 | /// We use a transcript in order to verify the Fiat-Shamir transformation. 94 | #[must_use] 95 | pub fn verify( 96 | transcript: &mut Transcript, 97 | statement: Statement<'_, C>, 98 | proof: &Proof, 99 | ) -> bool { 100 | let statement_data = encode(&statement); 101 | transcript.message(STATEMENT_LABEL, &statement_data); 102 | 103 | let (phi0, phi1) = statement.phi(&proof.s); 104 | let big_k0 = phi0 - *statement.public0 * proof.e; 105 | let big_k1 = phi1 - *statement.public1 * proof.e; 106 | 107 | transcript.message( 108 | COMMITMENT_LABEL, 109 | &encode(&( 110 | SerializablePoint::::from_projective(&big_k0), 111 | SerializablePoint::::from_projective(&big_k1), 112 | )), 113 | ); 114 | 115 | let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); 116 | 117 | e == proof.e 118 | } 119 | 120 | #[cfg(test)] 121 | mod test { 122 | use rand_core::OsRng; 123 | 124 | use super::*; 125 | 126 | use k256::{ProjectivePoint, Scalar, Secp256k1}; 127 | 128 | #[test] 129 | fn test_valid_proof_verifies() { 130 | let x = Scalar::generate_biased(&mut OsRng); 131 | 132 | let big_h = ProjectivePoint::GENERATOR * Scalar::generate_biased(&mut OsRng); 133 | let statement = Statement:: { 134 | public0: &(ProjectivePoint::GENERATOR * x), 135 | generator1: &big_h, 136 | public1: &(big_h * x), 137 | }; 138 | let witness = Witness { x: &x }; 139 | 140 | let transcript = Transcript::new(b"protocol"); 141 | 142 | let proof = prove( 143 | &mut OsRng, 144 | &mut transcript.forked(b"party", &[1]), 145 | statement, 146 | witness, 147 | ); 148 | 149 | let ok = verify(&mut transcript.forked(b"party", &[1]), statement, &proof); 150 | 151 | assert!(ok); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/proofs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dlog; 2 | pub mod dlogeq; 3 | -------------------------------------------------------------------------------- /src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides abstractions for working with protocols. 2 | //! 3 | //! This library tries to abstract away as much of the internal machinery 4 | //! of protocols as much as possible. To use a protocol, you just need to be able 5 | //! to deliver messages to and from that protocol, and eventually it will produce 6 | //! a result, without you having to worry about how many rounds it has, or how 7 | //! to serialize the emssages it produces. 8 | use core::fmt; 9 | use std::{collections::HashMap, error}; 10 | 11 | use ::serde::Serialize; 12 | 13 | use crate::compat::CSCurve; 14 | 15 | /// Represents an error which can happen when running a protocol. 16 | #[derive(Debug)] 17 | pub enum ProtocolError { 18 | /// Some assertion in the protocol failed. 19 | AssertionFailed(String), 20 | /// Some generic error happened. 21 | Other(Box), 22 | } 23 | 24 | impl fmt::Display for ProtocolError { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | ProtocolError::Other(e) => write!(f, "{}", e), 28 | ProtocolError::AssertionFailed(e) => write!(f, "assertion failed {}", e), 29 | } 30 | } 31 | } 32 | 33 | impl error::Error for ProtocolError {} 34 | 35 | impl From> for ProtocolError { 36 | fn from(e: Box) -> Self { 37 | Self::Other(e) 38 | } 39 | } 40 | 41 | /// Represents an error which can happen when *initializing* a protocol. 42 | /// 43 | /// These are related to bad parameters for the protocol, and things like that. 44 | /// 45 | /// These are usually more recoverable than other protocol errors. 46 | #[derive(Debug)] 47 | pub enum InitializationError { 48 | BadParameters(String), 49 | } 50 | 51 | impl fmt::Display for InitializationError { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | match self { 54 | InitializationError::BadParameters(s) => write!(f, "bad parameters: {}", s), 55 | } 56 | } 57 | } 58 | 59 | impl error::Error for InitializationError {} 60 | 61 | /// Represents a participant in the protocol. 62 | /// 63 | /// Each participant should be uniquely identified by some number, which this 64 | /// struct holds. In our case, we use a `u32`, which is enough for billions of 65 | /// participants. That said, you won't actually be able to make the protocols 66 | /// work with billions of users. 67 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Hash)] 68 | pub struct Participant(u32); 69 | 70 | impl Participant { 71 | /// Return this participant as little endian bytes. 72 | pub fn bytes(&self) -> [u8; 4] { 73 | self.0.to_le_bytes() 74 | } 75 | 76 | /// Return the scalar associated with this participant. 77 | pub fn scalar(&self) -> C::Scalar { 78 | C::Scalar::from(self.0 as u64 + 1) 79 | } 80 | } 81 | 82 | impl From for u32 { 83 | fn from(p: Participant) -> Self { 84 | p.0 85 | } 86 | } 87 | 88 | impl From for Participant { 89 | fn from(x: u32) -> Self { 90 | Participant(x) 91 | } 92 | } 93 | 94 | /// Represents the data making up a message. 95 | /// 96 | /// We choose to just represent messages as opaque vectors of bytes, with all 97 | /// the serialization logic handled internally. 98 | pub type MessageData = Vec; 99 | 100 | /// Represents an action by a participant in the protocol. 101 | /// 102 | /// The basic flow is that each participant receives messages from other participants, 103 | /// and then reacts with some kind of action. 104 | /// 105 | /// This action can consist of sending a message, doing nothing, etc. 106 | /// 107 | /// Eventually, the participant returns a value, ending the protocol. 108 | #[derive(Debug, Clone)] 109 | pub enum Action { 110 | /// Don't do anything. 111 | Wait, 112 | /// Send a message to all other participants. 113 | /// 114 | /// Participants *never* sends messages to themselves. 115 | SendMany(MessageData), 116 | /// Send a private message to another participant. 117 | /// 118 | /// It's imperactive that only this participant can read this message, 119 | /// so you might want to use some form of encryption. 120 | SendPrivate(Participant, MessageData), 121 | /// End the protocol by returning a value. 122 | Return(T), 123 | } 124 | 125 | /// A trait for protocols. 126 | /// 127 | /// Basically, this represents a struct for the behavior of a single participant 128 | /// in a protocol. The idea is that the computation of that participant is driven 129 | /// mainly by receiving messages from other participants. 130 | pub trait Protocol { 131 | type Output; 132 | 133 | /// Poke the protocol, receiving a new action. 134 | /// 135 | /// The idea is that the protocol should be poked until it returns an error, 136 | /// or it returns an action with a return value, or it returns a wait action. 137 | /// 138 | /// Upon returning a wait action, that protocol will not advance any further 139 | /// until a new message arrives. 140 | fn poke(&mut self) -> Result, ProtocolError>; 141 | 142 | /// Inform the protocol of a new message. 143 | fn message(&mut self, from: Participant, data: MessageData); 144 | } 145 | 146 | /// Run a protocol to completion, synchronously. 147 | /// 148 | /// This works by executing each participant in order. 149 | /// 150 | /// The reason this function exists is as a convenient testing utility. 151 | /// In practice each protocol participant is likely running on a different machine, 152 | /// and so orchestrating the protocol would happen differently. 153 | pub fn run_protocol( 154 | mut ps: Vec<(Participant, Box>)>, 155 | ) -> Result, ProtocolError> { 156 | let indices: HashMap = 157 | ps.iter().enumerate().map(|(i, (p, _))| (*p, i)).collect(); 158 | 159 | let size = ps.len(); 160 | let mut out = Vec::with_capacity(size); 161 | while out.len() < size { 162 | for i in 0..size { 163 | while { 164 | let action = ps[i].1.poke()?; 165 | match action { 166 | Action::Wait => false, 167 | Action::SendMany(m) => { 168 | for j in 0..size { 169 | if i == j { 170 | continue; 171 | } 172 | let from = ps[i].0; 173 | ps[j].1.message(from, m.clone()); 174 | } 175 | true 176 | } 177 | Action::SendPrivate(to, m) => { 178 | let from = ps[i].0; 179 | ps[indices[&to]].1.message(from, m); 180 | true 181 | } 182 | Action::Return(r) => { 183 | out.push((ps[i].0, r)); 184 | false 185 | } 186 | } 187 | } {} 188 | } 189 | } 190 | 191 | Ok(out) 192 | } 193 | 194 | /// Like [run_protocol()], except for just two parties. 195 | /// 196 | /// This is more useful for testing two party protocols with assymetric results, 197 | /// since the return types for the two protocols can be different. 198 | pub(crate) fn run_two_party_protocol( 199 | p0: Participant, 200 | p1: Participant, 201 | prot0: &mut dyn Protocol, 202 | prot1: &mut dyn Protocol, 203 | ) -> Result<(T0, T1), ProtocolError> { 204 | let mut active0 = true; 205 | 206 | let mut out0 = None; 207 | let mut out1 = None; 208 | 209 | while out0.is_none() || out1.is_none() { 210 | if active0 { 211 | let action = prot0.poke()?; 212 | match action { 213 | Action::Wait => active0 = false, 214 | Action::SendMany(m) => prot1.message(p0, m), 215 | Action::SendPrivate(to, m) if to == p1 => { 216 | prot1.message(p0, m); 217 | } 218 | Action::Return(out) => out0 = Some(out), 219 | // Ignore other actions, which means sending private messages to other people. 220 | _ => {} 221 | } 222 | } else { 223 | let action = prot1.poke()?; 224 | match action { 225 | Action::Wait => active0 = true, 226 | Action::SendMany(m) => prot0.message(p1, m), 227 | Action::SendPrivate(to, m) if to == p0 => { 228 | prot0.message(p1, m); 229 | } 230 | Action::Return(out) => out1 = Some(out), 231 | // Ignore other actions, which means sending private messages to other people. 232 | _ => {} 233 | } 234 | } 235 | } 236 | 237 | Ok((out0.unwrap(), out1.unwrap())) 238 | } 239 | 240 | pub(crate) mod internal; 241 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::compat::{CSCurve, SerializablePoint}; 4 | use ecdsa::elliptic_curve::ScalarPrimitive; 5 | use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; 6 | 7 | /// Encode an arbitrary serializable value into a vec. 8 | pub fn encode(val: &T) -> Vec { 9 | rmp_serde::encode::to_vec(val).expect("failed to encode value") 10 | } 11 | 12 | /// Encode an arbitrary serializable value into a writer. 13 | pub fn encode_writer(w: &mut W, val: &T) { 14 | rmp_serde::encode::write(w, val).expect("failed to encode value"); 15 | } 16 | 17 | /// Encode an arbitrary serializable with a tag. 18 | pub fn encode_with_tag(tag: &[u8], val: &T) -> Vec { 19 | // Matches rmp_serde's internal default. 20 | let mut out = Vec::with_capacity(128); 21 | out.extend_from_slice(tag); 22 | rmp_serde::encode::write(&mut out, val).expect("failed to encode value"); 23 | out 24 | } 25 | 26 | /// Serialize a list of projective points. 27 | pub fn serialize_projective_points( 28 | data: &[C::ProjectivePoint], 29 | serializer: S, 30 | ) -> Result { 31 | serializer.collect_seq(data.iter().map(SerializablePoint::::from_projective)) 32 | } 33 | 34 | /// Deserialize projective points. 35 | pub fn deserialize_projective_points<'de, C, D>( 36 | deserializer: D, 37 | ) -> Result, D::Error> 38 | where 39 | C: CSCurve, 40 | D: Deserializer<'de>, 41 | { 42 | let points: Vec> = Deserialize::deserialize(deserializer)?; 43 | Ok(points.into_iter().map(|p| p.to_projective()).collect()) 44 | } 45 | 46 | /// Serialize a single projective point. 47 | pub fn serialize_projective_point( 48 | data: &C::ProjectivePoint, 49 | serializer: S, 50 | ) -> Result { 51 | SerializablePoint::::from_projective(data).serialize(serializer) 52 | } 53 | 54 | /// Serialize an arbitrary scalar. 55 | pub fn serialize_scalar( 56 | data: &C::Scalar, 57 | serializer: S, 58 | ) -> Result { 59 | let data: ScalarPrimitive = (*data).into(); 60 | data.serialize(serializer) 61 | } 62 | 63 | /// Deserialize an arbitrary scalar. 64 | pub fn deserialize_scalar<'de, C, D>(deserializer: D) -> Result 65 | where 66 | C: CSCurve, 67 | D: Deserializer<'de>, 68 | { 69 | let out: ScalarPrimitive = ScalarPrimitive::deserialize(deserializer)?; 70 | Ok(out.into()) 71 | } 72 | 73 | /// Decode an arbitrary value from a slice of bytes. 74 | pub fn decode(input: &[u8]) -> Result { 75 | rmp_serde::decode::from_slice(input) 76 | } 77 | -------------------------------------------------------------------------------- /src/sign.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{ops::Invert, scalar::IsHigh, Field, Group, ScalarPrimitive}; 2 | use subtle::ConditionallySelectable; 3 | 4 | use crate::{ 5 | compat::{self, CSCurve}, 6 | participants::{ParticipantCounter, ParticipantList}, 7 | protocol::{ 8 | internal::{make_protocol, Context, SharedChannel}, 9 | InitializationError, Participant, Protocol, ProtocolError, 10 | }, 11 | PresignOutput, 12 | }; 13 | 14 | /// Represents a signature with extra information, to support different variants of ECDSA. 15 | /// 16 | /// An ECDSA signature is usually two scalars. The first scalar is derived from 17 | /// a point on the curve, and because this process is lossy, some other variants 18 | /// of ECDSA also include some extra information in order to recover this point. 19 | /// 20 | /// Furthermore, some signature formats may disagree on how precisely to serialize 21 | /// different values as bytes. 22 | /// 23 | /// To support these variants, this simply gives you a normal signature, along with the entire 24 | /// first point. 25 | #[derive(Clone)] 26 | pub struct FullSignature { 27 | /// This is the entire first point. 28 | pub big_r: C::AffinePoint, 29 | /// This is the second scalar, normalized to be in the lower range. 30 | pub s: C::Scalar, 31 | } 32 | 33 | impl FullSignature { 34 | #[must_use] 35 | fn verify(&self, public_key: &C::AffinePoint, msg_hash: &C::Scalar) -> bool { 36 | let r: C::Scalar = compat::x_coordinate::(&self.big_r); 37 | if r.is_zero().into() || self.s.is_zero().into() { 38 | return false; 39 | } 40 | let s_inv = self.s.invert_vartime().unwrap(); 41 | let reproduced = (C::ProjectivePoint::generator() * (*msg_hash * s_inv)) 42 | + (C::ProjectivePoint::from(*public_key) * (r * s_inv)); 43 | compat::x_coordinate::(&reproduced.into()) == r 44 | } 45 | } 46 | 47 | async fn do_sign( 48 | mut chan: SharedChannel, 49 | participants: ParticipantList, 50 | me: Participant, 51 | public_key: C::AffinePoint, 52 | presignature: PresignOutput, 53 | msg_hash: C::Scalar, 54 | ) -> Result, ProtocolError> { 55 | // Spec 1.1 56 | let lambda = participants.lagrange::(me); 57 | let k_i = lambda * presignature.k; 58 | 59 | // Spec 1.2 60 | let sigma_i = lambda * presignature.sigma; 61 | 62 | // Spec 1.3 63 | let r = compat::x_coordinate::(&presignature.big_r); 64 | let s_i: C::Scalar = msg_hash * k_i + r * sigma_i; 65 | 66 | // Spec 1.4 67 | let wait0 = chan.next_waitpoint(); 68 | { 69 | let s_i: ScalarPrimitive = s_i.into(); 70 | chan.send_many(wait0, &s_i).await; 71 | } 72 | 73 | // Spec 2.1 + 2.2 74 | let mut seen = ParticipantCounter::new(&participants); 75 | let mut s: C::Scalar = s_i; 76 | seen.put(me); 77 | while !seen.full() { 78 | let (from, s_j): (_, ScalarPrimitive) = chan.recv(wait0).await?; 79 | if !seen.put(from) { 80 | continue; 81 | } 82 | s += C::Scalar::from(s_j) 83 | } 84 | 85 | // Spec 2.3 86 | // Optionally, normalize s 87 | s.conditional_assign(&(-s), s.is_high()); 88 | let sig = FullSignature { 89 | big_r: presignature.big_r, 90 | s, 91 | }; 92 | if !sig.verify(&public_key, &msg_hash) { 93 | return Err(ProtocolError::AssertionFailed( 94 | "signature failed to verify".to_string(), 95 | )); 96 | } 97 | 98 | // Spec 2.4 99 | Ok(sig) 100 | } 101 | 102 | /// The signature protocol, allowing us to use a presignature to sign a message. 103 | /// 104 | /// **WARNING** You must absolutely hash an actual message before passing it to 105 | /// this function. Allowing the signing of arbitrary scalars *is* a security risk, 106 | /// and this function only tolerates this risk to allow for genericity. 107 | pub fn sign( 108 | participants: &[Participant], 109 | me: Participant, 110 | public_key: C::AffinePoint, 111 | presignature: PresignOutput, 112 | msg_hash: C::Scalar, 113 | ) -> Result>, InitializationError> { 114 | if participants.len() < 2 { 115 | return Err(InitializationError::BadParameters(format!( 116 | "participant count cannot be < 2, found: {}", 117 | participants.len() 118 | ))); 119 | }; 120 | 121 | let participants = ParticipantList::new(participants).ok_or_else(|| { 122 | InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) 123 | })?; 124 | 125 | let ctx = Context::new(); 126 | let fut = do_sign( 127 | ctx.shared_channel(), 128 | participants, 129 | me, 130 | public_key, 131 | presignature, 132 | msg_hash, 133 | ); 134 | Ok(make_protocol(ctx, fut)) 135 | } 136 | 137 | #[cfg(test)] 138 | mod test { 139 | use std::error::Error; 140 | 141 | use ecdsa::Signature; 142 | use k256::{ 143 | ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey, Scalar, 144 | Secp256k1, 145 | }; 146 | use rand_core::OsRng; 147 | 148 | use crate::{compat::scalar_hash, math::Polynomial, protocol::run_protocol}; 149 | 150 | use super::*; 151 | 152 | #[test] 153 | fn test_sign() -> Result<(), Box> { 154 | let threshold = 2; 155 | let msg = b"hello?"; 156 | 157 | // Run 4 times for flakiness reasons 158 | for _ in 0..4 { 159 | let f = Polynomial::::random(&mut OsRng, threshold); 160 | let x = f.evaluate_zero(); 161 | let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); 162 | 163 | let g = Polynomial::::random(&mut OsRng, threshold); 164 | 165 | let k: Scalar = g.evaluate_zero(); 166 | let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); 167 | 168 | let sigma = k * x; 169 | 170 | let h = Polynomial::::extend_random(&mut OsRng, threshold, &sigma); 171 | 172 | let participants = vec![Participant::from(0u32), Participant::from(1u32)]; 173 | #[allow(clippy::type_complexity)] 174 | let mut protocols: Vec<( 175 | Participant, 176 | Box>>, 177 | )> = Vec::with_capacity(participants.len()); 178 | for p in &participants { 179 | let p_scalar = p.scalar::(); 180 | let presignature = PresignOutput { 181 | big_r: big_k, 182 | k: g.evaluate(&p_scalar), 183 | sigma: h.evaluate(&p_scalar), 184 | }; 185 | let protocol = sign( 186 | &participants, 187 | *p, 188 | public_key, 189 | presignature, 190 | scalar_hash(msg), 191 | )?; 192 | protocols.push((*p, Box::new(protocol))); 193 | } 194 | 195 | let result = run_protocol(protocols)?; 196 | let sig = result[0].1.clone(); 197 | let sig = 198 | Signature::from_scalars(compat::x_coordinate::(&sig.big_r), sig.s)?; 199 | VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) 200 | .verify(&msg[..], &sig)?; 201 | } 202 | Ok(()) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use k256::{AffinePoint, Secp256k1}; 2 | use rand_core::OsRng; 3 | 4 | use crate::{ 5 | compat::scalar_hash, 6 | keygen, presign, 7 | protocol::{run_protocol, Participant, Protocol}, 8 | sign, 9 | triples::{self, TriplePub, TripleShare}, 10 | FullSignature, KeygenOutput, PresignArguments, PresignOutput, 11 | }; 12 | 13 | fn run_keygen( 14 | participants: Vec, 15 | threshold: usize, 16 | ) -> Vec<(Participant, KeygenOutput)> { 17 | #[allow(clippy::type_complexity)] 18 | let mut protocols: Vec<( 19 | Participant, 20 | Box>>, 21 | )> = Vec::with_capacity(participants.len()); 22 | 23 | for p in participants.iter() { 24 | let protocol = keygen(&participants, *p, threshold); 25 | assert!(protocol.is_ok()); 26 | let protocol = protocol.unwrap(); 27 | protocols.push((*p, Box::new(protocol))); 28 | } 29 | 30 | run_protocol(protocols).unwrap() 31 | } 32 | 33 | fn run_presign( 34 | participants: Vec<(Participant, KeygenOutput)>, 35 | shares0: Vec>, 36 | shares1: Vec>, 37 | pub0: &TriplePub, 38 | pub1: &TriplePub, 39 | threshold: usize, 40 | ) -> Vec<(Participant, PresignOutput)> { 41 | assert!(participants.len() == shares0.len()); 42 | assert!(participants.len() == shares1.len()); 43 | 44 | #[allow(clippy::type_complexity)] 45 | let mut protocols: Vec<( 46 | Participant, 47 | Box>>, 48 | )> = Vec::with_capacity(participants.len()); 49 | 50 | let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); 51 | 52 | for (((p, keygen_out), share0), share1) in participants 53 | .into_iter() 54 | .zip(shares0.into_iter()) 55 | .zip(shares1.into_iter()) 56 | { 57 | let protocol = presign( 58 | &participant_list, 59 | p, 60 | PresignArguments { 61 | triple0: (share0, pub0.clone()), 62 | triple1: (share1, pub1.clone()), 63 | keygen_out, 64 | threshold, 65 | }, 66 | ); 67 | assert!(protocol.is_ok()); 68 | let protocol = protocol.unwrap(); 69 | protocols.push((p, Box::new(protocol))); 70 | } 71 | 72 | run_protocol(protocols).unwrap() 73 | } 74 | 75 | #[allow(clippy::type_complexity)] 76 | fn run_sign( 77 | participants: Vec<(Participant, PresignOutput)>, 78 | public_key: AffinePoint, 79 | msg: &[u8], 80 | ) -> Vec<(Participant, FullSignature)> { 81 | let mut protocols: Vec<( 82 | Participant, 83 | Box>>, 84 | )> = Vec::with_capacity(participants.len()); 85 | 86 | let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); 87 | 88 | for (p, presign_out) in participants.into_iter() { 89 | let protocol = sign( 90 | &participant_list, 91 | p, 92 | public_key, 93 | presign_out, 94 | scalar_hash(msg), 95 | ); 96 | assert!(protocol.is_ok()); 97 | let protocol = protocol.unwrap(); 98 | protocols.push((p, Box::new(protocol))); 99 | } 100 | 101 | run_protocol(protocols).unwrap() 102 | } 103 | 104 | #[test] 105 | fn test_e2e() { 106 | let participants = vec![ 107 | Participant::from(0u32), 108 | Participant::from(1u32), 109 | Participant::from(2u32), 110 | ]; 111 | let t = 3; 112 | 113 | let mut keygen_result = run_keygen(participants.clone(), t); 114 | keygen_result.sort_by_key(|(p, _)| *p); 115 | 116 | let public_key = keygen_result[0].1.public_key; 117 | assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); 118 | assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); 119 | 120 | let (pub0, shares0) = triples::deal(&mut OsRng, &participants, t); 121 | let (pub1, shares1) = triples::deal(&mut OsRng, &participants, t); 122 | 123 | let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, t); 124 | presign_result.sort_by_key(|(p, _)| *p); 125 | 126 | let msg = b"hello world"; 127 | 128 | run_sign(presign_result, public_key, msg); 129 | } 130 | -------------------------------------------------------------------------------- /src/triples/batch_random_ot.rs: -------------------------------------------------------------------------------- 1 | use ck_meow::Meow; 2 | use elliptic_curve::{Field, Group}; 3 | use rand_core::OsRng; 4 | use smol::stream::{self, StreamExt}; 5 | use subtle::ConditionallySelectable; 6 | 7 | use crate::{ 8 | compat::{CSCurve, SerializablePoint}, 9 | constants::SECURITY_PARAMETER, 10 | protocol::{ 11 | internal::{make_protocol, Context, PrivateChannel}, 12 | run_two_party_protocol, Participant, ProtocolError, 13 | }, 14 | serde::encode, 15 | }; 16 | 17 | use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; 18 | 19 | const BATCH_RANDOM_OT_HASH: &[u8] = b"cait-sith v0.8.0 batch ROT"; 20 | 21 | fn hash( 22 | i: usize, 23 | big_x_i: &SerializablePoint, 24 | big_y: &SerializablePoint, 25 | p: &C::ProjectivePoint, 26 | ) -> BitVector { 27 | let mut meow = Meow::new(BATCH_RANDOM_OT_HASH); 28 | meow.ad(&(i as u64).to_le_bytes(), false); 29 | meow.ad(&encode(&big_x_i), false); 30 | meow.ad(&encode(&big_y), false); 31 | meow.ad(&encode(&SerializablePoint::::from_projective(p)), false); 32 | 33 | let mut bytes = [0u8; SEC_PARAM_8]; 34 | meow.prf(&mut bytes, false); 35 | 36 | BitVector::from_bytes(&bytes) 37 | } 38 | 39 | type BatchRandomOTOutputSender = (SquareBitMatrix, SquareBitMatrix); 40 | 41 | pub async fn batch_random_ot_sender( 42 | ctx: Context<'_>, 43 | mut chan: PrivateChannel, 44 | ) -> Result { 45 | // Spec 1 46 | let y = C::Scalar::random(&mut OsRng); 47 | let big_y = C::ProjectivePoint::generator() * y; 48 | let big_z = big_y * y; 49 | 50 | let wait0 = chan.next_waitpoint(); 51 | let big_y_affine = SerializablePoint::::from_projective(&big_y); 52 | chan.send(wait0, &big_y_affine).await; 53 | 54 | let tasks = (0..SECURITY_PARAMETER).map(|i| { 55 | let mut chan = chan.child(i as u64); 56 | ctx.spawn(async move { 57 | let wait0 = chan.next_waitpoint(); 58 | let big_x_i_affine: SerializablePoint = chan.recv(wait0).await?; 59 | 60 | let y_big_x_i = big_x_i_affine.to_projective() * y; 61 | 62 | let big_k0 = hash(i, &big_x_i_affine, &big_y_affine, &y_big_x_i); 63 | let big_k1 = hash(i, &big_x_i_affine, &big_y_affine, &(y_big_x_i - big_z)); 64 | 65 | Ok::<_, ProtocolError>((big_k0, big_k1)) 66 | }) 67 | }); 68 | let out: Vec<(BitVector, BitVector)> = stream::iter(tasks).then(|t| t).try_collect().await?; 69 | 70 | let big_k0: BitMatrix = out.iter().map(|r| r.0).collect(); 71 | let big_k1: BitMatrix = out.iter().map(|r| r.1).collect(); 72 | Ok((big_k0.try_into().unwrap(), big_k1.try_into().unwrap())) 73 | } 74 | 75 | type BatchRandomOTOutputReceiver = (BitVector, SquareBitMatrix); 76 | 77 | pub async fn batch_random_ot_receiver( 78 | ctx: Context<'_>, 79 | mut chan: PrivateChannel, 80 | ) -> Result { 81 | // Step 3 82 | let wait0 = chan.next_waitpoint(); 83 | let big_y_affine: SerializablePoint = chan.recv(wait0).await?; 84 | let big_y = big_y_affine.to_projective(); 85 | if bool::from(big_y.is_identity()) { 86 | return Err(ProtocolError::AssertionFailed( 87 | "Big y in batch random OT was zero.".into(), 88 | )); 89 | } 90 | 91 | let delta = BitVector::random(&mut OsRng); 92 | 93 | let tasks = delta.bits().enumerate().map(|(i, d_i)| { 94 | let mut chan = chan.child(i as u64); 95 | ctx.spawn(async move { 96 | // Step 4 97 | let x_i = C::Scalar::random(&mut OsRng); 98 | let mut big_x_i = C::ProjectivePoint::generator() * x_i; 99 | big_x_i.conditional_assign(&(big_x_i + big_y), d_i); 100 | 101 | // Step 6 102 | let wait0 = chan.next_waitpoint(); 103 | let big_x_i_affine = SerializablePoint::::from_projective(&big_x_i); 104 | chan.send(wait0, &big_x_i_affine).await; 105 | 106 | // Step 5 107 | hash(i, &big_x_i_affine, &big_y_affine, &(big_y * x_i)) 108 | }) 109 | }); 110 | let out: Vec<_> = stream::iter(tasks).then(|t| t).collect().await; 111 | let big_k: BitMatrix = out.into_iter().collect(); 112 | 113 | Ok((delta, big_k.try_into().unwrap())) 114 | } 115 | 116 | /// Run the batch random OT protocol between two parties. 117 | #[allow(dead_code)] 118 | pub(crate) fn run_batch_random_ot( 119 | ) -> Result<(BatchRandomOTOutputSender, BatchRandomOTOutputReceiver), ProtocolError> { 120 | let s = Participant::from(0u32); 121 | let r = Participant::from(1u32); 122 | let ctx_s = Context::new(); 123 | let ctx_r = Context::new(); 124 | 125 | run_two_party_protocol( 126 | s, 127 | r, 128 | &mut make_protocol( 129 | ctx_s.clone(), 130 | batch_random_ot_sender::(ctx_s.clone(), ctx_s.private_channel(s, r)), 131 | ), 132 | &mut make_protocol( 133 | ctx_r.clone(), 134 | batch_random_ot_receiver::(ctx_r.clone(), ctx_r.private_channel(r, s)), 135 | ), 136 | ) 137 | } 138 | 139 | #[cfg(test)] 140 | mod test { 141 | use super::*; 142 | 143 | use k256::Secp256k1; 144 | 145 | #[test] 146 | fn test_batch_random_ot() { 147 | let res = run_batch_random_ot::(); 148 | assert!(res.is_ok()); 149 | let ((k0, k1), (delta, k_delta)) = res.unwrap(); 150 | 151 | // Check that we've gotten the right rows of the two matrices. 152 | for (((row0, row1), delta_i), row_delta) in k0 153 | .matrix 154 | .rows() 155 | .zip(k1.matrix.rows()) 156 | .zip(delta.bits()) 157 | .zip(k_delta.matrix.rows()) 158 | { 159 | assert_eq!( 160 | BitVector::conditional_select(row0, row1, delta_i), 161 | *row_delta 162 | ); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/triples/bits.rs: -------------------------------------------------------------------------------- 1 | use auto_ops::impl_op_ex; 2 | use ck_meow::Meow; 3 | use rand_core::CryptoRngCore; 4 | use serde::{Deserialize, Serialize}; 5 | use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; 6 | 7 | use crate::constants::SECURITY_PARAMETER; 8 | 9 | pub const SEC_PARAM_64: usize = (SECURITY_PARAMETER + 64 - 1) / 64; 10 | pub const SEC_PARAM_8: usize = (SECURITY_PARAMETER + 8 - 1) / 8; 11 | 12 | /// Represents a vector of bits. 13 | /// 14 | /// This vector will have the size of our security parameter, which is useful 15 | /// for most of our OT extension protocols. 16 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Eq)] 17 | pub struct BitVector([u64; SEC_PARAM_64]); 18 | 19 | impl BitVector { 20 | pub fn zero() -> Self { 21 | Self([0u64; SEC_PARAM_64]) 22 | } 23 | 24 | /// Return a random bit vector. 25 | pub fn random(rng: &mut impl CryptoRngCore) -> Self { 26 | let mut out = [0u64; SEC_PARAM_64]; 27 | for o in &mut out { 28 | *o = rng.next_u64(); 29 | } 30 | Self(out) 31 | } 32 | 33 | /// Get a specific bit from the vector. 34 | #[inline(always)] 35 | pub fn bit(&self, j: usize) -> u64 { 36 | (self.0[j / 64] >> (j % 64)) & 1 37 | } 38 | 39 | pub fn from_bytes(bytes: &[u8; SEC_PARAM_8]) -> Self { 40 | let u64s = bytes 41 | .chunks_exact(8) 42 | .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())); 43 | let mut out = [0u64; SEC_PARAM_64]; 44 | for (o, u) in out.iter_mut().zip(u64s) { 45 | *o = u; 46 | } 47 | Self(out) 48 | } 49 | 50 | pub fn bytes(&self) -> [u8; SEC_PARAM_8] { 51 | let mut out = [0u8; SEC_PARAM_8]; 52 | for (i, x_i) in self.0.iter().enumerate() { 53 | out[8 * i..8 * (i + 1)].copy_from_slice(&x_i.to_le_bytes()) 54 | } 55 | out 56 | } 57 | 58 | /// Iterate over the bits of this vector. 59 | pub fn bits(&self) -> impl Iterator { 60 | self.0 61 | .into_iter() 62 | .flat_map(|u| (0..64).map(move |j| ((u >> j) & 1).ct_eq(&1))) 63 | } 64 | 65 | /// Modify this vector by xoring it with another vector. 66 | pub fn xor_mut(&mut self, other: &Self) { 67 | for (self_i, other_i) in self.0.iter_mut().zip(other.0.iter()) { 68 | *self_i ^= other_i; 69 | } 70 | } 71 | 72 | /// Xor this vector with another. 73 | pub fn xor(&self, other: &Self) -> Self { 74 | let mut out = *self; 75 | out.xor_mut(other); 76 | out 77 | } 78 | 79 | /// Return the bitwise not of this vector. 80 | pub fn not(&self) -> Self { 81 | let mut out = *self; 82 | for out_i in &mut out.0 { 83 | *out_i = !*out_i; 84 | } 85 | out 86 | } 87 | 88 | pub fn and_mut(&mut self, other: &Self) { 89 | for (self_i, other_i) in self.0.iter_mut().zip(other.0.iter()) { 90 | *self_i &= other_i; 91 | } 92 | } 93 | 94 | pub fn and(&self, other: &Self) -> Self { 95 | let mut out = *self; 96 | out.and_mut(other); 97 | out 98 | } 99 | 100 | /// Multiplication in the field. 101 | /// 102 | /// This returns an unreduced value, which is fine for our use case. 103 | pub fn gf_mul(&self, other: &Self) -> DoubleBitVector { 104 | // Algorithm 2.35 in "Guide to Elliptic Curve Cryptography" 105 | let mut out = [0u64; 2 * SEC_PARAM_64]; 106 | 107 | for k in (0..64).rev() { 108 | for j in 0..SEC_PARAM_64 { 109 | let to_add = Self::conditional_select( 110 | &Self::zero(), 111 | other, 112 | Choice::from(((self.0[j] >> k) & 1) as u8), 113 | ); 114 | 115 | for i in 0..SEC_PARAM_64 { 116 | out[j + i] ^= to_add.0[i]; 117 | } 118 | } 119 | if k != 0 { 120 | let mut prev = 0u64; 121 | for out_i in &mut out { 122 | let next_prev = *out_i >> 63; 123 | *out_i = (*out_i << 1) | prev; 124 | prev = next_prev; 125 | } 126 | } 127 | } 128 | 129 | DoubleBitVector(out) 130 | } 131 | } 132 | 133 | impl ConditionallySelectable for BitVector { 134 | fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { 135 | let mut out = [0u64; SEC_PARAM_64]; 136 | for ((o_i, a_i), b_i) in out.iter_mut().zip(a.0.iter()).zip(b.0.iter()) { 137 | *o_i = u64::conditional_select(a_i, b_i, choice); 138 | } 139 | Self(out) 140 | } 141 | } 142 | 143 | impl_op_ex!(^ |u: &BitVector, v: &BitVector| -> BitVector { u.xor(v) }); 144 | impl_op_ex!(^= |u: &mut BitVector, v: &BitVector| { u.xor_mut(v) }); 145 | impl_op_ex!(&|u: &BitVector, v: &BitVector| -> BitVector { u.and(v) }); 146 | impl_op_ex!(&= |u: &mut BitVector, v: &BitVector| { u.and_mut(v) }); 147 | impl_op_ex!(!|u: &BitVector| -> BitVector { u.not() }); 148 | 149 | /// A BitVector of double the size. 150 | /// 151 | /// This is useful because it's quicker to avoid reducing the result of GF multiplication. 152 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 153 | #[cfg_attr(test, derive(PartialEq, Eq))] 154 | pub struct DoubleBitVector([u64; Self::SIZE]); 155 | 156 | impl DoubleBitVector { 157 | const SIZE: usize = 2 * SEC_PARAM_64; 158 | 159 | pub fn zero() -> Self { 160 | Self([0u64; Self::SIZE]) 161 | } 162 | 163 | pub fn xor_mut(&mut self, other: &Self) { 164 | for (self_i, other_i) in self.0.iter_mut().zip(other.0.iter()) { 165 | *self_i ^= *other_i; 166 | } 167 | } 168 | 169 | pub fn xor(&self, other: &Self) -> Self { 170 | let mut out = *self; 171 | out.xor_mut(other); 172 | out 173 | } 174 | } 175 | 176 | impl ConditionallySelectable for DoubleBitVector { 177 | fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { 178 | let mut out = [0u64; 2 * SEC_PARAM_64]; 179 | for ((o_i, a_i), b_i) in out.iter_mut().zip(a.0.iter()).zip(b.0.iter()) { 180 | *o_i = u64::conditional_select(a_i, b_i, choice); 181 | } 182 | Self(out) 183 | } 184 | } 185 | 186 | impl ConstantTimeEq for DoubleBitVector { 187 | fn ct_eq(&self, other: &Self) -> Choice { 188 | let mut out = Choice::from(1); 189 | for (a, b) in self.0.iter().zip(other.0.iter()) { 190 | out &= a.ct_eq(b); 191 | } 192 | out 193 | } 194 | } 195 | 196 | impl_op_ex!(^ |u: &DoubleBitVector, v: &DoubleBitVector| -> DoubleBitVector { u.xor(v) }); 197 | impl_op_ex!(^= |u: &mut DoubleBitVector, v: &DoubleBitVector| { u.xor_mut(v) }); 198 | 199 | /// The context string for our PRG. 200 | const PRG_CTX: &[u8] = b"cait-sith v0.8.0 correlated OT PRG"; 201 | 202 | /// Represents a matrix of bits. 203 | /// 204 | /// Each row of this matrix is a `BitVector`, although we might have more or less 205 | /// rows. 206 | /// 207 | /// This is a fundamental object used for our OT extension protocol. 208 | #[derive(Debug, Clone, Serialize, Deserialize)] 209 | #[cfg_attr(test, derive(PartialEq, Eq))] 210 | pub struct BitMatrix(Vec); 211 | 212 | impl BitMatrix { 213 | /// Create a random matrix of a certain chunk size. 214 | /// 215 | /// Each chunk will have a security parameter's worth of rows. 216 | pub fn random(rng: &mut impl CryptoRngCore, height: usize) -> Self { 217 | assert!(height % SECURITY_PARAMETER == 0); 218 | Self((0..height).map(|_| BitVector::random(rng)).collect()) 219 | } 220 | 221 | /// Create a new matrix from a list of rows. 222 | pub fn from_rows<'a>(rows: impl IntoIterator) -> Self { 223 | Self(rows.into_iter().copied().collect()) 224 | } 225 | 226 | /// Return the number of rows in this matrix. 227 | pub fn height(&self) -> usize { 228 | self.0.len() 229 | } 230 | 231 | /// Iterate over the rows of this matrix. 232 | pub fn rows(&self) -> impl Iterator { 233 | self.0.iter() 234 | } 235 | 236 | /// Iterate over a given column in chunks. 237 | pub fn column_chunks(&self, j: usize) -> impl Iterator + '_ { 238 | self.0.chunks_exact(SECURITY_PARAMETER).map(move |chunk| { 239 | let mut out = BitVector::zero(); 240 | for (i, c_i) in chunk.iter().enumerate() { 241 | out.0[i / 64] |= c_i.bit(j) << (i % 64); 242 | } 243 | out 244 | }) 245 | } 246 | 247 | /// Modify this matrix by xoring it with another. 248 | pub fn xor_mut(&mut self, other: &Self) { 249 | for (self_i, other_i) in self.0.iter_mut().zip(other.0.iter()) { 250 | *self_i ^= other_i; 251 | } 252 | } 253 | 254 | /// The result of xoring this matrix with another. 255 | pub fn xor(&self, other: &Self) -> Self { 256 | let mut out = self.clone(); 257 | out.xor_mut(other); 258 | out 259 | } 260 | 261 | pub fn and_vec_mut(&mut self, v: &BitVector) { 262 | for self_i in &mut self.0 { 263 | *self_i &= v; 264 | } 265 | } 266 | 267 | pub fn and_vec(&self, v: &BitVector) -> Self { 268 | let mut out = self.clone(); 269 | out.and_vec_mut(v); 270 | out 271 | } 272 | } 273 | 274 | impl FromIterator for BitMatrix { 275 | fn from_iter>(iter: T) -> Self { 276 | Self(iter.into_iter().collect()) 277 | } 278 | } 279 | 280 | impl_op_ex!(^ |u: &BitMatrix, v: &BitMatrix| -> BitMatrix { u.xor(v) }); 281 | impl_op_ex!(^= |u: &mut BitMatrix, v: &BitMatrix| { u.xor_mut(v) }); 282 | impl_op_ex!(&|u: &BitMatrix, v: &BitVector| -> BitMatrix { u.and_vec(v) }); 283 | #[derive(Debug, Clone)] 284 | #[cfg_attr(test, derive(PartialEq, Eq))] 285 | pub struct SquareBitMatrix { 286 | pub matrix: BitMatrix, 287 | } 288 | 289 | impl TryFrom for SquareBitMatrix { 290 | type Error = (); 291 | 292 | fn try_from(matrix: BitMatrix) -> Result { 293 | if matrix.height() != SECURITY_PARAMETER { 294 | return Err(()); 295 | } 296 | Ok(Self { matrix }) 297 | } 298 | } 299 | 300 | impl SquareBitMatrix { 301 | /// Expand transpose expands each row to contain `chunks * SECURITY_PARAMETER` bits, and then transposes 302 | /// the resulting matrix. 303 | pub fn expand_transpose(&self, sid: &[u8], rows: usize) -> BitMatrix { 304 | assert!(rows % SECURITY_PARAMETER == 0); 305 | 306 | let mut meow = Meow::new(PRG_CTX); 307 | meow.meta_ad(b"sid", false); 308 | meow.ad(sid, false); 309 | 310 | let mut out = BitMatrix(vec![BitVector::zero(); rows]); 311 | 312 | // How many bytes to get rows bits? 313 | let row8 = (rows + 7) / 8; 314 | for (j, row) in self.matrix.0.iter().enumerate() { 315 | // Expand the row 316 | let mut expanded = vec![0u8; row8]; 317 | // We need to clone to make each row use the same prefix. 318 | let mut meow = meow.clone(); 319 | meow.meta_ad(b"row", false); 320 | meow.ad(b"", false); 321 | for u in row.0 { 322 | meow.ad(&u.to_le_bytes(), true); 323 | } 324 | meow.prf(&mut expanded, false); 325 | 326 | // Now, write into the correct column 327 | for i in 0..rows { 328 | out.0[i].0[j / 64] |= u64::from((expanded[i / 8] >> (i % 8)) & 1) << (j % 64); 329 | } 330 | } 331 | 332 | out 333 | } 334 | } 335 | 336 | /// A choice vector holds an arbitrary number of choice bits. 337 | /// 338 | /// This vector must always be non-empty. 339 | #[derive(Debug, Clone)] 340 | pub struct ChoiceVector(Vec); 341 | 342 | impl ChoiceVector { 343 | /// Generate a random vector with a certain number of bits. 344 | pub fn random(rng: &mut impl CryptoRngCore, size: usize) -> Self { 345 | assert!(size > 0 && size % SECURITY_PARAMETER == 0); 346 | 347 | let data = (0..(size / SECURITY_PARAMETER)) 348 | .map(|_| BitVector::random(rng)) 349 | .collect(); 350 | 351 | Self(data) 352 | } 353 | 354 | /// Iterate over the bits in this vector. 355 | pub fn bits(&self) -> impl Iterator + '_ { 356 | self.0.iter().flat_map(|v| v.bits()) 357 | } 358 | 359 | /// Iterate over bitvector chunks from this vector. 360 | /// 361 | /// If the size of this vector is not evenly divided into chunks, 362 | /// then the last bitvector will be padded with 0s up until the MSB. 363 | pub fn chunks(&self) -> impl Iterator { 364 | self.0.iter() 365 | } 366 | } 367 | 368 | #[cfg(test)] 369 | mod test { 370 | use super::*; 371 | 372 | #[test] 373 | fn test_gf_multiplication() { 374 | let a = BitVector([0b10, 0b10]); 375 | let b = BitVector([0b100, 0b100]); 376 | let c = DoubleBitVector([0b1000, 0, 0b1000, 0]); 377 | assert_eq!(a.gf_mul(&b), c); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/triples/correlated_ot_extension.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::{ 2 | internal::{make_protocol, Context, PrivateChannel}, 3 | run_two_party_protocol, Participant, ProtocolError, 4 | }; 5 | 6 | use super::bits::{BitMatrix, BitVector, SquareBitMatrix}; 7 | 8 | /// Parameters we need for the correlated OT. 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct CorrelatedOtParams<'sid> { 11 | pub(crate) sid: &'sid [u8], 12 | pub(crate) batch_size: usize, 13 | } 14 | 15 | pub async fn correlated_ot_sender( 16 | mut chan: PrivateChannel, 17 | params: CorrelatedOtParams<'_>, 18 | delta: BitVector, 19 | k: &SquareBitMatrix, 20 | ) -> Result { 21 | // Spec 2 22 | let t = k.expand_transpose(params.sid, params.batch_size); 23 | 24 | // Spec 5 25 | let wait0 = chan.next_waitpoint(); 26 | let u: BitMatrix = chan.recv(wait0).await?; 27 | if u.height() != params.batch_size { 28 | return Err(ProtocolError::AssertionFailed(format!( 29 | "expected matrix of height {} found {}", 30 | params.batch_size, 31 | u.height() 32 | ))); 33 | } 34 | 35 | // Spec 6 36 | let q = (u & delta) ^ t; 37 | 38 | Ok(q) 39 | } 40 | 41 | pub async fn correlated_ot_receiver( 42 | mut chan: PrivateChannel, 43 | params: CorrelatedOtParams<'_>, 44 | k0: &SquareBitMatrix, 45 | k1: &SquareBitMatrix, 46 | x: &BitMatrix, 47 | ) -> BitMatrix { 48 | assert_eq!(x.height(), params.batch_size); 49 | // Spec 1 50 | let t0 = k0.expand_transpose(params.sid, params.batch_size); 51 | let t1 = k1.expand_transpose(params.sid, params.batch_size); 52 | 53 | // Spec 3 54 | let u = &t0 ^ t1 ^ x; 55 | 56 | // Spec 4 57 | let wait0 = chan.next_waitpoint(); 58 | chan.send(wait0, &u).await; 59 | 60 | t0 61 | } 62 | 63 | /// Run the correlated OT protocol between two parties. 64 | #[allow(dead_code)] 65 | fn run_correlated_ot( 66 | (delta, k): (BitVector, &SquareBitMatrix), 67 | (k0, k1, x): (&SquareBitMatrix, &SquareBitMatrix, &BitMatrix), 68 | sid: &[u8], 69 | batch_size: usize, 70 | ) -> Result<(BitMatrix, BitMatrix), ProtocolError> { 71 | let s = Participant::from(0u32); 72 | let r = Participant::from(1u32); 73 | let ctx_s = Context::new(); 74 | let ctx_r = Context::new(); 75 | 76 | let params = CorrelatedOtParams { sid, batch_size }; 77 | 78 | run_two_party_protocol( 79 | s, 80 | r, 81 | &mut make_protocol( 82 | ctx_s.clone(), 83 | correlated_ot_sender(ctx_s.private_channel(s, r), params, delta, k), 84 | ), 85 | &mut make_protocol(ctx_r.clone(), async move { 86 | let out = correlated_ot_receiver(ctx_r.private_channel(r, s), params, k0, k1, x).await; 87 | Ok(out) 88 | }), 89 | ) 90 | } 91 | 92 | #[cfg(test)] 93 | mod test { 94 | use rand_core::OsRng; 95 | 96 | use crate::triples::batch_random_ot::run_batch_random_ot; 97 | 98 | use super::*; 99 | use k256::Secp256k1; 100 | 101 | #[test] 102 | fn test_correlated_ot() -> Result<(), ProtocolError> { 103 | let ((k0, k1), (delta, k)) = run_batch_random_ot::()?; 104 | let batch_size = 256; 105 | let x = BitMatrix::random(&mut OsRng, batch_size); 106 | let (q, t) = run_correlated_ot((delta, &k), (&k0, &k1, &x), b"test sid", batch_size)?; 107 | assert_eq!(t ^ (x & delta), q); 108 | Ok(()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/triples/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the types and protocols related to triple generation. 2 | //! 3 | //! The cait-sith signing protocol makes use of *committed* Beaver Triples. 4 | //! A triple is a value of the form `(a, b, c), (A, B, C)`, such that 5 | //! `c = a * b`, and `A = a * G`, `B = b * G`, `C = c * G`. This is a beaver 6 | //! triple along with commitments to its values in the form of group elements. 7 | //! 8 | //! The signing protocols make use of a triple where the scalar values `(a, b, c)` 9 | //! are secret-shared, and the commitments are public. Each signature requires 10 | //! two triples. These triples can be generated in advance without knowledge 11 | //! of the secret key used to sign. It's important that the value of the underlying 12 | //! scalars in the triple is kept secret, otherwise the private key used to create 13 | //! a signature with that triple could be recovered. 14 | //! 15 | //! There are two ways of generating these triples. 16 | //! 17 | //! One way is to have 18 | //! a trusted third party generate them. This is supported by the [deal] function. 19 | //! 20 | //! The other way is to run a protocol generating a secret shared triple without any party 21 | //! learning the secret values. This is better because no party learns the value of the 22 | //! triple, which needs to be kept secret. This method is supported by the [generate_triple] 23 | //! protocol. 24 | //! 25 | //! This protocol requires a setup protocol to be one once beforehand. 26 | //! After this setup protocol has been run, an arbitarary number of triples can 27 | //! be generated. 28 | use elliptic_curve::{Field, Group}; 29 | use rand_core::CryptoRngCore; 30 | use serde::Serialize; 31 | 32 | use crate::{compat::CSCurve, math::Polynomial, protocol::Participant}; 33 | 34 | /// Represents the public part of a triple. 35 | /// 36 | /// This contains commitments to each part of the triple. 37 | /// 38 | /// We also record who participated in the protocol, 39 | #[derive(Clone, Debug, Serialize, PartialEq, Eq)] 40 | pub struct TriplePub { 41 | pub big_a: C::AffinePoint, 42 | pub big_b: C::AffinePoint, 43 | pub big_c: C::AffinePoint, 44 | /// The participants in generating this triple. 45 | pub participants: Vec, 46 | /// The threshold which will be able to reconstruct it. 47 | pub threshold: usize, 48 | } 49 | 50 | /// Represents a share of a triple. 51 | /// 52 | /// This consists of shares of each individual part. 53 | /// 54 | /// i.e. we have a share of a, b, and c such that a * b = c. 55 | #[derive(Clone, Debug)] 56 | pub struct TripleShare { 57 | pub a: C::Scalar, 58 | pub b: C::Scalar, 59 | pub c: C::Scalar, 60 | } 61 | 62 | /// Create a new triple from scratch. 63 | /// 64 | /// This can be used to generate a triple if you then trust the person running 65 | /// this code to forget about the values they generated. 66 | pub fn deal( 67 | rng: &mut impl CryptoRngCore, 68 | participants: &[Participant], 69 | threshold: usize, 70 | ) -> (TriplePub, Vec>) { 71 | let a = C::Scalar::random(&mut *rng); 72 | let b = C::Scalar::random(&mut *rng); 73 | let c = a * b; 74 | 75 | let f_a = Polynomial::::extend_random(rng, threshold, &a); 76 | let f_b = Polynomial::::extend_random(rng, threshold, &b); 77 | let f_c = Polynomial::::extend_random(rng, threshold, &c); 78 | 79 | let mut shares = Vec::with_capacity(participants.len()); 80 | let mut participants_owned = Vec::with_capacity(participants.len()); 81 | 82 | for p in participants { 83 | participants_owned.push(*p); 84 | let p_scalar = p.scalar::(); 85 | shares.push(TripleShare { 86 | a: f_a.evaluate(&p_scalar), 87 | b: f_b.evaluate(&p_scalar), 88 | c: f_c.evaluate(&p_scalar), 89 | }); 90 | } 91 | 92 | let triple_pub = TriplePub { 93 | big_a: (C::ProjectivePoint::generator() * a).into(), 94 | big_b: (C::ProjectivePoint::generator() * b).into(), 95 | big_c: (C::ProjectivePoint::generator() * c).into(), 96 | participants: participants_owned, 97 | threshold, 98 | }; 99 | 100 | (triple_pub, shares) 101 | } 102 | 103 | mod batch_random_ot; 104 | mod bits; 105 | mod correlated_ot_extension; 106 | mod generation; 107 | mod mta; 108 | mod multiplication; 109 | mod random_ot_extension; 110 | 111 | pub use generation::{generate_triple, TripleGenerationOutput}; 112 | -------------------------------------------------------------------------------- /src/triples/mta.rs: -------------------------------------------------------------------------------- 1 | use elliptic_curve::{Field, ScalarPrimitive}; 2 | use magikitten::MeowRng; 3 | use rand_core::{OsRng, RngCore}; 4 | use subtle::{Choice, ConditionallySelectable}; 5 | 6 | use crate::{ 7 | compat::CSCurve, 8 | protocol::{ 9 | internal::{make_protocol, Context, PrivateChannel}, 10 | run_two_party_protocol, Participant, ProtocolError, 11 | }, 12 | }; 13 | 14 | /// The sender for multiplicative to additive conversion. 15 | pub async fn mta_sender( 16 | mut chan: PrivateChannel, 17 | v: Vec<(C::Scalar, C::Scalar)>, 18 | a: C::Scalar, 19 | ) -> Result { 20 | let size = v.len(); 21 | 22 | // Step 1 23 | let delta: Vec<_> = (0..size).map(|_| C::Scalar::random(&mut OsRng)).collect(); 24 | 25 | // Step 2 26 | let c: Vec<(ScalarPrimitive, ScalarPrimitive)> = delta 27 | .iter() 28 | .zip(v.iter()) 29 | .map(|(delta_i, (v0_i, v1_i))| ((*v0_i + delta_i + a).into(), (*v1_i + delta_i - a).into())) 30 | .collect(); 31 | let wait0 = chan.next_waitpoint(); 32 | chan.send(wait0, &c).await; 33 | 34 | // Step 7 35 | let wait1 = chan.next_waitpoint(); 36 | let (chi1, seed): (ScalarPrimitive, [u8; 32]) = chan.recv(wait1).await?; 37 | 38 | let mut alpha = delta[0] * C::Scalar::from(chi1); 39 | 40 | let mut prng = MeowRng::new(&seed); 41 | for &delta_i in &delta[1..] { 42 | let chi_i = C::Scalar::random(&mut prng); 43 | alpha += delta_i * chi_i; 44 | } 45 | 46 | Ok(-alpha) 47 | } 48 | 49 | /// The receiver for multiplicative to additive conversion. 50 | pub async fn mta_receiver( 51 | mut chan: PrivateChannel, 52 | tv: Vec<(Choice, C::Scalar)>, 53 | b: C::Scalar, 54 | ) -> Result { 55 | let size = tv.len(); 56 | 57 | // Step 3 58 | let wait0 = chan.next_waitpoint(); 59 | let c: Vec<(ScalarPrimitive, ScalarPrimitive)> = chan.recv(wait0).await?; 60 | if c.len() != tv.len() { 61 | return Err(ProtocolError::AssertionFailed( 62 | "length of c was incorrect".to_owned(), 63 | )); 64 | } 65 | let mut m = tv.iter().zip(c.iter()).map(|((t_i, v_i), (c0_i, c1_i))| { 66 | C::Scalar::conditional_select(&(*c0_i).into(), &(*c1_i).into(), *t_i) - v_i 67 | }); 68 | 69 | // Step 4 70 | let mut seed = [0u8; 32]; 71 | OsRng.fill_bytes(&mut seed); 72 | let mut prng = MeowRng::new(&seed); 73 | let chi: Vec = (1..size).map(|_| C::Scalar::random(&mut prng)).collect(); 74 | 75 | let mut chi1 = C::Scalar::ZERO; 76 | for ((t_i, _), &chi_i) in tv.iter().skip(1).zip(chi.iter()) { 77 | chi1 += C::Scalar::conditional_select(&chi_i, &(-chi_i), *t_i); 78 | } 79 | chi1 = b - chi1; 80 | chi1.conditional_assign(&(-chi1), tv[0].0); 81 | //chi1.conditional_negate(tv[0].0); 82 | 83 | // Step 5 84 | let mut beta = chi1 * m.next().unwrap(); 85 | for (&chi_i, m_i) in chi.iter().zip(m) { 86 | beta += chi_i * m_i; 87 | } 88 | 89 | // Step 6 90 | let wait1 = chan.next_waitpoint(); 91 | let chi1: ScalarPrimitive = chi1.into(); 92 | chan.send(wait1, &(chi1, seed)).await; 93 | 94 | Ok(beta) 95 | } 96 | 97 | /// Run the multiplicative to additive protocol 98 | #[allow(dead_code, clippy::type_complexity)] 99 | fn run_mta( 100 | (v, a): (Vec<(C::Scalar, C::Scalar)>, C::Scalar), 101 | (tv, b): (Vec<(Choice, C::Scalar)>, C::Scalar), 102 | ) -> Result<(C::Scalar, C::Scalar), ProtocolError> { 103 | let s = Participant::from(0u32); 104 | let r = Participant::from(1u32); 105 | let ctx_s = Context::new(); 106 | let ctx_r = Context::new(); 107 | 108 | run_two_party_protocol( 109 | s, 110 | r, 111 | &mut make_protocol( 112 | ctx_s.clone(), 113 | mta_sender::(ctx_s.private_channel(s, r), v, a), 114 | ), 115 | &mut make_protocol( 116 | ctx_r.clone(), 117 | mta_receiver::(ctx_r.private_channel(r, s), tv, b), 118 | ), 119 | ) 120 | } 121 | 122 | #[cfg(test)] 123 | mod test { 124 | use ecdsa::elliptic_curve::{bigint::Bounded, Curve}; 125 | use k256::{Scalar, Secp256k1}; 126 | use rand_core::RngCore; 127 | 128 | use crate::constants::SECURITY_PARAMETER; 129 | 130 | use super::*; 131 | 132 | #[test] 133 | fn test_mta() -> Result<(), ProtocolError> { 134 | let batch_size = <::Uint as Bounded>::BITS + SECURITY_PARAMETER; 135 | 136 | let v: Vec<_> = (0..batch_size) 137 | .map(|_| { 138 | ( 139 | Scalar::generate_biased(&mut OsRng), 140 | Scalar::generate_biased(&mut OsRng), 141 | ) 142 | }) 143 | .collect(); 144 | let tv: Vec<_> = v 145 | .iter() 146 | .map(|(v0, v1)| { 147 | let c = Choice::from((OsRng.next_u64() & 1) as u8); 148 | (c, Scalar::conditional_select(v0, v1, c)) 149 | }) 150 | .collect(); 151 | 152 | let a = Scalar::generate_biased(&mut OsRng); 153 | let b = Scalar::generate_biased(&mut OsRng); 154 | let (alpha, beta) = run_mta::((v, a), (tv, b))?; 155 | 156 | assert_eq!(a * b, alpha + beta); 157 | 158 | Ok(()) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/triples/multiplication.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | compat::CSCurve, 3 | constants::SECURITY_PARAMETER, 4 | crypto::Digest, 5 | participants::ParticipantList, 6 | protocol::{ 7 | internal::{Context, PrivateChannel}, 8 | Participant, ProtocolError, 9 | }, 10 | }; 11 | 12 | use super::{ 13 | batch_random_ot::{batch_random_ot_receiver, batch_random_ot_sender}, 14 | mta::{mta_receiver, mta_sender}, 15 | random_ot_extension::{ 16 | random_ot_extension_receiver, random_ot_extension_sender, RandomOtExtensionParams, 17 | }, 18 | }; 19 | 20 | pub async fn multiplication_sender<'a, C: CSCurve>( 21 | ctx: Context<'a>, 22 | chan: PrivateChannel, 23 | sid: &[u8], 24 | a_i: &C::Scalar, 25 | b_i: &C::Scalar, 26 | ) -> Result { 27 | // First, run a fresh batch random OT ourselves 28 | let (delta, k) = batch_random_ot_receiver::(ctx.clone(), chan.child(0)).await?; 29 | 30 | let batch_size = C::BITS + SECURITY_PARAMETER; 31 | // Step 1 32 | let mut res0 = random_ot_extension_sender::( 33 | chan.child(1), 34 | RandomOtExtensionParams { 35 | sid, 36 | batch_size: 2 * batch_size, 37 | }, 38 | delta, 39 | &k, 40 | ) 41 | .await?; 42 | let res1 = res0.split_off(batch_size); 43 | 44 | // Step 2 45 | let task0 = ctx.spawn(mta_sender::(chan.child(2), res0, *a_i)); 46 | let task1 = ctx.spawn(mta_sender::(chan.child(3), res1, *b_i)); 47 | 48 | // Step 3 49 | let gamma0 = ctx.run(task0).await?; 50 | let gamma1 = ctx.run(task1).await?; 51 | 52 | Ok(gamma0 + gamma1) 53 | } 54 | 55 | pub async fn multiplication_receiver<'a, C: CSCurve>( 56 | ctx: Context<'a>, 57 | chan: PrivateChannel, 58 | sid: &[u8], 59 | a_i: &C::Scalar, 60 | b_i: &C::Scalar, 61 | ) -> Result { 62 | // First, run a fresh batch random OT ourselves 63 | let (k0, k1) = batch_random_ot_sender::(ctx.clone(), chan.child(0)).await?; 64 | 65 | let batch_size = C::BITS + SECURITY_PARAMETER; 66 | // Step 1 67 | let mut res0 = random_ot_extension_receiver::( 68 | chan.child(1), 69 | RandomOtExtensionParams { 70 | sid, 71 | batch_size: 2 * batch_size, 72 | }, 73 | &k0, 74 | &k1, 75 | ) 76 | .await?; 77 | let res1 = res0.split_off(batch_size); 78 | 79 | // Step 2 80 | let task0 = ctx.spawn(mta_receiver::(chan.child(2), res0, *b_i)); 81 | let task1 = ctx.spawn(mta_receiver::(chan.child(3), res1, *a_i)); 82 | 83 | // Step 3 84 | let gamma0 = ctx.run(task0).await?; 85 | let gamma1 = ctx.run(task1).await?; 86 | 87 | Ok(gamma0 + gamma1) 88 | } 89 | 90 | pub async fn multiplication( 91 | ctx: Context<'_>, 92 | sid: Digest, 93 | participants: ParticipantList, 94 | me: Participant, 95 | a_i: C::Scalar, 96 | b_i: C::Scalar, 97 | ) -> Result { 98 | let mut tasks = Vec::with_capacity(participants.len() - 1); 99 | for p in participants.others(me) { 100 | let fut = { 101 | let ctx = ctx.clone(); 102 | let chan = ctx.private_channel(me, p); 103 | async move { 104 | if p < me { 105 | multiplication_sender::(ctx, chan, sid.as_ref(), &a_i, &b_i).await 106 | } else { 107 | multiplication_receiver::(ctx, chan, sid.as_ref(), &a_i, &b_i).await 108 | } 109 | } 110 | }; 111 | tasks.push(ctx.spawn(fut)); 112 | } 113 | let mut out = a_i * b_i; 114 | for task in tasks { 115 | out += task.await?; 116 | } 117 | Ok(out) 118 | } 119 | 120 | #[cfg(test)] 121 | mod test { 122 | use k256::{Scalar, Secp256k1}; 123 | use rand_core::OsRng; 124 | 125 | use crate::{ 126 | crypto::hash, 127 | participants::ParticipantList, 128 | protocol::{ 129 | internal::{make_protocol, Context}, 130 | run_protocol, Participant, Protocol, ProtocolError, 131 | }, 132 | }; 133 | 134 | use super::multiplication; 135 | 136 | #[test] 137 | fn test_multiplication() -> Result<(), ProtocolError> { 138 | let participants = vec![ 139 | Participant::from(0u32), 140 | Participant::from(1u32), 141 | Participant::from(2u32), 142 | ]; 143 | 144 | let prep: Vec<_> = participants 145 | .iter() 146 | .map(|p| { 147 | let a_i = Scalar::generate_biased(&mut OsRng); 148 | let b_i = Scalar::generate_biased(&mut OsRng); 149 | (p, a_i, b_i) 150 | }) 151 | .collect(); 152 | let a = prep 153 | .iter() 154 | .fold(Scalar::ZERO, |acc, (_, a_i, _)| acc + a_i); 155 | let b = prep 156 | .iter() 157 | .fold(Scalar::ZERO, |acc, (_, _, b_i)| acc + b_i); 158 | 159 | let mut protocols: Vec<(Participant, Box>)> = 160 | Vec::with_capacity(prep.len()); 161 | 162 | let sid = hash(b"sid"); 163 | 164 | for (p, a_i, b_i) in prep { 165 | let ctx = Context::new(); 166 | let prot = make_protocol( 167 | ctx.clone(), 168 | multiplication::( 169 | ctx, 170 | sid, 171 | ParticipantList::new(&participants).unwrap(), 172 | *p, 173 | a_i, 174 | b_i, 175 | ), 176 | ); 177 | protocols.push((*p, Box::new(prot))) 178 | } 179 | 180 | let result = run_protocol(protocols)?; 181 | let c = result 182 | .into_iter() 183 | .fold(Scalar::ZERO, |acc, (_, c_i)| acc + c_i); 184 | 185 | assert_eq!(a * b, c); 186 | 187 | Ok(()) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/triples/random_ot_extension.rs: -------------------------------------------------------------------------------- 1 | use ck_meow::Meow; 2 | use elliptic_curve::{CurveArithmetic}; 3 | use magikitten::MeowRng; 4 | use rand_core::{OsRng, RngCore}; 5 | use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; 6 | 7 | use crate::{ 8 | compat::CSCurve, 9 | constants::SECURITY_PARAMETER, 10 | protocol::{ 11 | internal::{make_protocol, Context, PrivateChannel}, 12 | run_two_party_protocol, Participant, ProtocolError, 13 | }, 14 | }; 15 | 16 | use super::{ 17 | bits::{BitMatrix, BitVector, ChoiceVector, DoubleBitVector, SquareBitMatrix}, 18 | correlated_ot_extension::{correlated_ot_receiver, correlated_ot_sender, CorrelatedOtParams}, 19 | }; 20 | 21 | const MEOW_CTX: &[u8] = b"Random OT Extension Hash"; 22 | 23 | fn hash_to_scalar(i: usize, v: &BitVector) -> C::Scalar { 24 | let mut meow = Meow::new(MEOW_CTX); 25 | let i64 = u64::try_from(i).expect("failed to convert usize to u64"); 26 | meow.meta_ad(&i64.to_le_bytes(), false); 27 | meow.ad(&v.bytes(), false); 28 | let mut seed = [0u8; 32]; 29 | meow.prf(&mut seed, false); 30 | // Could in theory avoid one PRF call by using a more direct RNG wrapper 31 | // over the prf function, but oh well. 32 | C::sample_scalar_constant_time(&mut MeowRng::new(&seed)) 33 | } 34 | 35 | fn adjust_size(size: usize) -> usize { 36 | let r = size % SECURITY_PARAMETER; 37 | let padded = if r == 0 { 38 | size 39 | } else { 40 | size + (SECURITY_PARAMETER - r) 41 | }; 42 | padded + 2 * SECURITY_PARAMETER 43 | } 44 | 45 | /// Parameters we need for random OT extension 46 | #[derive(Debug, Clone, Copy)] 47 | pub struct RandomOtExtensionParams<'sid> { 48 | pub sid: &'sid [u8], 49 | pub batch_size: usize, 50 | } 51 | 52 | /// The result that the sender gets. 53 | pub type RandomOTExtensionSenderOut = Vec<( 54 | ::Scalar, 55 | ::Scalar, 56 | )>; 57 | 58 | /// The result that the receiver gets. 59 | pub type RandomOTExtensionReceiverOut = Vec<(Choice, ::Scalar)>; 60 | 61 | pub async fn random_ot_extension_sender( 62 | mut chan: PrivateChannel, 63 | params: RandomOtExtensionParams<'_>, 64 | delta: BitVector, 65 | k: &SquareBitMatrix, 66 | ) -> Result, ProtocolError> { 67 | let adjusted_size = adjust_size(params.batch_size); 68 | 69 | // Step 2 70 | let q = correlated_ot_sender( 71 | chan.child(0), 72 | CorrelatedOtParams { 73 | sid: params.sid, 74 | batch_size: adjusted_size, 75 | }, 76 | delta, 77 | k, 78 | ) 79 | .await?; 80 | 81 | // Step 5 82 | let mut seed = [0u8; 32]; 83 | OsRng.fill_bytes(&mut seed); 84 | let wait0 = chan.next_waitpoint(); 85 | chan.send(wait0, &seed).await; 86 | 87 | let mu = adjusted_size / SECURITY_PARAMETER; 88 | 89 | // Step 7 90 | let mut prng = MeowRng::new(&seed); 91 | let chi: Vec = (0..mu).map(|_| BitVector::random(&mut prng)).collect(); 92 | 93 | // Step 11 94 | let wait1 = chan.next_waitpoint(); 95 | let (small_x, small_t): (DoubleBitVector, Vec) = chan.recv(wait1).await?; 96 | 97 | // Step 10 98 | if small_t.len() != SECURITY_PARAMETER { 99 | return Err(ProtocolError::AssertionFailed( 100 | "small t of incorrect length".to_owned(), 101 | )); 102 | } 103 | 104 | for (j, small_t_j) in small_t.iter().enumerate() { 105 | let delta_j = Choice::from(delta.bit(j) as u8); 106 | 107 | let mut small_q_j = DoubleBitVector::zero(); 108 | for (q_i, chi_i) in q.column_chunks(j).zip(chi.iter()) { 109 | small_q_j ^= q_i.gf_mul(chi_i); 110 | } 111 | 112 | let delta_j_x = 113 | DoubleBitVector::conditional_select(&DoubleBitVector::zero(), &small_x, delta_j); 114 | if !bool::from(small_q_j.ct_eq(&(small_t_j ^ delta_j_x))) { 115 | return Err(ProtocolError::AssertionFailed("q check failed".to_owned())); 116 | } 117 | } 118 | 119 | // Step 14 120 | let mut out = Vec::with_capacity(params.batch_size); 121 | 122 | for (i, q_i) in q.rows().take(params.batch_size).enumerate() { 123 | let v0_i = hash_to_scalar::(i, q_i); 124 | let v1_i = hash_to_scalar::(i, &(q_i ^ delta)); 125 | out.push((v0_i, v1_i)) 126 | } 127 | 128 | Ok(out) 129 | } 130 | 131 | pub async fn random_ot_extension_receiver( 132 | mut chan: PrivateChannel, 133 | params: RandomOtExtensionParams<'_>, 134 | k0: &SquareBitMatrix, 135 | k1: &SquareBitMatrix, 136 | ) -> Result, ProtocolError> { 137 | let adjusted_size = adjust_size(params.batch_size); 138 | 139 | // Step 1 140 | let b = ChoiceVector::random(&mut OsRng, adjusted_size); 141 | let x: BitMatrix = b 142 | .bits() 143 | .map(|b_i| BitVector::conditional_select(&BitVector::zero(), &!BitVector::zero(), b_i)) 144 | .collect(); 145 | 146 | // Step 2 147 | let t = correlated_ot_receiver( 148 | chan.child(0), 149 | CorrelatedOtParams { 150 | sid: params.sid, 151 | batch_size: adjusted_size, 152 | }, 153 | k0, 154 | k1, 155 | &x, 156 | ) 157 | .await; 158 | 159 | let wait0 = chan.next_waitpoint(); 160 | 161 | // Step 5 162 | let seed: [u8; 32] = chan.recv(wait0).await?; 163 | 164 | let mu = adjusted_size / SECURITY_PARAMETER; 165 | 166 | // Step 7 167 | let mut prng = MeowRng::new(&seed); 168 | let chi: Vec = (0..mu).map(|_| BitVector::random(&mut prng)).collect(); 169 | 170 | // Step 8 171 | let mut small_x = DoubleBitVector::zero(); 172 | for (b_i, chi_i) in b.chunks().zip(chi.iter()) { 173 | small_x.xor_mut(&b_i.gf_mul(chi_i)); 174 | } 175 | let small_t: Vec<_> = (0..SECURITY_PARAMETER) 176 | .map(|j| { 177 | let mut small_t_j = DoubleBitVector::zero(); 178 | for (t_i, chi_i) in t.column_chunks(j).zip(chi.iter()) { 179 | small_t_j ^= t_i.gf_mul(chi_i); 180 | } 181 | small_t_j 182 | }) 183 | .collect(); 184 | 185 | // Step 11 186 | let wait1 = chan.next_waitpoint(); 187 | chan.send(wait1, &(small_x, small_t)).await; 188 | 189 | // Step 15 190 | let out: Vec<_> = b 191 | .bits() 192 | .zip(t.rows()) 193 | .take(params.batch_size) 194 | .enumerate() 195 | .map(|(i, (b_i, t_i))| (b_i, hash_to_scalar::(i, t_i))) 196 | .collect(); 197 | 198 | Ok(out) 199 | } 200 | 201 | /// Run the random OT protocol between two parties. 202 | #[allow(dead_code)] 203 | fn run_random_ot( 204 | (delta, k): (BitVector, &SquareBitMatrix), 205 | (k0, k1): (&SquareBitMatrix, &SquareBitMatrix), 206 | sid: &[u8], 207 | batch_size: usize, 208 | ) -> Result< 209 | ( 210 | RandomOTExtensionSenderOut, 211 | RandomOTExtensionReceiverOut, 212 | ), 213 | ProtocolError, 214 | > { 215 | let s = Participant::from(0u32); 216 | let r = Participant::from(1u32); 217 | let ctx_s = Context::new(); 218 | let ctx_r = Context::new(); 219 | 220 | let params = RandomOtExtensionParams { sid, batch_size }; 221 | 222 | run_two_party_protocol( 223 | s, 224 | r, 225 | &mut make_protocol( 226 | ctx_s.clone(), 227 | random_ot_extension_sender::(ctx_s.private_channel(s, r), params, delta, k), 228 | ), 229 | &mut make_protocol( 230 | ctx_r.clone(), 231 | random_ot_extension_receiver::(ctx_r.private_channel(r, s), params, k0, k1), 232 | ), 233 | ) 234 | } 235 | 236 | #[cfg(test)] 237 | mod test { 238 | use crate::triples::batch_random_ot::run_batch_random_ot; 239 | 240 | use super::*; 241 | 242 | use k256::{Scalar, Secp256k1}; 243 | 244 | #[test] 245 | fn test_random_ot() -> Result<(), ProtocolError> { 246 | let ((k0, k1), (delta, k)) = run_batch_random_ot::()?; 247 | let batch_size = 16; 248 | let (sender_out, receiver_out) = 249 | run_random_ot::((delta, &k), (&k0, &k1), b"test sid", batch_size)?; 250 | assert_eq!(sender_out.len(), batch_size); 251 | assert_eq!(receiver_out.len(), batch_size); 252 | for ((v0_i, v1_i), (b_i, vb_i)) in sender_out.iter().zip(receiver_out.iter()) { 253 | assert_eq!(*vb_i, Scalar::conditional_select(v0_i, v1_i, *b_i)); 254 | } 255 | Ok(()) 256 | } 257 | } 258 | --------------------------------------------------------------------------------