Skip to content

rsa_pss.erl - RSA-PSS Digital Signatures

Overview

Purpose: RSA-PSS (Probabilistic Signature Scheme) implementation for digital signatures
Module: rsa_pss
License: Mozilla Public License v2.0
Origin: https://github.com/potatosalad/erlang-crypto_rsassa_pss
Modified by: The Arweave Team

RSA-PSS is a modern signature scheme that provides provable security and is recommended over traditional RSA signatures (PKCS#1 v1.5).

Dependencies

  • Erlang/OTP: crypto, public_key
  • External: None

Public Functions Overview

%% Signing
-spec sign(Message, DigestType, PrivateKey) -> Signature.
-spec sign(Message, DigestType, Salt, PrivateKey) -> Signature.
 
%% Verification
-spec verify(Message, DigestType, Signature, PublicKey) -> boolean().
-spec verify_legacy(Message, DigestType, Signature, PublicKey) -> boolean().

Public Functions

1. sign/3 - Sign with Auto-Generated Salt

-spec sign(Message, DigestType, PrivateKey) -> Signature
    when
        Message    :: binary() | {digest, binary()},
        DigestType :: rsa_digest_type() | atom(),
        PrivateKey :: rsa_private_key(),
        Signature  :: binary().

Description: Signs a message using RSA-PSS with automatically generated random salt.

Test Code:
-module(rsa_pss_sign3_test).
-include_lib("eunit/include/eunit.hrl").
-include_lib("public_key/include/public_key.hrl").
 
setup() ->
    #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D} = 
        public_key:generate_key({rsa, 2048, 65537}),
    PrivKey = #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D},
    PubKey = #'RSAPublicKey'{modulus = N, publicExponent = E},
    {PubKey, PrivKey}.
 
sign_binary_test() ->
    {PubKey, PrivKey} = setup(),
    Message = <<"Hello, World!">>,
    Signature = rsa_pss:sign(Message, sha256, PrivKey),
    ?assert(is_binary(Signature)),
    ?assert(rsa_pss:verify(Message, sha256, Signature, PubKey)).
 
sign_digest_test() ->
    {PubKey, PrivKey} = setup(),
    Digest = crypto:hash(sha256, <<"Test">>),
    Signature = rsa_pss:sign({digest, Digest}, sha256, PrivKey),
    ?assert(rsa_pss:verify({digest, Digest}, sha256, Signature, PubKey)).

2. sign/4 - Sign with Custom Salt

-spec sign(Message, DigestType, Salt, PrivateKey) -> Signature
    when
        Message    :: binary() | {digest, binary()},
        DigestType :: rsa_digest_type() | atom(),
        Salt       :: binary(),
        PrivateKey :: rsa_private_key(),
        Signature  :: binary().

Description: Signs with a custom salt for deterministic signatures.

Test Code:
-module(rsa_pss_sign4_test).
-include_lib("eunit/include/eunit.hrl").
-include_lib("public_key/include/public_key.hrl").
 
setup() ->
    #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D} = 
        public_key:generate_key({rsa, 2048, 65537}),
    PrivKey = #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D},
    PubKey = #'RSAPublicKey'{modulus = N, publicExponent = E},
    {PubKey, PrivKey}.
 
deterministic_sign_test() ->
    {PubKey, PrivKey} = setup(),
    Message = <<"Test">>,
    Salt = crypto:hash(sha256, <<>>),
    Sig1 = rsa_pss:sign(Message, sha256, Salt, PrivKey),
    Sig2 = rsa_pss:sign(Message, sha256, Salt, PrivKey),
    ?assertEqual(Sig1, Sig2),
    ?assert(rsa_pss:verify(Message, sha256, Sig1, PubKey)).

3. verify/4 - Verify Signature

-spec verify(Message, DigestType, Signature, PublicKey) -> boolean()
    when
        Message    :: binary() | {digest, binary()},
        DigestType :: rsa_digest_type() | atom(),
        Signature  :: binary(),
        PublicKey  :: rsa_public_key().

Description: Verifies an RSA-PSS signature.

Test Code:
-module(rsa_pss_verify_test).
-include_lib("eunit/include/eunit.hrl").
-include_lib("public_key/include/public_key.hrl").
 
setup() ->
    #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D} = 
        public_key:generate_key({rsa, 2048, 65537}),
    PrivKey = #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D},
    PubKey = #'RSAPublicKey'{modulus = N, publicExponent = E},
    {PubKey, PrivKey}.
 
valid_signature_test() ->
    {PubKey, PrivKey} = setup(),
    Message = <<"Valid">>,
    Signature = rsa_pss:sign(Message, sha256, PrivKey),
    ?assertEqual(true, rsa_pss:verify(Message, sha256, Signature, PubKey)).
 
invalid_signature_test() ->
    {PubKey, PrivKey} = setup(),
    Message = <<"Test">>,
    Signature = rsa_pss:sign(Message, sha256, PrivKey),
    <<First:8, Rest/binary>> = Signature,
    Tampered = <<(First bxor 1), Rest/binary>>,
    ?assertEqual(false, rsa_pss:verify(Message, sha256, Tampered, PubKey)).
 
wrong_message_test() ->
    {PubKey, PrivKey} = setup(),
    Signature = rsa_pss:sign(<<"A">>, sha256, PrivKey),
    ?assertEqual(false, rsa_pss:verify(<<"B">>, sha256, Signature, PubKey)).

4. verify_legacy/4 - Legacy Verification

-spec verify_legacy(Message, DigestType, Signature, PublicKey) -> boolean()

Description: Legacy verification with different bit size calculation.

Test Code:
-module(rsa_pss_verify_legacy_test).
-include_lib("eunit/include/eunit.hrl").
-include_lib("public_key/include/public_key.hrl").
 
setup() ->
    #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D} = 
        public_key:generate_key({rsa, 2048, 65537}),
    PrivKey = #'RSAPrivateKey'{modulus = N, publicExponent = E, privateExponent = D},
    PubKey = #'RSAPublicKey'{modulus = N, publicExponent = E},
    {PubKey, PrivKey}.
 
legacy_verify_test() ->
    {PubKey, PrivKey} = setup(),
    Message = <<"Legacy">>,
    Signature = rsa_pss:sign(Message, sha256, PrivKey),
    ?assertEqual(true, rsa_pss:verify(Message, sha256, Signature, PubKey)),
    ?assertEqual(true, rsa_pss:verify_legacy(Message, sha256, Signature, PubKey)).

Common Patterns

%% Sign and store
sign_and_store(Message, PrivateKey) ->
    Signature = rsa_pss:sign(Message, sha256, PrivateKey),
    #{message => Message, signature => Signature}.
 
%% Arweave transaction signing (SHA-384)
sign_arweave_tx(TxData, PrivateKey) ->
    DeepHash = crypto:hash(sha384, term_to_binary(TxData)),
    rsa_pss:sign({digest, DeepHash}, sha384, PrivateKey).

References