mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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.
This commit is contained in:
77
Cargo.lock
generated
77
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"]
|
||||
|
||||
50
crypto/dkg/evrf/README.md
Normal file
50
crypto/dkg/evrf/README.md
Normal file
@@ -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.
|
||||
109
crypto/dkg/evrf/src/curves.rs
Normal file
109
crypto/dkg/evrf/src/curves.rs
Normal file
@@ -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<U32>;
|
||||
|
||||
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<F: FromUniformBytes<64>>;
|
||||
/// The embedded curve which participants represent their public keys over.
|
||||
type EmbeddedCurve: Ciphersuite<
|
||||
G: DivisorCurve<FieldElement = <Self::ToweringCurve as Ciphersuite>::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<C: Curves>(pub(crate) BpGenerators<C::ToweringCurve>);
|
||||
|
||||
impl<C: Curves> Generators<C> {
|
||||
/// 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<C> {
|
||||
let entropy = <Blake2s256Keyed as KeyInit>::new(&{
|
||||
let mut key = GenericArray::<u8, <Blake2s256Keyed as KeySizeUser>::KeySize>::default();
|
||||
let key_len = key.len().min(<C::ToweringCurve as Ciphersuite>::ID.len());
|
||||
{
|
||||
let key: &mut [u8] = key.as_mut();
|
||||
key[.. key_len].copy_from_slice(&<C::ToweringCurve as Ciphersuite>::ID[.. key_len])
|
||||
}
|
||||
key
|
||||
})
|
||||
.chain_update(<C::ToweringCurve as Ciphersuite>::generator().to_bytes())
|
||||
.finalize()
|
||||
.into_bytes();
|
||||
let mut rng = ChaCha20Rng::from_seed(entropy.into());
|
||||
|
||||
let h = crate::sample_point::<C::ToweringCurve>(&mut rng);
|
||||
let generators =
|
||||
crate::Proof::<C>::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::<C::ToweringCurve>(&mut rng));
|
||||
h_bold.push(crate::sample_point::<C::ToweringCurve>(&mut rng));
|
||||
}
|
||||
Self(
|
||||
BpGenerators::new(<C::ToweringCurve as Ciphersuite>::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;
|
||||
}
|
||||
@@ -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<C: Ciphersuite> {
|
||||
|
||||
impl<C: Ciphersuite> Participation<C> {
|
||||
pub fn read<R: Read>(reader: &mut R, n: u16) -> io::Result<Self> {
|
||||
// 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<C: Ciphersuite> Participation<C> {
|
||||
}
|
||||
|
||||
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<C: Ciphersuite> Participation<C> {
|
||||
pub fn write<W: 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<F: PrimeField + Zeroize>(
|
||||
coefficients: &[Zeroizing<F>],
|
||||
l: Participant,
|
||||
) -> Zeroizing<F> {
|
||||
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<C: Ciphersuite>(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
commitments: &[C::G],
|
||||
n: u16,
|
||||
encryption_commitments: &[C::G],
|
||||
encrypted_secret_shares: &HashMap<Participant, C::F>,
|
||||
) -> (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<C: EvrfCurve> {
|
||||
Valid(EvrfDkg<C>),
|
||||
/// The result of calling `Dkg::verify`.
|
||||
pub enum VerifyResult<C: Curves> {
|
||||
/// The DKG participations were valid.
|
||||
Valid(Dkg<C>),
|
||||
/// The DKG participants were invalid, identifying the faulty participants.
|
||||
Invalid(Vec<Participant>),
|
||||
/// 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<C: EvrfCurve> {
|
||||
pub struct Dkg<C: Curves> {
|
||||
t: u16,
|
||||
n: u16,
|
||||
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
|
||||
group_key: C::G,
|
||||
verification_shares: HashMap<Participant, C::G>,
|
||||
verification_shares: HashMap<Participant, <C::ToweringCurve as Ciphersuite>::G>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
encrypted_secret_shares:
|
||||
HashMap<Participant, HashMap<Participant, ([<C::EmbeddedCurve as Ciphersuite>::G; 2], C::F)>>,
|
||||
encrypted_secret_shares: HashMap<
|
||||
Participant,
|
||||
HashMap<
|
||||
Participant,
|
||||
([<C::EmbeddedCurve as Ciphersuite>::G; 2], <C::ToweringCurve as Ciphersuite>::F),
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
impl<C: Curves> Dkg<C> {
|
||||
// Form the initial transcript for the proofs.
|
||||
fn initial_transcript(
|
||||
invocation: [u8; 32],
|
||||
@@ -260,40 +183,37 @@ impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
///
|
||||
/// 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<C>,
|
||||
generators: &Generators<C>,
|
||||
context: [u8; 32],
|
||||
t: u16,
|
||||
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
|
||||
) -> Result<Participation<C>, EvrfError> {
|
||||
let Ok(n) = u16::try_from(evrf_public_keys.len()) else { Err(EvrfError::TooManyParticipants)? };
|
||||
) -> Result<Participation<C::ToweringCurve>, 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 = <C::EmbeddedCurve as Ciphersuite>::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::<C>::prove(
|
||||
rng,
|
||||
&generators.0,
|
||||
per_proof_transcript.finalize().into(),
|
||||
@@ -302,7 +222,10 @@ impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
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::<C>::generators_to_use(usize::from(t), evrf_public_keys.len()),
|
||||
})?,
|
||||
Err(
|
||||
AcError::DifferingLrLengths |
|
||||
AcError::InconsistentAmountOfConstraints |
|
||||
@@ -317,14 +240,62 @@ impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
};
|
||||
|
||||
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::<C::F>(&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::<<C::ToweringCurve as Ciphersuite>::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<C: Curves>(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
coefficients: &[<C::ToweringCurve as Ciphersuite>::G],
|
||||
encryption_key_commitments: &[<C::ToweringCurve as Ciphersuite>::G],
|
||||
encrypted_secret_shares: &HashMap<Participant, <C::ToweringCurve as Ciphersuite>::F>,
|
||||
) -> (
|
||||
<C::ToweringCurve as Ciphersuite>::F,
|
||||
Vec<(<C::ToweringCurve as Ciphersuite>::F, <C::ToweringCurve as Ciphersuite>::G)>,
|
||||
) {
|
||||
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::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((<C::ToweringCurve as Ciphersuite>::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 = <C::ToweringCurve as Ciphersuite>::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 = <C::ToweringCurve as Ciphersuite>::F::from(u64::from(u16::from(*i)));
|
||||
(0 .. coefficients.len()).fold(weight, |exp, j| {
|
||||
pairs[j].0 += exp;
|
||||
exp * i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(g_scalar, pairs)
|
||||
}
|
||||
|
||||
impl<C: Curves> Dkg<C> {
|
||||
/// 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<C: EvrfCurve> EvrfDkg<C> {
|
||||
/// participate.
|
||||
pub fn verify(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
generators: &EvrfGenerators<C>,
|
||||
generators: &Generators<C>,
|
||||
context: [u8; 32],
|
||||
t: u16,
|
||||
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||
participations: &HashMap<Participant, Participation<C>>,
|
||||
) -> Result<VerifyResult<C>, EvrfError> {
|
||||
let Ok(n) = u16::try_from(evrf_public_keys.len()) else { Err(EvrfError::TooManyParticipants)? };
|
||||
participations: &HashMap<Participant, Participation<C::ToweringCurve>>,
|
||||
) -> Result<VerifyResult<C>, 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<C: EvrfCurve> EvrfDkg<C> {
|
||||
|
||||
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<C: EvrfCurve> EvrfDkg<C> {
|
||||
|
||||
// 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::<C>::verify(
|
||||
let Ok(data) = Proof::<C>::verify(
|
||||
rng,
|
||||
&generators.0,
|
||||
&mut verifier_clone,
|
||||
@@ -396,8 +369,8 @@ impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
if faulty.contains(i) {
|
||||
continue;
|
||||
}
|
||||
let mut evrf_verifier = Generators::batch_verifier();
|
||||
Evrf::<C>::verify(
|
||||
let mut evrf_verifier = generalized_bulletproofs::Generators::batch_verifier();
|
||||
Proof::<C>::verify(
|
||||
rng,
|
||||
&generators.0,
|
||||
&mut evrf_verifier,
|
||||
@@ -423,17 +396,13 @@ impl<C: EvrfCurve> EvrfDkg<C> {
|
||||
{
|
||||
let mut share_verification_statements_actual = HashMap::with_capacity(valid.len());
|
||||
if !{
|
||||
let mut g_scalar = C::F::ZERO;
|
||||
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::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::<C>(
|
||||
let (this_g_scalar, mut these_pairs) = verifiable_encryption_statements::<C>(
|
||||
&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<C: EvrfCurve> EvrfDkg<C> {
|
||||
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(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
let sum_mask =
|
||||
sum_masks.get(j).copied().unwrap_or(<C::ToweringCurve as Ciphersuite>::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<C: EvrfCurve> EvrfDkg<C> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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::<C::G>();
|
||||
// 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,
|
||||
(<C::ToweringCurve as Ciphersuite>::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<<C::EmbeddedCurve as Ciphersuite>::F>,
|
||||
) -> Vec<ThresholdKeys<C>> {
|
||||
) -> Vec<ThresholdKeys<C::ToweringCurve>> {
|
||||
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::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(<C::ToweringCurve as Ciphersuite>::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(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
for point in ecdh_commitments {
|
||||
let (mut x, mut y) =
|
||||
<C::EmbeddedCurve as Ciphersuite>::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],
|
||||
<C::ToweringCurve as Ciphersuite>::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,
|
||||
res.push(
|
||||
ThresholdKeys::new(
|
||||
ThresholdParams::new(self.t, self.n, i).unwrap(),
|
||||
Interpolation::Lagrange,
|
||||
secret_share,
|
||||
group_key: self.group_key,
|
||||
verification_shares: self.verification_shares.clone(),
|
||||
}));
|
||||
self.verification_shares.clone(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<G: DivisorCurve<FieldElement = <Self as 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<C: Ciphersuite>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
|
||||
let mut repr = <C::G as GroupEncoding>::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<C: EvrfCurve>(pub(crate) Generators<C>);
|
||||
|
||||
impl<C: EvrfCurve> EvrfGenerators<C> {
|
||||
/// Create a new set of generators.
|
||||
pub fn new(max_threshold: u16, max_participants: u16) -> EvrfGenerators<C> {
|
||||
let g = C::generator();
|
||||
let mut rng = ChaCha20Rng::from_seed(Blake2s256::digest(g.to_bytes()).into());
|
||||
let h = sample_point::<C>(&mut rng);
|
||||
let (_, generators) =
|
||||
Evrf::<C>::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::<C>(&mut rng));
|
||||
h_bold.push(sample_point::<C>(&mut rng));
|
||||
}
|
||||
Self(Generators::new(g, h, g_bold, h_bold).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of proving for an eVRF.
|
||||
pub(crate) struct EvrfProveResult<C: Ciphersuite> {
|
||||
/// The coefficients for use in the DKG.
|
||||
pub(crate) coefficients: Vec<Zeroizing<C::F>>,
|
||||
/// The masks to encrypt secret shares with.
|
||||
pub(crate) encryption_masks: Vec<Zeroizing<C::F>>,
|
||||
/// The proof itself.
|
||||
pub(crate) proof: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The result of verifying an eVRF.
|
||||
pub(crate) struct EvrfVerifyResult<C: EvrfCurve> {
|
||||
/// The commitments to the coefficients for use in the DKG.
|
||||
pub(crate) coefficients: Vec<C::G>,
|
||||
/// The ephemeral public keys to perform ECDHs with
|
||||
pub(crate) ecdh_keys: Vec<[<C::EmbeddedCurve as Ciphersuite>::G; 2]>,
|
||||
/// The commitments to the masks used to encrypt secret shares with.
|
||||
pub(crate) encryption_commitments: Vec<C::G>,
|
||||
}
|
||||
|
||||
impl<C: EvrfCurve> fmt::Debug for EvrfVerifyResult<C> {
|
||||
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<C: EvrfCurve>(PhantomData<C>);
|
||||
impl<C: EvrfCurve> Evrf<C> {
|
||||
// Sample uniform points (via rejection-sampling) on the embedded elliptic curve
|
||||
fn transcript_to_points(
|
||||
seed: [u8; 32],
|
||||
coefficients: usize,
|
||||
) -> Vec<<C::EmbeddedCurve as Ciphersuite>::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::<C::EmbeddedCurve>(&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<N: ArrayLength>(
|
||||
generators_to_use: usize,
|
||||
start: &mut usize,
|
||||
) -> GenericArray<Variable, N> {
|
||||
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<PointWithDlog<C::EmbeddedCurveParameters>> {
|
||||
// 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<C::F>,
|
||||
evrf_public_key: (C::F, C::F),
|
||||
coefficients: usize,
|
||||
ecdh_commitments: &[[(C::F, C::F); 2]],
|
||||
generator_tables: &[&GeneratorTable<C::F, C::EmbeddedCurveParameters>],
|
||||
circuit: &mut Circuit<C>,
|
||||
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<C>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
ecdh_public_keys: &[<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_private_key: &Zeroizing<<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
) -> Result<EvrfProveResult<C>, AcError> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as EvrfCurve>::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<<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
push_generator: bool,
|
||||
generator: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G,
|
||||
dh: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G| {
|
||||
if push_generator {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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() ..
|
||||
<C::EmbeddedCurveParameters as DiscreteLogParameters>::XCoefficientsMinusOne::USIZE
|
||||
{
|
||||
vector_commitment_tape.push(<C as Ciphersuite>::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() ..
|
||||
<C::EmbeddedCurveParameters as DiscreteLogParameters>::YxCoefficients::USIZE
|
||||
{
|
||||
vector_commitment_tape.push(<C as Ciphersuite>::F::ZERO);
|
||||
}
|
||||
|
||||
vector_commitment_tape
|
||||
.push(divisor.y_coefficients.first().copied().unwrap_or(<C as Ciphersuite>::F::ZERO));
|
||||
|
||||
divisor.zeroize();
|
||||
drop(divisor);
|
||||
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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::<<C::EmbeddedCurve as Ciphersuite>::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,
|
||||
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator(),
|
||||
<<C as EvrfCurve>::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 = <C::EmbeddedCurve as Ciphersuite>::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::<<C::EmbeddedCurve as Ciphersuite>::F>::new(ecdh_private_key)
|
||||
.expect("ECDH private key was zero");
|
||||
let ecdh_commitment = <C::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key;
|
||||
ecdh_commitments.push(ecdh_commitment);
|
||||
ecdh_commitments_xy.last_mut().unwrap()[j] =
|
||||
<<C::EmbeddedCurve as Ciphersuite>::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,
|
||||
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator(),
|
||||
<<C as EvrfCurve>::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::<Result<_, _>>()?,
|
||||
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::<Vec<_>>(),
|
||||
&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::<C>());
|
||||
}
|
||||
let mut x = commitments
|
||||
.iter()
|
||||
.zip(&agg_weights)
|
||||
.map(|(commitment, weight)| commitment.mask * *weight)
|
||||
.sum::<C::F>();
|
||||
|
||||
// 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::<C>();
|
||||
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<C>,
|
||||
verifier: &mut BatchVerifier<C>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
ecdh_public_keys: &[<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_public_key: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G,
|
||||
proof: &[u8],
|
||||
) -> Result<EvrfVerifyResult<C>, ()> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::b(),
|
||||
};
|
||||
|
||||
let mut generator_tables = Vec::with_capacity(1 + (2 * coefficients) + ecdh_public_keys.len());
|
||||
{
|
||||
let (x, y) =
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::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) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(generator).unwrap();
|
||||
generator_tables.push(GeneratorTable::new(&curve_spec, x, y));
|
||||
}
|
||||
for generator in ecdh_public_keys {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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 = <C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits::USIZE;
|
||||
let divisor_len = 1 +
|
||||
<C::EmbeddedCurveParameters as DiscreteLogParameters>::XCoefficientsMinusOne::USIZE +
|
||||
<C::EmbeddedCurveParameters as DiscreteLogParameters>::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::<C::EmbeddedCurve>().map_err(|_| ())?,
|
||||
transcript.read_point::<C::EmbeddedCurve>().map_err(|_| ())?,
|
||||
];
|
||||
ecdh_keys.push(ecdh_keys_i);
|
||||
// This bans zero ECDH keys
|
||||
ecdh_keys_xy.push([
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_keys_i[0]).ok_or(())?,
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_keys_i[1]).ok_or(())?,
|
||||
]);
|
||||
}
|
||||
|
||||
let mut circuit = Circuit::verify();
|
||||
Self::circuit(
|
||||
&curve_spec,
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
|
||||
coefficients,
|
||||
&ecdh_keys_xy,
|
||||
&generator_tables.iter().collect::<Vec<_>>(),
|
||||
&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::<C>().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::<C>());
|
||||
}
|
||||
|
||||
let sum_points =
|
||||
openings.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::<C::G>();
|
||||
let sum_commitments =
|
||||
commitments.into_iter().zip(agg_weights).map(|(point, weight)| point * weight).sum::<C::G>();
|
||||
#[allow(non_snake_case)]
|
||||
let A = sum_commitments - sum_points;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let R = transcript.read_point::<C>().map_err(|_| ())?;
|
||||
let c = transcript.challenge::<C>();
|
||||
let s = transcript.read_scalar::<C>().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 })
|
||||
}
|
||||
}
|
||||
684
crypto/dkg/evrf/src/proof/mod.rs
Normal file
684
crypto/dkg/evrf/src/proof/mod.rs
Normal file
@@ -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<C> = (
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
struct Circuit<
|
||||
'a,
|
||||
C: Curves,
|
||||
CG: Iterator<
|
||||
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
>,
|
||||
> {
|
||||
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
|
||||
circuit: &'a mut BpCircuit<C::ToweringCurve>,
|
||||
challenge: DiscreteLogChallenge<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
challenged_G:
|
||||
ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
challenged_generators: &'a mut CG,
|
||||
tape: Tape,
|
||||
pedersen_commitment_tape: PedersenCommitmentTape,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
C: Curves,
|
||||
CG: Iterator<
|
||||
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::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<C>, 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::<C>(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(<C::ToweringCurve as Ciphersuite>::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<C>; 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::<C>(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(<C::ToweringCurve as Ciphersuite>::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<C: Curves> {
|
||||
/// The coefficients for use in the DKG.
|
||||
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
|
||||
/// The masks to encrypt secret shares with.
|
||||
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
|
||||
/// The proof itself.
|
||||
pub(super) proof: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(super) struct Verified<C: Curves> {
|
||||
/// The commitments to the coefficients used within the DKG.
|
||||
pub(super) coefficients: Vec<<C::ToweringCurve as Ciphersuite>::G>,
|
||||
/// The ephemeral public keys to perform ECDHs with
|
||||
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as Ciphersuite>::G; 2]>,
|
||||
/// The commitments to the masks used to encrypt secret shares with.
|
||||
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as Ciphersuite>::G>,
|
||||
}
|
||||
|
||||
impl<C: Curves> fmt::Debug for Verified<C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Verified").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
type GeneratorTable<C> = generalized_bulletproofs_ec_gadgets::GeneratorTable<
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
<C as Curves>::EmbeddedCurveParameters,
|
||||
>;
|
||||
|
||||
pub(super) struct Proof<C>(PhantomData<C>);
|
||||
impl<C: Curves> Proof<C> {
|
||||
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::<C>(1 + (2 * coefficients)) +
|
||||
(participants * 2 * Tape::variables_for_points_with_common_dlog::<C>(2))
|
||||
}
|
||||
|
||||
fn circuit(
|
||||
curve_spec: &CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
|
||||
evrf_public_key: EmbeddedPoint<C>,
|
||||
coefficients: usize,
|
||||
ecdh_commitments: &[[EmbeddedPoint<C>; 2]],
|
||||
generator_tables: &[&GeneratorTable<C>],
|
||||
circuit: &mut BpCircuit<C::ToweringCurve>,
|
||||
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::<C, _> {
|
||||
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<<C::EmbeddedCurve as Ciphersuite>::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::<C::EmbeddedCurve>(&mut rng));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Create the required tables for the generators.
|
||||
fn generator_tables(
|
||||
coefficients_evrf_points: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||
participants: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
) -> Vec<GeneratorTable<C>> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
|
||||
};
|
||||
|
||||
let mut generator_tables =
|
||||
Vec::with_capacity(1 + coefficients_evrf_points.len() + participants.len());
|
||||
{
|
||||
let (x, y) =
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::generator())
|
||||
.unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
for generator in coefficients_evrf_points {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
for generator in participants {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
generator_tables
|
||||
}
|
||||
|
||||
pub(super) fn prove(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
generators: &Generators<C::ToweringCurve>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
) -> Result<ProveResult<C>, AcError> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::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<<<C as Curves>::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<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
generator: <<C as Curves>::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(<C::ToweringCurve as Ciphersuite>::F::ZERO),
|
||||
);
|
||||
}
|
||||
|
||||
let dh = generator * dlog.scalar();
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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::<<C::EmbeddedCurve as Ciphersuite>::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,
|
||||
<<C as Curves>::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(<C::ToweringCurve as Ciphersuite>::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 =
|
||||
[(<C::ToweringCurve as Ciphersuite>::F::ZERO, <C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
2];
|
||||
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::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(<C::EmbeddedCurve as Ciphersuite>::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::<<C::EmbeddedCurve as Ciphersuite>::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,
|
||||
<<C as Curves>::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: <C::ToweringCurve as Ciphersuite>::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: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
|
||||
});
|
||||
}
|
||||
for enc_mask in &encryption_keys {
|
||||
commitments.push(PedersenCommitment {
|
||||
value: **enc_mask,
|
||||
mask: <C::ToweringCurve as Ciphersuite>::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::<Result<_, _>>()?,
|
||||
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::<Vec<_>>(),
|
||||
&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(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
for commitment in commitments {
|
||||
*x += commitment.mask * transcript.challenge::<C::ToweringCurve>();
|
||||
}
|
||||
|
||||
// Produce a Schnorr PoK for the weighted-sum of the Pedersen commitments' blinding factors
|
||||
let r = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::random(&mut *rng));
|
||||
transcript.push_point(&(generators.h() * r.deref()));
|
||||
let c = transcript.challenge::<C::ToweringCurve>();
|
||||
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<C::ToweringCurve>,
|
||||
verifier: &mut BatchVerifier<C::ToweringCurve>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_public_key: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G,
|
||||
proof: &[u8],
|
||||
) -> Result<Verified<C>, ()> {
|
||||
let (mut transcript, ecdh_commitments, pedersen_commitments) = {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::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::<C::EmbeddedCurve>().map_err(|_| ())?,
|
||||
transcript.read_point::<C::EmbeddedCurve>().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([
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
|
||||
.ok_or(())?,
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
|
||||
.ok_or(())?,
|
||||
]);
|
||||
}
|
||||
|
||||
let mut circuit = BpCircuit::verify();
|
||||
Self::circuit(
|
||||
&curve_spec,
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
|
||||
coefficients,
|
||||
&ecdh_commitments_xy,
|
||||
&generator_tables.iter().collect::<Vec<_>>(),
|
||||
&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::<C::ToweringCurve>().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::<C::ToweringCurve>();
|
||||
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::<C::ToweringCurve>().map_err(|_| ())?;
|
||||
let c = transcript.challenge::<C::ToweringCurve>();
|
||||
let s = transcript.read_scalar::<C::ToweringCurve>().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 })
|
||||
}
|
||||
}
|
||||
106
crypto/dkg/evrf/src/proof/tape.rs
Normal file
106
crypto/dkg/evrf/src/proof/tape.rs
Normal file
@@ -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<N: ArrayLength>(&mut self) -> GenericArray<Variable, N> {
|
||||
GenericArray::<Variable, N>::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<C: Curves>(
|
||||
&mut self,
|
||||
quantity: usize,
|
||||
) -> impl use<'_, C> + Iterator<Item = PointWithDlog<C::EmbeddedCurveParameters>> {
|
||||
/*
|
||||
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::<<C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>();
|
||||
|
||||
struct PointIterator<'a, C: Curves>(
|
||||
&'a mut Tape,
|
||||
GenericArray<Variable, <C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>,
|
||||
PhantomData<C>,
|
||||
);
|
||||
impl<'a, C: Curves> Iterator for PointIterator<'a, C> {
|
||||
type Item = PointWithDlog<C::EmbeddedCurveParameters>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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::<C>).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<C: Curves>(quantity: usize) -> usize {
|
||||
let mut dummy_tape = Tape::new(usize::MAX);
|
||||
for _ in dummy_tape.read_points_with_common_dlog::<C>(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
|
||||
}
|
||||
}
|
||||
@@ -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::<Pallas>::new(THRESHOLD, PARTICIPANTS);
|
||||
fn dkg() {
|
||||
let generators = Generators::<Ristretto>::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 = <Vesta as Ciphersuite>::F::random(&mut OsRng);
|
||||
pub_keys.push(<Vesta as Ciphersuite>::generator() * priv_key);
|
||||
let priv_key = <Embedwards25519 as Ciphersuite>::F::random(&mut OsRng);
|
||||
pub_keys.push(<Embedwards25519 as Ciphersuite>::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::<Pallas>::participate(
|
||||
Dkg::<Ristretto>::participate(
|
||||
&mut OsRng,
|
||||
&generators,
|
||||
context,
|
||||
@@ -46,7 +46,7 @@ fn evrf_dkg() {
|
||||
);
|
||||
}
|
||||
|
||||
let VerifyResult::Valid(dkg) = EvrfDkg::<Pallas>::verify(
|
||||
let VerifyResult::Valid(dkg) = Dkg::<Ristretto>::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::<HashMap<_, _>>();
|
||||
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!(
|
||||
<<Ristretto as Curves>::ToweringCurve as Ciphersuite>::generator() *
|
||||
*recover_key(&all_keys.values().cloned().collect::<Vec<_>>()).unwrap(),
|
||||
group_key.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<{ <<Vesta as Ciphersuite>::F as PrimeField>::NUM_BITS as usize }>;
|
||||
type XCoefficients = Quot<Sum<Self::ScalarBits, U1>, U2>;
|
||||
type XCoefficientsMinusOne = Diff<Self::XCoefficients, U1>;
|
||||
type YxCoefficients = Diff<Quot<Sum<Sum<Self::ScalarBits, U1>, U1>, U2>, U2>;
|
||||
}
|
||||
|
||||
impl EvrfCurve for Pallas {
|
||||
type EmbeddedCurve = Vesta;
|
||||
type EmbeddedCurveParameters = VestaParams;
|
||||
}
|
||||
|
||||
fn evrf_proof_test<C: EvrfCurve>() {
|
||||
fn proof<C: Curves>() {
|
||||
let generators = generators(2048);
|
||||
let vesta_private_key = Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng));
|
||||
let ecdh_public_keys = [
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng),
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng),
|
||||
];
|
||||
let embedded_private_key =
|
||||
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng));
|
||||
let ecdh_public_keys: [_; PARTICIPANTS as usize] =
|
||||
core::array::from_fn(|_| <C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng));
|
||||
let time = Instant::now();
|
||||
let res =
|
||||
Evrf::<C>::prove(&mut OsRng, &generators, [0; 32], 1, &ecdh_public_keys, &vesta_private_key)
|
||||
let res = Proof::<C>::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::<C>::verify(
|
||||
Proof::<C>::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<C: EvrfCurve>() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pallas_evrf_proof_test() {
|
||||
evrf_proof_test::<Pallas>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secp256k1_evrf_proof_test() {
|
||||
evrf_proof_test::<Secp256k1>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_evrf_proof_test() {
|
||||
evrf_proof_test::<Ed25519>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ristretto_evrf_proof_test() {
|
||||
evrf_proof_test::<Ristretto>();
|
||||
fn ristretto_proof() {
|
||||
proof::<Ristretto>();
|
||||
}
|
||||
|
||||
43
crypto/dkg/evrf/src/utils.rs
Normal file
43
crypto/dkg/evrf/src/utils.rs
Normal file
@@ -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<C: Ciphersuite>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
|
||||
let mut repr = <C::G as GroupEncoding>::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<F: PrimeField + Zeroize>(
|
||||
coefficients: &[Zeroizing<F>],
|
||||
l: Participant,
|
||||
) -> Zeroizing<F> {
|
||||
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
|
||||
}
|
||||
@@ -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<Item = Participant> {
|
||||
struct ParticipantIterator(u16);
|
||||
impl Iterator for ParticipantIterator {
|
||||
type Item = Participant;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0 = self.0.checked_add(1)?;
|
||||
Some(Participant(self.0))
|
||||
}
|
||||
}
|
||||
ParticipantIterator(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Participant> for u16 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
|
||||
let mut res = $FieldName::ZERO;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
|
||||
let mut res = $FieldName::ZERO;
|
||||
|
||||
30
patches/ciphersuite/Cargo.toml
Normal file
30
patches/ciphersuite/Cargo.toml
Normal file
@@ -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 <lukeparker5132@gmail.com>"]
|
||||
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"]
|
||||
21
patches/ciphersuite/LICENSE
Normal file
21
patches/ciphersuite/LICENSE
Normal file
@@ -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.
|
||||
4
patches/ciphersuite/README.md
Normal file
4
patches/ciphersuite/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ciphersuite
|
||||
|
||||
Patch for the `crates.io` ciphersuite to use the in-tree ciphersuite, resolving
|
||||
breaking changes made since.
|
||||
5
patches/ciphersuite/src/lib.rs
Normal file
5
patches/ciphersuite/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use ciphersuite::*;
|
||||
#[cfg(feature = "ed25519")]
|
||||
use dalek_ff_group::Ed25519;
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user