diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index ab1d9e4..a143361 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -35,6 +35,7 @@ use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier}; +use subtle::ConstantTimeEq; use lazy_static::lazy_static; @@ -254,6 +255,21 @@ impl BridgeAuth { } } +/// Try to extract a u64 from a Scalar +pub fn scalar_u64(s: &Scalar) -> Option { + // Check that the top 24 bytes of the Scalar are 0 + let sbytes = s.as_bytes(); + if sbytes[8..].ct_eq(&[0u8; 24]).unwrap_u8() == 0 { + return None; + } + Some(u64::from_le_bytes(sbytes[..8].try_into().unwrap())) +} + +/// Double a Scalar +pub fn scalar_dbl(s: &Scalar) -> Scalar { + s + s +} + // The protocol modules pub mod open_invite; diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index 4cdb51d..2bc74c2 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -35,8 +35,7 @@ fn test_open_invite() { // Use it to get a Lox credential let (req, state) = open_invite::request(&inv); let resp = ba.handle_open_invite(req).unwrap(); - let cred = - open_invite::handle_response(state, resp, &ba.lox_pub, &ba.migration_pub).unwrap(); + let cred = open_invite::handle_response(state, resp, &ba.lox_pub, &ba.migration_pub).unwrap(); // Check that we can use the credential to read a bucket let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); @@ -82,9 +81,10 @@ fn test_trust_promotion() { // Use it to get a Lox credential let (req, state) = open_invite::request(&inv); let resp = ba.handle_open_invite(req).unwrap(); - let cred = - open_invite::handle_response(state, resp, &ba.lox_pub, &ba.migration_pub).unwrap(); + let cred = open_invite::handle_response(state, resp, &ba.lox_pub, &ba.migration_pub).unwrap(); // Time passes ba.advance_days(40); + + let (promreq, promstate) = trust_promotion::request(&cred, &ba.lox_pub, ba.today()).unwrap(); } diff --git a/crates/lox-library/src/trust_promotion.rs b/crates/lox-library/src/trust_promotion.rs index e090346..df1049f 100644 --- a/crates/lox-library/src/trust_promotion.rs +++ b/crates/lox-library/src/trust_promotion.rs @@ -36,6 +36,11 @@ use zkp::CompactProof; use zkp::ProofError; use zkp::Transcript; +use super::cred; +use super::IssuerPubKey; +use super::{scalar_dbl, scalar_u64}; +use super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE}; + /// The minimum number of days a user has to be at trust level 0 /// (untrusted) with their (single) bridge unblocked before they can /// move to level 1. @@ -46,5 +51,316 @@ use zkp::Transcript; pub const UNTRUSTED_INTERVAL: u64 = 30; pub struct Request { + // Fields for blind showing the Lox credential + // We don't need to include trust_level, invites_remaining, or + // invites_issued, since they must be 0 + P: RistrettoPoint, id: Scalar, + CBucket: RistrettoPoint, + CSince: RistrettoPoint, + CQ: RistrettoPoint, + + // Fields for user blinding of the Migration Key credential + D: RistrettoPoint, + EncBucket: (RistrettoPoint, RistrettoPoint), + + // Fields for the inequality proof (level_since + + // UNTRUSTED_INTERVAL <= today) + CG1: RistrettoPoint, + CG2: RistrettoPoint, + CG3: RistrettoPoint, + CG4: RistrettoPoint, + CG5: RistrettoPoint, + CG6: RistrettoPoint, + CG7: RistrettoPoint, + CG8: RistrettoPoint, + + // The combined ZKP + piUser: CompactProof, +} + +#[derive(Debug)] +pub struct State { + d: Scalar, + D: RistrettoPoint, + EncBucket: (RistrettoPoint, RistrettoPoint), + id: Scalar, + bucket: Scalar, +} + +define_proof! { + requestproof, + "Trust Promotion Request", + (bucket, since, zbucket, zsince, negzQ, + d, ebucket, + g0, g1, g2, g3, g4, g5, g6, g7, g8, + zg0, zg1, zg2, zg3, zg4, zg5, zg6, zg7, zg8, + wg0, wg1, wg2, wg3, wg4, wg5, wg6, wg7, wg8, + yg0, yg1, yg2, yg3, yg4, yg5, yg6, yg7, yg8), + (P, CBucket, CSince, V, Xbucket, Xsince, + EncBucket0, EncBucket1, D, + CG0, CG1, CG2, CG3, CG4, CG5, CG6, CG7, CG8, + CG0sq, CG1sq, CG2sq, CG3sq, CG4sq, CG5sq, CG6sq, CG7sq, CG8sq), + (A, B): + // Blind showing of the Lox credential + CBucket = (bucket*P + zbucket*A), + CSince = (since*P + zsince*A), + V = (zbucket*Xbucket + zsince*Xsince + negzQ*A), + // User blinding of the Migration Key credential + EncBucket0 = (ebucket*B), + EncBucket1 = (bucket*B + ebucket*D), + D = (d*B), + // Prove CSince encodes a value at least UNTRUSTED_INTERVAL + // days ago (at technically at most UNTRUSTED_INTERVAL+511 days + // ago): first prove each of g0, ..., g8 is a bit by proving that + // gi = gi^2 + CG0 = (g0*P + zg0*A), CG0sq = (g0*CG0 + wg0*A), CG0sq = (g0*P + yg0*A), + CG1 = (g1*P + zg1*A), CG1sq = (g1*CG1 + wg1*A), CG1sq = (g1*P + yg1*A), + CG2 = (g2*P + zg2*A), CG2sq = (g2*CG2 + wg2*A), CG2sq = (g2*P + yg2*A), + CG3 = (g3*P + zg3*A), CG3sq = (g3*CG3 + wg3*A), CG3sq = (g3*P + yg3*A), + CG4 = (g4*P + zg4*A), CG4sq = (g4*CG4 + wg4*A), CG4sq = (g4*P + yg4*A), + CG5 = (g5*P + zg5*A), CG5sq = (g5*CG5 + wg5*A), CG5sq = (g5*P + yg5*A), + CG6 = (g6*P + zg6*A), CG6sq = (g6*CG6 + wg6*A), CG6sq = (g6*P + yg6*A), + CG7 = (g7*P + zg7*A), CG7sq = (g7*CG7 + wg7*A), CG7sq = (g7*P + yg7*A), + CG8 = (g8*P + zg8*A), CG8sq = (g8*CG8 + wg8*A), CG8sq = (g8*P + yg8*A) + // Then we'll check that CSince + UNTRUSTED_INTERVAL*P + CG0 + 2*CG1 + // + 4*CG2 + 8*CG3 + ... + 256*CG8 = today*P by having the verifier + // plug in today*P - (CSince + UNTRUSTED_INTERVAL*P + 2*CG1 + 4*CG2 + // + ... + 256*CG8) as its value of CG0. +} + +pub fn request( + lox_cred: &cred::Lox, + lox_pub: &IssuerPubKey, + today: u64, +) -> Result<(Request, State), ProofError> { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + // Ensure the credential can be correctly shown: it must be the case + // that level_since + UNTRUSTED_INTERVAL <= today. + let level_since: u64 = match scalar_u64(&lox_cred.level_since) { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + if level_since + UNTRUSTED_INTERVAL > today { + return Err(ProofError::VerificationFailure); + } + let diffdays = today - (level_since + UNTRUSTED_INTERVAL); + if diffdays > 511 { + return Err(ProofError::VerificationFailure); + } + + // Blind showing the Lox credential + + // Reblind P and Q + let mut rng = rand::thread_rng(); + let t = Scalar::random(&mut rng); + let P = t * lox_cred.P; + let Q = t * lox_cred.Q; + + // Form Pedersen commitments to the blinded attributes + let zbucket = Scalar::random(&mut rng); + let zsince = Scalar::random(&mut rng); + let CBucket = lox_cred.bucket * P + &zbucket * Atable; + let CSince = lox_cred.level_since * P + &zsince * Atable; + + // Form a Pedersen commitment to the MAC Q + // We flip the sign of zQ from that of the Hyphae paper so that + // the ZKP has a "+" instead of a "-", as that's what the zkp + // macro supports. + let negzQ = Scalar::random(&mut rng); + let CQ = Q - &negzQ * Atable; + + // Compute the "error factor" + let V = zbucket * lox_pub.X[2] + zsince * lox_pub.X[4] + &negzQ * Atable; + + // User blinding the Migration Key credential + + // Pick an ElGamal keypair + let d = Scalar::random(&mut rng); + let D = &d * Btable; + + // Encrypt the attributes to be blinded (each times the + // basepoint B) to the public key we just created + let ebucket = Scalar::random(&mut rng); + let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D); + + // The range proof that 0 <= diffdays <= 511 + + // Extract the 9 bits from diffdays + let g0: Scalar = (diffdays & 1).into(); + let g1: Scalar = ((diffdays >> 1) & 1).into(); + let g2: Scalar = ((diffdays >> 2) & 1).into(); + let g3: Scalar = ((diffdays >> 3) & 1).into(); + let g4: Scalar = ((diffdays >> 4) & 1).into(); + let g5: Scalar = ((diffdays >> 5) & 1).into(); + let g6: Scalar = ((diffdays >> 6) & 1).into(); + let g7: Scalar = ((diffdays >> 7) & 1).into(); + let g8: Scalar = ((diffdays >> 8) & 1).into(); + + // Pick random factors for the Pedersen commitments + let wg0 = Scalar::random(&mut rng); + let zg1 = Scalar::random(&mut rng); + let wg1 = Scalar::random(&mut rng); + let zg2 = Scalar::random(&mut rng); + let wg2 = Scalar::random(&mut rng); + let zg3 = Scalar::random(&mut rng); + let wg3 = Scalar::random(&mut rng); + let zg4 = Scalar::random(&mut rng); + let wg4 = Scalar::random(&mut rng); + let zg5 = Scalar::random(&mut rng); + let wg5 = Scalar::random(&mut rng); + let zg6 = Scalar::random(&mut rng); + let wg6 = Scalar::random(&mut rng); + let zg7 = Scalar::random(&mut rng); + let wg7 = Scalar::random(&mut rng); + let zg8 = Scalar::random(&mut rng); + let wg8 = Scalar::random(&mut rng); + + // Compute zg0 to cancel things out as + // zg0 = -(zsince + 2*zg1 + 4*zg2 + 8*zg3 + 16*zg4 + 32*zg5 + 64*zg6 + 128*zg7 + 256*zg8) + // but use Horner's method + let zg0 = -scalar_dbl( + &(scalar_dbl( + &(scalar_dbl( + &(scalar_dbl( + &(scalar_dbl( + &(scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg8) + zg7)) + zg6)) + zg5), + ) + zg4), + ) + zg3), + ) + zg2), + ) + zg1), + ) + zsince; + + let yg0 = wg0 + g0 * zg0; + let yg1 = wg1 + g1 * zg1; + let yg2 = wg2 + g2 * zg2; + let yg3 = wg3 + g3 * zg3; + let yg4 = wg4 + g4 * zg4; + let yg5 = wg5 + g5 * zg5; + let yg6 = wg6 + g6 * zg6; + let yg7 = wg7 + g7 * zg7; + let yg8 = wg8 + g8 * zg8; + + let CG0 = g0 * P + &zg0 * Atable; + let CG1 = g1 * P + &zg1 * Atable; + let CG2 = g2 * P + &zg2 * Atable; + let CG3 = g3 * P + &zg3 * Atable; + let CG4 = g4 * P + &zg4 * Atable; + let CG5 = g5 * P + &zg5 * Atable; + let CG6 = g6 * P + &zg6 * Atable; + let CG7 = g7 * P + &zg7 * Atable; + let CG8 = g8 * P + &zg8 * Atable; + + // Construct the proof + let mut transcript = Transcript::new(b"trust promotion request"); + let piUser = requestproof::prove_compact( + &mut transcript, + requestproof::ProveAssignments { + A: &A, + B: &B, + P: &P, + CBucket: &CBucket, + CSince: &CSince, + V: &V, + Xbucket: &lox_pub.X[2], + Xsince: &lox_pub.X[4], + EncBucket0: &EncBucket.0, + EncBucket1: &EncBucket.1, + D: &D, + CG0: &CG0, + CG1: &CG1, + CG2: &CG2, + CG3: &CG3, + CG4: &CG4, + CG5: &CG5, + CG6: &CG6, + CG7: &CG7, + CG8: &CG8, + CG0sq: &(g0 * P + &yg0 * Atable), + CG1sq: &(g1 * P + &yg1 * Atable), + CG2sq: &(g2 * P + &yg2 * Atable), + CG3sq: &(g3 * P + &yg3 * Atable), + CG4sq: &(g4 * P + &yg4 * Atable), + CG5sq: &(g5 * P + &yg5 * Atable), + CG6sq: &(g6 * P + &yg6 * Atable), + CG7sq: &(g7 * P + &yg7 * Atable), + CG8sq: &(g8 * P + &yg8 * Atable), + bucket: &lox_cred.bucket, + since: &lox_cred.level_since, + zbucket: &zbucket, + zsince: &zsince, + negzQ: &negzQ, + d: &d, + ebucket: &ebucket, + g0: &g0, + g1: &g1, + g2: &g2, + g3: &g3, + g4: &g4, + g5: &g5, + g6: &g6, + g7: &g7, + g8: &g8, + zg0: &zg0, + zg1: &zg1, + zg2: &zg2, + zg3: &zg3, + zg4: &zg4, + zg5: &zg5, + zg6: &zg6, + zg7: &zg7, + zg8: &zg8, + wg0: &wg0, + wg1: &wg1, + wg2: &wg2, + wg3: &wg3, + wg4: &wg4, + wg5: &wg5, + wg6: &wg6, + wg7: &wg7, + wg8: &wg8, + yg0: &yg0, + yg1: &yg1, + yg2: &yg2, + yg3: &yg3, + yg4: &yg4, + yg5: &yg5, + yg6: &yg6, + yg7: &yg7, + yg8: &yg8, + }, + ) + .0; + + Ok(( + Request { + P, + id: lox_cred.id, + CBucket, + CSince, + CQ, + D, + EncBucket, + CG1, + CG2, + CG3, + CG4, + CG5, + CG6, + CG7, + CG8, + piUser, + }, + State { + d, + D, + EncBucket, + id: lox_cred.id, + bucket: lox_cred.bucket, + }, + )) }