Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,004 changes: 994 additions & 10 deletions bindings/go/iota_sdk_ffi/iota_sdk_ffi.go

Large diffs are not rendered by default.

347 changes: 347 additions & 0 deletions bindings/go/iota_sdk_ffi/iota_sdk_ffi.h

Large diffs are not rendered by default.

3,701 changes: 2,873 additions & 828 deletions bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt

Large diffs are not rendered by default.

1,078 changes: 1,078 additions & 0 deletions bindings/python/lib/iota_sdk_ffi.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion crates/iota-sdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ crate-type = ["lib", "cdylib"]
base64ct = { version = "1.6.0", features = ["alloc", "std"] }
derive_more = { version = "2.0", features = ["from", "deref"] }
rand = "0.8"
roaring = { version = "0.11.2", default-features = false }
serde_json = "1.0.95"
tokio = { version = "1.36.0", features = ["time"] }
uniffi = { version = "0.29", features = ["cli", "tokio"] }

iota-crypto = { path = "../iota-crypto", features = ["ed25519", "secp256r1", "passkey", "secp256k1", "zklogin", "pem"] }
iota-crypto = { path = "../iota-crypto", features = ["bls12381", "ed25519", "secp256r1", "passkey", "secp256k1", "zklogin", "pem"] }
iota-graphql-client = { path = "../iota-graphql-client" }
iota-transaction-builder = { path = "../iota-transaction-builder" }
iota-types = { package = "iota-sdk-types", path = "../iota-sdk-types", features = ["hash", "rand"] }
181 changes: 181 additions & 0 deletions crates/iota-sdk-ffi/src/crypto/bls12381.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::sync::RwLock;

use iota_types::SignatureScheme;
use rand::rngs::OsRng;

use crate::{
error::{Result, SdkFfiError},
types::{
checkpoint::CheckpointSummary,
crypto::{
Bls12381PublicKey, Bls12381Signature,
validator::{ValidatorAggregatedSignature, ValidatorCommittee, ValidatorSignature},
},
},
};

#[derive(derive_more::From, uniffi::Object)]
pub struct Bls12381PrivateKey(pub iota_crypto::bls12381::Bls12381PrivateKey);

#[uniffi::export]
impl Bls12381PrivateKey {
#[uniffi::constructor]
pub fn new(bytes: Vec<u8>) -> Result<Self> {
Ok(Self(iota_crypto::bls12381::Bls12381PrivateKey::new(
bytes.try_into().map_err(|v: Vec<u8>| {
SdkFfiError::custom(format!("expected bytes of length 32, found {}", v.len()))
})?,
)?))
}

pub fn scheme(&self) -> SignatureScheme {
self.0.scheme()
}

pub fn verifying_key(&self) -> Bls12381VerifyingKey {
self.0.verifying_key().into()
}

pub fn public_key(&self) -> Bls12381PublicKey {
self.0.public_key().into()
}

#[uniffi::constructor]
pub fn generate() -> Self {
Self(iota_crypto::bls12381::Bls12381PrivateKey::generate(OsRng))
}

pub fn sign_checkpoint_summary(&self, summary: &CheckpointSummary) -> ValidatorSignature {
self.0.sign_checkpoint_summary(&summary.0).into()
}

pub fn try_sign(&self, message: &[u8]) -> Result<Bls12381Signature> {
Ok(
iota_crypto::Signer::<iota_types::Bls12381Signature>::try_sign(&self.0, message)?
.into(),
)
}
}

#[derive(derive_more::From, uniffi::Object)]
pub struct Bls12381VerifyingKey(pub iota_crypto::bls12381::Bls12381VerifyingKey);

#[uniffi::export]
impl Bls12381VerifyingKey {
#[uniffi::constructor]
pub fn new(public_key: &Bls12381PublicKey) -> Result<Self> {
Ok(iota_crypto::bls12381::Bls12381VerifyingKey::new(&public_key.0).map(Self)?)
}

pub fn public_key(&self) -> Bls12381PublicKey {
self.0.public_key().into()
}

pub fn verify(&self, message: &[u8], signature: &Bls12381Signature) -> Result<()> {
Ok(
iota_crypto::Verifier::<iota_types::Bls12381Signature>::verify(
&self.0,
message,
&signature.0,
)?,
)
}
}

#[derive(derive_more::From, uniffi::Object)]
pub struct ValidatorCommitteeSignatureVerifier(
pub iota_crypto::bls12381::ValidatorCommitteeSignatureVerifier,
);

#[uniffi::export]
impl ValidatorCommitteeSignatureVerifier {
#[uniffi::constructor]
pub fn new(committee: ValidatorCommittee) -> Result<Self> {
Ok(Self(
iota_crypto::bls12381::ValidatorCommitteeSignatureVerifier::new(committee.into())?,
))
}

pub fn committee(&self) -> ValidatorCommittee {
self.0.committee().clone().into()
}

pub fn verify_checkpoint_summary(
&self,
summary: &CheckpointSummary,
signature: &ValidatorAggregatedSignature,
) -> Result<()> {
Ok(self.0.verify_checkpoint_summary(&summary.0, &signature.0)?)
}

pub fn verify(&self, message: &[u8], signature: &ValidatorSignature) -> Result<()> {
Ok(
iota_crypto::Verifier::<iota_types::ValidatorSignature>::verify(
&self.0,
message,
&signature.0,
)?,
)
}

pub fn verify_aggregated(
&self,
message: &[u8],
signature: &ValidatorAggregatedSignature,
) -> Result<()> {
Ok(iota_crypto::Verifier::<
iota_types::ValidatorAggregatedSignature,
>::verify(&self.0, message, &signature.0)?)
}
}

#[derive(derive_more::From, uniffi::Object)]
pub struct ValidatorCommitteeSignatureAggregator(
pub RwLock<iota_crypto::bls12381::ValidatorCommitteeSignatureAggregator>,
);

#[uniffi::export]
impl ValidatorCommitteeSignatureAggregator {
#[uniffi::constructor]
pub fn new_checkpoint_summary(
committee: ValidatorCommittee,
summary: &CheckpointSummary,
) -> Result<Self> {
Ok(Self(
iota_crypto::bls12381::ValidatorCommitteeSignatureAggregator::new_checkpoint_summary(
committee.into(),
&summary.0,
)?
.into(),
))
}

pub fn committee(&self) -> ValidatorCommittee {
self.0
.read()
.expect("failed to read validator committee signature aggregator")
.committee()
.clone()
.into()
}

pub fn add_signature(&self, signature: &ValidatorSignature) -> Result<()> {
Ok(self
.0
.write()
.expect("failed to read validator committee signature aggregator")
.add_signature(signature.0.clone())?)
}

pub fn finish(&self) -> Result<ValidatorAggregatedSignature> {
Ok(self
.0
.read()
.expect("failed to read validator committee signature aggregator")
.finish()?
.into())
}
}
1 change: 1 addition & 0 deletions crates/iota-sdk-ffi/src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

pub mod bls12381;
pub mod ed25519;
pub mod multisig;
pub mod passkey;
Expand Down
127 changes: 125 additions & 2 deletions crates/iota-sdk-ffi/src/types/crypto/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,48 @@

use std::sync::Arc;

use crate::types::crypto::Bls12381PublicKey;
use crate::{
error::Result,
types::{
checkpoint::EpochId,
crypto::{Bls12381PublicKey, Bls12381Signature},
signature,
},
};

/// The Validator Set for a particular epoch.
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// validator-committee = u64 ; epoch
/// (vector validator-committee-member)
/// ```
#[derive(uniffi::Record)]
pub struct ValidatorCommittee {
pub epoch: EpochId,
pub members: Vec<ValidatorCommitteeMember>,
}

impl From<iota_types::ValidatorCommittee> for ValidatorCommittee {
fn from(value: iota_types::ValidatorCommittee) -> Self {
Self {
epoch: value.epoch,
members: value.members.into_iter().map(Into::into).collect(),
}
}
}

impl From<ValidatorCommittee> for iota_types::ValidatorCommittee {
fn from(value: ValidatorCommittee) -> Self {
Self {
epoch: value.epoch,
members: value.members.into_iter().map(Into::into).collect(),
}
}
}

/// A member of a Validator Committee
///
Expand All @@ -15,7 +56,7 @@ use crate::types::crypto::Bls12381PublicKey;
/// validator-committee-member = bls-public-key
/// u64 ; stake
/// ```
#[derive(uniffi::Record)]
#[derive(Clone, uniffi::Record)]
pub struct ValidatorCommitteeMember {
pub public_key: Arc<Bls12381PublicKey>,
pub stake: u64,
Expand All @@ -38,3 +79,85 @@ impl From<ValidatorCommitteeMember> for iota_types::ValidatorCommitteeMember {
}
}
}

/// A signature from a Validator
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// validator-signature = u64 ; epoch
/// bls-public-key
/// bls-signature
/// ```
#[derive(derive_more::From, uniffi::Object)]
pub struct ValidatorSignature(pub iota_types::ValidatorSignature);

#[uniffi::export]
impl ValidatorSignature {
#[uniffi::constructor]
pub fn new(
epoch: EpochId,
public_key: &Bls12381PublicKey,
signature: &Bls12381Signature,
) -> Self {
Self(iota_types::ValidatorSignature {
epoch,
public_key: **public_key,
signature: **signature,
})
}

pub fn epoch(&self) -> EpochId {
self.0.epoch
}

pub fn public_key(&self) -> Bls12381PublicKey {
self.0.public_key.into()
}

pub fn signature(&self) -> Bls12381Signature {
self.0.signature.into()
}
}

/// An aggregated signature from multiple Validators.
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// validator-aggregated-signature = u64 ; epoch
/// bls-signature
/// roaring-bitmap
/// roaring-bitmap = bytes ; where the contents of the bytes are valid
/// ; according to the serialized spec for
/// ; roaring bitmaps
/// ```
///
/// See [here](https://github.com/RoaringBitmap/RoaringFormatSpec) for the specification for the
/// serialized format of RoaringBitmaps.
#[derive(derive_more::From, uniffi::Object)]
pub struct ValidatorAggregatedSignature(pub iota_types::ValidatorAggregatedSignature);

#[uniffi::export]
impl ValidatorAggregatedSignature {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a bitmap getter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't really sure how to handle it. I could just serialize the bytes for the getter but I can't just return the type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of lacking the context if it's useful to get it or not, in doubt I'd serialise it yeah

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

#[uniffi::constructor]
pub fn new(epoch: EpochId, signature: &Bls12381Signature, bitmap_bytes: &[u8]) -> Result<Self> {
Ok(Self(iota_types::ValidatorAggregatedSignature {
epoch,
signature: **signature,
bitmap: roaring::RoaringBitmap::deserialize_from(bitmap_bytes)?,
}))
}

pub fn epoch(&self) -> EpochId {
self.0.epoch
}

pub fn signature(&self) -> Bls12381Signature {
self.0.signature.into()
}
}