From 738babf7e993d88e93025f67e8e5cd20912d7d9b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 25 Aug 2025 04:49:54 -0400 Subject: [PATCH] dkg-evrf crate monero-oxide relies on ciphersuite, which is in-tree, yet we've made breaking changes since. This commit adds a patch so monero-oxide -> patches/ciphersuite -> crypto/ciphersuite, with patches/ciphersuite resolving the breaking changes. --- Cargo.lock | 77 +-- Cargo.toml | 6 + crypto/dkg/evrf/Cargo.toml | 48 +- crypto/dkg/evrf/README.md | 50 ++ crypto/dkg/evrf/src/curves.rs | 109 ++++ crypto/dkg/evrf/src/lib.rs | 430 ++++++------- crypto/dkg/evrf/src/proof.rs | 690 --------------------- crypto/dkg/evrf/src/proof/mod.rs | 684 ++++++++++++++++++++ crypto/dkg/evrf/src/proof/tape.rs | 106 ++++ crypto/dkg/evrf/src/tests/mod.rs | 38 +- crypto/dkg/evrf/src/tests/proof.rs | 114 +--- crypto/dkg/evrf/src/utils.rs | 43 ++ crypto/dkg/src/lib.rs | 13 + crypto/evrf/embedwards25519/Cargo.toml | 4 +- crypto/evrf/embedwards25519/src/backend.rs | 8 +- crypto/evrf/secq256k1/Cargo.toml | 4 +- crypto/evrf/secq256k1/src/backend.rs | 8 +- patches/ciphersuite/Cargo.toml | 30 + patches/ciphersuite/LICENSE | 21 + patches/ciphersuite/README.md | 4 + patches/ciphersuite/src/lib.rs | 5 + processor/key-gen/Cargo.toml | 2 +- processor/monero/Cargo.toml | 4 +- substrate/client/Cargo.toml | 2 +- substrate/node/Cargo.toml | 2 +- tests/no-std/Cargo.toml | 1 + tests/no-std/src/lib.rs | 1 + 27 files changed, 1419 insertions(+), 1085 deletions(-) create mode 100644 crypto/dkg/evrf/README.md create mode 100644 crypto/dkg/evrf/src/curves.rs delete mode 100644 crypto/dkg/evrf/src/proof.rs create mode 100644 crypto/dkg/evrf/src/proof/mod.rs create mode 100644 crypto/dkg/evrf/src/proof/tape.rs create mode 100644 crypto/dkg/evrf/src/utils.rs create mode 100644 patches/ciphersuite/Cargo.toml create mode 100644 patches/ciphersuite/LICENSE create mode 100644 patches/ciphersuite/README.md create mode 100644 patches/ciphersuite/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7f09466f..4ce11703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1916,25 +1916,10 @@ dependencies = [ [[package]] name = "ciphersuite" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae44ce6224d75ced726e5597265b8f334b9bf4767b8b42e058f2304005d8475" +version = "0.4.99" dependencies = [ + "ciphersuite 0.4.2", "dalek-ff-group", - "digest 0.10.7", - "elliptic-curve", - "ff", - "flexible-transcript", - "group", - "k256", - "minimal-ed448", - "p256", - "rand_core 0.6.4", - "sha2 0.10.9", - "sha3", - "std-shims", - "subtle", - "zeroize", ] [[package]] @@ -2759,8 +2744,10 @@ version = "0.1.0" dependencies = [ "blake2", "ciphersuite 0.4.2", + "ciphersuite-kp256", "dalek-ff-group", "dkg", + "dkg-recovery", "ec-divisors", "embedwards25519", "flexible-transcript", @@ -2773,6 +2760,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "secq256k1", + "std-shims", "thiserror 2.0.16", "zeroize", ] @@ -2873,7 +2861,7 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ec-divisors" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "crypto-bigint", "dalek-ff-group", @@ -3545,10 +3533,10 @@ dependencies = [ [[package]] name = "full-chain-membership-proofs" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "blake2", - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "ec-divisors", "generalized-bulletproofs", "generalized-bulletproofs-circuit-abstraction", @@ -3758,10 +3746,10 @@ dependencies = [ [[package]] name = "generalized-bulletproofs" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "blake2", - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "multiexp", "rand_core 0.6.4", "std-shims", @@ -3771,9 +3759,9 @@ dependencies = [ [[package]] name = "generalized-bulletproofs-circuit-abstraction" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "generalized-bulletproofs", "std-shims", "zeroize", @@ -3782,9 +3770,9 @@ dependencies = [ [[package]] name = "generalized-bulletproofs-ec-gadgets" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "generalized-bulletproofs-circuit-abstraction", "generic-array 1.2.0", "std-shims", @@ -4002,10 +3990,10 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helioselene" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "blake2", - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "crypto-bigint", "dalek-ff-group", "ec-divisors", @@ -6074,7 +6062,7 @@ dependencies = [ [[package]] name = "monero-address" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "monero-base58", @@ -6086,7 +6074,7 @@ dependencies = [ [[package]] name = "monero-base58" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "monero-primitives", "std-shims", @@ -6095,7 +6083,7 @@ dependencies = [ [[package]] name = "monero-borromean" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "monero-generators", @@ -6108,7 +6096,7 @@ dependencies = [ [[package]] name = "monero-bulletproofs" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "monero-generators", @@ -6123,7 +6111,7 @@ dependencies = [ [[package]] name = "monero-clsag" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "dalek-ff-group", @@ -6144,10 +6132,10 @@ dependencies = [ [[package]] name = "monero-fcmp-plus-plus" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "blake2", - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "curve25519-dalek", "dalek-ff-group", "ec-divisors", @@ -6166,10 +6154,10 @@ dependencies = [ [[package]] name = "monero-generators" version = "0.4.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "blake2", - "ciphersuite 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciphersuite 0.4.99", "crypto-bigint", "curve25519-dalek", "dalek-ff-group", @@ -6186,7 +6174,7 @@ dependencies = [ [[package]] name = "monero-io" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "std-shims", @@ -6195,7 +6183,7 @@ dependencies = [ [[package]] name = "monero-mlsag" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "monero-generators", @@ -6209,7 +6197,7 @@ dependencies = [ [[package]] name = "monero-oxide" version = "0.1.4-alpha" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "hex-literal", @@ -6228,7 +6216,7 @@ dependencies = [ [[package]] name = "monero-primitives" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "monero-generators", @@ -6241,7 +6229,7 @@ dependencies = [ [[package]] name = "monero-rpc" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "hex", @@ -6257,7 +6245,7 @@ dependencies = [ [[package]] name = "monero-simple-request-rpc" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "digest_auth", "hex", @@ -6270,7 +6258,7 @@ dependencies = [ [[package]] name = "monero-wallet" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide?rev=b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e#b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" +source = "git+https://github.com/kayabaNerve/monero-oxide?rev=54da48f27a05fa8656014942919da1dfbab4d8e3#54da48f27a05fa8656014942919da1dfbab4d8e3" dependencies = [ "curve25519-dalek", "dalek-ff-group", @@ -10198,6 +10186,7 @@ dependencies = [ "ciphersuite 0.4.2", "dalek-ff-group", "dkg", + "dkg-evrf", "embedwards25519", "flexible-transcript", "minimal-ed448", diff --git a/Cargo.toml b/Cargo.toml index bfd8ed97..624225ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,11 @@ members = [ "patches/option-ext", "patches/directories-next", + # monero-oxide expects ciphersuite, yet the ciphersuite in-tree here has breaking changes + # This re-exports the in-tree ciphersuite _without_ changes breaking to monero-oxide + # Not included in workspace to prevent having two crates with the same name (an error) + # "patches/ciphersuite", + "common/std-shims", "common/zalloc", "common/patchable-async-sleep", @@ -172,6 +177,7 @@ std-shims = { path = "common/std-shims" } simple-request = { path = "common/request" } multiexp = { path = "crypto/multiexp" } flexible-transcript = { path = "crypto/transcript" } +ciphersuite = { path = "patches/ciphersuite" } dalek-ff-group = { path = "crypto/dalek-ff-group" } minimal-ed448 = { path = "crypto/ed448" } modular-frost = { path = "crypto/frost" } diff --git a/crypto/dkg/evrf/Cargo.toml b/crypto/dkg/evrf/Cargo.toml index a2fe592c..4100edae 100644 --- a/crypto/dkg/evrf/Cargo.toml +++ b/crypto/dkg/evrf/Cargo.toml @@ -23,31 +23,37 @@ rand_core = { version = "0.6", default-features = false } zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } +std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false } + transcript = { package = "flexible-transcript", path = "../../transcript", version = "^0.3.2", default-features = false, features = ["recommended"] } -ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false } +ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false, features = ["alloc"] } multiexp = { path = "../../multiexp", version = "0.4", default-features = false } generic-array = { version = "1", default-features = false, features = ["alloc"] } -blake2 = { version = "0.10", default-features = false, features = ["std"] } -rand_chacha = { version = "0.3", default-features = false, features = ["std"] } -generalized-bulletproofs = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } -ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } -generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" } -generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" } +blake2 = { version = "0.10", default-features = false } +rand_chacha = { version = "0.3", default-features = false } + +generalized-bulletproofs = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } +ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } +generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } +generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } dkg = { path = ".." } +ciphersuite-kp256 = { path = "../../ciphersuite/kp256", default-features = false, optional = true } secq256k1 = { path = "../../evrf/secq256k1", optional = true } +dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, optional = true } embedwards25519 = { path = "../../evrf/embedwards25519", optional = true } [dev-dependencies] rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } rand = { version = "0.8", default-features = false, features = ["std"] } ciphersuite = { path = "../../ciphersuite", default-features = false, features = ["std"] } +embedwards25519 = { path = "../../evrf/embedwards25519", default-features = false, features = ["std"] } dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, features = ["std"] } -generalized-bulletproofs = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", features = ["tests"] } -ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e" } +generalized-bulletproofs = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", features = ["tests"] } +dkg-recovery = { path = "../recovery" } [features] std = [ @@ -55,14 +61,32 @@ std = [ "rand_core/std", + "zeroize/std", + "std-shims/std", + "transcript/std", "ciphersuite/std", "multiexp/std", "multiexp/batch", + + "blake2/std", + "rand_chacha/std", + + "generalized-bulletproofs/std", + "ec-divisors/std", + "generalized-bulletproofs-circuit-abstraction/std", + "generalized-bulletproofs-ec-gadgets/std", + + "dkg/std", + + "ciphersuite-kp256?/std", + "secq256k1?/std", + "dalek-ff-group?/std", + "embedwards25519?/std", ] -secp256k1 = ["secq256k1"] -ed25519 = ["embedwards25519"] -ristretto = ["embedwards25519"] +secp256k1 = ["ciphersuite-kp256", "secq256k1"] +ed25519 = ["dalek-ff-group", "embedwards25519"] +ristretto = ["dalek-ff-group", "embedwards25519"] tests = ["rand_core/getrandom"] default = ["std"] diff --git a/crypto/dkg/evrf/README.md b/crypto/dkg/evrf/README.md new file mode 100644 index 00000000..139a8124 --- /dev/null +++ b/crypto/dkg/evrf/README.md @@ -0,0 +1,50 @@ +# eVRF DKG + +The DKG from the [eVRF paper](https://eprint.iacr.org/2024/397), extended with +Verifiable Encryption premised on the same methodology present in the eVRF +paper. + +The DDH-premised VRF is used, yet the different instantiation presented in +section 6.4 premised on elliptic curve divisors. The one-round threshold DKG +presented in section 4.2 is extended, with the following changes: + +- Any threshold of `t` participants may complete the DKG. This allows an + adversary to bias the resulting key by choosing the set of participants, yet + offers a robust protocol. The caller is able to choose between robustness and + a lack of bias by completing the DKG with just `t` messages or by waiting for + all `n`. If the caller does opt for robustness, the caller must ensure + participants agree on the subset of participants who actually participated. + +- Communication of shares was prior defined as simply sending the share to the + relevant participant, with no description of the channel. Now, a pair of + ECDHs are performed on the embedded curve occurs (between the sender and the + recipient's public key), whose `x` coordinates are summed for a random, + uniform value (as an eVRF would). This value is used as a mask to encrypt the + communicated secret share, with the zero-knowledge proof proving it's + well-formed. This removes the need for a complaint round from the protocol, + allowing it to truly complete (with all recipients holding valid shares) in + just one round. + +For a gist of the verifiable encryption scheme, please see +https://gist.github.com/kayabaNerve/cfbde74b0660dfdf8dd55326d6ec33d7. Security +proofs are currently being worked on. + +--- + +This library relies on an implementation of Bulletproofs and various +zero-knowledge gadgets. This library uses +[`generalized-bulletproofs`](https://docs.rs/generalized-bulletproofs), +[`generalized-bulletproofs-circuit-abstraction`](https://docs.rs/generalized-bulletproofs-circuit-abstraction), +and +[`generalized-bulletproofs-ec-gadgets`](https://docs.rs/generalized-bulletproofs-ec-gadgets) +from the Monero project's FCMP++ codebase. These libraries have received the +following audits in the past: +- https://github.com/kayabaNerve/monero-oxide/tree/fcmp++/audits/generalized-bulletproofs +- https://github.com/kayabaNerve/monero-oxide/tree/fcmp++/audits/fcmps + +--- + +This library supports being run in no-std contexts with `alloc` when the `std` +feature (on by default) is disabled. Due to the intensity of the ZK proofs, +this isn't recommended, yet may be justified when _verifying_ posted proofs are +correct. diff --git a/crypto/dkg/evrf/src/curves.rs b/crypto/dkg/evrf/src/curves.rs new file mode 100644 index 00000000..debbe581 --- /dev/null +++ b/crypto/dkg/evrf/src/curves.rs @@ -0,0 +1,109 @@ +#[allow(unused_imports)] +use std_shims::prelude::*; +use std_shims::vec::Vec; + +use rand_core::SeedableRng; +use rand_chacha::ChaCha20Rng; + +use blake2::{ + digest::{ + generic_array::{typenum::U32, GenericArray}, + crypto_common::KeySizeUser, + KeyInit, Mac, + }, + Blake2sMac, +}; +type Blake2s256Keyed = Blake2sMac; + +use ciphersuite::{ + group::{ff::FromUniformBytes, GroupEncoding}, + Ciphersuite, +}; + +use ec_divisors::DivisorCurve; +use generalized_bulletproofs::Generators as BpGenerators; +use generalized_bulletproofs_ec_gadgets::*; + +/// A pair of curves to perform the eVRF with. +pub trait Curves { + /// The towering curve, for which the resulting key is on. + type ToweringCurve: Ciphersuite>; + /// The embedded curve which participants represent their public keys over. + type EmbeddedCurve: Ciphersuite< + G: DivisorCurve::F>, + >; + /// The parameters to use the embedded curve with the discrete-log gadget. + type EmbeddedCurveParameters: DiscreteLogParameters; +} + +/// Generators for an eVRF DKG. +/// +/// These should be kept within a static. They're non-trivial to generate. +#[derive(Clone, Debug)] +pub struct Generators(pub(crate) BpGenerators); + +impl Generators { + /// Create a new set of generators. + /// + /// This is deterministic to the towering curve's (possibly truncated) ID and generator. + pub fn new(max_threshold: u16, max_participants: u16) -> Generators { + let entropy = ::new(&{ + let mut key = GenericArray::::KeySize>::default(); + let key_len = key.len().min(::ID.len()); + { + let key: &mut [u8] = key.as_mut(); + key[.. key_len].copy_from_slice(&::ID[.. key_len]) + } + key + }) + .chain_update(::generator().to_bytes()) + .finalize() + .into_bytes(); + let mut rng = ChaCha20Rng::from_seed(entropy.into()); + + let h = crate::sample_point::(&mut rng); + let generators = + crate::Proof::::generators_to_use(max_threshold.into(), max_participants.into()); + let mut g_bold = Vec::with_capacity(generators); + let mut h_bold = Vec::with_capacity(generators); + for _ in 0 .. generators { + g_bold.push(crate::sample_point::(&mut rng)); + h_bold.push(crate::sample_point::(&mut rng)); + } + Self( + BpGenerators::new(::generator(), h, g_bold, h_bold).unwrap(), + ) + } +} + +/* TODO +/// Secp256k1, and an elliptic curve defined over its scalar field (secq256k1). +#[cfg(feature = "secp256k1")] +pub struct Secp256k1; +#[cfg(feature = "secp256k1")] +impl Curves for Secp256k1 { + type ToweringCurve = ciphersuite_kp256::Secp256k1; + type EmbeddedCurve = secq256k1::Secq256k1; + type EmbeddedCurveParameters = secq256k1::Secq256k1; +} +*/ + +/// Ed25519, and an elliptic curve defined over its scalar field (embedwards25519). +#[cfg(feature = "ed25519")] +pub struct Ed25519; +#[cfg(feature = "ed25519")] +impl Curves for Ed25519 { + type ToweringCurve = dalek_ff_group::Ed25519; + type EmbeddedCurve = embedwards25519::Embedwards25519; + type EmbeddedCurveParameters = embedwards25519::Embedwards25519; +} + +/// Ristretto, and an elliptic curve defined over its scalar field (embedwards25519). +#[cfg(any(test, feature = "ristretto"))] +pub struct Ristretto; +#[cfg(any(test, feature = "ristretto"))] +impl Curves for Ristretto { + type ToweringCurve = dalek_ff_group::Ristretto; + type EmbeddedCurve = embedwards25519::Embedwards25519; + type EmbeddedCurveParameters = embedwards25519::Embedwards25519; +} diff --git a/crypto/dkg/evrf/src/lib.rs b/crypto/dkg/evrf/src/lib.rs index 9b2f29b9..a32b964f 100644 --- a/crypto/dkg/evrf/src/lib.rs +++ b/crypto/dkg/evrf/src/lib.rs @@ -1,72 +1,12 @@ -/* - We implement a DKG using an eVRF, as detailed in the eVRF paper. For the eVRF itself, we do not - use a Paillier-based construction, nor the detailed construction premised on a Bulletproof. - - For reference, the detailed construction premised on a Bulletproof involves two curves, notated - here as `C` and `E`, where the scalar field of `C` is the field of `E`. Accordingly, Bulletproofs - over `C` can efficiently perform group operations of points of curve `E`. Each participant has a - private point (`P_i`) on curve `E` committed to over curve `C`. The eVRF selects a pair of - scalars `a, b`, where the participant proves in-Bulletproof the points `A_i, B_i` are - `a * P_i, b * P_i`. The eVRF proceeds to commit to `A_i.x + B_i.x` in a Pedersen Commitment. - - Our eVRF uses - [Generalized Bulletproofs]( - https://repo.getmonero.org/monero-project/ccs-proposals - /uploads/a9baa50c38c6312efc0fea5c6a188bb9/gbp.pdf - ). - This allows us much larger witnesses without growing the reference string, and enables us to - efficiently sample challenges off in-circuit variables (via placing the variables in a vector - commitment, then challenging from a transcript of the commitments). We proceed to use - [elliptic curve divisors]( - https://repo.getmonero.org/-/project/54/ - uploads/eb1bf5b4d4855a3480c38abf895bd8e8/Veridise_Divisor_Proofs.pdf - ) - (which require the ability to sample a challenge off in-circuit variables) to prove discrete - logarithms efficiently. - - This is done via having a private scalar (`p_i`) on curve `E`, not a private point, and - publishing the public key for it (`P_i = p_i * G`, where `G` is a generator of `E`). The eVRF - samples two points with unknown discrete logarithms `A, B`, and the circuit proves a Pedersen - Commitment commits to `(p_i * A).x + (p_i * B).x`. - - With the eVRF established, we now detail our other novel aspect. The eVRF paper expects secret - shares to be sent to the other parties yet does not detail a precise way to do so. If we - encrypted the secret shares with some stream cipher, each recipient would have to attest validity - or accuse the sender of impropriety. We want an encryption scheme where anyone can verify the - secret shares were encrypted properly, without additional info, efficiently. - - Please note from the published commitments, it's possible to calculcate a commitment to the - secret share each party should receive (`V_i`). - - We have the sender sample two scalars per recipient, denoted `x_i, y_i` (where `i` is the - recipient index). They perform the eVRF to prove a Pedersen Commitment commits to - `z_i = (x_i * P_i).x + (y_i * P_i).x` and `x_i, y_i` are the discrete logarithms of `X_i, Y_i` - over `G`. They then publish the encrypted share `s_i + z_i` and `X_i, Y_i`. - - The recipient is able to decrypt the share via calculating - `s_i - ((p_i * X_i).x + (p_i * Y_i).x)`. - - To verify the secret share, we have the `F` terms of the Pedersen Commitments revealed (where - `F, H` are generators of `C`, `F` is used for binding and `H` for blinding). This already needs - to be done for the eVRF outputs used within the DKG, in order to obtain thecommitments to the - coefficients. When we have the commitment `Z_i = ((p_i * A).x + (p_i * B).x) * F`, we simply - check `s_i * F = Z_i + V_i`. - - In order to open the Pedersen Commitments to their `F` terms, we transcript the commitments and - the claimed openings, then assign random weights to each pair of `(commitment, opening). The - prover proves knowledge of the discrete logarithm of the sum weighted commitments, minus the sum - sum weighted openings, over `H`. - - The benefit to this construction is that given an broadcast channel which is reliable and - ordered, only `t` messages must be broadcast from honest parties in order to create a `t`-of-`n` - multisig. If the encrypted secret shares were not verifiable, one would need at least `t + n` - messages to ensure every participant has a correct dealing and can participate in future - reconstructions of the secret. This would also require all `n` parties be online, whereas this is - robust to threshold `t`. -*/ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] use core::ops::Deref; -use std::{ +#[allow(unused_imports)] +use std_shims::prelude::*; +use std_shims::{ + vec::Vec, io::{self, Read, Write}, collections::{HashSet, HashMap}, }; @@ -85,14 +25,22 @@ use ciphersuite::{ }; use multiexp::multiexp_vartime; -use generalized_bulletproofs::{Generators, arithmetic_circuit_proof::*}; +use generalized_bulletproofs::arithmetic_circuit_proof::*; use ec_divisors::DivisorCurve; -use dkg::{Participant, ThresholdParams, Interpolation, ThresholdKeys}; +pub use dkg::*; -pub(crate) mod proof; +mod utils; +pub(crate) use utils::*; + +mod curves; +pub use curves::*; + +mod proof; use proof::*; -pub use proof::{EvrfCurve, EvrfGenerators}; + +#[cfg(test)] +mod tests; /// Participation in the DKG. /// @@ -106,14 +54,20 @@ pub struct Participation { impl Participation { pub fn read(reader: &mut R, n: u16) -> io::Result { + // Ban <32-bit platforms, allowing us to assume `u32` -> `usize` works + const _NO_16_BIT_PLATFORMS: [(); (usize::BITS - u32::BITS) as usize] = [(); _]; + // TODO: Replace `len` with some calculation deterministic to the params let mut len = [0; 4]; reader.read_exact(&mut len)?; let len = usize::try_from(u32::from_le_bytes(len)).expect("<32-bit platform?"); - // Don't allocate a buffer for the claimed length - // Read chunks until we reach the claimed length - // This means if we were told to read GB, we must actually be sent GB before allocating as such + /* + Don't allocate a buffer for the claimed length. + + We read chunks of a fixed-length until we reach the claimed length, preventing an adversary + from forcing us to allocate GB unless the proof is actually GB long. + */ const CHUNK_SIZE: usize = 1024; let mut proof = Vec::with_capacity(len.min(CHUNK_SIZE)); while proof.len() < len { @@ -124,7 +78,7 @@ impl Participation { } let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n)); - for i in (1 ..= n).map(Participant) { + for i in Participant::iter().take(usize::from(n)) { encrypted_secret_shares.insert(i, C::read_F(reader)?); } @@ -134,113 +88,82 @@ impl Participation { pub fn write(&self, writer: &mut W) -> io::Result<()> { writer.write_all(&u32::try_from(self.proof.len()).unwrap().to_le_bytes())?; writer.write_all(&self.proof)?; - for i in (1 ..= u16::try_from(self.encrypted_secret_shares.len()) - .expect("writing a Participation which has a n > u16::MAX")) - .map(Participant) - { + for i in Participant::iter().take(self.encrypted_secret_shares.len()) { writer.write_all(self.encrypted_secret_shares[&i].to_repr().as_ref())?; } Ok(()) } } -fn polynomial( - coefficients: &[Zeroizing], - l: Participant, -) -> Zeroizing { - let l = F::from(u64::from(u16::from(l))); - // This should never be reached since Participant is explicitly non-zero - assert!(l != F::ZERO, "zero participant passed to polynomial"); - let mut share = Zeroizing::new(F::ZERO); - for (idx, coefficient) in coefficients.iter().rev().enumerate() { - *share += coefficient.deref(); - if idx != (coefficients.len() - 1) { - *share *= l; - } - } - share -} - -#[allow(clippy::type_complexity)] -fn share_verification_statements( - rng: &mut (impl RngCore + CryptoRng), - commitments: &[C::G], - n: u16, - encryption_commitments: &[C::G], - encrypted_secret_shares: &HashMap, -) -> (C::F, Vec<(C::F, C::G)>) { - debug_assert_eq!(usize::from(n), encryption_commitments.len()); - debug_assert_eq!(usize::from(n), encrypted_secret_shares.len()); - - let mut g_scalar = C::F::ZERO; - let mut pairs = Vec::with_capacity(commitments.len() + encryption_commitments.len()); - for commitment in commitments { - pairs.push((C::F::ZERO, *commitment)); - } - - let mut weight; - for (i, enc_share) in encrypted_secret_shares { - let enc_commitment = encryption_commitments[usize::from(u16::from(*i)) - 1]; - - weight = C::F::random(&mut *rng); - - // s_i F - g_scalar += weight * enc_share; - // - Z_i - let weight = -weight; - pairs.push((weight, enc_commitment)); - // - V_i - { - let i = C::F::from(u64::from(u16::from(*i))); - // The first `commitments.len()` pairs are for the commitments - (0 .. commitments.len()).fold(weight, |exp, j| { - pairs[j].0 += exp; - exp * i - }); - } - } - - (g_scalar, pairs) -} - /// Errors from the eVRF DKG. #[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)] -pub enum EvrfError { - #[error("n, the amount of participants, exceeded a u16")] - TooManyParticipants, - #[error("the threshold t wasn't in range 1 <= t <= n")] - InvalidThreshold, +pub enum Error { + /// Too many participants were provided. + #[error("{provided} participants provided, exceeding the limit of u16::MAX")] + TooManyParticipants { + /// The amount of provided participants. + provided: usize, + }, + + /// The threshold exceeded the amount of participants. + #[error("invalid threshold (max {n}, got {t})")] + InvalidThreshold { + /// The specified threshold. + t: u16, + /// The specified total amount of participants. + n: u16, + }, + + /// A participant's public key was the identity point. #[error("a public key was the identity point")] PublicKeyWasIdentity, + + /// Participating in a DKG we aren't present in. #[error("participating in a DKG we aren't a participant in")] NotAParticipant, + + /// A participant which doesn't exist provided a participation. #[error("a participant with an unrecognized ID participated")] NonExistentParticipant, - #[error("the passed in generators did not have enough generators for this DKG")] - NotEnoughGenerators, + + /// Insufficient amount of generators for this DKG. + #[error("the passed in generators ({provided}) weren't enough for this DKG (needed {required})")] + NotEnoughGenerators { + /// The amount of generators provided. + provided: usize, + /// The amount of generators required. + required: usize, + }, } -/// The result of calling EvrfDkg::verify. -pub enum VerifyResult { - Valid(EvrfDkg), +/// The result of calling `Dkg::verify`. +pub enum VerifyResult { + /// The DKG participations were valid. + Valid(Dkg), + /// The DKG participants were invalid, identifying the faulty participants. Invalid(Vec), + /// Not enough participations were provided, yet no provided participations were faulty. NotEnoughParticipants, } -/// Struct to perform/verify the DKG with. +/// Struct representing a DKG. #[derive(Debug)] -pub struct EvrfDkg { +pub struct Dkg { t: u16, n: u16, evrf_public_keys: Vec<::G>, - group_key: C::G, - verification_shares: HashMap, + verification_shares: HashMap::G>, #[allow(clippy::type_complexity)] - encrypted_secret_shares: - HashMap::G; 2], C::F)>>, + encrypted_secret_shares: HashMap< + Participant, + HashMap< + Participant, + ([::G; 2], ::F), + >, + >, } -impl EvrfDkg { +impl Dkg { // Form the initial transcript for the proofs. fn initial_transcript( invocation: [u8; 32], @@ -260,40 +183,37 @@ impl EvrfDkg { /// /// The context MUST be unique across invocations. Reuse of context will lead to sharing /// prior-shared secrets. - /// - /// Public keys are not allowed to be the identity point. This will error if any are. pub fn participate( rng: &mut (impl RngCore + CryptoRng), - generators: &EvrfGenerators, + generators: &Generators, context: [u8; 32], t: u16, evrf_public_keys: &[::G], evrf_private_key: &Zeroizing<::F>, - ) -> Result, EvrfError> { - let Ok(n) = u16::try_from(evrf_public_keys.len()) else { Err(EvrfError::TooManyParticipants)? }; + ) -> Result, Error> { + let Ok(n) = u16::try_from(evrf_public_keys.len()) else { + Err(Error::TooManyParticipants { provided: evrf_public_keys.len() })? + }; if (t == 0) || (t > n) { - Err(EvrfError::InvalidThreshold)?; + Err(Error::InvalidThreshold { t, n })?; } if evrf_public_keys.iter().any(|key| bool::from(key.is_identity())) { - Err(EvrfError::PublicKeyWasIdentity)?; + Err(Error::PublicKeyWasIdentity)?; }; - // This also checks the private key is not 0 + // This also ensures the private key is not 0, due to the prior check the identity point wasn't + // present let evrf_public_key = ::generator() * evrf_private_key.deref(); - if !evrf_public_keys.iter().any(|key| *key == evrf_public_key) { - Err(EvrfError::NotAParticipant)?; + if !evrf_public_keys.contains(&evrf_public_key) { + Err(Error::NotAParticipant)?; }; let transcript = Self::initial_transcript(context, evrf_public_keys, t); - // Further bind to the participant index so each index gets unique generators - // This allows reusing eVRF public keys as the prover + // Bind to the participant let mut per_proof_transcript = Blake2s256::new(); per_proof_transcript.update(transcript); per_proof_transcript.update(evrf_public_key.to_bytes()); - // The above transcript is expected to be binding to all arguments here - // The generators are constant to this ciphersuite's generator, and the parameters are - // transcripted - let EvrfProveResult { coefficients, encryption_masks, proof } = match Evrf::prove( + let ProveResult { coefficients, encryption_keys, proof } = match Proof::::prove( rng, &generators.0, per_proof_transcript.finalize().into(), @@ -302,7 +222,10 @@ impl EvrfDkg { evrf_private_key, ) { Ok(res) => res, - Err(AcError::NotEnoughGenerators) => Err(EvrfError::NotEnoughGenerators)?, + Err(AcError::NotEnoughGenerators) => Err(Error::NotEnoughGenerators { + provided: generators.0.g_bold_slice().len(), + required: Proof::::generators_to_use(usize::from(t), evrf_public_keys.len()), + })?, Err( AcError::DifferingLrLengths | AcError::InconsistentAmountOfConstraints | @@ -317,14 +240,62 @@ impl EvrfDkg { }; let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n)); - for (l, encryption_mask) in (1 ..= n).map(Participant).zip(encryption_masks) { - let share = polynomial::(&coefficients, l); - encrypted_secret_shares.insert(l, *share + *encryption_mask); + for (l, encryption_key) in Participant::iter().take(usize::from(n)).zip(encryption_keys) { + let share = polynomial::<::F>(&coefficients, l); + encrypted_secret_shares.insert(l, *share + *encryption_key); } Ok(Participation { proof, encrypted_secret_shares }) } +} +/// Batch-verifiable statements to verify encrypted secret shares. +#[allow(clippy::type_complexity)] +fn verifiable_encryption_statements( + rng: &mut (impl RngCore + CryptoRng), + coefficients: &[::G], + encryption_key_commitments: &[::G], + encrypted_secret_shares: &HashMap::F>, +) -> ( + ::F, + Vec<(::F, ::G)>, +) { + let mut g_scalar = ::F::ZERO; + let mut pairs = Vec::with_capacity(coefficients.len() + encryption_key_commitments.len()); + + // Push on the commitments to the polynomial being secret-shared + for coefficient in coefficients { + // This uses `0` as we'll add to it later, given its fixed position + pairs.push((::F::ZERO, *coefficient)); + } + + for (i, encrypted_secret_share) in encrypted_secret_shares { + let encryption_key_commitment = encryption_key_commitments[usize::from(u16::from(*i)) - 1]; + + let weight = ::F::random(&mut *rng); + + /* + The encrypted secret share scaling `G`, minus the encryption key commitment, minus the + ommitment to the secret share, should equal the identity point. + + We actually subtract the encrypted share to optimize the amount of negations we perform. + */ + g_scalar -= weight * encrypted_secret_share; + pairs.push((weight, encryption_key_commitment)); + // Calculate the commitment to the secret share via the commitments to the polynomial + { + let i = ::F::from(u64::from(u16::from(*i))); + (0 .. coefficients.len()).fold(weight, |exp, j| { + pairs[j].0 += exp; + exp * i + }); + } + } + + (g_scalar, pairs) +} + +impl Dkg { /// Check if a batch of `Participation`s are valid. /// /// If any `Participation` is invalid, the list of all invalid participants will be returned. @@ -336,22 +307,24 @@ impl EvrfDkg { /// participate. pub fn verify( rng: &mut (impl RngCore + CryptoRng), - generators: &EvrfGenerators, + generators: &Generators, context: [u8; 32], t: u16, evrf_public_keys: &[::G], - participations: &HashMap>, - ) -> Result, EvrfError> { - let Ok(n) = u16::try_from(evrf_public_keys.len()) else { Err(EvrfError::TooManyParticipants)? }; + participations: &HashMap>, + ) -> Result, Error> { + let Ok(n) = u16::try_from(evrf_public_keys.len()) else { + Err(Error::TooManyParticipants { provided: evrf_public_keys.len() })? + }; if (t == 0) || (t > n) { - Err(EvrfError::InvalidThreshold)?; + Err(Error::InvalidThreshold { t, n })?; } if evrf_public_keys.iter().any(|key| bool::from(key.is_identity())) { - Err(EvrfError::PublicKeyWasIdentity)?; + Err(Error::PublicKeyWasIdentity)?; }; for i in participations.keys() { if u16::from(*i) > n { - Err(EvrfError::NonExistentParticipant)?; + Err(Error::NonExistentParticipant)?; } } @@ -360,7 +333,7 @@ impl EvrfDkg { let transcript = Self::initial_transcript(context, evrf_public_keys, t); - let mut evrf_verifier = Generators::batch_verifier(); + let mut evrf_verifier = generalized_bulletproofs::Generators::batch_verifier(); for (i, participation) in participations { let evrf_public_key = evrf_public_keys[usize::from(u16::from(*i)) - 1]; @@ -370,7 +343,7 @@ impl EvrfDkg { // Clone the verifier so if this proof is faulty, it doesn't corrupt the verifier let mut verifier_clone = evrf_verifier.clone(); - let Ok(data) = Evrf::::verify( + let Ok(data) = Proof::::verify( rng, &generators.0, &mut verifier_clone, @@ -396,8 +369,8 @@ impl EvrfDkg { if faulty.contains(i) { continue; } - let mut evrf_verifier = Generators::batch_verifier(); - Evrf::::verify( + let mut evrf_verifier = generalized_bulletproofs::Generators::batch_verifier(); + Proof::::verify( rng, &generators.0, &mut evrf_verifier, @@ -423,17 +396,13 @@ impl EvrfDkg { { let mut share_verification_statements_actual = HashMap::with_capacity(valid.len()); if !{ - let mut g_scalar = C::F::ZERO; + let mut g_scalar = ::F::ZERO; let mut pairs = Vec::with_capacity(valid.len() * (usize::from(t) + evrf_public_keys.len())); for (i, (encrypted_secret_shares, data)) in &valid { - let (this_g_scalar, mut these_pairs) = share_verification_statements::( + let (this_g_scalar, mut these_pairs) = verifiable_encryption_statements::( &mut *rng, &data.coefficients, - evrf_public_keys - .len() - .try_into() - .expect("n prior checked to be <= u16::MAX couldn't be converted to a u16"), - &data.encryption_commitments, + &data.encryption_key_commitments, encrypted_secret_shares, ); // Queue this into our batch @@ -452,18 +421,22 @@ impl EvrfDkg { We calculcate verification shares as the sum of the encrypted scalars, minus their masks. This only does one scalar multiplication, and `1+t` point additions (with one negation), and is accordingly much cheaper than interpolating the commitments. - This is only possible because already interpolated the commitments to verify the + This is only possible because we already interpolated the commitments to verify the encrypted secret share. */ - let sum_encrypted_secret_share = - sum_encrypted_secret_shares.get(j).copied().unwrap_or(C::F::ZERO); - let sum_mask = sum_masks.get(j).copied().unwrap_or(C::G::identity()); + let sum_encrypted_secret_share = sum_encrypted_secret_shares + .get(j) + .copied() + .unwrap_or(::F::ZERO); + let sum_mask = + sum_masks.get(j).copied().unwrap_or(::G::identity()); sum_encrypted_secret_shares.insert(*j, sum_encrypted_secret_share + enc_share); let j_index = usize::from(u16::from(*j)) - 1; - sum_masks.insert(*j, sum_mask + data.encryption_commitments[j_index]); + sum_masks.insert(*j, sum_mask + data.encryption_key_commitments[j_index]); - formatted_encrypted_secret_shares.insert(*j, (data.ecdh_keys[j_index], *enc_share)); + formatted_encrypted_secret_shares + .insert(*j, (data.ecdh_commitments[j_index], *enc_share)); } all_encrypted_secret_shares.insert(*i, formatted_encrypted_secret_shares); } @@ -517,69 +490,74 @@ impl EvrfDkg { } } - // If we now have >= t participations, calculate the group key and verification shares - - // The group key is the sum of the zero coefficients - let group_key = valid.values().map(|(_, evrf_data)| evrf_data.coefficients[0]).sum::(); + // If we now have >= t participations, output the result // Calculate each user's verification share let mut verification_shares = HashMap::with_capacity(usize::from(n)); - for i in (1 ..= n).map(Participant) { - verification_shares - .insert(i, (C::generator() * sum_encrypted_secret_shares[&i]) - sum_masks[&i]); + for i in Participant::iter().take(usize::from(n)) { + verification_shares.insert( + i, + (::generator() * sum_encrypted_secret_shares[&i]) - + sum_masks[&i], + ); } - Ok(VerifyResult::Valid(EvrfDkg { + Ok(VerifyResult::Valid(Dkg { t, n, evrf_public_keys: evrf_public_keys.to_vec(), - group_key, verification_shares, encrypted_secret_shares: all_encrypted_secret_shares, })) } + /// Retrieve keys from a successful DKG. + /// + /// This will return _all_ keys belong to the participant. pub fn keys( &self, evrf_private_key: &Zeroizing<::F>, - ) -> Vec> { + ) -> Vec> { let evrf_public_key = ::generator() * evrf_private_key.deref(); let mut is = Vec::with_capacity(1); - for (i, evrf_key) in self.evrf_public_keys.iter().enumerate() { + for (i, evrf_key) in Participant::iter().zip(self.evrf_public_keys.iter()) { if *evrf_key == evrf_public_key { - let i = u16::try_from(i).expect("n <= u16::MAX yet i > u16::MAX?"); - let i = Participant(1 + i); is.push(i); } } let mut res = Vec::with_capacity(is.len()); for i in is { - let mut secret_share = Zeroizing::new(C::F::ZERO); + let mut secret_share = Zeroizing::new(::F::ZERO); for shares in self.encrypted_secret_shares.values() { - let (ecdh_keys, enc_share) = shares[&i]; + let (ecdh_commitments, encrypted_secret_share) = shares[&i]; - let mut ecdh = Zeroizing::new(C::F::ZERO); - for point in ecdh_keys { + let mut ecdh = Zeroizing::new(::F::ZERO); + for point in ecdh_commitments { let (mut x, mut y) = ::G::to_xy(point * evrf_private_key.deref()).unwrap(); *ecdh += x; x.zeroize(); y.zeroize(); } - *secret_share += enc_share - ecdh.deref(); + *secret_share += encrypted_secret_share - ecdh.deref(); } + debug_assert_eq!( + self.verification_shares[&i], + ::G::generator() * secret_share.deref() + ); - debug_assert_eq!(self.verification_shares[&i], C::generator() * secret_share.deref()); - - res.push(ThresholdKeys::from(ThresholdCore { - params: ThresholdParams::new(self.t, self.n, i).unwrap(), - interpolation: Interpolation::Lagrange, - secret_share, - group_key: self.group_key, - verification_shares: self.verification_shares.clone(), - })); + res.push( + ThresholdKeys::new( + ThresholdParams::new(self.t, self.n, i).unwrap(), + Interpolation::Lagrange, + secret_share, + self.verification_shares.clone(), + ) + .unwrap(), + ); } + res } } diff --git a/crypto/dkg/evrf/src/proof.rs b/crypto/dkg/evrf/src/proof.rs deleted file mode 100644 index 9c16fec6..00000000 --- a/crypto/dkg/evrf/src/proof.rs +++ /dev/null @@ -1,690 +0,0 @@ -use core::{marker::PhantomData, ops::Deref, fmt}; - -use zeroize::{Zeroize, Zeroizing}; - -use rand_core::{RngCore, CryptoRng, SeedableRng}; -use rand_chacha::ChaCha20Rng; - -use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; - -use blake2::{Digest, Blake2s256}; -use ciphersuite::{ - group::{ff::Field, Group, GroupEncoding}, - Ciphersuite, -}; - -use generalized_bulletproofs::{ - *, - transcript::{Transcript as ProverTranscript, VerifierTranscript}, - arithmetic_circuit_proof::*, -}; -use generalized_bulletproofs_circuit_abstraction::*; - -use ec_divisors::{DivisorCurve, ScalarDecomposition}; -use generalized_bulletproofs_ec_gadgets::*; - -/// A pair of curves to perform the eVRF with. -pub trait EvrfCurve: Ciphersuite { - type EmbeddedCurve: Ciphersuite::F>>; - type EmbeddedCurveParameters: DiscreteLogParameters; -} - -#[cfg(feature = "evrf-secp256k1")] -impl EvrfCurve for ciphersuite::Secp256k1 { - type EmbeddedCurve = secq256k1::Secq256k1; - type EmbeddedCurveParameters = secq256k1::Secq256k1; -} - -#[cfg(feature = "evrf-ed25519")] -impl EvrfCurve for ciphersuite::Ed25519 { - type EmbeddedCurve = embedwards25519::Embedwards25519; - type EmbeddedCurveParameters = embedwards25519::Embedwards25519; -} - -#[cfg(feature = "evrf-ristretto")] -impl EvrfCurve for ciphersuite::Ristretto { - type EmbeddedCurve = embedwards25519::Embedwards25519; - type EmbeddedCurveParameters = embedwards25519::Embedwards25519; -} - -fn sample_point(rng: &mut (impl RngCore + CryptoRng)) -> C::G { - let mut repr = ::Repr::default(); - loop { - rng.fill_bytes(repr.as_mut()); - if let Ok(point) = C::read_G(&mut repr.as_ref()) { - if bool::from(!point.is_identity()) { - return point; - } - } - } -} - -/// Generators for eVRF proof. -#[derive(Clone, Debug)] -pub struct EvrfGenerators(pub(crate) Generators); - -impl EvrfGenerators { - /// Create a new set of generators. - pub fn new(max_threshold: u16, max_participants: u16) -> EvrfGenerators { - let g = C::generator(); - let mut rng = ChaCha20Rng::from_seed(Blake2s256::digest(g.to_bytes()).into()); - let h = sample_point::(&mut rng); - let (_, generators) = - Evrf::::muls_and_generators_to_use(max_threshold.into(), max_participants.into()); - let mut g_bold = vec![]; - let mut h_bold = vec![]; - for _ in 0 .. generators { - g_bold.push(sample_point::(&mut rng)); - h_bold.push(sample_point::(&mut rng)); - } - Self(Generators::new(g, h, g_bold, h_bold).unwrap()) - } -} - -/// The result of proving for an eVRF. -pub(crate) struct EvrfProveResult { - /// The coefficients for use in the DKG. - pub(crate) coefficients: Vec>, - /// The masks to encrypt secret shares with. - pub(crate) encryption_masks: Vec>, - /// The proof itself. - pub(crate) proof: Vec, -} - -/// The result of verifying an eVRF. -pub(crate) struct EvrfVerifyResult { - /// The commitments to the coefficients for use in the DKG. - pub(crate) coefficients: Vec, - /// The ephemeral public keys to perform ECDHs with - pub(crate) ecdh_keys: Vec<[::G; 2]>, - /// The commitments to the masks used to encrypt secret shares with. - pub(crate) encryption_commitments: Vec, -} - -impl fmt::Debug for EvrfVerifyResult { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("EvrfVerifyResult").finish_non_exhaustive() - } -} - -/// A struct to prove/verify eVRFs with. -pub(crate) struct Evrf(PhantomData); -impl Evrf { - // Sample uniform points (via rejection-sampling) on the embedded elliptic curve - fn transcript_to_points( - seed: [u8; 32], - coefficients: usize, - ) -> Vec<::G> { - // We need to do two Diffie-Hellman's per coefficient in order to achieve an unbiased result - let quantity = 2 * coefficients; - - let mut rng = ChaCha20Rng::from_seed(seed); - let mut res = Vec::with_capacity(quantity); - for _ in 0 .. quantity { - res.push(sample_point::(&mut rng)); - } - res - } - - /// Read a Variable from a theoretical vector commitment tape - fn read_one_from_tape(generators_to_use: usize, start: &mut usize) -> Variable { - // Each commitment has twice as many variables as generators in use - let commitment = *start / generators_to_use; - // The index will be less than the amount of generators in use, as half are left and half are - // right - let index = *start % generators_to_use; - let res = Variable::CG { commitment, index }; - *start += 1; - res - } - - /// Read a set of variables from a theoretical vector commitment tape - fn read_from_tape( - generators_to_use: usize, - start: &mut usize, - ) -> GenericArray { - let mut buf = Vec::with_capacity(N::USIZE); - for _ in 0 .. N::USIZE { - buf.push(Self::read_one_from_tape(generators_to_use, start)); - } - GenericArray::from_slice(&buf).clone() - } - - /// Read `PointWithDlog`s, which share a discrete logarithm, from the theoretical vector - /// commitment tape. - fn point_with_dlogs( - start: &mut usize, - quantity: usize, - generators_to_use: usize, - ) -> Vec> { - // We define a serialized tape of the discrete logarithm, then for each divisor/point, we push: - // zero, x**i, y x**i, y, x_coord, y_coord - // We then chunk that into vector commitments - // Here, we take the assumed layout and generate the expected `Variable`s for this layout - - let dlog = Self::read_from_tape(generators_to_use, start); - - let mut res = Vec::with_capacity(quantity); - let mut read_point_with_dlog = || { - let zero = Self::read_one_from_tape(generators_to_use, start); - let x_from_power_of_2 = Self::read_from_tape(generators_to_use, start); - let yx = Self::read_from_tape(generators_to_use, start); - let y = Self::read_one_from_tape(generators_to_use, start); - let divisor = Divisor { zero, x_from_power_of_2, yx, y }; - - let point = ( - Self::read_one_from_tape(generators_to_use, start), - Self::read_one_from_tape(generators_to_use, start), - ); - - res.push(PointWithDlog { dlog: dlog.clone(), divisor, point }); - }; - - for _ in 0 .. quantity { - read_point_with_dlog(); - } - res - } - - fn muls_and_generators_to_use(coefficients: usize, ecdhs: usize) -> (usize, usize) { - const MULS_PER_DH: usize = 7; - // 1 DH to prove the discrete logarithm corresponds to the eVRF public key - // 2 DHs per generated coefficient - // 2 DHs per generated ECDH - let expected_muls = MULS_PER_DH * (1 + (2 * coefficients) + (2 * 2 * ecdhs)); - let generators_to_use = { - let mut padded_pow_of_2 = 1; - while padded_pow_of_2 < expected_muls { - padded_pow_of_2 <<= 1; - } - // This may as small as 16, which would create an excessive amount of vector commitments - // We set a floor of 2048 rows for bandwidth reasons - padded_pow_of_2.max(2048) - }; - (expected_muls, generators_to_use) - } - - fn circuit( - curve_spec: &CurveSpec, - evrf_public_key: (C::F, C::F), - coefficients: usize, - ecdh_commitments: &[[(C::F, C::F); 2]], - generator_tables: &[&GeneratorTable], - circuit: &mut Circuit, - transcript: &mut impl Transcript, - ) { - let (expected_muls, generators_to_use) = - Self::muls_and_generators_to_use(coefficients, ecdh_commitments.len()); - let (challenge, challenged_generators) = - circuit.discrete_log_challenge(transcript, curve_spec, generator_tables); - debug_assert_eq!(challenged_generators.len(), 1 + (2 * coefficients) + ecdh_commitments.len()); - - // The generators tables/challenged generators are expected to have the following layouts - // G, coefficients * [A, B], ecdhs * [P] - #[allow(non_snake_case)] - let challenged_G = &challenged_generators[0]; - - // Execute the circuit for the coefficients - let mut tape_pos = 0; - { - let mut point_with_dlogs = - Self::point_with_dlogs(&mut tape_pos, 1 + (2 * coefficients), generators_to_use) - .into_iter(); - - // Verify the discrete logarithm is in the fact the discrete logarithm of the eVRF public key - let point = circuit.discrete_log( - curve_spec, - point_with_dlogs.next().unwrap(), - &challenge, - challenged_G, - ); - circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(evrf_public_key.0)); - circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(evrf_public_key.1)); - - // Verify the DLog claims against the sampled points - for (i, pair) in challenged_generators[1 ..].chunks(2).take(coefficients).enumerate() { - let mut lincomb = LinComb::empty(); - debug_assert_eq!(pair.len(), 2); - for challenged_generator in pair { - let point = circuit.discrete_log( - curve_spec, - point_with_dlogs.next().unwrap(), - &challenge, - challenged_generator, - ); - // For each point in this pair, add its x coordinate to a lincomb - lincomb = lincomb.term(C::F::ONE, point.x()); - } - // Constrain the sum of the two x coordinates to be equal to the value in the Pedersen - // commitment - circuit.equality(lincomb, &LinComb::from(Variable::V(i))); - } - debug_assert!(point_with_dlogs.next().is_none()); - } - - // Now execute the circuit for the ECDHs - let mut challenged_generators = challenged_generators.iter().skip(1 + (2 * coefficients)); - for (i, ecdh) in ecdh_commitments.iter().enumerate() { - let challenged_generator = challenged_generators.next().unwrap(); - let mut lincomb = LinComb::empty(); - for ecdh in ecdh { - let mut point_with_dlogs = - Self::point_with_dlogs(&mut tape_pos, 2, generators_to_use).into_iter(); - - // One proof of the ECDH secret * G for the commitment published - let point = circuit.discrete_log( - curve_spec, - point_with_dlogs.next().unwrap(), - &challenge, - challenged_G, - ); - circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(ecdh.0)); - circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(ecdh.1)); - - // One proof of the ECDH secret * P for the ECDH - let point = circuit.discrete_log( - curve_spec, - point_with_dlogs.next().unwrap(), - &challenge, - challenged_generator, - ); - // For each point in this pair, add its x coordinate to a lincomb - lincomb = lincomb.term(C::F::ONE, point.x()); - } - - // Constrain the sum of the two x coordinates to be equal to the value in the Pedersen - // commitment - circuit.equality(lincomb, &LinComb::from(Variable::V(coefficients + i))); - } - - debug_assert_eq!(expected_muls, circuit.muls()); - debug_assert!(challenged_generators.next().is_none()); - } - - /// Prove a point on an elliptic curve had its discrete logarithm generated via an eVRF. - pub(crate) fn prove( - rng: &mut (impl RngCore + CryptoRng), - generators: &Generators, - transcript: [u8; 32], - coefficients: usize, - ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], - evrf_private_key: &Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, - ) -> Result, AcError> { - let curve_spec = CurveSpec { - a: <::EmbeddedCurve as Ciphersuite>::G::a(), - b: <::EmbeddedCurve as Ciphersuite>::G::b(), - }; - - // A tape of the discrete logarithm, then [zero, x**i, y x**i, y, x_coord, y_coord] - let mut vector_commitment_tape = vec![]; - - let mut generator_tables = Vec::with_capacity(1 + (2 * coefficients) + ecdh_public_keys.len()); - - // A function to calculate a divisor and push it onto the tape - // This defines a vec, divisor_points, outside of the fn to reuse its allocation - let mut divisor = - |vector_commitment_tape: &mut Vec<_>, - dlog: &ScalarDecomposition<<::EmbeddedCurve as Ciphersuite>::F>, - push_generator: bool, - generator: <::EmbeddedCurve as Ciphersuite>::G, - dh: <::EmbeddedCurve as Ciphersuite>::G| { - if push_generator { - let (x, y) = ::G::to_xy(generator).unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } - - let mut divisor = dlog.scalar_mul_divisor(generator).normalize_x_coefficient(); - - vector_commitment_tape.push(divisor.zero_coefficient); - - for coefficient in divisor.x_coefficients.iter().skip(1) { - vector_commitment_tape.push(*coefficient); - } - for _ in divisor.x_coefficients.len() .. - ::XCoefficientsMinusOne::USIZE - { - vector_commitment_tape.push(::F::ZERO); - } - - for coefficient in divisor.yx_coefficients.first().unwrap_or(&vec![]) { - vector_commitment_tape.push(*coefficient); - } - for _ in divisor.yx_coefficients.first().unwrap_or(&vec![]).len() .. - ::YxCoefficients::USIZE - { - vector_commitment_tape.push(::F::ZERO); - } - - vector_commitment_tape - .push(divisor.y_coefficients.first().copied().unwrap_or(::F::ZERO)); - - divisor.zeroize(); - drop(divisor); - - let (x, y) = ::G::to_xy(dh).unwrap(); - vector_commitment_tape.push(x); - vector_commitment_tape.push(y); - - (x, y) - }; - - // Start with the coefficients - let evrf_public_key; - let mut actual_coefficients = Vec::with_capacity(coefficients); - { - // This is checked at a higher level - let dlog = - ScalarDecomposition::<::F>::new(**evrf_private_key) - .expect("eVRF private key was zero"); - let points = Self::transcript_to_points(transcript, coefficients); - - // Start by pushing the discrete logarithm onto the tape - for coefficient in dlog.decomposition() { - vector_commitment_tape.push(<_>::from(*coefficient)); - } - - // Push a divisor for proving that we're using the correct scalar - evrf_public_key = divisor( - &mut vector_commitment_tape, - &dlog, - true, - <::EmbeddedCurve as Ciphersuite>::generator(), - <::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref(), - ); - - // Push a divisor for each point we use in the eVRF - for pair in points.chunks(2) { - let mut res = Zeroizing::new(C::F::ZERO); - for point in pair { - let (dh_x, _) = divisor( - &mut vector_commitment_tape, - &dlog, - true, - *point, - *point * evrf_private_key.deref(), - ); - *res += dh_x; - } - actual_coefficients.push(res); - } - debug_assert_eq!(actual_coefficients.len(), coefficients); - } - - // Now do the ECDHs for the encryption - let mut encryption_masks = Vec::with_capacity(ecdh_public_keys.len()); - let mut ecdh_commitments = Vec::with_capacity(2 * ecdh_public_keys.len()); - let mut ecdh_commitments_xy = Vec::with_capacity(ecdh_public_keys.len()); - for ecdh_public_key in ecdh_public_keys { - ecdh_commitments_xy.push([(C::F::ZERO, C::F::ZERO); 2]); - - let mut res = Zeroizing::new(C::F::ZERO); - for j in 0 .. 2 { - let mut ecdh_private_key; - loop { - ecdh_private_key = ::F::random(&mut *rng); - // Generate a non-0 ECDH private key, as necessary to not produce an identity output - // Identity isn't representable with the divisors, hence the explicit effort - if bool::from(!ecdh_private_key.is_zero()) { - break; - } - } - let dlog = - ScalarDecomposition::<::F>::new(ecdh_private_key) - .expect("ECDH private key was zero"); - let ecdh_commitment = ::generator() * ecdh_private_key; - ecdh_commitments.push(ecdh_commitment); - ecdh_commitments_xy.last_mut().unwrap()[j] = - <::G as DivisorCurve>::to_xy(ecdh_commitment).unwrap(); - - // Start by pushing the discrete logarithm onto the tape - for coefficient in dlog.decomposition() { - vector_commitment_tape.push(<_>::from(*coefficient)); - } - - // Push a divisor for proving that we're using the correct scalar for the commitment - divisor( - &mut vector_commitment_tape, - &dlog, - false, - <::EmbeddedCurve as Ciphersuite>::generator(), - <::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key, - ); - // Push a divisor for the key we're performing the ECDH with - let (dh_x, _) = divisor( - &mut vector_commitment_tape, - &dlog, - j == 0, - *ecdh_public_key, - *ecdh_public_key * ecdh_private_key, - ); - *res += dh_x; - - ecdh_private_key.zeroize(); - } - encryption_masks.push(res); - } - debug_assert_eq!(encryption_masks.len(), ecdh_public_keys.len()); - - // Now that we have the vector commitment tape, chunk it - let (_, generators_to_use) = - Self::muls_and_generators_to_use(coefficients, ecdh_public_keys.len()); - - let mut vector_commitments = - Vec::with_capacity(vector_commitment_tape.len().div_ceil(generators_to_use)); - for chunk in vector_commitment_tape.chunks(generators_to_use) { - let g_values = chunk[.. generators_to_use.min(chunk.len())].to_vec().into(); - vector_commitments.push(PedersenVectorCommitment { g_values, mask: C::F::random(&mut *rng) }); - } - - vector_commitment_tape.zeroize(); - drop(vector_commitment_tape); - - let mut commitments = Vec::with_capacity(coefficients + ecdh_public_keys.len()); - for coefficient in &actual_coefficients { - commitments.push(PedersenCommitment { value: **coefficient, mask: C::F::random(&mut *rng) }); - } - for enc_mask in &encryption_masks { - commitments.push(PedersenCommitment { value: **enc_mask, mask: C::F::random(&mut *rng) }); - } - - let mut transcript = ProverTranscript::new(transcript); - let commited_commitments = transcript.write_commitments( - vector_commitments - .iter() - .map(|commitment| { - commitment - .commit(generators.g_bold_slice(), generators.h()) - .ok_or(AcError::NotEnoughGenerators) - }) - .collect::>()?, - commitments - .iter() - .map(|commitment| commitment.commit(generators.g(), generators.h())) - .collect(), - ); - for ecdh_commitment in ecdh_commitments { - transcript.push_point(ecdh_commitment); - } - - let mut circuit = Circuit::prove(vector_commitments, commitments.clone()); - Self::circuit( - &curve_spec, - evrf_public_key, - coefficients, - &ecdh_commitments_xy, - &generator_tables.iter().collect::>(), - &mut circuit, - &mut transcript, - ); - - let (statement, Some(witness)) = circuit - .statement( - generators.reduce(generators_to_use).ok_or(AcError::NotEnoughGenerators)?, - commited_commitments, - ) - .unwrap() - else { - panic!("proving yet wasn't yielded the witness"); - }; - statement.prove(&mut *rng, &mut transcript, witness).unwrap(); - - // Push the reveal onto the transcript - for commitment in &commitments { - transcript.push_point(generators.g() * commitment.value); - } - - // Define a weight to aggregate the commitments with - let mut agg_weights = Vec::with_capacity(commitments.len()); - agg_weights.push(C::F::ONE); - while agg_weights.len() < commitments.len() { - agg_weights.push(transcript.challenge::()); - } - let mut x = commitments - .iter() - .zip(&agg_weights) - .map(|(commitment, weight)| commitment.mask * *weight) - .sum::(); - - // Do a Schnorr PoK for the randomness of the aggregated Pedersen commitment - let mut r = C::F::random(&mut *rng); - transcript.push_point(generators.h() * r); - let c = transcript.challenge::(); - transcript.push_scalar(r + (c * x)); - r.zeroize(); - x.zeroize(); - - Ok(EvrfProveResult { - coefficients: actual_coefficients, - encryption_masks, - proof: transcript.complete(), - }) - } - - /// Verify an eVRF proof, returning the commitments output. - #[allow(clippy::too_many_arguments)] - pub(crate) fn verify( - rng: &mut (impl RngCore + CryptoRng), - generators: &Generators, - verifier: &mut BatchVerifier, - transcript: [u8; 32], - coefficients: usize, - ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], - evrf_public_key: <::EmbeddedCurve as Ciphersuite>::G, - proof: &[u8], - ) -> Result, ()> { - let curve_spec = CurveSpec { - a: <::EmbeddedCurve as Ciphersuite>::G::a(), - b: <::EmbeddedCurve as Ciphersuite>::G::b(), - }; - - let mut generator_tables = Vec::with_capacity(1 + (2 * coefficients) + ecdh_public_keys.len()); - { - let (x, y) = - ::G::to_xy(::generator()) - .unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } - let points = Self::transcript_to_points(transcript, coefficients); - for generator in points { - let (x, y) = ::G::to_xy(generator).unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } - for generator in ecdh_public_keys { - let (x, y) = ::G::to_xy(*generator).unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } - - let (_, generators_to_use) = - Self::muls_and_generators_to_use(coefficients, ecdh_public_keys.len()); - - let mut transcript = VerifierTranscript::new(transcript, proof); - - let dlog_len = ::ScalarBits::USIZE; - let divisor_len = 1 + - ::XCoefficientsMinusOne::USIZE + - ::YxCoefficients::USIZE + - 1; - let dlog_proof_len = divisor_len + 2; - - let coeffs_vc_variables = dlog_len + ((1 + (2 * coefficients)) * dlog_proof_len); - let ecdhs_vc_variables = ((2 * ecdh_public_keys.len()) * dlog_len) + - ((2 * 2 * ecdh_public_keys.len()) * dlog_proof_len); - let vcs = (coeffs_vc_variables + ecdhs_vc_variables).div_ceil(generators_to_use); - - let all_commitments = - transcript.read_commitments(vcs, coefficients + ecdh_public_keys.len()).map_err(|_| ())?; - let commitments = all_commitments.V().to_vec(); - - let mut ecdh_keys = Vec::with_capacity(ecdh_public_keys.len()); - let mut ecdh_keys_xy = Vec::with_capacity(ecdh_public_keys.len()); - for _ in 0 .. ecdh_public_keys.len() { - let ecdh_keys_i = [ - transcript.read_point::().map_err(|_| ())?, - transcript.read_point::().map_err(|_| ())?, - ]; - ecdh_keys.push(ecdh_keys_i); - // This bans zero ECDH keys - ecdh_keys_xy.push([ - <::G as DivisorCurve>::to_xy(ecdh_keys_i[0]).ok_or(())?, - <::G as DivisorCurve>::to_xy(ecdh_keys_i[1]).ok_or(())?, - ]); - } - - let mut circuit = Circuit::verify(); - Self::circuit( - &curve_spec, - ::G::to_xy(evrf_public_key).ok_or(())?, - coefficients, - &ecdh_keys_xy, - &generator_tables.iter().collect::>(), - &mut circuit, - &mut transcript, - ); - - let (statement, None) = - circuit.statement(generators.reduce(generators_to_use).ok_or(())?, all_commitments).unwrap() - else { - panic!("verifying yet was yielded a witness"); - }; - - statement.verify(rng, verifier, &mut transcript).map_err(|_| ())?; - - // Read the openings for the commitments - let mut openings = Vec::with_capacity(commitments.len()); - for _ in 0 .. commitments.len() { - openings.push(transcript.read_point::().map_err(|_| ())?); - } - - // Verify the openings of the commitments - let mut agg_weights = Vec::with_capacity(commitments.len()); - agg_weights.push(C::F::ONE); - while agg_weights.len() < commitments.len() { - agg_weights.push(transcript.challenge::()); - } - - let sum_points = - openings.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::(); - let sum_commitments = - commitments.into_iter().zip(agg_weights).map(|(point, weight)| point * weight).sum::(); - #[allow(non_snake_case)] - let A = sum_commitments - sum_points; - - #[allow(non_snake_case)] - let R = transcript.read_point::().map_err(|_| ())?; - let c = transcript.challenge::(); - let s = transcript.read_scalar::().map_err(|_| ())?; - - // Doesn't batch verify this as we can't access the internals of the GBP batch verifier - if (R + (A * c)) != (generators.h() * s) { - Err(())?; - } - - if !transcript.complete().is_empty() { - Err(())? - }; - - let encryption_commitments = openings[coefficients ..].to_vec(); - let coefficients = openings[.. coefficients].to_vec(); - Ok(EvrfVerifyResult { coefficients, ecdh_keys, encryption_commitments }) - } -} diff --git a/crypto/dkg/evrf/src/proof/mod.rs b/crypto/dkg/evrf/src/proof/mod.rs new file mode 100644 index 00000000..a5e2e67a --- /dev/null +++ b/crypto/dkg/evrf/src/proof/mod.rs @@ -0,0 +1,684 @@ +use core::{marker::PhantomData, ops::Deref, fmt}; +#[allow(unused_imports)] +use std_shims::prelude::*; +use std_shims::{vec, vec::Vec}; + +use zeroize::Zeroizing; + +use rand_core::{RngCore, CryptoRng, SeedableRng}; +use rand_chacha::ChaCha20Rng; + +use ciphersuite::{group::ff::Field, Ciphersuite}; + +use generalized_bulletproofs::{ + Generators, BatchVerifier, PedersenCommitment, PedersenVectorCommitment, + transcript::{Transcript as ProverTranscript, VerifierTranscript}, + arithmetic_circuit_proof::*, +}; +use generalized_bulletproofs_circuit_abstraction::{Transcript, Circuit as BpCircuit}; + +use ec_divisors::{DivisorCurve, ScalarDecomposition}; +use generalized_bulletproofs_ec_gadgets::{ + CurveSpec, DiscreteLogChallenge, ChallengedGenerator, EcDlogGadgets, +}; + +use crate::Curves; + +mod tape; +use tape::*; + +type EmbeddedPoint = ( + <<::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement, + <<::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement, +); + +#[allow(non_snake_case)] +struct Circuit< + 'a, + C: Curves, + CG: Iterator< + Item = ChallengedGenerator<::F, C::EmbeddedCurveParameters>, + >, +> { + curve_spec: &'a CurveSpec<<::G as DivisorCurve>::FieldElement>, + circuit: &'a mut BpCircuit, + challenge: DiscreteLogChallenge<::F, C::EmbeddedCurveParameters>, + challenged_G: + ChallengedGenerator<::F, C::EmbeddedCurveParameters>, + challenged_generators: &'a mut CG, + tape: Tape, + pedersen_commitment_tape: PedersenCommitmentTape, +} + +impl< + 'a, + C: Curves, + CG: Iterator< + Item = ChallengedGenerator<::F, C::EmbeddedCurveParameters>, + >, + > Circuit<'a, C, CG> +{ + /// Generate coefficients for secret-sharing via an eVRF. + /// + /// This follows the methodology of Protocol 5 from the + /// [eVRF paper](https://eprint.iacr.org/2024/397.pdf). + fn coefficients(&mut self, evrf_public_key: EmbeddedPoint, coefficients: usize) { + /* + Read the opening of the prover's eVRF public key, along with all the proofs for the eVRF. + Each invocation of the eVRF requires performing _two_ Diffie-Hellmans against + uniformly-sampled points. + */ + let mut point_with_dlogs = self.tape.read_points_with_common_dlog::(1 + (2 * coefficients)); + + // Assert this discrete logarithm opens the prover's public key + let point = self.circuit.discrete_log( + self.curve_spec, + point_with_dlogs.next().unwrap(), + &self.challenge, + &self.challenged_G, + ); + self.circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(evrf_public_key.0)); + self.circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(evrf_public_key.1)); + + // Verify the eVRF invocations + for _ in 0 .. coefficients { + let mut lincomb = LinComb::empty(); + for challenged_generator in + [self.challenged_generators.next().unwrap(), self.challenged_generators.next().unwrap()] + { + let point = self.circuit.discrete_log( + self.curve_spec, + point_with_dlogs.next().unwrap(), + &self.challenge, + &challenged_generator, + ); + lincomb = lincomb.term(::F::ONE, point.x()); + } + /* + Constrain the sum of the two `x` coordinates to be equal to the value committed to in a + Pedersen commitment + */ + self.circuit.equality( + lincomb, + &LinComb::from(self.pedersen_commitment_tape.allocate_pedersen_commitment()), + ); + } + debug_assert!(point_with_dlogs.next().is_none()); + } + + /// Sample an encryption key, proving it's correctly-formed and committed to within a Pedersen + /// commitment. + fn verifiable_encryption(&mut self, ecdh_commitments: &[EmbeddedPoint; 2]) { + // Read the public key used for this encryption + let challenged_public_key = self.challenged_generators.next().unwrap(); + // We perform two separate ECDHs, the sum of their `x` coordinates being our encryption key + let mut lincomb = LinComb::empty(); + for ecdh_commitment in ecdh_commitments { + // We open the posted commitment to the ephemeral secret used, and the ECDH value + let mut point_with_dlogs = self.tape.read_points_with_common_dlog::(2); + + let point = self.circuit.discrete_log( + self.curve_spec, + point_with_dlogs.next().unwrap(), + &self.challenge, + &self.challenged_G, + ); + // Ensure this equals the publicly posted commitment + self + .circuit + .equality(LinComb::from(point.x()), &LinComb::empty().constant(ecdh_commitment.0)); + self + .circuit + .equality(LinComb::from(point.y()), &LinComb::empty().constant(ecdh_commitment.1)); + + let point = self.circuit.discrete_log( + self.curve_spec, + point_with_dlogs.next().unwrap(), + &self.challenge, + &challenged_public_key, + ); + lincomb = lincomb.term(::F::ONE, point.x()); + debug_assert!(point_with_dlogs.next().is_none()); + } + + // Require the encryption mask be successfully commited to within a Pedersen commitment + self.circuit.equality( + lincomb, + &LinComb::from(self.pedersen_commitment_tape.allocate_pedersen_commitment()), + ); + } +} + +/// The result of proving. +pub(super) struct ProveResult { + /// The coefficients for use in the DKG. + pub(super) coefficients: Vec::F>>, + /// The masks to encrypt secret shares with. + pub(super) encryption_keys: Vec::F>>, + /// The proof itself. + pub(super) proof: Vec, +} + +pub(super) struct Verified { + /// The commitments to the coefficients used within the DKG. + pub(super) coefficients: Vec<::G>, + /// The ephemeral public keys to perform ECDHs with + pub(super) ecdh_commitments: Vec<[::G; 2]>, + /// The commitments to the masks used to encrypt secret shares with. + pub(super) encryption_key_commitments: Vec<::G>, +} + +impl fmt::Debug for Verified { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Verified").finish_non_exhaustive() + } +} + +type GeneratorTable = generalized_bulletproofs_ec_gadgets::GeneratorTable< + <<::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement, + ::EmbeddedCurveParameters, +>; + +pub(super) struct Proof(PhantomData); +impl Proof { + fn discrete_log_claims(coefficients: usize, participants: usize) -> usize { + /* + - 1 DLOG to prove the discrete logarithm corresponds to the eVRF public key + - 2 DLOGs per coefficient in the secret-sharing polynomial + - 2 DLOGs per each ECDH (one to open the commitment, one for the ECDH itself), with two ECDHs + for each participant (with the sum of their `x` coordinates being uniform and used as the + mask) + */ + const DLOGS_PER_COEFFICIENT: usize = 2; + const ECDHS_PER_PARTICIPANT: usize = 2; + const DLOGS_PER_ECDH: usize = 2; + const DLOGS_PER_PARTICIPANT: usize = ECDHS_PER_PARTICIPANT * DLOGS_PER_ECDH; + 1 + (DLOGS_PER_COEFFICIENT * coefficients) + (DLOGS_PER_PARTICIPANT * participants) + } + + fn expected_multiplications(coefficients: usize, participants: usize) -> usize { + const MULS_PER_DLOG: usize = 7; + MULS_PER_DLOG * Self::discrete_log_claims(coefficients, participants) + } + + pub(crate) fn generators_to_use(coefficients: usize, participants: usize) -> usize { + /* + `expected_multiplications` may be as small as 16, which would create an excessive amount of + vector commitments (as a vector commitment can only commit to as many variables as we have + multiplications). + + We require the actual amount of multiplications to be at least 2048 (even though that + that 'wastes' thousands of multiplications) to ensure the bandwidth usage remains reasonable. + */ + Self::expected_multiplications(coefficients, participants).next_power_of_two().max(2048) + } + + fn variables_in_vector_commitments(coefficients: usize, participants: usize) -> usize { + Tape::variables_for_points_with_common_dlog::(1 + (2 * coefficients)) + + (participants * 2 * Tape::variables_for_points_with_common_dlog::(2)) + } + + fn circuit( + curve_spec: &CurveSpec<<::G as DivisorCurve>::FieldElement>, + evrf_public_key: EmbeddedPoint, + coefficients: usize, + ecdh_commitments: &[[EmbeddedPoint; 2]], + generator_tables: &[&GeneratorTable], + circuit: &mut BpCircuit, + transcript: &mut impl Transcript, + ) { + let participants = ecdh_commitments.len(); + let generators_to_use = Self::generators_to_use(coefficients, participants); + + // Sample the challenge for all the discrete-logarithm claims + let (challenge, challenged_generators) = + circuit.discrete_log_challenge(transcript, curve_spec, generator_tables); + + /* + The generator tables, and the challenged generators, will have the following layout: + - G + - Generators for the eVRFs used to sample the coefficients + - The participants' public keys, used for performing ECDHs with + */ + let mut challenged_generators = challenged_generators.into_iter(); + #[allow(non_snake_case)] + let challenged_G = challenged_generators.next().unwrap(); + + let tape = Tape::new(generators_to_use); + let pedersen_commitment_tape = PedersenCommitmentTape::new(); + + { + let mut circuit = Circuit:: { + curve_spec, + circuit, + challenge, + challenged_G, + challenged_generators: &mut challenged_generators, + tape, + pedersen_commitment_tape, + }; + + circuit.coefficients(evrf_public_key, coefficients); + + // Now execute the circuit for the ECDHs + for ecdh_commitments in ecdh_commitments { + circuit.verifiable_encryption(ecdh_commitments); + } + } + + debug_assert_eq!( + Self::expected_multiplications(coefficients, participants), + circuit.muls(), + "unexpected amount of multiplications actually used" + ); + debug_assert!( + challenged_generators.next().is_none(), + "didn't consume all challenged generators" + ); + } + + /// Sample the points for the eVRF invocations used for the coefficients. + fn sample_coefficients_evrf_points( + seed: [u8; 32], + coefficients: usize, + ) -> Vec<::G> { + let mut rng = ChaCha20Rng::from_seed(seed); + let quantity = 2 * coefficients; + let mut res = Vec::with_capacity(quantity); + for _ in 0 .. quantity { + res.push(crate::sample_point::(&mut rng)); + } + res + } + + /// Create the required tables for the generators. + fn generator_tables( + coefficients_evrf_points: &[::G], + participants: &[<::EmbeddedCurve as Ciphersuite>::G], + ) -> Vec> { + let curve_spec = CurveSpec { + a: <::EmbeddedCurve as Ciphersuite>::G::a(), + b: <::EmbeddedCurve as Ciphersuite>::G::b(), + }; + + let mut generator_tables = + Vec::with_capacity(1 + coefficients_evrf_points.len() + participants.len()); + { + let (x, y) = + ::G::to_xy(::generator()) + .unwrap(); + generator_tables.push(GeneratorTable::::new(&curve_spec, x, y)); + } + for generator in coefficients_evrf_points { + let (x, y) = ::G::to_xy(*generator).unwrap(); + generator_tables.push(GeneratorTable::::new(&curve_spec, x, y)); + } + for generator in participants { + let (x, y) = ::G::to_xy(*generator).unwrap(); + generator_tables.push(GeneratorTable::::new(&curve_spec, x, y)); + } + generator_tables + } + + pub(super) fn prove( + rng: &mut (impl RngCore + CryptoRng), + generators: &Generators, + transcript: [u8; 32], + coefficients: usize, + participant_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], + evrf_private_key: &Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, + ) -> Result, AcError> { + let curve_spec = CurveSpec { + a: <::EmbeddedCurve as Ciphersuite>::G::a(), + b: <::EmbeddedCurve as Ciphersuite>::G::b(), + }; + + let coefficients_evrf_points = Self::sample_coefficients_evrf_points(transcript, coefficients); + let generator_tables = + Self::generator_tables(&coefficients_evrf_points, participant_public_keys); + + // Push a discrete logarithm onto the tape + let discrete_log = + |vector_commitment_tape: &mut Vec<_>, + dlog: &ScalarDecomposition<<::EmbeddedCurve as Ciphersuite>::F>| { + for coefficient in dlog.decomposition() { + vector_commitment_tape.push(<_>::from(*coefficient)); + } + }; + + // Push a discrete-log claim onto the tape. + // + // Returns the point for which the claim was made. + let discrete_log_claim = + |vector_commitment_tape: &mut Vec<_>, + dlog: &ScalarDecomposition<<::EmbeddedCurve as Ciphersuite>::F>, + generator: <::EmbeddedCurve as Ciphersuite>::G| { + { + let divisor = + Zeroizing::new(dlog.scalar_mul_divisor(generator).normalize_x_coefficient()); + vector_commitment_tape.push(divisor.zero_coefficient); + for coefficient in divisor.x_coefficients.iter().skip(1) { + vector_commitment_tape.push(*coefficient); + } + for coefficient in divisor.yx_coefficients.first().unwrap_or(&vec![]) { + vector_commitment_tape.push(*coefficient); + } + vector_commitment_tape.push( + divisor + .y_coefficients + .first() + .copied() + .unwrap_or(::F::ZERO), + ); + } + + let dh = generator * dlog.scalar(); + let (x, y) = ::G::to_xy(dh).unwrap(); + vector_commitment_tape.push(x); + vector_commitment_tape.push(y); + (dh, (x, y)) + }; + + let mut vector_commitment_tape = Zeroizing::new(Vec::with_capacity( + Self::variables_in_vector_commitments(coefficients, participant_public_keys.len()), + )); + + // Handle the coefficients + let mut coefficients = Vec::with_capacity(coefficients); + let evrf_public_key = { + let evrf_private_key = + ScalarDecomposition::<::F>::new(**evrf_private_key) + .expect("eVRF private key was zero"); + + discrete_log(&mut vector_commitment_tape, &evrf_private_key); + + // Push the divisor for proving that we're using the correct scalar + let (_, evrf_public_key) = discrete_log_claim( + &mut vector_commitment_tape, + &evrf_private_key, + <::EmbeddedCurve as Ciphersuite>::generator(), + ); + + // Push the divisor for each point we use in the eVRF + for pair in coefficients_evrf_points.chunks(2) { + let mut coefficient = Zeroizing::new(::F::ZERO); + for point in pair { + let (_, (dh_x, _)) = + discrete_log_claim(&mut vector_commitment_tape, &evrf_private_key, *point); + *coefficient += dh_x; + } + coefficients.push(coefficient); + } + + evrf_public_key + }; + + // Handle the verifiable encryption + let mut encryption_keys = Vec::with_capacity(participant_public_keys.len()); + let mut ecdh_commitments = Vec::with_capacity(2 * participant_public_keys.len()); + let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len()); + for participant_public_key in participant_public_keys { + let mut ecdh_commitments_xy_i = + [(::F::ZERO, ::F::ZERO); + 2]; + let mut encryption_key = Zeroizing::new(::F::ZERO); + for ecdh_commitments_xy_i_j_dest in &mut ecdh_commitments_xy_i { + let mut ecdh_ephemeral_secret; + loop { + ecdh_ephemeral_secret = + Zeroizing::new(::F::random(&mut *rng)); + // 0 would produce the identity, which isn't representable within the discrete-log proof. + if bool::from(!ecdh_ephemeral_secret.is_zero()) { + break; + } + } + + let ecdh_ephemeral_secret = + ScalarDecomposition::<::F>::new(*ecdh_ephemeral_secret) + .expect("ECDH ephemeral secret zero"); + discrete_log(&mut vector_commitment_tape, &ecdh_ephemeral_secret); + + // Push a divisor for proving that we're using the correct scalar for the commitment + let (ecdh_commitment, ecdh_commitment_xy_i_j) = discrete_log_claim( + &mut vector_commitment_tape, + &ecdh_ephemeral_secret, + <::EmbeddedCurve as Ciphersuite>::generator(), + ); + ecdh_commitments.push(ecdh_commitment); + *ecdh_commitments_xy_i_j_dest = ecdh_commitment_xy_i_j; + // Push a divisor for the key we're performing the ECDH with + let (_, (dh_x, _)) = discrete_log_claim( + &mut vector_commitment_tape, + &ecdh_ephemeral_secret, + *participant_public_key, + ); + *encryption_key += dh_x; + } + ecdh_commitments_xy.push(ecdh_commitments_xy_i); + encryption_keys.push(encryption_key); + } + + // Convert the vector commitment tape into vector commitments + let generators_to_use = + Self::generators_to_use(coefficients.len(), participant_public_keys.len()); + debug_assert_eq!( + Self::variables_in_vector_commitments(coefficients.len(), participant_public_keys.len()), + vector_commitment_tape.len() + ); + let mut vector_commitments = + Vec::with_capacity(vector_commitment_tape.len().div_ceil(generators_to_use)); + for chunk in vector_commitment_tape.chunks(generators_to_use) { + vector_commitments.push(PedersenVectorCommitment { + g_values: chunk.to_vec().into(), + mask: ::F::random(&mut *rng), + }); + } + + // Create the Pedersen commitments + let mut commitments = Vec::with_capacity(coefficients.len() + participant_public_keys.len()); + for coefficient in &coefficients { + commitments.push(PedersenCommitment { + value: **coefficient, + mask: ::F::random(&mut *rng), + }); + } + for enc_mask in &encryption_keys { + commitments.push(PedersenCommitment { + value: **enc_mask, + mask: ::F::random(&mut *rng), + }); + } + + let mut transcript = ProverTranscript::new(transcript); + let commited_commitments = transcript.write_commitments( + vector_commitments + .iter() + .map(|commitment| { + commitment + .commit(generators.g_bold_slice(), generators.h()) + .ok_or(AcError::NotEnoughGenerators) + }) + .collect::>()?, + commitments + .iter() + .map(|commitment| commitment.commit(generators.g(), generators.h())) + .collect(), + ); + for ecdh_commitment in ecdh_commitments { + transcript.push_point(&ecdh_commitment); + } + + let mut circuit = BpCircuit::prove(vector_commitments, commitments.clone()); + Self::circuit( + &curve_spec, + evrf_public_key, + coefficients.len(), + &ecdh_commitments_xy, + &generator_tables.iter().collect::>(), + &mut circuit, + &mut transcript, + ); + + let (statement, Some(witness)) = circuit + .statement( + generators.reduce(generators_to_use).ok_or(AcError::NotEnoughGenerators)?, + commited_commitments, + ) + .unwrap() + else { + panic!("proving yet wasn't yielded the witness"); + }; + statement.prove(&mut *rng, &mut transcript, witness).unwrap(); + + // Push the reveal onto the transcript + for commitment in &commitments { + transcript.push_point(&(generators.g() * commitment.value)); + } + + // Prove the openings of the commitments were correct + let mut x = Zeroizing::new(::F::ZERO); + for commitment in commitments { + *x += commitment.mask * transcript.challenge::(); + } + + // Produce a Schnorr PoK for the weighted-sum of the Pedersen commitments' blinding factors + let r = Zeroizing::new(::F::random(&mut *rng)); + transcript.push_point(&(generators.h() * r.deref())); + let c = transcript.challenge::(); + transcript.push_scalar((c * x.deref()) + r.deref()); + + Ok(ProveResult { coefficients, encryption_keys, proof: transcript.complete() }) + } + + #[allow(clippy::too_many_arguments)] + pub(super) fn verify( + rng: &mut (impl RngCore + CryptoRng), + generators: &Generators, + verifier: &mut BatchVerifier, + transcript: [u8; 32], + coefficients: usize, + participant_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], + evrf_public_key: <::EmbeddedCurve as Ciphersuite>::G, + proof: &[u8], + ) -> Result, ()> { + let (mut transcript, ecdh_commitments, pedersen_commitments) = { + let curve_spec = CurveSpec { + a: <::EmbeddedCurve as Ciphersuite>::G::a(), + b: <::EmbeddedCurve as Ciphersuite>::G::b(), + }; + + let coefficients_evrf_points = + Self::sample_coefficients_evrf_points(transcript, coefficients); + let generator_tables = + Self::generator_tables(&coefficients_evrf_points, participant_public_keys); + + let generators_to_use = Self::generators_to_use(coefficients, participant_public_keys.len()); + + let mut transcript = VerifierTranscript::new(transcript, proof); + + let vector_commitments = + Self::variables_in_vector_commitments(coefficients, participant_public_keys.len()) + .div_ceil(generators_to_use); + /* + One commitment is used to commit to each coefficient of the secret-sharing polynomial, and + one commitment is used to commit to each encryption key used to encrypt a secret share to + its recipient. + */ + let pedersen_commitments = coefficients + participant_public_keys.len(); + let all_commitments = + transcript.read_commitments(vector_commitments, pedersen_commitments).map_err(|_| ())?; + let pedersen_commitments = all_commitments.V().to_vec(); + + // Read the commitments to the ephemeral secrets for the ECDHs + let mut ecdh_commitments = Vec::with_capacity(participant_public_keys.len()); + let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len()); + for _ in 0 .. participant_public_keys.len() { + let ecdh_commitments_i = [ + transcript.read_point::().map_err(|_| ())?, + transcript.read_point::().map_err(|_| ())?, + ]; + ecdh_commitments.push(ecdh_commitments_i); + // This inherently bans using the identity point, as it won't have an affine representation + ecdh_commitments_xy.push([ + <::G as DivisorCurve>::to_xy(ecdh_commitments_i[0]) + .ok_or(())?, + <::G as DivisorCurve>::to_xy(ecdh_commitments_i[1]) + .ok_or(())?, + ]); + } + + let mut circuit = BpCircuit::verify(); + Self::circuit( + &curve_spec, + ::G::to_xy(evrf_public_key).ok_or(())?, + coefficients, + &ecdh_commitments_xy, + &generator_tables.iter().collect::>(), + &mut circuit, + &mut transcript, + ); + + let (statement, None) = circuit + .statement(generators.reduce(generators_to_use).ok_or(())?, all_commitments) + .unwrap() + else { + panic!("verifying yet was yielded a witness"); + }; + + statement.verify(rng, verifier, &mut transcript).map_err(|_| ())?; + + (transcript, ecdh_commitments, pedersen_commitments) + }; + + // Read the openings for each of the Pedersen commitments + let mut openings = Vec::with_capacity(pedersen_commitments.len()); + for _ in 0 .. pedersen_commitments.len() { + openings.push(transcript.read_point::().map_err(|_| ())?); + } + + /* + Verify the openings of each of the Pedersen commitments. + + We do this via verifying the prover knows an opening of their Pedersen commitment, minus the + claimed opening, over the blinding generator. For efficiency, we take a random combination of + all commitments/openings, solely requiring the prover know the single opening for the + combination. + */ + { + let (weighted_sum_commitments, weighted_sum_openings) = { + let mut weighted_sum_commitments = Vec::with_capacity(pedersen_commitments.len()); + let mut weighted_sum_openings = Vec::with_capacity(pedersen_commitments.len()); + for (pedersen_commitment, opening) in pedersen_commitments.iter().zip(&openings) { + let weight = transcript.challenge::(); + weighted_sum_commitments.push((weight, *pedersen_commitment)); + weighted_sum_openings.push((weight, *opening)); + } + ( + multiexp::multiexp_vartime(&weighted_sum_commitments), + multiexp::multiexp_vartime(&weighted_sum_openings), + ) + }; + #[allow(non_snake_case)] + let A = weighted_sum_commitments - weighted_sum_openings; + + // Schnorr signature + #[allow(non_snake_case)] + let R = transcript.read_point::().map_err(|_| ())?; + let c = transcript.challenge::(); + let s = transcript.read_scalar::().map_err(|_| ())?; + + // Doesn't batch verify this as we can't access the internals of the GBP batch verifier + if (R + (A * c)) != (generators.h() * s) { + Err(())?; + } + } + + if !transcript.complete().is_empty() { + Err(())? + }; + + let coefficients = openings[.. coefficients].to_vec(); + let encryption_key_commitments = openings[coefficients.len() ..].to_vec(); + Ok(Verified { coefficients, ecdh_commitments, encryption_key_commitments }) + } +} diff --git a/crypto/dkg/evrf/src/proof/tape.rs b/crypto/dkg/evrf/src/proof/tape.rs new file mode 100644 index 00000000..3d39b735 --- /dev/null +++ b/crypto/dkg/evrf/src/proof/tape.rs @@ -0,0 +1,106 @@ +use core::marker::PhantomData; + +use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; + +use generalized_bulletproofs_circuit_abstraction::Variable; +use generalized_bulletproofs_ec_gadgets::{DiscreteLogParameters, Divisor, PointWithDlog}; + +use crate::Curves; + +/* + For all variables we must commit to during the ZK proof, we place them on the 'tape'. The tape + is a linear representation of every single variable committed to by the proof, from which we can + read a collection of variables from/push a collection of variables onto. This offers an API + similar to reading/writing to a byte stream, despite working with variables in a ZK proof. +*/ +pub(super) struct Tape { + generators: usize, + current_position: usize, +} +impl Tape { + // Construct a new tape. + pub(super) fn new(generators: usize) -> Self { + Self { generators, current_position: 0 } + } + + /// Read a Variable from the tape. + fn read_one_from_tape(&mut self) -> Variable { + let commitment = self.current_position / self.generators; + let index = self.current_position % self.generators; + let res = Variable::CG { commitment, index }; + self.current_position += 1; + res + } + + /// Read a fixed-length array of variables from the tape. + fn read_from_tape(&mut self) -> GenericArray { + GenericArray::::generate(|_| self.read_one_from_tape()) + } + + /// Read `PointWithDlog`s, which share a discrete logarithm, from the tape. + pub(super) fn read_points_with_common_dlog( + &mut self, + quantity: usize, + ) -> impl use<'_, C> + Iterator> { + /* + The tape expects the format of: + - Discrete logarithm + - Divisor (zero coefficient, x coefficients, y x**i coefficients, y coefficient) + - Point (x, y) + Note the `x` coefficients are only from the power of two, and `i >= 1`. + */ + let dlog = + self.read_from_tape::<::ScalarBits>(); + + struct PointIterator<'a, C: Curves>( + &'a mut Tape, + GenericArray::ScalarBits>, + PhantomData, + ); + impl<'a, C: Curves> Iterator for PointIterator<'a, C> { + type Item = PointWithDlog; + fn next(&mut self) -> Option { + let divisor = { + let zero = self.0.read_one_from_tape(); + let x_from_power_of_2 = self.0.read_from_tape(); + let yx = self.0.read_from_tape(); + let y = self.0.read_one_from_tape(); + Divisor { zero, x_from_power_of_2, yx, y } + }; + + let point = ( + // x coordinate + self.0.read_one_from_tape(), + // y coordinate + self.0.read_one_from_tape(), + ); + + Some(PointWithDlog { dlog: self.1.clone(), divisor, point }) + } + } + + PointIterator(self, dlog, PhantomData::).take(quantity) + } + + /// The amount of variables the points with a common discrete logarithm will use on the tape. + pub(super) fn variables_for_points_with_common_dlog(quantity: usize) -> usize { + let mut dummy_tape = Tape::new(usize::MAX); + for _ in dummy_tape.read_points_with_common_dlog::(quantity) {} + dummy_tape.current_position + } +} + +pub(super) struct PedersenCommitmentTape { + pedersen_commitments: usize, +} +impl PedersenCommitmentTape { + pub(super) fn new() -> Self { + Self { pedersen_commitments: 0 } + } + /// Allocate a Pedersen commitment. + pub(super) fn allocate_pedersen_commitment(&mut self) -> Variable { + let res = Variable::V(self.pedersen_commitments); + self.pedersen_commitments += 1; + res + } +} diff --git a/crypto/dkg/evrf/src/tests/mod.rs b/crypto/dkg/evrf/src/tests/mod.rs index e6fd2230..ec10fb07 100644 --- a/crypto/dkg/evrf/src/tests/mod.rs +++ b/crypto/dkg/evrf/src/tests/mod.rs @@ -5,26 +5,26 @@ use rand_core::OsRng; use rand::seq::SliceRandom; use ciphersuite::{group::ff::Field, Ciphersuite}; +use embedwards25519::Embedwards25519; -use crate::{ - Participant, - evrf::*, - tests::{THRESHOLD, PARTICIPANTS, recover_key}, -}; +use dkg_recovery::recover_key; +use crate::{Participant, Curves, Generators, VerifyResult, Dkg, Ristretto}; mod proof; -use proof::{Pallas, Vesta}; + +const THRESHOLD: u16 = 3; +const PARTICIPANTS: u16 = 5; #[test] -fn evrf_dkg() { - let generators = EvrfGenerators::::new(THRESHOLD, PARTICIPANTS); +fn dkg() { + let generators = Generators::::new(THRESHOLD, PARTICIPANTS); let context = [0; 32]; let mut priv_keys = vec![]; let mut pub_keys = vec![]; for i in 0 .. PARTICIPANTS { - let priv_key = ::F::random(&mut OsRng); - pub_keys.push(::generator() * priv_key); + let priv_key = ::F::random(&mut OsRng); + pub_keys.push(::generator() * priv_key); priv_keys.push((Participant::new(1 + i).unwrap(), Zeroizing::new(priv_key))); } @@ -34,7 +34,7 @@ fn evrf_dkg() { for (i, priv_key) in priv_keys.iter().take(usize::from(THRESHOLD)) { participations.insert( *i, - EvrfDkg::::participate( + Dkg::::participate( &mut OsRng, &generators, context, @@ -46,7 +46,7 @@ fn evrf_dkg() { ); } - let VerifyResult::Valid(dkg) = EvrfDkg::::verify( + let VerifyResult::Valid(dkg) = Dkg::::verify( &mut OsRng, &generators, context, @@ -67,13 +67,21 @@ fn evrf_dkg() { assert_eq!(keys.params().t(), THRESHOLD); assert_eq!(keys.params().n(), PARTICIPANTS); group_key = group_key.or(Some(keys.group_key())); - verification_shares = verification_shares.or(Some(keys.verification_shares())); + let these_verification_shares = Participant::iter() + .take(usize::from(PARTICIPANTS)) + .map(|i| (i, keys.original_verification_share(i))) + .collect::>(); + verification_shares = verification_shares.or(Some(these_verification_shares.clone())); assert_eq!(Some(keys.group_key()), group_key); - assert_eq!(Some(keys.verification_shares()), verification_shares); + assert_eq!(Some(these_verification_shares), verification_shares); all_keys.insert(i, keys); } // TODO: Test for all possible combinations of keys - assert_eq!(Pallas::generator() * recover_key(&all_keys), group_key.unwrap()); + assert_eq!( + <::ToweringCurve as Ciphersuite>::generator() * + *recover_key(&all_keys.values().cloned().collect::>()).unwrap(), + group_key.unwrap() + ); } diff --git a/crypto/dkg/evrf/src/tests/proof.rs b/crypto/dkg/evrf/src/tests/proof.rs index 200ac8b2..64fb7896 100644 --- a/crypto/dkg/evrf/src/tests/proof.rs +++ b/crypto/dkg/evrf/src/tests/proof.rs @@ -2,94 +2,49 @@ use std::time::Instant; use rand_core::OsRng; -use zeroize::{Zeroize, Zeroizing}; -use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}; -use blake2::{Digest, Blake2b512}; +use zeroize::Zeroizing; use ciphersuite::{ - group::{ - ff::{FromUniformBytes, Field, PrimeField}, - Group, - }, - Ciphersuite, Secp256k1, Ed25519, Ristretto, + group::{ff::Field, Group}, + Ciphersuite, }; -use pasta_curves::{Ep, Eq, Fp, Fq}; use generalized_bulletproofs::{Generators, tests::generators}; -use generalized_bulletproofs_ec_gadgets::DiscreteLogParameters; -use crate::evrf::proof::*; +use crate::{ + Curves, Ristretto, + proof::*, + tests::{THRESHOLD, PARTICIPANTS}, +}; -#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -pub(crate) struct Pallas; -impl Ciphersuite for Pallas { - type F = Fq; - type G = Ep; - type H = Blake2b512; - const ID: &'static [u8] = b"Pallas"; - fn generator() -> Ep { - Ep::generator() - } - fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { - // This naive concat may be insecure in a real world deployment - // This is solely test code so it's fine - Self::F::from_uniform_bytes(&Self::H::digest([dst, msg].concat()).into()) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -pub(crate) struct Vesta; -impl Ciphersuite for Vesta { - type F = Fp; - type G = Eq; - type H = Blake2b512; - const ID: &'static [u8] = b"Vesta"; - fn generator() -> Eq { - Eq::generator() - } - fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { - // This naive concat may be insecure in a real world deployment - // This is solely test code so it's fine - Self::F::from_uniform_bytes(&Self::H::digest([dst, msg].concat()).into()) - } -} - -pub struct VestaParams; -impl DiscreteLogParameters for VestaParams { - type ScalarBits = U<{ <::F as PrimeField>::NUM_BITS as usize }>; - type XCoefficients = Quot, U2>; - type XCoefficientsMinusOne = Diff; - type YxCoefficients = Diff, U1>, U2>, U2>; -} - -impl EvrfCurve for Pallas { - type EmbeddedCurve = Vesta; - type EmbeddedCurveParameters = VestaParams; -} - -fn evrf_proof_test() { +fn proof() { let generators = generators(2048); - let vesta_private_key = Zeroizing::new(::F::random(&mut OsRng)); - let ecdh_public_keys = [ - ::G::random(&mut OsRng), - ::G::random(&mut OsRng), - ]; + let embedded_private_key = + Zeroizing::new(::F::random(&mut OsRng)); + let ecdh_public_keys: [_; PARTICIPANTS as usize] = + core::array::from_fn(|_| ::G::random(&mut OsRng)); let time = Instant::now(); - let res = - Evrf::::prove(&mut OsRng, &generators, [0; 32], 1, &ecdh_public_keys, &vesta_private_key) - .unwrap(); + let res = Proof::::prove( + &mut OsRng, + &generators, + [0; 32], + THRESHOLD.into(), + &ecdh_public_keys, + &embedded_private_key, + ) + .unwrap(); println!("Proving time: {:?}", time.elapsed()); let time = Instant::now(); let mut verifier = Generators::batch_verifier(); - Evrf::::verify( + Proof::::verify( &mut OsRng, &generators, &mut verifier, [0; 32], - 1, + THRESHOLD.into(), &ecdh_public_keys, - C::EmbeddedCurve::generator() * *vesta_private_key, + C::EmbeddedCurve::generator() * *embedded_private_key, &res.proof, ) .unwrap(); @@ -98,21 +53,6 @@ fn evrf_proof_test() { } #[test] -fn pallas_evrf_proof_test() { - evrf_proof_test::(); -} - -#[test] -fn secp256k1_evrf_proof_test() { - evrf_proof_test::(); -} - -#[test] -fn ed25519_evrf_proof_test() { - evrf_proof_test::(); -} - -#[test] -fn ristretto_evrf_proof_test() { - evrf_proof_test::(); +fn ristretto_proof() { + proof::(); } diff --git a/crypto/dkg/evrf/src/utils.rs b/crypto/dkg/evrf/src/utils.rs new file mode 100644 index 00000000..bb121a5f --- /dev/null +++ b/crypto/dkg/evrf/src/utils.rs @@ -0,0 +1,43 @@ +use core::ops::Deref; + +use zeroize::{Zeroize, Zeroizing}; +use rand_core::{RngCore, CryptoRng}; + +use ciphersuite::{ + group::{ff::PrimeField, Group, GroupEncoding}, + Ciphersuite, +}; + +use dkg::Participant; + +/// Sample a random, unbiased point on the elliptic curve with an unknown discrete logarithm. +/// +/// This keeps it simple by using rejection sampling. +pub(crate) fn sample_point(rng: &mut (impl RngCore + CryptoRng)) -> C::G { + let mut repr = ::Repr::default(); + loop { + rng.fill_bytes(repr.as_mut()); + if let Ok(point) = C::read_G(&mut repr.as_ref()) { + if bool::from(!point.is_identity()) { + return point; + } + } + } +} + +pub(super) fn polynomial( + coefficients: &[Zeroizing], + l: Participant, +) -> Zeroizing { + let l = F::from(u64::from(u16::from(l))); + // This should never be reached since Participant is explicitly non-zero + assert!(l != F::ZERO, "zero participant passed to polynomial"); + let mut share = Zeroizing::new(F::ZERO); + for (idx, coefficient) in coefficients.iter().rev().enumerate() { + *share += coefficient.deref(); + if idx != (coefficients.len() - 1) { + *share *= l; + } + } + share +} diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index bb1aa560..f7d8cdcf 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -39,6 +39,19 @@ impl Participant { pub const fn to_bytes(&self) -> [u8; 2] { self.0.to_le_bytes() } + + /// Create an iterator over participant indexes. + pub fn iter() -> impl Iterator { + struct ParticipantIterator(u16); + impl Iterator for ParticipantIterator { + type Item = Participant; + fn next(&mut self) -> Option { + self.0 = self.0.checked_add(1)?; + Some(Participant(self.0)) + } + } + ParticipantIterator(0) + } } impl From for u16 { diff --git a/crypto/evrf/embedwards25519/Cargo.toml b/crypto/evrf/embedwards25519/Cargo.toml index 96aa2b3c..b76eb34f 100644 --- a/crypto/evrf/embedwards25519/Cargo.toml +++ b/crypto/evrf/embedwards25519/Cargo.toml @@ -31,8 +31,8 @@ dalek-ff-group = { path = "../../dalek-ff-group", version = "0.4", default-featu blake2 = { version = "0.10", default-features = false } ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false } -ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } -generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } +ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } +generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } [dev-dependencies] hex = "0.4" diff --git a/crypto/evrf/embedwards25519/src/backend.rs b/crypto/evrf/embedwards25519/src/backend.rs index 304fa0bc..2a5f962a 100644 --- a/crypto/evrf/embedwards25519/src/backend.rs +++ b/crypto/evrf/embedwards25519/src/backend.rs @@ -91,7 +91,7 @@ macro_rules! field { use crypto_bigint::{Integer, NonZero, Encoding, impl_modulus}; use ciphersuite::group::ff::{ - Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic, + Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes, helpers::sqrt_ratio_generic, }; use $crate::backend::u8_from_bool; @@ -258,6 +258,12 @@ macro_rules! field { } } + impl FromUniformBytes<64> for $FieldName { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + $FieldName(Residue::new(&reduce(U512::from_le_slice(bytes)))) + } + } + impl Sum<$FieldName> for $FieldName { fn sum>(iter: I) -> $FieldName { let mut res = $FieldName::ZERO; diff --git a/crypto/evrf/secq256k1/Cargo.toml b/crypto/evrf/secq256k1/Cargo.toml index 52fd797e..2437e1e0 100644 --- a/crypto/evrf/secq256k1/Cargo.toml +++ b/crypto/evrf/secq256k1/Cargo.toml @@ -31,8 +31,8 @@ k256 = { version = "0.13", default-features = false, features = ["arithmetic"] } blake2 = { version = "0.10", default-features = false } ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false } -ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } -generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } +ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } +generalized-bulletproofs-ec-gadgets = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } [dev-dependencies] hex = "0.4" diff --git a/crypto/evrf/secq256k1/src/backend.rs b/crypto/evrf/secq256k1/src/backend.rs index 6f8653c8..9551caa5 100644 --- a/crypto/evrf/secq256k1/src/backend.rs +++ b/crypto/evrf/secq256k1/src/backend.rs @@ -91,7 +91,7 @@ macro_rules! field { use crypto_bigint::{Integer, NonZero, Encoding, impl_modulus}; use ciphersuite::group::ff::{ - Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic, + Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes, helpers::sqrt_ratio_generic, }; use $crate::backend::u8_from_bool; @@ -260,6 +260,12 @@ macro_rules! field { } } + impl FromUniformBytes<64> for $FieldName { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + $FieldName(Residue::new(&reduce(U512::from_le_slice(bytes)))) + } + } + impl Sum<$FieldName> for $FieldName { fn sum>(iter: I) -> $FieldName { let mut res = $FieldName::ZERO; diff --git a/patches/ciphersuite/Cargo.toml b/patches/ciphersuite/Cargo.toml new file mode 100644 index 00000000..a27befed --- /dev/null +++ b/patches/ciphersuite/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ciphersuite" +version = "0.4.99" +description = "Ciphersuites built around ff/group" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ciphersuite" +authors = ["Luke Parker "] +keywords = ["ciphersuite", "ff", "group"] +edition = "2021" +rust-version = "1.66" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +ciphersuite = { path = "../../crypto/ciphersuite", default-features = false } +dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, optional = true } + +[features] +alloc = ["ciphersuite/alloc"] +std = [ + "ciphersuite/std", + "dalek-ff-group?/std", +] +ed25519 = ["dalek-ff-group"] +default = ["std"] diff --git a/patches/ciphersuite/LICENSE b/patches/ciphersuite/LICENSE new file mode 100644 index 00000000..6f7adff3 --- /dev/null +++ b/patches/ciphersuite/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2025 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/patches/ciphersuite/README.md b/patches/ciphersuite/README.md new file mode 100644 index 00000000..e5aa0ff3 --- /dev/null +++ b/patches/ciphersuite/README.md @@ -0,0 +1,4 @@ +# Ciphersuite + +Patch for the `crates.io` ciphersuite to use the in-tree ciphersuite, resolving +breaking changes made since. diff --git a/patches/ciphersuite/src/lib.rs b/patches/ciphersuite/src/lib.rs new file mode 100644 index 00000000..ce4f6dc9 --- /dev/null +++ b/patches/ciphersuite/src/lib.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use ciphersuite::*; +#[cfg(feature = "ed25519")] +use dalek_ff_group::Ed25519; diff --git a/processor/key-gen/Cargo.toml b/processor/key-gen/Cargo.toml index b13797db..ea2eb16b 100644 --- a/processor/key-gen/Cargo.toml +++ b/processor/key-gen/Cargo.toml @@ -31,7 +31,7 @@ rand_chacha = { version = "0.3", default-features = false, features = ["std"] } # Cryptography blake2 = { version = "0.10", default-features = false, features = ["std"] } transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std"] } -ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } +ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] } dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, features = ["std"] } dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] } diff --git a/processor/monero/Cargo.toml b/processor/monero/Cargo.toml index 9f858333..7a3313c2 100644 --- a/processor/monero/Cargo.toml +++ b/processor/monero/Cargo.toml @@ -30,8 +30,8 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ed25519"] } frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false } -monero-wallet = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false, features = ["std", "multisig"] } -monero-simple-request-rpc = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false } +monero-wallet = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false, features = ["std", "multisig"] } +monero-simple-request-rpc = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false } serai-client = { path = "../../substrate/client", default-features = false, features = ["monero"] } diff --git a/substrate/client/Cargo.toml b/substrate/client/Cargo.toml index 12356f64..c1a589c6 100644 --- a/substrate/client/Cargo.toml +++ b/substrate/client/Cargo.toml @@ -43,7 +43,7 @@ bitcoin = { version = "0.32", optional = true } dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true } ciphersuite = { path = "../../crypto/ciphersuite", version = "0.4", optional = true } -monero-address = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", version = "0.1.0", default-features = false, features = ["std"], optional = true } +monero-address = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", version = "0.1.0", default-features = false, features = ["std"], optional = true } [dev-dependencies] rand_core = "0.6" diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index d6891e00..c6a7b50e 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -81,7 +81,7 @@ serai-env = { path = "../../common/env" } curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, features = ["std", "hazmat"] } -monero-address = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false, features = ["std"] } +monero-address = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "54da48f27a05fa8656014942919da1dfbab4d8e3", default-features = false, features = ["std"] } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate" } diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index bb15a9b2..feff6515 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -32,6 +32,7 @@ secq256k1 = { path = "../../crypto/evrf/secq256k1", default-features = false } embedwards25519 = { path = "../../crypto/evrf/embedwards25519", default-features = false } dkg = { path = "../../crypto/dkg", default-features = false } +dkg-evrf = { path = "../../crypto/dkg/evrf", default-features = false } # modular-frost = { path = "../../crypto/frost", default-features = false } # frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false } diff --git a/tests/no-std/src/lib.rs b/tests/no-std/src/lib.rs index 4f6d58dd..ca84e139 100644 --- a/tests/no-std/src/lib.rs +++ b/tests/no-std/src/lib.rs @@ -15,6 +15,7 @@ pub use secq256k1; pub use embedwards25519; pub use dkg; +pub use dkg_evrf; /* pub use modular_frost; pub use frost_schnorrkel;