mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-14 03:59:30 +00:00
derive identifier from shared secret and update circuit
This commit is contained in:
parent
9d2abc76a1
commit
338900b50d
@ -9,7 +9,9 @@ pub use commitment::{
|
||||
compute_digest_for_path,
|
||||
};
|
||||
pub use encryption::{EncryptionScheme, SharedSecretKey};
|
||||
pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey};
|
||||
pub use nullifier::{
|
||||
Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey, derive_identifier,
|
||||
};
|
||||
|
||||
pub mod account;
|
||||
mod circuit_io;
|
||||
|
||||
@ -2,9 +2,11 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Commitment, account::AccountId};
|
||||
use crate::{Commitment, SharedSecretKey, account::AccountId};
|
||||
|
||||
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
|
||||
const IDENTIFIER_DOMAIN_SEPARATOR: &[u8; 32] =
|
||||
b"/LEE/v0.3/Identifier/\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
pub type Identifier = u128;
|
||||
|
||||
@ -99,6 +101,20 @@ impl Nullifier {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use a dedicated struct for Identifier and make this into a constructor of the type
|
||||
#[must_use]
|
||||
pub fn derive_identifier(shared_secret: &SharedSecretKey) -> Identifier {
|
||||
let mut bytes = [0_u8; 64];
|
||||
bytes[..32].copy_from_slice(IDENTIFIER_DOMAIN_SEPARATOR);
|
||||
bytes[32..].copy_from_slice(&shared_secret.0);
|
||||
let hash = Impl::hash_bytes(&bytes);
|
||||
Identifier::from_le_bytes(
|
||||
hash.as_bytes()[..16]
|
||||
.try_into()
|
||||
.expect("slice of length 16"),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -143,6 +159,27 @@ mod tests {
|
||||
assert_eq!(npk, expected_npk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_identifier_is_deterministic_and_domain_separated() {
|
||||
let shared_secret = SharedSecretKey([0x11; 32]);
|
||||
let other_secret = SharedSecretKey([0x12; 32]);
|
||||
|
||||
let a = derive_identifier(&shared_secret);
|
||||
let b = derive_identifier(&shared_secret);
|
||||
let c = derive_identifier(&other_secret);
|
||||
|
||||
assert_eq!(a, b, "same shared secret must derive the same identifier");
|
||||
assert_ne!(a, c, "different secrets must derive different identifiers");
|
||||
|
||||
// Cross-check: output equals first 16 LE bytes of sha256(domain || secret).
|
||||
let mut preimage = [0_u8; 64];
|
||||
preimage[..32].copy_from_slice(IDENTIFIER_DOMAIN_SEPARATOR);
|
||||
preimage[32..].copy_from_slice(&shared_secret.0);
|
||||
let expected =
|
||||
Identifier::from_le_bytes(Impl::hash_bytes(&preimage).as_bytes()[..16].try_into().unwrap());
|
||||
assert_eq!(a, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_id_from_nullifier_public_key() {
|
||||
let nsk = [
|
||||
|
||||
@ -8,7 +8,7 @@ use nssa_core::{
|
||||
MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
|
||||
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
compute_digest_for_path,
|
||||
compute_digest_for_path, derive_identifier,
|
||||
program::{
|
||||
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
|
||||
MAX_NUMBER_CHAINED_CALLS, ProgramId, ProgramOutput, TimestampValidityWindow,
|
||||
@ -343,11 +343,22 @@ fn compute_circuit_output(
|
||||
output.public_post_states.push(post_state);
|
||||
}
|
||||
1 | 2 => {
|
||||
let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else {
|
||||
let Some((npk, input_identifier, shared_secret)) = private_keys_iter.next() else {
|
||||
panic!("Missing private account key");
|
||||
};
|
||||
|
||||
let account_id = AccountId::from((npk, *identifier));
|
||||
let Some(membership_proof_opt) = private_membership_proofs_iter.next() else {
|
||||
panic!("Missing membership proof");
|
||||
};
|
||||
|
||||
// On init the identifier is fixed by the protocol using the shared secret as source of entropy. On update the witness is used
|
||||
let identifier = if membership_proof_opt.is_none() {
|
||||
derive_identifier(shared_secret)
|
||||
} else {
|
||||
*input_identifier
|
||||
};
|
||||
|
||||
let account_id = AccountId::from((npk, identifier));
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
|
||||
@ -371,10 +382,6 @@ fn compute_circuit_output(
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
|
||||
let Some(membership_proof_opt) = private_membership_proofs_iter.next() else {
|
||||
panic!("Missing membership proof");
|
||||
};
|
||||
|
||||
let new_nullifier = compute_nullifier_and_set_digest(
|
||||
membership_proof_opt.as_ref(),
|
||||
&pre_state.account,
|
||||
@ -386,7 +393,7 @@ fn compute_circuit_output(
|
||||
|
||||
(new_nullifier, new_nonce)
|
||||
} else {
|
||||
// Private account without authentication
|
||||
// Private account without authentication (always INIT).
|
||||
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
@ -399,10 +406,6 @@ fn compute_circuit_output(
|
||||
"Found new private account marked as authorized."
|
||||
);
|
||||
|
||||
let Some(membership_proof_opt) = private_membership_proofs_iter.next() else {
|
||||
panic!("Missing membership proof");
|
||||
};
|
||||
|
||||
assert!(
|
||||
membership_proof_opt.is_none(),
|
||||
"Membership proof must be None for unauthorized accounts"
|
||||
@ -426,7 +429,7 @@ fn compute_circuit_output(
|
||||
// Encrypt and push post state
|
||||
let encrypted_account = EncryptionScheme::encrypt(
|
||||
&post_with_updated_nonce,
|
||||
*identifier,
|
||||
identifier,
|
||||
shared_secret,
|
||||
&commitment_post,
|
||||
output_index,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user