diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d0b0058..ee91e56a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog + +## 1.0.1 +* Updated `build_get_utxo_request_handler` function to accept an additional parameter `from` used for pagination. +* Updated `parse_get_utxo_response_handler` function to return an additional parameter `next` pointing to the next slice of payment sources. +* Added `sign_with_address_handler` function to sign a message with a payment address. +* Added `verify_with_address_handler` function to verify a signature with a payment address. +* bugfixes + ## 1.0.0 * bugfixes diff --git a/devops/Makefile b/devops/Makefile index 80f9a00b0..ccf4c040d 100644 --- a/devops/Makefile +++ b/devops/Makefile @@ -45,7 +45,7 @@ FPM_P_VENDOR := Sovrin FPM_P_DESCRIPTION := libsovtoken written in Rust FPM_P_NAME = $(PACKAGE_NAME) FPM_P_VERSION ?= $(SRC_VERSION) -FPM_P_DEPENDS = libindy(>=1.10.0~77) +FPM_P_DEPENDS = libindy(>=1.11.0) FPM_P_OUTPUT_DIR = $(LIB_TARGET_DIR) FPM_ARGS = $(LIB_DYNAMIC)=/usr/lib/ diff --git a/devops/aws-codebuild/Jenkinsfile.cd b/devops/aws-codebuild/Jenkinsfile.cd index dfc72df15..57d46183c 100644 --- a/devops/aws-codebuild/Jenkinsfile.cd +++ b/devops/aws-codebuild/Jenkinsfile.cd @@ -15,8 +15,8 @@ String srcVersion gitHubUserCredId = env.GITHUB_BOT_USER ?: 'sovbot-github' sovrinPackagingRepo = env.SOVRIN_PACKAGING_REPO ?: 'https://github.com/sovrin-foundation/sovrin-packaging' sovrinPackagingBranch = env.SOVRIN_PACKAGING_BRANCH ?: 'master' -LIBINDY_STREAM = "rc" -LIBINDY_VERSION = "1.10.0-77" +LIBINDY_STREAM = "stable" +LIBINDY_VERSION = "1.11.0" def downloadPackagingUtils() { git branch: sovrinPackagingBranch, credentialsId: gitHubUserCredId, url: sovrinPackagingRepo diff --git a/devops/aws-codebuild/Jenkinsfile.ci b/devops/aws-codebuild/Jenkinsfile.ci index 5d00d0ea8..7ea8ec452 100644 --- a/devops/aws-codebuild/Jenkinsfile.ci +++ b/devops/aws-codebuild/Jenkinsfile.ci @@ -7,8 +7,8 @@ def sovLibrary = library(identifier: 'sovrin-aws-codebuild@master', retriever: m logger = sovLibrary.Logger.new(this) notifier = sovLibrary.Notifier.new(this) logger.setGlobalLevel('TRACE') -LIBINDY_STREAM = "rc" -LIBINDY_VERSION = "1.10.0-77" +LIBINDY_STREAM = "stable" +LIBINDY_VERSION = "1.11.0" def nodeLabels = [ codeBuild: env.LIBSOVTOKEN_CODEBUILD_NODE_LABEL ?: 'codebuild', diff --git a/devops/docker/base/xenial/Dockerfile b/devops/docker/base/xenial/Dockerfile index 65aca97c0..82713f330 100644 --- a/devops/docker/base/xenial/Dockerfile +++ b/devops/docker/base/xenial/Dockerfile @@ -21,9 +21,9 @@ RUN cd /tmp \ # need for libsodium to be reachable via pkg-config (sodiumoxide uses it) ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig:$PKG_CONFIG_PATH # TODO ??? is it really needed -ENV LIBINDY_VERSION=1.10.0~77 +ENV LIBINDY_VERSION=1.11.0 RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 68DB5E88 \ - && echo "deb https://repo.sovrin.org/sdk/deb xenial rc" >> /etc/apt/sources.list \ + && echo "deb https://repo.sovrin.org/sdk/deb xenial stable" >> /etc/apt/sources.list \ && apt-get update && apt-get install -y --no-install-recommends \ libssl-dev \ libindy=${LIBINDY_VERSION} \ @@ -47,4 +47,4 @@ RUN cd /tmp/libsovtoken \ # TODO CMD ENTRYPOINT ... -ENV LIBSOVTOKEN_BASE_ENV_VERSION=0.26.0 +ENV LIBSOVTOKEN_BASE_ENV_VERSION=0.33.0 diff --git a/devops/docker/ci/xenial/Dockerfile b/devops/docker/ci/xenial/Dockerfile index 2ca5cffbc..1a2dd1a68 100644 --- a/devops/docker/ci/xenial/Dockerfile +++ b/devops/docker/ci/xenial/Dockerfile @@ -1,4 +1,4 @@ -FROM sovrin/libsovtoken:base-xenial-0.26.0 +FROM sovrin/libsovtoken:base-xenial-0.33.0 # TODO LABEL maintainer="Name " ARG LIBINDY_CRYPTO_VERSION @@ -35,11 +35,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # or python3-rocksdb are not specified here) ENV LIBINDY_CRYPTO_VERSION ${LIBINDY_CRYPTO_VERSION:-0.4.5} ENV PYTHON3_INDY_CRYPTO_VERSION ${PYTHON3_INDY_CRYPTO_VERSION:-0.4.5} -ENV INDY_PLENUM_VERSION ${INDY_PLENUM_VERSION:-1.9.0} -ENV INDY_NODE_VERSION ${INDY_NODE_VERSION:-1.9.0~rc3} -ENV TOKEN_VER ${TOKEN_VER:-1.0.0~rc13} +ENV INDY_PLENUM_VERSION ${INDY_PLENUM_VERSION:-1.9.0~dev836} +ENV INDY_NODE_VERSION ${INDY_NODE_VERSION:-1.9.0~dev1028} +ENV TOKEN_VER ${TOKEN_VER:-1.0.0~dev65} RUN echo "deb https://repo.sovrin.org/sdk/deb xenial master" >> /etc/apt/sources.list -RUN echo "deb https://repo.sovrin.org/deb xenial rc" >> /etc/apt/sources.list \ +RUN echo "deb https://repo.sovrin.org/deb xenial master" >> /etc/apt/sources.list \ && apt-get update && apt-get install -y --no-install-recommends \ libindy-crypto=${LIBINDY_CRYPTO_VERSION} \ python3-indy-crypto=${PYTHON3_INDY_CRYPTO_VERSION} \ @@ -66,4 +66,4 @@ COPY libsovtoken-ci-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/libsovtoken-ci-entrypoint.sh ENTRYPOINT ["libsovtoken-ci-entrypoint.sh"] -ENV LIBSOVTOKEN_CI_ENV_VERSION=0.66.0 +ENV LIBSOVTOKEN_CI_ENV_VERSION=0.723.0 diff --git a/devops/indy-pool/Dockerfile b/devops/indy-pool/Dockerfile index 5da72beae..73bd48dee 100644 --- a/devops/indy-pool/Dockerfile +++ b/devops/indy-pool/Dockerfile @@ -19,15 +19,15 @@ FROM camparra/ubuntu16.04-rocksdb ARG uid=1000 -ARG indy_stream=rc +ARG indy_stream=master -ARG indy_plenum_ver=1.9.0 -ARG indy_node_ver=1.9.0~rc3 +ARG indy_plenum_ver=1.9.0~dev836 +ARG indy_node_ver=1.9.0~dev1028 ARG python3_indy_crypto_ver=0.4.5 ARG indy_crypto_ver=0.4.5 -ARG token_ver=1.0.0~rc13 -ARG fees_ver=1.0.0~rc13 +ARG token_ver=1.0.0~dev65 +ARG fees_ver=1.0.0~dev65 # Install environment RUN apt-get update -y && apt-get install -y \ diff --git a/doc/data_structures.md b/doc/data_structures.md index db8628e68..26c130a46 100644 --- a/doc/data_structures.md +++ b/doc/data_structures.md @@ -326,7 +326,8 @@ This API call is handled by LibSovToken build_get_sources_request_handler "operation": { "address": , // the payment address - "type": 10002 + "type": 10002, + "from": // shift to the next slice of payment sources }, "reqId": , // a random identifier "protocolVersion": // (optional) the version of the client/node communication protocol @@ -342,7 +343,8 @@ Example get_sources_txn_json: "operation": { "address": "2jyMWLv8NuxUV4yDc46mLQMn9WUUzeKURX3d2yQqgoLqEQC2sf", - "type": "10002" + "type": "10002", + "from": 1 }, "reqId": 6284, "protocolVersion": 1 @@ -366,6 +368,7 @@ This API call is handled by LibSovToken parse_get_sources_response_handler "outputs": [ ["", , ], ], + "next": // (optional) pointer to the next slice of payment sources "state_proof": { "multi_signature": @@ -402,6 +405,7 @@ Example resp_json from the ledger: [ ["dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q", 1, 40] ], + "next": 1, "state_proof": { "multi_signature": @@ -427,6 +431,7 @@ Example resp_json from the ledger: ### return: + next - (optional) pointer to the next slice of payment sources sources_json - parsed (payment method and node version agnostic) sources info as json: ``` [ diff --git a/libsovtoken/Cargo.toml b/libsovtoken/Cargo.toml index e14734da4..a539c57bb 100644 --- a/libsovtoken/Cargo.toml +++ b/libsovtoken/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libsovtoken" -version = "1.0.0" +version = "1.0.1" authors = [ "Matt Raffel i32 { trace!("api::build_get_utxo_request_handler called"); let handle_result = api_result_handler!(< *const c_char >, command_handle, cb); + let from: Option = if from == -1 { None } else {Some(from)}; let payment_address = match str_from_char_ptr(payment_address) { Some(s) => s, @@ -483,7 +489,7 @@ pub extern "C" fn build_get_utxo_request_handler(command_handle: i32, debug!("api::build_get_utxo_request_handler >> wallet_handle: {:?}, payment_address: {:?}", wallet_handle, secret!(&payment_address)); let utxo_request = - GetUtxoOperationRequest::new(String::from(payment_address)); + GetUtxoOperationRequest::new(String::from(payment_address), from); info!("Built GET_UTXO request: {:?}", utxo_request); let utxo_request = utxo_request.serialize_to_pointer() .map_err(|_| ErrorCode::CommonInvalidStructure); @@ -503,6 +509,7 @@ pub extern "C" fn build_get_utxo_request_handler(command_handle: i32, /// resp_json: json. \For format see https://github.com/sovrin-foundation/libsovtoken/blob/master/doc/data_structures.md /// /// # Returns +/// next: u64 (optional) - pointer to the next slice of payment sources /// utxo_json: json. For format see https://github.com/sovrin-foundation/libsovtoken/blob/master/doc/data_structures.md /// /// # Errors @@ -512,7 +519,7 @@ pub extern "C" fn build_get_utxo_request_handler(command_handle: i32, pub extern "C" fn parse_get_utxo_response_handler( command_handle: i32, resp_json: *const c_char, - cb: JsonCallback + cb: JsonI64Callback ) -> i32 { trace!("api::parse_get_utxo_response_handler called"); @@ -542,7 +549,7 @@ pub extern "C" fn parse_get_utxo_response_handler( // here is where the magic happens--conversion from input structure to output structure // is handled in ParseGetUtxoReply::from_response - let reply: ParseGetUtxoReply = match parse_get_utxo_response::from_response(response) { + let (sources, next) = match parse_get_utxo_response::from_response(response) { Ok(reply) => reply, Err(err) => { trace!("api::parse_get_utxo_response_handler << result: {:?}", err); @@ -550,7 +557,7 @@ pub extern "C" fn parse_get_utxo_response_handler( } }; - let reply_str: String = match reply.to_json().map_err(map_err_err!()) { + let reply_str: String = match sources.to_json().map_err(map_err_err!()) { Ok(j) => j, Err(_) => return ErrorCode::CommonInvalidState as i32, }; @@ -558,7 +565,7 @@ pub extern "C" fn parse_get_utxo_response_handler( let reply_str_ptr: *const c_char = c_pointer_from_string(reply_str); - cb(command_handle, ErrorCode::Success as i32, reply_str_ptr); + cb(command_handle, ErrorCode::Success as i32, reply_str_ptr, next.map(|a| a as i64).unwrap_or(-1)); trace!("api::parse_get_utxo_response_handler << result: {:?}", ErrorCode::Success); return ErrorCode::Success as i32; } @@ -939,6 +946,73 @@ pub extern fn free_parsed_state_proof(sp: *const c_char) -> i32 { return ErrorCode::Success as i32; } +#[no_mangle] +pub extern "C" fn sign_with_address_handler( + command_handle: i32, + wallet_handle: i32, + address: *const c_char, + message_raw: *const u8, + message_len: u32, + cb: Option +) -> i32 { + trace!("api::sign_with_address_handler called >> submitter_did (address) {:?}", secret!(&address)); + + match _check_address_is_vk(address) { + Ok(verkey) => { + unsafe { + let vk = CString::new(verkey).unwrap(); + indy_sys::crypto::indy_crypto_sign(command_handle, wallet_handle, vk.as_ptr(), message_raw, message_len, cb) + } + }, + Err(err) => { + if let Some(callback) = cb { + callback(command_handle, err as i32, ::std::ptr::null(), 0); + } + err as i32 + } + } +} + +pub extern "C" fn verify_with_address_handler( + command_handle: i32, + address: *const c_char, + message_raw: *const u8, + message_len: u32, + signature_raw: *const u8, + signature_len: u32, + cb: Option +) -> i32 { + trace!("api::verify_with_address_handler called >> submitter_did (address) {:?}", secret!(&address)); + + match _check_address_is_vk(address) { + Ok(verkey) => { + unsafe { + let vk = CString::new(verkey).unwrap(); + indy_sys::crypto::indy_crypto_verify(command_handle, vk.as_ptr(), message_raw, message_len, signature_raw, signature_len, cb) + } + }, + Err(err) => { + if let Some(callback) = cb { + callback(command_handle, err as i32, false); + } + err as i32 + } + } +} + +fn _check_address_is_vk(address: *const c_char) -> Result { + match str_from_char_ptr(address) { + Some(s) => { + let unqualified = address::unqualified_address_from_address(s)?; + let verkey = address::verkey_from_unqualified_address(&unqualified)?; + Ok(verkey) + }, + None => { + Err(ErrorCode::CommonInvalidStructure) + } + } +} + /** exported method indy-sdk will call for us to register our payment methods with indy-sdk @@ -980,6 +1054,8 @@ pub extern fn sovtoken_init() -> i32 { Some(parse_get_txn_fees_response_handler), Some(build_verify_req_handler), Some(parse_verify_response_handler), + Some(sign_with_address_handler), + Some(verify_with_address_handler), cb, ) ) diff --git a/libsovtoken/src/lib.rs b/libsovtoken/src/lib.rs index 6034d603f..f7a7f62ae 100644 --- a/libsovtoken/src/lib.rs +++ b/libsovtoken/src/lib.rs @@ -14,7 +14,6 @@ extern crate libc; extern crate openssl; extern crate rand; extern crate serde; -extern crate sodiumoxide; extern crate sha2; extern crate time; // ------------------------------------------ @@ -48,4 +47,4 @@ pub mod api; pub mod logic; pub mod libraries; -pub use indy::{ErrorCode, IndyHandle}; \ No newline at end of file +pub use indy::{ErrorCode, IndyHandle}; diff --git a/libsovtoken/src/logic/config/get_utxo_config.rs b/libsovtoken/src/logic/config/get_utxo_config.rs index 4fea5c1bb..f853b5ea1 100644 --- a/libsovtoken/src/logic/config/get_utxo_config.rs +++ b/libsovtoken/src/logic/config/get_utxo_config.rs @@ -19,16 +19,19 @@ use logic::address::verkey_from_unqualified_address; pub struct GetUtxoOperationRequest { address : String, #[serde(rename = "type")] - req_type: String + req_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + from: Option } impl GetUtxoOperationRequest { - pub fn new(address : String) -> Request { + pub fn new(address : String, from: Option) -> Request { let unqualified_address: String = strip_qualifier_from_address(&address); let identifier = verkey_from_unqualified_address(&unqualified_address).ok(); let req = GetUtxoOperationRequest { address : unqualified_address, req_type : GET_UTXO.to_string(), + from }; return Request::new(req, identifier); } @@ -48,7 +51,7 @@ mod get_utxo_config_tests { let ver_key: String = "EFfodscoymgdJDuM885uEWmgCcA25P6VR6TjVqsYZLW3".to_string(); let payment_address: String = qualified_address_from_verkey(&ver_key).unwrap(); - let utxo_request = GetUtxoOperationRequest::new(String::from(payment_address)); + let utxo_request = GetUtxoOperationRequest::new(String::from(payment_address), None); trace!("utxo_request => {:?}", utxo_request); diff --git a/libsovtoken/src/logic/parsers/common.rs b/libsovtoken/src/logic/parsers/common.rs index 9953094ba..4818cf72e 100644 --- a/libsovtoken/src/logic/parsers/common.rs +++ b/libsovtoken/src/logic/parsers/common.rs @@ -135,7 +135,32 @@ pub struct KeyValuesSubTrieData { */ #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct KeyValueSimpleData { - pub kvs: Vec<(String /* key */, Option)> + pub kvs: Vec<(String /* key */, Option)>, + #[serde(default)] + pub verification_type: KeyValueSimpleDataVerificationType +} + +/** + Options of common state proof check process +*/ +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[serde(tag = "type")] +pub enum KeyValueSimpleDataVerificationType { + Simple, + NumericalSuffixAscendingNoGaps(NumericalSuffixAscendingNoGapsData) +} + +impl Default for KeyValueSimpleDataVerificationType { + fn default() -> Self { + KeyValueSimpleDataVerificationType::Simple + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct NumericalSuffixAscendingNoGapsData { + pub from: Option, + pub next: Option, + pub prefix: String } /** @@ -198,6 +223,20 @@ mod common_tests { assert_eq!(return_error, ErrorCode::CommonInvalidStructure); } + #[test] + fn test_deserialize_vertype_simple() { + let obj: KeyValuesInSP = KeyValuesInSP::Simple(KeyValueSimpleData { + kvs: vec![("a".to_string(), Some("b".to_string()))], + verification_type: KeyValueSimpleDataVerificationType::NumericalSuffixAscendingNoGaps(NumericalSuffixAscendingNoGapsData { + from: Some(1), + next: Some(2), + prefix: "abc".to_string() + }) + }); + let json = serde_json::to_string(&obj).unwrap(); + let _vertype: KeyValuesInSP = serde_json::from_str(&json).unwrap(); + } + #[test] fn test_extraction_with_invalid_reply_json() { let invalid_json = r#"{ "some_key : "value"}"#; diff --git a/libsovtoken/src/logic/parsers/parse_get_txn_fees.rs b/libsovtoken/src/logic/parsers/parse_get_txn_fees.rs index 41c7fdcfa..3a13fcac4 100644 --- a/libsovtoken/src/logic/parsers/parse_get_txn_fees.rs +++ b/libsovtoken/src/logic/parsers/parse_get_txn_fees.rs @@ -14,6 +14,7 @@ use utils::json_conversion::JsonDeserialize; use utils::ffi_support::c_pointer_from_string; use utils::constants::txn_fields::FEES; use logic::type_aliases::{ProtocolVersion, TokenAmount, ReqId}; +use logic::parsers::common::KeyValueSimpleDataVerificationType; /** Structure for parsing GET_FEES request @@ -78,7 +79,8 @@ pub fn get_fees_state_proof_extractor(reply_from_node: *const c_char, parsed_sp: // TODO: Make sure JSON serialisation preserves order let kvs_to_verify = KeyValuesInSP::Simple(KeyValueSimpleData { - kvs: vec![(base64::encode(FEES), Some(fees.to_string()))] + kvs: vec![(base64::encode(FEES), Some(fees.to_string()))], + verification_type: KeyValueSimpleDataVerificationType::Simple }); let proof_nodes = match state_proof.proof_nodes { Some(o) => o, @@ -119,6 +121,7 @@ mod parse_fees_responses_test { use serde_json; use std::ffi::CString; use utils::ffi_support::string_from_char_ptr; + use logic::parsers::common::KeyValueSimpleDataVerificationType; #[test] fn success_parse_fees_from_reply_response() { @@ -207,7 +210,7 @@ mod parse_fees_responses_test { let expected_parsed_sp = vec![ParsedSP { proof_nodes: String::from("29qFIGZlZXOT0pF7IjEiOjQsIjEwMDAxIjo4fQ=="), root_hash: String::from("5BU5Rc3sRtTJB6tVprGiTSqiRaa9o6ei11MjH4Vu16ms"), - kvs_to_verify: KeyValuesInSP::Simple(KeyValueSimpleData { kvs: vec![(base64::encode("fees"), Some(json!({"1": 4, "10001": 8}).to_string()))] }), + kvs_to_verify: KeyValuesInSP::Simple(KeyValueSimpleData { kvs: vec![(base64::encode("fees"), Some(json!({"1": 4, "10001": 8}).to_string()))], verification_type: KeyValueSimpleDataVerificationType::Simple }), multi_signature: json!({ "participants": ["Gamma", "Delta", "Beta"], "value": {"timestamp": 1530059419, "state_root_hash": "5BU5Rc3sRtTJB6tVprGiTSqiRaa9o6ei11MjH4Vu16ms", "ledger_id": 2, "txn_root_hash": "AKboMiJZJm247Sa7GsKQo5Ba8ukgxTQ3DsLc2pyVuDkU", "pool_state_root_hash": "J3ATG63R2JKHDCdpKpQf81FTNyQg2Vgz7Pu1ZHZw6zNy"}, "signature": "Qk67ePVhxdjHivAf8H4Loy1hN5zfb1dq79VSJKYx485EAXmj44PASpp8gj2faysdN8CNzSoUVvXgd3U4P2CA7VkwD7FHKUuviAFJfRQ68FnpUS8hVuqn6PAuv9RGUobohcJnKJ8CVKxr5i3Zn2JNXbk7AqeYRZQ2egq8fdoP3woPW7" diff --git a/libsovtoken/src/logic/parsers/parse_get_utxo_response.rs b/libsovtoken/src/logic/parsers/parse_get_utxo_response.rs index 7cff8afae..a36d670ba 100644 --- a/libsovtoken/src/logic/parsers/parse_get_utxo_response.rs +++ b/libsovtoken/src/logic/parsers/parse_get_utxo_response.rs @@ -1,6 +1,5 @@ //! types used for parse_get_utxo_response_handler -use base64; use ErrorCode; use libc::c_char; use logic::parsers::common::{ResponseOperations, TXO, StateProof, ParsedSP, KeyValuesInSP, @@ -11,6 +10,9 @@ use logic::address; use serde_json; use utils::constants::txn_fields::OUTPUTS; use utils::ffi_support::c_pointer_from_string; +use logic::parsers::common::KeyValueSimpleDataVerificationType; +use utils::constants::txn_fields::{FROM, NEXT, ADDRESS}; +use logic::parsers::common::NumericalSuffixAscendingNoGapsData; type UTXOs = Vec; @@ -48,6 +50,7 @@ pub struct ParseGetUtxoResponseResult { pub identifier: String, pub req_id : ReqId, pub outputs : UTXOs, + pub next : Option, #[serde(rename = "state_proof", skip_serializing_if = "Option::is_none")] pub state_proof : Option } @@ -72,7 +75,7 @@ pub struct UTXO { /** for parse_get_utxo_response_handler output parameter utxo_json */ -pub type ParseGetUtxoReply = Vec; +pub type ParseGetUtxoReply = (Vec, Option); /** Converts ParseGetUtxoResponse (which should be input via indy-sdk) to ParseGetUtxoReply @@ -94,7 +97,7 @@ pub fn from_response(base : ParseGetUtxoResponse) -> Result { let reason = base.reason.ok_or(ErrorCode::CommonInvalidStructure)?; @@ -105,7 +108,7 @@ pub fn from_response(base : ParseGetUtxoResponse) -> Result String { - base64::encode(&format!("{}:{}", address, seq_no)) + format!("{}:{}", address, seq_no) } pub fn get_utxo_state_proof_extractor(reply_from_node: *const c_char, parsed_sp: *mut *const c_char) -> ErrorCode { @@ -114,7 +117,6 @@ pub fn get_utxo_state_proof_extractor(reply_from_node: *const c_char, parsed_sp: Ok((r, s)) => (r, s), Err(_) => return ErrorCode::CommonInvalidStructure }; - // TODO: No validation of outputs being done. This has to fixed by creating an `Address` with // a single private field called `address` and with implementation defining `new` and a getter. // The `new` method will do the validation. @@ -130,6 +132,37 @@ pub fn get_utxo_state_proof_extractor(reply_from_node: *const c_char, parsed_sp: None => return ErrorCode::CommonInvalidStructure }; + let from: Option = match result.get(FROM) { + Some(from) => { + let from: u64 = match serde_json::from_value(from.to_owned()) { + Ok(o) => o, + Err(_) => return ErrorCode::CommonInvalidStructure + }; + Some(from) + }, + None => None + }; + let next: Option = match result.get(NEXT) { + Some(next) => { + let next: u64 = match serde_json::from_value(next.to_owned()) { + Ok(o) => o, + Err(_) => return ErrorCode::CommonInvalidStructure + }; + Some(next) + }, + None => None + }; + let prefix: String = match result.get(ADDRESS) { + Some(prefix) => { + let prefix: String = match serde_json::from_value(prefix.to_owned()) { + Ok(p) => p, + Err(_) => return ErrorCode::CommonInvalidStructure + }; + prefix + ":" + }, + None => return ErrorCode::CommonInvalidStructure + }; + let mut kvs: Vec<(String, Option)> = Vec::new(); for output in outputs { @@ -137,7 +170,10 @@ pub fn get_utxo_state_proof_extractor(reply_from_node: *const c_char, parsed_sp: Some(output.amount.to_string()))); } - let kvs_to_verify = KeyValuesInSP::Simple(KeyValueSimpleData { kvs }); + let kvs_to_verify = KeyValuesInSP::Simple(KeyValueSimpleData { + kvs, + verification_type: KeyValueSimpleDataVerificationType::NumericalSuffixAscendingNoGaps(NumericalSuffixAscendingNoGapsData {from, next, prefix}) + }); let proof_nodes = match state_proof.proof_nodes { Some(o) => o, None => return ErrorCode::CommonInvalidStructure @@ -193,6 +229,7 @@ mod parse_get_utxo_responses_tests { [ ["dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q", 1, 40] ], + "next": 1, "state_proof": { "multi_signature": @@ -259,6 +296,7 @@ mod parse_get_utxo_responses_tests { identifier, req_id: 123457890, outputs, + next: None, state_proof: Some(state_proof) }; @@ -269,8 +307,9 @@ mod parse_get_utxo_responses_tests { reason: None, }; - let reply: ParseGetUtxoReply = from_response(response).unwrap(); + let (reply, next) = from_response(response).unwrap(); + assert!(next.is_none()); assert_eq!(outputs_len, reply.len()); } @@ -307,6 +346,7 @@ mod parse_get_utxo_responses_tests { identifier, req_id: 123457890, outputs, + next: None, state_proof: Some(state_proof) }; @@ -317,8 +357,9 @@ mod parse_get_utxo_responses_tests { reason: None, }; - let reply: ParseGetUtxoReply = from_response(response).unwrap(); + let (reply, next) = from_response(response).unwrap(); + assert!(next.is_none()); assert_eq!(outputs_len, reply.len()); } @@ -329,6 +370,8 @@ mod parse_get_utxo_responses_tests { let response: ParseGetUtxoResponse = ParseGetUtxoResponse::from_json(PARSE_GET_UTXO_RESPONSE_JSON).unwrap(); assert_eq!(response.op, ResponseOperations::REPLY); + let res = response.result.unwrap(); + assert_eq!(res.next, Some(1)); } // this test passes when the valid JSON defined in PARSE_GET_UTXO_RESPONSE_JSON is correctly serialized into @@ -347,7 +390,7 @@ mod parse_get_utxo_responses_tests { let address = "dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q"; let seq_no = 32; let key = get_utxo_state_key(&address, seq_no); - assert_eq!(key, base64::encode("dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q:32")); + assert_eq!(key, "dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q:32"); } #[test] @@ -538,27 +581,137 @@ mod parse_get_utxo_responses_tests { proof_nodes: String::from("+QHF4hOgCBgvwaPO/KIJjOyzhA9dx8yXqPgqKY9sqKPIAZgHujTsgICA2cQggsExxCCCwTGAgICAgICAgICAgICAgICAgICAgICAgICAgICCwTH4VrQAJqUzRQSFdRSktjYXdSeGRXNkdWc2puWkJhMWVjR2RDc3NuN0toV1lKWkdUWGdMN0VzOjoEREJ+KujHB//IMaixsQMlj9+4DLVQHzu4WJczS7X8ED+G2AoMk4QTH20sQPm8C23HQjM7dFR6HIi99DdtySfD9VnTGsoDyHJeCRAIf9srqEpWYrQ1nq9jBE67eMCBK+ewpvMu2UxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTGAgICAgICA+DnEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMYCAgICAgID4cYCAgKDXpPuRat5Zsa2SRHuGjslN7/QaBcZvwSae8dKLWybem4CAoAzQlchQvYEDh57N1ilzx/G5Gj05oHksuf4nOK/6KGqfoF/sqT9NVI/hFuFzQ8LUFSymIKOpOG9nepF29+TB2bWOgICAgICAgICA"), root_hash: String::from("EuHbjY9oaqAXBDxLBM4KcBLASs7RK35maoHjQMbDvmw1"), kvs_to_verify: KeyValuesInSP::Simple(KeyValueSimpleData { kvs: vec![ - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:4")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:16")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:6")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:17")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:20")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:8")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:13")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:3")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:19")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:11")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:18")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:5")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:15")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:7")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:10")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:9")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:12")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:2")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:21")), Some("1".to_string())), - (String::from(base64::encode("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:14")), Some("1".to_string())) - ] }), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:4"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:16"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:6"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:17"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:20"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:8"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:13"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:3"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:19"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:11"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:18"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:5"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:15"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:7"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:10"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:9"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:12"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:2"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:21"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:14"), Some("1".to_string())) + ], + verification_type: KeyValueSimpleDataVerificationType::NumericalSuffixAscendingNoGaps(NumericalSuffixAscendingNoGapsData {from: None, next: None, prefix: "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:".to_string()}) + }), + multi_signature: json!({ + "participants": ["Beta", "Delta", "Gamma"], + "signature": "Qz5rGskoz8xuRLdaAoA5m1He4dBbfg3RBKQ5wmvRper4yTmuKEbbXZ5jidVXYzrJymHcN3xiRYqDSkZ3JbggzWj4NQATsYRSPSc6xP768vAMHA1iNSgxhGV5uW47MSeYihrV9e9YLDjYyzuyUHkBhbWrxMoo8jtowvDMQMZ7qHMhfd", + "value": { + "pool_state_root_hash": "DyMrH7X17UW4k9KcsAUPLKL479dsZ6dvj3bvEAEyYNxZ", + "ledger_id": 1001, + "state_root_hash": "EuHbjY9oaqAXBDxLBM4KcBLASs7RK35maoHjQMbDvmw1", + "txn_root_hash": "9i1knJtwTD3NToyCrHoh93HBrTnaq6CeL7F1KtZUBaBz", + "timestamp": 1530212673 + } + }), + }]; + + let json_str = string_from_char_ptr(new_str_ptr).unwrap(); + let parsed_sp: Vec = serde_json::from_str(&json_str).unwrap(); + assert_eq!(parsed_sp.len(), 1); + assert_eq!(parsed_sp[0].proof_nodes, expected_parsed_sp[0].proof_nodes); + assert_eq!(parsed_sp[0].root_hash, expected_parsed_sp[0].root_hash); + assert_eq!(parsed_sp[0].kvs_to_verify, expected_parsed_sp[0].kvs_to_verify); + assert_eq!(parsed_sp[0].multi_signature, expected_parsed_sp[0].multi_signature); + } + + #[test] + fn test_parse_state_proof_with_from_and_next() { + let valid_json = r#"{ + "op": "REPLY", + "protocol_version": 1, + "result": + { + "type": "10002", + "address": "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + "identifier": "6ouriXMZkLeHsuXrN1X1fd", + "reqId": 15424, + "from": 4, + "next": 9, + "outputs":[ + [ + "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + 4, + 1 + ], + [ + "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + 5, + 1 + ], + [ + "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + 6, + 1 + ], + [ + "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + 7, + 1 + ], + [ + "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es", + 8, + 1 + ] + ], + "state_proof":{ + "root_hash":"EuHbjY9oaqAXBDxLBM4KcBLASs7RK35maoHjQMbDvmw1", + "proof_nodes":"+QHF4hOgCBgvwaPO/KIJjOyzhA9dx8yXqPgqKY9sqKPIAZgHujTsgICA2cQggsExxCCCwTGAgICAgICAgICAgICAgICAgICAgICAgICAgICCwTH4VrQAJqUzRQSFdRSktjYXdSeGRXNkdWc2puWkJhMWVjR2RDc3NuN0toV1lKWkdUWGdMN0VzOjoEREJ+KujHB//IMaixsQMlj9+4DLVQHzu4WJczS7X8ED+G2AoMk4QTH20sQPm8C23HQjM7dFR6HIi99DdtySfD9VnTGsoDyHJeCRAIf9srqEpWYrQ1nq9jBE67eMCBK+ewpvMu2UxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTGAgICAgICA+DnEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMYCAgICAgID4cYCAgKDXpPuRat5Zsa2SRHuGjslN7/QaBcZvwSae8dKLWybem4CAoAzQlchQvYEDh57N1ilzx/G5Gj05oHksuf4nOK/6KGqfoF/sqT9NVI/hFuFzQ8LUFSymIKOpOG9nepF29+TB2bWOgICAgICAgICA", + "multi_signature":{ + "signature":"Qz5rGskoz8xuRLdaAoA5m1He4dBbfg3RBKQ5wmvRper4yTmuKEbbXZ5jidVXYzrJymHcN3xiRYqDSkZ3JbggzWj4NQATsYRSPSc6xP768vAMHA1iNSgxhGV5uW47MSeYihrV9e9YLDjYyzuyUHkBhbWrxMoo8jtowvDMQMZ7qHMhfd", + "participants":[ + "Beta", + "Delta", + "Gamma" + ], + "value":{ + "pool_state_root_hash":"DyMrH7X17UW4k9KcsAUPLKL479dsZ6dvj3bvEAEyYNxZ", + "ledger_id":1001, + "state_root_hash":"EuHbjY9oaqAXBDxLBM4KcBLASs7RK35maoHjQMbDvmw1", + "txn_root_hash":"9i1knJtwTD3NToyCrHoh93HBrTnaq6CeL7F1KtZUBaBz", + "timestamp":1530212673 + } + } + } + } + }"#; + + let json_str = CString::new(valid_json).unwrap(); + let json_str_ptr = json_str.as_ptr(); + + let mut new_str_ptr = ::std::ptr::null(); + let return_error = get_utxo_state_proof_extractor(json_str_ptr, &mut new_str_ptr); + assert_eq!(return_error, ErrorCode::Success); + + let expected_parsed_sp: Vec = vec![ParsedSP { + proof_nodes: String::from("+QHF4hOgCBgvwaPO/KIJjOyzhA9dx8yXqPgqKY9sqKPIAZgHujTsgICA2cQggsExxCCCwTGAgICAgICAgICAgICAgICAgICAgICAgICAgICCwTH4VrQAJqUzRQSFdRSktjYXdSeGRXNkdWc2puWkJhMWVjR2RDc3NuN0toV1lKWkdUWGdMN0VzOjoEREJ+KujHB//IMaixsQMlj9+4DLVQHzu4WJczS7X8ED+G2AoMk4QTH20sQPm8C23HQjM7dFR6HIi99DdtySfD9VnTGsoDyHJeCRAIf9srqEpWYrQ1nq9jBE67eMCBK+ewpvMu2UxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTGAgICAgICA+DnEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMcQggsExxCCCwTHEIILBMYCAgICAgID4cYCAgKDXpPuRat5Zsa2SRHuGjslN7/QaBcZvwSae8dKLWybem4CAoAzQlchQvYEDh57N1ilzx/G5Gj05oHksuf4nOK/6KGqfoF/sqT9NVI/hFuFzQ8LUFSymIKOpOG9nepF29+TB2bWOgICAgICAgICA"), + root_hash: String::from("EuHbjY9oaqAXBDxLBM4KcBLASs7RK35maoHjQMbDvmw1"), + kvs_to_verify: KeyValuesInSP::Simple(KeyValueSimpleData { kvs: vec![ + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:4"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:5"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:6"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:7"), Some("1".to_string())), + (String::from("2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:8"), Some("1".to_string())), + ], + verification_type: KeyValueSimpleDataVerificationType::NumericalSuffixAscendingNoGaps( + NumericalSuffixAscendingNoGapsData { + from: Some(4), + next: Some(9), + prefix: "2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es:".to_string() + }) + }), multi_signature: json!({ "participants": ["Beta", "Delta", "Gamma"], "signature": "Qz5rGskoz8xuRLdaAoA5m1He4dBbfg3RBKQ5wmvRper4yTmuKEbbXZ5jidVXYzrJymHcN3xiRYqDSkZ3JbggzWj4NQATsYRSPSc6xP768vAMHA1iNSgxhGV5uW47MSeYihrV9e9YLDjYyzuyUHkBhbWrxMoo8jtowvDMQMZ7qHMhfd", diff --git a/libsovtoken/src/utils/constants/general.rs b/libsovtoken/src/utils/constants/general.rs index c58c00734..f763180a8 100644 --- a/libsovtoken/src/utils/constants/general.rs +++ b/libsovtoken/src/utils/constants/general.rs @@ -19,3 +19,6 @@ Defines a callback to communicate results to Indy-sdk as type */ pub type JsonCallback = Option; pub type JsonCallbackUnwrapped = extern fn(command_handle: i32, err: i32, json_pointer: *const c_char) -> i32; + +pub type JsonI64Callback = Option; +pub type JsonI64CallbackUnwrapped = extern fn(command_handle: i32, err: i32, json_pointer: *const c_char, num: i64) -> i32; diff --git a/libsovtoken/src/utils/constants/txn_fields.rs b/libsovtoken/src/utils/constants/txn_fields.rs index c9523b9f8..f90dfd264 100644 --- a/libsovtoken/src/utils/constants/txn_fields.rs +++ b/libsovtoken/src/utils/constants/txn_fields.rs @@ -5,5 +5,8 @@ pub const FEES: &'static str = "fees"; pub const INPUTS: &'static str = "inputs"; pub const OUTPUTS: &'static str = "outputs"; +pub const FROM: &'static str = "from"; +pub const NEXT: &'static str = "next"; +pub const ADDRESS: &'static str = "address"; pub const RESULT: &'static str = "result"; pub const STATE_PROOF: &'static str = "state_proof"; \ No newline at end of file diff --git a/libsovtoken/src/utils/sequence.rs b/libsovtoken/src/utils/sequence.rs index b1cfdf7f6..077dae6e2 100644 --- a/libsovtoken/src/utils/sequence.rs +++ b/libsovtoken/src/utils/sequence.rs @@ -1,13 +1,13 @@ -use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +use std::sync::atomic::{AtomicUsize, Ordering}; pub struct SequenceUtils {} lazy_static! { - static ref IDS_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; //TODO use AtomicI32 + static ref IDS_COUNTER: AtomicUsize = AtomicUsize::new(4); //TODO use AtomicI32 } impl SequenceUtils { pub fn get_next_id() -> i32 { (IDS_COUNTER.fetch_add(1, Ordering::SeqCst) + 1) as i32 } -} \ No newline at end of file +} diff --git a/libsovtoken/src/utils/test/callbacks.rs b/libsovtoken/src/utils/test/callbacks.rs index ec9ed4174..bdc768311 100644 --- a/libsovtoken/src/utils/test/callbacks.rs +++ b/libsovtoken/src/utils/test/callbacks.rs @@ -5,7 +5,7 @@ */ use ErrorCode; -use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use std::sync::mpsc::{channel, Receiver}; use std::collections::HashMap; @@ -19,7 +19,7 @@ type Callbacks = Mutex>>; macro_rules! closure_cb { ($closure:ident, $($name:ident : $ntype:ty),*) => {{ lazy_static! { - static ref COMMAND_HANDLE_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; + static ref COMMAND_HANDLE_COUNTER: AtomicUsize = AtomicUsize::new(4); static ref CALLBACKS: Callbacks = Default::default(); } @@ -54,4 +54,20 @@ pub fn cb_ec_string() -> ( let (command_handle, callback) = closure_cb!(closure, char_value: *const c_char); (receiver, command_handle, Some(callback)) -} \ No newline at end of file +} + +pub fn cb_ec_string_i64() -> ( + Receiver<(ErrorCode, (String, i64))>, + i32, + Option i32>) { + let (sender, receiver) = channel(); + + let closure = Box::new(move|error_code, c_str, num| { + let string = unsafe { CStr::from_ptr(c_str).to_str().unwrap().to_string() }; + sender.send((ErrorCode::from(error_code), (string, num))).unwrap(); + }); + + let (command_handle, callback) = closure_cb!(closure, char_value: *const c_char, num: i64); + + (receiver, command_handle, Some(callback)) +} diff --git a/libsovtoken/tests/add_fees_for_nym.rs b/libsovtoken/tests/add_fees_for_nym.rs index f2a804cef..d375c0003 100644 --- a/libsovtoken/tests/add_fees_for_nym.rs +++ b/libsovtoken/tests/add_fees_for_nym.rs @@ -198,12 +198,13 @@ pub fn build_and_submit_nym_with_fees_and_get_utxo() { assert_eq!(parsed_resp_json[0].get("amount").unwrap().as_u64().unwrap(), 9); assert_eq!(parsed_resp_json[0].get("recipient").unwrap().as_str().unwrap(), addresses[0]); - let utxos = get_utxo::send_get_utxo_request(&wallet, pool_handle, dids[0], &addresses[0]); + let (utxos, next) = get_utxo::send_get_utxo_request(&wallet, pool_handle, dids[0], &addresses[0], None); let utxo = &utxos[0]; assert_eq!(utxos.len(), 1); assert_eq!(utxo.payment_address, addresses[0]); assert_eq!(utxo.amount, 9); + assert_eq!(next, None); } #[test] diff --git a/libsovtoken/tests/build_get_utxo_request_handler_test.rs b/libsovtoken/tests/build_get_utxo_request_handler_test.rs index e489c6521..6555944c4 100644 --- a/libsovtoken/tests/build_get_utxo_request_handler_test.rs +++ b/libsovtoken/tests/build_get_utxo_request_handler_test.rs @@ -23,7 +23,7 @@ use sovtoken::utils::ffi_support::c_pointer_from_str; use sovtoken::{ErrorCode, IndyHandle}; // ***** HELPER METHODS ***** -fn build_get_payment_sources_request(wallet_handle: IndyHandle, did: &str, payment_address: &str) -> Result { +fn build_get_payment_sources_request(wallet_handle: IndyHandle, did: &str, payment_address: &str, from:Option) -> Result { let (receiver, command_handle, cb) = callbacks::cb_ec_string(); let error_code = sovtoken::api::build_get_utxo_request_handler( @@ -31,14 +31,15 @@ fn build_get_payment_sources_request(wallet_handle: IndyHandle, did: &str, payme wallet_handle, c_pointer_from_str(did), c_pointer_from_str(payment_address), + from.map(|a| a as i64).unwrap_or(-1), cb ); return ResultHandler::one(ErrorCode::from(error_code), receiver); } -fn parse_get_payment_sources_response(res: &str) -> Result { - let (receiver, command_handle, cb) = callbacks::cb_ec_string(); +fn parse_get_payment_sources_response(res: &str) -> Result<(String, Option), ErrorCode> { + let (receiver, command_handle, cb) = callbacks::cb_ec_string_i64(); let error_code = sovtoken::api::parse_get_utxo_response_handler( command_handle, @@ -46,7 +47,8 @@ fn parse_get_payment_sources_response(res: &str) -> Result { cb ); - return ResultHandler::one(ErrorCode::from(error_code), receiver); + return ResultHandler::one(ErrorCode::from(error_code), receiver) + .map(|(arg1, arg2)| (arg1, if arg2 == -1 {None} else {Some(arg2 as u64)})); } // ***** HELPER TEST DATA ***** @@ -60,7 +62,7 @@ const ADDRESS: &str = "pay:sov:dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q // receive an error when no callback is provided #[test] fn get_utxo_errors_with_no_call_back() { - let return_error = sovtoken::api::build_get_utxo_request_handler(COMMAND_HANDLE, WALLET_ID, ptr::null(), ptr::null(), None); + let return_error = sovtoken::api::build_get_utxo_request_handler(COMMAND_HANDLE, WALLET_ID, ptr::null(), ptr::null(), -1, None); assert_eq!(return_error, ErrorCode::CommonInvalidStructure as i32, "Expecting Callback for 'build_get_utxo_request_handler'"); } @@ -68,7 +70,7 @@ fn get_utxo_errors_with_no_call_back() { // a error is returned when no config is provided #[test] fn get_utxo_errors_with_no_payment_address() { - let return_error = sovtoken::api::build_get_utxo_request_handler(COMMAND_HANDLE, WALLET_ID, ptr::null(), ptr::null(), CB); + let return_error = sovtoken::api::build_get_utxo_request_handler(COMMAND_HANDLE, WALLET_ID, ptr::null(), ptr::null(), -1,CB); assert_eq!(return_error, ErrorCode::CommonInvalidStructure as i32, "Expecting outputs_json for 'build_fees_txn_handler'"); } @@ -80,7 +82,23 @@ fn build_get_utxo_json() { "address": "dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q" }); - let request = build_get_payment_sources_request(WALLET_ID, &did, &ADDRESS).unwrap(); + let request = build_get_payment_sources_request(WALLET_ID, &did, &ADDRESS, None).unwrap(); + + let request_value: serde_json::value::Value = serde_json::from_str(&request).unwrap(); + + assert_eq!(&expected_operation, request_value.get("operation").unwrap()); +} + +#[test] +fn build_get_utxo_json_with_from() { + let did = bs58::encode("1234567890123456").into_string(); + let expected_operation = json!({ + "type": "10002", + "address": "dctKSXBbv2My3TGGUgTFjkxu1A9JM3Sscd5FydY4dkxnfwA7q", + "from": 1 + }); + + let request = build_get_payment_sources_request(WALLET_ID, &did, &ADDRESS, Some(1)).unwrap(); let request_value: serde_json::value::Value = serde_json::from_str(&request).unwrap(); @@ -101,15 +119,36 @@ pub fn build_and_submit_get_utxo_request() { let pool_handle = setup.pool_handle; let dids = setup.trustees.dids(); - let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0]).unwrap(); + let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0], None).unwrap(); let res = indy::ledger::sign_and_submit_request(pool_handle, wallet.handle, dids[0], &get_utxo_req).wait().unwrap(); - let res = parse_get_payment_sources_response(&res).unwrap(); + let (res, next) = parse_get_payment_sources_response(&res).unwrap(); let res_parsed: Vec = serde_json::from_str(&res).unwrap(); assert_eq!(res_parsed.len(), 1); let utxo = res_parsed.get(0).unwrap().as_object().unwrap(); assert_eq!(utxo.get("paymentAddress").unwrap().as_str().unwrap(), payment_addresses[0]); assert_eq!(utxo.get("amount").unwrap().as_u64().unwrap(), 10); + assert!(next.is_none()); +} + +#[test] +pub fn build_and_submit_get_utxo_request_negative() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 4, + num_users: 0, + mint_tokens: Some(vec![10]), + fees: None + }); + let payment_addresses = &setup.addresses; + let pool_handle = setup.pool_handle; + let dids = setup.trustees.dids(); + + let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0], Some(-15)).unwrap(); + let res = indy::ledger::sign_and_submit_request(pool_handle, wallet.handle, dids[0], &get_utxo_req).wait().unwrap(); + let res = parse_get_payment_sources_response(&res); + assert_eq!(res.unwrap_err(), ErrorCode::CommonInvalidStructure); } #[test] @@ -126,12 +165,13 @@ pub fn build_and_submit_get_utxo_request_no_utxo() { let pool_handle = setup.pool_handle; let dids = setup.trustees.dids(); - let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0]).unwrap(); + let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0], None).unwrap(); let res = indy::ledger::sign_and_submit_request(pool_handle, wallet.handle, dids[0], &get_utxo_req).wait().unwrap(); - let res = parse_get_payment_sources_response(&res).unwrap(); + let (res, next) = parse_get_payment_sources_response(&res).unwrap(); let res_parsed: Vec = serde_json::from_str(&res).unwrap(); assert_eq!(res_parsed.len(), 0); + assert_eq!(next, None); } #[test] @@ -147,7 +187,7 @@ pub fn payment_address_is_identifier() { let payment_addresses = &setup.addresses; let dids = setup.trustees.dids(); - let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0]).unwrap(); + let get_utxo_req = build_get_payment_sources_request(wallet.handle, dids[0], &payment_addresses[0], None).unwrap(); let req: serde_json::Value = serde_json::from_str(&get_utxo_req).unwrap(); let identifier = req.as_object().unwrap().get("identifier").unwrap().as_str().unwrap(); let unqualified_addr = strip_qualifier_from_address(&payment_addresses[0]); diff --git a/libsovtoken/tests/build_mint_txn_handler_test.rs b/libsovtoken/tests/build_mint_txn_handler_test.rs index 581e9ef2a..bc7c39c99 100644 --- a/libsovtoken/tests/build_mint_txn_handler_test.rs +++ b/libsovtoken/tests/build_mint_txn_handler_test.rs @@ -198,9 +198,10 @@ pub fn build_and_submit_mint_txn_works() { let result = indy::ledger::submit_request(pool_handle, &mint_req).wait().unwrap(); let response = ParseMintResponse::from_json(&result).unwrap(); assert_eq!(response.op, ResponseOperations::REPLY); - let utxos = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0]); + let (utxos, next) = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0], None); assert_eq!(utxos[0].amount, 5); assert_eq!(utxos[0].payment_address, payment_addresses[0]); + assert_eq!(next, None); } #[test] @@ -253,9 +254,10 @@ pub fn build_and_submit_mint_txn_works_with_empty_did() { let result = indy::ledger::submit_request(pool_handle, &mint_req).wait().unwrap(); let response = ParseMintResponse::from_json(&result).unwrap(); assert_eq!(response.op, ResponseOperations::REPLY); - let utxos = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0]); + let (utxos, next) = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0], None); assert_eq!(utxos[0].amount, 5); assert_eq!(utxos[0].payment_address, payment_addresses[0]); + assert!(next.is_none()) } #[test] @@ -311,10 +313,11 @@ pub fn build_and_submit_mint_txn_works_for_double_send_mint() { let result = indy::ledger::submit_request(pool_handle, &mint_req).wait().unwrap(); let response = ParseMintResponse::from_json(&result).unwrap(); assert_eq!(response.op, ResponseOperations::REPLY); - let utxos = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0]); + let (utxos, next) = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0], None); assert_eq!(utxos.len(), 1); assert_eq!(utxos[0].amount, 5); assert_eq!(utxos[0].payment_address, payment_addresses[0]); + assert!(next.is_none()); } #[test] @@ -363,7 +366,8 @@ fn mint_10_billion_tokens() { trace!("{:?}", &response); assert_eq!(response.op, ResponseOperations::REPLY); - let utxos = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0]); + let (utxos, next) = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, &dids[0], &payment_addresses[0], None); assert_eq!(utxos[0].amount, tokens); assert_eq!(utxos[0].payment_address, payment_addresses[0]); + assert_eq!(next, None); } diff --git a/libsovtoken/tests/build_payment_req_handler_test.rs b/libsovtoken/tests/build_payment_req_handler_test.rs index 8f4b7d651..971f29775 100644 --- a/libsovtoken/tests/build_payment_req_handler_test.rs +++ b/libsovtoken/tests/build_payment_req_handler_test.rs @@ -427,7 +427,8 @@ pub fn build_and_submit_payment_req_with_spent_utxo() { assert_eq!(err, ErrorCode::PaymentSourceDoesNotExistError); //utxo should stay unspent! - let utxos = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, dids[0], &addresses[0]); + let (utxos, next) = utils::payment::get_utxo::send_get_utxo_request(&wallet, pool_handle, dids[0], &addresses[0], None); + assert_eq!(next, None); assert_eq!(utxos.len(), 2); let first_old = utxos[0].source == utxo; let second_old = utxos[1].source == utxo; diff --git a/libsovtoken/tests/sign_with_address_test.rs b/libsovtoken/tests/sign_with_address_test.rs new file mode 100644 index 000000000..ce0a0eab5 --- /dev/null +++ b/libsovtoken/tests/sign_with_address_test.rs @@ -0,0 +1,99 @@ +#[macro_use] extern crate serde_json; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate lazy_static; +extern crate libc; +extern crate sovtoken; +extern crate indyrs as indy; // lib-sdk project +extern crate bs58; + +mod utils; +use indy::future::Future; +use utils::wallet::Wallet; +use utils::setup::{Setup, SetupConfig}; +use indy::payments::{sign_with_address, verify_with_address}; +use indy::ErrorCode; + +#[test] +pub fn sign_with_address_works() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 1, + num_users: 0, + mint_tokens: None, + fees: None, + }); + + let addr = setup.addresses.get(0).unwrap(); + let msg = vec![1, 2, 3, 4]; + let sig = sign_with_address(wallet.handle, addr, msg.as_slice()).wait().unwrap(); + assert!(verify_with_address(addr, msg.as_slice(), sig.as_slice()).wait().unwrap()); +} + +#[test] +pub fn sign_with_address_fails_for_incorrect_signature() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 1, + num_users: 0, + mint_tokens: None, + fees: None, + }); + + let addr = setup.addresses.get(0).unwrap(); + let msg = vec![1, 2, 3, 4]; + let sig = sign_with_address(wallet.handle, addr, msg.as_slice()).wait().unwrap(); + assert_eq!(verify_with_address(addr, msg.as_slice(), &sig[..sig.len()-1]).wait().unwrap_err().error_code, ErrorCode::CommonInvalidStructure); +} + +#[test] +pub fn sign_with_address_fails_for_invalid_addr() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 1, + num_users: 0, + mint_tokens: None, + fees: None, + }); + + let addr = setup.addresses.get(0).unwrap(); + let msg = vec![1, 2, 3, 4]; + assert_eq!(sign_with_address(wallet.handle, &addr[..addr.len()-1], msg.as_slice()).wait().unwrap_err().error_code, ErrorCode::CommonInvalidStructure); +} + +#[test] +pub fn sign_with_address_fails_for_no_such_addr_in_wallet() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 1, + num_users: 0, + mint_tokens: None, + fees: None, + }); + let wallet_2 = Wallet::new(); + + let addr = setup.addresses.get(0).unwrap(); + let msg = vec![1, 2, 3, 4]; + //TODO: Should be WalletItemNotFound + assert_eq!(sign_with_address(wallet_2.handle, &addr[..addr.len()-1], msg.as_slice()).wait().unwrap_err().error_code, ErrorCode::CommonInvalidStructure); +} + +#[test] +pub fn verify_with_address_fails_for_invalid_address() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 1, + num_users: 0, + mint_tokens: None, + fees: None, + }); + + let addr = setup.addresses.get(0).unwrap(); + let msg = vec![1, 2, 3, 4]; + let sig = sign_with_address(wallet.handle, addr, msg.as_slice()).wait().unwrap(); + assert_eq!(verify_with_address(&addr[..addr.len()-1], msg.as_slice(), &sig[..sig.len()-1]).wait().unwrap_err().error_code, ErrorCode::CommonInvalidStructure); +} diff --git a/libsovtoken/tests/utils/payment/get_utxo.rs b/libsovtoken/tests/utils/payment/get_utxo.rs index 4f26e251c..023c5e1b9 100644 --- a/libsovtoken/tests/utils/payment/get_utxo.rs +++ b/libsovtoken/tests/utils/payment/get_utxo.rs @@ -8,14 +8,14 @@ use sovtoken::logic::parsers::parse_get_utxo_response::UTXO; use utils::wallet::Wallet; pub fn get_first_utxo_txo_for_payment_address(wallet: &Wallet, pool_handle: i32, did: &str, address: &str) -> String { - let mut utxos = send_get_utxo_request(wallet, pool_handle, did, address); + let (mut utxos, _) = send_get_utxo_request(wallet, pool_handle, did, address, None); let utxo = utxos.remove(0); utxo.source } -pub fn send_get_utxo_request(wallet: &Wallet, pool_handle: i32, did: &str, address: &str) -> Vec { - let (req, method) = indy::payments::build_get_payment_sources_request(wallet.handle, Some(did), address).wait().unwrap(); +pub fn send_get_utxo_request(wallet: &Wallet, pool_handle: i32, did: &str, address: &str, from: Option) -> (Vec, Option) { + let (req, method) = indy::payments::build_get_payment_sources_with_from_request(wallet.handle, Some(did), address, from).wait().unwrap(); let res = indy::ledger::sign_and_submit_request(pool_handle, wallet.handle, did, &req).wait().unwrap(); - let parsed_resp = indy::payments::parse_get_payment_sources_response(&method, &res).wait().unwrap(); - serde_json::from_str(&parsed_resp).unwrap() + let (parsed_resp, next) = indy::payments::parse_get_payment_sources_with_from_response(&method, &res).wait().unwrap(); + (serde_json::from_str(&parsed_resp).unwrap(), next) } \ No newline at end of file