feat(zk): empower the poq (#3259)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
thomaslavaur
2026-08-06 08:09:42 +00:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent c66f86cb51
commit 23eb2a4635
32 changed files with 699 additions and 230 deletions
Generated
+26 -26
View File
@@ -105,7 +105,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -116,7 +116,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -1567,7 +1567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
"syn 2.0.118",
"syn 1.0.109",
]
[[package]]
@@ -1752,7 +1752,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -1968,7 +1968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -3070,7 +3070,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -4297,8 +4297,8 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-build"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"dirs",
"fd-lock",
@@ -4309,16 +4309,16 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-common"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"logos-blockchain-circuits-types",
]
[[package]]
name = "logos-blockchain-circuits-poc-sys"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"logos-blockchain-circuits-build",
"logos-blockchain-circuits-common",
@@ -4327,8 +4327,8 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-pol-sys"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"logos-blockchain-circuits-build",
"logos-blockchain-circuits-common",
@@ -4337,8 +4337,8 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-poq-sys"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"logos-blockchain-circuits-build",
"logos-blockchain-circuits-common",
@@ -4354,8 +4354,8 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-signature-sys"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"logos-blockchain-circuits-build",
"logos-blockchain-circuits-common",
@@ -4364,8 +4364,8 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-types"
version = "0.5.3"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512"
version = "0.5.5"
source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.5#32f1ac43f331f0eeff3a2a0aec0f153564d03583"
dependencies = [
"bytes",
"libc",
@@ -5890,7 +5890,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7457,7 +7457,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7967,7 +7967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8262,7 +8262,7 @@ dependencies = [
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8294,7 +8294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
dependencies = [
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -9525,7 +9525,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
+5 -5
View File
@@ -164,11 +164,11 @@ lb-zksign = { default-features = false, package = "logo
lb-zone-sdk = { default-features = false, package = "logos-blockchain-zone-sdk", path = "./zone-sdk" }
# When bumping any lbc-* tag, also update the logos-blockchain-circuits flake input tag in flake.nix
# and run `nix flake lock --update-input logos-blockchain-circuits`.
lbc-poc-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-poc-sys", tag = "v0.5.3" }
lbc-pol-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-pol-sys", tag = "v0.5.3" }
lbc-poq-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-poq-sys", tag = "v0.5.3" }
lbc-signature-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-signature-sys", tag = "v0.5.3" }
lbc-types = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-types", tag = "v0.5.3" }
lbc-poc-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-poc-sys", tag = "v0.5.5" }
lbc-pol-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-pol-sys", tag = "v0.5.5" }
lbc-poq-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-poq-sys", tag = "v0.5.5" }
lbc-signature-sys = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-signature-sys", tag = "v0.5.5" }
lbc-types = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-circuits.git", package = "logos-blockchain-circuits-types", tag = "v0.5.5" }
lbp-error = { default-features = false, package = "logos-blockchain-proofs-error", path = "./zk/proofs/error" }
testing-framework-core = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-testing.git", rev = "db880d61155db3a4f32177ef16b0f1e4bf22b1f6" }
testing-framework-runner-compose = { default-features = false, git = "https://github.com/logos-blockchain/logos-blockchain-testing.git", rev = "db880d61155db3a4f32177ef16b0f1e4bf22b1f6" }
+5 -2
View File
@@ -5,7 +5,7 @@ use lb_blend_proofs::{
self, ProofOfQuota, VerifiedProofOfQuota,
inputs::prove::{
PublicInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
},
},
selection::{self, ProofOfSelection, VerifiedProofOfSelection, inputs::VerifyInputs},
@@ -24,6 +24,7 @@ use crate::encap::ProofsVerifier;
pub struct PoQVerificationInputsMinusSigningKey {
pub core: CoreInputs,
pub leader: LeaderInputs,
pub pow: PowInputs,
}
#[cfg(test)]
@@ -44,6 +45,7 @@ impl Default for PoQVerificationInputsMinusSigningKey {
lottery_0: Fr::ZERO,
lottery_1: Fr::ZERO,
},
pow: PowInputs::unwired_placeholder(),
}
}
}
@@ -77,7 +79,7 @@ impl ProofsVerifier for RealProofsVerifier {
proof: ProofOfQuota,
signing_key: &Ed25519PublicKey,
) -> Result<VerifiedProofOfQuota, Self::Error> {
let PoQVerificationInputsMinusSigningKey { core, leader } = self.current_inputs;
let PoQVerificationInputsMinusSigningKey { core, leader, pow } = self.current_inputs;
// Try with current input, and if it fails, try with the previous one, if any
// (i.e., within the epoch transition period).
@@ -90,6 +92,7 @@ impl ProofsVerifier for RealProofsVerifier {
.verify(&PublicInputs {
core,
leader,
pow,
signing_key: *signing_key.as_inner(),
})
.map_err(Error::ProofOfQuota);
+127 -58
View File
@@ -6,8 +6,8 @@ use crate::quota::{
Ed25519PublicKey,
inputs::prove::{
PublicInputs,
private::{ProofOfCoreQuotaInputs, ProofOfLeadershipQuotaInputs},
public::{CoreInputs, LeaderInputs},
private::{ProofOfCoreQuotaInputs, ProofOfLeadershipQuotaInputs, ProofOfWorkQuotaInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
},
};
@@ -28,11 +28,10 @@ pub fn valid_proof_of_core_quota_inputs(
signing_key,
core: CoreInputs {
quota: core_quota,
zk_root:
"10774149910279330054096178616484626574938100628643657398591620611653283350567"
.parse::<BigUint>()
.unwrap()
.into(),
zk_root: "8648439177578503276872559887462337398415999326614652623958174178821393732006"
.parse::<BigUint>()
.unwrap()
.into(),
},
leader: LeaderInputs {
pol_epoch_nonce: BigUint::from(1u64).into(),
@@ -41,93 +40,97 @@ pub fn valid_proof_of_core_quota_inputs(
lottery_0,
lottery_1,
},
pow: PowInputs {
pow_blend_difficulty: BigUint::from(1u64).into(),
pow_quota: 1,
},
};
let private_inputs = ProofOfCoreQuotaInputs {
core_path_and_selectors: [
(
"13664394408134714148474230123476522725121022841643115538772760906625244311247",
false,
),
(
"19991080109804084229148819955447485980685271866236940378355513894509403472825",
false,
),
(
"1719423980074249650282196742833822060536614400613876573240720330836944657046",
"7495929195953210541208591724669352166258167386402673863836582332959049741998",
true,
),
(
"9139059421670197230454683564023894631544291367010097325710917953251896109820",
true,
),
(
"5364655248740251237070591917425080374871240308613634110599105641060308262480",
"8777307683015870709408351814333112152804794621246499110711643849444602886454",
false,
),
(
"4321492523488456904492279703871677986518013774282254436225897785632983582392",
"2036778247346771710692375429289939913839125337381038976370669720942761418159",
false,
),
(
"2990224148203398705081847530695215902031679797575769158839150593627687374477",
true,
),
(
"10115668570671746589151792513284313334215992216493690986604010084064526087897",
"5150238042344629386129384874056943525919076545561275756461664527118718260316",
false,
),
(
"6861547258030107481535610157091754819359096245516659866713074178195667725661",
"4936482837140898160990313117063047116523911033722639295600657041261330950251",
true,
),
(
"8937835534203757508211957478554582398600683099364069427807070320868647534222",
false,
),
(
"7263098007194293142502159691381240540663513841811088748578355471278661748398",
true,
),
(
"19870876389339998317953427375552419155167215853924118334240834433312282250010",
true,
),
(
"8986537640578106247026054824010082082887537390181434985471297569735311570527",
true,
),
(
"2677828888032047082692090535060454810065355495500936244017540612071241172074",
"20952980496079374121937279088975994236259798048934859728621453910938508315429",
false,
),
(
"17555659938930335545491158932916791395314469587225130013830235195468776678581",
true,
),
(
"9071399479181997918431555676567419518917037685697025275563780306843446304571",
"3127567055231452038159138967497074106547716639376277019791213152208715502108",
false,
),
(
"6281992864280083991852918191693022891803454129450254484599792207873685708287",
false,
),
(
"17218725285443376817865240748479799866804154572691475341349156521930190101705",
false,
),
(
"14439152042823579940992841514440450319175689090721997229482427278436880180442",
"4156278452715681991545944691699148599903237834447534444828105997249209183668",
true,
),
(
"4375092350636859316334567236773062868645005901112681926345752432026107616905",
"18179601246737692255359026481063507429449403349479564041630890140769203876921",
true,
),
(
"2391466682452790260991116539507901841740149925567473331054306831262572734122",
"20437752904871274044354648385284243748143753378271484669957282746297202768728",
false,
),
(
"20964128655175295722279612439890819641847965870342369257222152533521036200042",
false,
),
(
"8529481264681700999783095712819666338972397694516091342589511693767349600946",
true,
),
(
"11390366370077994090069924023908925478862142650386311632070176411828525668517",
true,
),
(
"5917548759338686071972714539493044537194763096388397090814246211223906547270",
false,
),
(
"20167409213677086350276694709897905285253180362287004012973310966314090006733",
true,
),
(
"2263236988262202014904251664760262847563737954962247776121488194923407644186",
false,
),
(
"19725021492526941122134445491655907123723766494432771594886452496452660956448",
false,
),
(
"5142311427791207713153244085802613121123367252788982353300919738927631606801",
false,
),
(
"9162839441909107733910143825064550229009975662836406280871655871464192283628",
false,
),
]
.map(|(value, selector)| (value.parse::<BigUint>().unwrap().into(), selector)),
core_sk: "6576495977526760241501499963811136028674473098047722736567052812753357178145"
core_sk: "12667207762918681737213577433372645554941956657477853383508894204444641435708"
.parse::<BigUint>()
.unwrap()
.into(),
@@ -170,6 +173,10 @@ pub fn valid_proof_of_leadership_quota_inputs(
zk_root: BigUint::from(1u64).into(),
quota: 1,
},
pow: PowInputs {
pow_blend_difficulty: BigUint::from(1u64).into(),
pow_quota: 1,
},
};
let private_inputs = ProofOfLeadershipQuotaInputs {
@@ -320,3 +327,65 @@ pub fn valid_proof_of_leadership_quota_inputs(
(public_inputs, private_inputs)
}
// Reference values taken from the tests within the `poq` crate.
#[must_use]
pub fn valid_proof_of_work_quota_inputs(
signing_key: Ed25519PublicKey,
pow_quota: u64,
) -> (PublicInputs, ProofOfWorkQuotaInputs) {
let public_inputs = PublicInputs {
signing_key,
pow: PowInputs {
pow_blend_difficulty:
"10631870504716456348838861774188160492563879712126054449569633827216160699117"
.parse::<BigUint>()
.unwrap()
.into(),
pow_quota,
},
core: CoreInputs {
zk_root: "7196120816867742764244305920813414287385070139349175831448761050095304066684"
.parse::<BigUint>()
.unwrap()
.into(),
quota: 10,
},
leader: LeaderInputs {
pol_epoch_nonce:
"10612486568558363932403470969435464905237449260307875593547304337083779862075"
.parse::<BigUint>()
.unwrap()
.into(),
pol_ledger_aged:
"12901234158445930888471913831760642108627105440164948073754288938400615804116"
.parse::<BigUint>()
.unwrap()
.into(),
message_quota: 15,
lottery_0: "148409079361904587837471709956458430342187235603420891378597419711486680"
.parse::<BigUint>()
.unwrap()
.into(),
lottery_1:
"21888242871336145414933615591437256729835795974444825699352339602896135814447"
.parse::<BigUint>()
.unwrap()
.into(),
},
};
let private_inputs = ProofOfWorkQuotaInputs {
pow_sk: "10044758699144566038746293679996441958807939592793641056821682251877616662024"
.parse::<BigUint>()
.unwrap()
.into(),
pow_block_hash:
"17412116459874055221726429396167151400213699852365025340176158516975240665302"
.parse::<BigUint>()
.unwrap()
.into(),
};
(public_inputs, private_inputs)
}
+19 -3
View File
@@ -1,11 +1,13 @@
use lb_groth16::fr_from_bytes;
use lb_poq::{
PoQBlendInputsData, PoQChainInputsData, PoQCommonInputsData, PoQInputsFromDataError,
PoQWalletInputsData, PoQWitnessInputs,
PoQPowInputsData, PoQWalletInputsData, PoQWitnessInputs,
};
use crate::quota::inputs::{
prove::private::{ProofOfCoreQuotaInputs, ProofOfLeadershipQuotaInputs, ProofType},
prove::private::{
ProofOfCoreQuotaInputs, ProofOfLeadershipQuotaInputs, ProofOfWorkQuotaInputs, ProofType,
},
split_ephemeral_signing_key,
};
@@ -36,6 +38,7 @@ impl TryFrom<Inputs> for PoQWitnessInputs {
core_quota: value.public.core.quota,
index: value.private.key_index,
leader_quota: value.public.leader.message_quota,
pow_quota: value.public.pow.pow_quota,
message_key: (
fr_from_bytes(&signing_key_first_half[..]).expect(
"First half of signing public key does not represent a valid `Fr` point.",
@@ -45,6 +48,7 @@ impl TryFrom<Inputs> for PoQWitnessInputs {
),
),
selector: value.private.selector,
pow_difficulty: value.public.pow.pow_blend_difficulty,
};
witness_input_for_proof_type(
chain_input_data,
@@ -91,7 +95,7 @@ fn witness_input_for_proof_type(
note_value,
output_number,
slot,
secret_key,
pol_secret_key: secret_key,
transaction_hash,
};
PoQWitnessInputs::from_leader_data(
@@ -100,5 +104,17 @@ fn witness_input_for_proof_type(
wallet_input_data,
)
}
ProofType::PowQuota(pow_quota_private_inputs) => {
let ProofOfWorkQuotaInputs {
pow_sk,
pow_block_hash,
} = *pow_quota_private_inputs;
let pow_input_data = PoQPowInputsData {
pow_secret_key: pow_sk,
block_hash: pow_block_hash,
};
PoQWitnessInputs::from_pow_data(chain_input_data, common_input_data, pow_input_data)
}
}
}
+37 -5
View File
@@ -1,6 +1,6 @@
use core::fmt::{self, Debug, Formatter};
use lb_poq::AgedNotePathAndSelectors;
use lb_poq::{AgedNotePathAndSelectors, PoQSelector};
use zeroize::ZeroizeOnDrop;
use crate::{
@@ -16,7 +16,7 @@ use crate::{
#[non_exhaustive]
pub struct Inputs {
pub key_index: u64,
pub selector: bool,
pub selector: PoQSelector,
pub proof_type: ProofType,
}
@@ -47,6 +47,19 @@ impl Inputs {
}
}
#[must_use]
pub fn new_proof_of_work_quota_inputs(
key_index: u64,
proof_of_work_quota_inputs: ProofOfWorkQuotaInputs,
) -> Self {
let proof_type: ProofType = proof_of_work_quota_inputs.into();
Self {
key_index,
selector: proof_type.proof_selector(),
proof_type,
}
}
/// Return the right `sk` for a Proof of Quota depending on the proof type, as per the spec: <https://lip.logos.co/blockchain/raw/proof-of-quota.html#constraints>.
#[must_use]
pub fn get_secret_selection_randomness_sk(
@@ -71,6 +84,10 @@ impl Inputs {
slot_number: leadership_quota_private_inputs.slot,
}
}
ProofType::PowQuota(pow_quota_private_inputs) => SelectionRandomnessSecretInput::Pow {
pow_sk: pow_quota_private_inputs.pow_sk,
epoch_nonce: *pol_epoch_nonce,
},
}
}
}
@@ -79,6 +96,7 @@ impl Inputs {
pub enum ProofType {
CoreQuota(Box<ProofOfCoreQuotaInputs>),
LeadershipQuota(Box<ProofOfLeadershipQuotaInputs>),
PowQuota(Box<ProofOfWorkQuotaInputs>),
}
impl Debug for ProofType {
@@ -86,16 +104,18 @@ impl Debug for ProofType {
match self {
Self::CoreQuota(_) => f.write_str("ProofType::CoreQuota"),
Self::LeadershipQuota(_) => f.write_str("ProofType::LeadershipQuota"),
Self::PowQuota(_) => f.write_str("ProofType::PowQuota"),
}
}
}
impl ProofType {
#[must_use]
pub const fn proof_selector(&self) -> bool {
pub const fn proof_selector(&self) -> PoQSelector {
match self {
Self::CoreQuota(_) => false,
Self::LeadershipQuota(_) => true,
Self::CoreQuota(_) => PoQSelector::Core,
Self::LeadershipQuota(_) => PoQSelector::Leader,
Self::PowQuota(_) => PoQSelector::Pow,
}
}
}
@@ -127,3 +147,15 @@ impl From<ProofOfLeadershipQuotaInputs> for ProofType {
Self::LeadershipQuota(Box::new(value))
}
}
#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)]
pub struct ProofOfWorkQuotaInputs {
pub pow_sk: ZkHash,
pub pow_block_hash: ZkHash,
}
impl From<ProofOfWorkQuotaInputs> for ProofType {
fn from(value: ProofOfWorkQuotaInputs) -> Self {
Self::PowQuota(Box::new(value))
}
}
+41 -1
View File
@@ -1,6 +1,6 @@
use core::fmt::{self, Debug, Formatter};
use lb_groth16::{Fr, fr_to_bytes};
use lb_groth16::{AdditiveGroup as _, Fr, fr_to_bytes};
use serde::{Deserialize, Serialize};
use crate::{ZkHash, quota::Ed25519PublicKey};
@@ -11,6 +11,7 @@ pub struct Inputs {
pub signing_key: Ed25519PublicKey,
pub core: CoreInputs,
pub leader: LeaderInputs,
pub pow: PowInputs,
}
impl Debug for Inputs {
@@ -19,6 +20,7 @@ impl Debug for Inputs {
.field("signing_key", &hex::encode(self.signing_key.as_bytes()))
.field("core", &self.core)
.field("leader", &self.leader)
.field("pow", &self.pow)
.finish()
}
}
@@ -32,6 +34,7 @@ impl Default for Inputs {
signing_key: Ed25519PublicKey::from_bytes(&[0; ED25519_PUBLIC_KEY_SIZE]).unwrap(),
core: CoreInputs::default(),
leader: LeaderInputs::default(),
pow: PowInputs::default(),
}
}
}
@@ -84,3 +87,40 @@ impl Debug for LeaderInputs {
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(test, derive(Default))]
pub struct PowInputs {
#[serde(with = "lb_groth16::serde::serde_fr")]
pub pow_blend_difficulty: ZkHash,
pub pow_quota: u64,
}
impl PowInputs {
/// Placeholder inputs for the `PoW` quota parameters, which are not plumbed
/// through from the chain yet.
///
/// These are public inputs, so a prover and the verifier checking its
/// proofs must use identical values. Every call site shares this single
/// definition so they cannot drift apart.
// TODO: Remove once the PoW quota parameters are sourced from the chain.
#[must_use]
pub const fn unwired_placeholder() -> Self {
Self {
pow_blend_difficulty: ZkHash::ZERO,
pow_quota: 0,
}
}
}
impl Debug for PowInputs {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("PowInputs")
.field(
"pow_blend_difficulty",
&hex::encode(fr_to_bytes(&self.pow_blend_difficulty)),
)
.field("pow_quota", &self.pow_quota)
.finish()
}
}
+2
View File
@@ -43,6 +43,8 @@ impl From<Inputs> for PoQVerifierInput {
.expect("Second half of signing public key does not represent a valid `Fr` point."),
key_nullifier: value.key_nullifier,
leader_quota: value.prove_inputs.leader.message_quota,
pow_quota: value.prove_inputs.pow.pow_quota,
pow_blend_difficulty: value.prove_inputs.pow.pow_blend_difficulty,
pol_epoch_nonce: value.prove_inputs.leader.pol_epoch_nonce,
pol_ledger_aged: value.prove_inputs.leader.pol_ledger_aged,
lottery_0: value.prove_inputs.leader.lottery_0,
+8
View File
@@ -251,6 +251,10 @@ pub enum SelectionRandomnessSecretInput {
note_secret_key: ZkHash,
slot_number: u64,
},
Pow {
pow_sk: ZkHash,
epoch_nonce: ZkHash,
},
}
const DOMAIN_SEPARATION_TAG: [u8; 23] = *b"SELECTION_RANDOMNESS_V1";
static DOMAIN_SEPARATION_TAG_FR: LazyLock<ZkHash> = LazyLock::new(|| {
@@ -268,6 +272,10 @@ fn generate_secret_selection_randomness(
note_secret_key,
slot_number,
} => (note_secret_key, (*slot_number).into()),
SelectionRandomnessSecretInput::Pow {
pow_sk,
epoch_nonce,
} => (pow_sk, (*epoch_nonce)),
};
[
*DOMAIN_SEPARATION_TAG_FR,
+28 -2
View File
@@ -6,11 +6,14 @@ use lb_key_management_system_keys::keys::UnsecuredZkKey;
use crate::{
quota::{
DOMAIN_SEPARATION_TAG_FR, ED25519_PUBLIC_KEY_SIZE, Ed25519PublicKey, VerifiedProofOfQuota,
fixtures::{valid_proof_of_core_quota_inputs, valid_proof_of_leadership_quota_inputs},
fixtures::{
valid_proof_of_core_quota_inputs, valid_proof_of_leadership_quota_inputs,
valid_proof_of_work_quota_inputs,
},
inputs::prove::{
PrivateInputs, PublicInputs,
private::ProofOfCoreQuotaInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
},
},
selection::derive_key_nullifier_from_secret_selection_randomness,
@@ -104,6 +107,26 @@ fn valid_proof_of_leadership_quota() {
);
}
#[test]
fn valid_proof_of_work_quota() {
let (public_inputs, private_inputs) = valid_proof_of_work_quota_inputs(
Ed25519PublicKey::from_bytes(&[0; ED25519_PUBLIC_KEY_SIZE]).unwrap(),
20,
);
let (proof, secret_selection_randomness) = VerifiedProofOfQuota::new(
&public_inputs,
PrivateInputs::new_proof_of_work_quota_inputs(0, private_inputs),
)
.unwrap();
let verified_proof_of_quota = proof.into_inner().verify(&public_inputs).unwrap();
assert_eq!(
derive_key_nullifier_from_secret_selection_randomness(secret_selection_randomness),
verified_proof_of_quota.key_nullifier()
);
}
struct PoQInputs<const INPUTS: usize> {
public_inputs: PublicInputs,
secret_inputs: [ProofOfCoreQuotaInputs; INPUTS],
@@ -137,6 +160,7 @@ fn generate_inputs<const INPUTS: usize>() -> PoQInputs<INPUTS> {
PublicInputs {
core: core_inputs,
leader: leader_inputs,
pow: PowInputs::default(),
signing_key,
}
};
@@ -254,6 +278,7 @@ fn same_key_different_indices() {
zk_root: merkle_tree.root(),
},
leader: LeaderInputs::default(),
pow: PowInputs::default(),
signing_key: Ed25519PublicKey::from_bytes(&[10; _]).unwrap(),
},
secret_inputs: [ProofOfCoreQuotaInputs {
@@ -306,6 +331,7 @@ fn different_keys_same_index() {
zk_root: merkle_tree.root(),
},
leader: LeaderInputs::default(),
pow: PowInputs::default(),
signing_key: Ed25519PublicKey::from_bytes(&[1; _]).unwrap(),
},
secret_inputs: [ProofOfCoreQuotaInputs {
@@ -212,7 +212,7 @@ mod test {
use lb_blend_message::crypto::proofs::PoQVerificationInputsMinusSigningKey;
use lb_blend_proofs::quota::inputs::prove::{
private::ProofOfLeadershipQuotaInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
};
use lb_core::crypto::ZkHash;
use lb_cryptarchia_engine::Epoch;
@@ -257,6 +257,7 @@ mod test {
zk_root: ZkHash::ZERO,
},
leader: leader_inputs,
pow: PowInputs::unwired_placeholder(),
},
MockCorePoQGenerator,
Epoch::new(0),
@@ -124,7 +124,7 @@ mod test {
use lb_blend_message::crypto::proofs::PoQVerificationInputsMinusSigningKey;
use lb_blend_proofs::quota::inputs::prove::{
private::ProofOfLeadershipQuotaInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
};
use lb_core::crypto::ZkHash;
use lb_cryptarchia_engine::Epoch;
@@ -176,6 +176,7 @@ mod test {
zk_root: ZkHash::ZERO,
},
leader: initial_leader,
pow: PowInputs::unwired_placeholder(),
},
MockCorePoQGenerator,
Epoch::new(0),
@@ -109,6 +109,7 @@ where
signing_key: ephemeral_signing_key.public_key().into_inner(),
core: public_inputs.core,
leader: public_inputs.leader,
pow: public_inputs.pow,
},
key_index,
).await.expect("Core PoQ generation should not fail.");
@@ -104,6 +104,7 @@ where
let current_epoch_membership_size = self.core_proofs_generator.settings.membership_size;
let current_epoch_core_public_inputs =
self.core_proofs_generator.settings.public_inputs.core;
let current_epoch_pow_public_inputs = self.core_proofs_generator.settings.public_inputs.pow;
self.leader_proofs_generator = Some(RealLeaderProofsGenerator::new(
ProofsGeneratorSettings {
@@ -113,6 +114,7 @@ where
public_inputs: PoQVerificationInputsMinusSigningKey {
core: current_epoch_core_public_inputs,
leader: current_leader_inputs,
pow: current_epoch_pow_public_inputs,
},
encapsulation_layers: self.core_proofs_generator.settings.encapsulation_layers,
},
@@ -115,6 +115,7 @@ fn create_proof_stream(
signing_key: ephemeral_signing_key.public_key().into_inner(),
core: public_inputs.core,
leader: public_inputs.leader,
pow: public_inputs.pow,
},
PrivateInputs::new_proof_of_leadership_quota_inputs(
message_release_index,
@@ -14,7 +14,7 @@ use lb_key_management_system_keys::keys::{ED25519_PUBLIC_KEY_SIZE, Ed25519Public
use crate::message_blend::CoreProofOfQuotaGenerator;
pub const fn poq_public_inputs_from_epoch_public_inputs_and_signing_key(
(PoQVerificationInputsMinusSigningKey { core, leader }, signing_key): (
(PoQVerificationInputsMinusSigningKey { core, leader, pow }, signing_key): (
PoQVerificationInputsMinusSigningKey,
Ed25519PublicKey,
),
@@ -23,20 +23,26 @@ pub const fn poq_public_inputs_from_epoch_public_inputs_and_signing_key(
signing_key: signing_key.into_inner(),
core,
leader,
pow,
}
}
pub fn valid_proof_of_quota_inputs(
core_quota: u64,
) -> (PoQVerificationInputsMinusSigningKey, ProofOfCoreQuotaInputs) {
let (PoQPublicInputs { core, leader, .. }, private_inputs) = valid_proof_of_core_quota_inputs(
let (
PoQPublicInputs {
core, leader, pow, ..
},
private_inputs,
) = valid_proof_of_core_quota_inputs(
Ed25519PublicKey::from_bytes(&[0; ED25519_PUBLIC_KEY_SIZE])
.unwrap()
.into_inner(),
core_quota,
);
(
PoQVerificationInputsMinusSigningKey { core, leader },
PoQVerificationInputsMinusSigningKey { core, leader, pow },
private_inputs,
)
}
@@ -47,15 +53,19 @@ pub fn valid_proof_of_leader_inputs(
PoQVerificationInputsMinusSigningKey,
ProofOfLeadershipQuotaInputs,
) {
let (PoQPublicInputs { core, leader, .. }, private_inputs) =
valid_proof_of_leadership_quota_inputs(
Ed25519PublicKey::from_bytes(&[0; ED25519_PUBLIC_KEY_SIZE])
.unwrap()
.into_inner(),
leader_quota,
);
let (
PoQPublicInputs {
core, leader, pow, ..
},
private_inputs,
) = valid_proof_of_leadership_quota_inputs(
Ed25519PublicKey::from_bytes(&[0; ED25519_PUBLIC_KEY_SIZE])
.unwrap()
.into_inner(),
leader_quota,
);
(
PoQVerificationInputsMinusSigningKey { core, leader },
PoQVerificationInputsMinusSigningKey { core, leader, pow },
private_inputs,
)
}
+8 -3
View File
@@ -104,7 +104,10 @@ mod tests {
use std::collections::HashMap;
use ark_ff::AdditiveGroup as _;
use lb_blend_proofs::{quota::VerifiedProofOfQuota, selection::VerifiedProofOfSelection};
use lb_blend_proofs::{
quota::{PROOF_OF_QUOTA_SIZE, VerifiedProofOfQuota},
selection::VerifiedProofOfSelection,
};
use lb_groth16::{CompressedGroth16Proof, Fr};
use lb_key_management_system_keys::keys::{Ed25519Key, Ed25519Signature, ZkKey, ZkPublicKey};
use lb_utils::bounded::BoundedError;
@@ -575,7 +578,8 @@ mod tests {
let blend_proof = ActivityProof {
epoch: 42.into(),
signing_key: signing_key.public_key(),
proof_of_quota: VerifiedProofOfQuota::from_bytes_unchecked([0u8; 160]).into(),
proof_of_quota: VerifiedProofOfQuota::from_bytes_unchecked([0u8; PROOF_OF_QUOTA_SIZE])
.into(),
proof_of_selection: VerifiedProofOfSelection::from_bytes_unchecked([0u8; 32]).into(),
};
@@ -631,7 +635,8 @@ mod tests {
let blend_proof = ActivityProof {
epoch: u32::MAX.into(),
signing_key: signing_key.public_key(),
proof_of_quota: VerifiedProofOfQuota::from_bytes_unchecked([0u8; 160]).into(),
proof_of_quota: VerifiedProofOfQuota::from_bytes_unchecked([0u8; PROOF_OF_QUOTA_SIZE])
.into(),
proof_of_selection: VerifiedProofOfSelection::from_bytes_unchecked([0u8; 32]).into(),
};
Generated
+8 -8
View File
@@ -20,17 +20,17 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1781004244,
"narHash": "sha256-zd4XYY+HPOMJXigHQDmXDvt7D1988YNPUwPPznhpFdA=",
"lastModified": 1785937360,
"narHash": "sha256-Id4IZ1pQ5qcTabs4fFWbfSZPHEbyDAx/3CDKv4CawII=",
"owner": "logos-blockchain",
"repo": "logos-blockchain-circuits",
"rev": "2846ee7a4cfa24458bb8063412ab2e753b344d2f",
"rev": "32f1ac43f331f0eeff3a2a0aec0f153564d03583",
"type": "github"
},
"original": {
"owner": "logos-blockchain",
"ref": "v0.5.5",
"repo": "logos-blockchain-circuits",
"rev": "2846ee7a4cfa24458bb8063412ab2e753b344d2f",
"type": "github"
}
},
@@ -98,17 +98,17 @@
]
},
"locked": {
"lastModified": 1780024773,
"narHash": "sha256-aU9nlrS9S+IJ2EiCzsaxzOXUhggogqTrJojBicE6Oeg=",
"lastModified": 1785302874,
"narHash": "sha256-fpKEww3TJoo1ANHO2q918ei+ayOrp0YEQAO1DuBLOB4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "40b0a3a193e0840c76174b4a322874c8f6dd0a63",
"rev": "b99d48435bc3e34309d2c7ae6f7d45e77a156c38",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "40b0a3a193e0840c76174b4a322874c8f6dd0a63",
"rev": "b99d48435bc3e34309d2c7ae6f7d45e77a156c38",
"type": "github"
}
},
+1 -6
View File
@@ -12,13 +12,8 @@
crane.url = "github:ipetkov/crane";
# Must stay in sync with the lbc-* tags in Cargo.toml.
# Pinned to the commit right after the v0.5.3 tag (127626881faa975aa8e9868422cf6bbb08fcb512):
# the tag itself predates the CI job that registers its Nix hashes in
# circuits-nix-hashes.json, so building directly off the tag fails with
# `attribute '"0.5.3"' missing`. This commit adds that entry without
# otherwise changing the source.
logos-blockchain-circuits = {
url = "github:logos-blockchain/logos-blockchain-circuits/2846ee7a4cfa24458bb8063412ab2e753b344d2f";
url = "github:logos-blockchain/logos-blockchain-circuits/v0.5.5";
};
# Must stay in sync with the rust-rapidsnark rev in Cargo.toml.
@@ -5,7 +5,7 @@ use lb_blend_message::{
crypto::proofs::PoQVerificationInputsMinusSigningKey,
encap::ProofsVerifier as ProofsVerifierTrait, reward::EpochRandomness,
};
use lb_blend_proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs};
use lb_blend_proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs, PowInputs};
use lb_core::{
crypto::ZkHash,
mantle::Value,
@@ -218,6 +218,7 @@ impl CurrentEpochTracker {
quota: core_quota,
},
leader: leader_input,
pow: PowInputs::unwired_placeholder(),
})
}
}
+2 -1
View File
@@ -9,7 +9,7 @@ use lb_blend_proofs::{
inputs::prove::{
PrivateInputs, PublicInputs,
private::ProofOfCoreQuotaInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
},
},
selection::VerifiedProofOfSelection,
@@ -83,6 +83,7 @@ pub fn generate_activity_proof(
signing_key: ephemeral.public_key().into_inner(),
core: core_inputs,
leader: leader_inputs,
pow: PowInputs::unwired_placeholder(),
};
for message_release_index in 0u64.. {
let private_inputs = PrivateInputs::new_proof_of_core_quota_inputs(
+3 -1
View File
@@ -30,7 +30,7 @@ use lb_blend::{
OldEpochBlendingTokenCollector,
},
},
proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs},
proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs, PowInputs},
scheduling::{
EpochMessageScheduler,
epoch::{EpochEvent, UninitializedEpochEventStream},
@@ -604,6 +604,7 @@ where
PoQVerificationInputsMinusSigningKey {
core: current_epoch_public_info.poq_core_public_inputs,
leader: current_epoch_public_info.poq_leadership_public_inputs,
pow: PowInputs::unwired_placeholder(),
},
current_epoch_core_poq_generator
.expect("Core PoQ generator must be present at startup: the proxy service only launches CoreMode when the node is part of the core membership."),
@@ -1050,6 +1051,7 @@ where
PoQVerificationInputsMinusSigningKey {
core: new_epoch_info.poq_core_public_inputs,
leader: new_epoch_info.poq_leadership_public_inputs,
pow: PowInputs::unwired_placeholder(),
},
core_poq_generator,
new_epoch_info.epoch,
+2 -1
View File
@@ -271,7 +271,7 @@ mod tests {
proofs::{
quota::{
VerifiedProofOfQuota,
inputs::prove::public::{CoreInputs, LeaderInputs},
inputs::prove::public::{CoreInputs, LeaderInputs, PowInputs},
},
selection::{self, VerifiedProofOfSelection},
},
@@ -305,6 +305,7 @@ mod tests {
lottery_0: Fr::ZERO,
lottery_1: Fr::ZERO,
},
pow: PowInputs::unwired_placeholder(),
}
}
+2 -1
View File
@@ -20,7 +20,7 @@ use lb_blend::{
ProofOfQuota, VerifiedProofOfQuota,
inputs::prove::{
private::ProofOfLeadershipQuotaInputs,
public::{CoreInputs, LeaderInputs},
public::{CoreInputs, LeaderInputs, PowInputs},
},
},
selection::{ProofOfSelection, VerifiedProofOfSelection, inputs::VerifyInputs},
@@ -330,6 +330,7 @@ pub fn new_crypto_processor<CorePoQGenerator>(
PoQVerificationInputsMinusSigningKey {
core: epoch_info.poq_core_public_inputs,
leader: epoch_info.poq_leadership_public_inputs,
pow: PowInputs::unwired_placeholder(),
},
core_poq_generator,
epoch_info.epoch,
+2 -1
View File
@@ -16,7 +16,7 @@ use backends::BlendBackend;
use futures::{Stream, StreamExt as _};
use lb_blend::{
message::crypto::proofs::PoQVerificationInputsMinusSigningKey,
proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs},
proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs, PowInputs},
scheduling::{
epoch::{EpochEvent, UninitializedEpochEventStream},
message_blend::provers::leader::LeaderProofsGenerator,
@@ -425,6 +425,7 @@ where
pol_ledger_aged: current_public_epoch_info.aged,
message_quota: settings.epoch_leadership_quota(),
},
pow: PowInputs::unwired_placeholder(),
};
debug!(target: LOG_TARGET, "Creating new handler for epoch {:?}", current_public_epoch_info.epoch);
+22 -8
View File
@@ -8,12 +8,12 @@ use std::str::FromStr as _;
use lb_pol::LotteryConstants;
use lb_utils::math::NonNegativeRatio;
use logos_blockchain_poq::{
PoQBlendInputsData, PoQChainInputsData, PoQCommonInputsData, PoQWalletInputsData,
PoQBlendInputsData, PoQChainInputsData, PoQCommonInputsData, PoQSelector, PoQWalletInputsData,
PoQWitnessInputs,
};
use num_bigint::BigUint;
/// Quota advertised by the fixtures, for both the core and the leader branch.
/// Quota advertised by the fixtures, for the core, the leader and pow branches.
///
/// It has to stay above every key index the benchmarks prove for, since the
/// circuit only accepts indices below the quota of the branch being proven.
@@ -24,16 +24,22 @@ fn lottery() -> (lb_groth16::Fr, lb_groth16::Fr) {
.compute_lottery_values(5000)
}
fn common_data(selector: bool, key_index: u64) -> PoQCommonInputsData {
fn common_data(selector: PoQSelector, key_index: u64) -> PoQCommonInputsData {
PoQCommonInputsData {
core_quota: QUOTA,
leader_quota: QUOTA,
pow_quota: QUOTA,
message_key: (
BigUint::from(123_456u32).into(),
BigUint::from(654_321u32).into(),
),
selector,
index: key_index,
pow_difficulty: BigUint::from_str(
"2334035772366381927456001473031809359552034265892309520954243351196711606017",
)
.unwrap()
.into(),
}
}
@@ -157,8 +163,12 @@ pub fn core_node_inputs(key_index: u64) -> PoQWitnessInputs {
.map(|(v, s)| (BigUint::from_str(v).unwrap().into(), s)),
};
PoQWitnessInputs::from_core_node_data(chain_data, common_data(false, key_index), blend_data)
.unwrap()
PoQWitnessInputs::from_core_node_data(
chain_data,
common_data(PoQSelector::Core, key_index),
blend_data,
)
.unwrap()
}
/// Witness inputs for a leadership Proof of Quota at the given key index.
@@ -327,13 +337,17 @@ pub fn leader_inputs(key_index: u64) -> PoQWitnessInputs {
),
]
.map(|(v, s)| (BigUint::from_str(v).unwrap().into(), s)),
secret_key: BigUint::from_str(
pol_secret_key: BigUint::from_str(
"17558656186380761716563192806052137702710836103853413805442046164934618201080",
)
.unwrap()
.into(),
};
PoQWitnessInputs::from_leader_data(chain_data, common_data(true, key_index), wallet_data)
.unwrap()
PoQWitnessInputs::from_leader_data(
chain_data,
common_data(PoQSelector::Leader, key_index),
wallet_data,
)
.unwrap()
}
+2
View File
@@ -77,6 +77,8 @@ pub enum PoQInputsFromDataError {
CoreQuotaMoreThan20Bits,
#[error("Leader quota is greater than 20 bits")]
LeaderQuotaMoreThan20Bits,
#[error("PoW quota is greater than 20 bits")]
PowQuotaMoreThan20Bits,
}
impl TryFrom<PoQChainInputsData> for PoQChainInputs {
+36 -6
View File
@@ -1,41 +1,59 @@
use lb_groth16::{AdditiveGroup as _, Field as _, Fr, Groth16Input, Groth16InputDeser};
use lb_groth16::{Fr, Groth16Input, Groth16InputDeser};
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use crate::chain_inputs::{
PoQInputsFromDataError,
PoQInputsFromDataError::{CoreQuotaMoreThan20Bits, LeaderQuotaMoreThan20Bits},
use crate::{
PoQInputsFromDataError::PowQuotaMoreThan20Bits,
chain_inputs::{
PoQInputsFromDataError,
PoQInputsFromDataError::{CoreQuotaMoreThan20Bits, LeaderQuotaMoreThan20Bits},
},
};
#[derive(Copy, Clone)]
pub struct PoQCommonInputs {
pub core_quota: Groth16Input,
pub leader_quota: Groth16Input,
pub pow_quota: Groth16Input,
pub key_part_one: Groth16Input,
pub key_part_two: Groth16Input,
pub selector: Groth16Input,
pub index: Groth16Input,
pub pow_difficulty: Groth16Input,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum PoQSelector {
Core = 0,
Leader = 1,
Pow = 2,
}
#[derive(Clone, Copy)]
pub struct PoQCommonInputsData {
pub core_quota: u64,
pub leader_quota: u64,
pub pow_quota: u64,
pub message_key: (Fr, Fr),
pub selector: bool,
pub selector: PoQSelector,
pub index: u64,
pub pow_difficulty: Fr,
}
#[derive(Deserialize, Serialize)]
pub struct PoQCommonInputsJson {
core_quota: Groth16InputDeser,
leader_quota: Groth16InputDeser,
pow_quota: Groth16InputDeser,
#[serde(rename = "K_part_one")]
key_part_one: Groth16InputDeser,
#[serde(rename = "K_part_two")]
key_part_two: Groth16InputDeser,
selector: Groth16InputDeser,
index: Groth16InputDeser,
#[serde(rename = "pow_blend_difficulty")]
pow_difficulty: Groth16InputDeser,
}
impl From<&PoQCommonInputs> for PoQCommonInputsJson {
@@ -43,19 +61,23 @@ impl From<&PoQCommonInputs> for PoQCommonInputsJson {
PoQCommonInputs {
core_quota,
leader_quota,
pow_quota,
key_part_one,
key_part_two,
selector,
index,
pow_difficulty,
}: &PoQCommonInputs,
) -> Self {
Self {
core_quota: core_quota.into(),
leader_quota: leader_quota.into(),
pow_quota: pow_quota.into(),
key_part_one: key_part_one.into(),
key_part_two: key_part_two.into(),
selector: selector.into(),
index: index.into(),
pow_difficulty: pow_difficulty.into(),
}
}
}
@@ -66,9 +88,11 @@ impl TryFrom<PoQCommonInputsData> for PoQCommonInputs {
PoQCommonInputsData {
core_quota,
leader_quota,
pow_quota,
message_key,
selector,
index,
pow_difficulty,
}: PoQCommonInputsData,
) -> Result<Self, Self::Error> {
let leader_quota_bits = leader_quota.checked_ilog2().map_or(0, |v| v + 1);
@@ -79,13 +103,19 @@ impl TryFrom<PoQCommonInputsData> for PoQCommonInputs {
if core_quota_bits > 20 {
return Err(CoreQuotaMoreThan20Bits);
}
let pow_quota_bits = pow_quota.checked_ilog2().map_or(0, |v| v + 1);
if pow_quota_bits > 20 {
return Err(PowQuotaMoreThan20Bits);
}
Ok(Self {
core_quota: Groth16Input::new(Fr::from(BigUint::from(core_quota))),
leader_quota: Groth16Input::new(Fr::from(BigUint::from(leader_quota))),
pow_quota: Groth16Input::new(Fr::from(BigUint::from(pow_quota))),
key_part_one: message_key.0.into(),
key_part_two: message_key.1.into(),
selector: Groth16Input::new(if selector { Fr::ONE } else { Fr::ZERO }),
selector: Groth16Input::new(Fr::from(BigUint::from(selector as u8))),
index: Groth16Input::new(Fr::from(BigUint::from(index))),
pow_difficulty: pow_difficulty.into(),
})
}
}
+62 -14
View File
@@ -6,6 +6,7 @@ use crate::{
blend_inputs::{PoQBlendInputs, PoQBlendInputsData, PoQBlendInputsJson},
chain_inputs::{PoQChainInputs, PoQChainInputsJson},
common_inputs::{PoQCommonInputs, PoQCommonInputsJson},
pow_inputs::{PoQPowInputs, PoQPowInputsData, PoQPowInputsJson},
wallet_inputs::{PoQWalletInputs, PoQWalletInputsData, PoQWalletInputsJson},
};
@@ -16,6 +17,7 @@ pub struct PoQWitnessInputs {
pub common: PoQCommonInputs,
pub blend: PoQBlendInputs,
pub wallet: PoQWalletInputs,
pub pow: PoQPowInputs,
}
impl PoQWitnessInputs {
@@ -27,11 +29,9 @@ impl PoQWitnessInputs {
Ok(Self {
chain: chain.try_into()?,
common: common.try_into()?,
blend: PoQBlendInputs::from(PoQBlendInputsData {
core_sk: Fr::ZERO,
core_path_and_selectors: [(Fr::ZERO, false); _],
}),
blend: Self::unused_blend_inputs(),
wallet: wallet.into(),
pow: Self::unused_pow_inputs(),
})
}
@@ -44,14 +44,47 @@ impl PoQWitnessInputs {
chain: chain.try_into()?,
common: common.try_into()?,
blend: blend.into(),
wallet: PoQWalletInputs::from(PoQWalletInputsData {
slot: 0,
note_value: 0,
transaction_hash: Fr::ZERO,
output_number: 0,
aged_path_and_selectors: [(Fr::ZERO, false); _],
secret_key: Fr::ZERO,
}),
wallet: Self::unused_wallet_inputs(),
pow: Self::unused_pow_inputs(),
})
}
pub fn from_pow_data(
chain: PoQChainInputsData,
common: PoQCommonInputsData,
pow: PoQPowInputsData,
) -> Result<Self, <PoQChainInputs as TryFrom<PoQChainInputsData>>::Error> {
Ok(Self {
chain: chain.try_into()?,
common: common.try_into()?,
blend: Self::unused_blend_inputs(),
wallet: Self::unused_wallet_inputs(),
pow: pow.into(),
})
}
fn unused_blend_inputs() -> PoQBlendInputs {
PoQBlendInputs::from(PoQBlendInputsData {
core_sk: Fr::ZERO,
core_path_and_selectors: [(Fr::ZERO, false); _],
})
}
fn unused_wallet_inputs() -> PoQWalletInputs {
PoQWalletInputs::from(PoQWalletInputsData {
slot: 0,
note_value: 0,
transaction_hash: Fr::ZERO,
output_number: 0,
aged_path_and_selectors: [(Fr::ZERO, false); _],
pol_secret_key: Fr::ZERO,
})
}
fn unused_pow_inputs() -> PoQPowInputs {
PoQPowInputs::from(PoQPowInputsData {
pow_secret_key: Fr::ZERO,
block_hash: Fr::ZERO,
})
}
}
@@ -77,6 +110,8 @@ pub struct PoQInputsJson {
pub blend: PoQBlendInputsJson,
#[serde(flatten)]
pub wallet: PoQWalletInputsJson,
#[serde(flatten)]
pub pow: PoQPowInputsJson,
}
impl From<PoQWitnessInputs> for PoQInputsJson {
@@ -86,12 +121,13 @@ impl From<PoQWitnessInputs> for PoQInputsJson {
chain: (&inputs.chain).into(),
common: (&inputs.common).into(),
blend: inputs.blend.into(),
pow: inputs.pow.into(),
}
}
}
#[derive(Deserialize, Serialize)]
pub struct PoQVerifierInputJson([Groth16InputDeser; 10]);
pub struct PoQVerifierInputJson([Groth16InputDeser; 12]);
#[derive(Clone)]
pub struct PoQVerifierInput {
@@ -99,8 +135,10 @@ pub struct PoQVerifierInput {
pub core_quota: Groth16Input,
pub leader_quota: Groth16Input,
pub core_root: Groth16Input,
pub pow_quota: Groth16Input,
pub k_part_one: Groth16Input,
pub k_part_two: Groth16Input,
pub pow_blend_difficulty: Groth16Input,
pub pol_epoch_nonce: Groth16Input,
pub pol_t0: Groth16Input,
pub pol_t1: Groth16Input,
@@ -112,8 +150,10 @@ pub struct PoQVerifierInputData {
pub core_quota: u64,
pub leader_quota: u64,
pub core_root: Fr,
pub pow_quota: u64,
pub k_part_one: Fr,
pub k_part_two: Fr,
pub pow_blend_difficulty: Fr,
pub pol_epoch_nonce: Fr,
pub lottery_0: Fr,
pub lottery_1: Fr,
@@ -129,9 +169,11 @@ impl TryFrom<PoQVerifierInputJson> for PoQVerifierInput {
core_quota,
leader_quota,
core_root,
pow_quota,
pol_ledger_aged,
k_part_one,
k_part_two,
pow_blend_difficulty,
pol_epoch_nonce,
pol_t0,
pol_t1,
@@ -141,8 +183,10 @@ impl TryFrom<PoQVerifierInputJson> for PoQVerifierInput {
core_quota: core_quota.try_into()?,
leader_quota: leader_quota.try_into()?,
core_root: core_root.try_into()?,
pow_quota: pow_quota.try_into()?,
k_part_one: k_part_one.try_into()?,
k_part_two: k_part_two.try_into()?,
pow_blend_difficulty: pow_blend_difficulty.try_into()?,
pol_epoch_nonce: pol_epoch_nonce.try_into()?,
pol_t0: pol_t0.try_into()?,
pol_t1: pol_t1.try_into()?,
@@ -153,15 +197,17 @@ impl TryFrom<PoQVerifierInputJson> for PoQVerifierInput {
impl PoQVerifierInput {
#[must_use]
pub const fn to_inputs(self) -> [Fr; 10] {
pub const fn to_inputs(self) -> [Fr; 12] {
[
self.key_nullifier.into_inner(),
self.core_quota.into_inner(),
self.leader_quota.into_inner(),
self.core_root.into_inner(),
self.pow_quota.into_inner(),
self.pol_ledger_aged.into_inner(),
self.k_part_one.into_inner(),
self.k_part_two.into_inner(),
self.pow_blend_difficulty.into_inner(),
self.pol_epoch_nonce.into_inner(),
self.pol_t0.into_inner(),
self.pol_t1.into_inner(),
@@ -178,6 +224,8 @@ impl From<PoQVerifierInputData> for PoQVerifierInput {
k_part_two: value.k_part_two.into(),
key_nullifier: value.key_nullifier.into(),
leader_quota: Groth16Input::new(value.leader_quota.into()),
pow_quota: Groth16Input::new(value.pow_quota.into()),
pow_blend_difficulty: value.pow_blend_difficulty.into(),
pol_epoch_nonce: value.pol_epoch_nonce.into(),
pol_ledger_aged: value.pol_ledger_aged.into(),
pol_t0: Groth16Input::new(value.lottery_0),
+166 -59
View File
@@ -2,6 +2,7 @@ mod blend_inputs;
mod chain_inputs;
mod common_inputs;
mod inputs;
mod pow_inputs;
mod verification_key;
mod wallet_inputs;
mod witness;
@@ -12,7 +13,7 @@ pub use blend_inputs::{
CORE_MERKLE_TREE_HEIGHT, CorePathAndSelectors, PoQBlendInputs, PoQBlendInputsData,
};
pub use chain_inputs::{PoQChainInputs, PoQChainInputsData, PoQInputsFromDataError};
pub use common_inputs::{PoQCommonInputs, PoQCommonInputsData};
pub use common_inputs::{PoQCommonInputs, PoQCommonInputsData, PoQSelector};
pub use inputs::{PoQVerifierInput, PoQVerifierInputData, PoQWitnessInputs};
use lb_circuits_prover::Prover as _;
use lb_groth16::{
@@ -20,6 +21,7 @@ use lb_groth16::{
};
use lb_log_targets::proofs;
pub use lb_pol::AGED_NOTE_MERKLE_TREE_HEIGHT;
pub use pow_inputs::{PoQPowInputs, PoQPowInputsData};
use tracing::error;
pub use wallet_inputs::{AgedNotePathAndSelectors, PoQWalletInputs, PoQWalletInputsData};
@@ -141,125 +143,136 @@ mod tests {
fn test_core_node_full_flow() {
let blend_data = PoQBlendInputsData {
core_sk: BigUint::from_str(
"8905547699320869461831852104817789469875723637735901345741281588946310324235",
"12667207762918681737213577433372645554941956657477853383508894204444641435708",
)
.unwrap()
.into(),
core_path_and_selectors: [
(
"16869616652852667189559016717243338331068120739649173884502336155264218010727",
"7495929195953210541208591724669352166258167386402673863836582332959049741998",
true,
),
(
"8777307683015870709408351814333112152804794621246499110711643849444602886454",
false,
),
(
"13091718879067163858034522323644176312242009811093736972498389939059085177974",
true,
),
(
"5615497624396623086866765843762273938268745340944523291991473843820399587958",
true,
),
(
"12252618726807031528274527895794936410875679656522675436955173925070564886479",
true,
),
(
"15676171816835539786276219242768254181009067816239948700832551985003974594975",
"2036778247346771710692375429289939913839125337381038976370669720942761418159",
false,
),
(
"19704272158800036501582000538191584664181486372889137807570676877252162790252",
"5150238042344629386129384874056943525919076545561275756461664527118718260316",
false,
),
(
"17220466132075642970726646606687934619880159579542773841528618335448239077575",
"4936482837140898160990313117063047116523911033722639295600657041261330950251",
true,
),
(
"8937835534203757508211957478554582398600683099364069427807070320868647534222",
false,
),
(
"19568444328548325046015987107341253380027495775774622463368229855377989226290",
true,
),
(
"21061581629256637244739982573784550742652688800467757972406229545047035347638",
"20952980496079374121937279088975994236259798048934859728621453910938508315429",
false,
),
(
"21518036264660447465371694976644098419156368727076924435365409888210990153413",
true,
),
(
"18601741813400480889163627600644403937577783284065467384061180740490941766846",
true,
),
(
"3276819021875171213616879397115905648698624783190949445325727117619871062684",
"3127567055231452038159138967497074106547716639376277019791213152208715502108",
false,
),
(
"16346413070923069365311460658994366491438423790174305844846934643885739701955",
"4156278452715681991545944691699148599903237834447534444828105997249209183668",
true,
),
(
"18179601246737692255359026481063507429449403349479564041630890140769203876921",
true,
),
(
"20437752904871274044354648385284243748143753378271484669957282746297202768728",
false,
),
(
"18052187374311288196723181314946958960293262462466262108812751192264820802527",
true,
),
(
"18798652887540222143344482340074977888006884307412611551550673334473344453748",
"20964128655175295722279612439890819641847965870342369257222152533521036200042",
false,
),
(
"6340439308577685187208962346420910386234054724707742363615331612477333766009",
"8529481264681700999783095712819666338972397694516091342589511693767349600946",
true,
),
(
"6116045944554536720728524915454748535792176096846806949818056548601205224613",
"11390366370077994090069924023908925478862142650386311632070176411828525668517",
true,
),
(
"7495766221993429009053819635630800227825524659472307268648873524676590491716",
"5917548759338686071972714539493044537194763096388397090814246211223906547270",
false,
),
(
"20167409213677086350276694709897905285253180362287004012973310966314090006733",
true,
),
(
"4135588845309195152336413092515000771731525447810984287718633658115964724154",
true,
"2263236988262202014904251664760262847563737954962247776121488194923407644186",
false,
),
(
"16515196782727402790941217652547735699069943062019512321107139568403168848690",
true,
"19725021492526941122134445491655907123723766494432771594886452496452660956448",
false,
),
(
"5142311427791207713153244085802613121123367252788982353300919738927631606801",
false,
),
(
"9162839441909107733910143825064550229009975662836406280871655871464192283628",
false,
),
]
.map(|(value, selector)| (BigUint::from_str(value).unwrap().into(), selector)),
};
let (lottery_0, lottery_1) =
LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap()))
.compute_lottery_values(5000);
let chain_data = PoQChainInputsData {
core_root: BigUint::from_str(
"12497635102173390657108276580981678137198622257613601634363274282193270703654",
"8648439177578503276872559887462337398415999326614652623958174178821393732006",
)
.unwrap()
.into(),
pol_ledger_aged: BigUint::from_str(
"3285301153701124106247898475175231377413013906491501039690975241845353257824",
"17215820411670358459256686882934231030146782155124511326026608377144127196093",
)
.unwrap()
.into(),
pol_epoch_nonce: BigUint::from_str(
"12441952563111122190593710132868692987264877387856913250622883462242318292882",
"16164570534004466859325445480705051126415612717114918921927075731584584730308",
)
.unwrap()
.into(),
lottery_0: BigUint::from_str(
"148409079361904587837471709956458430342187235603420891378597419711486680",
)
.unwrap()
.into(),
lottery_1: BigUint::from_str(
"21888242871336145414933615591437256729835795974444825699352339602896135814447",
)
.unwrap()
.into(),
lottery_0,
lottery_1,
};
let common_data = PoQCommonInputsData {
core_quota: 15,
leader_quota: 10,
core_quota: 10,
leader_quota: 15,
pow_quota: 20,
message_key: (
BigUint::from(123_456u32).into(),
BigUint::from(654_321u32).into(),
),
selector: false,
index: 2,
selector: PoQSelector::Core,
index: 9,
pow_difficulty: BigUint::from_str(
"2334035772366381927456001473031809359552034265892309520954243351196711606017",
)
.unwrap()
.into(),
};
let witness_inputs =
@@ -278,6 +291,8 @@ mod tests {
k_part_two: common_data.message_key.1,
key_nullifier,
leader_quota: common_data.leader_quota,
pow_quota: common_data.pow_quota,
pow_blend_difficulty: common_data.pow_difficulty,
pol_epoch_nonce: chain_data.pol_epoch_nonce,
pol_ledger_aged: chain_data.pol_ledger_aged,
lottery_0: chain_data.lottery_0,
@@ -314,12 +329,18 @@ mod tests {
let common_data = PoQCommonInputsData {
core_quota: 15,
leader_quota: 10,
pow_quota: 5,
message_key: (
BigUint::from(123_456u32).into(),
BigUint::from(654_321u32).into(),
),
selector: true,
selector: PoQSelector::Leader,
index: 2,
pow_difficulty: BigUint::from_str(
"2334035772366381927456001473031809359552034265892309520954243351196711606017",
)
.unwrap()
.into(),
};
let wallet_data = PoQWalletInputsData {
slot: 3_934_028_363,
@@ -461,7 +482,7 @@ mod tests {
),
]
.map(|(value, selector)| (BigUint::from_str(value).unwrap().into(), selector)),
secret_key: BigUint::from_str(
pol_secret_key: BigUint::from_str(
"14029017592631272654768426994822986470491530234220356957377918534745805708829",
)
.unwrap()
@@ -484,6 +505,92 @@ mod tests {
k_part_two: common_data.message_key.1,
key_nullifier,
leader_quota: common_data.leader_quota,
pow_quota: common_data.pow_quota,
pow_blend_difficulty: common_data.pow_difficulty,
pol_epoch_nonce: chain_data.pol_epoch_nonce,
pol_ledger_aged: chain_data.pol_ledger_aged,
lottery_0: chain_data.lottery_0,
lottery_1: chain_data.lottery_1,
};
assert!(verify(&proof, recomputed_verify_inputs.into()).unwrap());
}
#[test]
fn test_pow_full_flow() {
let chain_data = PoQChainInputsData {
core_root: BigUint::from_str(
"7196120816867742764244305920813414287385070139349175831448761050095304066684",
)
.unwrap()
.into(),
pol_ledger_aged: BigUint::from_str(
"12901234158445930888471913831760642108627105440164948073754288938400615804116",
)
.unwrap()
.into(),
pol_epoch_nonce: BigUint::from_str(
"10612486568558363932403470969435464905237449260307875593547304337083779862075",
)
.unwrap()
.into(),
lottery_0: BigUint::from_str(
"148409079361904587837471709956458430342187235603420891378597419711486680",
)
.unwrap()
.into(),
lottery_1: BigUint::from_str(
"21888242871336145414933615591437256729835795974444825699352339602896135814447",
)
.unwrap()
.into(),
};
let common_data = PoQCommonInputsData {
core_quota: 10,
leader_quota: 15,
pow_quota: 20,
message_key: (
BigUint::from(123_456u32).into(),
BigUint::from(654_321u32).into(),
),
selector: PoQSelector::Pow,
index: 8,
pow_difficulty: BigUint::from_str(
"10631870504716456348838861774188160492563879712126054449569633827216160699117",
)
.unwrap()
.into(),
};
let pow_data = PoQPowInputsData {
pow_secret_key: BigUint::from_str(
"10044758699144566038746293679996441958807939592793641056821682251877616662024",
)
.unwrap()
.into(),
block_hash: BigUint::from_str(
"17412116459874055221726429396167151400213699852365025340176158516975240665302",
)
.unwrap()
.into(),
};
let witness_inputs =
PoQWitnessInputs::from_pow_data(chain_data, common_data, pow_data).unwrap();
let (proof, inputs) = prove(witness_inputs).unwrap();
let key_nullifier = inputs.key_nullifier.into_inner();
// Test that verifying with the inputs returned by `prove` works.
assert!(verify(&proof, inputs).unwrap());
// Test that verifying with the reconstructed inputs inside the verifier context
// works.
let recomputed_verify_inputs = PoQVerifierInputData {
core_quota: common_data.core_quota,
core_root: chain_data.core_root,
k_part_one: common_data.message_key.0,
k_part_two: common_data.message_key.1,
key_nullifier,
leader_quota: common_data.leader_quota,
pow_quota: common_data.pow_quota,
pow_blend_difficulty: common_data.pow_difficulty,
pol_epoch_nonce: chain_data.pol_epoch_nonce,
pol_ledger_aged: chain_data.pol_ledger_aged,
lottery_0: chain_data.lottery_0,
+49
View File
@@ -0,0 +1,49 @@
use lb_groth16::{Fr, Groth16Input, Groth16InputDeser};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub struct PoQPowInputs {
pub pow_secret_key: Groth16Input,
pub block_hash: Groth16Input,
}
pub struct PoQPowInputsData {
pub pow_secret_key: Fr,
pub block_hash: Fr,
}
#[derive(Deserialize, Serialize)]
pub struct PoQPowInputsJson {
#[serde(rename = "pow_sk")]
pow_secret_key: Groth16InputDeser,
#[serde(rename = "pow_block_hash")]
block_hash: Groth16InputDeser,
}
impl From<PoQPowInputs> for PoQPowInputsJson {
fn from(
PoQPowInputs {
pow_secret_key,
block_hash,
}: PoQPowInputs,
) -> Self {
Self {
pow_secret_key: (&pow_secret_key).into(),
block_hash: (&block_hash).into(),
}
}
}
impl From<PoQPowInputsData> for PoQPowInputs {
fn from(
PoQPowInputsData {
pow_secret_key,
block_hash,
}: PoQPowInputsData,
) -> Self {
Self {
pow_secret_key: pow_secret_key.into(),
block_hash: block_hash.into(),
}
}
}
+5 -5
View File
@@ -21,7 +21,7 @@ pub struct PoQWalletInputsData {
pub transaction_hash: Fr,
pub output_number: u64,
pub aged_path_and_selectors: AgedNotePathAndSelectors,
pub secret_key: Fr,
pub pol_secret_key: Fr,
}
#[derive(Serialize)]
@@ -39,7 +39,7 @@ pub struct PoQWalletInputsJson {
#[serde(rename = "pol_noteid_path_selectors")]
aged_selector: [Groth16InputDeser; AGED_NOTE_MERKLE_TREE_HEIGHT],
#[serde(rename = "pol_secret_key")]
secret_key: Groth16InputDeser,
pol_secret_key: Groth16InputDeser,
}
impl From<PoQWalletInputs> for PoQWalletInputsJson {
fn from(
@@ -66,7 +66,7 @@ impl From<PoQWalletInputs> for PoQWalletInputsJson {
output_number: (&pol_note_output_number).into(),
aged_path,
aged_selector,
secret_key: (&pol_secret_key).into(),
pol_secret_key: (&pol_secret_key).into(),
}
}
}
@@ -79,12 +79,12 @@ impl From<PoQWalletInputsData> for PoQWalletInputs {
transaction_hash,
output_number,
aged_path_and_selectors,
secret_key,
pol_secret_key,
}: PoQWalletInputsData,
) -> Self {
Self {
pol_slot: Groth16Input::new(Fr::from(BigUint::from(slot))),
pol_secret_key: Groth16Input::new(Fr::from(BigUint::from(secret_key))),
pol_secret_key: pol_secret_key.into(),
pol_noteid_path_and_selectors: aged_path_and_selectors.map(|(hash, selector)| {
(
hash.into(),