Skip to content

Commit

Permalink
Merge pull request #37 from fjarri/stricter-iters
Browse files Browse the repository at this point in the history
Take an iterable of capsule and vcfrag in `ReencryptionResponse` constructor
  • Loading branch information
fjarri authored Oct 2, 2022
2 parents d44d506 + 188bd8f commit 6c1a471
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use a workaround with `wasm-bindgen-derive` to support `Option<&T>` and `Vec<&T>` arguments, and `Vec<T>` and tuple return values, with correct TypeScript annotations. Removed all the Builder pattern helper classes. (#[34])
- Use `Address` instead of plain bytes in arguments and return values (both in WASM and Python bindgins). Export the `Address` type. (#[34])
- `umbral-pre` dependency bumped to 0.7. (#[36])
- `ReencryptionResponse::new()` now takes an iterator of pairs `(Capsule, VerifiedCapsuleFrag)` instead of two separate iterators; bindings changed correspondingly. (#[37])
- Change `Iterable` to `Sequence` in Python binding type stubs: bindings cannot actually take just iterables. (#[37])
- `AuthorizedKeyFrag.verify()`, `ReencryptionResponse.verify()`, and `AuthorizedTreasureMap.verify()` now consume `self`. (#[37])


### Added
Expand All @@ -32,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#33]: https://github.com/nucypher/nucypher-core/pull/33
[#34]: https://github.com/nucypher/nucypher-core/pull/34
[#36]: https://github.com/nucypher/nucypher-core/pull/36
[#37]: https://github.com/nucypher/nucypher-core/pull/37


## [0.4.0-alpha.0] - 2022-09-07
Expand Down
16 changes: 8 additions & 8 deletions nucypher-core-python/nucypher_core/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Dict, Iterable, Optional, Mapping, Tuple, Set
from typing import List, Dict, Sequence, Optional, Mapping, Tuple, Set

from .umbral import SecretKey, PublicKey, Signer, Capsule, VerifiedKeyFrag, VerifiedCapsuleFrag

Expand Down Expand Up @@ -65,7 +65,7 @@ class MessageKit:
self,
sk: SecretKey,
policy_encrypting_key: PublicKey,
vcfrags: Iterable[VerifiedCapsuleFrag]
vcfrags: Sequence[VerifiedCapsuleFrag]
) -> bytes:
...

Expand Down Expand Up @@ -176,7 +176,7 @@ class ReencryptionRequest:

def __init__(
self,
capsules: Iterable[Capsule],
capsules: Sequence[Capsule],
hrac: HRAC,
encrypted_kfrag: EncryptedKeyFrag,
publisher_verifying_key: PublicKey,
Expand Down Expand Up @@ -210,12 +210,12 @@ class ReencryptionRequest:

class ReencryptionResponse:

def __init__(self, signer: Signer, capsules: Iterable[Capsule], vcfrags: Iterable[VerifiedCapsuleFrag]):
def __init__(self, signer: Signer, capsules_and_vcfrags: Sequence[Tuple[Capsule, VerifiedCapsuleFrag]]):
...

def verify(
self,
capsules: Iterable[Capsule],
capsules: Sequence[Capsule],
alice_verifying_key: PublicKey,
ursula_verifying_key: PublicKey,
policy_encrypting_key: PublicKey,
Expand Down Expand Up @@ -341,7 +341,7 @@ class NodeMetadata:

class FleetStateChecksum:

def __init__(self, this_node: Optional[NodeMetadata], other_nodes: Iterable[NodeMetadata]):
def __init__(self, this_node: Optional[NodeMetadata], other_nodes: Sequence[NodeMetadata]):
...


Expand All @@ -350,7 +350,7 @@ class MetadataRequest:
def __init__(
self,
fleet_state_checksum: FleetStateChecksum,
announce_nodes: Iterable[NodeMetadata],
announce_nodes: Sequence[NodeMetadata],
):
...

Expand All @@ -368,7 +368,7 @@ class MetadataRequest:

class MetadataResponsePayload:

def __init__(self, timestamp_epoch: int, announce_nodes: Iterable[NodeMetadata]):
def __init__(self, timestamp_epoch: int, announce_nodes: Sequence[NodeMetadata]):
...

timestamp_epoch: int
Expand Down
21 changes: 11 additions & 10 deletions nucypher-core-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,20 +573,20 @@ pub struct ReencryptionResponse {
#[pymethods]
impl ReencryptionResponse {
#[new]
pub fn new(signer: &Signer, capsules: Vec<Capsule>, vcfrags: Vec<VerifiedCapsuleFrag>) -> Self {
let capsules_backend = capsules
pub fn new(signer: &Signer, capsules_and_vcfrags: Vec<(Capsule, VerifiedCapsuleFrag)>) -> Self {
let (capsules_backend, vcfrags_backend): (Vec<_>, Vec<_>) = capsules_and_vcfrags
.into_iter()
.map(umbral_pre::Capsule::from)
.collect::<Vec<_>>();
let vcfrags_backend = vcfrags
.into_iter()
.map(umbral_pre::VerifiedCapsuleFrag::from)
.collect::<Vec<_>>();
.map(|(capsule, vcfrag)| {
(
umbral_pre::Capsule::from(capsule),
umbral_pre::VerifiedCapsuleFrag::from(vcfrag),
)
})
.unzip();
ReencryptionResponse {
backend: nucypher_core::ReencryptionResponse::new(
signer.as_ref(),
&capsules_backend,
vcfrags_backend,
capsules_backend.iter().zip(vcfrags_backend.into_iter()),
),
}
}
Expand All @@ -605,6 +605,7 @@ impl ReencryptionResponse {
.collect::<Vec<_>>();
let vcfrags_backend = self
.backend
.clone()
.verify(
&capsules_backend,
alice_verifying_key.as_ref(),
Expand Down
11 changes: 8 additions & 3 deletions nucypher-core-wasm/examples/node/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Context,
Capsule,
VerifiedKeyFrag,
VerifiedCapsuleFrag,
EncryptedKeyFrag,
generateKFrags,
HRAC,
Expand Down Expand Up @@ -325,20 +326,24 @@ describe("ReencryptionResponse", () => {
const policyEncryptingKey = aliceSk.publicKey();
const message = new Uint8Array(Buffer.from("Hello, world!"));
const messageKit = new MessageKit(policyEncryptingKey, message, null);
const capsules = vkfrags.map((_) => messageKit.capsule);

// Perform the reencryption
const cfrags = vkfrags.map((kfrag) => reencrypt(capsules[0], kfrag));
const vcfrags = vkfrags.map((vkfrag) => reencrypt(messageKit.capsule, vkfrag));

// Make the reencryption response
const ursulaSk = SecretKey.random();
const reencryptionResponse = new ReencryptionResponse(new Signer(ursulaSk), capsules, cfrags);
const capsules_and_vcfrags: [Capsule, VerifiedCapsuleFrag][] =
vcfrags.map((vcfrag) => [messageKit.capsule, vcfrag]);
const reencryptionResponse = new ReencryptionResponse(
new Signer(ursulaSk), capsules_and_vcfrags
);

// Test serialization
const asBytes = reencryptionResponse.toBytes();
expect(ReencryptionResponse.fromBytes(asBytes).toBytes()).toEqual(asBytes);

// Verify the reencryption response
const capsules = vkfrags.map((_) => messageKit.capsule);
const verified = reencryptionResponse.verify(
capsules,
aliceSk.publicKey(),
Expand Down
45 changes: 31 additions & 14 deletions nucypher-core-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ extern "C" {
#[wasm_bindgen(typescript_type = "VerifiedCapsuleFrag[]")]
pub type VerifiedCapsuleFragArray;

#[wasm_bindgen(typescript_type = "[Capsule, VerifiedCapsuleFrag][]")]
pub type CapsuleAndVerifiedCapsuleFragArray;

#[wasm_bindgen(typescript_type = "Conditions | null")]
pub type OptionConditions;

Expand Down Expand Up @@ -631,25 +634,38 @@ impl ReencryptionResponse {
#[wasm_bindgen(constructor)]
pub fn new(
signer: &Signer,
capsules: &CapsuleArray,
vcfrags: &VerifiedCapsuleFragArray,
capsules_and_vcfrags: &CapsuleAndVerifiedCapsuleFragArray,
) -> Result<ReencryptionResponse, Error> {
let typed_capsules = try_from_js_array::<Capsule>(capsules)?;
let typed_vcfrags = try_from_js_array::<VerifiedCapsuleFrag>(vcfrags)?;
let js_capsules_and_vcfrags: &JsValue = capsules_and_vcfrags.as_ref();
let capsules_and_vcfrags_array: &js_sys::Array = js_capsules_and_vcfrags
.dyn_ref()
.ok_or_else(|| Error::new("`capsules_and_vcfrags` must be an array"))?;

let backend_capsules = typed_capsules
.into_iter()
.map(umbral_pre::Capsule::from)
.collect::<Vec<_>>();
let backend_vcfrags = typed_vcfrags
.into_iter()
.map(umbral_pre::VerifiedCapsuleFrag::from)
.collect::<Vec<_>>();
let mut backend_capsules = Vec::new();
let mut backend_vcfrags = Vec::new();

for entry in capsules_and_vcfrags_array.iter() {
let entry_tuple: js_sys::Array = entry.dyn_into()?;
if entry_tuple.length() != 2 {
return Err(Error::new(
"A tuple of an incorrect size received when iterating through list's entries",
));
}

let capsule = umbral_pre::Capsule::from(
Capsule::try_from(&entry_tuple.get(0)).map_err(map_js_err)?,
);
let vcfrag = umbral_pre::VerifiedCapsuleFrag::from(
VerifiedCapsuleFrag::try_from(&entry_tuple.get(1)).map_err(map_js_err)?,
);

backend_capsules.push(capsule);
backend_vcfrags.push(vcfrag);
}

Ok(Self(nucypher_core::ReencryptionResponse::new(
signer.as_ref(),
&backend_capsules,
backend_vcfrags,
backend_capsules.iter().zip(backend_vcfrags.into_iter()),
)))
}

Expand Down Expand Up @@ -679,6 +695,7 @@ impl ReencryptionResponse {
.collect::<Vec<_>>();
let backend_vcfrags = self
.0
.clone()
.verify(
&backend_capsules,
alice_verifying_key.as_ref(),
Expand Down
17 changes: 12 additions & 5 deletions nucypher-core-wasm/tests/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ fn reencryption_response_verify() {
assert_eq!(capsules.len(), kfrags.len());

// Simulate the reencryption
let cfrags: Vec<VerifiedCapsuleFrag> = kfrags
let vcfrags: Vec<VerifiedCapsuleFrag> = kfrags
.iter()
.map(|kfrag| reencrypt(&capsules[0], &kfrag))
.collect();
Expand All @@ -446,14 +446,21 @@ fn reencryption_response_verify() {
let ursula_sk = SecretKey::random();
let signer = Signer::new(&ursula_sk);

let capsules_js = into_js_array(capsules);
let cfrags_js = into_js_array(cfrags.iter().cloned());
let capsules_and_vcfrags_js =
into_js_array(capsules.iter().cloned().zip(vcfrags.iter().cloned()).map(
|(capsule, vcfrag)| {
[JsValue::from(capsule), JsValue::from(vcfrag)]
.into_iter()
.collect::<js_sys::Array>()
},
));
let reencryption_response =
ReencryptionResponse::new(&signer, &capsules_js, &cfrags_js).unwrap();
ReencryptionResponse::new(&signer, &capsules_and_vcfrags_js).unwrap();

// Now that the response is created, we're going to "send it" to the client and verify it

// Verify reencryption response
let capsules_js = into_js_array(capsules);
let verified_array = reencryption_response
.verify(
&capsules_js,
Expand All @@ -465,7 +472,7 @@ fn reencryption_response_verify() {
.unwrap();

let verified = try_from_js_array::<VerifiedCapsuleFrag>(verified_array);
assert_eq!(cfrags, verified, "Capsule fragments do not match");
assert_eq!(vcfrags, verified, "Capsule fragments do not match");

let as_bytes = reencryption_response.to_bytes();
assert_eq!(
Expand Down
19 changes: 12 additions & 7 deletions nucypher-core/src/key_frag.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;

use serde::{Deserialize, Serialize};
Expand All @@ -21,6 +22,10 @@ struct AuthorizedKeyFrag {
kfrag: KeyFrag,
}

fn signed_message(hrac: &HRAC, kfrag: &KeyFrag) -> Vec<u8> {
[hrac.as_ref(), kfrag.to_array().as_ref()].concat()
}

impl AuthorizedKeyFrag {
fn new(signer: &Signer, hrac: &HRAC, verified_kfrag: VerifiedKeyFrag) -> Self {
// Alice makes plain to Ursula that, upon decrypting this message,
Expand All @@ -29,22 +34,22 @@ impl AuthorizedKeyFrag {
// TODO (rust-umbral#73): add VerifiedKeyFrag::unverify()?
let kfrag = verified_kfrag.unverify();

let signature = signer.sign(&[hrac.as_ref(), kfrag.to_array().as_ref()].concat());
let signature = signer.sign(&signed_message(hrac, &kfrag));

Self { signature, kfrag }
}

fn verify(&self, hrac: &HRAC, publisher_verifying_key: &PublicKey) -> Option<VerifiedKeyFrag> {
if !self.signature.verify(
publisher_verifying_key,
&[hrac.as_ref(), self.kfrag.to_array().as_ref()].concat(),
) {
fn verify(self, hrac: &HRAC, publisher_verifying_key: &PublicKey) -> Option<VerifiedKeyFrag> {
if !self
.signature
.verify(publisher_verifying_key, &signed_message(hrac, &self.kfrag))
{
return None;
}

// Ursula has no side channel to get the KeyFrag author's key,
// so verifying the keyfrag is useless.
Some(self.kfrag.clone().skip_verification())
Some(self.kfrag.skip_verification())
}
}

Expand Down
15 changes: 8 additions & 7 deletions nucypher-core/src/reencryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,19 @@ fn signed_message(capsules: &[Capsule], cfrags: &[CapsuleFrag]) -> Vec<u8> {

impl ReencryptionResponse {
/// Creates and signs a new reencryption response.
pub fn new(
pub fn new<'a>(
signer: &Signer,
capsules: &[Capsule],
vcfrags: impl IntoIterator<Item = VerifiedCapsuleFrag>,
capsules_and_vcfrags: impl IntoIterator<Item = (&'a Capsule, VerifiedCapsuleFrag)>,
) -> Self {
let (capsules, vcfrags): (Vec<_>, Vec<_>) = capsules_and_vcfrags.into_iter().unzip();

// un-verify
let cfrags: Vec<_> = vcfrags
.into_iter()
.map(|vcfrag| vcfrag.unverify())
.collect();

let signature = signer.sign(&signed_message(capsules, &cfrags));
let signature = signer.sign(&signed_message(&capsules, &cfrags));

ReencryptionResponse {
cfrags: cfrags.into_boxed_slice(),
Expand All @@ -147,7 +148,7 @@ impl ReencryptionResponse {

/// Verifies the reencryption response and returns the contained kfrags on success.
pub fn verify(
&self,
self,
capsules: &[Capsule],
alice_verifying_key: &PublicKey,
ursula_verifying_key: &PublicKey,
Expand All @@ -169,8 +170,8 @@ impl ReencryptionResponse {

let vcfrags = self
.cfrags
.iter()
.cloned()
.into_vec()
.into_iter()
.zip(capsules.iter())
.map(|(cfrag, capsule)| {
cfrag.verify(
Expand Down
4 changes: 2 additions & 2 deletions nucypher-core/src/treasure_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl AuthorizedTreasureMap {
}

fn verify(
&self,
self,
recipient_key: &PublicKey,
publisher_verifying_key: &PublicKey,
) -> Option<TreasureMap> {
Expand All @@ -151,7 +151,7 @@ impl AuthorizedTreasureMap {
if !self.signature.verify(publisher_verifying_key, &message) {
return None;
}
Some(self.treasure_map.clone())
Some(self.treasure_map)
}
}

Expand Down

0 comments on commit 6c1a471

Please sign in to comment.