sui-crypto 0.3.0

Defines the interface for signing and verying messages in the Sui ecosystem
Documentation
use base64ct::Encoding;
use signature::Signer;
use signature::Verifier;
use sui_sdk_types::PersonalMessage;
use sui_sdk_types::ZkLoginAuthenticator;
use sui_sdk_types::ZkLoginInputs;

use crate::SuiVerifier;
use crate::ed25519::Ed25519PrivateKey;

use super::*;

/// Returns a valid zklogin material for testing only.
fn test_zklogin_material() -> (Jwk, JwkId, ZkLoginInputs, Ed25519PrivateKey, u64) {
    let inputs = serde_json::json!({
        "proof_points": {
            "a": [
                "17318089125952421736342263717932719437717844282410187957984751939942898251250",
                "11373966645469122582074082295985388258840681618268593976697325892280915681207",
                "1"
            ],
            "b": [
                [
                    "5939871147348834997361720122238980177152303274311047249905942384915768690895",
                    "4533568271134785278731234570361482651996740791888285864966884032717049811708"
                ],
                [
                    "10564387285071555469753990661410840118635925466597037018058770041347518461368",
                    "12597323547277579144698496372242615368085801313343155735511330003884767957854"
                ],
                ["1","0"]
            ],
            "c": [
                "15791589472556826263231644728873337629015269984699404073623603352537678813171",
                "4547866499248881449676161158024748060485373250029423904113017422539037162527",
                "1"
            ]
        },
        "iss_base64_details": {
            "value": "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw",
            "index_mod_4": 2
        },
        "header_base64": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ",
        "address_seed": "20794788559620669596206457022966176986688727876128223628113916380927502737911"
    });

    let zklogin_inputs: ZkLoginInputs = serde_json::from_value(inputs).unwrap();

    let key = Ed25519PrivateKey::new([
        155, 244, 154, 106, 7, 85, 249, 83, 129, 31, 206, 18, 95, 38, 131, 213, 4, 41, 195, 187,
        73, 224, 116, 20, 126, 0, 137, 165, 46, 174, 21, 95,
    ]);

    let jwk: Jwk = serde_json::from_str(r#"{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}"#).unwrap();
    let jwk_id = JwkId {
        iss: "https://id.twitch.tv/oauth2".to_string(),
        kid: "1".to_string(),
    };
    let max_epoch = 10;
    (jwk, jwk_id, zklogin_inputs, key, max_epoch)
}

#[test]
fn zklogin_sign_personal_message() {
    let message = PersonalMessage(b"hello world".into());

    let (jwk, jwk_id, inputs, key, max_epoch) = test_zklogin_material();
    let signature = key.sign(&message.signing_digest());
    let zklogin_authenticator = ZkLoginAuthenticator {
        inputs,
        max_epoch,
        signature,
    };
    let mut verifier = ZkloginVerifier::new_dev();
    verifier.jwks_mut().insert(jwk_id, jwk);

    verifier
        .verify(&message.signing_digest(), &zklogin_authenticator)
        .unwrap();

    let user_signature = UserSignature::ZkLogin(zklogin_authenticator.into());
    verifier
        .verify_personal_message(&message, &user_signature)
        .unwrap();
}

/// Regression test: an oversized JWK modulus (> 256 bytes) must return an
/// error instead of panicking via `unwrap()`.
#[test]
fn zklogin_verify_oversized_jwk_modulus_returns_error() {
    let message = PersonalMessage(b"hello world".into());

    let (mut jwk, jwk_id, inputs, key, max_epoch) = test_zklogin_material();

    // Replace the modulus with a 257-byte value encoded as base64url.
    // U2048 is 256 bytes, so 257 bytes will overflow it.
    jwk.n = base64ct::Base64UrlUnpadded::encode_string(&[0x42u8; 257]);

    let signature = key.sign(&message.signing_digest());
    let zklogin_authenticator = ZkLoginAuthenticator {
        inputs,
        max_epoch,
        signature,
    };
    let mut verifier = ZkloginVerifier::new_dev();
    verifier.jwks_mut().insert(jwk_id, jwk);

    let result = verifier.verify(&message.signing_digest(), &zklogin_authenticator);
    assert!(
        result.is_err(),
        "expected error for oversized modulus, got Ok"
    );
}