Skip to content

Conversation

@samkim-crypto
Copy link
Contributor

@samkim-crypto samkim-crypto commented Nov 23, 2025

Problem

Currently, the PoP generation API in the crate doesn't support custom payload. To support solana-foundation/solana-improvement-documents#387 where custom message is used for domain separation, we need the interface for the caller to generate and verify PoP on custom payload.

Summary of Changes

I added a field payload: Option<&[u8]> to the PoP generation and verification functions. If this field is None, then the functions just use the BLS public key as bytes for the payload. If this field is Some(...), then it uses the provided bytes to generate the message for PoP.

@samkim-crypto samkim-crypto marked this pull request as ready for review November 24, 2025 22:13
/// possession signing and verification functions. See the
/// [standard](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#section-4.2.3).
pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_";
pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
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 also realized that this domain separation tag was written for G1 while we hash the message on G2 for PoP, so I fixed this.

@samkim-crypto samkim-crypto requested a review from zz-sol November 24, 2025 22:41
Copy link

@zz-sol zz-sol left a comment

Choose a reason for hiding this comment

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

LGTM.

payload: Option<&[u8]>,
) -> G2Projective {
if let Some(bytes) = payload {
G2Projective::hash_to_curve(bytes, POP_DST, &[])
Copy link

Choose a reason for hiding this comment

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

nits: this means bytes = Some(&[]) and bytes=None have different outputs. Is this behavior acceptable? Or should bytes=None also hash the public key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was thinking that this is acceptable.

I originally made the function take in prefix: Option<&[u8]> instead. It would then concatenate prefix || pubkey_bytes and then use this as the payload. This is probably the safer API because the public key will always be hashed. But if someone wants to add a suffix instead of prefix pubkey_bytes || suffix, for example, they cannot use this API. This is why I made the function to take in paylod: Option<&[u8]> instead.

I think hashing on bytes = Some(&[]) is not any safer than hashing any other string that does not have the public key bytes in it anyway, so there doesn't seem to be a justification to specialize for this case. Of course, if you think specializing addressing the case Some(&[]) to hash the public key bytes instead, then let me know 😄

Copy link

Choose a reason for hiding this comment

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

sounds good to me

@samkim-crypto samkim-crypto merged commit 2933068 into anza-xyz:master Nov 25, 2025
66 of 72 checks passed
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.

2 participants