1212from solana_agent import AutoTool , ToolRegistry
1313from privy import AsyncPrivyAPI
1414from privy .lib .authorization_signatures import get_authorization_signature
15+ from cryptography .hazmat .primitives import serialization
1516from solders .keypair import Keypair
1617from solders .transaction import VersionedTransaction
1718from solders .message import to_bytes_versioned
2122logger = logging .getLogger (__name__ )
2223
2324
25+ def convert_key_to_pkcs8_pem (key_string : str ) -> str :
26+ """Convert a private key to PKCS#8 PEM format for the Privy SDK.
27+
28+ The SDK expects keys in PKCS#8 PEM format (-----BEGIN PRIVATE KEY-----).
29+ This function handles keys in various formats:
30+ - Already in PKCS#8 PEM base64
31+ - SEC1 EC format (-----BEGIN EC PRIVATE KEY-----)
32+ - Raw DER bytes (PKCS#8 or SEC1)
33+
34+ Returns the base64 content (without headers) in PKCS#8 format.
35+ """
36+ # Strip wallet-auth: prefix if present
37+ private_key_string = key_string .replace ("wallet-auth:" , "" )
38+
39+ # Try loading as PKCS#8 PEM format first
40+ try :
41+ private_key_pem = f"-----BEGIN PRIVATE KEY-----\n { private_key_string } \n -----END PRIVATE KEY-----"
42+ private_key = serialization .load_pem_private_key (
43+ private_key_pem .encode ("utf-8" ), password = None
44+ )
45+ # Already in correct format, return as-is
46+ return private_key_string
47+ except (ValueError , TypeError ):
48+ pass
49+
50+ # Try as EC PRIVATE KEY (SEC1) format
51+ try :
52+ ec_key_pem = f"-----BEGIN EC PRIVATE KEY-----\n { private_key_string } \n -----END EC PRIVATE KEY-----"
53+ private_key = serialization .load_pem_private_key (
54+ ec_key_pem .encode ("utf-8" ), password = None
55+ )
56+ # Convert to PKCS#8 format
57+ pkcs8_bytes = private_key .private_bytes (
58+ encoding = serialization .Encoding .PEM ,
59+ format = serialization .PrivateFormat .PKCS8 ,
60+ encryption_algorithm = serialization .NoEncryption (),
61+ )
62+ # Extract base64 content (remove headers and newlines)
63+ pkcs8_pem = pkcs8_bytes .decode ("utf-8" )
64+ lines = pkcs8_pem .strip ().split ("\n " )
65+ base64_content = "" .join (lines [1 :- 1 ])
66+ return base64_content
67+ except (ValueError , TypeError ):
68+ pass
69+
70+ # Try loading as raw DER bytes
71+ try :
72+ der_bytes = base64 .b64decode (private_key_string )
73+ # Try PKCS#8 DER
74+ try :
75+ private_key = serialization .load_der_private_key (der_bytes , password = None )
76+ except (ValueError , TypeError ):
77+ # Try SEC1 DER
78+ from cryptography .hazmat .primitives .asymmetric import ec
79+
80+ private_key = ec .derive_private_key (
81+ int .from_bytes (der_bytes , "big" ), ec .SECP256R1 ()
82+ )
83+ # Convert to PKCS#8 PEM format
84+ pkcs8_bytes = private_key .private_bytes (
85+ encoding = serialization .Encoding .PEM ,
86+ format = serialization .PrivateFormat .PKCS8 ,
87+ encryption_algorithm = serialization .NoEncryption (),
88+ )
89+ pkcs8_pem = pkcs8_bytes .decode ("utf-8" )
90+ lines = pkcs8_pem .strip ().split ("\n " )
91+ base64_content = "" .join (lines [1 :- 1 ])
92+ return base64_content
93+ except (ValueError , TypeError ) as e :
94+ raise ValueError (
95+ f"Could not load private key. Expected base64-encoded PKCS#8 or SEC1 format. "
96+ f"Generate with: openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256. Error: { e } "
97+ )
98+
99+
24100async def get_privy_embedded_wallet (
25101 privy_client : AsyncPrivyAPI , user_id : str
26102) -> Optional [Dict [str , str ]]:
@@ -116,6 +192,9 @@ async def privy_sign_transaction(
116192 Uses the wallets.rpc method with method="signTransaction" for Solana.
117193 The SDK handles authorization signature generation automatically when provided.
118194 """
195+ # Convert the key to PKCS#8 format expected by the SDK
196+ pkcs8_key = convert_key_to_pkcs8_pem (signing_key )
197+
119198 # Generate the authorization signature using the SDK's utility
120199 url = f"https://api.privy.io/v1/wallets/{ wallet_id } /rpc"
121200 body = {
@@ -129,7 +208,7 @@ async def privy_sign_transaction(
129208 body = body ,
130209 method = "POST" ,
131210 app_id = privy_client .app_id ,
132- private_key = signing_key ,
211+ private_key = pkcs8_key ,
133212 )
134213
135214 # Call the SDK's rpc method for Solana signTransaction
0 commit comments