Compare commits
11 Commits
0762052c98
...
cd371ac580
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd371ac580 | ||
![]() |
bb5fb5e75f | ||
![]() |
58dfe53408 | ||
![]() |
f3e504689e | ||
![]() |
1e1dbdf8df | ||
![]() |
4f278278b7 | ||
![]() |
6d308e90d2 | ||
![]() |
3346c7f0af | ||
![]() |
8643523e0e | ||
![]() |
38b2e5fd4c | ||
![]() |
036db10ae5 |
179
src/api.rs
179
src/api.rs
@ -21,10 +21,10 @@ use anyhow::Error as AnyhowError;
|
|||||||
use anyhow::Result as AnyhowResult;
|
use anyhow::Result as AnyhowResult;
|
||||||
use sdk_common::aes_gcm::aead::{Aead, Payload};
|
use sdk_common::aes_gcm::aead::{Aead, Payload};
|
||||||
use sdk_common::crypto::{
|
use sdk_common::crypto::{
|
||||||
decrypt_with_key, encrypt_with_key, generate_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD
|
decrypt_with_key, encrypt_with_key, generate_key, verify_merkle_proof, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD
|
||||||
};
|
};
|
||||||
use sdk_common::process::{check_tx_for_process_updates, lock_processes, Process, ProcessState};
|
use sdk_common::process::{check_tx_for_process_updates, lock_processes, Process, ProcessState};
|
||||||
use sdk_common::serialization::{OutPointMemberMap, OutPointProcessMap, ciborium_deserialize as common_deserialize};
|
use sdk_common::serialization::{OutPointMemberMap, OutPointProcessMap};
|
||||||
use sdk_common::signature::{AnkHash, AnkMessageHash, AnkValidationNoHash, AnkValidationYesHash, Proof};
|
use sdk_common::signature::{AnkHash, AnkMessageHash, AnkValidationNoHash, AnkValidationYesHash, Proof};
|
||||||
use sdk_common::sp_client::bitcoin::blockdata::fee_rate;
|
use sdk_common::sp_client::bitcoin::blockdata::fee_rate;
|
||||||
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
|
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
|
||||||
@ -65,7 +65,7 @@ use sdk_common::network::{
|
|||||||
NewTxMessage,
|
NewTxMessage,
|
||||||
};
|
};
|
||||||
use sdk_common::pcd::{
|
use sdk_common::pcd::{
|
||||||
FileBlob, Member, Pcd, PcdCommitments, RoleDefinition, Roles, ValidationRule
|
DataType, FileBlob, Member, Pcd, PcdCommitments, RoleDefinition, Roles, ValidationRule, PCD_VERSION, PcdSerializable
|
||||||
};
|
};
|
||||||
use sdk_common::prd::{AnkPrdHash, Prd, PrdType};
|
use sdk_common::prd::{AnkPrdHash, Prd, PrdType};
|
||||||
use sdk_common::silentpayments::{create_transaction as internal_create_transaction, sign_transaction as internal_sign_tx, TsUnsignedTransaction};
|
use sdk_common::silentpayments::{create_transaction as internal_create_transaction, sign_transaction as internal_sign_tx, TsUnsignedTransaction};
|
||||||
@ -73,7 +73,7 @@ use sdk_common::sp_client::{FeeRate, OutputSpendStatus, OwnedOutput, Recipient,
|
|||||||
use sdk_common::secrets::SecretsStore;
|
use sdk_common::secrets::SecretsStore;
|
||||||
|
|
||||||
use crate::user::{lock_local_device, set_new_device, LOCAL_DEVICE};
|
use crate::user::{lock_local_device, set_new_device, LOCAL_DEVICE};
|
||||||
use crate::wallet::{generate_sp_wallet, lock_freezed_utxos};
|
use crate::wallet::{generate_sp_wallet, lock_freezed_utxos, scan_blocks};
|
||||||
|
|
||||||
const EMPTYSTATEID: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
const EMPTYSTATEID: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
|
||||||
@ -253,7 +253,6 @@ pub fn get_address() -> ApiResult<String> {
|
|||||||
.get_sp_client()
|
.get_sp_client()
|
||||||
.get_receiving_address()
|
.get_receiving_address()
|
||||||
.to_string();
|
.to_string();
|
||||||
debug!("{}", address);
|
|
||||||
Ok(address)
|
Ok(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +265,8 @@ pub fn get_member() -> ApiResult<Member> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn restore_device(device_str: String) -> ApiResult<()> {
|
pub fn restore_device(device: JsValue) -> ApiResult<()> {
|
||||||
let device: Device = serde_json::from_str(&device_str)?;
|
let device: Device = serde_wasm_bindgen::from_value(device)?;
|
||||||
|
|
||||||
let mut local_device = lock_local_device()?;
|
let mut local_device = lock_local_device()?;
|
||||||
|
|
||||||
@ -434,10 +433,10 @@ pub fn get_pairing_process_id() -> ApiResult<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn dump_device() -> ApiResult<String> {
|
pub fn dump_device() -> ApiResult<Device> {
|
||||||
let local_device = lock_local_device()?;
|
let local_device = lock_local_device()?;
|
||||||
|
|
||||||
Ok(serde_json::to_string(&local_device.clone())?)
|
Ok(local_device.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -1115,7 +1114,7 @@ pub fn create_transaction(addresses: Vec<String>, fee_rate: u32) -> ApiResult<Ap
|
|||||||
// We mark the utxos in the inputs as spent to prevent accidental double spends
|
// We mark the utxos in the inputs as spent to prevent accidental double spends
|
||||||
for input in &unsigned_tx.unsigned_tx.as_ref().unwrap().input {
|
for input in &unsigned_tx.unsigned_tx.as_ref().unwrap().input {
|
||||||
if let Some(output) = outputs.get_mut(&input.previous_output) {
|
if let Some(output) = outputs.get_mut(&input.previous_output) {
|
||||||
output.spend_status = OutputSpendStatus::Spent(new_txid.to_string());
|
output.spend_status = OutputSpendStatus::Spent(new_txid.to_byte_array());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1257,18 +1256,19 @@ pub fn update_process(
|
|||||||
roles.clone()
|
roles.clone()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// We compare the new state with the previous one
|
// TODO duplicate check is broken
|
||||||
let last_state_merkle_root = &prev_state.state_id;
|
// // We compare the new state with the previous one
|
||||||
|
// let last_state_merkle_root = &prev_state.state_id;
|
||||||
|
|
||||||
if *last_state_merkle_root == new_state.state_id {
|
// if *last_state_merkle_root == new_state.state_id {
|
||||||
return Err(ApiError::new("new proposed state is identical to the previous commited state".to_owned()));
|
// return Err(ApiError::new("new proposed state is identical to the previous commited state".to_owned()));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// We check that we don't have already a similar concurrent state
|
// // We check that we don't have already a similar concurrent state
|
||||||
let concurrent_processes = process.get_latest_concurrent_states()?;
|
// let concurrent_processes = process.get_latest_concurrent_states()?;
|
||||||
if concurrent_processes.iter().any(|p| p.state_id == new_state.state_id) {
|
// if concurrent_processes.iter().any(|p| p.state_id == new_state.state_id) {
|
||||||
return Err(ApiError::new("New state already known".to_owned()));
|
// return Err(ApiError::new("New state already known".to_owned()));
|
||||||
}
|
// }
|
||||||
|
|
||||||
let diffs = create_diffs(&lock_local_device()?, &process, &new_state, &members_list)?;
|
let diffs = create_diffs(&lock_local_device()?, &process, &new_state, &members_list)?;
|
||||||
|
|
||||||
@ -1279,9 +1279,8 @@ pub fn update_process(
|
|||||||
for (field, plain_value) in new_attributes.iter() {
|
for (field, plain_value) in new_attributes.iter() {
|
||||||
let hash = new_state.pcd_commitment.get(field).ok_or(anyhow::Error::msg("Missing commitment"))?;
|
let hash = new_state.pcd_commitment.get(field).ok_or(anyhow::Error::msg("Missing commitment"))?;
|
||||||
let key = generate_key(&mut rng);
|
let key = generate_key(&mut rng);
|
||||||
|
let cipher = encrypt_with_key(&key, plain_value.as_slice())?;
|
||||||
new_state.keys.insert(field.to_owned(), key);
|
new_state.keys.insert(field.to_owned(), key);
|
||||||
let serialized = serde_json::to_string(plain_value)?;
|
|
||||||
let cipher = encrypt_with_key(&key, serialized.as_bytes())?;
|
|
||||||
encrypted_data.insert(hash.to_lower_hex_string(), cipher.to_lower_hex_string());
|
encrypted_data.insert(hash.to_lower_hex_string(), cipher.to_lower_hex_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1451,7 +1450,8 @@ pub fn create_update_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We shouldn't ever have error here since we already checked above
|
// We shouldn't ever have error here since we already checked above
|
||||||
let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?).unwrap();
|
let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?)
|
||||||
|
.ok_or(AnyhowError::msg("Failed to retrieve secret".to_owned()))?;
|
||||||
|
|
||||||
let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?;
|
let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?;
|
||||||
ciphers.push(cipher.to_lower_hex_string());
|
ciphers.push(cipher.to_lower_hex_string());
|
||||||
@ -1709,30 +1709,133 @@ pub fn encode_json(json_data: JsValue) -> ApiResult<Pcd> {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn encode_value(value: JsValue) -> ApiResult<Vec<u8>> {
|
|
||||||
let res = sdk_common::serialization::ciborium_serialize(&serde_wasm_bindgen::from_value(value)?)?;
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn decode_value(value: Vec<u8>) -> ApiResult<JsValue> {
|
pub fn decode_value(value: Vec<u8>) -> ApiResult<JsValue> {
|
||||||
if let Ok(deserialized) = sdk_common::serialization::ciborium_deserialize::<FileBlob>(&value) {
|
// Try FileBlob first
|
||||||
let u8IntArray = Uint8Array::from(deserialized.data.as_slice());
|
if let Ok(file_blob) = sdk_common::pcd::FileBlob::deserialize_from_pcd(&value) {
|
||||||
|
let u8IntArray = Uint8Array::from(file_blob.data.as_slice());
|
||||||
let res = Object::new();
|
let res = Object::new();
|
||||||
Reflect::set(&res, &JsValue::from_str("type"), &JsValue::from_str(&deserialized.r#type));
|
Reflect::set(&res, &JsValue::from_str("type"), &JsValue::from_str(&file_blob.r#type)).unwrap();
|
||||||
Reflect::set(&res, &JsValue::from_str("data"), &JsValue::from(u8IntArray));
|
Reflect::set(&res, &JsValue::from_str("data"), &JsValue::from(u8IntArray)).unwrap();
|
||||||
Ok(JsValue::from(res))
|
return Ok(JsValue::from(res));
|
||||||
} else {
|
|
||||||
let res: Value = sdk_common::serialization::ciborium_deserialize(&value)?;
|
|
||||||
return Ok(serde_wasm_bindgen::to_value(&res)?);
|
|
||||||
}
|
}
|
||||||
|
// Try JSON next
|
||||||
|
if let Ok(json) = serde_json::Value::deserialize_from_pcd(&value) {
|
||||||
|
return Ok(serde_wasm_bindgen::to_value(&json)?);
|
||||||
|
}
|
||||||
|
Err(ApiError::new("Invalid or unsupported PCD data".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn hash_value(value: JsValue, commited_in: String, label: String) -> ApiResult<String> {
|
pub fn hash_value(value: JsValue, commited_in: String, label: String) -> ApiResult<String> {
|
||||||
let outpoint = OutPoint::from_str(&commited_in)?;
|
let outpoint = OutPoint::from_str(&commited_in)?;
|
||||||
let encoded_value = sdk_common::serialization::ciborium_serialize(&serde_wasm_bindgen::from_value::<FileBlob>(value)?)?;
|
let encoded_value = if let Ok(file_blob) = serde_wasm_bindgen::from_value::<FileBlob>(value.clone()) {
|
||||||
|
file_blob.serialize_to_pcd()?
|
||||||
|
} else if let Ok(json) = serde_wasm_bindgen::from_value::<Value>(value) {
|
||||||
|
json.serialize_to_pcd()?
|
||||||
|
} else {
|
||||||
|
return Err(ApiError::new("Invalid or unsupported PCD data".to_owned()));
|
||||||
|
};
|
||||||
let hash = AnkPcdHash::from_pcd_value(encoded_value.as_slice(), label.as_bytes(), &outpoint);
|
let hash = AnkPcdHash::from_pcd_value(encoded_value.as_slice(), label.as_bytes(), &outpoint);
|
||||||
Ok(hash.as_byte_array().to_lower_hex_string())
|
Ok(hash.as_byte_array().to_lower_hex_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Tsify, Serialize, Deserialize)]
|
||||||
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub struct MerkleProofResult {
|
||||||
|
pub proof: String,
|
||||||
|
pub root: String,
|
||||||
|
pub attribute: String,
|
||||||
|
pub attribute_index: usize,
|
||||||
|
pub total_leaves_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
/// Generate a merkle proof for a specific attribute in a process state.
|
||||||
|
///
|
||||||
|
/// This function creates a merkle proof that proves the existence of a specific attribute
|
||||||
|
/// in a given state of a process. The proof can be used to verify that the attribute
|
||||||
|
/// was indeed part of the state without revealing the entire state.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `process_state` - The process state object as a JavaScript value
|
||||||
|
/// * `attribute_name` - The name of the attribute to generate a proof for
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A MerkleProofResult object containing:
|
||||||
|
/// * `proof` - The merkle proof as a hex string
|
||||||
|
/// * `root` - The merkle root (state_id) as a hex string
|
||||||
|
/// * `attribute` - The attribute name that was proven
|
||||||
|
/// * `attribute_index` - The index of the attribute in the merkle tree
|
||||||
|
/// * `total_leaves_count` - The total number of leaves in the merkle tree
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// * "Failed to deserialize process state" - If the process state cannot be deserialized from JsValue
|
||||||
|
/// * "Attribute not found in state" - If the attribute doesn't exist in the state
|
||||||
|
pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiResult<MerkleProofResult> {
|
||||||
|
// Deserialize the process state from JsValue
|
||||||
|
let state: ProcessState = serde_wasm_bindgen::from_value(process_state)
|
||||||
|
.map_err(|_| ApiError::new("Failed to deserialize process state".to_owned()))?;
|
||||||
|
|
||||||
|
// Create merkle tree from the PCD commitments
|
||||||
|
let merkle_tree = state.pcd_commitment.create_merkle_tree()?;
|
||||||
|
|
||||||
|
// Find the index of the attribute in the commitments
|
||||||
|
let attribute_index = state.pcd_commitment.find_index_of(&attribute_name)
|
||||||
|
.ok_or(ApiError::new("Attribute not found in state".to_owned()))?;
|
||||||
|
|
||||||
|
// Generate the merkle proof for the attribute
|
||||||
|
let proof = merkle_tree.proof(&[attribute_index]);
|
||||||
|
|
||||||
|
// Convert the proof to a format that can be serialized to JavaScript
|
||||||
|
let proof_bytes = proof.to_bytes();
|
||||||
|
let proof_hex = proof_bytes.to_lower_hex_string();
|
||||||
|
|
||||||
|
Ok(MerkleProofResult {
|
||||||
|
proof: proof_hex,
|
||||||
|
root: state.state_id.to_lower_hex_string(),
|
||||||
|
attribute: attribute_name,
|
||||||
|
attribute_index: attribute_index,
|
||||||
|
total_leaves_count: merkle_tree.leaves_len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
/// Validate a merkle proof for a specific attribute.
|
||||||
|
///
|
||||||
|
/// This function verifies that a merkle proof is valid and proves the existence
|
||||||
|
/// of a specific attribute in a given state. It checks that the proof correctly
|
||||||
|
/// leads to the claimed root when combined with the attribute hash.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `proof_result` - a JsValue expected to contain a MerkleProofResult with the proof and metadata
|
||||||
|
/// * `hash` - The hash of the attribute data as a hex string (the leaf value)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A boolean indicating whether the proof is valid
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// * "serde_wasm_bindgen deserialization error" - If the proof is not a valid MerkleProofResult
|
||||||
|
/// * "Invalid proof format" - If the proof cannot be parsed
|
||||||
|
/// * "Invalid hash format" - If the hash is not a valid 32-byte hex string
|
||||||
|
/// * "Invalid root format" - If the root is not a valid 32-byte hex string
|
||||||
|
pub fn validate_merkle_proof(proof_result: JsValue, hash: String) -> ApiResult<bool> {
|
||||||
|
let proof_result: MerkleProofResult = serde_wasm_bindgen::from_value(proof_result)?;
|
||||||
|
let root_bytes: [u8; 32] = Vec::from_hex(&proof_result.root)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| ApiError::new("Invalid root format".to_owned()))?;
|
||||||
|
|
||||||
|
let proof_bytes = Vec::from_hex(&proof_result.proof)
|
||||||
|
.map_err(|_| ApiError::new("Invalid proof format".to_owned()))?;
|
||||||
|
|
||||||
|
let index = proof_result.attribute_index;
|
||||||
|
let total_leaves_count = proof_result.total_leaves_count;
|
||||||
|
|
||||||
|
let hash_bytes: [u8; 32] = Vec::from_hex(&hash)?.try_into()
|
||||||
|
.map_err(|_| ApiError::new("Invalid hash format".to_owned()))?;
|
||||||
|
|
||||||
|
let res = verify_merkle_proof(&proof_bytes, &root_bytes, index, &hash_bytes, total_leaves_count)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user