Sign¶
KeyStore.sign(..) uses the pyca/cryptography (https://cryptography.io/en/latest/) library to securely sign data with SECP256K1 EC private keys. It produces canonical, deterministic DER-encoded signatures in accordance with RFC 6979.
Currency Transaction¶
Parameters
Parameter |
Type |
Description |
|---|---|---|
private_key |
|
The private key used for signing, in hexadecimal format. |
msg |
|
Message or transaction hash generated during transaction preparation. |
Example Usage
from pypergraph import KeyStore
# Generate a signature for a transaction
signature = KeyStore().sign(private_key="e123...", msg="f123...")
Lifecycle
import hashlib
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
Prehashed
)
from pypergraph.core.constants.SECP256K1_ORDER
class KeyStore:
@staticmethod
def sign(private_key: str, msg: str) -> str:
"""
Create transaction signature using the `cryptography` library.
:param private_key: Private key in hex format.
:param msg: Transaction message (string).
:return: Canonical DER signature in hex.
"""
# Convert hex private key to cryptography object
private_key_bytes = bytes.fromhex(private_key)
private_key_int = int.from_bytes(private_key_bytes, byteorder='big')
private_key = ec.derive_private_key(
private_key_int, ec.SECP256K1(), default_backend()
)
# Prehash message with SHA-512 and truncate to 32 bytes
msg_digest = hashlib.sha512(msg.encode("utf-8")).digest()[:32]
# Sign deterministically (RFC 6979) and enforce canonical form
signature = private_key.sign(
msg_digest,
ec.ECDSA(Prehashed(hashes.SHA256()))) # Prehashed for raw digest
# Decode signature to (r, s) and enforce canonical `s`
r, s = decode_dss_signature(signature)
if s > SECP256K1_ORDER // 2:
s = SECP256K1_ORDER - s
# Re-encode as canonical DER signature
canonical_signature = encode_dss_signature(r, s)
return canonical_signature.hex()
# Example usage of signing a transaction
signature = KeyStore().sign(private_key="e123...", msg="f123...")
Data¶
Attention
Encoding should match exactly what the Metagraph expects.
Custom Metagraph data is signed using the same method as for transaction signing, with differences in message serialization and encoding. By default, the transaction value is taken as the msg parameter. In addition to JSON encoding, the system supports base64 encoding or injection of custom encoding functions and prefixes.
Parameters
Parameter |
Type |
Description |
|---|---|---|
private_key |
|
The private key used for signing, in hexadecimal format. |
msg |
|
Custom Metagraph data to be signed. |
prefix |
|
Determines whether to prepend a signature prefix. If |
encoding |
|
The encoding to apply to the message. Use |
Default Prefix
Setting the parameter prefix=True will prepend "\u0019Constellation Signed Data:\n" along with the message length to the encoded message before serialization. Setting it to False will omit the prefix, and providing a custom string will use that string as the prefix.
Example Usage
# Required imports
import time
import json
import base64
from pypergraph import KeyStore
# Sample data to sign
water_and_energy_usage = {
"address": "from_address_value",
"energyUsage": {
"usage": 7,
"timestamp": int(time.time() * 1000),
},
"waterUsage": {
"usage": 7,
"timestamp": int(time.time() * 1000),
},
}
# Custom encoding function example
def encode(data: dict) -> str:
return json.dumps(data, separators=(',', ':'))
# Generate a signature and hash for the custom data
signature, hash_value = KeyStore().data_sign(
private_key="f123...",
msg=water_and_energy_usage,
prefix=False,
encoding=encode
)
Lifecycle
from typing import Union, Optional, Callable, Tuple, Literal
import hashlib
import json
import base64
import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
Prehashed
)
from pypergraph.core.constants.SECP256K1_ORDER
class KeyStore:
DATA_SIGN_PREFIX = "\u0019Constellation Signed Data:\n"
def encode_data(
self,
msg: dict,
prefix: Union[bool, str] = True,
encoding: Optional[Union[Literal["base64"], Callable[[dict], str], None]] = None,
) -> str:
"""
Encode the message using the provided encoding method.
"""
if encoding:
if callable(encoding):
msg = encoding(msg)
elif encoding == "base64":
encoded = json.dumps(msg, separators=(",", ":"))
msg = base64.b64encode(encoded.encode()).decode()
else:
raise ValueError("KeyStore :: Not a valid encoding method.")
else:
msg = json.dumps(msg, separators=(",", ":"))
if prefix is True:
msg = f"{self.DATA_SIGN_PREFIX}{len(msg)}\n{msg}"
elif isinstance(prefix, str):
msg = f"{prefix}{len(msg)}\n{msg}"
return msg
def data_sign(
self,
private_key: str,
msg: dict,
prefix: Union[bool, str] = True,
encoding: Optional[Union[Literal["base64"], Callable[[dict], str], None]] = None,
) -> Tuple[str, str]:
"""
Encode, serialize, and sign custom Metagraph data.
Returns a tuple of (signature, hash).
"""
# Encode the data
msg_encoded = self.encode_data(msg=msg, prefix=prefix, encoding=encoding)
# Serialize the message
serialized = msg_encoded.encode("utf-8")
# Generate SHA-256 hash of the serialized data
hash_ = hashlib.sha256(serialized).hexdigest()
# Sign the hash using the sign method
signature = self.sign(private_key, hash_)
return signature, hash_
@staticmethod
def sign(private_key: str, msg: str) -> str:
"""
Create transaction signature using the `cryptography` library.
:param private_key: Private key in hex format.
:param msg: Transaction message (string).
:return: Canonical DER signature in hex.
"""
# Convert hex private key to cryptography object
private_key_bytes = bytes.fromhex(private_key)
private_key_int = int.from_bytes(private_key_bytes, byteorder='big')
private_key = ec.derive_private_key(
private_key_int, ec.SECP256K1(), default_backend()
)
# Prehash message with SHA-512 and truncate to 32 bytes
msg_digest = hashlib.sha512(msg.encode("utf-8")).digest()[:32]
# Sign deterministically (RFC 6979) and enforce canonical form
signature = private_key.sign(
msg_digest,
ec.ECDSA(Prehashed(hashes.SHA256()))) # Prehashed for raw digest
# Decode signature to (r, s) and enforce canonical `s`
r, s = decode_dss_signature(signature)
if s > SECP256K1_ORDER // 2:
s = SECP256K1_ORDER - s
# Re-encode as canonical DER signature
canonical_signature = encode_dss_signature(r, s)
return canonical_signature.hex()
# Example usage of data signing
water_and_energy_usage = {
"address": "from_address_value",
"energyUsage": {
"usage": 7,
"timestamp": int(time.time() * 1000),
},
"waterUsage": {
"usage": 7,
"timestamp": int(time.time() * 1000),
},
}
def encode(data: dict) -> str:
return json.dumps(data, separators=(',', ':'))
signature, hash_value = KeyStore().data_sign(
private_key="f123...",
msg=water_and_energy_usage,
prefix=False,
encoding=encode
)
Personal Message¶
Parameters
Parameter |
Type |
Description |
|---|---|---|
private_key |
|
The private key used for signing, in hexadecimal format. |
msg |
|
Message to sign. |
Personal Sign Prefix
Prepends "\u0019Constellation Signed Message:\n" to the message before signing with private key.
Example Usage
from pypergraph import KeyStore
signature = KeyStore().personal_sign(msg="...", private_key="f123...")