diff --git a/.gitignore b/.gitignore index c41cc9e..1de5659 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target \ No newline at end of file +target \ No newline at end of file diff --git a/README.md b/README.md index afbb501..faf94cc 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,12 @@ The library contains the following components: - `src`: Rust library for multi-party PSI using BFV -- `pkg`: JS-TS-WASM package +- `pkg`: JS-TS-WASM package -### Build +### Build The rust library is used to build the JS-TS-WASM package using `wasm-pack` targeting `web` [guide](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm). When compiling to `web` the output can natively be included on a web page, and doesn't require any further postprocessing. The output is included as an ES module. For more information check [`wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/reference/deployment.html) - ```bash wasm-pack build --target web ``` @@ -20,4 +19,4 @@ To test the rust library, run: ```bash cargo test --release -``` \ No newline at end of file +``` diff --git a/pkg/LICENSE b/pkg/LICENSE deleted file mode 100644 index ff7657e..0000000 --- a/pkg/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Gaussian - -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. \ No newline at end of file diff --git a/pkg/README.md b/pkg/README.md deleted file mode 100644 index 5557f28..0000000 --- a/pkg/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# MP-PSI - -The package contains the APIs to build a multi party private set intersection (MP-PSI) web-app. Note that the APIs are compiled specifically for web browser environments and are not compatible with a node environment. - -PSI stands for Private Set Intersection. It allows two parties to compute the intersection of their sets without revealing anything else about their sets. - -[BFV](https://inferati.azureedge.net/docs/inferati-fhe-bfv.pdf) is a fully homomorphic encryption scheme. It allows to perform addition and multiplication on encrypted data. The PSI protocol made available in this library uses the BFV scheme to perform the encryption of the sets, compute the intersection and decrypt the result. In particular, the multiparty protocol is based on the paper [Mouchet, Christian, et al. "Multiparty homomorphic encryption from ring-learning-with-errors."](https://eprint.iacr.org/2020/304.pdf). - -## Security Notice - -This is a research project and is not meant to be used in production. The code has not been audited. - -## Usage in web-app - -```js - import init, { state0_bindgen, state1_bindgen, state2_bindgen, state3_bindgen, state4_bindgen } from "./mp_psi.js"; - - init().then(() => { - const state0 = state0_bindgen(); - const bit_vector_b = [1, 0, 1, 0, 1, 0, 1, 0, 1, 1]; - const state1 = state1_bindgen(state0.message_a_to_b, bit_vector_b); - const bit_vector_a = [1, 1, 1, 1, 1, 0, 1, 0, 0, 0]; - const state2 = state2_bindgen(state0.private_output_a, state0.public_output_a, state1.message_b_to_a, bit_vector_a); - const state3 = state3_bindgen(state1.private_output_b, state1.public_output_b, state2.message_a_to_b); - const psi_output_a = state4_bindgen(state2.public_output_a, state3.message_b_to_a); - const psi_output_b = state3.psi_output; - }); -``` - -The `mp_psi.js` can natively be included on a web page as an ES module. An example of the usage is include in the `index.html` file. - -You can test it by: -- Cloning the repo -- Serving the `pkg` directory with a local web server, (e.g. `python3 -m http.server`) -- Visit `http://localhost:8000` in your browser -- Open the console. It will show you the result of the PSI protocol. - -### `state0_bindgen()` - -Called by party A. Generates the public key share of A starting from a newly generated secret key. Generates the round 1 relinearization key share of A starting from a newly generated ephermeral key. - -Returns: -- `message_a_to_b`: message to be sent from A to B after round 0. Contains the public key share `share_pk_a` and the round 1 relinearization key share `share_rlk_a_round1`. -- `private_output_a`: private output of A after round 0. It has to be privately stored by A. Contains the secret key `s_pk_a` behind the public key share and the ephemeral key `s_rlk_a` behind the round 1 relinearization key share. -- `public_output_a`: public output of A after round 0. It has to be stored by A (not necessarily privately). Contains the public key share `share_pk_a` and the round 1 relinearization key share `share_rlk_a_round1`. - -```js - const state0 = state0_bindgen(); - const {message_a_to_b, private_output_a, public_output_a} = state0; -``` - -### `state1_bindgen(message_a_to_b, bit_vector_b)` - -Called by party B after receiving `message_a_to_b` from A. Takes as input `message_a_to_b` and the plaintext bit vector of B. This is the bit vector that B wants to find the intersection with the bit vector of A. Generates the public key share of B starting from a newly generated secret key. Generates the round 1 relinearization key share of B starting from a newly generated ephermeral key. Aggregates the received round 1 relinearization key share of A, and their own round 1 relinearization key share to generate the round 2 relinearization key share of B. Aggregates the received public key share of A and their own generated public key share to generate the collective public key used for encryption. Encrypts the bit vector of B using the collective public key. - -Returns: -- `message_b_to_a`: message to be sent from B to A after round 1. Contains the public key share `share_pk_b`, the round 1 relinearization key share `share_rlk_b_round1`, the round 2 relinearization key share `share_rlk_b_round2` and the encrypted bit vector `ciphertexts_b`. -- `private_output_b`: private output of B after round 1. It has to be privately stored by B. Contains the secret key `s_pk_b` behind the public key share. -- `public_output_b`: public output of B after round 1. It has to be stored by B (not necessarily privately). Contains the encrypted bit vector `ciphertexts_b`, the round 2 relinearization key share `share_rlk_b_round2` and the round 1 aggregate relinearization key share `rlk_agg_round1_h1s`. - -```js - const state1 = state1_bindgen(state0.message_a_to_b, bit_vector_b); - const {message_b_to_a, private_output_b, public_output_b} = state1; -``` - -### `state2_bindgen(private_output_a_state0, public_output_a_state0, message_b_to_a, bit_vector_a)` - -Called by party A after receiving `message_b_to_a` from B. Takes as input `private_output_a_state0` and `public_output_a_state0` stored by A from state 0, `message_b_to_a` from state 1 and the plaintext bit vector of A. This is the bit vector that A wants to find the intersection with the bit vector of B. Aggregates the received round 1 relinearization key share of B, and their own round 1 relinearization key share to generate the round 2 relinearization key share of A. Aggregates the received round 2 relinearization key share of B, and their own round 2 relinearization key share to generate the collective relinearization key. Aggregates the received public key share of B and their own generated public key share to generate the collective public key used for encryption. Encrypts the bit vector of A using the collective public key. Performs Private Set Intersection (PSI) on the encrypted bit vectors of A and B to get a new ciphertext. The collective relinearization key is used to reduce the size of the ciphertext. Perform partial decryption of the ciphertext (result of the PSI) using their own secret key. - -Returns: - -- `message_a_to_b`: message to be sent from A to B after round 2. Contains the partial decryption of the ciphertext (result of the PSI) `decryption_shares_a`, the encrypted bit vector `ciphertexts_a` and the round 2 relinearization key share `share_rlk_a_round2`. -- `public_output_a`: public output of A after round 2. It has to be stored by A (not necessarily privately). Contains the partial decryption of the ciphertext (result of the PSI) `decryption_shares_a` and the result of the PSI `ciphertexts_res` - -```js - const state2 = state2_bindgen(state0.private_output_a, state0.public_output_a, state1.message_b_to_a, bit_vector_a); - const {message_a_to_b, public_output_a} = state2; -``` - -### `state3_bindgen(private_output_b_state1, public_output_b_state1, message_a_to_b)` - -Called by party B after receiving `message_a_to_b` from A. Takes as input `private_output_b_state1` and `public_output_b_state1` stored by B from state 1 and `message_a_to_b` from state 2. Aggregates the received round 2 relinearization key share of A, and their own round 2 relinearization key share to generate the collective relinearization key. Performs Private Set Intersection (PSI) on the encrypted bit vectors of A and B to get a new ciphertext. The collective relinearization key is used to reduce the size of the ciphertext. Performs partial decryption of the ciphertext (result of the PSI) using their own secret key. Aggregates the partial decryption share of A and their own partial decryption share to fully decrypt the ciphertext and obtain the intersection of the two vectors. - -Returns: -- `message_b_to_a`: message to be sent from B to A after round 3. Contains the partial decryption of the ciphertext (result of the PSI) `decryption_shares_b`. -- `psi_output`: the intersection of the bit vectors of A and B. - -```js - const state3 = state3_bindgen(state1.private_output_b, state1.public_output_b, state2.message_a_to_b); - const {message_b_to_a, psi_output} = state3; -``` - -### `state4_bindgen(public_output_a_state2, message_b_to_a)` - -Called by party A after receiving `message_b_to_a` from B. Takes as input `public_output_a_state2` stored by A from state 2 and `message_b_to_a` from state 3. Aggregates the partial decryption share of B and their own partial decryption share to fully decrypt the ciphertext and obtain the intersection of the two vectors. - -Returns: -- `psi_output`: the intersection of the bit vectors of A and B. - -```js - const state4 = state4_bindgen(state2.public_output_a, state3.message_b_to_a); - const psi_output = state4; -``` - -Note that the `psi_output` returned by `state3_bindgen` and `state4_bindgen` should be the same. It is the intersection of the bit vectors of A and B. - -### Benchmarks in web-app - -The benchmark relates to a PSI protocol are based on the following `bfv` parameters: - -- `ciphertext_moduli`: `[1032193, 1073692673]` -- `extension_moduli` : `[995329, 1073668097]` -- `plaintext_modulus`: `40961` -- `ring_size`: `2048` - -The bit vector size, which is the subject of the PSI protocol, is set to `ring_size * 3` - -**Runtime** - -The benchmarks are run on M2 Macbook Pro with 12 cores and 32GB of RAM. The browser used is Brave v1.61.116 Chromium:120.0.6099.217. The benchmark code is also part of `index.html`. - -| Operation | Time (ms) | -| --- | --- | -| state0_bindgen | 13.86 | -| state1_bindgen | 33.25 | -| state2_bindgen | 53.91 | -| state3_bindgen | 38.12 | -| state4_bindgen | 11.44 | - -**Communication Bandwidth** - -The following benchmarks measure the size in terms of bytes of the output of each state. The benchmark code can be found inside `src/lib.rs` and reproduced by running `cargo test --release -- --nocapture`. - -| State | Output | Size in Bytes | -| ------- | ------------------ | ------------- | -| State 0 | `private_output_a` | 1026 | -| State 0 | `public_output_a` | 84495 | -| State 0 | `message_a_to_b` | 84495 | -| State 1 | `private_output_b` | 513 | -| State 1 | `public_output_b` | 144408 | -| State 1 | `message_b_to_a` | 195105 | -| State 2 | `public_output_a` | 115218 | -| State 2 | `message_a_to_b` | 149016 | -| State 3 | `message_b_to_a` | 38406 | -| State 3 | `psi_output_b` | 768 | -| State 4 | `psi_output_a` | 768 | diff --git a/pkg/index.html b/pkg/index.html index 6332282..a7bc8d6 100644 --- a/pkg/index.html +++ b/pkg/index.html @@ -1,5 +1,4 @@ - - +
@@ -7,63 +6,94 @@ diff --git a/pkg/mp_psi.d.ts b/pkg/mp_psi.d.ts index 3f4742a..daac80f 100644 --- a/pkg/mp_psi.d.ts +++ b/pkg/mp_psi.d.ts @@ -3,44 +3,37 @@ /** * @returns {any} */ -export function state0_bindgen(): any; +export function gen_keys_js(): any; /** -* @param {any} message_a_to_b +* @param {any} gen_keys_output +* @param {any} other_message_round1 * @param {Uint32Array} bit_vector * @returns {any} */ -export function state1_bindgen(message_a_to_b: any, bit_vector: Uint32Array): any; +export function round1_js(gen_keys_output: any, other_message_round1: any, bit_vector: Uint32Array): any; /** -* @param {any} private_output_a_state0 -* @param {any} public_output_a_state0 -* @param {any} message_b_to_a -* @param {Uint32Array} bit_vector -* @returns {any} -*/ -export function state2_bindgen(private_output_a_state0: any, public_output_a_state0: any, message_b_to_a: any, bit_vector: Uint32Array): any; -/** -* @param {any} private_output_b_state1 -* @param {any} public_output_b_state1 -* @param {any} message_a_to_b +* @param {any} gen_keys_output +* @param {any} round1_output +* @param {any} other_message_round2 +* @param {boolean} is_a * @returns {any} */ -export function state3_bindgen(private_output_b_state1: any, public_output_b_state1: any, message_a_to_b: any): any; +export function round2_js(gen_keys_output: any, round1_output: any, other_message_round2: any, is_a: boolean): any; /** -* @param {any} public_output_a_state2 -* @param {any} message_b_to_a +* @param {any} round2_output +* @param {any} other_message * @returns {Uint32Array} */ -export function state4_bindgen(public_output_a_state2: any, message_b_to_a: any): Uint32Array; +export function round3_js(round2_output: any, other_message: any): Uint32Array; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly state0_bindgen: () => number; - readonly state1_bindgen: (a: number, b: number, c: number) => number; - readonly state2_bindgen: (a: number, b: number, c: number, d: number, e: number) => number; - readonly state3_bindgen: (a: number, b: number, c: number) => number; - readonly state4_bindgen: (a: number, b: number, c: number) => void; + readonly gen_keys_js: () => number; + readonly round1_js: (a: number, b: number, c: number, d: number) => number; + readonly round2_js: (a: number, b: number, c: number, d: number) => number; + readonly round3_js: (a: number, b: number, c: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/pkg/mp_psi.js b/pkg/mp_psi.js index bd5e794..f8711bf 100644 --- a/pkg/mp_psi.js +++ b/pkg/mp_psi.js @@ -191,8 +191,8 @@ function debugString(val) { /** * @returns {any} */ -export function state0_bindgen() { - const ret = wasm.state0_bindgen(); +export function gen_keys_js() { + const ret = wasm.gen_keys_js(); return takeObject(ret); } @@ -212,39 +212,27 @@ function passArray32ToWasm0(arg, malloc) { return ptr; } /** -* @param {any} message_a_to_b +* @param {any} gen_keys_output +* @param {any} other_message_round1 * @param {Uint32Array} bit_vector * @returns {any} */ -export function state1_bindgen(message_a_to_b, bit_vector) { +export function round1_js(gen_keys_output, other_message_round1, bit_vector) { const ptr0 = passArray32ToWasm0(bit_vector, wasm.__wbindgen_malloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.state1_bindgen(addHeapObject(message_a_to_b), ptr0, len0); + const ret = wasm.round1_js(addHeapObject(gen_keys_output), addHeapObject(other_message_round1), ptr0, len0); return takeObject(ret); } /** -* @param {any} private_output_a_state0 -* @param {any} public_output_a_state0 -* @param {any} message_b_to_a -* @param {Uint32Array} bit_vector -* @returns {any} -*/ -export function state2_bindgen(private_output_a_state0, public_output_a_state0, message_b_to_a, bit_vector) { - const ptr0 = passArray32ToWasm0(bit_vector, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.state2_bindgen(addHeapObject(private_output_a_state0), addHeapObject(public_output_a_state0), addHeapObject(message_b_to_a), ptr0, len0); - return takeObject(ret); -} - -/** -* @param {any} private_output_b_state1 -* @param {any} public_output_b_state1 -* @param {any} message_a_to_b +* @param {any} gen_keys_output +* @param {any} round1_output +* @param {any} other_message_round2 +* @param {boolean} is_a * @returns {any} */ -export function state3_bindgen(private_output_b_state1, public_output_b_state1, message_a_to_b) { - const ret = wasm.state3_bindgen(addHeapObject(private_output_b_state1), addHeapObject(public_output_b_state1), addHeapObject(message_a_to_b)); +export function round2_js(gen_keys_output, round1_output, other_message_round2, is_a) { + const ret = wasm.round2_js(addHeapObject(gen_keys_output), addHeapObject(round1_output), addHeapObject(other_message_round2), is_a); return takeObject(ret); } @@ -253,14 +241,14 @@ function getArrayU32FromWasm0(ptr, len) { return getUint32Memory0().subarray(ptr / 4, ptr / 4 + len); } /** -* @param {any} public_output_a_state2 -* @param {any} message_b_to_a +* @param {any} round2_output +* @param {any} other_message * @returns {Uint32Array} */ -export function state4_bindgen(public_output_a_state2, message_b_to_a) { +export function round3_js(round2_output, other_message) { try { const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.state4_bindgen(retptr, addHeapObject(public_output_a_state2), addHeapObject(message_b_to_a)); + wasm.round3_js(retptr, addHeapObject(round2_output), addHeapObject(other_message)); var r0 = getInt32Memory0()[retptr / 4 + 0]; var r1 = getInt32Memory0()[retptr / 4 + 1]; var v1 = getArrayU32FromWasm0(r0, r1).slice(); diff --git a/pkg/mp_psi_bg.wasm b/pkg/mp_psi_bg.wasm index 444c218..5b0eced 100644 Binary files a/pkg/mp_psi_bg.wasm and b/pkg/mp_psi_bg.wasm differ diff --git a/pkg/mp_psi_bg.wasm.d.ts b/pkg/mp_psi_bg.wasm.d.ts index 70120d6..4aa366d 100644 --- a/pkg/mp_psi_bg.wasm.d.ts +++ b/pkg/mp_psi_bg.wasm.d.ts @@ -1,11 +1,10 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; -export function state0_bindgen(): number; -export function state1_bindgen(a: number, b: number, c: number): number; -export function state2_bindgen(a: number, b: number, c: number, d: number, e: number): number; -export function state3_bindgen(a: number, b: number, c: number): number; -export function state4_bindgen(a: number, b: number, c: number): void; +export function gen_keys_js(): number; +export function round1_js(a: number, b: number, c: number, d: number): number; +export function round2_js(a: number, b: number, c: number, d: number): number; +export function round3_js(a: number, b: number, c: number): void; export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; export function __wbindgen_add_to_stack_pointer(a: number): number; diff --git a/pkg/package.json b/pkg/package.json index 1f97f59..c8d846e 100644 --- a/pkg/package.json +++ b/pkg/package.json @@ -1,6 +1,6 @@ { "name": "mp-psi", - "version": "0.1.9.1", + "version": "0.1.0", "files": [ "mp_psi_bg.wasm", "mp_psi.js", @@ -8,7 +8,5 @@ ], "module": "mp_psi.js", "types": "mp_psi.d.ts", - "sideEffects": [ - "./snippets/*" - ] + "sideEffects": false } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b25f600..9b69abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,13 @@ -use std::char::ParseCharError; - use bfv::{ - BfvParameters, Ciphertext, CiphertextProto, CollectiveDecryption, CollectiveDecryptionShare, - CollectiveDecryptionShareProto, CollectivePublicKeyGenerator, CollectivePublicKeyShare, - CollectivePublicKeyShareProto, CollectiveRlkAggShare1, CollectiveRlkAggTrimmedShare1, - CollectiveRlkAggTrimmedShare1Proto, CollectiveRlkGenerator, CollectiveRlkShare1, - CollectiveRlkShare1Proto, CollectiveRlkShare2, CollectiveRlkShare2Proto, Encoding, - EvaluationKey, Evaluator, Plaintext, SecretKey, SecretKeyProto, + BfvParameters, CiphertextProto, CollectiveDecryption, CollectiveDecryptionShare, + CollectiveDecryptionShareProto, CollectivePublicKeyGenerator, CollectivePublicKeyShareProto, + CollectiveRlkAggTrimmedShare1Proto, CollectiveRlkGenerator, CollectiveRlkShare1Proto, + CollectiveRlkShare2Proto, Encoding, EvaluationKey, Evaluator, Plaintext, SecretKey, + SecretKeyProto, }; use itertools::{izip, Itertools}; use rand::thread_rng; -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use traits::{ TryDecodingWithParameters, TryEncodingWithParameters, TryFromWithLevelledParameters, TryFromWithParameters, @@ -36,18 +33,33 @@ fn params() -> BfvParameters { params } +/************* GENERATING KEYS *************/ + +#[derive(Serialize, Deserialize)] struct PsiKeys { - s: SecretKey, - s_rlk: SecretKey, + s: SecretKeyProto, + s_rlk: SecretKeyProto, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] struct MessageRound1 { - share_pk: CollectivePublicKeyShare, - share_rlk1: CollectiveRlkShare1, + share_pk: CollectivePublicKeyShareProto, + share_rlk1: CollectiveRlkShare1Proto, +} + +#[derive(Serialize, Deserialize)] +struct GenKeysOutput { + psi_keys: PsiKeys, + message_round1: MessageRound1, } -fn gen_keys() -> (PsiKeys, MessageRound1) { +#[wasm_bindgen] +pub fn gen_keys_js() -> JsValue { + let output = gen_keys(); + serde_wasm_bindgen::to_value(&output).unwrap() +} + +fn gen_keys() -> GenKeysOutput { let params = params(); let mut rng = thread_rng(); let s = SecretKey::random_with_params(¶ms, &mut rng); @@ -57,23 +69,55 @@ fn gen_keys() -> (PsiKeys, MessageRound1) { let share_rlk1 = CollectiveRlkGenerator::generate_share_1(¶ms, &s, &s_rlk, CRS_RLK, 0, &mut rng); - ( - PsiKeys { s, s_rlk }, - MessageRound1 { - share_pk, - share_rlk1, + GenKeysOutput { + psi_keys: PsiKeys { + s: convert(&s, ¶ms), + s_rlk: convert(&s_rlk, ¶ms), }, - ) + message_round1: MessageRound1 { + share_pk: convert(&share_pk, ¶ms), + share_rlk1: convert(&share_rlk1, ¶ms), + }, + } +} + +/************* ROUND 1 *************/ + +#[derive(Serialize, Deserialize)] +struct Round1Output { + state_round2: StateRound2, + message_round2: MessageRound2, } +#[derive(Serialize, Deserialize)] struct StateRound2 { - rlk_agg1_trimmed: CollectiveRlkAggTrimmedShare1, + rlk_agg1_trimmed: CollectiveRlkAggTrimmedShare1Proto, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] struct MessageRound2 { - share_rlk2: CollectiveRlkShare2, - cts: Vec