Skip to content

Implement callback functionality#21

Open
F16oxy wants to merge 3 commits into
mainfrom
feat/callback-support
Open

Implement callback functionality#21
F16oxy wants to merge 3 commits into
mainfrom
feat/callback-support

Conversation

@F16oxy

@F16oxy F16oxy commented May 4, 2026

Copy link
Copy Markdown

No description provided.

@F16oxy F16oxy requested a review from ko1N May 4, 2026 15:57

@ko1N ko1N left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR, I think the logic is not fully there yet but it's going into the right direction. Can you try wiring this into the example html so we have an example of the new signing methods for easier testing?

I think the end goal is that we don't need the bytes-backed signing key on the rust side anymore and can just rely on the new trait methods.

We might also want all the functions on the signing key to return result futures. Because the signing might take some time, so async is probably the best way to go about this. All operations are also relying on transports, so they are fallible as well.

Comment thread crates/iris-wasm/src/tx.rs Outdated
pub fn public_key_bytes(&self) -> Vec<u8> {
match &self.backend {
PrivateKeyBackend::Bytes(bytes_backend) => bytes_backend.public_key_bytes.to_vec(),
PrivateKeyBackend::Callback(_) => Vec::new(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should fail properly instead of returning an empty buffer here.

Comment thread crates/iris-wasm/src/crypto.rs Outdated
/// Sign a 40-byte digest (same encoding as [`iris_ztd::Digest::to_bytes`]) with a 32-byte
/// private key. Returns 64 bytes: `c` then `s`, each 32-byte little-endian scalars.
#[wasm_bindgen(js_name = signDigestBytes)]
pub fn sign_digest_bytes(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should not be needed as we can construct the private key from bytes on the wasm side anyways. Downstream signing should be transparent to the implementation.

@h33p h33p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Change signDigest to signDigests

if spend.accepts_signing_pubkey(&pk) {
let digest = spend.sig_hash();
let sig = signing_key
.sign_digest(digest)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should change this loop to collect all digests, then call sign_digests. This way, users don't click "yes" 20 times on a ledger, and do it only once instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

@F16oxy F16oxy force-pushed the feat/callback-support branch from 8099cf2 to dd5221c Compare June 2, 2026 11:57
@F16oxy F16oxy marked this pull request as ready for review June 5, 2026 11:48

@ko1N ko1N left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, this already looks quite good! I believe what would also be nice to add is a second example to the grpc-web-demo (not necessarily a ledger example, but maybe a different way of signing via the callbacks). 🙏

fn sign_digests<'a>(
&'a self,
digests: &'a [Digest],
) -> impl Future<Output = Result<alloc::vec::Vec<Signature>, Self::Error>> + 'a {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe this needs to be gated by the alloc feature, otherwise it might break compilation on iris-rs with --no-default features.

self

let signatures = signing_key
.sign_digests(&digests)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should check if digests is empty before hitting the ledger with a potential empty signing request. This can happen if the spends do not match the given pkh. I believe this is something that could happen to legitimate users (e.g. when you plug in the wrong ledger) so we should probably propagate this as an error or at least abort early.

}
let Some(spend) = self.spends.get_mut(&name) else {
debug_assert!(false, "signature staging used names from self.spends");
return Err(SigningError::SignatureRejected);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Returning here could yield to an incomplete internal state, because some signatures might've already be applied to the spends (see 2 lines below). This could leave us in inconsistent internal state - not sure if we need to address it or if this is recoverable, but maybe its worth leaving a comment that this behavior is okay/expected.

.sign_digests(&[digest])
.await
.map_err(SigningError::Signer)?
.pop()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe it would be worthwhile adding an assertion that 1 digest we provide only yields to 1 signature so if a subsequent implementation breaks this contract we fail early here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants