Repository: ergoplatform/sigma-rust (language: rust)

Issue:
[100 SigmaUSD] Wasm and Swift bindings for the arbitrary message signing and verification
## Rust API (DONE)

### Implementation (DONE)

Add `wallet:signing::sign_message` with the following signature:
```rust
/// Sign arbitrary message under a key representing a statement provable via a sigma-protocol.
/// A statement can be a simple ProveDlog (PK) or a complex sigma conjectives tree
pub fn sign_message(prover: &dyn Prover, sigma_tree: SigmaBoolean, msg: &[u8],) -> Result<Vec<u8>, ProverError> 
```
which should just call the modified `Prover::generate_proof` with removed `ctx_ext` param and returning `ProofBytes` instead of `ProverResult` (should be constructed at the call sites):
```rust
    fn generate_proof(
        &self,
        sigmabool: SigmaBoolean,
        message: &[u8],
        hints_bag: &HintsBag,
    ) -> Result<ProofBytes, ProverError> {
```
Add `Wallet::sign_message` which should just be a wrapper of `wallet::signing::sign_message` (see existing `Wallet::sign_*`).

Add `verifier::verify_signature` with the following signature:
```rust
pub fn verify_signature(sigma_tree: SigmaBoolean, message: &[u8], signature: &[u8]) -> Result<bool, VerifierError>
```
implementation of which should be extracted from `Verifier::verify` (the part after `cprop`).

### Testing (DONE)
Since we're extracting/calling existing code covered with tests a simple smoke test should be enough. Something similar to `verifier::tests::test_prover_verifier_p2pk`.

## Wasm API

Add `wallet::sigma_message_using_p2pk` with the following signature:
```rust
/// Sign arbitrary an message using a P2PK address
pub fn sign_message_using_p2pk(&self, address: Address, message: &[u8]) -> Result<Vec<u8>, JsValue> 
```
Create new `verifier::verify_signature(address: Address, message: &[u8], signature: &[u8]) -> Result<bool, JsValue>`

Create a smoke test in `ergo-lib-wasm/tests` JS tests. 

## Swift API
Add `Wallet.signMessage(withP2PK addr: Address, message: ByteArray) throws -> ByteArray` with all needed underlying functions in `ergo-lib-core-c` and `ergo-lib-c` (see existing `Wallet.signTransaction` for examples).
Create `Verifier.swift` module with a function `verifySignature(address: Address, message: ByteArray, signature: ByteArray) throws -> Bool` with all needed underlying functions in `ergo-lib-core-c` and `ergo-lib-c`
Create a smoke test in `ergo-lib-ios/Tests/ErgoLibTests`. 


Discussion / hints:
I'll take this one.
The 100 SigmaUSD bounty is sent for #553 PR with the implementation but without the Wasm and Swift bindings.
I will complete the bindings.

Fix patch:
diff --git a/bindings/ergo-lib-c-core/src/error_conversion.rs b/bindings/ergo-lib-c-core/src/error_conversion.rs
index 2483e121c..53b2b1fdc 100644
--- a/bindings/ergo-lib-c-core/src/error_conversion.rs
+++ b/bindings/ergo-lib-c-core/src/error_conversion.rs
@@ -9,6 +9,7 @@ use ergo_lib::chain::transaction::TransactionError;
 use ergo_lib::ergo_chain_types::DigestNError;
 use ergo_lib::ergo_nipopow::NipopowProofError;
 use ergo_lib::ergoscript_compiler::compiler::CompileError;
+use ergo_lib::ergotree_interpreter::sigma_protocol::verifier::VerifierError;
 use ergo_lib::ergotree_ir::chain::address::AddressEncoderError;
 use ergo_lib::ergotree_ir::chain::address::AddressError;
 use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValueError;
@@ -72,3 +73,4 @@ convert_error_via_debug!(ChildIndexError);
 convert_error_via_debug!(BoundedVecOutOfBounds);
 convert_error_via_debug!(TransactionError);
 convert_error_via_debug!(NipopowProofError);
+convert_error_via_debug!(VerifierError);
diff --git a/bindings/ergo-lib-c-core/src/wallet.rs b/bindings/ergo-lib-c-core/src/wallet.rs
index 870b1f44d..6b6e24a6e 100644
--- a/bindings/ergo-lib-c-core/src/wallet.rs
+++ b/bindings/ergo-lib-c-core/src/wallet.rs
@@ -1,8 +1,11 @@
 //! Wallet-like features
 
-use ergo_lib::chain::transaction::TxIoVec;
+use ergo_lib::{
+    chain::transaction::TxIoVec, ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean,
+};
 
 use crate::{
+    address::{Address, ConstAddressPtr},
     collections::ConstCollectionPtr,
     ergo_box::ErgoBox,
     ergo_state_ctx::ConstErgoStateContextPtr,
@@ -231,3 +234,58 @@ pub unsafe fn wallet_generate_commitments_for_reduced_transaction(
     )));
     Ok(())
 }
+
+/// Represents the signature of a signed message
+pub struct SignedMessage(Vec<u8>);
+pub type SignedMessagePtr = *mut SignedMessage;
+pub type ConstSignedMessagePtr = *const SignedMessage;
+
+/// Sign an arbitrary message using a P2PK address
+pub unsafe fn wallet_sign_message_using_p2pk(
+    wallet_ptr: ConstWalletPtr,
+    address_ptr: ConstAddressPtr,
+    message_ptr: *const u8,
+    message_length: usize,
+    signed_message_out: *mut SignedMessagePtr,
+) -> Result<(), Error> {
+    let wallet = const_ptr_as_ref(wallet_ptr, "wallet_ptr")?;
+    let address = const_ptr_as_ref(address_ptr, "address_ptr")?;
+    let msg = std::slice::from_raw_parts(message_ptr, message_length);
+    let signed_message_out = mut_ptr_as_mut(signed_message_out, "signed_message_out")?;
+    if let Address(ergo_lib::ergotree_ir::chain::address::Address::P2Pk(d)) = address {
+        let sb = SigmaBoolean::from(d.clone());
+        let sig = wallet.0.sign_message(sb, msg)?;
+        *signed_message_out = Box::into_raw(Box::new(SignedMessage(sig)));
+        Ok(())
+    } else {
+        Err(Error::Misc(
+            "wallet::sign_message_using_p2pk: Address:P2Pk expected".into(),
+        ))
+    }
+}
+
+/// Verify that the signature is presented to satisfy SigmaProp conditions.
+pub unsafe fn verify_signature(
+    address_ptr: ConstAddressPtr,
+    message_ptr: *const u8,
+    message_length: usize,
+    signed_message_ptr: ConstSignedMessagePtr,
+) -> Result<bool, Error> {
+    let address = const_ptr_as_ref(address_ptr, "address_ptr")?;
+    let msg = std::slice::from_raw_parts(message_ptr, message_length);
+    let signed_message = const_ptr_as_ref(signed_message_ptr, "signed_message_ptr")?;
+
+    if let Address(ergo_lib::ergotree_ir::chain::address::Address::P2Pk(d)) = address {
+        let sb = SigmaBoolean::from(d.clone());
+        let res = ergo_lib::ergotree_interpreter::sigma_protocol::verifier::verify_signature(
+            sb,
+            msg,
+            signed_message.0.as_slice(),
+        )?;
+        Ok(res)
+    } else {
+        Err(Error::Misc(
+            "wallet::verify_signature: Address:P2Pk expected".into(),
+        ))
+    }
+}
diff --git a/bindings/ergo-lib-c/src/wallet.rs b/bindings/ergo-lib-c/src/wallet.rs
index 29b8a7e3b..1805f2c3a 100644
--- a/bindings/ergo-lib-c/src/wallet.rs
+++ b/bindings/ergo-lib-c/src/wallet.rs
@@ -1,8 +1,8 @@
 //! Wallet-like features
 
-use std::{ffi::CStr, os::raw::c_char};
-
+use crate::ReturnBool;
 use ergo_lib_c_core::{
+    address::ConstAddressPtr,
     collections::ConstCollectionPtr,
     ergo_box::ErgoBox,
     ergo_state_ctx::ConstErgoStateContextPtr,
@@ -15,6 +15,7 @@ use ergo_lib_c_core::{
     wallet::*,
     Error, ErrorPtr,
 };
+use std::{ffi::CStr, os::raw::c_char};
 
 use crate::delete_ptr;
 
@@ -159,6 +160,51 @@ pub unsafe extern "C" fn ergo_lib_wallet_generate_commitments_for_reduced_transa
     Error::c_api_from(res)
 }
 
+/// Sign an arbitrary message using a P2PK address
+#[no_mangle]
+pub unsafe extern "C" fn ergo_lib_wallet_sign_message_using_p2pk(
+    wallet_ptr: ConstWalletPtr,
+    address_ptr: ConstAddressPtr,
+    message_ptr: *const u8,
+    message_length: usize,
+    signed_message_out: *mut SignedMessagePtr,
+) -> ErrorPtr {
+    let res = wallet_sign_message_using_p2pk(
+        wallet_ptr,
+        address_ptr,
+        message_ptr,
+        message_length,
+        signed_message_out,
+    );
+    Error::c_api_from(res)
+}
+
+/// Verify that the signature is presented to satisfy SigmaProp conditions.
+#[no_mangle]
+pub unsafe extern "C" fn ergo_lib_verify_signature(
+    address_ptr: ConstAddressPtr,
+    message_ptr: *const u8,
+    message_length: usize,
+    signed_message_ptr: ConstSignedMessagePtr,
+) -> ReturnBool {
+    match verify_signature(address_ptr, message_ptr, message_length, signed_message_ptr) {
+        Ok(value) => ReturnBool {
+            value,
+            error: std::ptr::null_mut(),
+        },
+        Err(e) => ReturnBool {
+            value: false, // Just a dummy value
+            error: Error::c_api_from(Err(e)),
+        },
+    }
+}
+
+/// Drop `SignedMessage`
+#[no_mangle]
+pub unsafe extern "C" fn ergo_lib_signed_message_delete(ptr: SignedMessagePtr) {
+    delete_ptr(ptr)
+}
+
 /// Drop `Wallet`
 #[no_mangle]
 pub unsafe extern "C" fn ergo_lib_wallet_delete(ptr: WalletPtr) {
diff --git a/bindings/ergo-lib-ios/Sources/ErgoLib/Verifier.swift b/bindings/ergo-lib-ios/Sources/ErgoLib/Verifier.swift
new file mode 100644
index 000000000..30047c40f
--- /dev/null
+++ b/bindings/ergo-lib-ios/Sources/ErgoLib/Verifier.swift
@@ -0,0 +1,18 @@
+import Foundation
+import ErgoLibC
+
+/// Verify that the signature is presented to satisfy SigmaProp conditions.
+func verifySignature(
+    address: Address,
+    message: [UInt8],
+    signature: SignedMessage
+) throws -> Bool {
+    let res = ergo_lib_verify_signature(
+        address.pointer,
+        message,
+        UInt(message.count),
+        signature.pointer
+    )
+    try checkError(res.error)
+    return res.value
+}
diff --git a/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift b/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
index 93c5d5389..a662a106d 100644
--- a/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
+++ b/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
@@ -126,7 +126,38 @@ class Wallet {
         return TransactionHintsBag(withRawPointer: ptr!)
     }
     
+    /// Sign an arbitrary message using a P2PK address
+    func signedMessageUsingP2PK(
+        address: Address,
+        message: [UInt8]
+    ) throws -> SignedMessage {
+        var ptr: SignedMessagePtr?
+        let error = ergo_lib_wallet_sign_message_using_p2pk(
+            self.pointer,
+            address.pointer,
+            message,
+            UInt(message.count),
+            &ptr
+        )
+        try checkError(error)
+        return SignedMessage(withRawPointer: ptr!)
+    }
+    
     deinit {
         ergo_lib_wallet_delete(self.pointer)
     }
 }
+
+class SignedMessage {
+    internal var pointer: SignedMessagePtr
+    
+    /// Takes ownership of an existing ``SignedMessagePtr``. Note: we must ensure that no other instance
+    /// of ``SignedMessage`` can hold this pointer.
+    internal init(withRawPointer ptr: SignedMessagePtr) {
+        self.pointer = ptr
+    }
+    
+    deinit {
+        ergo_lib_signed_message_delete(self.pointer)
+    }
+}
diff --git a/bindings/ergo-lib-ios/Tests/ErgoLibTests/MessageSigningTests.swift b/bindings/ergo-lib-ios/Tests/ErgoLibTests/MessageSigningTests.swift
new file mode 100644
index 000000000..be0631bd9
--- /dev/null
+++ b/bindings/ergo-lib-ios/Tests/ErgoLibTests/MessageSigningTests.swift
@@ -0,0 +1,16 @@
+import XCTest
+@testable import ErgoLib
+@testable import ErgoLibC
+
+final class MessageSigningTests: XCTestCase {
+    func testSigningAndVerifying() throws {
+        let key = SecretKey()
+        let addr = key.getAddress()
+        let keys = SecretKeys()
+        keys.add(secretKey: key)
+        let wallet = Wallet(secrets: keys)
+        let msg: [UInt8] = Array("this is a message".utf8)
+        let sig = try wallet.signedMessageUsingP2PK(address: addr, message: msg)
+        XCTAssert(try verifySignature(address: addr, message: msg, signature: sig))
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/address.rs b/bindings/ergo-lib-wasm/src/address.rs
index 80dead316..325f9a57f 100644
--- a/bindings/ergo-lib-wasm/src/address.rs
+++ b/bindings/ergo-lib-wasm/src/address.rs
@@ -120,7 +120,7 @@ impl From<ergo_lib::ergotree_ir::chain::address::AddressTypePrefix> for AddressT
  */
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct Address(ergo_lib::ergotree_ir::chain::address::Address);
+pub struct Address(pub(crate) ergo_lib::ergotree_ir::chain::address::Address);
 
 #[wasm_bindgen]
 impl Address {
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index 535b17c1f..23b8db207 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -10,6 +10,7 @@ use ergo_lib::ergo_chain_types::DigestNError;
 use ergo_lib::ergo_nipopow::NipopowProofError;
 #[cfg(feature = "rest")]
 use ergo_lib::ergo_rest::NodeError;
+use ergo_lib::ergotree_interpreter::sigma_protocol::verifier::VerifierError;
 use ergo_lib::ergotree_ir::chain::address::AddressEncoderError;
 use ergo_lib::ergotree_ir::chain::address::AddressError;
 use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValueError;
@@ -89,5 +90,6 @@ from_error_to_wrap_via_debug!(ExtSecretKeyError);
 from_error_to_wrap_via_debug!(DerivationPathError);
 from_error_to_wrap_via_debug!(ExtPubKeyError);
 from_error_to_wrap_via_debug!(NipopowProofError);
+from_error_to_wrap_via_debug!(VerifierError);
 #[cfg(feature = "rest")]
 from_error_to_wrap_via_debug!(NodeError);
diff --git a/bindings/ergo-lib-wasm/src/lib.rs b/bindings/ergo-lib-wasm/src/lib.rs
index ab7855ace..4a0f984ef 100644
--- a/bindings/ergo-lib-wasm/src/lib.rs
+++ b/bindings/ergo-lib-wasm/src/lib.rs
@@ -38,6 +38,7 @@ pub mod token;
 pub mod transaction;
 pub mod tx_builder;
 pub mod utils;
+pub mod verifier;
 pub mod wallet;
 
 mod error_conversion;
diff --git a/bindings/ergo-lib-wasm/src/verifier.rs b/bindings/ergo-lib-wasm/src/verifier.rs
new file mode 100644
index 000000000..ee99509af
--- /dev/null
+++ b/bindings/ergo-lib-wasm/src/verifier.rs
@@ -0,0 +1,28 @@
+//! Verifier
+
+use std::convert::TryFrom;
+
+use ergo_lib::ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+use wasm_bindgen::{prelude::*, JsValue};
+
+use crate::{address::Address, error_conversion::to_js};
+
+/// Verify that the signature is presented to satisfy SigmaProp conditions.
+#[wasm_bindgen]
+pub fn verify_signature(
+    address: &Address,
+    message: &[u8],
+    signature: &[u8],
+) -> Result<bool, JsValue> {
+    if let Address(ergo_lib::ergotree_ir::chain::address::Address::P2Pk(d)) = address.clone() {
+        let sb = SigmaBoolean::try_from(d).map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
+        ergo_lib::ergotree_interpreter::sigma_protocol::verifier::verify_signature(
+            sb, message, signature,
+        )
+        .map_err(to_js)
+    } else {
+        Err(JsValue::from_str(
+            "wallet::verify_signature: Address:P2Pk expected",
+        ))
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index 24b89ebb1..f46e4bb96 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -1,6 +1,9 @@
 //! Wallet-like features
 use derive_more::{From, Into};
-use ergo_lib::chain::transaction::TxIoVec;
+use ergo_lib::{
+    chain::transaction::TxIoVec, ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean,
+};
+use js_sys::Uint8Array;
 use wasm_bindgen::prelude::*;
 
 pub mod derivation_path;
@@ -8,6 +11,7 @@ pub mod ext_pub_key;
 pub mod ext_secret_key;
 pub mod mnemonic;
 
+use crate::address::Address;
 use crate::transaction::TransactionHintsBag;
 use crate::{
     box_coll::ErgoBoxes,
@@ -188,4 +192,24 @@ impl Wallet {
             .map_err(to_js)
             .map(TransactionHintsBag::from)
     }
+
+    /// Sign an arbitrary message using a P2PK address
+    #[wasm_bindgen]
+    pub fn sign_message_using_p2pk(
+        &self,
+        address: &Address,
+        message: &[u8],
+    ) -> Result<Uint8Array, JsValue> {
+        if let Address(ergo_lib::ergotree_ir::chain::address::Address::P2Pk(d)) = address.clone() {
+            let sb = SigmaBoolean::from(d);
+            self.0
+                .sign_message(sb, message)
+                .map_err(to_js)
+                .map(|v| Uint8Array::from(v.as_slice()))
+        } else {
+            Err(JsValue::from_str(
+                "wallet::sign_message_using_p2pk: Address:P2Pk expected",
+            ))
+        }
+    }
 }


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_message_signing.js b/bindings/ergo-lib-wasm/tests/test_message_signing.js
new file mode 100644
index 000000000..e91329959
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/test_message_signing.js
@@ -0,0 +1,22 @@
+
+import { expect, assert } from "chai";
+
+import * as ergo from "..";
+let ergo_wasm;
+beforeEach(async () => {
+    ergo_wasm = await ergo;
+});
+
+it("Message signing smoke test", async () => {
+    const sk = ergo_wasm.SecretKey.random_dlog();
+    const addr = sk.get_address()
+    const sks = new ergo_wasm.SecretKeys();
+    assert(addr != null);
+    sks.add(sk);
+    const wallet = ergo_wasm.Wallet.from_secrets(sks);
+    const message = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+    const signature = wallet.sign_message_using_p2pk(addr, message);
+    assert(signature != null);
+    const res = ergo_wasm.verify_signature(addr, message, signature);
+    assert(res == true);
+});
\ No newline at end of file