feat(sdk): Add Crypto SDK for all 12 languages
Implements quantum-resistant cryptographic primitives across all SDK languages: - Hybrid Ed25519 + Dilithium3 signatures (classical + post-quantum) - BIP-39 mnemonic support (12, 15, 18, 21, 24 words) - BIP-44 hierarchical key derivation (coin type 0x5359) - Post-quantum algorithms: Falcon (FIPS 206), SPHINCS+ (FIPS 205) - Key derivation: HKDF-SHA3-256, PBKDF2 - Hash functions: SHA3-256, BLAKE3, Keccak-256 Languages: JavaScript/TypeScript, Python, Go, Rust, Flutter/Dart, Java, Kotlin, Swift, C, C++, C#/.NET, Ruby
This commit is contained in:
parent
2c534a18bb
commit
08a55aa80e
25 changed files with 11265 additions and 0 deletions
799
sdk/c/include/synor_crypto.h
Normal file
799
sdk/c/include/synor_crypto.h
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/**
|
||||
* Synor Crypto SDK for C
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*
|
||||
* Example:
|
||||
* ```c
|
||||
* synor_crypto_config_t config = {
|
||||
* .api_key = "your-api-key",
|
||||
* .endpoint = "https://crypto.synor.io/v1",
|
||||
* .timeout = 30000,
|
||||
* .retries = 3
|
||||
* };
|
||||
*
|
||||
* synor_crypto_t* crypto = synor_crypto_create(&config);
|
||||
*
|
||||
* // Generate a mnemonic
|
||||
* synor_mnemonic_t mnemonic;
|
||||
* synor_crypto_mnemonic_generate(crypto, 24, &mnemonic);
|
||||
* printf("Backup words: %s\n", mnemonic.phrase);
|
||||
*
|
||||
* // Create keypair from mnemonic
|
||||
* synor_hybrid_keypair_t keypair;
|
||||
* synor_crypto_keypair_from_mnemonic(crypto, mnemonic.phrase, "", &keypair);
|
||||
*
|
||||
* synor_crypto_destroy(crypto);
|
||||
* ```
|
||||
*/
|
||||
|
||||
#ifndef SYNOR_CRYPTO_H
|
||||
#define SYNOR_CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ============================================================================
|
||||
* Constants
|
||||
* ============================================================================ */
|
||||
|
||||
#define SYNOR_CRYPTO_ED25519_PUBLIC_KEY_SIZE 32
|
||||
#define SYNOR_CRYPTO_ED25519_SECRET_KEY_SIZE 32
|
||||
#define SYNOR_CRYPTO_ED25519_SIGNATURE_SIZE 64
|
||||
#define SYNOR_CRYPTO_DILITHIUM3_PUBLIC_KEY_SIZE 1952
|
||||
#define SYNOR_CRYPTO_DILITHIUM3_SIGNATURE_SIZE 3293
|
||||
#define SYNOR_CRYPTO_HYBRID_SIGNATURE_SIZE (64 + 3293)
|
||||
#define SYNOR_CRYPTO_COIN_TYPE 0x5359
|
||||
#define SYNOR_CRYPTO_MIN_PBKDF2_ITERATIONS 10000
|
||||
#define SYNOR_CRYPTO_MIN_SALT_LENGTH 8
|
||||
#define SYNOR_CRYPTO_DEFAULT_ENDPOINT "https://crypto.synor.io/v1"
|
||||
|
||||
#define SYNOR_CRYPTO_MAX_PHRASE_LEN 1024
|
||||
#define SYNOR_CRYPTO_MAX_ADDRESS_LEN 128
|
||||
#define SYNOR_CRYPTO_MAX_HASH_LEN 64
|
||||
#define SYNOR_CRYPTO_MAX_ERROR_LEN 256
|
||||
|
||||
/* ============================================================================
|
||||
* Enumerations
|
||||
* ============================================================================ */
|
||||
|
||||
typedef enum {
|
||||
SYNOR_NETWORK_MAINNET = 0,
|
||||
SYNOR_NETWORK_TESTNET = 1,
|
||||
SYNOR_NETWORK_DEVNET = 2
|
||||
} synor_network_t;
|
||||
|
||||
typedef enum {
|
||||
SYNOR_FALCON_512 = 0,
|
||||
SYNOR_FALCON_1024 = 1
|
||||
} synor_falcon_variant_t;
|
||||
|
||||
typedef enum {
|
||||
SYNOR_SPHINCS_SHAKE128S = 0,
|
||||
SYNOR_SPHINCS_SHAKE192S = 1,
|
||||
SYNOR_SPHINCS_SHAKE256S = 2
|
||||
} synor_sphincs_variant_t;
|
||||
|
||||
typedef enum {
|
||||
SYNOR_PQ_DILITHIUM3 = 0,
|
||||
SYNOR_PQ_FALCON512 = 1,
|
||||
SYNOR_PQ_FALCON1024 = 2,
|
||||
SYNOR_PQ_SPHINCS128S = 3,
|
||||
SYNOR_PQ_SPHINCS192S = 4,
|
||||
SYNOR_PQ_SPHINCS256S = 5
|
||||
} synor_pq_algorithm_t;
|
||||
|
||||
typedef enum {
|
||||
SYNOR_ALGO_FAMILY_CLASSICAL = 0,
|
||||
SYNOR_ALGO_FAMILY_LATTICE = 1,
|
||||
SYNOR_ALGO_FAMILY_HASH_BASED = 2,
|
||||
SYNOR_ALGO_FAMILY_HYBRID = 3
|
||||
} synor_algorithm_family_t;
|
||||
|
||||
typedef enum {
|
||||
SYNOR_OK = 0,
|
||||
SYNOR_ERROR_INVALID_PARAMS = -1,
|
||||
SYNOR_ERROR_NETWORK = -2,
|
||||
SYNOR_ERROR_AUTH = -3,
|
||||
SYNOR_ERROR_TIMEOUT = -4,
|
||||
SYNOR_ERROR_API = -5,
|
||||
SYNOR_ERROR_CLOSED = -6,
|
||||
SYNOR_ERROR_MEMORY = -7,
|
||||
SYNOR_ERROR_PARSE = -8
|
||||
} synor_error_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Configuration Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
const char* api_key;
|
||||
const char* endpoint;
|
||||
uint32_t timeout;
|
||||
uint32_t retries;
|
||||
bool debug;
|
||||
synor_network_t default_network;
|
||||
} synor_crypto_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* salt;
|
||||
size_t salt_len;
|
||||
uint8_t* info;
|
||||
size_t info_len;
|
||||
uint32_t output_length;
|
||||
} synor_derivation_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* salt;
|
||||
size_t salt_len;
|
||||
uint32_t iterations;
|
||||
uint32_t output_length;
|
||||
} synor_password_derivation_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t account;
|
||||
uint32_t change;
|
||||
uint32_t index;
|
||||
} synor_derivation_path_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Key Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
char ed25519[88]; /* Base64 encoded */
|
||||
char dilithium[2608]; /* Base64 encoded */
|
||||
} synor_hybrid_public_key_t;
|
||||
|
||||
typedef struct {
|
||||
char ed25519_seed[88]; /* Base64 encoded */
|
||||
char master_seed[88]; /* Base64 encoded */
|
||||
} synor_secret_key_t;
|
||||
|
||||
typedef struct {
|
||||
synor_falcon_variant_t variant;
|
||||
uint8_t* bytes;
|
||||
size_t bytes_len;
|
||||
} synor_falcon_public_key_t;
|
||||
|
||||
typedef struct {
|
||||
synor_falcon_variant_t variant;
|
||||
uint8_t* bytes;
|
||||
size_t bytes_len;
|
||||
} synor_falcon_secret_key_t;
|
||||
|
||||
typedef struct {
|
||||
synor_sphincs_variant_t variant;
|
||||
uint8_t* bytes;
|
||||
size_t bytes_len;
|
||||
} synor_sphincs_public_key_t;
|
||||
|
||||
typedef struct {
|
||||
synor_sphincs_variant_t variant;
|
||||
uint8_t* bytes;
|
||||
size_t bytes_len;
|
||||
} synor_sphincs_secret_key_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Signature Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
char ed25519[88]; /* Base64 encoded */
|
||||
char dilithium[4392]; /* Base64 encoded */
|
||||
} synor_hybrid_signature_t;
|
||||
|
||||
typedef struct {
|
||||
synor_falcon_variant_t variant;
|
||||
uint8_t* signature;
|
||||
size_t signature_len;
|
||||
} synor_falcon_signature_t;
|
||||
|
||||
typedef struct {
|
||||
synor_sphincs_variant_t variant;
|
||||
uint8_t* signature;
|
||||
size_t signature_len;
|
||||
} synor_sphincs_signature_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Keypair Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
synor_hybrid_public_key_t public_key;
|
||||
synor_secret_key_t secret_key;
|
||||
char addresses[3][SYNOR_CRYPTO_MAX_ADDRESS_LEN]; /* mainnet, testnet, devnet */
|
||||
} synor_hybrid_keypair_t;
|
||||
|
||||
typedef struct {
|
||||
synor_falcon_variant_t variant;
|
||||
synor_falcon_public_key_t public_key;
|
||||
synor_falcon_secret_key_t secret_key;
|
||||
} synor_falcon_keypair_t;
|
||||
|
||||
typedef struct {
|
||||
synor_sphincs_variant_t variant;
|
||||
synor_sphincs_public_key_t public_key;
|
||||
synor_sphincs_secret_key_t secret_key;
|
||||
} synor_sphincs_keypair_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Mnemonic Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
char phrase[SYNOR_CRYPTO_MAX_PHRASE_LEN];
|
||||
char** words;
|
||||
size_t word_count;
|
||||
uint8_t* entropy;
|
||||
size_t entropy_len;
|
||||
} synor_mnemonic_t;
|
||||
|
||||
typedef struct {
|
||||
bool valid;
|
||||
char error[SYNOR_CRYPTO_MAX_ERROR_LEN];
|
||||
} synor_mnemonic_validation_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Address Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
char address[SYNOR_CRYPTO_MAX_ADDRESS_LEN];
|
||||
synor_network_t network;
|
||||
uint8_t pubkey_hash[32];
|
||||
} synor_address_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Hash Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
char hex[SYNOR_CRYPTO_MAX_HASH_LEN + 1];
|
||||
uint8_t bytes[32];
|
||||
} synor_hash256_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Client Handle
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct synor_crypto_s synor_crypto_t;
|
||||
|
||||
/* ============================================================================
|
||||
* Client Lifecycle
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Create a new Synor Crypto client.
|
||||
*
|
||||
* @param config Configuration for the client
|
||||
* @return Pointer to the client handle, or NULL on error
|
||||
*/
|
||||
synor_crypto_t* synor_crypto_create(const synor_crypto_config_t* config);
|
||||
|
||||
/**
|
||||
* Destroy a Synor Crypto client and free resources.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
*/
|
||||
void synor_crypto_destroy(synor_crypto_t* crypto);
|
||||
|
||||
/**
|
||||
* Check if the client has been closed.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @return true if closed, false otherwise
|
||||
*/
|
||||
bool synor_crypto_is_closed(const synor_crypto_t* crypto);
|
||||
|
||||
/**
|
||||
* Perform a health check on the API.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @return SYNOR_OK if healthy, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_health_check(synor_crypto_t* crypto);
|
||||
|
||||
/* ============================================================================
|
||||
* Mnemonic Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Generate a new mnemonic phrase.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param word_count Number of words (12, 15, 18, 21, or 24)
|
||||
* @param out Output mnemonic
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_mnemonic_generate(
|
||||
synor_crypto_t* crypto,
|
||||
uint32_t word_count,
|
||||
synor_mnemonic_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Parse an existing mnemonic phrase.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param phrase Mnemonic phrase
|
||||
* @param out Output mnemonic
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_mnemonic_from_phrase(
|
||||
synor_crypto_t* crypto,
|
||||
const char* phrase,
|
||||
synor_mnemonic_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Validate a mnemonic phrase.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param phrase Mnemonic phrase to validate
|
||||
* @param out Validation result
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_mnemonic_validate(
|
||||
synor_crypto_t* crypto,
|
||||
const char* phrase,
|
||||
synor_mnemonic_validation_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Convert a mnemonic phrase to a seed.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param phrase Mnemonic phrase
|
||||
* @param passphrase Optional passphrase (can be empty string)
|
||||
* @param seed Output seed buffer (must be at least 64 bytes)
|
||||
* @param seed_len Output seed length
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_mnemonic_to_seed(
|
||||
synor_crypto_t* crypto,
|
||||
const char* phrase,
|
||||
const char* passphrase,
|
||||
uint8_t* seed,
|
||||
size_t* seed_len
|
||||
);
|
||||
|
||||
/**
|
||||
* Free mnemonic resources.
|
||||
*
|
||||
* @param mnemonic Mnemonic to free
|
||||
*/
|
||||
void synor_crypto_mnemonic_free(synor_mnemonic_t* mnemonic);
|
||||
|
||||
/* ============================================================================
|
||||
* Keypair Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Generate a new hybrid keypair.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param out Output keypair
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_keypair_generate(
|
||||
synor_crypto_t* crypto,
|
||||
synor_hybrid_keypair_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a keypair from a mnemonic phrase.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param phrase Mnemonic phrase
|
||||
* @param passphrase Optional passphrase (can be empty string)
|
||||
* @param out Output keypair
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_keypair_from_mnemonic(
|
||||
synor_crypto_t* crypto,
|
||||
const char* phrase,
|
||||
const char* passphrase,
|
||||
synor_hybrid_keypair_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a keypair from a seed.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param seed Seed bytes
|
||||
* @param seed_len Seed length
|
||||
* @param out Output keypair
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_keypair_from_seed(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* seed,
|
||||
size_t seed_len,
|
||||
synor_hybrid_keypair_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Get an address for a public key.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param public_key Public key
|
||||
* @param network Network type
|
||||
* @param out Output address
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_keypair_get_address(
|
||||
synor_crypto_t* crypto,
|
||||
const synor_hybrid_public_key_t* public_key,
|
||||
synor_network_t network,
|
||||
synor_address_t* out
|
||||
);
|
||||
|
||||
/* ============================================================================
|
||||
* Signing Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Sign a message with a hybrid keypair.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param keypair Keypair for signing
|
||||
* @param message Message to sign
|
||||
* @param message_len Message length
|
||||
* @param out Output signature
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_sign(
|
||||
synor_crypto_t* crypto,
|
||||
const synor_hybrid_keypair_t* keypair,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
synor_hybrid_signature_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify a hybrid signature.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param public_key Public key for verification
|
||||
* @param message Original message
|
||||
* @param message_len Message length
|
||||
* @param signature Signature to verify
|
||||
* @param valid Output: true if valid, false otherwise
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_verify(
|
||||
synor_crypto_t* crypto,
|
||||
const synor_hybrid_public_key_t* public_key,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
const synor_hybrid_signature_t* signature,
|
||||
bool* valid
|
||||
);
|
||||
|
||||
/**
|
||||
* Sign a message with Ed25519.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param secret_key Ed25519 secret key (32 bytes)
|
||||
* @param message Message to sign
|
||||
* @param message_len Message length
|
||||
* @param signature Output signature (64 bytes)
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_sign_ed25519(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* secret_key,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
uint8_t* signature
|
||||
);
|
||||
|
||||
/* ============================================================================
|
||||
* Falcon Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Generate a Falcon keypair.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param variant Falcon variant (512 or 1024)
|
||||
* @param out Output keypair
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_falcon_generate(
|
||||
synor_crypto_t* crypto,
|
||||
synor_falcon_variant_t variant,
|
||||
synor_falcon_keypair_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Sign a message with Falcon.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param keypair Falcon keypair
|
||||
* @param message Message to sign
|
||||
* @param message_len Message length
|
||||
* @param out Output signature
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_falcon_sign(
|
||||
synor_crypto_t* crypto,
|
||||
const synor_falcon_keypair_t* keypair,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
synor_falcon_signature_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify a Falcon signature.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param public_key Falcon public key
|
||||
* @param public_key_len Public key length
|
||||
* @param message Original message
|
||||
* @param message_len Message length
|
||||
* @param signature Signature to verify
|
||||
* @param valid Output: true if valid, false otherwise
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_falcon_verify(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* public_key,
|
||||
size_t public_key_len,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
const synor_falcon_signature_t* signature,
|
||||
bool* valid
|
||||
);
|
||||
|
||||
/**
|
||||
* Free Falcon keypair resources.
|
||||
*
|
||||
* @param keypair Keypair to free
|
||||
*/
|
||||
void synor_crypto_falcon_keypair_free(synor_falcon_keypair_t* keypair);
|
||||
|
||||
/**
|
||||
* Free Falcon signature resources.
|
||||
*
|
||||
* @param signature Signature to free
|
||||
*/
|
||||
void synor_crypto_falcon_signature_free(synor_falcon_signature_t* signature);
|
||||
|
||||
/* ============================================================================
|
||||
* SPHINCS+ Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Generate a SPHINCS+ keypair.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param variant SPHINCS+ variant
|
||||
* @param out Output keypair
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_sphincs_generate(
|
||||
synor_crypto_t* crypto,
|
||||
synor_sphincs_variant_t variant,
|
||||
synor_sphincs_keypair_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Sign a message with SPHINCS+.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param keypair SPHINCS+ keypair
|
||||
* @param message Message to sign
|
||||
* @param message_len Message length
|
||||
* @param out Output signature
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_sphincs_sign(
|
||||
synor_crypto_t* crypto,
|
||||
const synor_sphincs_keypair_t* keypair,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
synor_sphincs_signature_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify a SPHINCS+ signature.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param public_key SPHINCS+ public key
|
||||
* @param public_key_len Public key length
|
||||
* @param message Original message
|
||||
* @param message_len Message length
|
||||
* @param signature Signature to verify
|
||||
* @param valid Output: true if valid, false otherwise
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_sphincs_verify(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* public_key,
|
||||
size_t public_key_len,
|
||||
const uint8_t* message,
|
||||
size_t message_len,
|
||||
const synor_sphincs_signature_t* signature,
|
||||
bool* valid
|
||||
);
|
||||
|
||||
/**
|
||||
* Free SPHINCS+ keypair resources.
|
||||
*
|
||||
* @param keypair Keypair to free
|
||||
*/
|
||||
void synor_crypto_sphincs_keypair_free(synor_sphincs_keypair_t* keypair);
|
||||
|
||||
/**
|
||||
* Free SPHINCS+ signature resources.
|
||||
*
|
||||
* @param signature Signature to free
|
||||
*/
|
||||
void synor_crypto_sphincs_signature_free(synor_sphincs_signature_t* signature);
|
||||
|
||||
/* ============================================================================
|
||||
* KDF Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Derive a key using HKDF.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param seed Input seed
|
||||
* @param seed_len Seed length
|
||||
* @param config Derivation configuration
|
||||
* @param key Output key buffer
|
||||
* @param key_len Output key length
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_kdf_derive(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* seed,
|
||||
size_t seed_len,
|
||||
const synor_derivation_config_t* config,
|
||||
uint8_t* key,
|
||||
size_t* key_len
|
||||
);
|
||||
|
||||
/**
|
||||
* Derive a key from a password using PBKDF2.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param password Password bytes
|
||||
* @param password_len Password length
|
||||
* @param config Password derivation configuration
|
||||
* @param key Output key buffer
|
||||
* @param key_len Output key length
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_kdf_derive_password(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* password,
|
||||
size_t password_len,
|
||||
const synor_password_derivation_config_t* config,
|
||||
uint8_t* key,
|
||||
size_t* key_len
|
||||
);
|
||||
|
||||
/* ============================================================================
|
||||
* Hash Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Compute SHA3-256 hash.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param data Input data
|
||||
* @param data_len Data length
|
||||
* @param out Output hash
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_hash_sha3_256(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
synor_hash256_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Compute BLAKE3 hash.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param data Input data
|
||||
* @param data_len Data length
|
||||
* @param out Output hash
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_hash_blake3(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
synor_hash256_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* Compute Keccak-256 hash.
|
||||
*
|
||||
* @param crypto Client handle
|
||||
* @param data Input data
|
||||
* @param data_len Data length
|
||||
* @param out Output hash
|
||||
* @return SYNOR_OK on success, error code otherwise
|
||||
*/
|
||||
synor_error_t synor_crypto_hash_keccak256(
|
||||
synor_crypto_t* crypto,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
synor_hash256_t* out
|
||||
);
|
||||
|
||||
/* ============================================================================
|
||||
* Utility Functions
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Get the signature size for a Falcon variant.
|
||||
*
|
||||
* @param variant Falcon variant
|
||||
* @return Signature size in bytes
|
||||
*/
|
||||
size_t synor_crypto_falcon_signature_size(synor_falcon_variant_t variant);
|
||||
|
||||
/**
|
||||
* Get the public key size for a Falcon variant.
|
||||
*
|
||||
* @param variant Falcon variant
|
||||
* @return Public key size in bytes
|
||||
*/
|
||||
size_t synor_crypto_falcon_public_key_size(synor_falcon_variant_t variant);
|
||||
|
||||
/**
|
||||
* Get the signature size for a SPHINCS+ variant.
|
||||
*
|
||||
* @param variant SPHINCS+ variant
|
||||
* @return Signature size in bytes
|
||||
*/
|
||||
size_t synor_crypto_sphincs_signature_size(synor_sphincs_variant_t variant);
|
||||
|
||||
/**
|
||||
* Get the network name string.
|
||||
*
|
||||
* @param network Network type
|
||||
* @return Network name ("mainnet", "testnet", or "devnet")
|
||||
*/
|
||||
const char* synor_crypto_network_name(synor_network_t network);
|
||||
|
||||
/**
|
||||
* Get human-readable error message.
|
||||
*
|
||||
* @param error Error code
|
||||
* @return Error message string
|
||||
*/
|
||||
const char* synor_crypto_error_message(synor_error_t error);
|
||||
|
||||
/**
|
||||
* Format a derivation path as a string.
|
||||
*
|
||||
* @param path Derivation path
|
||||
* @param buffer Output buffer
|
||||
* @param buffer_size Buffer size
|
||||
* @return Number of characters written (excluding null terminator)
|
||||
*/
|
||||
int synor_crypto_derivation_path_format(
|
||||
const synor_derivation_path_t* path,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SYNOR_CRYPTO_H */
|
||||
524
sdk/cpp/include/synor/crypto.hpp
Normal file
524
sdk/cpp/include/synor/crypto.hpp
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
/**
|
||||
* Synor Crypto SDK for C++
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* #include <synor/crypto.hpp>
|
||||
*
|
||||
* int main() {
|
||||
* synor::crypto::Config config{"your-api-key"};
|
||||
* synor::crypto::SynorCrypto crypto{config};
|
||||
*
|
||||
* // Generate a mnemonic
|
||||
* auto mnemonic = crypto.mnemonic().generate(24).get();
|
||||
* std::cout << "Backup words: " << mnemonic.phrase << std::endl;
|
||||
*
|
||||
* // Create keypair from mnemonic
|
||||
* auto keypair = crypto.keypairs().from_mnemonic(mnemonic.phrase, "").get();
|
||||
* auto address = keypair.get_address(synor::crypto::Network::Mainnet);
|
||||
*
|
||||
* // Sign a message
|
||||
* auto signature = crypto.signing().sign(keypair, "Hello!").get();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
#ifndef SYNOR_CRYPTO_HPP
|
||||
#define SYNOR_CRYPTO_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
|
||||
namespace synor {
|
||||
namespace crypto {
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
constexpr int ED25519_PUBLIC_KEY_SIZE = 32;
|
||||
constexpr int ED25519_SECRET_KEY_SIZE = 32;
|
||||
constexpr int ED25519_SIGNATURE_SIZE = 64;
|
||||
constexpr int DILITHIUM3_PUBLIC_KEY_SIZE = 1952;
|
||||
constexpr int DILITHIUM3_SIGNATURE_SIZE = 3293;
|
||||
constexpr int HYBRID_SIGNATURE_SIZE = 64 + 3293;
|
||||
constexpr int COIN_TYPE = 0x5359;
|
||||
constexpr int MIN_PBKDF2_ITERATIONS = 10000;
|
||||
constexpr int MIN_SALT_LENGTH = 8;
|
||||
constexpr const char* DEFAULT_ENDPOINT = "https://crypto.synor.io/v1";
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
enum class Network {
|
||||
Mainnet,
|
||||
Testnet,
|
||||
Devnet
|
||||
};
|
||||
|
||||
inline std::string to_string(Network n) {
|
||||
switch (n) {
|
||||
case Network::Mainnet: return "mainnet";
|
||||
case Network::Testnet: return "testnet";
|
||||
case Network::Devnet: return "devnet";
|
||||
}
|
||||
return "mainnet";
|
||||
}
|
||||
|
||||
inline Network network_from_string(const std::string& s) {
|
||||
if (s == "testnet") return Network::Testnet;
|
||||
if (s == "devnet") return Network::Devnet;
|
||||
return Network::Mainnet;
|
||||
}
|
||||
|
||||
enum class FalconVariant {
|
||||
Falcon512,
|
||||
Falcon1024
|
||||
};
|
||||
|
||||
inline std::string to_string(FalconVariant v) {
|
||||
switch (v) {
|
||||
case FalconVariant::Falcon512: return "falcon512";
|
||||
case FalconVariant::Falcon1024: return "falcon1024";
|
||||
}
|
||||
return "falcon512";
|
||||
}
|
||||
|
||||
inline int signature_size(FalconVariant v) {
|
||||
return v == FalconVariant::Falcon512 ? 690 : 1330;
|
||||
}
|
||||
|
||||
inline int public_key_size(FalconVariant v) {
|
||||
return v == FalconVariant::Falcon512 ? 897 : 1793;
|
||||
}
|
||||
|
||||
inline int security_level(FalconVariant v) {
|
||||
return v == FalconVariant::Falcon512 ? 128 : 256;
|
||||
}
|
||||
|
||||
enum class SphincsVariant {
|
||||
Shake128s,
|
||||
Shake192s,
|
||||
Shake256s
|
||||
};
|
||||
|
||||
inline std::string to_string(SphincsVariant v) {
|
||||
switch (v) {
|
||||
case SphincsVariant::Shake128s: return "shake128s";
|
||||
case SphincsVariant::Shake192s: return "shake192s";
|
||||
case SphincsVariant::Shake256s: return "shake256s";
|
||||
}
|
||||
return "shake128s";
|
||||
}
|
||||
|
||||
inline int signature_size(SphincsVariant v) {
|
||||
switch (v) {
|
||||
case SphincsVariant::Shake128s: return 7856;
|
||||
case SphincsVariant::Shake192s: return 16224;
|
||||
case SphincsVariant::Shake256s: return 29792;
|
||||
}
|
||||
return 7856;
|
||||
}
|
||||
|
||||
inline int security_level(SphincsVariant v) {
|
||||
switch (v) {
|
||||
case SphincsVariant::Shake128s: return 128;
|
||||
case SphincsVariant::Shake192s: return 192;
|
||||
case SphincsVariant::Shake256s: return 256;
|
||||
}
|
||||
return 128;
|
||||
}
|
||||
|
||||
enum class PqAlgorithm {
|
||||
Dilithium3,
|
||||
Falcon512,
|
||||
Falcon1024,
|
||||
Sphincs128s,
|
||||
Sphincs192s,
|
||||
Sphincs256s
|
||||
};
|
||||
|
||||
enum class AlgorithmFamily {
|
||||
Classical,
|
||||
Lattice,
|
||||
HashBased,
|
||||
Hybrid
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
class CryptoException : public std::runtime_error {
|
||||
public:
|
||||
explicit CryptoException(const std::string& message, const std::string& code = "")
|
||||
: std::runtime_error(message), code_(code) {}
|
||||
|
||||
const std::string& code() const { return code_; }
|
||||
|
||||
private:
|
||||
std::string code_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
struct Config {
|
||||
std::string api_key;
|
||||
std::string endpoint = DEFAULT_ENDPOINT;
|
||||
int timeout = 30000;
|
||||
int retries = 3;
|
||||
bool debug = false;
|
||||
Network default_network = Network::Mainnet;
|
||||
|
||||
explicit Config(std::string api_key) : api_key(std::move(api_key)) {}
|
||||
};
|
||||
|
||||
struct DerivationConfig {
|
||||
std::vector<uint8_t> salt;
|
||||
std::vector<uint8_t> info;
|
||||
int output_length = 32;
|
||||
};
|
||||
|
||||
struct PasswordDerivationConfig {
|
||||
std::vector<uint8_t> salt;
|
||||
int iterations = 100000;
|
||||
int output_length = 32;
|
||||
|
||||
explicit PasswordDerivationConfig(std::vector<uint8_t> salt)
|
||||
: salt(std::move(salt)) {}
|
||||
};
|
||||
|
||||
struct DerivationPath {
|
||||
static constexpr int COIN_TYPE = 0x5359;
|
||||
|
||||
int account;
|
||||
int change;
|
||||
int index;
|
||||
|
||||
static DerivationPath external_path(int account, int index) {
|
||||
return {account, 0, index};
|
||||
}
|
||||
|
||||
static DerivationPath internal_path(int account, int index) {
|
||||
return {account, 1, index};
|
||||
}
|
||||
|
||||
std::string to_string() const {
|
||||
return "m/44'/" + std::to_string(COIN_TYPE) + "'/" +
|
||||
std::to_string(account) + "'/" +
|
||||
std::to_string(change) + "/" +
|
||||
std::to_string(index);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
struct HybridPublicKey {
|
||||
std::string ed25519;
|
||||
std::string dilithium;
|
||||
|
||||
std::vector<uint8_t> ed25519_bytes() const;
|
||||
std::vector<uint8_t> dilithium_bytes() const;
|
||||
size_t size() const;
|
||||
};
|
||||
|
||||
struct SecretKey {
|
||||
std::string ed25519_seed;
|
||||
std::string master_seed;
|
||||
|
||||
std::vector<uint8_t> ed25519_seed_bytes() const;
|
||||
std::vector<uint8_t> master_seed_bytes() const;
|
||||
};
|
||||
|
||||
struct FalconPublicKey {
|
||||
FalconVariant variant;
|
||||
std::vector<uint8_t> bytes;
|
||||
};
|
||||
|
||||
struct FalconSecretKey {
|
||||
FalconVariant variant;
|
||||
std::vector<uint8_t> bytes;
|
||||
};
|
||||
|
||||
struct SphincsPublicKey {
|
||||
SphincsVariant variant;
|
||||
std::vector<uint8_t> bytes;
|
||||
};
|
||||
|
||||
struct SphincsSecretKey {
|
||||
SphincsVariant variant;
|
||||
std::vector<uint8_t> bytes;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
struct HybridSignature {
|
||||
std::string ed25519;
|
||||
std::string dilithium;
|
||||
|
||||
std::vector<uint8_t> ed25519_bytes() const;
|
||||
std::vector<uint8_t> dilithium_bytes() const;
|
||||
size_t size() const;
|
||||
std::vector<uint8_t> to_bytes() const;
|
||||
};
|
||||
|
||||
struct FalconSignature {
|
||||
FalconVariant variant;
|
||||
std::vector<uint8_t> signature;
|
||||
|
||||
size_t size() const { return signature.size(); }
|
||||
};
|
||||
|
||||
struct SphincsSignature {
|
||||
SphincsVariant variant;
|
||||
std::vector<uint8_t> signature;
|
||||
|
||||
size_t size() const { return signature.size(); }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
struct HybridKeypair {
|
||||
HybridPublicKey public_key;
|
||||
SecretKey secret_key;
|
||||
std::map<std::string, std::string> addresses;
|
||||
|
||||
std::string get_address(Network network) const;
|
||||
};
|
||||
|
||||
struct FalconKeypair {
|
||||
FalconVariant variant;
|
||||
FalconPublicKey public_key;
|
||||
FalconSecretKey secret_key;
|
||||
};
|
||||
|
||||
struct SphincsKeypair {
|
||||
SphincsVariant variant;
|
||||
SphincsPublicKey public_key;
|
||||
SphincsSecretKey secret_key;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
struct Mnemonic {
|
||||
std::string phrase;
|
||||
std::vector<std::string> words;
|
||||
int word_count = 0;
|
||||
std::vector<uint8_t> entropy;
|
||||
|
||||
std::vector<std::string> get_words() const;
|
||||
int get_word_count() const;
|
||||
};
|
||||
|
||||
struct MnemonicValidation {
|
||||
bool valid;
|
||||
std::optional<std::string> error;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
struct Address {
|
||||
std::string address;
|
||||
Network network;
|
||||
std::vector<uint8_t> pubkey_hash;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
struct Hash256 {
|
||||
std::string hash;
|
||||
|
||||
std::string hex() const { return hash; }
|
||||
std::vector<uint8_t> bytes() const;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Forward Declarations
|
||||
// ============================================================================
|
||||
|
||||
class SynorCrypto;
|
||||
|
||||
// ============================================================================
|
||||
// Sub-Clients
|
||||
// ============================================================================
|
||||
|
||||
class MnemonicClient {
|
||||
public:
|
||||
explicit MnemonicClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<Mnemonic> generate(int word_count = 24);
|
||||
std::future<Mnemonic> from_phrase(const std::string& phrase);
|
||||
std::future<MnemonicValidation> validate(const std::string& phrase);
|
||||
std::future<std::vector<uint8_t>> to_seed(const std::string& phrase, const std::string& passphrase = "");
|
||||
std::future<std::vector<std::string>> suggest_words(const std::string& partial, int limit = 5);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class KeypairClient {
|
||||
public:
|
||||
explicit KeypairClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<HybridKeypair> generate();
|
||||
std::future<HybridKeypair> from_mnemonic(const std::string& phrase, const std::string& passphrase = "");
|
||||
std::future<HybridKeypair> from_seed(const std::vector<uint8_t>& seed);
|
||||
std::future<Address> get_address(const HybridPublicKey& public_key, Network network);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class SigningClient {
|
||||
public:
|
||||
explicit SigningClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<HybridSignature> sign(const HybridKeypair& keypair, const std::vector<uint8_t>& message);
|
||||
std::future<HybridSignature> sign(const HybridKeypair& keypair, const std::string& message);
|
||||
std::future<bool> verify(const HybridPublicKey& public_key, const std::vector<uint8_t>& message, const HybridSignature& signature);
|
||||
std::future<std::vector<uint8_t>> sign_ed25519(const std::vector<uint8_t>& secret_key, const std::vector<uint8_t>& message);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class FalconClient {
|
||||
public:
|
||||
explicit FalconClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<FalconKeypair> generate(FalconVariant variant = FalconVariant::Falcon512);
|
||||
std::future<FalconSignature> sign(const FalconKeypair& keypair, const std::vector<uint8_t>& message);
|
||||
std::future<bool> verify(const std::vector<uint8_t>& public_key, const std::vector<uint8_t>& message, const FalconSignature& signature);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class SphincsClient {
|
||||
public:
|
||||
explicit SphincsClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<SphincsKeypair> generate(SphincsVariant variant = SphincsVariant::Shake128s);
|
||||
std::future<SphincsSignature> sign(const SphincsKeypair& keypair, const std::vector<uint8_t>& message);
|
||||
std::future<bool> verify(const std::vector<uint8_t>& public_key, const std::vector<uint8_t>& message, const SphincsSignature& signature);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class KdfClient {
|
||||
public:
|
||||
explicit KdfClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<std::vector<uint8_t>> derive_key(const std::vector<uint8_t>& seed, const DerivationConfig& config = {});
|
||||
std::future<std::vector<uint8_t>> derive_from_password(const std::vector<uint8_t>& password, const PasswordDerivationConfig& config);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
class HashClient {
|
||||
public:
|
||||
explicit HashClient(SynorCrypto* crypto) : crypto_(crypto) {}
|
||||
|
||||
std::future<Hash256> sha3_256(const std::vector<uint8_t>& data);
|
||||
std::future<Hash256> blake3(const std::vector<uint8_t>& data);
|
||||
std::future<Hash256> keccak256(const std::vector<uint8_t>& data);
|
||||
|
||||
private:
|
||||
SynorCrypto* crypto_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Main Client
|
||||
// ============================================================================
|
||||
|
||||
class SynorCrypto {
|
||||
public:
|
||||
explicit SynorCrypto(const Config& config);
|
||||
~SynorCrypto();
|
||||
|
||||
// Non-copyable
|
||||
SynorCrypto(const SynorCrypto&) = delete;
|
||||
SynorCrypto& operator=(const SynorCrypto&) = delete;
|
||||
|
||||
// Movable
|
||||
SynorCrypto(SynorCrypto&&) noexcept;
|
||||
SynorCrypto& operator=(SynorCrypto&&) noexcept;
|
||||
|
||||
// Accessors
|
||||
Network default_network() const { return config_.default_network; }
|
||||
bool is_closed() const { return closed_; }
|
||||
|
||||
// Sub-clients
|
||||
MnemonicClient& mnemonic() { return mnemonic_client_; }
|
||||
KeypairClient& keypairs() { return keypair_client_; }
|
||||
SigningClient& signing() { return signing_client_; }
|
||||
FalconClient& falcon() { return falcon_client_; }
|
||||
SphincsClient& sphincs() { return sphincs_client_; }
|
||||
KdfClient& kdf() { return kdf_client_; }
|
||||
HashClient& hash() { return hash_client_; }
|
||||
|
||||
// Operations
|
||||
std::future<bool> health_check();
|
||||
void close();
|
||||
|
||||
// Internal HTTP methods (used by sub-clients)
|
||||
template<typename T>
|
||||
std::future<T> get(const std::string& path);
|
||||
|
||||
template<typename T>
|
||||
std::future<T> post(const std::string& path, const std::map<std::string, std::string>& body = {});
|
||||
|
||||
private:
|
||||
void check_closed() const;
|
||||
|
||||
Config config_;
|
||||
bool closed_ = false;
|
||||
|
||||
MnemonicClient mnemonic_client_;
|
||||
KeypairClient keypair_client_;
|
||||
SigningClient signing_client_;
|
||||
FalconClient falcon_client_;
|
||||
SphincsClient sphincs_client_;
|
||||
KdfClient kdf_client_;
|
||||
HashClient hash_client_;
|
||||
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t>& data);
|
||||
std::vector<uint8_t> base64_decode(const std::string& encoded);
|
||||
|
||||
} // namespace crypto
|
||||
} // namespace synor
|
||||
|
||||
#endif // SYNOR_CRYPTO_HPP
|
||||
336
sdk/csharp/src/Synor.Crypto/SynorCrypto.cs
Normal file
336
sdk/csharp/src/Synor.Crypto/SynorCrypto.cs
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Synor.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Synor Crypto SDK for C#/.NET
|
||||
///
|
||||
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
/// </summary>
|
||||
public class SynorCrypto : IDisposable
|
||||
{
|
||||
private readonly CryptoConfig _config;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private bool _closed;
|
||||
|
||||
public MnemonicClient Mnemonic { get; }
|
||||
public KeypairClient Keypairs { get; }
|
||||
public SigningClient Signing { get; }
|
||||
public FalconClient Falcon { get; }
|
||||
public SphincsClient Sphincs { get; }
|
||||
public KdfClient Kdf { get; }
|
||||
public HashClient Hash { get; }
|
||||
|
||||
public SynorCrypto(CryptoConfig config)
|
||||
{
|
||||
_config = config;
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(config.Endpoint),
|
||||
Timeout = TimeSpan.FromMilliseconds(config.Timeout)
|
||||
};
|
||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
|
||||
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
|
||||
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) }
|
||||
};
|
||||
|
||||
Mnemonic = new MnemonicClient(this);
|
||||
Keypairs = new KeypairClient(this);
|
||||
Signing = new SigningClient(this);
|
||||
Falcon = new FalconClient(this);
|
||||
Sphincs = new SphincsClient(this);
|
||||
Kdf = new KdfClient(this);
|
||||
Hash = new HashClient(this);
|
||||
}
|
||||
|
||||
public Network DefaultNetwork => _config.DefaultNetwork;
|
||||
|
||||
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await GetAsync<Dictionary<string, object>>("/health", ct);
|
||||
return result.TryGetValue("status", out var status) && status?.ToString() == "healthy";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetInfoAsync(CancellationToken ct = default) =>
|
||||
GetAsync<Dictionary<string, object>>("/info", ct);
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
|
||||
public bool IsClosed => _closed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
|
||||
{
|
||||
CheckClosed();
|
||||
var response = await _httpClient.GetAsync(path, ct);
|
||||
return await HandleResponseAsync<T>(response);
|
||||
}
|
||||
|
||||
internal async Task<T> PostAsync<T>(string path, object? body = null, CancellationToken ct = default)
|
||||
{
|
||||
CheckClosed();
|
||||
var content = body != null
|
||||
? JsonContent.Create(body, options: _jsonOptions)
|
||||
: JsonContent.Create(new { });
|
||||
var response = await _httpClient.PostAsync(path, content, ct);
|
||||
return await HandleResponseAsync<T>(response);
|
||||
}
|
||||
|
||||
private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response)
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(json, _jsonOptions) ?? new();
|
||||
throw new CryptoException(
|
||||
error.TryGetValue("message", out var msg) ? msg?.ToString() ?? $"HTTP {(int)response.StatusCode}" : $"HTTP {(int)response.StatusCode}",
|
||||
error.TryGetValue("code", out var code) ? code?.ToString() : null,
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(json, _jsonOptions)!;
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (_closed) throw new CryptoException("Client has been closed", "CLIENT_CLOSED");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mnemonic sub-client</summary>
|
||||
public class MnemonicClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal MnemonicClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<Mnemonic> GenerateAsync(int wordCount = 24, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Mnemonic>("/mnemonic/generate", new { word_count = wordCount }, ct);
|
||||
|
||||
public Task<Mnemonic> FromPhraseAsync(string phrase, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Mnemonic>("/mnemonic/from-phrase", new { phrase }, ct);
|
||||
|
||||
public Task<MnemonicValidation> ValidateAsync(string phrase, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<MnemonicValidation>("/mnemonic/validate", new { phrase }, ct);
|
||||
|
||||
public async Task<byte[]> ToSeedAsync(string phrase, string passphrase = "", CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<SeedResponse>("/mnemonic/to-seed", new { phrase, passphrase }, ct);
|
||||
return Convert.FromBase64String(result.Seed);
|
||||
}
|
||||
|
||||
public async Task<List<string>> SuggestWordsAsync(string partial, int limit = 5, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<SuggestResponse>("/mnemonic/suggest", new { partial, limit }, ct);
|
||||
return result.Suggestions ?? new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Keypair sub-client</summary>
|
||||
public class KeypairClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal KeypairClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<HybridKeypair> GenerateAsync(CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<HybridKeypair>("/keypair/generate", null, ct);
|
||||
|
||||
public Task<HybridKeypair> FromMnemonicAsync(string phrase, string passphrase = "", CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<HybridKeypair>("/keypair/from-mnemonic", new { phrase, passphrase }, ct);
|
||||
|
||||
public Task<HybridKeypair> FromSeedAsync(byte[] seed, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<HybridKeypair>("/keypair/from-seed", new { seed = Convert.ToBase64String(seed) }, ct);
|
||||
|
||||
public Task<Address> GetAddressAsync(HybridPublicKey publicKey, Network network, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Address>("/keypair/address", new
|
||||
{
|
||||
public_key = publicKey.ToDict(),
|
||||
network = network.ToApiString()
|
||||
}, ct);
|
||||
}
|
||||
|
||||
/// <summary>Signing sub-client</summary>
|
||||
public class SigningClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal SigningClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<HybridSignature> SignAsync(HybridKeypair keypair, byte[] message, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<HybridSignature>("/sign/hybrid", new
|
||||
{
|
||||
secret_key = keypair.SecretKey.ToDict(),
|
||||
message = Convert.ToBase64String(message)
|
||||
}, ct);
|
||||
|
||||
public async Task<bool> VerifyAsync(HybridPublicKey publicKey, byte[] message, HybridSignature signature, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<ValidResponse>("/sign/verify", new
|
||||
{
|
||||
public_key = publicKey.ToDict(),
|
||||
message = Convert.ToBase64String(message),
|
||||
signature = signature.ToDict()
|
||||
}, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
|
||||
public async Task<byte[]> SignEd25519Async(byte[] secretKey, byte[] message, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<SignatureResponse>("/sign/ed25519", new
|
||||
{
|
||||
secret_key = Convert.ToBase64String(secretKey),
|
||||
message = Convert.ToBase64String(message)
|
||||
}, ct);
|
||||
return Convert.FromBase64String(result.Signature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Falcon sub-client</summary>
|
||||
public class FalconClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal FalconClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<FalconKeypair> GenerateAsync(FalconVariant variant = FalconVariant.Falcon512, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<FalconKeypair>("/falcon/generate", new { variant = variant.ToApiString() }, ct);
|
||||
|
||||
public Task<FalconSignature> SignAsync(FalconKeypair keypair, byte[] message, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<FalconSignature>("/falcon/sign", new
|
||||
{
|
||||
variant = keypair.VariantEnum.ToApiString(),
|
||||
secret_key = Convert.ToBase64String(keypair.SecretKey.KeyBytes),
|
||||
message = Convert.ToBase64String(message)
|
||||
}, ct);
|
||||
|
||||
public async Task<bool> VerifyAsync(byte[] publicKey, byte[] message, FalconSignature signature, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<ValidResponse>("/falcon/verify", new
|
||||
{
|
||||
variant = signature.VariantEnum.ToApiString(),
|
||||
public_key = Convert.ToBase64String(publicKey),
|
||||
message = Convert.ToBase64String(message),
|
||||
signature = Convert.ToBase64String(signature.SignatureBytes)
|
||||
}, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ sub-client</summary>
|
||||
public class SphincsClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal SphincsClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<SphincsKeypair> GenerateAsync(SphincsVariant variant = SphincsVariant.Shake128s, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<SphincsKeypair>("/sphincs/generate", new { variant = variant.ToApiString() }, ct);
|
||||
|
||||
public Task<SphincsSignature> SignAsync(SphincsKeypair keypair, byte[] message, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<SphincsSignature>("/sphincs/sign", new
|
||||
{
|
||||
variant = keypair.VariantEnum.ToApiString(),
|
||||
secret_key = Convert.ToBase64String(keypair.SecretKey.KeyBytes),
|
||||
message = Convert.ToBase64String(message)
|
||||
}, ct);
|
||||
|
||||
public async Task<bool> VerifyAsync(byte[] publicKey, byte[] message, SphincsSignature signature, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<ValidResponse>("/sphincs/verify", new
|
||||
{
|
||||
variant = signature.VariantEnum.ToApiString(),
|
||||
public_key = Convert.ToBase64String(publicKey),
|
||||
message = Convert.ToBase64String(message),
|
||||
signature = Convert.ToBase64String(signature.SignatureBytes)
|
||||
}, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>KDF sub-client</summary>
|
||||
public class KdfClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal KdfClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public async Task<byte[]> DeriveKeyAsync(byte[] seed, DerivationConfig? config = null, CancellationToken ct = default)
|
||||
{
|
||||
config ??= new DerivationConfig();
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
["seed"] = Convert.ToBase64String(seed),
|
||||
["output_length"] = config.OutputLength
|
||||
};
|
||||
if (config.Salt != null) body["salt"] = Convert.ToBase64String(config.Salt);
|
||||
if (config.Info != null) body["info"] = Convert.ToBase64String(config.Info);
|
||||
|
||||
var result = await _crypto.PostAsync<KeyResponse>("/kdf/hkdf", body, ct);
|
||||
return Convert.FromBase64String(result.Key);
|
||||
}
|
||||
|
||||
public async Task<byte[]> DeriveFromPasswordAsync(byte[] password, PasswordDerivationConfig config, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _crypto.PostAsync<KeyResponse>("/kdf/pbkdf2", new
|
||||
{
|
||||
password = Convert.ToBase64String(password),
|
||||
salt = Convert.ToBase64String(config.Salt),
|
||||
iterations = config.Iterations,
|
||||
output_length = config.OutputLength
|
||||
}, ct);
|
||||
return Convert.FromBase64String(result.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Hash sub-client</summary>
|
||||
public class HashClient
|
||||
{
|
||||
private readonly SynorCrypto _crypto;
|
||||
|
||||
internal HashClient(SynorCrypto crypto) => _crypto = crypto;
|
||||
|
||||
public Task<Hash256> Sha3_256Async(byte[] data, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Hash256>("/hash/sha3-256", new { data = Convert.ToBase64String(data) }, ct);
|
||||
|
||||
public Task<Hash256> Blake3Async(byte[] data, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Hash256>("/hash/blake3", new { data = Convert.ToBase64String(data) }, ct);
|
||||
|
||||
public Task<Hash256> Keccak256Async(byte[] data, CancellationToken ct = default) =>
|
||||
_crypto.PostAsync<Hash256>("/hash/keccak256", new { data = Convert.ToBase64String(data) }, ct);
|
||||
}
|
||||
|
||||
// Response helper types
|
||||
internal record SeedResponse(string Seed);
|
||||
internal record SuggestResponse(List<string>? Suggestions);
|
||||
internal record SignatureResponse(string Signature);
|
||||
internal record KeyResponse(string Key);
|
||||
internal record ValidResponse(bool Valid);
|
||||
505
sdk/csharp/src/Synor.Crypto/Types.cs
Normal file
505
sdk/csharp/src/Synor.Crypto/Types.cs
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Synor.Crypto;
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Network type for the Synor blockchain.</summary>
|
||||
public enum Network
|
||||
{
|
||||
Mainnet,
|
||||
Testnet,
|
||||
Devnet
|
||||
}
|
||||
|
||||
public static class NetworkExtensions
|
||||
{
|
||||
public static string ToApiString(this Network network) => network switch
|
||||
{
|
||||
Network.Mainnet => "mainnet",
|
||||
Network.Testnet => "testnet",
|
||||
Network.Devnet => "devnet",
|
||||
_ => "mainnet"
|
||||
};
|
||||
|
||||
public static Network FromApiString(string value) => value switch
|
||||
{
|
||||
"mainnet" => Network.Mainnet,
|
||||
"testnet" => Network.Testnet,
|
||||
"devnet" => Network.Devnet,
|
||||
_ => Network.Mainnet
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Falcon signature variant.</summary>
|
||||
public enum FalconVariant
|
||||
{
|
||||
Falcon512,
|
||||
Falcon1024
|
||||
}
|
||||
|
||||
public static class FalconVariantExtensions
|
||||
{
|
||||
public static string ToApiString(this FalconVariant variant) => variant switch
|
||||
{
|
||||
FalconVariant.Falcon512 => "falcon512",
|
||||
FalconVariant.Falcon1024 => "falcon1024",
|
||||
_ => "falcon512"
|
||||
};
|
||||
|
||||
public static int SignatureSize(this FalconVariant variant) => variant switch
|
||||
{
|
||||
FalconVariant.Falcon512 => 690,
|
||||
FalconVariant.Falcon1024 => 1330,
|
||||
_ => 690
|
||||
};
|
||||
|
||||
public static int PublicKeySize(this FalconVariant variant) => variant switch
|
||||
{
|
||||
FalconVariant.Falcon512 => 897,
|
||||
FalconVariant.Falcon1024 => 1793,
|
||||
_ => 897
|
||||
};
|
||||
|
||||
public static int SecurityLevel(this FalconVariant variant) => variant switch
|
||||
{
|
||||
FalconVariant.Falcon512 => 128,
|
||||
FalconVariant.Falcon1024 => 256,
|
||||
_ => 128
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ signature variant.</summary>
|
||||
public enum SphincsVariant
|
||||
{
|
||||
Shake128s,
|
||||
Shake192s,
|
||||
Shake256s
|
||||
}
|
||||
|
||||
public static class SphincsVariantExtensions
|
||||
{
|
||||
public static string ToApiString(this SphincsVariant variant) => variant switch
|
||||
{
|
||||
SphincsVariant.Shake128s => "shake128s",
|
||||
SphincsVariant.Shake192s => "shake192s",
|
||||
SphincsVariant.Shake256s => "shake256s",
|
||||
_ => "shake128s"
|
||||
};
|
||||
|
||||
public static int SignatureSize(this SphincsVariant variant) => variant switch
|
||||
{
|
||||
SphincsVariant.Shake128s => 7856,
|
||||
SphincsVariant.Shake192s => 16224,
|
||||
SphincsVariant.Shake256s => 29792,
|
||||
_ => 7856
|
||||
};
|
||||
|
||||
public static int SecurityLevel(this SphincsVariant variant) => variant switch
|
||||
{
|
||||
SphincsVariant.Shake128s => 128,
|
||||
SphincsVariant.Shake192s => 192,
|
||||
SphincsVariant.Shake256s => 256,
|
||||
_ => 128
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Post-quantum algorithm types.</summary>
|
||||
public enum PqAlgorithm
|
||||
{
|
||||
Dilithium3,
|
||||
Falcon512,
|
||||
Falcon1024,
|
||||
Sphincs128s,
|
||||
Sphincs192s,
|
||||
Sphincs256s
|
||||
}
|
||||
|
||||
/// <summary>Cryptographic algorithm family.</summary>
|
||||
public enum AlgorithmFamily
|
||||
{
|
||||
Classical,
|
||||
Lattice,
|
||||
HashBased,
|
||||
Hybrid
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Configuration for the Crypto SDK.</summary>
|
||||
public record CryptoConfig
|
||||
{
|
||||
public required string ApiKey { get; init; }
|
||||
public string Endpoint { get; init; } = "https://crypto.synor.io/v1";
|
||||
public int Timeout { get; init; } = 30000;
|
||||
public int Retries { get; init; } = 3;
|
||||
public bool Debug { get; init; } = false;
|
||||
public Network DefaultNetwork { get; init; } = Network.Mainnet;
|
||||
}
|
||||
|
||||
/// <summary>Configuration for key derivation.</summary>
|
||||
public record DerivationConfig
|
||||
{
|
||||
public byte[]? Salt { get; init; }
|
||||
public byte[]? Info { get; init; }
|
||||
public int OutputLength { get; init; } = 32;
|
||||
}
|
||||
|
||||
/// <summary>Configuration for password-based key derivation.</summary>
|
||||
public record PasswordDerivationConfig
|
||||
{
|
||||
public required byte[] Salt { get; init; }
|
||||
public int Iterations { get; init; } = 100000;
|
||||
public int OutputLength { get; init; } = 32;
|
||||
}
|
||||
|
||||
/// <summary>BIP-44 derivation path.</summary>
|
||||
public record DerivationPath(int Account, int Change, int Index)
|
||||
{
|
||||
public const int CoinType = 0x5359;
|
||||
|
||||
public static DerivationPath External(int account, int index) => new(account, 0, index);
|
||||
public static DerivationPath Internal(int account, int index) => new(account, 1, index);
|
||||
|
||||
public override string ToString() => $"m/44'/{CoinType}'/{Account}'/{Change}/{Index}";
|
||||
|
||||
public Dictionary<string, int> ToDict() => new()
|
||||
{
|
||||
["account"] = Account,
|
||||
["change"] = Change,
|
||||
["index"] = Index
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Hybrid public key combining Ed25519 and Dilithium3.</summary>
|
||||
public record HybridPublicKey
|
||||
{
|
||||
public string Ed25519 { get; init; } = "";
|
||||
public string Dilithium { get; init; } = "";
|
||||
|
||||
public byte[] Ed25519Bytes => Convert.FromBase64String(Ed25519);
|
||||
public byte[] DilithiumBytes => Convert.FromBase64String(Dilithium);
|
||||
public int Size => Ed25519Bytes.Length + DilithiumBytes.Length;
|
||||
|
||||
public Dictionary<string, string> ToDict() => new()
|
||||
{
|
||||
["ed25519"] = Ed25519,
|
||||
["dilithium"] = Dilithium
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Secret key for hybrid signatures.</summary>
|
||||
public record SecretKey
|
||||
{
|
||||
[JsonPropertyName("ed25519_seed")]
|
||||
public string Ed25519Seed { get; init; } = "";
|
||||
|
||||
[JsonPropertyName("master_seed")]
|
||||
public string MasterSeed { get; init; } = "";
|
||||
|
||||
public byte[] Ed25519SeedBytes => Convert.FromBase64String(Ed25519Seed);
|
||||
public byte[] MasterSeedBytes => Convert.FromBase64String(MasterSeed);
|
||||
|
||||
public Dictionary<string, string> ToDict() => new()
|
||||
{
|
||||
["ed25519_seed"] = Ed25519Seed,
|
||||
["master_seed"] = MasterSeed
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Falcon public key.</summary>
|
||||
public record FalconPublicKey
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Bytes { get; init; } = "";
|
||||
|
||||
public FalconVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"falcon512" => FalconVariant.Falcon512,
|
||||
"falcon1024" => FalconVariant.Falcon1024,
|
||||
_ => FalconVariant.Falcon512
|
||||
};
|
||||
|
||||
public byte[] KeyBytes => Convert.FromBase64String(Bytes);
|
||||
}
|
||||
|
||||
/// <summary>Falcon secret key.</summary>
|
||||
public record FalconSecretKey
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Bytes { get; init; } = "";
|
||||
|
||||
public FalconVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"falcon512" => FalconVariant.Falcon512,
|
||||
"falcon1024" => FalconVariant.Falcon1024,
|
||||
_ => FalconVariant.Falcon512
|
||||
};
|
||||
|
||||
public byte[] KeyBytes => Convert.FromBase64String(Bytes);
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ public key.</summary>
|
||||
public record SphincsPublicKey
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Bytes { get; init; } = "";
|
||||
|
||||
public SphincsVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"shake128s" => SphincsVariant.Shake128s,
|
||||
"shake192s" => SphincsVariant.Shake192s,
|
||||
"shake256s" => SphincsVariant.Shake256s,
|
||||
_ => SphincsVariant.Shake128s
|
||||
};
|
||||
|
||||
public byte[] KeyBytes => Convert.FromBase64String(Bytes);
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ secret key.</summary>
|
||||
public record SphincsSecretKey
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Bytes { get; init; } = "";
|
||||
|
||||
public SphincsVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"shake128s" => SphincsVariant.Shake128s,
|
||||
"shake192s" => SphincsVariant.Shake192s,
|
||||
"shake256s" => SphincsVariant.Shake256s,
|
||||
_ => SphincsVariant.Shake128s
|
||||
};
|
||||
|
||||
public byte[] KeyBytes => Convert.FromBase64String(Bytes);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Hybrid signature combining Ed25519 and Dilithium3.</summary>
|
||||
public record HybridSignature
|
||||
{
|
||||
public string Ed25519 { get; init; } = "";
|
||||
public string Dilithium { get; init; } = "";
|
||||
|
||||
public byte[] Ed25519Bytes => Convert.FromBase64String(Ed25519);
|
||||
public byte[] DilithiumBytes => Convert.FromBase64String(Dilithium);
|
||||
public int Size => Ed25519Bytes.Length + DilithiumBytes.Length;
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var e = Ed25519Bytes;
|
||||
var d = DilithiumBytes;
|
||||
var result = new byte[e.Length + d.Length];
|
||||
Buffer.BlockCopy(e, 0, result, 0, e.Length);
|
||||
Buffer.BlockCopy(d, 0, result, e.Length, d.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ToDict() => new()
|
||||
{
|
||||
["ed25519"] = Ed25519,
|
||||
["dilithium"] = Dilithium
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Falcon signature.</summary>
|
||||
public record FalconSignature
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Signature { get; init; } = "";
|
||||
|
||||
public FalconVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"falcon512" => FalconVariant.Falcon512,
|
||||
"falcon1024" => FalconVariant.Falcon1024,
|
||||
_ => FalconVariant.Falcon512
|
||||
};
|
||||
|
||||
public byte[] SignatureBytes => Convert.FromBase64String(Signature);
|
||||
public int Size => SignatureBytes.Length;
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ signature.</summary>
|
||||
public record SphincsSignature
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
public string Signature { get; init; } = "";
|
||||
|
||||
public SphincsVariant VariantEnum => Variant.ToLower() switch
|
||||
{
|
||||
"shake128s" => SphincsVariant.Shake128s,
|
||||
"shake192s" => SphincsVariant.Shake192s,
|
||||
"shake256s" => SphincsVariant.Shake256s,
|
||||
_ => SphincsVariant.Shake128s
|
||||
};
|
||||
|
||||
public byte[] SignatureBytes => Convert.FromBase64String(Signature);
|
||||
public int Size => SignatureBytes.Length;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Hybrid keypair for Ed25519 + Dilithium3.</summary>
|
||||
public record HybridKeypair
|
||||
{
|
||||
[JsonPropertyName("public_key")]
|
||||
public HybridPublicKey PublicKey { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("secret_key")]
|
||||
public SecretKey SecretKey { get; init; } = new();
|
||||
|
||||
public Dictionary<string, string> Addresses { get; init; } = new();
|
||||
|
||||
public string GetAddress(Network network) =>
|
||||
Addresses.TryGetValue(network.ToApiString(), out var addr) ? addr : "";
|
||||
}
|
||||
|
||||
/// <summary>Falcon keypair.</summary>
|
||||
public record FalconKeypair
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
|
||||
[JsonPropertyName("public_key")]
|
||||
public FalconPublicKey PublicKey { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("secret_key")]
|
||||
public FalconSecretKey SecretKey { get; init; } = new();
|
||||
|
||||
public FalconVariant VariantEnum => PublicKey.VariantEnum;
|
||||
}
|
||||
|
||||
/// <summary>SPHINCS+ keypair.</summary>
|
||||
public record SphincsKeypair
|
||||
{
|
||||
public string Variant { get; init; } = "";
|
||||
|
||||
[JsonPropertyName("public_key")]
|
||||
public SphincsPublicKey PublicKey { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("secret_key")]
|
||||
public SphincsSecretKey SecretKey { get; init; } = new();
|
||||
|
||||
public SphincsVariant VariantEnum => PublicKey.VariantEnum;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>BIP-39 mnemonic phrase.</summary>
|
||||
public record Mnemonic
|
||||
{
|
||||
public string Phrase { get; init; } = "";
|
||||
public List<string>? Words { get; init; }
|
||||
|
||||
[JsonPropertyName("word_count")]
|
||||
public int WordCount { get; init; }
|
||||
|
||||
public string? Entropy { get; init; }
|
||||
|
||||
public List<string> GetWords() => Words ?? new List<string>(Phrase.Split(' '));
|
||||
public int GetWordCount() => WordCount > 0 ? WordCount : GetWords().Count;
|
||||
public byte[] GetEntropy() => Entropy != null ? Convert.FromBase64String(Entropy) : Array.Empty<byte>();
|
||||
}
|
||||
|
||||
/// <summary>Mnemonic validation result.</summary>
|
||||
public record MnemonicValidation
|
||||
{
|
||||
public bool Valid { get; init; }
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Blockchain address.</summary>
|
||||
public record Address
|
||||
{
|
||||
[JsonPropertyName("address")]
|
||||
public string AddressString { get; init; } = "";
|
||||
|
||||
public string NetworkString { get; init; } = "";
|
||||
|
||||
[JsonPropertyName("pubkey_hash")]
|
||||
public string? PubkeyHash { get; init; }
|
||||
|
||||
public Network Network => NetworkExtensions.FromApiString(NetworkString);
|
||||
public byte[] PubkeyHashBytes => PubkeyHash != null ? Convert.FromBase64String(PubkeyHash) : Array.Empty<byte>();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>256-bit hash result.</summary>
|
||||
public record Hash256
|
||||
{
|
||||
public string Hash { get; init; } = "";
|
||||
|
||||
public string Hex => Hash;
|
||||
|
||||
public byte[] Bytes
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new byte[Hash.Length / 2];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
result[i] = Convert.ToByte(Hash.Substring(i * 2, 2), 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Crypto SDK exception.</summary>
|
||||
public class CryptoException : Exception
|
||||
{
|
||||
public string? Code { get; }
|
||||
public int? HttpStatus { get; }
|
||||
|
||||
public CryptoException(string message, string? code = null, int? httpStatus = null)
|
||||
: base(message)
|
||||
{
|
||||
Code = code;
|
||||
HttpStatus = httpStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Cryptographic constants.</summary>
|
||||
public static class CryptoConstants
|
||||
{
|
||||
public const int Ed25519PublicKeySize = 32;
|
||||
public const int Ed25519SecretKeySize = 32;
|
||||
public const int Ed25519SignatureSize = 64;
|
||||
public const int Dilithium3PublicKeySize = 1952;
|
||||
public const int Dilithium3SignatureSize = 3293;
|
||||
public const int HybridSignatureSize = 64 + 3293;
|
||||
public const int CoinType = 0x5359;
|
||||
public const int MinPbkdf2Iterations = 10000;
|
||||
public const int MinSaltLength = 8;
|
||||
public const string DefaultEndpoint = "https://crypto.synor.io/v1";
|
||||
}
|
||||
466
sdk/flutter/lib/src/crypto/client.dart
Normal file
466
sdk/flutter/lib/src/crypto/client.dart
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
/// Synor Crypto SDK Client for Flutter/Dart
|
||||
///
|
||||
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
library synor_crypto;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'types.dart';
|
||||
|
||||
export 'types.dart';
|
||||
|
||||
/// Main Synor Crypto client
|
||||
class SynorCrypto {
|
||||
final CryptoConfig _config;
|
||||
final http.Client _client;
|
||||
bool _closed = false;
|
||||
|
||||
late final MnemonicClient mnemonic;
|
||||
late final KeypairClient keypairs;
|
||||
late final SigningClient signing;
|
||||
late final FalconClient falcon;
|
||||
late final SphincsClient sphincs;
|
||||
late final KdfClient kdf;
|
||||
late final HashClient hash;
|
||||
late final NegotiationClient negotiation;
|
||||
|
||||
SynorCrypto(this._config) : _client = http.Client() {
|
||||
mnemonic = MnemonicClient(this);
|
||||
keypairs = KeypairClient(this);
|
||||
signing = SigningClient(this);
|
||||
falcon = FalconClient(this);
|
||||
sphincs = SphincsClient(this);
|
||||
kdf = KdfClient(this);
|
||||
hash = HashClient(this);
|
||||
negotiation = NegotiationClient(this);
|
||||
}
|
||||
|
||||
/// Returns the default network
|
||||
Network get defaultNetwork => _config.defaultNetwork;
|
||||
|
||||
/// Checks service health
|
||||
Future<bool> healthCheck() async {
|
||||
try {
|
||||
final result = await _get('/health');
|
||||
return result['status'] == 'healthy';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets service info
|
||||
Future<Map<String, dynamic>> getInfo() => _get('/info');
|
||||
|
||||
/// Closes the client
|
||||
void close() {
|
||||
_closed = true;
|
||||
_client.close();
|
||||
}
|
||||
|
||||
/// Returns true if closed
|
||||
bool get isClosed => _closed;
|
||||
|
||||
Future<Map<String, dynamic>> _get(String path) async {
|
||||
_checkClosed();
|
||||
final response = await _client.get(
|
||||
Uri.parse('${_config.endpoint}$path'),
|
||||
headers: _headers(),
|
||||
);
|
||||
return _handleResponse(response);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _post(String path, [Map<String, dynamic>? body]) async {
|
||||
_checkClosed();
|
||||
final response = await _client.post(
|
||||
Uri.parse('${_config.endpoint}$path'),
|
||||
headers: _headers(),
|
||||
body: body != null ? jsonEncode(body) : null,
|
||||
);
|
||||
return _handleResponse(response);
|
||||
}
|
||||
|
||||
Map<String, String> _headers() => {
|
||||
'Authorization': 'Bearer ${_config.apiKey}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'dart/0.1.0',
|
||||
};
|
||||
|
||||
Map<String, dynamic> _handleResponse(http.Response response) {
|
||||
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
if (response.statusCode >= 400) {
|
||||
throw CryptoException(
|
||||
json['message'] as String? ?? 'HTTP ${response.statusCode}',
|
||||
json['code'] as String?,
|
||||
);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
void _checkClosed() {
|
||||
if (_closed) {
|
||||
throw const CryptoException('Client has been closed', 'CLIENT_CLOSED');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mnemonic operations
|
||||
class MnemonicClient {
|
||||
final SynorCrypto _crypto;
|
||||
MnemonicClient(this._crypto);
|
||||
|
||||
/// Generates a new random mnemonic
|
||||
Future<Mnemonic> generate([int wordCount = 24]) async {
|
||||
final result = await _crypto._post('/mnemonic/generate', {'word_count': wordCount});
|
||||
return Mnemonic.fromJson(result);
|
||||
}
|
||||
|
||||
/// Creates a mnemonic from a phrase
|
||||
Future<Mnemonic> fromPhrase(String phrase) async {
|
||||
final result = await _crypto._post('/mnemonic/from-phrase', {'phrase': phrase});
|
||||
return Mnemonic.fromJson(result);
|
||||
}
|
||||
|
||||
/// Creates a mnemonic from entropy
|
||||
Future<Mnemonic> fromEntropy(Uint8List entropy) async {
|
||||
final result = await _crypto._post('/mnemonic/from-entropy', {
|
||||
'entropy': base64Encode(entropy),
|
||||
});
|
||||
return Mnemonic.fromJson(result);
|
||||
}
|
||||
|
||||
/// Validates a mnemonic phrase
|
||||
Future<MnemonicValidation> validate(String phrase) async {
|
||||
final result = await _crypto._post('/mnemonic/validate', {'phrase': phrase});
|
||||
return MnemonicValidation.fromJson(result);
|
||||
}
|
||||
|
||||
/// Derives a seed from a mnemonic
|
||||
Future<Uint8List> toSeed(String phrase, [String passphrase = '']) async {
|
||||
final result = await _crypto._post('/mnemonic/to-seed', {
|
||||
'phrase': phrase,
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
return base64Decode(result['seed'] as String);
|
||||
}
|
||||
|
||||
/// Suggests word completions
|
||||
Future<List<String>> suggestWords(String partial, [int limit = 10]) async {
|
||||
final result = await _crypto._post('/mnemonic/suggest', {
|
||||
'partial': partial,
|
||||
'limit': limit,
|
||||
});
|
||||
return List<String>.from(result['suggestions'] as List);
|
||||
}
|
||||
}
|
||||
|
||||
/// Keypair operations
|
||||
class KeypairClient {
|
||||
final SynorCrypto _crypto;
|
||||
KeypairClient(this._crypto);
|
||||
|
||||
/// Generates a new random keypair
|
||||
Future<HybridKeypair> generate() async {
|
||||
final result = await _crypto._post('/keypair/generate');
|
||||
return HybridKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Creates a keypair from a mnemonic
|
||||
Future<HybridKeypair> fromMnemonic(String phrase, [String passphrase = '']) async {
|
||||
final result = await _crypto._post('/keypair/from-mnemonic', {
|
||||
'phrase': phrase,
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
return HybridKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Creates a keypair from a seed
|
||||
Future<HybridKeypair> fromSeed(Uint8List seed) async {
|
||||
final result = await _crypto._post('/keypair/from-seed', {
|
||||
'seed': base64Encode(seed),
|
||||
});
|
||||
return HybridKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Derives a child keypair
|
||||
Future<HybridKeypair> derive(HybridKeypair parent, DerivationPath path) async {
|
||||
final result = await _crypto._post('/keypair/derive', {
|
||||
'public_key': parent.publicKey.toJson(),
|
||||
'path': path.toJson(),
|
||||
});
|
||||
return HybridKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Gets the address for a public key
|
||||
Future<Address> getAddress(HybridPublicKey publicKey, Network network) async {
|
||||
final result = await _crypto._post('/keypair/address', {
|
||||
'public_key': publicKey.toJson(),
|
||||
'network': network.toJson(),
|
||||
});
|
||||
return Address.fromJson(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Signing operations
|
||||
class SigningClient {
|
||||
final SynorCrypto _crypto;
|
||||
SigningClient(this._crypto);
|
||||
|
||||
/// Signs a message with a hybrid keypair
|
||||
Future<HybridSignature> sign(HybridKeypair keypair, Uint8List message) async {
|
||||
final result = await _crypto._post('/sign/hybrid', {
|
||||
'secret_key': {
|
||||
'ed25519_seed': base64Encode(keypair.secretKey.ed25519Seed),
|
||||
'master_seed': base64Encode(keypair.secretKey.masterSeed),
|
||||
},
|
||||
'message': base64Encode(message),
|
||||
});
|
||||
return HybridSignature.fromJson(result);
|
||||
}
|
||||
|
||||
/// Verifies a hybrid signature
|
||||
Future<bool> verify(
|
||||
HybridPublicKey publicKey,
|
||||
Uint8List message,
|
||||
HybridSignature signature,
|
||||
) async {
|
||||
final result = await _crypto._post('/sign/verify', {
|
||||
'public_key': publicKey.toJson(),
|
||||
'message': base64Encode(message),
|
||||
'signature': signature.toJson(),
|
||||
});
|
||||
return result['valid'] as bool;
|
||||
}
|
||||
|
||||
/// Signs with Ed25519 only
|
||||
Future<Uint8List> signEd25519(Uint8List secretKey, Uint8List message) async {
|
||||
final result = await _crypto._post('/sign/ed25519', {
|
||||
'secret_key': base64Encode(secretKey),
|
||||
'message': base64Encode(message),
|
||||
});
|
||||
return base64Decode(result['signature'] as String);
|
||||
}
|
||||
|
||||
/// Verifies an Ed25519 signature
|
||||
Future<bool> verifyEd25519(
|
||||
Uint8List publicKey,
|
||||
Uint8List message,
|
||||
Uint8List signature,
|
||||
) async {
|
||||
final result = await _crypto._post('/sign/verify-ed25519', {
|
||||
'public_key': base64Encode(publicKey),
|
||||
'message': base64Encode(message),
|
||||
'signature': base64Encode(signature),
|
||||
});
|
||||
return result['valid'] as bool;
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon operations
|
||||
class FalconClient {
|
||||
final SynorCrypto _crypto;
|
||||
FalconClient(this._crypto);
|
||||
|
||||
/// Generates a Falcon keypair
|
||||
Future<FalconKeypair> generate([FalconVariant variant = FalconVariant.falcon512]) async {
|
||||
final result = await _crypto._post('/falcon/generate', {'variant': variant.toJson()});
|
||||
return FalconKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Signs with Falcon
|
||||
Future<FalconSignature> sign(FalconKeypair keypair, Uint8List message) async {
|
||||
final result = await _crypto._post('/falcon/sign', {
|
||||
'variant': keypair.variant.toJson(),
|
||||
'secret_key': base64Encode(keypair.secretKey.bytes),
|
||||
'message': base64Encode(message),
|
||||
});
|
||||
return FalconSignature.fromJson(result);
|
||||
}
|
||||
|
||||
/// Verifies a Falcon signature
|
||||
Future<bool> verify(
|
||||
Uint8List publicKey,
|
||||
Uint8List message,
|
||||
FalconSignature signature,
|
||||
) async {
|
||||
final result = await _crypto._post('/falcon/verify', {
|
||||
'variant': signature.variant.toJson(),
|
||||
'public_key': base64Encode(publicKey),
|
||||
'message': base64Encode(message),
|
||||
'signature': base64Encode(signature.bytes),
|
||||
});
|
||||
return result['valid'] as bool;
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ operations
|
||||
class SphincsClient {
|
||||
final SynorCrypto _crypto;
|
||||
SphincsClient(this._crypto);
|
||||
|
||||
/// Generates a SPHINCS+ keypair
|
||||
Future<SphincsKeypair> generate([SphincsVariant variant = SphincsVariant.shake128s]) async {
|
||||
final result = await _crypto._post('/sphincs/generate', {'variant': variant.toJson()});
|
||||
return SphincsKeypair.fromJson(result);
|
||||
}
|
||||
|
||||
/// Signs with SPHINCS+
|
||||
Future<SphincsSignature> sign(SphincsKeypair keypair, Uint8List message) async {
|
||||
final result = await _crypto._post('/sphincs/sign', {
|
||||
'variant': keypair.variant.toJson(),
|
||||
'secret_key': base64Encode(keypair.secretKey.bytes),
|
||||
'message': base64Encode(message),
|
||||
});
|
||||
return SphincsSignature.fromJson(result);
|
||||
}
|
||||
|
||||
/// Verifies a SPHINCS+ signature
|
||||
Future<bool> verify(
|
||||
Uint8List publicKey,
|
||||
Uint8List message,
|
||||
SphincsSignature signature,
|
||||
) async {
|
||||
final result = await _crypto._post('/sphincs/verify', {
|
||||
'variant': signature.variant.toJson(),
|
||||
'public_key': base64Encode(publicKey),
|
||||
'message': base64Encode(message),
|
||||
'signature': base64Encode(signature.bytes),
|
||||
});
|
||||
return result['valid'] as bool;
|
||||
}
|
||||
}
|
||||
|
||||
/// Key derivation operations
|
||||
class KdfClient {
|
||||
final SynorCrypto _crypto;
|
||||
KdfClient(this._crypto);
|
||||
|
||||
/// Derives a key using HKDF
|
||||
Future<Uint8List> deriveKey(Uint8List seed, [DerivationConfig? config]) async {
|
||||
config ??= const DerivationConfig();
|
||||
final body = <String, dynamic>{
|
||||
'seed': base64Encode(seed),
|
||||
'output_length': config.outputLength,
|
||||
};
|
||||
if (config.salt != null) body['salt'] = base64Encode(config.salt!);
|
||||
if (config.info != null) body['info'] = base64Encode(config.info!);
|
||||
|
||||
final result = await _crypto._post('/kdf/hkdf', body);
|
||||
return base64Decode(result['key'] as String);
|
||||
}
|
||||
|
||||
/// Derives a key from a password
|
||||
Future<Uint8List> deriveFromPassword(
|
||||
String password,
|
||||
PasswordDerivationConfig config,
|
||||
) async {
|
||||
final result = await _crypto._post('/kdf/pbkdf2', {
|
||||
'password': base64Encode(utf8.encode(password)),
|
||||
'salt': base64Encode(config.salt),
|
||||
'iterations': config.iterations,
|
||||
'output_length': config.outputLength,
|
||||
});
|
||||
return base64Decode(result['key'] as String);
|
||||
}
|
||||
|
||||
/// Derives a child key
|
||||
Future<({Uint8List key, Uint8List chainCode})> deriveChildKey(
|
||||
Uint8List parentKey,
|
||||
Uint8List chainCode,
|
||||
int index,
|
||||
) async {
|
||||
final result = await _crypto._post('/kdf/child', {
|
||||
'parent_key': base64Encode(parentKey),
|
||||
'chain_code': base64Encode(chainCode),
|
||||
'index': index,
|
||||
});
|
||||
return (
|
||||
key: base64Decode(result['key'] as String),
|
||||
chainCode: base64Decode(result['chain_code'] as String),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hashing operations
|
||||
class HashClient {
|
||||
final SynorCrypto _crypto;
|
||||
HashClient(this._crypto);
|
||||
|
||||
/// Computes SHA3-256 hash
|
||||
Future<Hash256> sha3_256(Uint8List data) async {
|
||||
final result = await _crypto._post('/hash/sha3-256', {
|
||||
'data': base64Encode(data),
|
||||
});
|
||||
return Hash256.fromJson(result);
|
||||
}
|
||||
|
||||
/// Computes BLAKE3 hash
|
||||
Future<Hash256> blake3(Uint8List data) async {
|
||||
final result = await _crypto._post('/hash/blake3', {
|
||||
'data': base64Encode(data),
|
||||
});
|
||||
return Hash256.fromJson(result);
|
||||
}
|
||||
|
||||
/// Computes Keccak-256 hash
|
||||
Future<Hash256> keccak256(Uint8List data) async {
|
||||
final result = await _crypto._post('/hash/keccak256', {
|
||||
'data': base64Encode(data),
|
||||
});
|
||||
return Hash256.fromJson(result);
|
||||
}
|
||||
|
||||
/// Combines multiple hashes
|
||||
Future<Hash256> combine(List<Uint8List> hashes) async {
|
||||
final result = await _crypto._post('/hash/combine', {
|
||||
'hashes': hashes.map(base64Encode).toList(),
|
||||
});
|
||||
return Hash256.fromJson(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Algorithm negotiation operations
|
||||
class NegotiationClient {
|
||||
final SynorCrypto _crypto;
|
||||
NegotiationClient(this._crypto);
|
||||
|
||||
/// Gets local capabilities
|
||||
Future<AlgorithmCapabilities> getCapabilities() async {
|
||||
final result = await _crypto._get('/negotiation/capabilities');
|
||||
return AlgorithmCapabilities.fromJson(result);
|
||||
}
|
||||
|
||||
/// Negotiates with a peer
|
||||
Future<NegotiationResult> negotiate(
|
||||
AlgorithmCapabilities peerCapabilities,
|
||||
NegotiationPolicy policy,
|
||||
) async {
|
||||
final result = await _crypto._post('/negotiation/negotiate', {
|
||||
'peer_capabilities': {
|
||||
'pq_algorithms': peerCapabilities.pqAlgorithms.map((e) => e.toJson()).toList(),
|
||||
'classical': peerCapabilities.classical,
|
||||
'hybrid': peerCapabilities.hybrid,
|
||||
'preferred': peerCapabilities.preferred?.toJson(),
|
||||
},
|
||||
'policy': policy.toJson(),
|
||||
});
|
||||
return NegotiationResult.fromJson(result);
|
||||
}
|
||||
|
||||
/// Establishes session parameters
|
||||
Future<SessionParams> establishSession(
|
||||
NegotiationResult result,
|
||||
Uint8List peerPublicKey,
|
||||
) async {
|
||||
final resp = await _crypto._post('/negotiation/session', {
|
||||
'negotiation_result': {
|
||||
'algorithm': result.algorithm.toJson(),
|
||||
'security_level': result.securityLevel,
|
||||
'family': result.family.toJson(),
|
||||
'hybrid': result.hybrid,
|
||||
},
|
||||
'peer_public_key': base64Encode(peerPublicKey),
|
||||
});
|
||||
return SessionParams.fromJson(resp);
|
||||
}
|
||||
}
|
||||
658
sdk/flutter/lib/src/crypto/types.dart
Normal file
658
sdk/flutter/lib/src/crypto/types.dart
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
/// Synor Crypto SDK Types for Flutter/Dart
|
||||
///
|
||||
/// Quantum-resistant cryptographic types for the Synor blockchain.
|
||||
library synor_crypto_types;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
/// Network type for address generation
|
||||
enum Network {
|
||||
mainnet,
|
||||
testnet,
|
||||
devnet;
|
||||
|
||||
String toJson() => name;
|
||||
static Network fromJson(String json) => Network.values.byName(json);
|
||||
}
|
||||
|
||||
/// Falcon variant selection
|
||||
enum FalconVariant {
|
||||
falcon512,
|
||||
falcon1024;
|
||||
|
||||
String toJson() => name;
|
||||
static FalconVariant fromJson(String json) => FalconVariant.values.byName(json);
|
||||
|
||||
/// Returns the signature size for this variant
|
||||
int get signatureSize => this == falcon512 ? 690 : 1330;
|
||||
|
||||
/// Returns the public key size for this variant
|
||||
int get publicKeySize => this == falcon512 ? 897 : 1793;
|
||||
|
||||
/// Returns the security level in bits
|
||||
int get securityLevel => this == falcon512 ? 128 : 256;
|
||||
}
|
||||
|
||||
/// SPHINCS+ variant selection
|
||||
enum SphincsVariant {
|
||||
shake128s,
|
||||
shake192s,
|
||||
shake256s;
|
||||
|
||||
String toJson() => name;
|
||||
static SphincsVariant fromJson(String json) => SphincsVariant.values.byName(json);
|
||||
|
||||
/// Returns the signature size for this variant
|
||||
int get signatureSize {
|
||||
switch (this) {
|
||||
case shake128s:
|
||||
return 7856;
|
||||
case shake192s:
|
||||
return 16224;
|
||||
case shake256s:
|
||||
return 29792;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the security level in bits
|
||||
int get securityLevel {
|
||||
switch (this) {
|
||||
case shake128s:
|
||||
return 128;
|
||||
case shake192s:
|
||||
return 192;
|
||||
case shake256s:
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Post-quantum algorithm selection
|
||||
enum PqAlgorithm {
|
||||
dilithium3,
|
||||
falcon512,
|
||||
falcon1024,
|
||||
sphincs128s,
|
||||
sphincs192s,
|
||||
sphincs256s;
|
||||
|
||||
String toJson() => name;
|
||||
static PqAlgorithm fromJson(String json) => PqAlgorithm.values.byName(json);
|
||||
}
|
||||
|
||||
/// Algorithm family classification
|
||||
enum AlgorithmFamily {
|
||||
classical,
|
||||
lattice,
|
||||
hashBased,
|
||||
hybrid;
|
||||
|
||||
String toJson() => name;
|
||||
static AlgorithmFamily fromJson(String json) => AlgorithmFamily.values.byName(json);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
/// Crypto SDK configuration
|
||||
class CryptoConfig {
|
||||
final String apiKey;
|
||||
final String endpoint;
|
||||
final int timeout;
|
||||
final int retries;
|
||||
final bool debug;
|
||||
final Network defaultNetwork;
|
||||
|
||||
const CryptoConfig({
|
||||
required this.apiKey,
|
||||
this.endpoint = 'https://crypto.synor.io/v1',
|
||||
this.timeout = 30000,
|
||||
this.retries = 3,
|
||||
this.debug = false,
|
||||
this.defaultNetwork = Network.mainnet,
|
||||
});
|
||||
}
|
||||
|
||||
/// Key derivation configuration
|
||||
class DerivationConfig {
|
||||
final Uint8List? salt;
|
||||
final Uint8List? info;
|
||||
final int outputLength;
|
||||
|
||||
const DerivationConfig({
|
||||
this.salt,
|
||||
this.info,
|
||||
this.outputLength = 32,
|
||||
});
|
||||
}
|
||||
|
||||
/// Password derivation configuration
|
||||
class PasswordDerivationConfig {
|
||||
final Uint8List salt;
|
||||
final int iterations;
|
||||
final int outputLength;
|
||||
|
||||
const PasswordDerivationConfig({
|
||||
required this.salt,
|
||||
this.iterations = 100000,
|
||||
this.outputLength = 32,
|
||||
});
|
||||
}
|
||||
|
||||
/// BIP-44 derivation path
|
||||
class DerivationPath {
|
||||
static const int coinType = 0x5359; // Synor coin type
|
||||
|
||||
final int account;
|
||||
final int change;
|
||||
final int index;
|
||||
|
||||
const DerivationPath({
|
||||
this.account = 0,
|
||||
this.change = 0,
|
||||
this.index = 0,
|
||||
});
|
||||
|
||||
factory DerivationPath.external(int account, int index) =>
|
||||
DerivationPath(account: account, change: 0, index: index);
|
||||
|
||||
factory DerivationPath.internal(int account, int index) =>
|
||||
DerivationPath(account: account, change: 1, index: index);
|
||||
|
||||
@override
|
||||
String toString() => "m/44'/$coinType'/$account'/$change/$index";
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'account': account,
|
||||
'change': change,
|
||||
'index': index,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid public key (Ed25519 + Dilithium3)
|
||||
class HybridPublicKey {
|
||||
/// Ed25519 component (32 bytes)
|
||||
final Uint8List ed25519;
|
||||
|
||||
/// Dilithium3 component (~1952 bytes)
|
||||
final Uint8List dilithium;
|
||||
|
||||
const HybridPublicKey({
|
||||
required this.ed25519,
|
||||
required this.dilithium,
|
||||
});
|
||||
|
||||
/// Returns the total size in bytes
|
||||
int get size => ed25519.length + dilithium.length;
|
||||
|
||||
factory HybridPublicKey.fromJson(Map<String, dynamic> json) => HybridPublicKey(
|
||||
ed25519: base64Decode(json['ed25519'] as String),
|
||||
dilithium: base64Decode(json['dilithium'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'ed25519': base64Encode(ed25519),
|
||||
'dilithium': base64Encode(dilithium),
|
||||
};
|
||||
}
|
||||
|
||||
/// Secret key (master seed)
|
||||
class SecretKey {
|
||||
/// Ed25519 seed (32 bytes)
|
||||
final Uint8List ed25519Seed;
|
||||
|
||||
/// Master seed (64 bytes)
|
||||
final Uint8List masterSeed;
|
||||
|
||||
const SecretKey({
|
||||
required this.ed25519Seed,
|
||||
required this.masterSeed,
|
||||
});
|
||||
|
||||
factory SecretKey.fromJson(Map<String, dynamic> json) => SecretKey(
|
||||
ed25519Seed: base64Decode(json['ed25519_seed'] as String),
|
||||
masterSeed: base64Decode(json['master_seed'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// Falcon public key
|
||||
class FalconPublicKey {
|
||||
final FalconVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const FalconPublicKey({required this.variant, required this.bytes});
|
||||
|
||||
factory FalconPublicKey.fromJson(Map<String, dynamic> json) => FalconPublicKey(
|
||||
variant: FalconVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['bytes'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// Falcon secret key
|
||||
class FalconSecretKey {
|
||||
final FalconVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const FalconSecretKey({required this.variant, required this.bytes});
|
||||
|
||||
factory FalconSecretKey.fromJson(Map<String, dynamic> json) => FalconSecretKey(
|
||||
variant: FalconVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['bytes'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// SPHINCS+ public key
|
||||
class SphincsPublicKey {
|
||||
final SphincsVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const SphincsPublicKey({required this.variant, required this.bytes});
|
||||
|
||||
factory SphincsPublicKey.fromJson(Map<String, dynamic> json) => SphincsPublicKey(
|
||||
variant: SphincsVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['bytes'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// SPHINCS+ secret key
|
||||
class SphincsSecretKey {
|
||||
final SphincsVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const SphincsSecretKey({required this.variant, required this.bytes});
|
||||
|
||||
factory SphincsSecretKey.fromJson(Map<String, dynamic> json) => SphincsSecretKey(
|
||||
variant: SphincsVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['bytes'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid signature (Ed25519 + Dilithium3)
|
||||
class HybridSignature {
|
||||
/// Ed25519 component (64 bytes)
|
||||
final Uint8List ed25519;
|
||||
|
||||
/// Dilithium3 component (~3293 bytes)
|
||||
final Uint8List dilithium;
|
||||
|
||||
const HybridSignature({
|
||||
required this.ed25519,
|
||||
required this.dilithium,
|
||||
});
|
||||
|
||||
/// Returns the total size in bytes
|
||||
int get size => ed25519.length + dilithium.length;
|
||||
|
||||
/// Serializes to bytes
|
||||
Uint8List toBytes() {
|
||||
final result = Uint8List(size);
|
||||
result.setAll(0, ed25519);
|
||||
result.setAll(ed25519.length, dilithium);
|
||||
return result;
|
||||
}
|
||||
|
||||
factory HybridSignature.fromBytes(Uint8List data) {
|
||||
if (data.length < 64) {
|
||||
throw ArgumentError('Invalid signature length');
|
||||
}
|
||||
return HybridSignature(
|
||||
ed25519: Uint8List.fromList(data.sublist(0, 64)),
|
||||
dilithium: Uint8List.fromList(data.sublist(64)),
|
||||
);
|
||||
}
|
||||
|
||||
factory HybridSignature.fromJson(Map<String, dynamic> json) => HybridSignature(
|
||||
ed25519: base64Decode(json['ed25519'] as String),
|
||||
dilithium: base64Decode(json['dilithium'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'ed25519': base64Encode(ed25519),
|
||||
'dilithium': base64Encode(dilithium),
|
||||
};
|
||||
}
|
||||
|
||||
/// Falcon signature
|
||||
class FalconSignature {
|
||||
final FalconVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const FalconSignature({required this.variant, required this.bytes});
|
||||
|
||||
int get size => bytes.length;
|
||||
|
||||
factory FalconSignature.fromJson(Map<String, dynamic> json) => FalconSignature(
|
||||
variant: FalconVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['signature'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// SPHINCS+ signature
|
||||
class SphincsSignature {
|
||||
final SphincsVariant variant;
|
||||
final Uint8List bytes;
|
||||
|
||||
const SphincsSignature({required this.variant, required this.bytes});
|
||||
|
||||
int get size => bytes.length;
|
||||
|
||||
factory SphincsSignature.fromJson(Map<String, dynamic> json) => SphincsSignature(
|
||||
variant: SphincsVariant.fromJson(json['variant'] as String),
|
||||
bytes: base64Decode(json['signature'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid keypair (Ed25519 + Dilithium3)
|
||||
class HybridKeypair {
|
||||
final HybridPublicKey publicKey;
|
||||
final SecretKey secretKey;
|
||||
final Map<Network, String> _addresses;
|
||||
|
||||
const HybridKeypair({
|
||||
required this.publicKey,
|
||||
required this.secretKey,
|
||||
Map<Network, String> addresses = const {},
|
||||
}) : _addresses = addresses;
|
||||
|
||||
/// Returns the address for a network
|
||||
String address(Network network) => _addresses[network] ?? '';
|
||||
|
||||
factory HybridKeypair.fromJson(Map<String, dynamic> json) {
|
||||
final addressesJson = json['addresses'] as Map<String, dynamic>? ?? {};
|
||||
final addresses = <Network, String>{};
|
||||
addressesJson.forEach((k, v) {
|
||||
try {
|
||||
addresses[Network.fromJson(k)] = v as String;
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
return HybridKeypair(
|
||||
publicKey: HybridPublicKey.fromJson(json['public_key'] as Map<String, dynamic>),
|
||||
secretKey: SecretKey.fromJson(json['secret_key'] as Map<String, dynamic>),
|
||||
addresses: addresses,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon keypair
|
||||
class FalconKeypair {
|
||||
final FalconVariant variant;
|
||||
final FalconPublicKey publicKey;
|
||||
final FalconSecretKey secretKey;
|
||||
|
||||
const FalconKeypair({
|
||||
required this.variant,
|
||||
required this.publicKey,
|
||||
required this.secretKey,
|
||||
});
|
||||
|
||||
factory FalconKeypair.fromJson(Map<String, dynamic> json) => FalconKeypair(
|
||||
variant: FalconVariant.fromJson(json['variant'] as String),
|
||||
publicKey: FalconPublicKey.fromJson(json['public_key'] as Map<String, dynamic>),
|
||||
secretKey: FalconSecretKey.fromJson(json['secret_key'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
/// SPHINCS+ keypair
|
||||
class SphincsKeypair {
|
||||
final SphincsVariant variant;
|
||||
final SphincsPublicKey publicKey;
|
||||
final SphincsSecretKey secretKey;
|
||||
|
||||
const SphincsKeypair({
|
||||
required this.variant,
|
||||
required this.publicKey,
|
||||
required this.secretKey,
|
||||
});
|
||||
|
||||
factory SphincsKeypair.fromJson(Map<String, dynamic> json) => SphincsKeypair(
|
||||
variant: SphincsVariant.fromJson(json['variant'] as String),
|
||||
publicKey: SphincsPublicKey.fromJson(json['public_key'] as Map<String, dynamic>),
|
||||
secretKey: SphincsSecretKey.fromJson(json['secret_key'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
/// BIP-39 mnemonic phrase
|
||||
class Mnemonic {
|
||||
final String phrase;
|
||||
final List<String> words;
|
||||
final int wordCount;
|
||||
final Uint8List entropy;
|
||||
|
||||
const Mnemonic({
|
||||
required this.phrase,
|
||||
required this.words,
|
||||
required this.wordCount,
|
||||
required this.entropy,
|
||||
});
|
||||
|
||||
factory Mnemonic.fromJson(Map<String, dynamic> json) => Mnemonic(
|
||||
phrase: json['phrase'] as String,
|
||||
words: List<String>.from(json['words'] ?? (json['phrase'] as String).split(' ')),
|
||||
wordCount: json['word_count'] as int? ?? (json['phrase'] as String).split(' ').length,
|
||||
entropy: json['entropy'] != null ? base64Decode(json['entropy'] as String) : Uint8List(0),
|
||||
);
|
||||
}
|
||||
|
||||
/// Mnemonic validation result
|
||||
class MnemonicValidation {
|
||||
final bool valid;
|
||||
final String? error;
|
||||
|
||||
const MnemonicValidation({required this.valid, this.error});
|
||||
|
||||
factory MnemonicValidation.fromJson(Map<String, dynamic> json) => MnemonicValidation(
|
||||
valid: json['valid'] as bool,
|
||||
error: json['error'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
/// Blockchain address
|
||||
class Address {
|
||||
final String address;
|
||||
final Network network;
|
||||
final Uint8List pubkeyHash;
|
||||
|
||||
const Address({
|
||||
required this.address,
|
||||
required this.network,
|
||||
required this.pubkeyHash,
|
||||
});
|
||||
|
||||
factory Address.fromJson(Map<String, dynamic> json) => Address(
|
||||
address: json['address'] as String,
|
||||
network: Network.fromJson(json['network'] as String),
|
||||
pubkeyHash: json['pubkey_hash'] != null
|
||||
? base64Decode(json['pubkey_hash'] as String)
|
||||
: Uint8List(0),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
/// 256-bit hash
|
||||
class Hash256 {
|
||||
final Uint8List bytes;
|
||||
final String hex;
|
||||
|
||||
const Hash256({required this.bytes, required this.hex});
|
||||
|
||||
factory Hash256.fromJson(Map<String, dynamic> json) {
|
||||
final hexStr = json['hash'] as String;
|
||||
return Hash256(
|
||||
bytes: _hexDecode(hexStr),
|
||||
hex: hexStr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _hexDecode(String hex) {
|
||||
final result = Uint8List(hex.length ~/ 2);
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
result[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Negotiation Types
|
||||
// ============================================================================
|
||||
|
||||
/// Algorithm capabilities for negotiation
|
||||
class AlgorithmCapabilities {
|
||||
final List<PqAlgorithm> pqAlgorithms;
|
||||
final bool classical;
|
||||
final bool hybrid;
|
||||
final PqAlgorithm? preferred;
|
||||
|
||||
const AlgorithmCapabilities({
|
||||
required this.pqAlgorithms,
|
||||
this.classical = true,
|
||||
this.hybrid = true,
|
||||
this.preferred,
|
||||
});
|
||||
|
||||
factory AlgorithmCapabilities.fromJson(Map<String, dynamic> json) => AlgorithmCapabilities(
|
||||
pqAlgorithms: (json['pq_algorithms'] as List)
|
||||
.map((e) => PqAlgorithm.fromJson(e as String))
|
||||
.toList(),
|
||||
classical: json['classical'] as bool? ?? true,
|
||||
hybrid: json['hybrid'] as bool? ?? true,
|
||||
preferred: json['preferred'] != null
|
||||
? PqAlgorithm.fromJson(json['preferred'] as String)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Negotiation policy
|
||||
class NegotiationPolicy {
|
||||
final int minSecurityLevel;
|
||||
final bool preferCompact;
|
||||
final bool allowClassical;
|
||||
final List<AlgorithmFamily> requiredFamilies;
|
||||
|
||||
const NegotiationPolicy({
|
||||
this.minSecurityLevel = 128,
|
||||
this.preferCompact = false,
|
||||
this.allowClassical = false,
|
||||
this.requiredFamilies = const [],
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'min_security_level': minSecurityLevel,
|
||||
'prefer_compact': preferCompact,
|
||||
'allow_classical': allowClassical,
|
||||
'required_families': requiredFamilies.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Negotiation result
|
||||
class NegotiationResult {
|
||||
final PqAlgorithm algorithm;
|
||||
final int securityLevel;
|
||||
final AlgorithmFamily family;
|
||||
final bool hybrid;
|
||||
|
||||
const NegotiationResult({
|
||||
required this.algorithm,
|
||||
required this.securityLevel,
|
||||
required this.family,
|
||||
required this.hybrid,
|
||||
});
|
||||
|
||||
factory NegotiationResult.fromJson(Map<String, dynamic> json) => NegotiationResult(
|
||||
algorithm: PqAlgorithm.fromJson(json['algorithm'] as String),
|
||||
securityLevel: json['security_level'] as int,
|
||||
family: AlgorithmFamily.fromJson(json['family'] as String),
|
||||
hybrid: json['hybrid'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
/// Session parameters after negotiation
|
||||
class SessionParams {
|
||||
final PqAlgorithm algorithm;
|
||||
final Uint8List? sessionKey;
|
||||
final int? expiresAt;
|
||||
|
||||
const SessionParams({
|
||||
required this.algorithm,
|
||||
this.sessionKey,
|
||||
this.expiresAt,
|
||||
});
|
||||
|
||||
factory SessionParams.fromJson(Map<String, dynamic> json) => SessionParams(
|
||||
algorithm: PqAlgorithm.fromJson(json['algorithm'] as String),
|
||||
sessionKey: json['session_key'] != null
|
||||
? base64Decode(json['session_key'] as String)
|
||||
: null,
|
||||
expiresAt: json['expires_at'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
/// Crypto operation error
|
||||
class CryptoException implements Exception {
|
||||
final String message;
|
||||
final String? code;
|
||||
|
||||
const CryptoException(this.message, [this.code]);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
code != null ? 'CryptoException: $message (code: $code)' : 'CryptoException: $message';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
abstract class CryptoConstants {
|
||||
static const int ed25519PublicKeySize = 32;
|
||||
static const int ed25519SecretKeySize = 32;
|
||||
static const int ed25519SignatureSize = 64;
|
||||
static const int dilithium3PublicKeySize = 1952;
|
||||
static const int dilithium3SignatureSize = 3293;
|
||||
static const int hybridSignatureSize = 64 + 3293;
|
||||
static const int falcon512SignatureSize = 690;
|
||||
static const int falcon512PublicKeySize = 897;
|
||||
static const int falcon1024SignatureSize = 1330;
|
||||
static const int falcon1024PublicKeySize = 1793;
|
||||
static const int sphincs128sSignatureSize = 7856;
|
||||
static const int sphincs192sSignatureSize = 16224;
|
||||
static const int sphincs256sSignatureSize = 29792;
|
||||
static const int coinType = 0x5359;
|
||||
static const int minPbkdf2Iterations = 10000;
|
||||
static const int minSaltLength = 8;
|
||||
static const String defaultEndpoint = 'https://crypto.synor.io/v1';
|
||||
}
|
||||
691
sdk/go/crypto/client.go
Normal file
691
sdk/go/crypto/client.go
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
// Package crypto provides quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SynorCrypto is the main client for the Crypto SDK
|
||||
type SynorCrypto struct {
|
||||
config Config
|
||||
client *http.Client
|
||||
closed bool
|
||||
|
||||
Mnemonic *MnemonicClient
|
||||
Keypairs *KeypairClient
|
||||
Signing *SigningClient
|
||||
Falcon *FalconClient
|
||||
Sphincs *SphincsClient
|
||||
KDF *KDFClient
|
||||
Hash *HashClient
|
||||
Negotiation *NegotiationClient
|
||||
}
|
||||
|
||||
// New creates a new SynorCrypto client
|
||||
func New(config Config) *SynorCrypto {
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = DefaultEndpoint
|
||||
}
|
||||
if config.Timeout == 0 {
|
||||
config.Timeout = 30000
|
||||
}
|
||||
if config.Retries == 0 {
|
||||
config.Retries = 3
|
||||
}
|
||||
if config.DefaultNetwork == "" {
|
||||
config.DefaultNetwork = NetworkMainnet
|
||||
}
|
||||
|
||||
c := &SynorCrypto{
|
||||
config: config,
|
||||
client: &http.Client{
|
||||
Timeout: time.Duration(config.Timeout) * time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
c.Mnemonic = &MnemonicClient{crypto: c}
|
||||
c.Keypairs = &KeypairClient{crypto: c}
|
||||
c.Signing = &SigningClient{crypto: c}
|
||||
c.Falcon = &FalconClient{crypto: c}
|
||||
c.Sphincs = &SphincsClient{crypto: c}
|
||||
c.KDF = &KDFClient{crypto: c}
|
||||
c.Hash = &HashClient{crypto: c}
|
||||
c.Negotiation = &NegotiationClient{crypto: c}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// DefaultNetwork returns the default network
|
||||
func (c *SynorCrypto) DefaultNetwork() Network {
|
||||
return c.config.DefaultNetwork
|
||||
}
|
||||
|
||||
// HealthCheck checks if the service is healthy
|
||||
func (c *SynorCrypto) HealthCheck(ctx context.Context) (bool, error) {
|
||||
var result map[string]interface{}
|
||||
if err := c.get(ctx, "/health", &result); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, _ := result["status"].(string)
|
||||
return status == "healthy", nil
|
||||
}
|
||||
|
||||
// GetInfo returns service information
|
||||
func (c *SynorCrypto) GetInfo(ctx context.Context) (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
err := c.get(ctx, "/info", &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Close closes the client
|
||||
func (c *SynorCrypto) Close() {
|
||||
c.closed = true
|
||||
}
|
||||
|
||||
// IsClosed returns true if the client is closed
|
||||
func (c *SynorCrypto) IsClosed() bool {
|
||||
return c.closed
|
||||
}
|
||||
|
||||
func (c *SynorCrypto) get(ctx context.Context, path string, result interface{}) error {
|
||||
if c.closed {
|
||||
return NewCryptoError("Client has been closed", "CLIENT_CLOSED")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", c.config.Endpoint+path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return c.handleResponse(resp, result)
|
||||
}
|
||||
|
||||
func (c *SynorCrypto) post(ctx context.Context, path string, body interface{}, result interface{}) error {
|
||||
if c.closed {
|
||||
return NewCryptoError("Client has been closed", "CLIENT_CLOSED")
|
||||
}
|
||||
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqBody = bytes.NewBuffer(jsonBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", c.config.Endpoint+path, reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return c.handleResponse(resp, result)
|
||||
}
|
||||
|
||||
func (c *SynorCrypto) setHeaders(req *http.Request) {
|
||||
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-SDK-Version", "go/0.1.0")
|
||||
}
|
||||
|
||||
func (c *SynorCrypto) handleResponse(resp *http.Response, result interface{}) error {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var errResp map[string]interface{}
|
||||
json.Unmarshal(body, &errResp)
|
||||
msg := fmt.Sprintf("HTTP %d", resp.StatusCode)
|
||||
code := ""
|
||||
if m, ok := errResp["message"].(string); ok {
|
||||
msg = m
|
||||
}
|
||||
if c, ok := errResp["code"].(string); ok {
|
||||
code = c
|
||||
}
|
||||
return NewCryptoError(msg, code)
|
||||
}
|
||||
|
||||
return json.Unmarshal(body, result)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Client
|
||||
// ============================================================================
|
||||
|
||||
// MnemonicClient handles mnemonic operations
|
||||
type MnemonicClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// Generate creates a new random mnemonic
|
||||
func (c *MnemonicClient) Generate(ctx context.Context, wordCount int) (*Mnemonic, error) {
|
||||
var result mnemonicResponse
|
||||
err := c.crypto.post(ctx, "/mnemonic/generate", map[string]int{"word_count": wordCount}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toMnemonic()
|
||||
}
|
||||
|
||||
// FromPhrase creates a mnemonic from a phrase
|
||||
func (c *MnemonicClient) FromPhrase(ctx context.Context, phrase string) (*Mnemonic, error) {
|
||||
var result mnemonicResponse
|
||||
err := c.crypto.post(ctx, "/mnemonic/from-phrase", map[string]string{"phrase": phrase}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toMnemonic()
|
||||
}
|
||||
|
||||
// FromEntropy creates a mnemonic from entropy
|
||||
func (c *MnemonicClient) FromEntropy(ctx context.Context, entropy []byte) (*Mnemonic, error) {
|
||||
var result mnemonicResponse
|
||||
err := c.crypto.post(ctx, "/mnemonic/from-entropy", map[string]string{
|
||||
"entropy": EncodeBase64(entropy),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toMnemonic()
|
||||
}
|
||||
|
||||
// Validate checks if a phrase is valid
|
||||
func (c *MnemonicClient) Validate(ctx context.Context, phrase string) (*MnemonicValidation, error) {
|
||||
var result MnemonicValidation
|
||||
err := c.crypto.post(ctx, "/mnemonic/validate", map[string]string{"phrase": phrase}, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// ToSeed derives a seed from a mnemonic
|
||||
func (c *MnemonicClient) ToSeed(ctx context.Context, phrase string, passphrase string) ([]byte, error) {
|
||||
var result struct {
|
||||
Seed string `json:"seed"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/mnemonic/to-seed", map[string]string{
|
||||
"phrase": phrase,
|
||||
"passphrase": passphrase,
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeBase64(result.Seed)
|
||||
}
|
||||
|
||||
// SuggestWords suggests completions for a partial word
|
||||
func (c *MnemonicClient) SuggestWords(ctx context.Context, partial string, limit int) ([]string, error) {
|
||||
var result struct {
|
||||
Suggestions []string `json:"suggestions"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/mnemonic/suggest", map[string]interface{}{
|
||||
"partial": partial,
|
||||
"limit": limit,
|
||||
}, &result)
|
||||
return result.Suggestions, err
|
||||
}
|
||||
|
||||
type mnemonicResponse struct {
|
||||
Phrase string `json:"phrase"`
|
||||
Words []string `json:"words"`
|
||||
WordCount int `json:"word_count"`
|
||||
Entropy string `json:"entropy"`
|
||||
}
|
||||
|
||||
func (r *mnemonicResponse) toMnemonic() (*Mnemonic, error) {
|
||||
entropy, _ := DecodeBase64(r.Entropy)
|
||||
return &Mnemonic{
|
||||
Phrase: r.Phrase,
|
||||
Words: r.Words,
|
||||
WordCount: r.WordCount,
|
||||
Entropy: entropy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Client
|
||||
// ============================================================================
|
||||
|
||||
// KeypairClient handles keypair operations
|
||||
type KeypairClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// Generate creates a new random keypair
|
||||
func (c *KeypairClient) Generate(ctx context.Context) (*HybridKeypair, error) {
|
||||
var result keypairResponse
|
||||
err := c.crypto.post(ctx, "/keypair/generate", nil, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toKeypair()
|
||||
}
|
||||
|
||||
// FromMnemonic creates a keypair from a mnemonic
|
||||
func (c *KeypairClient) FromMnemonic(ctx context.Context, phrase string, passphrase string) (*HybridKeypair, error) {
|
||||
var result keypairResponse
|
||||
err := c.crypto.post(ctx, "/keypair/from-mnemonic", map[string]string{
|
||||
"phrase": phrase,
|
||||
"passphrase": passphrase,
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toKeypair()
|
||||
}
|
||||
|
||||
// FromSeed creates a keypair from a seed
|
||||
func (c *KeypairClient) FromSeed(ctx context.Context, seed []byte) (*HybridKeypair, error) {
|
||||
var result keypairResponse
|
||||
err := c.crypto.post(ctx, "/keypair/from-seed", map[string]string{
|
||||
"seed": EncodeBase64(seed),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.toKeypair()
|
||||
}
|
||||
|
||||
// GetAddress gets the address for a public key
|
||||
func (c *KeypairClient) GetAddress(ctx context.Context, pk *HybridPublicKey, network Network) (*Address, error) {
|
||||
var result Address
|
||||
err := c.crypto.post(ctx, "/keypair/address", map[string]interface{}{
|
||||
"public_key": map[string]string{
|
||||
"ed25519": EncodeBase64(pk.Ed25519),
|
||||
"dilithium": EncodeBase64(pk.Dilithium),
|
||||
},
|
||||
"network": network,
|
||||
}, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
type keypairResponse struct {
|
||||
PublicKey struct {
|
||||
Ed25519 string `json:"ed25519"`
|
||||
Dilithium string `json:"dilithium"`
|
||||
} `json:"public_key"`
|
||||
SecretKey struct {
|
||||
Ed25519Seed string `json:"ed25519_seed"`
|
||||
MasterSeed string `json:"master_seed"`
|
||||
} `json:"secret_key"`
|
||||
Addresses map[string]string `json:"addresses"`
|
||||
}
|
||||
|
||||
func (r *keypairResponse) toKeypair() (*HybridKeypair, error) {
|
||||
ed25519, _ := DecodeBase64(r.PublicKey.Ed25519)
|
||||
dilithium, _ := DecodeBase64(r.PublicKey.Dilithium)
|
||||
ed25519Seed, _ := DecodeBase64(r.SecretKey.Ed25519Seed)
|
||||
masterSeed, _ := DecodeBase64(r.SecretKey.MasterSeed)
|
||||
|
||||
addresses := make(map[Network]string)
|
||||
for k, v := range r.Addresses {
|
||||
addresses[Network(k)] = v
|
||||
}
|
||||
|
||||
return &HybridKeypair{
|
||||
PublicKey: &HybridPublicKey{
|
||||
Ed25519: ed25519,
|
||||
Dilithium: dilithium,
|
||||
},
|
||||
SecretKey: &SecretKey{
|
||||
Ed25519Seed: ed25519Seed,
|
||||
MasterSeed: masterSeed,
|
||||
},
|
||||
addresses: addresses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signing Client
|
||||
// ============================================================================
|
||||
|
||||
// SigningClient handles signing operations
|
||||
type SigningClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// Sign signs a message with a hybrid keypair
|
||||
func (c *SigningClient) Sign(ctx context.Context, kp *HybridKeypair, message []byte) (*HybridSignature, error) {
|
||||
var result struct {
|
||||
Ed25519 string `json:"ed25519"`
|
||||
Dilithium string `json:"dilithium"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/sign/hybrid", map[string]interface{}{
|
||||
"secret_key": map[string]string{
|
||||
"ed25519_seed": EncodeBase64(kp.SecretKey.Ed25519Seed),
|
||||
"master_seed": EncodeBase64(kp.SecretKey.MasterSeed),
|
||||
},
|
||||
"message": EncodeBase64(message),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ed25519, _ := DecodeBase64(result.Ed25519)
|
||||
dilithium, _ := DecodeBase64(result.Dilithium)
|
||||
return &HybridSignature{
|
||||
Ed25519: ed25519,
|
||||
Dilithium: dilithium,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a hybrid signature
|
||||
func (c *SigningClient) Verify(ctx context.Context, pk *HybridPublicKey, message []byte, sig *HybridSignature) (bool, error) {
|
||||
var result struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/sign/verify", map[string]interface{}{
|
||||
"public_key": map[string]string{
|
||||
"ed25519": EncodeBase64(pk.Ed25519),
|
||||
"dilithium": EncodeBase64(pk.Dilithium),
|
||||
},
|
||||
"message": EncodeBase64(message),
|
||||
"signature": map[string]string{
|
||||
"ed25519": EncodeBase64(sig.Ed25519),
|
||||
"dilithium": EncodeBase64(sig.Dilithium),
|
||||
},
|
||||
}, &result)
|
||||
return result.Valid, err
|
||||
}
|
||||
|
||||
// SignEd25519 signs with Ed25519 only
|
||||
func (c *SigningClient) SignEd25519(ctx context.Context, secretKey []byte, message []byte) ([]byte, error) {
|
||||
var result struct {
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/sign/ed25519", map[string]string{
|
||||
"secret_key": EncodeBase64(secretKey),
|
||||
"message": EncodeBase64(message),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeBase64(result.Signature)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Falcon Client
|
||||
// ============================================================================
|
||||
|
||||
// FalconClient handles Falcon operations
|
||||
type FalconClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// Generate creates a Falcon keypair
|
||||
func (c *FalconClient) Generate(ctx context.Context, variant FalconVariant) (*FalconKeypair, error) {
|
||||
var result map[string]interface{}
|
||||
err := c.crypto.post(ctx, "/falcon/generate", map[string]string{"variant": string(variant)}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Parse response into FalconKeypair
|
||||
return parseFalconKeypair(result, variant)
|
||||
}
|
||||
|
||||
// Sign signs with Falcon
|
||||
func (c *FalconClient) Sign(ctx context.Context, kp *FalconKeypair, message []byte) (*FalconSignature, error) {
|
||||
var result struct {
|
||||
Signature string `json:"signature"`
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/falcon/sign", map[string]string{
|
||||
"variant": string(kp.Variant),
|
||||
"secret_key": EncodeBase64(kp.SecretKey.Bytes),
|
||||
"message": EncodeBase64(message),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigBytes, _ := DecodeBase64(result.Signature)
|
||||
return &FalconSignature{
|
||||
Variant: FalconVariant(result.Variant),
|
||||
Bytes: sigBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a Falcon signature
|
||||
func (c *FalconClient) Verify(ctx context.Context, publicKey []byte, message []byte, sig *FalconSignature) (bool, error) {
|
||||
var result struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/falcon/verify", map[string]interface{}{
|
||||
"variant": sig.Variant,
|
||||
"public_key": EncodeBase64(publicKey),
|
||||
"message": EncodeBase64(message),
|
||||
"signature": EncodeBase64(sig.Bytes),
|
||||
}, &result)
|
||||
return result.Valid, err
|
||||
}
|
||||
|
||||
func parseFalconKeypair(data map[string]interface{}, variant FalconVariant) (*FalconKeypair, error) {
|
||||
pk := data["public_key"].(map[string]interface{})
|
||||
sk := data["secret_key"].(map[string]interface{})
|
||||
pkBytes, _ := DecodeBase64(pk["bytes"].(string))
|
||||
skBytes, _ := DecodeBase64(sk["bytes"].(string))
|
||||
return &FalconKeypair{
|
||||
Variant: variant,
|
||||
PublicKey: &FalconPublicKey{Variant: variant, Bytes: pkBytes},
|
||||
SecretKey: &FalconSecretKey{Variant: variant, Bytes: skBytes},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPHINCS+ Client
|
||||
// ============================================================================
|
||||
|
||||
// SphincsClient handles SPHINCS+ operations
|
||||
type SphincsClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// Generate creates a SPHINCS+ keypair
|
||||
func (c *SphincsClient) Generate(ctx context.Context, variant SphincsVariant) (*SphincsKeypair, error) {
|
||||
var result map[string]interface{}
|
||||
err := c.crypto.post(ctx, "/sphincs/generate", map[string]string{"variant": string(variant)}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseSphincsKeypair(result, variant)
|
||||
}
|
||||
|
||||
// Sign signs with SPHINCS+
|
||||
func (c *SphincsClient) Sign(ctx context.Context, kp *SphincsKeypair, message []byte) (*SphincsSignature, error) {
|
||||
var result struct {
|
||||
Signature string `json:"signature"`
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/sphincs/sign", map[string]string{
|
||||
"variant": string(kp.Variant),
|
||||
"secret_key": EncodeBase64(kp.SecretKey.Bytes),
|
||||
"message": EncodeBase64(message),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigBytes, _ := DecodeBase64(result.Signature)
|
||||
return &SphincsSignature{
|
||||
Variant: SphincsVariant(result.Variant),
|
||||
Bytes: sigBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a SPHINCS+ signature
|
||||
func (c *SphincsClient) Verify(ctx context.Context, publicKey []byte, message []byte, sig *SphincsSignature) (bool, error) {
|
||||
var result struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/sphincs/verify", map[string]interface{}{
|
||||
"variant": sig.Variant,
|
||||
"public_key": EncodeBase64(publicKey),
|
||||
"message": EncodeBase64(message),
|
||||
"signature": EncodeBase64(sig.Bytes),
|
||||
}, &result)
|
||||
return result.Valid, err
|
||||
}
|
||||
|
||||
func parseSphincsKeypair(data map[string]interface{}, variant SphincsVariant) (*SphincsKeypair, error) {
|
||||
pk := data["public_key"].(map[string]interface{})
|
||||
sk := data["secret_key"].(map[string]interface{})
|
||||
pkBytes, _ := DecodeBase64(pk["bytes"].(string))
|
||||
skBytes, _ := DecodeBase64(sk["bytes"].(string))
|
||||
return &SphincsKeypair{
|
||||
Variant: variant,
|
||||
PublicKey: &SphincsPublicKey{Variant: variant, Bytes: pkBytes},
|
||||
SecretKey: &SphincsSecretKey{Variant: variant, Bytes: skBytes},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KDF Client
|
||||
// ============================================================================
|
||||
|
||||
// KDFClient handles key derivation
|
||||
type KDFClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// DeriveKey derives a key using HKDF
|
||||
func (c *KDFClient) DeriveKey(ctx context.Context, seed []byte, config *DerivationConfig) ([]byte, error) {
|
||||
body := map[string]interface{}{
|
||||
"seed": EncodeBase64(seed),
|
||||
"output_length": config.OutputLength,
|
||||
}
|
||||
if config.Salt != nil {
|
||||
body["salt"] = EncodeBase64(config.Salt)
|
||||
}
|
||||
if config.Info != nil {
|
||||
body["info"] = EncodeBase64(config.Info)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/kdf/hkdf", body, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeBase64(result.Key)
|
||||
}
|
||||
|
||||
// DeriveFromPassword derives a key from a password
|
||||
func (c *KDFClient) DeriveFromPassword(ctx context.Context, password []byte, config *PasswordDerivationConfig) ([]byte, error) {
|
||||
var result struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/kdf/pbkdf2", map[string]interface{}{
|
||||
"password": EncodeBase64(password),
|
||||
"salt": EncodeBase64(config.Salt),
|
||||
"iterations": config.Iterations,
|
||||
"output_length": config.OutputLength,
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeBase64(result.Key)
|
||||
}
|
||||
|
||||
// DeriveChildKey derives a child key
|
||||
func (c *KDFClient) DeriveChildKey(ctx context.Context, parentKey, chainCode []byte, index int) ([]byte, []byte, error) {
|
||||
var result struct {
|
||||
Key string `json:"key"`
|
||||
ChainCode string `json:"chain_code"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/kdf/child", map[string]interface{}{
|
||||
"parent_key": EncodeBase64(parentKey),
|
||||
"chain_code": EncodeBase64(chainCode),
|
||||
"index": index,
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, _ := DecodeBase64(result.Key)
|
||||
cc, _ := DecodeBase64(result.ChainCode)
|
||||
return key, cc, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Client
|
||||
// ============================================================================
|
||||
|
||||
// HashClient handles hashing operations
|
||||
type HashClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// SHA3_256 computes SHA3-256 hash
|
||||
func (c *HashClient) SHA3_256(ctx context.Context, data []byte) (*Hash256, error) {
|
||||
var result struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/hash/sha3-256", map[string]string{
|
||||
"data": EncodeBase64(data),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashBytes, _ := DecodeBase64(result.Hash)
|
||||
return &Hash256{Bytes: hashBytes, Hex: result.Hash}, nil
|
||||
}
|
||||
|
||||
// Blake3 computes BLAKE3 hash
|
||||
func (c *HashClient) Blake3(ctx context.Context, data []byte) (*Hash256, error) {
|
||||
var result struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
err := c.crypto.post(ctx, "/hash/blake3", map[string]string{
|
||||
"data": EncodeBase64(data),
|
||||
}, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashBytes, _ := DecodeBase64(result.Hash)
|
||||
return &Hash256{Bytes: hashBytes, Hex: result.Hash}, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Negotiation Client
|
||||
// ============================================================================
|
||||
|
||||
// NegotiationClient handles algorithm negotiation
|
||||
type NegotiationClient struct {
|
||||
crypto *SynorCrypto
|
||||
}
|
||||
|
||||
// GetCapabilities returns local capabilities
|
||||
func (c *NegotiationClient) GetCapabilities(ctx context.Context) (*AlgorithmCapabilities, error) {
|
||||
var result AlgorithmCapabilities
|
||||
err := c.crypto.get(ctx, "/negotiation/capabilities", &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// Negotiate negotiates with a peer
|
||||
func (c *NegotiationClient) Negotiate(ctx context.Context, peerCaps *AlgorithmCapabilities, policy *NegotiationPolicy) (*NegotiationResult, error) {
|
||||
var result NegotiationResult
|
||||
err := c.crypto.post(ctx, "/negotiation/negotiate", map[string]interface{}{
|
||||
"peer_capabilities": peerCaps,
|
||||
"policy": policy,
|
||||
}, &result)
|
||||
return &result, err
|
||||
}
|
||||
479
sdk/go/crypto/types.go
Normal file
479
sdk/go/crypto/types.go
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
// Package crypto provides quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
// Network type for address generation
|
||||
type Network string
|
||||
|
||||
const (
|
||||
NetworkMainnet Network = "mainnet"
|
||||
NetworkTestnet Network = "testnet"
|
||||
NetworkDevnet Network = "devnet"
|
||||
)
|
||||
|
||||
// FalconVariant selection
|
||||
type FalconVariant string
|
||||
|
||||
const (
|
||||
Falcon512 FalconVariant = "falcon512" // 128-bit security, ~690 byte signatures
|
||||
Falcon1024 FalconVariant = "falcon1024" // 256-bit security, ~1330 byte signatures
|
||||
)
|
||||
|
||||
// SphincsVariant selection
|
||||
type SphincsVariant string
|
||||
|
||||
const (
|
||||
Shake128s SphincsVariant = "shake128s" // 128-bit security, ~7.8KB signatures
|
||||
Shake192s SphincsVariant = "shake192s" // 192-bit security, ~16KB signatures
|
||||
Shake256s SphincsVariant = "shake256s" // 256-bit security, ~30KB signatures
|
||||
)
|
||||
|
||||
// PqAlgorithm post-quantum algorithm selection
|
||||
type PqAlgorithm string
|
||||
|
||||
const (
|
||||
Dilithium3 PqAlgorithm = "dilithium3"
|
||||
PqFalcon512 PqAlgorithm = "falcon512"
|
||||
PqFalcon1024 PqAlgorithm = "falcon1024"
|
||||
Sphincs128s PqAlgorithm = "sphincs128s"
|
||||
Sphincs192s PqAlgorithm = "sphincs192s"
|
||||
Sphincs256s PqAlgorithm = "sphincs256s"
|
||||
)
|
||||
|
||||
// AlgorithmFamily classification
|
||||
type AlgorithmFamily string
|
||||
|
||||
const (
|
||||
Classical AlgorithmFamily = "classical"
|
||||
Lattice AlgorithmFamily = "lattice"
|
||||
HashBased AlgorithmFamily = "hash_based"
|
||||
Hybrid AlgorithmFamily = "hybrid"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
// Config for the crypto SDK
|
||||
type Config struct {
|
||||
APIKey string
|
||||
Endpoint string
|
||||
Timeout int // milliseconds
|
||||
Retries int
|
||||
Debug bool
|
||||
DefaultNetwork Network
|
||||
}
|
||||
|
||||
// DefaultConfig returns a config with sensible defaults
|
||||
func DefaultConfig(apiKey string) Config {
|
||||
return Config{
|
||||
APIKey: apiKey,
|
||||
Endpoint: "https://crypto.synor.io/v1",
|
||||
Timeout: 30000,
|
||||
Retries: 3,
|
||||
Debug: false,
|
||||
DefaultNetwork: NetworkMainnet,
|
||||
}
|
||||
}
|
||||
|
||||
// DerivationConfig for key derivation
|
||||
type DerivationConfig struct {
|
||||
Salt []byte
|
||||
Info []byte
|
||||
OutputLength int
|
||||
}
|
||||
|
||||
// PasswordDerivationConfig for password-based key derivation
|
||||
type PasswordDerivationConfig struct {
|
||||
Salt []byte
|
||||
Iterations int
|
||||
OutputLength int
|
||||
}
|
||||
|
||||
// DerivationPath BIP-44 style path
|
||||
type DerivationPath struct {
|
||||
Account int
|
||||
Change int
|
||||
Index int
|
||||
}
|
||||
|
||||
// CoinType for Synor in BIP-44
|
||||
const CoinType = 0x5359
|
||||
|
||||
// String formats the derivation path
|
||||
func (p DerivationPath) String() string {
|
||||
return fmt.Sprintf("m/44'/%d'/%d'/%d/%d", CoinType, p.Account, p.Change, p.Index)
|
||||
}
|
||||
|
||||
// ExternalPath creates a path for external addresses
|
||||
func ExternalPath(account, index int) DerivationPath {
|
||||
return DerivationPath{Account: account, Change: 0, Index: index}
|
||||
}
|
||||
|
||||
// InternalPath creates a path for internal (change) addresses
|
||||
func InternalPath(account, index int) DerivationPath {
|
||||
return DerivationPath{Account: account, Change: 1, Index: index}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
// HybridPublicKey combines Ed25519 and Dilithium3
|
||||
type HybridPublicKey struct {
|
||||
Ed25519 []byte // 32 bytes
|
||||
Dilithium []byte // ~1952 bytes
|
||||
}
|
||||
|
||||
// Size returns the total key size
|
||||
func (pk *HybridPublicKey) Size() int {
|
||||
return len(pk.Ed25519) + len(pk.Dilithium)
|
||||
}
|
||||
|
||||
// SecretKey holds the master seed
|
||||
type SecretKey struct {
|
||||
Ed25519Seed []byte // 32 bytes
|
||||
MasterSeed []byte // 64 bytes
|
||||
}
|
||||
|
||||
// FalconPublicKey for Falcon signatures
|
||||
type FalconPublicKey struct {
|
||||
Variant FalconVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// FalconSecretKey for Falcon signatures
|
||||
type FalconSecretKey struct {
|
||||
Variant FalconVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// SphincsPublicKey for SPHINCS+ signatures
|
||||
type SphincsPublicKey struct {
|
||||
Variant SphincsVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// SphincsSecretKey for SPHINCS+ signatures
|
||||
type SphincsSecretKey struct {
|
||||
Variant SphincsVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
// HybridSignature combines Ed25519 and Dilithium3 signatures
|
||||
type HybridSignature struct {
|
||||
Ed25519 []byte // 64 bytes
|
||||
Dilithium []byte // ~3293 bytes
|
||||
}
|
||||
|
||||
// Size returns the total signature size
|
||||
func (s *HybridSignature) Size() int {
|
||||
return len(s.Ed25519) + len(s.Dilithium)
|
||||
}
|
||||
|
||||
// ToBytes serializes the signature
|
||||
func (s *HybridSignature) ToBytes() []byte {
|
||||
result := make([]byte, len(s.Ed25519)+len(s.Dilithium))
|
||||
copy(result, s.Ed25519)
|
||||
copy(result[len(s.Ed25519):], s.Dilithium)
|
||||
return result
|
||||
}
|
||||
|
||||
// HybridSignatureFromBytes deserializes a signature
|
||||
func HybridSignatureFromBytes(data []byte) (*HybridSignature, error) {
|
||||
if len(data) < 64 {
|
||||
return nil, fmt.Errorf("invalid signature length: %d", len(data))
|
||||
}
|
||||
return &HybridSignature{
|
||||
Ed25519: data[:64],
|
||||
Dilithium: data[64:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FalconSignature for Falcon
|
||||
type FalconSignature struct {
|
||||
Variant FalconVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// Size returns the signature size
|
||||
func (s *FalconSignature) Size() int {
|
||||
return len(s.Bytes)
|
||||
}
|
||||
|
||||
// SphincsSignature for SPHINCS+
|
||||
type SphincsSignature struct {
|
||||
Variant SphincsVariant
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// Size returns the signature size
|
||||
func (s *SphincsSignature) Size() int {
|
||||
return len(s.Bytes)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
// HybridKeypair combines Ed25519 and Dilithium3
|
||||
type HybridKeypair struct {
|
||||
PublicKey *HybridPublicKey
|
||||
SecretKey *SecretKey
|
||||
addresses map[Network]string
|
||||
}
|
||||
|
||||
// Address returns the address for a network
|
||||
func (kp *HybridKeypair) Address(network Network) string {
|
||||
if kp.addresses == nil {
|
||||
return ""
|
||||
}
|
||||
return kp.addresses[network]
|
||||
}
|
||||
|
||||
// FalconKeypair for Falcon signatures
|
||||
type FalconKeypair struct {
|
||||
Variant FalconVariant
|
||||
PublicKey *FalconPublicKey
|
||||
SecretKey *FalconSecretKey
|
||||
}
|
||||
|
||||
// SphincsKeypair for SPHINCS+ signatures
|
||||
type SphincsKeypair struct {
|
||||
Variant SphincsVariant
|
||||
PublicKey *SphincsPublicKey
|
||||
SecretKey *SphincsSecretKey
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
// Mnemonic BIP-39 phrase
|
||||
type Mnemonic struct {
|
||||
Phrase string
|
||||
Words []string
|
||||
WordCount int
|
||||
Entropy []byte
|
||||
}
|
||||
|
||||
// MnemonicValidation result
|
||||
type MnemonicValidation struct {
|
||||
Valid bool
|
||||
Error string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
// Address on the blockchain
|
||||
type Address struct {
|
||||
Address string
|
||||
Network Network
|
||||
PubkeyHash []byte
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
// Hash256 is a 256-bit hash
|
||||
type Hash256 struct {
|
||||
Bytes []byte
|
||||
Hex string
|
||||
}
|
||||
|
||||
// NewHash256 creates a hash from bytes
|
||||
func NewHash256(bytes []byte) Hash256 {
|
||||
return Hash256{
|
||||
Bytes: bytes,
|
||||
Hex: hex.EncodeToString(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Negotiation Types
|
||||
// ============================================================================
|
||||
|
||||
// AlgorithmCapabilities for negotiation
|
||||
type AlgorithmCapabilities struct {
|
||||
PqAlgorithms []PqAlgorithm
|
||||
Classical bool
|
||||
Hybrid bool
|
||||
Preferred *PqAlgorithm
|
||||
}
|
||||
|
||||
// NegotiationPolicy for algorithm selection
|
||||
type NegotiationPolicy struct {
|
||||
MinSecurityLevel int
|
||||
PreferCompact bool
|
||||
AllowClassical bool
|
||||
RequiredFamilies []AlgorithmFamily
|
||||
}
|
||||
|
||||
// NegotiationResult after negotiation
|
||||
type NegotiationResult struct {
|
||||
Algorithm PqAlgorithm
|
||||
SecurityLevel int
|
||||
Family AlgorithmFamily
|
||||
Hybrid bool
|
||||
}
|
||||
|
||||
// SessionParams after establishing a session
|
||||
type SessionParams struct {
|
||||
Algorithm PqAlgorithm
|
||||
SessionKey []byte
|
||||
ExpiresAt *int64
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
// CryptoError represents a crypto operation error
|
||||
type CryptoError struct {
|
||||
Message string
|
||||
Code string
|
||||
}
|
||||
|
||||
func (e *CryptoError) Error() string {
|
||||
if e.Code != "" {
|
||||
return fmt.Sprintf("%s (code: %s)", e.Message, e.Code)
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewCryptoError creates a new error
|
||||
func NewCryptoError(message, code string) *CryptoError {
|
||||
return &CryptoError{Message: message, Code: code}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
const (
|
||||
Ed25519PublicKeySize = 32
|
||||
Ed25519SecretKeySize = 32
|
||||
Ed25519SignatureSize = 64
|
||||
Dilithium3PublicKeySize = 1952
|
||||
Dilithium3SignatureSize = 3293
|
||||
HybridSignatureSize = 64 + 3293
|
||||
Falcon512SignatureSize = 690
|
||||
Falcon512PublicKeySize = 897
|
||||
Falcon1024SignatureSize = 1330
|
||||
Falcon1024PublicKeySize = 1793
|
||||
Sphincs128sSignatureSize = 7856
|
||||
Sphincs192sSignatureSize = 16224
|
||||
Sphincs256sSignatureSize = 29792
|
||||
MinPBKDF2Iterations = 10000
|
||||
MinSaltLength = 8
|
||||
DefaultEndpoint = "https://crypto.synor.io/v1"
|
||||
)
|
||||
|
||||
// AlgorithmSizes maps algorithms to their sizes
|
||||
var AlgorithmSizes = map[string]struct {
|
||||
Signature int
|
||||
PublicKey int
|
||||
}{
|
||||
"ed25519": {64, 32},
|
||||
"dilithium3": {3293, 1952},
|
||||
"hybrid": {3357, 1984},
|
||||
"falcon512": {690, 897},
|
||||
"falcon1024": {1330, 1793},
|
||||
"sphincs128s": {7856, 32},
|
||||
"sphincs192s": {16224, 48},
|
||||
"sphincs256s": {29792, 64},
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
// EncodeBase64 encodes bytes to base64
|
||||
func EncodeBase64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// DecodeBase64 decodes base64 to bytes
|
||||
func DecodeBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// FalconSignatureSize returns the signature size for a variant
|
||||
func FalconSignatureSize(variant FalconVariant) int {
|
||||
switch variant {
|
||||
case Falcon512:
|
||||
return Falcon512SignatureSize
|
||||
case Falcon1024:
|
||||
return Falcon1024SignatureSize
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// FalconPublicKeySize returns the public key size for a variant
|
||||
func FalconPublicKeySize(variant FalconVariant) int {
|
||||
switch variant {
|
||||
case Falcon512:
|
||||
return Falcon512PublicKeySize
|
||||
case Falcon1024:
|
||||
return Falcon1024PublicKeySize
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// FalconSecurityLevel returns the security level for a variant
|
||||
func FalconSecurityLevel(variant FalconVariant) int {
|
||||
switch variant {
|
||||
case Falcon512:
|
||||
return 128
|
||||
case Falcon1024:
|
||||
return 256
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// SphincsSignatureSize returns the signature size for a variant
|
||||
func SphincsSignatureSize(variant SphincsVariant) int {
|
||||
switch variant {
|
||||
case Shake128s:
|
||||
return Sphincs128sSignatureSize
|
||||
case Shake192s:
|
||||
return Sphincs192sSignatureSize
|
||||
case Shake256s:
|
||||
return Sphincs256sSignatureSize
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// SphincsSecurityLevel returns the security level for a variant
|
||||
func SphincsSecurityLevel(variant SphincsVariant) int {
|
||||
switch variant {
|
||||
case Shake128s:
|
||||
return 128
|
||||
case Shake192s:
|
||||
return 192
|
||||
case Shake256s:
|
||||
return 256
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
351
sdk/java/src/main/java/io/synor/crypto/SynorCrypto.java
Normal file
351
sdk/java/src/main/java/io/synor/crypto/SynorCrypto.java
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
package io.synor.crypto;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Synor Crypto SDK for Java
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*
|
||||
* <pre>{@code
|
||||
* CryptoConfig config = new CryptoConfig("your-api-key");
|
||||
* SynorCrypto crypto = new SynorCrypto(config);
|
||||
*
|
||||
* // Generate a mnemonic
|
||||
* Mnemonic mnemonic = crypto.mnemonic().generate(24).join();
|
||||
* System.out.println("Backup words: " + mnemonic.getPhrase());
|
||||
*
|
||||
* // Create keypair from mnemonic
|
||||
* HybridKeypair keypair = crypto.keypairs().fromMnemonic(mnemonic.getPhrase(), "").join();
|
||||
* String address = keypair.getAddress(Network.MAINNET);
|
||||
*
|
||||
* // Sign a message
|
||||
* HybridSignature signature = crypto.signing().sign(keypair, "Hello!".getBytes()).join();
|
||||
* }</pre>
|
||||
*/
|
||||
public class SynorCrypto {
|
||||
private final CryptoConfig config;
|
||||
private final HttpClient httpClient;
|
||||
private final Gson gson;
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
|
||||
private final MnemonicClient mnemonicClient;
|
||||
private final KeypairClient keypairClient;
|
||||
private final SigningClient signingClient;
|
||||
private final FalconClient falconClient;
|
||||
private final SphincsClient sphincsClient;
|
||||
private final KdfClient kdfClient;
|
||||
private final HashClient hashClient;
|
||||
|
||||
public SynorCrypto(CryptoConfig config) {
|
||||
this.config = config;
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofMillis(config.getTimeout()))
|
||||
.build();
|
||||
this.gson = new GsonBuilder()
|
||||
.setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.create();
|
||||
|
||||
this.mnemonicClient = new MnemonicClient(this);
|
||||
this.keypairClient = new KeypairClient(this);
|
||||
this.signingClient = new SigningClient(this);
|
||||
this.falconClient = new FalconClient(this);
|
||||
this.sphincsClient = new SphincsClient(this);
|
||||
this.kdfClient = new KdfClient(this);
|
||||
this.hashClient = new HashClient(this);
|
||||
}
|
||||
|
||||
public Network getDefaultNetwork() {
|
||||
return config.getDefaultNetwork();
|
||||
}
|
||||
|
||||
public MnemonicClient mnemonic() { return mnemonicClient; }
|
||||
public KeypairClient keypairs() { return keypairClient; }
|
||||
public SigningClient signing() { return signingClient; }
|
||||
public FalconClient falcon() { return falconClient; }
|
||||
public SphincsClient sphincs() { return sphincsClient; }
|
||||
public KdfClient kdf() { return kdfClient; }
|
||||
public HashClient hash() { return hashClient; }
|
||||
|
||||
public CompletableFuture<Boolean> healthCheck() {
|
||||
return get("/health", new TypeToken<Map<String, Object>>() {})
|
||||
.thenApply(result -> "healthy".equals(result.get("status")))
|
||||
.exceptionally(e -> false);
|
||||
}
|
||||
|
||||
public CompletableFuture<Map<String, Object>> getInfo() {
|
||||
return get("/info", new TypeToken<Map<String, Object>>() {});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
closed.set(true);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed.get();
|
||||
}
|
||||
|
||||
<T> CompletableFuture<T> get(String path, TypeToken<T> type) {
|
||||
checkClosed();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(config.getEndpoint() + path))
|
||||
.header("Authorization", "Bearer " + config.getApiKey())
|
||||
.header("X-SDK-Version", "java/0.1.0")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> handleResponse(response, type));
|
||||
}
|
||||
|
||||
<T> CompletableFuture<T> post(String path, Object body, TypeToken<T> type) {
|
||||
checkClosed();
|
||||
String jsonBody = body != null ? gson.toJson(body) : "{}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(config.getEndpoint() + path))
|
||||
.header("Authorization", "Bearer " + config.getApiKey())
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-SDK-Version", "java/0.1.0")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.build();
|
||||
|
||||
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> handleResponse(response, type));
|
||||
}
|
||||
|
||||
private <T> T handleResponse(HttpResponse<String> response, TypeToken<T> type) {
|
||||
if (response.statusCode() >= 400) {
|
||||
Map<String, Object> error = gson.fromJson(response.body(), new TypeToken<Map<String, Object>>() {}.getType());
|
||||
String message = error != null && error.get("message") != null
|
||||
? (String) error.get("message")
|
||||
: "HTTP " + response.statusCode();
|
||||
String code = error != null ? (String) error.get("code") : null;
|
||||
throw new CryptoException(message, code);
|
||||
}
|
||||
return gson.fromJson(response.body(), type.getType());
|
||||
}
|
||||
|
||||
private void checkClosed() {
|
||||
if (closed.get()) {
|
||||
throw new CryptoException("Client has been closed", "CLIENT_CLOSED");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Sub-Clients
|
||||
// ========================================================================
|
||||
|
||||
public static class MnemonicClient {
|
||||
private final SynorCrypto crypto;
|
||||
MnemonicClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<Mnemonic> generate(int wordCount) {
|
||||
Map<String, Object> body = Map.of("word_count", wordCount);
|
||||
return crypto.post("/mnemonic/generate", body, new TypeToken<Mnemonic>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Mnemonic> fromPhrase(String phrase) {
|
||||
Map<String, Object> body = Map.of("phrase", phrase);
|
||||
return crypto.post("/mnemonic/from-phrase", body, new TypeToken<Mnemonic>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<MnemonicValidation> validate(String phrase) {
|
||||
Map<String, Object> body = Map.of("phrase", phrase);
|
||||
return crypto.post("/mnemonic/validate", body, new TypeToken<MnemonicValidation>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> toSeed(String phrase, String passphrase) {
|
||||
Map<String, Object> body = Map.of("phrase", phrase, "passphrase", passphrase);
|
||||
return crypto.post("/mnemonic/to-seed", body, new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> Base64.getDecoder().decode(result.get("seed")));
|
||||
}
|
||||
|
||||
public CompletableFuture<List<String>> suggestWords(String partial, int limit) {
|
||||
Map<String, Object> body = Map.of("partial", partial, "limit", limit);
|
||||
return crypto.post("/mnemonic/suggest", body, new TypeToken<Map<String, List<String>>>() {})
|
||||
.thenApply(result -> result.get("suggestions"));
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeypairClient {
|
||||
private final SynorCrypto crypto;
|
||||
KeypairClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<HybridKeypair> generate() {
|
||||
return crypto.post("/keypair/generate", null, new TypeToken<HybridKeypair>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<HybridKeypair> fromMnemonic(String phrase, String passphrase) {
|
||||
Map<String, Object> body = Map.of("phrase", phrase, "passphrase", passphrase);
|
||||
return crypto.post("/keypair/from-mnemonic", body, new TypeToken<HybridKeypair>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<HybridKeypair> fromSeed(byte[] seed) {
|
||||
Map<String, Object> body = Map.of("seed", Base64.getEncoder().encodeToString(seed));
|
||||
return crypto.post("/keypair/from-seed", body, new TypeToken<HybridKeypair>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Address> getAddress(HybridPublicKey publicKey, Network network) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"public_key", publicKey.toMap(),
|
||||
"network", network.getValue()
|
||||
);
|
||||
return crypto.post("/keypair/address", body, new TypeToken<Address>() {});
|
||||
}
|
||||
}
|
||||
|
||||
public static class SigningClient {
|
||||
private final SynorCrypto crypto;
|
||||
SigningClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<HybridSignature> sign(HybridKeypair keypair, byte[] message) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"secret_key", keypair.getSecretKey().toMap(),
|
||||
"message", Base64.getEncoder().encodeToString(message)
|
||||
);
|
||||
return crypto.post("/sign/hybrid", body, new TypeToken<HybridSignature>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> verify(HybridPublicKey publicKey, byte[] message, HybridSignature signature) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"public_key", publicKey.toMap(),
|
||||
"message", Base64.getEncoder().encodeToString(message),
|
||||
"signature", signature.toMap()
|
||||
);
|
||||
return crypto.post("/sign/verify", body, new TypeToken<Map<String, Boolean>>() {})
|
||||
.thenApply(result -> result.get("valid"));
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> signEd25519(byte[] secretKey, byte[] message) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"secret_key", Base64.getEncoder().encodeToString(secretKey),
|
||||
"message", Base64.getEncoder().encodeToString(message)
|
||||
);
|
||||
return crypto.post("/sign/ed25519", body, new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> Base64.getDecoder().decode(result.get("signature")));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FalconClient {
|
||||
private final SynorCrypto crypto;
|
||||
FalconClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<FalconKeypair> generate(FalconVariant variant) {
|
||||
Map<String, Object> body = Map.of("variant", variant.getValue());
|
||||
return crypto.post("/falcon/generate", body, new TypeToken<FalconKeypair>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<FalconSignature> sign(FalconKeypair keypair, byte[] message) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"variant", keypair.getVariant().getValue(),
|
||||
"secret_key", Base64.getEncoder().encodeToString(keypair.getSecretKey().getBytes()),
|
||||
"message", Base64.getEncoder().encodeToString(message)
|
||||
);
|
||||
return crypto.post("/falcon/sign", body, new TypeToken<FalconSignature>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> verify(byte[] publicKey, byte[] message, FalconSignature signature) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"variant", signature.getVariant().getValue(),
|
||||
"public_key", Base64.getEncoder().encodeToString(publicKey),
|
||||
"message", Base64.getEncoder().encodeToString(message),
|
||||
"signature", Base64.getEncoder().encodeToString(signature.getBytes())
|
||||
);
|
||||
return crypto.post("/falcon/verify", body, new TypeToken<Map<String, Boolean>>() {})
|
||||
.thenApply(result -> result.get("valid"));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SphincsClient {
|
||||
private final SynorCrypto crypto;
|
||||
SphincsClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<SphincsKeypair> generate(SphincsVariant variant) {
|
||||
Map<String, Object> body = Map.of("variant", variant.getValue());
|
||||
return crypto.post("/sphincs/generate", body, new TypeToken<SphincsKeypair>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<SphincsSignature> sign(SphincsKeypair keypair, byte[] message) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"variant", keypair.getVariant().getValue(),
|
||||
"secret_key", Base64.getEncoder().encodeToString(keypair.getSecretKey().getBytes()),
|
||||
"message", Base64.getEncoder().encodeToString(message)
|
||||
);
|
||||
return crypto.post("/sphincs/sign", body, new TypeToken<SphincsSignature>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> verify(byte[] publicKey, byte[] message, SphincsSignature signature) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"variant", signature.getVariant().getValue(),
|
||||
"public_key", Base64.getEncoder().encodeToString(publicKey),
|
||||
"message", Base64.getEncoder().encodeToString(message),
|
||||
"signature", Base64.getEncoder().encodeToString(signature.getBytes())
|
||||
);
|
||||
return crypto.post("/sphincs/verify", body, new TypeToken<Map<String, Boolean>>() {})
|
||||
.thenApply(result -> result.get("valid"));
|
||||
}
|
||||
}
|
||||
|
||||
public static class KdfClient {
|
||||
private final SynorCrypto crypto;
|
||||
KdfClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<byte[]> deriveKey(byte[] seed, DerivationConfig config) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("seed", Base64.getEncoder().encodeToString(seed));
|
||||
body.put("output_length", config.getOutputLength());
|
||||
if (config.getSalt() != null) {
|
||||
body.put("salt", Base64.getEncoder().encodeToString(config.getSalt()));
|
||||
}
|
||||
if (config.getInfo() != null) {
|
||||
body.put("info", Base64.getEncoder().encodeToString(config.getInfo()));
|
||||
}
|
||||
return crypto.post("/kdf/hkdf", body, new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> Base64.getDecoder().decode(result.get("key")));
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> deriveFromPassword(byte[] password, PasswordDerivationConfig config) {
|
||||
Map<String, Object> body = Map.of(
|
||||
"password", Base64.getEncoder().encodeToString(password),
|
||||
"salt", Base64.getEncoder().encodeToString(config.getSalt()),
|
||||
"iterations", config.getIterations(),
|
||||
"output_length", config.getOutputLength()
|
||||
);
|
||||
return crypto.post("/kdf/pbkdf2", body, new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> Base64.getDecoder().decode(result.get("key")));
|
||||
}
|
||||
}
|
||||
|
||||
public static class HashClient {
|
||||
private final SynorCrypto crypto;
|
||||
HashClient(SynorCrypto crypto) { this.crypto = crypto; }
|
||||
|
||||
public CompletableFuture<Hash256> sha3_256(byte[] data) {
|
||||
Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
|
||||
return crypto.post("/hash/sha3-256", body, new TypeToken<Hash256>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Hash256> blake3(byte[] data) {
|
||||
Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
|
||||
return crypto.post("/hash/blake3", body, new TypeToken<Hash256>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Hash256> keccak256(byte[] data) {
|
||||
Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
|
||||
return crypto.post("/hash/keccak256", body, new TypeToken<Hash256>() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
404
sdk/java/src/main/java/io/synor/crypto/Types.java
Normal file
404
sdk/java/src/main/java/io/synor/crypto/Types.java
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
package io.synor.crypto;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Synor Crypto SDK Types for Java
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
enum Network {
|
||||
MAINNET("mainnet"),
|
||||
TESTNET("testnet"),
|
||||
DEVNET("devnet");
|
||||
|
||||
private final String value;
|
||||
Network(String value) { this.value = value; }
|
||||
public String getValue() { return value; }
|
||||
public static Network fromValue(String value) {
|
||||
for (Network n : values()) if (n.value.equals(value)) return n;
|
||||
return MAINNET;
|
||||
}
|
||||
}
|
||||
|
||||
enum FalconVariant {
|
||||
FALCON512("falcon512", 690, 897, 128),
|
||||
FALCON1024("falcon1024", 1330, 1793, 256);
|
||||
|
||||
private final String value;
|
||||
private final int signatureSize;
|
||||
private final int publicKeySize;
|
||||
private final int securityLevel;
|
||||
|
||||
FalconVariant(String value, int sigSize, int pkSize, int secLevel) {
|
||||
this.value = value;
|
||||
this.signatureSize = sigSize;
|
||||
this.publicKeySize = pkSize;
|
||||
this.securityLevel = secLevel;
|
||||
}
|
||||
|
||||
public String getValue() { return value; }
|
||||
public int getSignatureSize() { return signatureSize; }
|
||||
public int getPublicKeySize() { return publicKeySize; }
|
||||
public int getSecurityLevel() { return securityLevel; }
|
||||
}
|
||||
|
||||
enum SphincsVariant {
|
||||
SHAKE128S("shake128s", 7856, 128),
|
||||
SHAKE192S("shake192s", 16224, 192),
|
||||
SHAKE256S("shake256s", 29792, 256);
|
||||
|
||||
private final String value;
|
||||
private final int signatureSize;
|
||||
private final int securityLevel;
|
||||
|
||||
SphincsVariant(String value, int sigSize, int secLevel) {
|
||||
this.value = value;
|
||||
this.signatureSize = sigSize;
|
||||
this.securityLevel = secLevel;
|
||||
}
|
||||
|
||||
public String getValue() { return value; }
|
||||
public int getSignatureSize() { return signatureSize; }
|
||||
public int getSecurityLevel() { return securityLevel; }
|
||||
}
|
||||
|
||||
enum PqAlgorithm {
|
||||
DILITHIUM3("dilithium3"),
|
||||
FALCON512("falcon512"),
|
||||
FALCON1024("falcon1024"),
|
||||
SPHINCS128S("sphincs128s"),
|
||||
SPHINCS192S("sphincs192s"),
|
||||
SPHINCS256S("sphincs256s");
|
||||
|
||||
private final String value;
|
||||
PqAlgorithm(String value) { this.value = value; }
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
enum AlgorithmFamily {
|
||||
CLASSICAL("classical"),
|
||||
LATTICE("lattice"),
|
||||
HASH_BASED("hash_based"),
|
||||
HYBRID("hybrid");
|
||||
|
||||
private final String value;
|
||||
AlgorithmFamily(String value) { this.value = value; }
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
class CryptoConfig {
|
||||
private final String apiKey;
|
||||
private String endpoint = "https://crypto.synor.io/v1";
|
||||
private int timeout = 30000;
|
||||
private int retries = 3;
|
||||
private boolean debug = false;
|
||||
private Network defaultNetwork = Network.MAINNET;
|
||||
|
||||
public CryptoConfig(String apiKey) { this.apiKey = apiKey; }
|
||||
|
||||
public String getApiKey() { return apiKey; }
|
||||
public String getEndpoint() { return endpoint; }
|
||||
public int getTimeout() { return timeout; }
|
||||
public int getRetries() { return retries; }
|
||||
public boolean isDebug() { return debug; }
|
||||
public Network getDefaultNetwork() { return defaultNetwork; }
|
||||
|
||||
public CryptoConfig setEndpoint(String endpoint) { this.endpoint = endpoint; return this; }
|
||||
public CryptoConfig setTimeout(int timeout) { this.timeout = timeout; return this; }
|
||||
public CryptoConfig setRetries(int retries) { this.retries = retries; return this; }
|
||||
public CryptoConfig setDebug(boolean debug) { this.debug = debug; return this; }
|
||||
public CryptoConfig setDefaultNetwork(Network network) { this.defaultNetwork = network; return this; }
|
||||
}
|
||||
|
||||
class DerivationConfig {
|
||||
private byte[] salt;
|
||||
private byte[] info;
|
||||
private int outputLength = 32;
|
||||
|
||||
public byte[] getSalt() { return salt; }
|
||||
public byte[] getInfo() { return info; }
|
||||
public int getOutputLength() { return outputLength; }
|
||||
|
||||
public DerivationConfig setSalt(byte[] salt) { this.salt = salt; return this; }
|
||||
public DerivationConfig setInfo(byte[] info) { this.info = info; return this; }
|
||||
public DerivationConfig setOutputLength(int len) { this.outputLength = len; return this; }
|
||||
}
|
||||
|
||||
class PasswordDerivationConfig {
|
||||
private final byte[] salt;
|
||||
private int iterations = 100000;
|
||||
private int outputLength = 32;
|
||||
|
||||
public PasswordDerivationConfig(byte[] salt) { this.salt = salt; }
|
||||
|
||||
public byte[] getSalt() { return salt; }
|
||||
public int getIterations() { return iterations; }
|
||||
public int getOutputLength() { return outputLength; }
|
||||
|
||||
public PasswordDerivationConfig setIterations(int iterations) { this.iterations = iterations; return this; }
|
||||
public PasswordDerivationConfig setOutputLength(int len) { this.outputLength = len; return this; }
|
||||
}
|
||||
|
||||
class DerivationPath {
|
||||
public static final int COIN_TYPE = 0x5359;
|
||||
private final int account;
|
||||
private final int change;
|
||||
private final int index;
|
||||
|
||||
public DerivationPath(int account, int change, int index) {
|
||||
this.account = account;
|
||||
this.change = change;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public static DerivationPath external(int account, int index) { return new DerivationPath(account, 0, index); }
|
||||
public static DerivationPath internal(int account, int index) { return new DerivationPath(account, 1, index); }
|
||||
|
||||
public int getAccount() { return account; }
|
||||
public int getChange() { return change; }
|
||||
public int getIndex() { return index; }
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("m/44'/%d'/%d'/%d/%d", COIN_TYPE, account, change, index); }
|
||||
|
||||
public Map<String, Integer> toMap() { return Map.of("account", account, "change", change, "index", index); }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
class HybridPublicKey {
|
||||
private String ed25519;
|
||||
private String dilithium;
|
||||
|
||||
public byte[] getEd25519() { return Base64.getDecoder().decode(ed25519); }
|
||||
public byte[] getDilithium() { return Base64.getDecoder().decode(dilithium); }
|
||||
public int size() { return getEd25519().length + getDilithium().length; }
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
return Map.of("ed25519", ed25519, "dilithium", dilithium);
|
||||
}
|
||||
}
|
||||
|
||||
class SecretKey {
|
||||
private String ed25519Seed;
|
||||
private String masterSeed;
|
||||
|
||||
public byte[] getEd25519Seed() { return Base64.getDecoder().decode(ed25519Seed); }
|
||||
public byte[] getMasterSeed() { return Base64.getDecoder().decode(masterSeed); }
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
return Map.of("ed25519_seed", ed25519Seed, "master_seed", masterSeed);
|
||||
}
|
||||
}
|
||||
|
||||
class FalconPublicKey {
|
||||
private String variant;
|
||||
private String bytes;
|
||||
|
||||
public FalconVariant getVariant() { return FalconVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(bytes); }
|
||||
}
|
||||
|
||||
class FalconSecretKey {
|
||||
private String variant;
|
||||
private String bytes;
|
||||
|
||||
public FalconVariant getVariant() { return FalconVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(bytes); }
|
||||
}
|
||||
|
||||
class SphincsPublicKey {
|
||||
private String variant;
|
||||
private String bytes;
|
||||
|
||||
public SphincsVariant getVariant() { return SphincsVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(bytes); }
|
||||
}
|
||||
|
||||
class SphincsSecretKey {
|
||||
private String variant;
|
||||
private String bytes;
|
||||
|
||||
public SphincsVariant getVariant() { return SphincsVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(bytes); }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
class HybridSignature {
|
||||
private String ed25519;
|
||||
private String dilithium;
|
||||
|
||||
public byte[] getEd25519() { return Base64.getDecoder().decode(ed25519); }
|
||||
public byte[] getDilithium() { return Base64.getDecoder().decode(dilithium); }
|
||||
public int size() { return getEd25519().length + getDilithium().length; }
|
||||
|
||||
public byte[] toBytes() {
|
||||
byte[] e = getEd25519();
|
||||
byte[] d = getDilithium();
|
||||
byte[] result = new byte[e.length + d.length];
|
||||
System.arraycopy(e, 0, result, 0, e.length);
|
||||
System.arraycopy(d, 0, result, e.length, d.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
return Map.of("ed25519", ed25519, "dilithium", dilithium);
|
||||
}
|
||||
}
|
||||
|
||||
class FalconSignature {
|
||||
private String variant;
|
||||
private String signature;
|
||||
|
||||
public FalconVariant getVariant() { return FalconVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(signature); }
|
||||
public int size() { return getBytes().length; }
|
||||
}
|
||||
|
||||
class SphincsSignature {
|
||||
private String variant;
|
||||
private String signature;
|
||||
|
||||
public SphincsVariant getVariant() { return SphincsVariant.valueOf(variant.toUpperCase()); }
|
||||
public byte[] getBytes() { return Base64.getDecoder().decode(signature); }
|
||||
public int size() { return getBytes().length; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
class HybridKeypair {
|
||||
private HybridPublicKey publicKey;
|
||||
private SecretKey secretKey;
|
||||
private Map<String, String> addresses = new HashMap<>();
|
||||
|
||||
public HybridPublicKey getPublicKey() { return publicKey; }
|
||||
public SecretKey getSecretKey() { return secretKey; }
|
||||
|
||||
public String getAddress(Network network) {
|
||||
return addresses.getOrDefault(network.getValue(), "");
|
||||
}
|
||||
}
|
||||
|
||||
class FalconKeypair {
|
||||
private String variant;
|
||||
private FalconPublicKey publicKey;
|
||||
private FalconSecretKey secretKey;
|
||||
|
||||
public FalconVariant getVariant() { return FalconVariant.valueOf(variant.toUpperCase()); }
|
||||
public FalconPublicKey getPublicKey() { return publicKey; }
|
||||
public FalconSecretKey getSecretKey() { return secretKey; }
|
||||
}
|
||||
|
||||
class SphincsKeypair {
|
||||
private String variant;
|
||||
private SphincsPublicKey publicKey;
|
||||
private SphincsSecretKey secretKey;
|
||||
|
||||
public SphincsVariant getVariant() { return SphincsVariant.valueOf(variant.toUpperCase()); }
|
||||
public SphincsPublicKey getPublicKey() { return publicKey; }
|
||||
public SphincsSecretKey getSecretKey() { return secretKey; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
class Mnemonic {
|
||||
private String phrase;
|
||||
private List<String> words;
|
||||
private int wordCount;
|
||||
private String entropy;
|
||||
|
||||
public String getPhrase() { return phrase; }
|
||||
public List<String> getWords() { return words != null ? words : Arrays.asList(phrase.split(" ")); }
|
||||
public int getWordCount() { return wordCount > 0 ? wordCount : getWords().size(); }
|
||||
public byte[] getEntropy() { return entropy != null ? Base64.getDecoder().decode(entropy) : new byte[0]; }
|
||||
}
|
||||
|
||||
class MnemonicValidation {
|
||||
private boolean valid;
|
||||
private String error;
|
||||
|
||||
public boolean isValid() { return valid; }
|
||||
public String getError() { return error; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
class Address {
|
||||
private String address;
|
||||
private String network;
|
||||
private String pubkeyHash;
|
||||
|
||||
public String getAddress() { return address; }
|
||||
public Network getNetwork() { return Network.fromValue(network); }
|
||||
public byte[] getPubkeyHash() { return pubkeyHash != null ? Base64.getDecoder().decode(pubkeyHash) : new byte[0]; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
class Hash256 {
|
||||
private String hash;
|
||||
|
||||
public String getHex() { return hash; }
|
||||
public byte[] getBytes() {
|
||||
byte[] result = new byte[hash.length() / 2];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (byte) Integer.parseInt(hash.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
class CryptoException extends RuntimeException {
|
||||
private final String code;
|
||||
|
||||
public CryptoException(String message, String code) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() { return code; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
final class CryptoConstants {
|
||||
public static final int ED25519_PUBLIC_KEY_SIZE = 32;
|
||||
public static final int ED25519_SECRET_KEY_SIZE = 32;
|
||||
public static final int ED25519_SIGNATURE_SIZE = 64;
|
||||
public static final int DILITHIUM3_PUBLIC_KEY_SIZE = 1952;
|
||||
public static final int DILITHIUM3_SIGNATURE_SIZE = 3293;
|
||||
public static final int HYBRID_SIGNATURE_SIZE = 64 + 3293;
|
||||
public static final int COIN_TYPE = 0x5359;
|
||||
public static final int MIN_PBKDF2_ITERATIONS = 10000;
|
||||
public static final int MIN_SALT_LENGTH = 8;
|
||||
public static final String DEFAULT_ENDPOINT = "https://crypto.synor.io/v1";
|
||||
|
||||
private CryptoConstants() {}
|
||||
}
|
||||
767
sdk/js/src/crypto/index.ts
Normal file
767
sdk/js/src/crypto/index.ts
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
/**
|
||||
* Synor Crypto SDK for JavaScript/TypeScript
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*
|
||||
* Features:
|
||||
* - Hybrid Ed25519 + Dilithium3 signatures (quantum-resistant)
|
||||
* - BIP-39 mnemonic support (12-24 words)
|
||||
* - BIP-44 hierarchical key derivation
|
||||
* - Post-quantum algorithms: Falcon, SPHINCS+
|
||||
* - Secure key derivation (HKDF, PBKDF2)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { SynorCrypto } from '@synor/crypto';
|
||||
*
|
||||
* const crypto = new SynorCrypto({ apiKey: 'your-api-key' });
|
||||
*
|
||||
* // Generate a mnemonic
|
||||
* const mnemonic = await crypto.mnemonic.generate(24);
|
||||
* console.log('Backup words:', mnemonic.phrase);
|
||||
*
|
||||
* // Create keypair from mnemonic
|
||||
* const keypair = await crypto.keypairs.fromMnemonic(mnemonic);
|
||||
* const address = keypair.address('mainnet');
|
||||
*
|
||||
* // Sign a message
|
||||
* const signature = await crypto.signing.sign(keypair, Buffer.from('Hello!'));
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type {
|
||||
CryptoConfig,
|
||||
Network,
|
||||
MnemonicWordCount,
|
||||
Mnemonic,
|
||||
MnemonicValidation,
|
||||
HybridKeypair,
|
||||
HybridPublicKey,
|
||||
HybridSignature,
|
||||
FalconVariant,
|
||||
FalconKeypair,
|
||||
FalconSignature,
|
||||
SphincsVariant,
|
||||
SphincsKeypair,
|
||||
SphincsSignature,
|
||||
DerivationPath,
|
||||
DerivationConfig,
|
||||
PasswordDerivationConfig,
|
||||
AlgorithmCapabilities,
|
||||
NegotiationPolicy,
|
||||
NegotiationResult,
|
||||
SessionParams,
|
||||
Hash256,
|
||||
Address,
|
||||
} from './types';
|
||||
|
||||
export * from './types';
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Client
|
||||
// ============================================================================
|
||||
|
||||
/** Mnemonic operations */
|
||||
export class MnemonicClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Generates a new random mnemonic.
|
||||
* @param wordCount Number of words (12, 15, 18, 21, or 24)
|
||||
*/
|
||||
async generate(wordCount: MnemonicWordCount = 24): Promise<Mnemonic> {
|
||||
const body = { word_count: wordCount };
|
||||
return this.crypto.post<Mnemonic>('/mnemonic/generate', body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mnemonic from a phrase string.
|
||||
*/
|
||||
async fromPhrase(phrase: string): Promise<Mnemonic> {
|
||||
return this.crypto.post<Mnemonic>('/mnemonic/from-phrase', { phrase });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mnemonic from entropy bytes.
|
||||
*/
|
||||
async fromEntropy(entropy: Uint8Array): Promise<Mnemonic> {
|
||||
const entropyBase64 = Buffer.from(entropy).toString('base64');
|
||||
return this.crypto.post<Mnemonic>('/mnemonic/from-entropy', { entropy: entropyBase64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a mnemonic phrase.
|
||||
*/
|
||||
async validate(phrase: string): Promise<MnemonicValidation> {
|
||||
return this.crypto.post<MnemonicValidation>('/mnemonic/validate', { phrase });
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a 64-byte seed from a mnemonic.
|
||||
* @param mnemonic The mnemonic phrase or object
|
||||
* @param passphrase Optional BIP-39 passphrase
|
||||
*/
|
||||
async toSeed(mnemonic: Mnemonic | string, passphrase: string = ''): Promise<Uint8Array> {
|
||||
const phrase = typeof mnemonic === 'string' ? mnemonic : mnemonic.phrase;
|
||||
const result = await this.crypto.post<{ seed: string }>('/mnemonic/to-seed', {
|
||||
phrase,
|
||||
passphrase,
|
||||
});
|
||||
return Buffer.from(result.seed, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests word completions from the BIP-39 wordlist.
|
||||
*/
|
||||
async suggestWords(partial: string, limit: number = 10): Promise<string[]> {
|
||||
const result = await this.crypto.post<{ suggestions: string[] }>('/mnemonic/suggest', {
|
||||
partial,
|
||||
limit,
|
||||
});
|
||||
return result.suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Client
|
||||
// ============================================================================
|
||||
|
||||
/** Keypair operations */
|
||||
export class KeypairClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Generates a new random hybrid keypair.
|
||||
*/
|
||||
async generate(): Promise<HybridKeypair> {
|
||||
const result = await this.crypto.post<HybridKeypairResponse>('/keypair/generate');
|
||||
return this.wrapKeypair(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keypair from a mnemonic.
|
||||
* @param mnemonic The mnemonic phrase or object
|
||||
* @param passphrase Optional BIP-39 passphrase
|
||||
*/
|
||||
async fromMnemonic(mnemonic: Mnemonic | string, passphrase: string = ''): Promise<HybridKeypair> {
|
||||
const phrase = typeof mnemonic === 'string' ? mnemonic : mnemonic.phrase;
|
||||
const result = await this.crypto.post<HybridKeypairResponse>('/keypair/from-mnemonic', {
|
||||
phrase,
|
||||
passphrase,
|
||||
});
|
||||
return this.wrapKeypair(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keypair from a 64-byte seed.
|
||||
*/
|
||||
async fromSeed(seed: Uint8Array): Promise<HybridKeypair> {
|
||||
const seedBase64 = Buffer.from(seed).toString('base64');
|
||||
const result = await this.crypto.post<HybridKeypairResponse>('/keypair/from-seed', {
|
||||
seed: seedBase64,
|
||||
});
|
||||
return this.wrapKeypair(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a child keypair using BIP-44 path.
|
||||
*/
|
||||
async derive(
|
||||
parentKeypair: HybridKeypair,
|
||||
path: DerivationPath
|
||||
): Promise<HybridKeypair> {
|
||||
const result = await this.crypto.post<HybridKeypairResponse>('/keypair/derive', {
|
||||
public_key: this.encodePublicKey(parentKeypair.publicKey),
|
||||
path,
|
||||
});
|
||||
return this.wrapKeypair(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address for a public key.
|
||||
*/
|
||||
async getAddress(publicKey: HybridPublicKey, network: Network): Promise<Address> {
|
||||
return this.crypto.post<Address>('/keypair/address', {
|
||||
public_key: this.encodePublicKey(publicKey),
|
||||
network,
|
||||
});
|
||||
}
|
||||
|
||||
private wrapKeypair(response: HybridKeypairResponse): HybridKeypair {
|
||||
const publicKey: HybridPublicKey = {
|
||||
ed25519: Buffer.from(response.public_key.ed25519, 'base64'),
|
||||
dilithium: Buffer.from(response.public_key.dilithium, 'base64'),
|
||||
};
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
secretKey: {
|
||||
ed25519Seed: Buffer.from(response.secret_key.ed25519_seed, 'base64'),
|
||||
masterSeed: Buffer.from(response.secret_key.master_seed, 'base64'),
|
||||
},
|
||||
address: (network: Network) => response.addresses[network] || '',
|
||||
};
|
||||
}
|
||||
|
||||
private encodePublicKey(pk: HybridPublicKey): object {
|
||||
return {
|
||||
ed25519: Buffer.from(pk.ed25519).toString('base64'),
|
||||
dilithium: Buffer.from(pk.dilithium).toString('base64'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface HybridKeypairResponse {
|
||||
public_key: {
|
||||
ed25519: string;
|
||||
dilithium: string;
|
||||
};
|
||||
secret_key: {
|
||||
ed25519_seed: string;
|
||||
master_seed: string;
|
||||
};
|
||||
addresses: Record<Network, string>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signing Client
|
||||
// ============================================================================
|
||||
|
||||
/** Signing and verification operations */
|
||||
export class SigningClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Signs a message with a hybrid keypair.
|
||||
*/
|
||||
async sign(keypair: HybridKeypair, message: Uint8Array): Promise<HybridSignature> {
|
||||
const result = await this.crypto.post<{ ed25519: string; dilithium: string }>(
|
||||
'/sign/hybrid',
|
||||
{
|
||||
secret_key: {
|
||||
ed25519_seed: Buffer.from(keypair.secretKey.ed25519Seed).toString('base64'),
|
||||
master_seed: Buffer.from(keypair.secretKey.masterSeed).toString('base64'),
|
||||
},
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
ed25519: Buffer.from(result.ed25519, 'base64'),
|
||||
dilithium: Buffer.from(result.dilithium, 'base64'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a hybrid signature.
|
||||
*/
|
||||
async verify(
|
||||
publicKey: HybridPublicKey,
|
||||
message: Uint8Array,
|
||||
signature: HybridSignature
|
||||
): Promise<boolean> {
|
||||
const result = await this.crypto.post<{ valid: boolean }>('/sign/verify', {
|
||||
public_key: {
|
||||
ed25519: Buffer.from(publicKey.ed25519).toString('base64'),
|
||||
dilithium: Buffer.from(publicKey.dilithium).toString('base64'),
|
||||
},
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
signature: {
|
||||
ed25519: Buffer.from(signature.ed25519).toString('base64'),
|
||||
dilithium: Buffer.from(signature.dilithium).toString('base64'),
|
||||
},
|
||||
});
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message using Ed25519 only (faster but not quantum-resistant).
|
||||
*/
|
||||
async signEd25519(secretKey: Uint8Array, message: Uint8Array): Promise<Uint8Array> {
|
||||
const result = await this.crypto.post<{ signature: string }>('/sign/ed25519', {
|
||||
secret_key: Buffer.from(secretKey).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
});
|
||||
return Buffer.from(result.signature, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an Ed25519 signature.
|
||||
*/
|
||||
async verifyEd25519(
|
||||
publicKey: Uint8Array,
|
||||
message: Uint8Array,
|
||||
signature: Uint8Array
|
||||
): Promise<boolean> {
|
||||
const result = await this.crypto.post<{ valid: boolean }>('/sign/verify-ed25519', {
|
||||
public_key: Buffer.from(publicKey).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
signature: Buffer.from(signature).toString('base64'),
|
||||
});
|
||||
return result.valid;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Falcon Client
|
||||
// ============================================================================
|
||||
|
||||
/** Falcon (FIPS 206) compact signature operations */
|
||||
export class FalconClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Generates a Falcon keypair.
|
||||
* @param variant Falcon-512 (128-bit) or Falcon-1024 (256-bit)
|
||||
*/
|
||||
async generate(variant: FalconVariant = 'falcon512'): Promise<FalconKeypair> {
|
||||
return this.crypto.post<FalconKeypair>('/falcon/generate', { variant });
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message with Falcon.
|
||||
*/
|
||||
async sign(keypair: FalconKeypair, message: Uint8Array): Promise<FalconSignature> {
|
||||
const result = await this.crypto.post<{ signature: string; variant: FalconVariant }>(
|
||||
'/falcon/sign',
|
||||
{
|
||||
variant: keypair.variant,
|
||||
secret_key: Buffer.from(keypair.secretKey.bytes).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
}
|
||||
);
|
||||
return {
|
||||
variant: result.variant,
|
||||
bytes: Buffer.from(result.signature, 'base64'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a Falcon signature.
|
||||
*/
|
||||
async verify(
|
||||
publicKey: Uint8Array,
|
||||
message: Uint8Array,
|
||||
signature: FalconSignature
|
||||
): Promise<boolean> {
|
||||
const result = await this.crypto.post<{ valid: boolean }>('/falcon/verify', {
|
||||
variant: signature.variant,
|
||||
public_key: Buffer.from(publicKey).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
signature: Buffer.from(signature.bytes).toString('base64'),
|
||||
});
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature size for a variant.
|
||||
*/
|
||||
signatureSize(variant: FalconVariant): number {
|
||||
return variant === 'falcon512' ? 690 : 1330;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public key size for a variant.
|
||||
*/
|
||||
publicKeySize(variant: FalconVariant): number {
|
||||
return variant === 'falcon512' ? 897 : 1793;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the security level in bits.
|
||||
*/
|
||||
securityLevel(variant: FalconVariant): number {
|
||||
return variant === 'falcon512' ? 128 : 256;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPHINCS+ Client
|
||||
// ============================================================================
|
||||
|
||||
/** SPHINCS+ (FIPS 205) hash-based signature operations */
|
||||
export class SphincsClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Generates a SPHINCS+ keypair.
|
||||
*/
|
||||
async generate(variant: SphincsVariant = 'shake128s'): Promise<SphincsKeypair> {
|
||||
return this.crypto.post<SphincsKeypair>('/sphincs/generate', { variant });
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message with SPHINCS+.
|
||||
*/
|
||||
async sign(keypair: SphincsKeypair, message: Uint8Array): Promise<SphincsSignature> {
|
||||
const result = await this.crypto.post<{ signature: string; variant: SphincsVariant }>(
|
||||
'/sphincs/sign',
|
||||
{
|
||||
variant: keypair.variant,
|
||||
secret_key: Buffer.from(keypair.secretKey.bytes).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
}
|
||||
);
|
||||
return {
|
||||
variant: result.variant,
|
||||
bytes: Buffer.from(result.signature, 'base64'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a SPHINCS+ signature.
|
||||
*/
|
||||
async verify(
|
||||
publicKey: Uint8Array,
|
||||
message: Uint8Array,
|
||||
signature: SphincsSignature
|
||||
): Promise<boolean> {
|
||||
const result = await this.crypto.post<{ valid: boolean }>('/sphincs/verify', {
|
||||
variant: signature.variant,
|
||||
public_key: Buffer.from(publicKey).toString('base64'),
|
||||
message: Buffer.from(message).toString('base64'),
|
||||
signature: Buffer.from(signature.bytes).toString('base64'),
|
||||
});
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature size for a variant.
|
||||
*/
|
||||
signatureSize(variant: SphincsVariant): number {
|
||||
const sizes: Record<SphincsVariant, number> = {
|
||||
shake128s: 7856,
|
||||
shake192s: 16224,
|
||||
shake256s: 29792,
|
||||
};
|
||||
return sizes[variant];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the security level in bits.
|
||||
*/
|
||||
securityLevel(variant: SphincsVariant): number {
|
||||
const levels: Record<SphincsVariant, number> = {
|
||||
shake128s: 128,
|
||||
shake192s: 192,
|
||||
shake256s: 256,
|
||||
};
|
||||
return levels[variant];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Derivation Client
|
||||
// ============================================================================
|
||||
|
||||
/** Key derivation operations */
|
||||
export class KdfClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Derives a key using HKDF-SHA3-256.
|
||||
*/
|
||||
async deriveKey(
|
||||
seed: Uint8Array,
|
||||
config: DerivationConfig = {}
|
||||
): Promise<Uint8Array> {
|
||||
const result = await this.crypto.post<{ key: string }>('/kdf/hkdf', {
|
||||
seed: Buffer.from(seed).toString('base64'),
|
||||
salt: config.salt ? Buffer.from(config.salt).toString('base64') : undefined,
|
||||
info: config.info ? Buffer.from(config.info).toString('base64') : undefined,
|
||||
output_length: config.outputLength ?? 32,
|
||||
});
|
||||
return Buffer.from(result.key, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a key from a password using PBKDF2.
|
||||
*/
|
||||
async deriveFromPassword(
|
||||
password: string | Uint8Array,
|
||||
config: PasswordDerivationConfig
|
||||
): Promise<Uint8Array> {
|
||||
const passwordBytes = typeof password === 'string'
|
||||
? Buffer.from(password, 'utf-8')
|
||||
: password;
|
||||
|
||||
const result = await this.crypto.post<{ key: string }>('/kdf/pbkdf2', {
|
||||
password: Buffer.from(passwordBytes).toString('base64'),
|
||||
salt: Buffer.from(config.salt).toString('base64'),
|
||||
iterations: config.iterations,
|
||||
output_length: config.outputLength ?? 32,
|
||||
});
|
||||
return Buffer.from(result.key, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives an Ed25519 key from a master seed.
|
||||
*/
|
||||
async deriveEd25519Key(masterSeed: Uint8Array, account: number): Promise<Uint8Array> {
|
||||
const result = await this.crypto.post<{ key: string }>('/kdf/ed25519', {
|
||||
master_seed: Buffer.from(masterSeed).toString('base64'),
|
||||
account,
|
||||
});
|
||||
return Buffer.from(result.key, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a child key using BIP-32 style derivation.
|
||||
*/
|
||||
async deriveChildKey(
|
||||
parentKey: Uint8Array,
|
||||
chainCode: Uint8Array,
|
||||
index: number
|
||||
): Promise<{ key: Uint8Array; chainCode: Uint8Array }> {
|
||||
const result = await this.crypto.post<{ key: string; chain_code: string }>('/kdf/child', {
|
||||
parent_key: Buffer.from(parentKey).toString('base64'),
|
||||
chain_code: Buffer.from(chainCode).toString('base64'),
|
||||
index,
|
||||
});
|
||||
return {
|
||||
key: Buffer.from(result.key, 'base64'),
|
||||
chainCode: Buffer.from(result.chain_code, 'base64'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BIP-44 derivation path.
|
||||
*/
|
||||
createPath(account: number, change: number = 0, index: number = 0): DerivationPath {
|
||||
return { account, change, index };
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a derivation path as a string.
|
||||
*/
|
||||
formatPath(path: DerivationPath): string {
|
||||
const coinType = 0x5359; // Synor coin type
|
||||
return `m/44'/${coinType}'/${path.account}'/${path.change}/${path.index}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hashing Client
|
||||
// ============================================================================
|
||||
|
||||
/** Hashing operations */
|
||||
export class HashClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Computes SHA3-256 hash.
|
||||
*/
|
||||
async sha3_256(data: Uint8Array): Promise<Hash256> {
|
||||
const result = await this.crypto.post<{ hash: string }>('/hash/sha3-256', {
|
||||
data: Buffer.from(data).toString('base64'),
|
||||
});
|
||||
const bytes = Buffer.from(result.hash, 'hex');
|
||||
return { bytes, hex: result.hash };
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes BLAKE3 hash.
|
||||
*/
|
||||
async blake3(data: Uint8Array): Promise<Hash256> {
|
||||
const result = await this.crypto.post<{ hash: string }>('/hash/blake3', {
|
||||
data: Buffer.from(data).toString('base64'),
|
||||
});
|
||||
const bytes = Buffer.from(result.hash, 'hex');
|
||||
return { bytes, hex: result.hash };
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes Keccak-256 hash (Ethereum compatible).
|
||||
*/
|
||||
async keccak256(data: Uint8Array): Promise<Hash256> {
|
||||
const result = await this.crypto.post<{ hash: string }>('/hash/keccak256', {
|
||||
data: Buffer.from(data).toString('base64'),
|
||||
});
|
||||
const bytes = Buffer.from(result.hash, 'hex');
|
||||
return { bytes, hex: result.hash };
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple hashes.
|
||||
*/
|
||||
async combine(hashes: Uint8Array[]): Promise<Hash256> {
|
||||
const result = await this.crypto.post<{ hash: string }>('/hash/combine', {
|
||||
hashes: hashes.map(h => Buffer.from(h).toString('base64')),
|
||||
});
|
||||
const bytes = Buffer.from(result.hash, 'hex');
|
||||
return { bytes, hex: result.hash };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Negotiation Client
|
||||
// ============================================================================
|
||||
|
||||
/** Algorithm negotiation operations */
|
||||
export class NegotiationClient {
|
||||
constructor(private crypto: SynorCrypto) {}
|
||||
|
||||
/**
|
||||
* Gets the local capabilities.
|
||||
*/
|
||||
async getCapabilities(): Promise<AlgorithmCapabilities> {
|
||||
return this.crypto.get<AlgorithmCapabilities>('/negotiation/capabilities');
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiates algorithm selection with a peer.
|
||||
*/
|
||||
async negotiate(
|
||||
peerCapabilities: AlgorithmCapabilities,
|
||||
policy: NegotiationPolicy
|
||||
): Promise<NegotiationResult> {
|
||||
return this.crypto.post<NegotiationResult>('/negotiation/negotiate', {
|
||||
peer_capabilities: peerCapabilities,
|
||||
policy,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes session parameters after negotiation.
|
||||
*/
|
||||
async establishSession(
|
||||
result: NegotiationResult,
|
||||
peerPublicKey: Uint8Array
|
||||
): Promise<SessionParams> {
|
||||
return this.crypto.post<SessionParams>('/negotiation/session', {
|
||||
negotiation_result: result,
|
||||
peer_public_key: Buffer.from(peerPublicKey).toString('base64'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Client
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Synor Crypto SDK Client
|
||||
*
|
||||
* Provides quantum-resistant cryptographic operations for the Synor blockchain.
|
||||
*/
|
||||
export class SynorCrypto {
|
||||
private config: Required<CryptoConfig>;
|
||||
private closed = false;
|
||||
|
||||
/** Mnemonic operations */
|
||||
readonly mnemonic: MnemonicClient;
|
||||
/** Keypair operations */
|
||||
readonly keypairs: KeypairClient;
|
||||
/** Signing operations */
|
||||
readonly signing: SigningClient;
|
||||
/** Falcon (compact PQ) operations */
|
||||
readonly falcon: FalconClient;
|
||||
/** SPHINCS+ (hash-based) operations */
|
||||
readonly sphincs: SphincsClient;
|
||||
/** Key derivation operations */
|
||||
readonly kdf: KdfClient;
|
||||
/** Hashing operations */
|
||||
readonly hash: HashClient;
|
||||
/** Algorithm negotiation */
|
||||
readonly negotiation: NegotiationClient;
|
||||
|
||||
constructor(config: CryptoConfig) {
|
||||
this.config = {
|
||||
apiKey: config.apiKey,
|
||||
endpoint: config.endpoint ?? 'https://crypto.synor.io/v1',
|
||||
timeout: config.timeout ?? 30000,
|
||||
retries: config.retries ?? 3,
|
||||
debug: config.debug ?? false,
|
||||
defaultNetwork: config.defaultNetwork ?? 'mainnet',
|
||||
};
|
||||
|
||||
this.mnemonic = new MnemonicClient(this);
|
||||
this.keypairs = new KeypairClient(this);
|
||||
this.signing = new SigningClient(this);
|
||||
this.falcon = new FalconClient(this);
|
||||
this.sphincs = new SphincsClient(this);
|
||||
this.kdf = new KdfClient(this);
|
||||
this.hash = new HashClient(this);
|
||||
this.negotiation = new NegotiationClient(this);
|
||||
}
|
||||
|
||||
/** Default network */
|
||||
get defaultNetwork(): Network {
|
||||
return this.config.defaultNetwork;
|
||||
}
|
||||
|
||||
/** Health check */
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.get<{ status: string }>('/health');
|
||||
return result.status === 'healthy';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get service info */
|
||||
async getInfo(): Promise<Record<string, unknown>> {
|
||||
return this.get<Record<string, unknown>>('/info');
|
||||
}
|
||||
|
||||
/** Close the client */
|
||||
close(): void {
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
/** Check if client is closed */
|
||||
get isClosed(): boolean {
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async get<T>(path: string): Promise<T> {
|
||||
this.checkClosed();
|
||||
const response = await fetch(`${this.config.endpoint}${path}`, {
|
||||
method: 'GET',
|
||||
headers: this.headers(),
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async post<T>(path: string, body?: unknown): Promise<T> {
|
||||
this.checkClosed();
|
||||
const response = await fetch(`${this.config.endpoint}${path}`, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
private headers(): Record<string, string> {
|
||||
return {
|
||||
'Authorization': `Bearer ${this.config.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'js/0.1.0',
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResponse<T>(response: Response): Promise<T> {
|
||||
const json = await response.json();
|
||||
if (!response.ok) {
|
||||
const { CryptoError } = await import('./types');
|
||||
throw new CryptoError(
|
||||
json.message ?? `HTTP ${response.status}`,
|
||||
json.code
|
||||
);
|
||||
}
|
||||
return json as T;
|
||||
}
|
||||
|
||||
private checkClosed(): void {
|
||||
if (this.closed) {
|
||||
const { CryptoError } = require('./types');
|
||||
throw new CryptoError('Client has been closed', 'NETWORK_ERROR');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SynorCrypto;
|
||||
370
sdk/js/src/crypto/types.ts
Normal file
370
sdk/js/src/crypto/types.ts
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
/**
|
||||
* Synor Crypto SDK Types
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
/** Network type for address generation */
|
||||
export type Network = 'mainnet' | 'testnet' | 'devnet';
|
||||
|
||||
/** Supported mnemonic word counts */
|
||||
export type MnemonicWordCount = 12 | 15 | 18 | 21 | 24;
|
||||
|
||||
/** Falcon variant selection */
|
||||
export type FalconVariant = 'falcon512' | 'falcon1024';
|
||||
|
||||
/** SPHINCS+ variant selection */
|
||||
export type SphincsVariant = 'shake128s' | 'shake192s' | 'shake256s';
|
||||
|
||||
/** Post-quantum algorithm family */
|
||||
export type PqAlgorithm = 'dilithium3' | 'falcon512' | 'falcon1024' | 'sphincs128s' | 'sphincs192s' | 'sphincs256s';
|
||||
|
||||
/** Algorithm family */
|
||||
export type AlgorithmFamily = 'classical' | 'lattice' | 'hash_based' | 'hybrid';
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
/** Crypto SDK configuration */
|
||||
export interface CryptoConfig {
|
||||
apiKey: string;
|
||||
endpoint?: string;
|
||||
timeout?: number;
|
||||
retries?: number;
|
||||
debug?: boolean;
|
||||
defaultNetwork?: Network;
|
||||
}
|
||||
|
||||
/** Key derivation configuration */
|
||||
export interface DerivationConfig {
|
||||
/** Salt for HKDF (optional) */
|
||||
salt?: Uint8Array;
|
||||
/** Info/context for domain separation */
|
||||
info?: Uint8Array;
|
||||
/** Output key length in bytes */
|
||||
outputLength?: number;
|
||||
}
|
||||
|
||||
/** Password derivation configuration */
|
||||
export interface PasswordDerivationConfig {
|
||||
/** Salt (minimum 8 bytes) */
|
||||
salt: Uint8Array;
|
||||
/** Number of PBKDF2 iterations (minimum 10,000) */
|
||||
iterations: number;
|
||||
/** Output key length in bytes (max 64) */
|
||||
outputLength?: number;
|
||||
}
|
||||
|
||||
/** BIP-44 derivation path */
|
||||
export interface DerivationPath {
|
||||
/** Account number (hardened) */
|
||||
account: number;
|
||||
/** Change: 0 = external, 1 = internal */
|
||||
change: number;
|
||||
/** Address index */
|
||||
index: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
/** Ed25519 public key (32 bytes) */
|
||||
export interface Ed25519PublicKey {
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** Dilithium3 public key (~1952 bytes) */
|
||||
export interface Dilithium3PublicKey {
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** Hybrid public key (Ed25519 + Dilithium3) */
|
||||
export interface HybridPublicKey {
|
||||
/** Ed25519 component (32 bytes) */
|
||||
ed25519: Uint8Array;
|
||||
/** Dilithium3 component (~1952 bytes) */
|
||||
dilithium: Uint8Array;
|
||||
}
|
||||
|
||||
/** Secret key (master seed) */
|
||||
export interface SecretKey {
|
||||
/** Ed25519 seed (32 bytes) */
|
||||
ed25519Seed: Uint8Array;
|
||||
/** Master seed for derivation (64 bytes) */
|
||||
masterSeed: Uint8Array;
|
||||
}
|
||||
|
||||
/** Falcon public key */
|
||||
export interface FalconPublicKey {
|
||||
variant: FalconVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** Falcon secret key */
|
||||
export interface FalconSecretKey {
|
||||
variant: FalconVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** SPHINCS+ public key */
|
||||
export interface SphincsPublicKey {
|
||||
variant: SphincsVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** SPHINCS+ secret key */
|
||||
export interface SphincsSecretKey {
|
||||
variant: SphincsVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
/** Ed25519 signature (64 bytes) */
|
||||
export interface Ed25519Signature {
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** Dilithium3 signature (~3293 bytes) */
|
||||
export interface Dilithium3Signature {
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** Hybrid signature (Ed25519 + Dilithium3) */
|
||||
export interface HybridSignature {
|
||||
/** Ed25519 component (64 bytes) */
|
||||
ed25519: Uint8Array;
|
||||
/** Dilithium3 component (~3293 bytes) */
|
||||
dilithium: Uint8Array;
|
||||
}
|
||||
|
||||
/** Falcon signature */
|
||||
export interface FalconSignature {
|
||||
variant: FalconVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
/** SPHINCS+ signature */
|
||||
export interface SphincsSignature {
|
||||
variant: SphincsVariant;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
/** Ed25519 keypair */
|
||||
export interface Ed25519Keypair {
|
||||
publicKey: Ed25519PublicKey;
|
||||
secretKey: Uint8Array;
|
||||
}
|
||||
|
||||
/** Hybrid keypair (Ed25519 + Dilithium3) */
|
||||
export interface HybridKeypair {
|
||||
publicKey: HybridPublicKey;
|
||||
secretKey: SecretKey;
|
||||
/** Get address for this keypair */
|
||||
address(network: Network): string;
|
||||
}
|
||||
|
||||
/** Falcon keypair */
|
||||
export interface FalconKeypair {
|
||||
variant: FalconVariant;
|
||||
publicKey: FalconPublicKey;
|
||||
secretKey: FalconSecretKey;
|
||||
}
|
||||
|
||||
/** SPHINCS+ keypair */
|
||||
export interface SphincsKeypair {
|
||||
variant: SphincsVariant;
|
||||
publicKey: SphincsPublicKey;
|
||||
secretKey: SphincsSecretKey;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
/** Mnemonic phrase representation */
|
||||
export interface Mnemonic {
|
||||
/** The mnemonic phrase */
|
||||
phrase: string;
|
||||
/** Individual words */
|
||||
words: string[];
|
||||
/** Word count */
|
||||
wordCount: MnemonicWordCount;
|
||||
/** Entropy bytes */
|
||||
entropy: Uint8Array;
|
||||
}
|
||||
|
||||
/** Mnemonic validation result */
|
||||
export interface MnemonicValidation {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
/** Blockchain address */
|
||||
export interface Address {
|
||||
/** Address string (bech32 encoded) */
|
||||
address: string;
|
||||
/** Network */
|
||||
network: Network;
|
||||
/** Public key hash */
|
||||
pubkeyHash: Uint8Array;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
/** 256-bit hash */
|
||||
export interface Hash256 {
|
||||
bytes: Uint8Array;
|
||||
hex: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Algorithm Negotiation Types
|
||||
// ============================================================================
|
||||
|
||||
/** Algorithm capabilities */
|
||||
export interface AlgorithmCapabilities {
|
||||
/** Supported post-quantum algorithms */
|
||||
pqAlgorithms: PqAlgorithm[];
|
||||
/** Classical algorithm support */
|
||||
classical: boolean;
|
||||
/** Hybrid mode support */
|
||||
hybrid: boolean;
|
||||
/** Preferred algorithm */
|
||||
preferred?: PqAlgorithm;
|
||||
}
|
||||
|
||||
/** Negotiation policy */
|
||||
export interface NegotiationPolicy {
|
||||
/** Required minimum security level (bits) */
|
||||
minSecurityLevel: number;
|
||||
/** Prefer smaller signatures */
|
||||
preferCompact: boolean;
|
||||
/** Allow classical-only fallback */
|
||||
allowClassical: boolean;
|
||||
/** Required algorithm families */
|
||||
requiredFamilies: AlgorithmFamily[];
|
||||
}
|
||||
|
||||
/** Negotiation result */
|
||||
export interface NegotiationResult {
|
||||
/** Selected algorithm */
|
||||
algorithm: PqAlgorithm;
|
||||
/** Security level in bits */
|
||||
securityLevel: number;
|
||||
/** Algorithm family */
|
||||
family: AlgorithmFamily;
|
||||
/** Is hybrid mode */
|
||||
hybrid: boolean;
|
||||
}
|
||||
|
||||
/** Session parameters after negotiation */
|
||||
export interface SessionParams {
|
||||
/** Negotiated algorithm */
|
||||
algorithm: PqAlgorithm;
|
||||
/** Session key (if established) */
|
||||
sessionKey?: Uint8Array;
|
||||
/** Expiry timestamp */
|
||||
expiresAt?: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
/** Crypto error codes */
|
||||
export type CryptoErrorCode =
|
||||
| 'INVALID_MNEMONIC'
|
||||
| 'INVALID_WORD_COUNT'
|
||||
| 'INVALID_SEED'
|
||||
| 'INVALID_KEY'
|
||||
| 'INVALID_SIGNATURE'
|
||||
| 'VERIFICATION_FAILED'
|
||||
| 'DERIVATION_FAILED'
|
||||
| 'ALGORITHM_MISMATCH'
|
||||
| 'NEGOTIATION_FAILED'
|
||||
| 'NETWORK_ERROR'
|
||||
| 'TIMEOUT';
|
||||
|
||||
/** Crypto exception */
|
||||
export class CryptoError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code?: CryptoErrorCode,
|
||||
public readonly cause?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'CryptoError';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
export const CryptoConstants = {
|
||||
/** Ed25519 public key size in bytes */
|
||||
ED25519_PUBLIC_KEY_SIZE: 32,
|
||||
/** Ed25519 secret key size in bytes */
|
||||
ED25519_SECRET_KEY_SIZE: 32,
|
||||
/** Ed25519 signature size in bytes */
|
||||
ED25519_SIGNATURE_SIZE: 64,
|
||||
/** Dilithium3 public key size in bytes */
|
||||
DILITHIUM3_PUBLIC_KEY_SIZE: 1952,
|
||||
/** Dilithium3 signature size in bytes */
|
||||
DILITHIUM3_SIGNATURE_SIZE: 3293,
|
||||
/** Hybrid signature size (Ed25519 + Dilithium3) */
|
||||
HYBRID_SIGNATURE_SIZE: 64 + 3293,
|
||||
/** Falcon-512 signature size */
|
||||
FALCON512_SIGNATURE_SIZE: 690,
|
||||
/** Falcon-512 public key size */
|
||||
FALCON512_PUBLIC_KEY_SIZE: 897,
|
||||
/** Falcon-1024 signature size */
|
||||
FALCON1024_SIGNATURE_SIZE: 1330,
|
||||
/** Falcon-1024 public key size */
|
||||
FALCON1024_PUBLIC_KEY_SIZE: 1793,
|
||||
/** SPHINCS+-128s signature size */
|
||||
SPHINCS128S_SIGNATURE_SIZE: 7856,
|
||||
/** SPHINCS+-192s signature size */
|
||||
SPHINCS192S_SIGNATURE_SIZE: 16224,
|
||||
/** SPHINCS+-256s signature size */
|
||||
SPHINCS256S_SIGNATURE_SIZE: 29792,
|
||||
/** BIP-44 coin type for Synor */
|
||||
COIN_TYPE: 0x5359,
|
||||
/** Minimum PBKDF2 iterations */
|
||||
MIN_PBKDF2_ITERATIONS: 10000,
|
||||
/** Minimum salt length for password derivation */
|
||||
MIN_SALT_LENGTH: 8,
|
||||
/** Default API endpoint */
|
||||
DEFAULT_ENDPOINT: 'https://crypto.synor.io/v1',
|
||||
} as const;
|
||||
|
||||
/** Algorithm size comparison */
|
||||
export const AlgorithmSizes = {
|
||||
ed25519: { signature: 64, publicKey: 32 },
|
||||
dilithium3: { signature: 3293, publicKey: 1952 },
|
||||
hybrid: { signature: 3357, publicKey: 1984 },
|
||||
falcon512: { signature: 690, publicKey: 897 },
|
||||
falcon1024: { signature: 1330, publicKey: 1793 },
|
||||
sphincs128s: { signature: 7856, publicKey: 32 },
|
||||
sphincs192s: { signature: 16224, publicKey: 48 },
|
||||
sphincs256s: { signature: 29792, publicKey: 64 },
|
||||
} as const;
|
||||
290
sdk/kotlin/src/main/kotlin/io/synor/crypto/SynorCrypto.kt
Normal file
290
sdk/kotlin/src/main/kotlin/io/synor/crypto/SynorCrypto.kt
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
package io.synor.crypto
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.*
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Synor Crypto SDK for Kotlin
|
||||
*
|
||||
* Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
*
|
||||
* Example:
|
||||
* ```kotlin
|
||||
* val config = CryptoConfig("your-api-key")
|
||||
* val crypto = SynorCrypto(config)
|
||||
*
|
||||
* // Generate a mnemonic
|
||||
* val mnemonic = crypto.mnemonic().generate(24)
|
||||
* println("Backup words: ${mnemonic.phrase}")
|
||||
*
|
||||
* // Create keypair from mnemonic
|
||||
* val keypair = crypto.keypairs().fromMnemonic(mnemonic.phrase, "")
|
||||
* val address = keypair.getAddress(Network.MAINNET)
|
||||
*
|
||||
* // Sign a message
|
||||
* val signature = crypto.signing().sign(keypair, "Hello!".toByteArray())
|
||||
* ```
|
||||
*/
|
||||
class SynorCrypto(private val config: CryptoConfig) {
|
||||
private val httpClient: HttpClient
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
}
|
||||
private val closed = AtomicBoolean(false)
|
||||
|
||||
private val mnemonicClient: MnemonicClient
|
||||
private val keypairClient: KeypairClient
|
||||
private val signingClient: SigningClient
|
||||
private val falconClient: FalconClient
|
||||
private val sphincsClient: SphincsClient
|
||||
private val kdfClient: KdfClient
|
||||
private val hashClient: HashClient
|
||||
|
||||
init {
|
||||
httpClient = HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = config.timeout
|
||||
connectTimeoutMillis = config.timeout
|
||||
}
|
||||
defaultRequest {
|
||||
header("Authorization", "Bearer ${config.apiKey}")
|
||||
header("X-SDK-Version", "kotlin/0.1.0")
|
||||
}
|
||||
}
|
||||
|
||||
mnemonicClient = MnemonicClient(this)
|
||||
keypairClient = KeypairClient(this)
|
||||
signingClient = SigningClient(this)
|
||||
falconClient = FalconClient(this)
|
||||
sphincsClient = SphincsClient(this)
|
||||
kdfClient = KdfClient(this)
|
||||
hashClient = HashClient(this)
|
||||
}
|
||||
|
||||
fun getDefaultNetwork(): Network = config.defaultNetwork
|
||||
|
||||
fun mnemonic(): MnemonicClient = mnemonicClient
|
||||
fun keypairs(): KeypairClient = keypairClient
|
||||
fun signing(): SigningClient = signingClient
|
||||
fun falcon(): FalconClient = falconClient
|
||||
fun sphincs(): SphincsClient = sphincsClient
|
||||
fun kdf(): KdfClient = kdfClient
|
||||
fun hash(): HashClient = hashClient
|
||||
|
||||
suspend fun healthCheck(): Boolean = try {
|
||||
val result = get<Map<String, Any>>("/health")
|
||||
result["status"] == "healthy"
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
suspend fun getInfo(): Map<String, Any> = get("/info")
|
||||
|
||||
fun close() {
|
||||
closed.set(true)
|
||||
httpClient.close()
|
||||
}
|
||||
|
||||
fun isClosed(): Boolean = closed.get()
|
||||
|
||||
internal suspend inline fun <reified T> get(path: String): T {
|
||||
checkClosed()
|
||||
val response = httpClient.get("${config.endpoint}$path")
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
internal suspend inline fun <reified T> post(path: String, body: Any? = null): T {
|
||||
checkClosed()
|
||||
val response = httpClient.post("${config.endpoint}$path") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body ?: emptyMap<String, Any>())
|
||||
}
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal suspend inline fun <reified T> handleResponse(response: HttpResponse): T {
|
||||
if (response.status.value >= 400) {
|
||||
val errorBody = response.bodyAsText()
|
||||
val error = try {
|
||||
json.decodeFromString<Map<String, String>>(errorBody)
|
||||
} catch (e: Exception) {
|
||||
emptyMap()
|
||||
}
|
||||
throw CryptoException(
|
||||
error["message"] ?: "HTTP ${response.status.value}",
|
||||
error["code"]
|
||||
)
|
||||
}
|
||||
return response.body()
|
||||
}
|
||||
|
||||
private fun checkClosed() {
|
||||
if (closed.get()) {
|
||||
throw CryptoException("Client has been closed", "CLIENT_CLOSED")
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Sub-Clients
|
||||
// ========================================================================
|
||||
|
||||
class MnemonicClient(private val crypto: SynorCrypto) {
|
||||
suspend fun generate(wordCount: Int = 24): Mnemonic =
|
||||
crypto.post("/mnemonic/generate", mapOf("word_count" to wordCount))
|
||||
|
||||
suspend fun fromPhrase(phrase: String): Mnemonic =
|
||||
crypto.post("/mnemonic/from-phrase", mapOf("phrase" to phrase))
|
||||
|
||||
suspend fun validate(phrase: String): MnemonicValidation =
|
||||
crypto.post("/mnemonic/validate", mapOf("phrase" to phrase))
|
||||
|
||||
suspend fun toSeed(phrase: String, passphrase: String = ""): ByteArray {
|
||||
val result: Map<String, String> = crypto.post(
|
||||
"/mnemonic/to-seed",
|
||||
mapOf("phrase" to phrase, "passphrase" to passphrase)
|
||||
)
|
||||
return Base64.getDecoder().decode(result["seed"])
|
||||
}
|
||||
|
||||
suspend fun suggestWords(partial: String, limit: Int = 5): List<String> {
|
||||
val result: Map<String, List<String>> = crypto.post(
|
||||
"/mnemonic/suggest",
|
||||
mapOf("partial" to partial, "limit" to limit)
|
||||
)
|
||||
return result["suggestions"] ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
class KeypairClient(private val crypto: SynorCrypto) {
|
||||
suspend fun generate(): HybridKeypair =
|
||||
crypto.post("/keypair/generate")
|
||||
|
||||
suspend fun fromMnemonic(phrase: String, passphrase: String = ""): HybridKeypair =
|
||||
crypto.post("/keypair/from-mnemonic", mapOf("phrase" to phrase, "passphrase" to passphrase))
|
||||
|
||||
suspend fun fromSeed(seed: ByteArray): HybridKeypair =
|
||||
crypto.post("/keypair/from-seed", mapOf("seed" to Base64.getEncoder().encodeToString(seed)))
|
||||
|
||||
suspend fun getAddress(publicKey: HybridPublicKey, network: Network): Address =
|
||||
crypto.post("/keypair/address", mapOf("public_key" to publicKey.toMap(), "network" to network.value))
|
||||
}
|
||||
|
||||
class SigningClient(private val crypto: SynorCrypto) {
|
||||
suspend fun sign(keypair: HybridKeypair, message: ByteArray): HybridSignature =
|
||||
crypto.post("/sign/hybrid", mapOf(
|
||||
"secret_key" to keypair.secretKey.toMap(),
|
||||
"message" to Base64.getEncoder().encodeToString(message)
|
||||
))
|
||||
|
||||
suspend fun verify(publicKey: HybridPublicKey, message: ByteArray, signature: HybridSignature): Boolean {
|
||||
val result: Map<String, Boolean> = crypto.post("/sign/verify", mapOf(
|
||||
"public_key" to publicKey.toMap(),
|
||||
"message" to Base64.getEncoder().encodeToString(message),
|
||||
"signature" to signature.toMap()
|
||||
))
|
||||
return result["valid"] ?: false
|
||||
}
|
||||
|
||||
suspend fun signEd25519(secretKey: ByteArray, message: ByteArray): ByteArray {
|
||||
val result: Map<String, String> = crypto.post("/sign/ed25519", mapOf(
|
||||
"secret_key" to Base64.getEncoder().encodeToString(secretKey),
|
||||
"message" to Base64.getEncoder().encodeToString(message)
|
||||
))
|
||||
return Base64.getDecoder().decode(result["signature"])
|
||||
}
|
||||
}
|
||||
|
||||
class FalconClient(private val crypto: SynorCrypto) {
|
||||
suspend fun generate(variant: FalconVariant = FalconVariant.FALCON512): FalconKeypair =
|
||||
crypto.post("/falcon/generate", mapOf("variant" to variant.value))
|
||||
|
||||
suspend fun sign(keypair: FalconKeypair, message: ByteArray): FalconSignature =
|
||||
crypto.post("/falcon/sign", mapOf(
|
||||
"variant" to keypair.getVariantEnum().value,
|
||||
"secret_key" to Base64.getEncoder().encodeToString(keypair.secretKey.getKeyBytes()),
|
||||
"message" to Base64.getEncoder().encodeToString(message)
|
||||
))
|
||||
|
||||
suspend fun verify(publicKey: ByteArray, message: ByteArray, signature: FalconSignature): Boolean {
|
||||
val result: Map<String, Boolean> = crypto.post("/falcon/verify", mapOf(
|
||||
"variant" to signature.getVariantEnum().value,
|
||||
"public_key" to Base64.getEncoder().encodeToString(publicKey),
|
||||
"message" to Base64.getEncoder().encodeToString(message),
|
||||
"signature" to Base64.getEncoder().encodeToString(signature.getBytes())
|
||||
))
|
||||
return result["valid"] ?: false
|
||||
}
|
||||
}
|
||||
|
||||
class SphincsClient(private val crypto: SynorCrypto) {
|
||||
suspend fun generate(variant: SphincsVariant = SphincsVariant.SHAKE128S): SphincsKeypair =
|
||||
crypto.post("/sphincs/generate", mapOf("variant" to variant.value))
|
||||
|
||||
suspend fun sign(keypair: SphincsKeypair, message: ByteArray): SphincsSignature =
|
||||
crypto.post("/sphincs/sign", mapOf(
|
||||
"variant" to keypair.getVariantEnum().value,
|
||||
"secret_key" to Base64.getEncoder().encodeToString(keypair.secretKey.getKeyBytes()),
|
||||
"message" to Base64.getEncoder().encodeToString(message)
|
||||
))
|
||||
|
||||
suspend fun verify(publicKey: ByteArray, message: ByteArray, signature: SphincsSignature): Boolean {
|
||||
val result: Map<String, Boolean> = crypto.post("/sphincs/verify", mapOf(
|
||||
"variant" to signature.getVariantEnum().value,
|
||||
"public_key" to Base64.getEncoder().encodeToString(publicKey),
|
||||
"message" to Base64.getEncoder().encodeToString(message),
|
||||
"signature" to Base64.getEncoder().encodeToString(signature.getBytes())
|
||||
))
|
||||
return result["valid"] ?: false
|
||||
}
|
||||
}
|
||||
|
||||
class KdfClient(private val crypto: SynorCrypto) {
|
||||
suspend fun deriveKey(seed: ByteArray, config: DerivationConfig = DerivationConfig()): ByteArray {
|
||||
val body = mutableMapOf<String, Any>(
|
||||
"seed" to Base64.getEncoder().encodeToString(seed),
|
||||
"output_length" to config.outputLength
|
||||
)
|
||||
config.salt?.let { body["salt"] = Base64.getEncoder().encodeToString(it) }
|
||||
config.info?.let { body["info"] = Base64.getEncoder().encodeToString(it) }
|
||||
|
||||
val result: Map<String, String> = crypto.post("/kdf/hkdf", body)
|
||||
return Base64.getDecoder().decode(result["key"])
|
||||
}
|
||||
|
||||
suspend fun deriveFromPassword(password: ByteArray, config: PasswordDerivationConfig): ByteArray {
|
||||
val result: Map<String, String> = crypto.post("/kdf/pbkdf2", mapOf(
|
||||
"password" to Base64.getEncoder().encodeToString(password),
|
||||
"salt" to Base64.getEncoder().encodeToString(config.salt),
|
||||
"iterations" to config.iterations,
|
||||
"output_length" to config.outputLength
|
||||
))
|
||||
return Base64.getDecoder().decode(result["key"])
|
||||
}
|
||||
}
|
||||
|
||||
class HashClient(private val crypto: SynorCrypto) {
|
||||
suspend fun sha3_256(data: ByteArray): Hash256 =
|
||||
crypto.post("/hash/sha3-256", mapOf("data" to Base64.getEncoder().encodeToString(data)))
|
||||
|
||||
suspend fun blake3(data: ByteArray): Hash256 =
|
||||
crypto.post("/hash/blake3", mapOf("data" to Base64.getEncoder().encodeToString(data)))
|
||||
|
||||
suspend fun keccak256(data: ByteArray): Hash256 =
|
||||
crypto.post("/hash/keccak256", mapOf("data" to Base64.getEncoder().encodeToString(data)))
|
||||
}
|
||||
}
|
||||
353
sdk/kotlin/src/main/kotlin/io/synor/crypto/Types.kt
Normal file
353
sdk/kotlin/src/main/kotlin/io/synor/crypto/Types.kt
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
package io.synor.crypto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.Base64
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
enum class Network(val value: String) {
|
||||
@SerialName("mainnet") MAINNET("mainnet"),
|
||||
@SerialName("testnet") TESTNET("testnet"),
|
||||
@SerialName("devnet") DEVNET("devnet");
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: String): Network =
|
||||
entries.find { it.value == value } ?: MAINNET
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class FalconVariant(
|
||||
val value: String,
|
||||
val signatureSize: Int,
|
||||
val publicKeySize: Int,
|
||||
val securityLevel: Int
|
||||
) {
|
||||
@SerialName("falcon512") FALCON512("falcon512", 690, 897, 128),
|
||||
@SerialName("falcon1024") FALCON1024("falcon1024", 1330, 1793, 256);
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: String): FalconVariant =
|
||||
entries.find { it.value == value } ?: FALCON512
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class SphincsVariant(
|
||||
val value: String,
|
||||
val signatureSize: Int,
|
||||
val securityLevel: Int
|
||||
) {
|
||||
@SerialName("shake128s") SHAKE128S("shake128s", 7856, 128),
|
||||
@SerialName("shake192s") SHAKE192S("shake192s", 16224, 192),
|
||||
@SerialName("shake256s") SHAKE256S("shake256s", 29792, 256);
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: String): SphincsVariant =
|
||||
entries.find { it.value == value } ?: SHAKE128S
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class PqAlgorithm(val value: String) {
|
||||
@SerialName("dilithium3") DILITHIUM3("dilithium3"),
|
||||
@SerialName("falcon512") FALCON512("falcon512"),
|
||||
@SerialName("falcon1024") FALCON1024("falcon1024"),
|
||||
@SerialName("sphincs128s") SPHINCS128S("sphincs128s"),
|
||||
@SerialName("sphincs192s") SPHINCS192S("sphincs192s"),
|
||||
@SerialName("sphincs256s") SPHINCS256S("sphincs256s")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class AlgorithmFamily(val value: String) {
|
||||
@SerialName("classical") CLASSICAL("classical"),
|
||||
@SerialName("lattice") LATTICE("lattice"),
|
||||
@SerialName("hash_based") HASH_BASED("hash_based"),
|
||||
@SerialName("hybrid") HYBRID("hybrid")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
data class CryptoConfig(
|
||||
val apiKey: String,
|
||||
var endpoint: String = "https://crypto.synor.io/v1",
|
||||
var timeout: Long = 30000,
|
||||
var retries: Int = 3,
|
||||
var debug: Boolean = false,
|
||||
var defaultNetwork: Network = Network.MAINNET
|
||||
)
|
||||
|
||||
data class DerivationConfig(
|
||||
var salt: ByteArray? = null,
|
||||
var info: ByteArray? = null,
|
||||
var outputLength: Int = 32
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DerivationConfig) return false
|
||||
return salt.contentEquals(other.salt) && info.contentEquals(other.info) && outputLength == other.outputLength
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = salt?.contentHashCode() ?: 0
|
||||
result = 31 * result + (info?.contentHashCode() ?: 0)
|
||||
result = 31 * result + outputLength
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class PasswordDerivationConfig(
|
||||
val salt: ByteArray,
|
||||
var iterations: Int = 100000,
|
||||
var outputLength: Int = 32
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is PasswordDerivationConfig) return false
|
||||
return salt.contentEquals(other.salt) && iterations == other.iterations && outputLength == other.outputLength
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = salt.contentHashCode()
|
||||
result = 31 * result + iterations
|
||||
result = 31 * result + outputLength
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class DerivationPath(
|
||||
val account: Int,
|
||||
val change: Int,
|
||||
val index: Int
|
||||
) {
|
||||
companion object {
|
||||
const val COIN_TYPE = 0x5359
|
||||
|
||||
fun external(account: Int, index: Int) = DerivationPath(account, 0, index)
|
||||
fun internal(account: Int, index: Int) = DerivationPath(account, 1, index)
|
||||
}
|
||||
|
||||
override fun toString(): String = "m/44'/$COIN_TYPE'/$account'/$change/$index"
|
||||
|
||||
fun toMap(): Map<String, Int> = mapOf("account" to account, "change" to change, "index" to index)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class HybridPublicKey(
|
||||
val ed25519: String,
|
||||
val dilithium: String
|
||||
) {
|
||||
fun getEd25519Bytes(): ByteArray = Base64.getDecoder().decode(ed25519)
|
||||
fun getDilithiumBytes(): ByteArray = Base64.getDecoder().decode(dilithium)
|
||||
fun size(): Int = getEd25519Bytes().size + getDilithiumBytes().size
|
||||
fun toMap(): Map<String, String> = mapOf("ed25519" to ed25519, "dilithium" to dilithium)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SecretKey(
|
||||
@SerialName("ed25519_seed") val ed25519Seed: String,
|
||||
@SerialName("master_seed") val masterSeed: String
|
||||
) {
|
||||
fun getEd25519SeedBytes(): ByteArray = Base64.getDecoder().decode(ed25519Seed)
|
||||
fun getMasterSeedBytes(): ByteArray = Base64.getDecoder().decode(masterSeed)
|
||||
fun toMap(): Map<String, String> = mapOf("ed25519_seed" to ed25519Seed, "master_seed" to masterSeed)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FalconPublicKey(
|
||||
val variant: String,
|
||||
val bytes: String
|
||||
) {
|
||||
fun getVariantEnum(): FalconVariant = FalconVariant.fromValue(variant)
|
||||
fun getKeyBytes(): ByteArray = Base64.getDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FalconSecretKey(
|
||||
val variant: String,
|
||||
val bytes: String
|
||||
) {
|
||||
fun getVariantEnum(): FalconVariant = FalconVariant.fromValue(variant)
|
||||
fun getKeyBytes(): ByteArray = Base64.getDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SphincsPublicKey(
|
||||
val variant: String,
|
||||
val bytes: String
|
||||
) {
|
||||
fun getVariantEnum(): SphincsVariant = SphincsVariant.fromValue(variant)
|
||||
fun getKeyBytes(): ByteArray = Base64.getDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SphincsSecretKey(
|
||||
val variant: String,
|
||||
val bytes: String
|
||||
) {
|
||||
fun getVariantEnum(): SphincsVariant = SphincsVariant.fromValue(variant)
|
||||
fun getKeyBytes(): ByteArray = Base64.getDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class HybridSignature(
|
||||
val ed25519: String,
|
||||
val dilithium: String
|
||||
) {
|
||||
fun getEd25519Bytes(): ByteArray = Base64.getDecoder().decode(ed25519)
|
||||
fun getDilithiumBytes(): ByteArray = Base64.getDecoder().decode(dilithium)
|
||||
fun size(): Int = getEd25519Bytes().size + getDilithiumBytes().size
|
||||
|
||||
fun toBytes(): ByteArray {
|
||||
val e = getEd25519Bytes()
|
||||
val d = getDilithiumBytes()
|
||||
return e + d
|
||||
}
|
||||
|
||||
fun toMap(): Map<String, String> = mapOf("ed25519" to ed25519, "dilithium" to dilithium)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FalconSignature(
|
||||
val variant: String,
|
||||
val signature: String
|
||||
) {
|
||||
fun getVariantEnum(): FalconVariant = FalconVariant.fromValue(variant)
|
||||
fun getBytes(): ByteArray = Base64.getDecoder().decode(signature)
|
||||
fun size(): Int = getBytes().size
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SphincsSignature(
|
||||
val variant: String,
|
||||
val signature: String
|
||||
) {
|
||||
fun getVariantEnum(): SphincsVariant = SphincsVariant.fromValue(variant)
|
||||
fun getBytes(): ByteArray = Base64.getDecoder().decode(signature)
|
||||
fun size(): Int = getBytes().size
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class HybridKeypair(
|
||||
@SerialName("public_key") val publicKey: HybridPublicKey,
|
||||
@SerialName("secret_key") val secretKey: SecretKey,
|
||||
val addresses: Map<String, String> = emptyMap()
|
||||
) {
|
||||
fun getAddress(network: Network): String = addresses[network.value] ?: ""
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FalconKeypair(
|
||||
val variant: String,
|
||||
@SerialName("public_key") val publicKey: FalconPublicKey,
|
||||
@SerialName("secret_key") val secretKey: FalconSecretKey
|
||||
) {
|
||||
fun getVariantEnum(): FalconVariant = FalconVariant.fromValue(variant)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SphincsKeypair(
|
||||
val variant: String,
|
||||
@SerialName("public_key") val publicKey: SphincsPublicKey,
|
||||
@SerialName("secret_key") val secretKey: SphincsSecretKey
|
||||
) {
|
||||
fun getVariantEnum(): SphincsVariant = SphincsVariant.fromValue(variant)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class Mnemonic(
|
||||
val phrase: String,
|
||||
val words: List<String>? = null,
|
||||
@SerialName("word_count") val wordCount: Int = 0,
|
||||
val entropy: String? = null
|
||||
) {
|
||||
fun getWords(): List<String> = words ?: phrase.split(" ")
|
||||
fun getWordCount(): Int = if (wordCount > 0) wordCount else getWords().size
|
||||
fun getEntropy(): ByteArray = entropy?.let { Base64.getDecoder().decode(it) } ?: ByteArray(0)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MnemonicValidation(
|
||||
val valid: Boolean,
|
||||
val error: String? = null
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class Address(
|
||||
val address: String,
|
||||
val network: String,
|
||||
@SerialName("pubkey_hash") val pubkeyHash: String? = null
|
||||
) {
|
||||
fun getNetwork(): Network = Network.fromValue(network)
|
||||
fun getPubkeyHashBytes(): ByteArray = pubkeyHash?.let { Base64.getDecoder().decode(it) } ?: ByteArray(0)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
@Serializable
|
||||
data class Hash256(
|
||||
val hash: String
|
||||
) {
|
||||
fun getHex(): String = hash
|
||||
fun getBytes(): ByteArray {
|
||||
val result = ByteArray(hash.length / 2)
|
||||
for (i in result.indices) {
|
||||
result[i] = hash.substring(i * 2, i * 2 + 2).toInt(16).toByte()
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
class CryptoException(
|
||||
message: String,
|
||||
val code: String? = null
|
||||
) : RuntimeException(message)
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
object CryptoConstants {
|
||||
const val ED25519_PUBLIC_KEY_SIZE = 32
|
||||
const val ED25519_SECRET_KEY_SIZE = 32
|
||||
const val ED25519_SIGNATURE_SIZE = 64
|
||||
const val DILITHIUM3_PUBLIC_KEY_SIZE = 1952
|
||||
const val DILITHIUM3_SIGNATURE_SIZE = 3293
|
||||
const val HYBRID_SIGNATURE_SIZE = 64 + 3293
|
||||
const val COIN_TYPE = 0x5359
|
||||
const val MIN_PBKDF2_ITERATIONS = 10000
|
||||
const val MIN_SALT_LENGTH = 8
|
||||
const val DEFAULT_ENDPOINT = "https://crypto.synor.io/v1"
|
||||
}
|
||||
173
sdk/python/src/synor_crypto/__init__.py
Normal file
173
sdk/python/src/synor_crypto/__init__.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
"""
|
||||
Synor Crypto SDK for Python
|
||||
|
||||
Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
|
||||
Features:
|
||||
- Hybrid Ed25519 + Dilithium3 signatures (quantum-resistant)
|
||||
- BIP-39 mnemonic support (12-24 words)
|
||||
- BIP-44 hierarchical key derivation
|
||||
- Post-quantum algorithms: Falcon, SPHINCS+
|
||||
- Secure key derivation (HKDF, PBKDF2)
|
||||
|
||||
Example:
|
||||
```python
|
||||
from synor_crypto import SynorCrypto, CryptoConfig, Network
|
||||
|
||||
async def main():
|
||||
crypto = SynorCrypto(CryptoConfig(api_key='your-api-key'))
|
||||
|
||||
# Generate a mnemonic
|
||||
mnemonic = await crypto.mnemonic.generate(24)
|
||||
print(f'Backup words: {mnemonic.phrase}')
|
||||
|
||||
# Create keypair from mnemonic
|
||||
keypair = await crypto.keypairs.from_mnemonic(mnemonic)
|
||||
address = keypair.address(Network.MAINNET)
|
||||
print(f'Address: {address}')
|
||||
|
||||
# Sign a message
|
||||
message = b'Hello, Synor!'
|
||||
signature = await crypto.signing.sign(keypair, message)
|
||||
|
||||
# Verify signature
|
||||
valid = await crypto.signing.verify(keypair.public_key, message, signature)
|
||||
print(f'Signature valid: {valid}')
|
||||
|
||||
await crypto.close()
|
||||
```
|
||||
"""
|
||||
|
||||
from .types import (
|
||||
# Enumerations
|
||||
Network,
|
||||
FalconVariant,
|
||||
SphincsVariant,
|
||||
PqAlgorithm,
|
||||
AlgorithmFamily,
|
||||
|
||||
# Configuration
|
||||
CryptoConfig,
|
||||
DerivationConfig,
|
||||
PasswordDerivationConfig,
|
||||
DerivationPath,
|
||||
|
||||
# Keys
|
||||
HybridPublicKey,
|
||||
SecretKey,
|
||||
FalconPublicKey,
|
||||
FalconSecretKey,
|
||||
SphincsPublicKey,
|
||||
SphincsSecretKey,
|
||||
|
||||
# Signatures
|
||||
HybridSignature,
|
||||
FalconSignature,
|
||||
SphincsSignature,
|
||||
|
||||
# Keypairs
|
||||
HybridKeypair,
|
||||
FalconKeypair,
|
||||
SphincsKeypair,
|
||||
|
||||
# Mnemonic
|
||||
Mnemonic,
|
||||
MnemonicValidation,
|
||||
|
||||
# Address & Hash
|
||||
Address,
|
||||
Hash256,
|
||||
|
||||
# Negotiation
|
||||
AlgorithmCapabilities,
|
||||
NegotiationPolicy,
|
||||
NegotiationResult,
|
||||
SessionParams,
|
||||
|
||||
# Error
|
||||
CryptoError,
|
||||
|
||||
# Constants
|
||||
CryptoConstants,
|
||||
ALGORITHM_SIZES,
|
||||
)
|
||||
|
||||
from .client import (
|
||||
SynorCrypto,
|
||||
MnemonicClient,
|
||||
KeypairClient,
|
||||
SigningClient,
|
||||
FalconClient,
|
||||
SphincsClient,
|
||||
KdfClient,
|
||||
HashClient,
|
||||
NegotiationClient,
|
||||
)
|
||||
|
||||
__version__ = '0.1.0'
|
||||
__all__ = [
|
||||
# Main client
|
||||
'SynorCrypto',
|
||||
|
||||
# Sub-clients
|
||||
'MnemonicClient',
|
||||
'KeypairClient',
|
||||
'SigningClient',
|
||||
'FalconClient',
|
||||
'SphincsClient',
|
||||
'KdfClient',
|
||||
'HashClient',
|
||||
'NegotiationClient',
|
||||
|
||||
# Enumerations
|
||||
'Network',
|
||||
'FalconVariant',
|
||||
'SphincsVariant',
|
||||
'PqAlgorithm',
|
||||
'AlgorithmFamily',
|
||||
|
||||
# Configuration
|
||||
'CryptoConfig',
|
||||
'DerivationConfig',
|
||||
'PasswordDerivationConfig',
|
||||
'DerivationPath',
|
||||
|
||||
# Keys
|
||||
'HybridPublicKey',
|
||||
'SecretKey',
|
||||
'FalconPublicKey',
|
||||
'FalconSecretKey',
|
||||
'SphincsPublicKey',
|
||||
'SphincsSecretKey',
|
||||
|
||||
# Signatures
|
||||
'HybridSignature',
|
||||
'FalconSignature',
|
||||
'SphincsSignature',
|
||||
|
||||
# Keypairs
|
||||
'HybridKeypair',
|
||||
'FalconKeypair',
|
||||
'SphincsKeypair',
|
||||
|
||||
# Mnemonic
|
||||
'Mnemonic',
|
||||
'MnemonicValidation',
|
||||
|
||||
# Address & Hash
|
||||
'Address',
|
||||
'Hash256',
|
||||
|
||||
# Negotiation
|
||||
'AlgorithmCapabilities',
|
||||
'NegotiationPolicy',
|
||||
'NegotiationResult',
|
||||
'SessionParams',
|
||||
|
||||
# Error
|
||||
'CryptoError',
|
||||
|
||||
# Constants
|
||||
'CryptoConstants',
|
||||
'ALGORITHM_SIZES',
|
||||
]
|
||||
552
sdk/python/src/synor_crypto/client.py
Normal file
552
sdk/python/src/synor_crypto/client.py
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
"""
|
||||
Synor Crypto SDK Client for Python
|
||||
|
||||
Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
"""
|
||||
|
||||
import base64
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
import httpx
|
||||
|
||||
from .types import (
|
||||
CryptoConfig, Network, Mnemonic, MnemonicValidation,
|
||||
HybridKeypair, HybridPublicKey, HybridSignature, SecretKey,
|
||||
FalconVariant, FalconKeypair, FalconSignature,
|
||||
SphincsVariant, SphincsKeypair, SphincsSignature,
|
||||
DerivationPath, DerivationConfig, PasswordDerivationConfig,
|
||||
AlgorithmCapabilities, NegotiationPolicy, NegotiationResult, SessionParams,
|
||||
Hash256, Address, CryptoError
|
||||
)
|
||||
|
||||
|
||||
class MnemonicClient:
|
||||
"""Mnemonic operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def generate(self, word_count: int = 24) -> Mnemonic:
|
||||
"""Generates a new random mnemonic."""
|
||||
result = await self._crypto._post('/mnemonic/generate', {'word_count': word_count})
|
||||
return Mnemonic.from_dict(result)
|
||||
|
||||
async def from_phrase(self, phrase: str) -> Mnemonic:
|
||||
"""Creates a mnemonic from a phrase string."""
|
||||
result = await self._crypto._post('/mnemonic/from-phrase', {'phrase': phrase})
|
||||
return Mnemonic.from_dict(result)
|
||||
|
||||
async def from_entropy(self, entropy: bytes) -> Mnemonic:
|
||||
"""Creates a mnemonic from entropy bytes."""
|
||||
result = await self._crypto._post('/mnemonic/from-entropy', {
|
||||
'entropy': base64.b64encode(entropy).decode()
|
||||
})
|
||||
return Mnemonic.from_dict(result)
|
||||
|
||||
async def validate(self, phrase: str) -> MnemonicValidation:
|
||||
"""Validates a mnemonic phrase."""
|
||||
result = await self._crypto._post('/mnemonic/validate', {'phrase': phrase})
|
||||
return MnemonicValidation.from_dict(result)
|
||||
|
||||
async def to_seed(self, mnemonic: Union[Mnemonic, str], passphrase: str = '') -> bytes:
|
||||
"""Derives a 64-byte seed from a mnemonic."""
|
||||
phrase = mnemonic.phrase if isinstance(mnemonic, Mnemonic) else mnemonic
|
||||
result = await self._crypto._post('/mnemonic/to-seed', {
|
||||
'phrase': phrase,
|
||||
'passphrase': passphrase
|
||||
})
|
||||
return base64.b64decode(result['seed'])
|
||||
|
||||
async def suggest_words(self, partial: str, limit: int = 10) -> List[str]:
|
||||
"""Suggests word completions from the BIP-39 wordlist."""
|
||||
result = await self._crypto._post('/mnemonic/suggest', {
|
||||
'partial': partial,
|
||||
'limit': limit
|
||||
})
|
||||
return result['suggestions']
|
||||
|
||||
|
||||
class KeypairClient:
|
||||
"""Keypair operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def generate(self) -> HybridKeypair:
|
||||
"""Generates a new random hybrid keypair."""
|
||||
result = await self._crypto._post('/keypair/generate')
|
||||
return HybridKeypair.from_dict(result)
|
||||
|
||||
async def from_mnemonic(
|
||||
self,
|
||||
mnemonic: Union[Mnemonic, str],
|
||||
passphrase: str = ''
|
||||
) -> HybridKeypair:
|
||||
"""Creates a keypair from a mnemonic."""
|
||||
phrase = mnemonic.phrase if isinstance(mnemonic, Mnemonic) else mnemonic
|
||||
result = await self._crypto._post('/keypair/from-mnemonic', {
|
||||
'phrase': phrase,
|
||||
'passphrase': passphrase
|
||||
})
|
||||
return HybridKeypair.from_dict(result)
|
||||
|
||||
async def from_seed(self, seed: bytes) -> HybridKeypair:
|
||||
"""Creates a keypair from a 64-byte seed."""
|
||||
result = await self._crypto._post('/keypair/from-seed', {
|
||||
'seed': base64.b64encode(seed).decode()
|
||||
})
|
||||
return HybridKeypair.from_dict(result)
|
||||
|
||||
async def derive(
|
||||
self,
|
||||
parent_keypair: HybridKeypair,
|
||||
path: DerivationPath
|
||||
) -> HybridKeypair:
|
||||
"""Derives a child keypair using BIP-44 path."""
|
||||
result = await self._crypto._post('/keypair/derive', {
|
||||
'public_key': parent_keypair.public_key.to_dict(),
|
||||
'path': {'account': path.account, 'change': path.change, 'index': path.index}
|
||||
})
|
||||
return HybridKeypair.from_dict(result)
|
||||
|
||||
async def get_address(
|
||||
self,
|
||||
public_key: HybridPublicKey,
|
||||
network: Network
|
||||
) -> Address:
|
||||
"""Gets the address for a public key."""
|
||||
result = await self._crypto._post('/keypair/address', {
|
||||
'public_key': public_key.to_dict(),
|
||||
'network': network.value
|
||||
})
|
||||
return Address.from_dict(result)
|
||||
|
||||
|
||||
class SigningClient:
|
||||
"""Signing and verification operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def sign(self, keypair: HybridKeypair, message: bytes) -> HybridSignature:
|
||||
"""Signs a message with a hybrid keypair."""
|
||||
result = await self._crypto._post('/sign/hybrid', {
|
||||
'secret_key': {
|
||||
'ed25519_seed': base64.b64encode(keypair.secret_key.ed25519_seed).decode(),
|
||||
'master_seed': base64.b64encode(keypair.secret_key.master_seed).decode()
|
||||
},
|
||||
'message': base64.b64encode(message).decode()
|
||||
})
|
||||
return HybridSignature.from_dict(result)
|
||||
|
||||
async def verify(
|
||||
self,
|
||||
public_key: HybridPublicKey,
|
||||
message: bytes,
|
||||
signature: HybridSignature
|
||||
) -> bool:
|
||||
"""Verifies a hybrid signature."""
|
||||
result = await self._crypto._post('/sign/verify', {
|
||||
'public_key': public_key.to_dict(),
|
||||
'message': base64.b64encode(message).decode(),
|
||||
'signature': {
|
||||
'ed25519': base64.b64encode(signature.ed25519).decode(),
|
||||
'dilithium': base64.b64encode(signature.dilithium).decode()
|
||||
}
|
||||
})
|
||||
return result['valid']
|
||||
|
||||
async def sign_ed25519(self, secret_key: bytes, message: bytes) -> bytes:
|
||||
"""Signs a message using Ed25519 only."""
|
||||
result = await self._crypto._post('/sign/ed25519', {
|
||||
'secret_key': base64.b64encode(secret_key).decode(),
|
||||
'message': base64.b64encode(message).decode()
|
||||
})
|
||||
return base64.b64decode(result['signature'])
|
||||
|
||||
async def verify_ed25519(
|
||||
self,
|
||||
public_key: bytes,
|
||||
message: bytes,
|
||||
signature: bytes
|
||||
) -> bool:
|
||||
"""Verifies an Ed25519 signature."""
|
||||
result = await self._crypto._post('/sign/verify-ed25519', {
|
||||
'public_key': base64.b64encode(public_key).decode(),
|
||||
'message': base64.b64encode(message).decode(),
|
||||
'signature': base64.b64encode(signature).decode()
|
||||
})
|
||||
return result['valid']
|
||||
|
||||
|
||||
class FalconClient:
|
||||
"""Falcon (FIPS 206) compact signature operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def generate(self, variant: FalconVariant = FalconVariant.FALCON512) -> FalconKeypair:
|
||||
"""Generates a Falcon keypair."""
|
||||
result = await self._crypto._post('/falcon/generate', {'variant': variant.value})
|
||||
return FalconKeypair.from_dict(result)
|
||||
|
||||
async def sign(self, keypair: FalconKeypair, message: bytes) -> FalconSignature:
|
||||
"""Signs a message with Falcon."""
|
||||
result = await self._crypto._post('/falcon/sign', {
|
||||
'variant': keypair.variant.value,
|
||||
'secret_key': base64.b64encode(keypair.secret_key.bytes).decode(),
|
||||
'message': base64.b64encode(message).decode()
|
||||
})
|
||||
return FalconSignature.from_dict(result)
|
||||
|
||||
async def verify(
|
||||
self,
|
||||
public_key: bytes,
|
||||
message: bytes,
|
||||
signature: FalconSignature
|
||||
) -> bool:
|
||||
"""Verifies a Falcon signature."""
|
||||
result = await self._crypto._post('/falcon/verify', {
|
||||
'variant': signature.variant.value,
|
||||
'public_key': base64.b64encode(public_key).decode(),
|
||||
'message': base64.b64encode(message).decode(),
|
||||
'signature': base64.b64encode(signature.bytes).decode()
|
||||
})
|
||||
return result['valid']
|
||||
|
||||
def signature_size(self, variant: FalconVariant) -> int:
|
||||
"""Returns the signature size for a variant."""
|
||||
return 690 if variant == FalconVariant.FALCON512 else 1330
|
||||
|
||||
def public_key_size(self, variant: FalconVariant) -> int:
|
||||
"""Returns the public key size for a variant."""
|
||||
return 897 if variant == FalconVariant.FALCON512 else 1793
|
||||
|
||||
def security_level(self, variant: FalconVariant) -> int:
|
||||
"""Returns the security level in bits."""
|
||||
return 128 if variant == FalconVariant.FALCON512 else 256
|
||||
|
||||
|
||||
class SphincsClient:
|
||||
"""SPHINCS+ (FIPS 205) hash-based signature operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def generate(self, variant: SphincsVariant = SphincsVariant.SHAKE128S) -> SphincsKeypair:
|
||||
"""Generates a SPHINCS+ keypair."""
|
||||
result = await self._crypto._post('/sphincs/generate', {'variant': variant.value})
|
||||
return SphincsKeypair.from_dict(result)
|
||||
|
||||
async def sign(self, keypair: SphincsKeypair, message: bytes) -> SphincsSignature:
|
||||
"""Signs a message with SPHINCS+."""
|
||||
result = await self._crypto._post('/sphincs/sign', {
|
||||
'variant': keypair.variant.value,
|
||||
'secret_key': base64.b64encode(keypair.secret_key.bytes).decode(),
|
||||
'message': base64.b64encode(message).decode()
|
||||
})
|
||||
return SphincsSignature.from_dict(result)
|
||||
|
||||
async def verify(
|
||||
self,
|
||||
public_key: bytes,
|
||||
message: bytes,
|
||||
signature: SphincsSignature
|
||||
) -> bool:
|
||||
"""Verifies a SPHINCS+ signature."""
|
||||
result = await self._crypto._post('/sphincs/verify', {
|
||||
'variant': signature.variant.value,
|
||||
'public_key': base64.b64encode(public_key).decode(),
|
||||
'message': base64.b64encode(message).decode(),
|
||||
'signature': base64.b64encode(signature.bytes).decode()
|
||||
})
|
||||
return result['valid']
|
||||
|
||||
def signature_size(self, variant: SphincsVariant) -> int:
|
||||
"""Returns the signature size for a variant."""
|
||||
sizes = {
|
||||
SphincsVariant.SHAKE128S: 7856,
|
||||
SphincsVariant.SHAKE192S: 16224,
|
||||
SphincsVariant.SHAKE256S: 29792,
|
||||
}
|
||||
return sizes[variant]
|
||||
|
||||
def security_level(self, variant: SphincsVariant) -> int:
|
||||
"""Returns the security level in bits."""
|
||||
levels = {
|
||||
SphincsVariant.SHAKE128S: 128,
|
||||
SphincsVariant.SHAKE192S: 192,
|
||||
SphincsVariant.SHAKE256S: 256,
|
||||
}
|
||||
return levels[variant]
|
||||
|
||||
|
||||
class KdfClient:
|
||||
"""Key derivation operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def derive_key(
|
||||
self,
|
||||
seed: bytes,
|
||||
config: Optional[DerivationConfig] = None
|
||||
) -> bytes:
|
||||
"""Derives a key using HKDF-SHA3-256."""
|
||||
cfg = config or DerivationConfig()
|
||||
body: Dict[str, Any] = {
|
||||
'seed': base64.b64encode(seed).decode(),
|
||||
'output_length': cfg.output_length
|
||||
}
|
||||
if cfg.salt:
|
||||
body['salt'] = base64.b64encode(cfg.salt).decode()
|
||||
if cfg.info:
|
||||
body['info'] = base64.b64encode(cfg.info).decode()
|
||||
|
||||
result = await self._crypto._post('/kdf/hkdf', body)
|
||||
return base64.b64decode(result['key'])
|
||||
|
||||
async def derive_from_password(
|
||||
self,
|
||||
password: Union[str, bytes],
|
||||
config: PasswordDerivationConfig
|
||||
) -> bytes:
|
||||
"""Derives a key from a password using PBKDF2."""
|
||||
password_bytes = password.encode() if isinstance(password, str) else password
|
||||
result = await self._crypto._post('/kdf/pbkdf2', {
|
||||
'password': base64.b64encode(password_bytes).decode(),
|
||||
'salt': base64.b64encode(config.salt).decode(),
|
||||
'iterations': config.iterations,
|
||||
'output_length': config.output_length
|
||||
})
|
||||
return base64.b64decode(result['key'])
|
||||
|
||||
async def derive_ed25519_key(self, master_seed: bytes, account: int) -> bytes:
|
||||
"""Derives an Ed25519 key from a master seed."""
|
||||
result = await self._crypto._post('/kdf/ed25519', {
|
||||
'master_seed': base64.b64encode(master_seed).decode(),
|
||||
'account': account
|
||||
})
|
||||
return base64.b64decode(result['key'])
|
||||
|
||||
async def derive_child_key(
|
||||
self,
|
||||
parent_key: bytes,
|
||||
chain_code: bytes,
|
||||
index: int
|
||||
) -> tuple[bytes, bytes]:
|
||||
"""Derives a child key using BIP-32 style derivation."""
|
||||
result = await self._crypto._post('/kdf/child', {
|
||||
'parent_key': base64.b64encode(parent_key).decode(),
|
||||
'chain_code': base64.b64encode(chain_code).decode(),
|
||||
'index': index
|
||||
})
|
||||
return (
|
||||
base64.b64decode(result['key']),
|
||||
base64.b64decode(result['chain_code'])
|
||||
)
|
||||
|
||||
def create_path(
|
||||
self,
|
||||
account: int,
|
||||
change: int = 0,
|
||||
index: int = 0
|
||||
) -> DerivationPath:
|
||||
"""Creates a BIP-44 derivation path."""
|
||||
return DerivationPath(account=account, change=change, index=index)
|
||||
|
||||
|
||||
class HashClient:
|
||||
"""Hashing operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def sha3_256(self, data: bytes) -> Hash256:
|
||||
"""Computes SHA3-256 hash."""
|
||||
result = await self._crypto._post('/hash/sha3-256', {
|
||||
'data': base64.b64encode(data).decode()
|
||||
})
|
||||
return Hash256.from_dict(result)
|
||||
|
||||
async def blake3(self, data: bytes) -> Hash256:
|
||||
"""Computes BLAKE3 hash."""
|
||||
result = await self._crypto._post('/hash/blake3', {
|
||||
'data': base64.b64encode(data).decode()
|
||||
})
|
||||
return Hash256.from_dict(result)
|
||||
|
||||
async def keccak256(self, data: bytes) -> Hash256:
|
||||
"""Computes Keccak-256 hash."""
|
||||
result = await self._crypto._post('/hash/keccak256', {
|
||||
'data': base64.b64encode(data).decode()
|
||||
})
|
||||
return Hash256.from_dict(result)
|
||||
|
||||
async def combine(self, hashes: List[bytes]) -> Hash256:
|
||||
"""Combines multiple hashes."""
|
||||
result = await self._crypto._post('/hash/combine', {
|
||||
'hashes': [base64.b64encode(h).decode() for h in hashes]
|
||||
})
|
||||
return Hash256.from_dict(result)
|
||||
|
||||
|
||||
class NegotiationClient:
|
||||
"""Algorithm negotiation operations."""
|
||||
|
||||
def __init__(self, crypto: 'SynorCrypto'):
|
||||
self._crypto = crypto
|
||||
|
||||
async def get_capabilities(self) -> AlgorithmCapabilities:
|
||||
"""Gets the local capabilities."""
|
||||
result = await self._crypto._get('/negotiation/capabilities')
|
||||
return AlgorithmCapabilities.from_dict(result)
|
||||
|
||||
async def negotiate(
|
||||
self,
|
||||
peer_capabilities: AlgorithmCapabilities,
|
||||
policy: NegotiationPolicy
|
||||
) -> NegotiationResult:
|
||||
"""Negotiates algorithm selection with a peer."""
|
||||
result = await self._crypto._post('/negotiation/negotiate', {
|
||||
'peer_capabilities': {
|
||||
'pq_algorithms': [a.value for a in peer_capabilities.pq_algorithms],
|
||||
'classical': peer_capabilities.classical,
|
||||
'hybrid': peer_capabilities.hybrid,
|
||||
'preferred': peer_capabilities.preferred.value if peer_capabilities.preferred else None
|
||||
},
|
||||
'policy': {
|
||||
'min_security_level': policy.min_security_level,
|
||||
'prefer_compact': policy.prefer_compact,
|
||||
'allow_classical': policy.allow_classical,
|
||||
'required_families': [f.value for f in policy.required_families]
|
||||
}
|
||||
})
|
||||
return NegotiationResult.from_dict(result)
|
||||
|
||||
async def establish_session(
|
||||
self,
|
||||
result: NegotiationResult,
|
||||
peer_public_key: bytes
|
||||
) -> SessionParams:
|
||||
"""Establishes session parameters after negotiation."""
|
||||
resp = await self._crypto._post('/negotiation/session', {
|
||||
'negotiation_result': {
|
||||
'algorithm': result.algorithm.value,
|
||||
'security_level': result.security_level,
|
||||
'family': result.family.value,
|
||||
'hybrid': result.hybrid
|
||||
},
|
||||
'peer_public_key': base64.b64encode(peer_public_key).decode()
|
||||
})
|
||||
return SessionParams.from_dict(resp)
|
||||
|
||||
|
||||
class SynorCrypto:
|
||||
"""
|
||||
Synor Crypto SDK Client
|
||||
|
||||
Provides quantum-resistant cryptographic operations for the Synor blockchain.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from synor_crypto import SynorCrypto, CryptoConfig
|
||||
|
||||
async def main():
|
||||
crypto = SynorCrypto(CryptoConfig(api_key='your-api-key'))
|
||||
|
||||
# Generate a mnemonic
|
||||
mnemonic = await crypto.mnemonic.generate(24)
|
||||
print(f'Backup words: {mnemonic.phrase}')
|
||||
|
||||
# Create keypair from mnemonic
|
||||
keypair = await crypto.keypairs.from_mnemonic(mnemonic)
|
||||
address = keypair.address(Network.MAINNET)
|
||||
|
||||
# Sign a message
|
||||
signature = await crypto.signing.sign(keypair, b'Hello!')
|
||||
|
||||
await crypto.close()
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, config: CryptoConfig):
|
||||
self._config = config
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=config.endpoint,
|
||||
timeout=config.timeout / 1000,
|
||||
headers={
|
||||
'Authorization': f'Bearer {config.api_key}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'python/0.1.0'
|
||||
}
|
||||
)
|
||||
self._closed = False
|
||||
|
||||
self.mnemonic = MnemonicClient(self)
|
||||
self.keypairs = KeypairClient(self)
|
||||
self.signing = SigningClient(self)
|
||||
self.falcon = FalconClient(self)
|
||||
self.sphincs = SphincsClient(self)
|
||||
self.kdf = KdfClient(self)
|
||||
self.hash = HashClient(self)
|
||||
self.negotiation = NegotiationClient(self)
|
||||
|
||||
@property
|
||||
def default_network(self) -> Network:
|
||||
"""Returns the default network."""
|
||||
return self._config.default_network
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Checks if the service is healthy."""
|
||||
try:
|
||||
result = await self._get('/health')
|
||||
return result.get('status') == 'healthy'
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def get_info(self) -> Dict[str, Any]:
|
||||
"""Gets service information."""
|
||||
return await self._get('/info')
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Closes the client."""
|
||||
self._closed = True
|
||||
await self._client.aclose()
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""Returns True if the client is closed."""
|
||||
return self._closed
|
||||
|
||||
async def __aenter__(self) -> 'SynorCrypto':
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
await self.close()
|
||||
|
||||
async def _get(self, path: str) -> Dict[str, Any]:
|
||||
"""Makes a GET request."""
|
||||
self._check_closed()
|
||||
response = await self._client.get(path)
|
||||
return self._handle_response(response)
|
||||
|
||||
async def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""Makes a POST request."""
|
||||
self._check_closed()
|
||||
response = await self._client.post(path, json=body or {})
|
||||
return self._handle_response(response)
|
||||
|
||||
def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
|
||||
"""Handles the HTTP response."""
|
||||
data = response.json()
|
||||
if not response.is_success:
|
||||
raise CryptoError(
|
||||
data.get('message', f'HTTP {response.status_code}'),
|
||||
data.get('code')
|
||||
)
|
||||
return data
|
||||
|
||||
def _check_closed(self) -> None:
|
||||
"""Checks if the client is closed."""
|
||||
if self._closed:
|
||||
raise CryptoError('Client has been closed', 'CLIENT_CLOSED')
|
||||
526
sdk/python/src/synor_crypto/types.py
Normal file
526
sdk/python/src/synor_crypto/types.py
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
"""
|
||||
Synor Crypto SDK Types
|
||||
|
||||
Quantum-resistant cryptographic types for the Synor blockchain.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Dict, Callable
|
||||
import base64
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Enumerations
|
||||
# ============================================================================
|
||||
|
||||
class Network(str, Enum):
|
||||
"""Network type for address generation."""
|
||||
MAINNET = "mainnet"
|
||||
TESTNET = "testnet"
|
||||
DEVNET = "devnet"
|
||||
|
||||
|
||||
class FalconVariant(str, Enum):
|
||||
"""Falcon variant selection."""
|
||||
FALCON512 = "falcon512" # 128-bit security, ~690 byte signatures
|
||||
FALCON1024 = "falcon1024" # 256-bit security, ~1330 byte signatures
|
||||
|
||||
|
||||
class SphincsVariant(str, Enum):
|
||||
"""SPHINCS+ variant selection."""
|
||||
SHAKE128S = "shake128s" # 128-bit security, ~7.8KB signatures
|
||||
SHAKE192S = "shake192s" # 192-bit security, ~16KB signatures
|
||||
SHAKE256S = "shake256s" # 256-bit security, ~30KB signatures
|
||||
|
||||
|
||||
class PqAlgorithm(str, Enum):
|
||||
"""Post-quantum algorithm selection."""
|
||||
DILITHIUM3 = "dilithium3"
|
||||
FALCON512 = "falcon512"
|
||||
FALCON1024 = "falcon1024"
|
||||
SPHINCS128S = "sphincs128s"
|
||||
SPHINCS192S = "sphincs192s"
|
||||
SPHINCS256S = "sphincs256s"
|
||||
|
||||
|
||||
class AlgorithmFamily(str, Enum):
|
||||
"""Algorithm family classification."""
|
||||
CLASSICAL = "classical"
|
||||
LATTICE = "lattice"
|
||||
HASH_BASED = "hash_based"
|
||||
HYBRID = "hybrid"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class CryptoConfig:
|
||||
"""Crypto SDK configuration."""
|
||||
api_key: str
|
||||
endpoint: str = "https://crypto.synor.io/v1"
|
||||
timeout: int = 30000
|
||||
retries: int = 3
|
||||
debug: bool = False
|
||||
default_network: Network = Network.MAINNET
|
||||
|
||||
|
||||
@dataclass
|
||||
class DerivationConfig:
|
||||
"""Key derivation configuration."""
|
||||
salt: Optional[bytes] = None
|
||||
info: Optional[bytes] = None
|
||||
output_length: int = 32
|
||||
|
||||
|
||||
@dataclass
|
||||
class PasswordDerivationConfig:
|
||||
"""Password derivation configuration."""
|
||||
salt: bytes = field(default_factory=bytes)
|
||||
iterations: int = 100000
|
||||
output_length: int = 32
|
||||
|
||||
|
||||
@dataclass
|
||||
class DerivationPath:
|
||||
"""BIP-44 derivation path."""
|
||||
account: int = 0
|
||||
change: int = 0
|
||||
index: int = 0
|
||||
|
||||
COIN_TYPE: int = 0x5359 # Synor coin type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"m/44'/{self.COIN_TYPE}'/{self.account}'/{self.change}/{self.index}"
|
||||
|
||||
@classmethod
|
||||
def external(cls, account: int, index: int) -> 'DerivationPath':
|
||||
"""Creates a path for external addresses."""
|
||||
return cls(account=account, change=0, index=index)
|
||||
|
||||
@classmethod
|
||||
def internal(cls, account: int, index: int) -> 'DerivationPath':
|
||||
"""Creates a path for internal (change) addresses."""
|
||||
return cls(account=account, change=1, index=index)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Key Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class HybridPublicKey:
|
||||
"""Hybrid public key (Ed25519 + Dilithium3)."""
|
||||
ed25519: bytes # 32 bytes
|
||||
dilithium: bytes # ~1952 bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'HybridPublicKey':
|
||||
return cls(
|
||||
ed25519=base64.b64decode(data['ed25519']),
|
||||
dilithium=base64.b64decode(data['dilithium'])
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
'ed25519': base64.b64encode(self.ed25519).decode(),
|
||||
'dilithium': base64.b64encode(self.dilithium).decode()
|
||||
}
|
||||
|
||||
def size(self) -> int:
|
||||
"""Returns the total size in bytes."""
|
||||
return len(self.ed25519) + len(self.dilithium)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SecretKey:
|
||||
"""Secret key (master seed)."""
|
||||
ed25519_seed: bytes # 32 bytes
|
||||
master_seed: bytes # 64 bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SecretKey':
|
||||
return cls(
|
||||
ed25519_seed=base64.b64decode(data['ed25519_seed']),
|
||||
master_seed=base64.b64decode(data['master_seed'])
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FalconPublicKey:
|
||||
"""Falcon public key."""
|
||||
variant: FalconVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'FalconPublicKey':
|
||||
return cls(
|
||||
variant=FalconVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['bytes'])
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FalconSecretKey:
|
||||
"""Falcon secret key."""
|
||||
variant: FalconVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'FalconSecretKey':
|
||||
return cls(
|
||||
variant=FalconVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['bytes'])
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SphincsPublicKey:
|
||||
"""SPHINCS+ public key."""
|
||||
variant: SphincsVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SphincsPublicKey':
|
||||
return cls(
|
||||
variant=SphincsVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['bytes'])
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SphincsSecretKey:
|
||||
"""SPHINCS+ secret key."""
|
||||
variant: SphincsVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SphincsSecretKey':
|
||||
return cls(
|
||||
variant=SphincsVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['bytes'])
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Signature Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class HybridSignature:
|
||||
"""Hybrid signature (Ed25519 + Dilithium3)."""
|
||||
ed25519: bytes # 64 bytes
|
||||
dilithium: bytes # ~3293 bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'HybridSignature':
|
||||
return cls(
|
||||
ed25519=base64.b64decode(data['ed25519']),
|
||||
dilithium=base64.b64decode(data['dilithium'])
|
||||
)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""Serializes the signature to bytes."""
|
||||
return self.ed25519 + self.dilithium
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'HybridSignature':
|
||||
"""Deserializes a signature from bytes."""
|
||||
if len(data) < 64:
|
||||
raise ValueError("Invalid signature length")
|
||||
return cls(
|
||||
ed25519=data[:64],
|
||||
dilithium=data[64:]
|
||||
)
|
||||
|
||||
def size(self) -> int:
|
||||
"""Returns the total size in bytes."""
|
||||
return len(self.ed25519) + len(self.dilithium)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FalconSignature:
|
||||
"""Falcon signature."""
|
||||
variant: FalconVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'FalconSignature':
|
||||
return cls(
|
||||
variant=FalconVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['signature'])
|
||||
)
|
||||
|
||||
def size(self) -> int:
|
||||
return len(self.bytes)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SphincsSignature:
|
||||
"""SPHINCS+ signature."""
|
||||
variant: SphincsVariant
|
||||
bytes: bytes
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SphincsSignature':
|
||||
return cls(
|
||||
variant=SphincsVariant(data['variant']),
|
||||
bytes=base64.b64decode(data['signature'])
|
||||
)
|
||||
|
||||
def size(self) -> int:
|
||||
return len(self.bytes)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Keypair Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class HybridKeypair:
|
||||
"""Hybrid keypair (Ed25519 + Dilithium3)."""
|
||||
public_key: HybridPublicKey
|
||||
secret_key: SecretKey
|
||||
_addresses: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def address(self, network: Network) -> str:
|
||||
"""Returns the address for the given network."""
|
||||
return self._addresses.get(network.value, "")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'HybridKeypair':
|
||||
return cls(
|
||||
public_key=HybridPublicKey.from_dict(data['public_key']),
|
||||
secret_key=SecretKey.from_dict(data['secret_key']),
|
||||
_addresses=data.get('addresses', {})
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FalconKeypair:
|
||||
"""Falcon keypair."""
|
||||
variant: FalconVariant
|
||||
public_key: FalconPublicKey
|
||||
secret_key: FalconSecretKey
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'FalconKeypair':
|
||||
return cls(
|
||||
variant=FalconVariant(data['variant']),
|
||||
public_key=FalconPublicKey.from_dict(data['public_key']),
|
||||
secret_key=FalconSecretKey.from_dict(data['secret_key'])
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SphincsKeypair:
|
||||
"""SPHINCS+ keypair."""
|
||||
variant: SphincsVariant
|
||||
public_key: SphincsPublicKey
|
||||
secret_key: SphincsSecretKey
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SphincsKeypair':
|
||||
return cls(
|
||||
variant=SphincsVariant(data['variant']),
|
||||
public_key=SphincsPublicKey.from_dict(data['public_key']),
|
||||
secret_key=SphincsSecretKey.from_dict(data['secret_key'])
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Mnemonic Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class Mnemonic:
|
||||
"""BIP-39 mnemonic phrase."""
|
||||
phrase: str
|
||||
words: List[str] = field(default_factory=list)
|
||||
word_count: int = 24
|
||||
entropy: bytes = field(default_factory=bytes)
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.words:
|
||||
self.words = self.phrase.split()
|
||||
if not self.word_count:
|
||||
self.word_count = len(self.words)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'Mnemonic':
|
||||
return cls(
|
||||
phrase=data['phrase'],
|
||||
words=data.get('words', data['phrase'].split()),
|
||||
word_count=data.get('word_count', len(data['phrase'].split())),
|
||||
entropy=base64.b64decode(data.get('entropy', '')) if data.get('entropy') else b''
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MnemonicValidation:
|
||||
"""Mnemonic validation result."""
|
||||
valid: bool
|
||||
error: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'MnemonicValidation':
|
||||
return cls(
|
||||
valid=data['valid'],
|
||||
error=data.get('error')
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Address Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class Address:
|
||||
"""Blockchain address."""
|
||||
address: str
|
||||
network: Network
|
||||
pubkey_hash: bytes = field(default_factory=bytes)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'Address':
|
||||
return cls(
|
||||
address=data['address'],
|
||||
network=Network(data['network']),
|
||||
pubkey_hash=base64.b64decode(data.get('pubkey_hash', '')) if data.get('pubkey_hash') else b''
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Hash Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class Hash256:
|
||||
"""256-bit hash."""
|
||||
bytes: bytes
|
||||
hex: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'Hash256':
|
||||
hex_str = data['hash']
|
||||
return cls(
|
||||
bytes=bytes.fromhex(hex_str),
|
||||
hex=hex_str
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Negotiation Types
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class AlgorithmCapabilities:
|
||||
"""Algorithm capabilities for negotiation."""
|
||||
pq_algorithms: List[PqAlgorithm]
|
||||
classical: bool = True
|
||||
hybrid: bool = True
|
||||
preferred: Optional[PqAlgorithm] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'AlgorithmCapabilities':
|
||||
return cls(
|
||||
pq_algorithms=[PqAlgorithm(a) for a in data.get('pq_algorithms', [])],
|
||||
classical=data.get('classical', True),
|
||||
hybrid=data.get('hybrid', True),
|
||||
preferred=PqAlgorithm(data['preferred']) if data.get('preferred') else None
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NegotiationPolicy:
|
||||
"""Negotiation policy."""
|
||||
min_security_level: int = 128
|
||||
prefer_compact: bool = False
|
||||
allow_classical: bool = False
|
||||
required_families: List[AlgorithmFamily] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NegotiationResult:
|
||||
"""Negotiation result."""
|
||||
algorithm: PqAlgorithm
|
||||
security_level: int
|
||||
family: AlgorithmFamily
|
||||
hybrid: bool
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'NegotiationResult':
|
||||
return cls(
|
||||
algorithm=PqAlgorithm(data['algorithm']),
|
||||
security_level=data['security_level'],
|
||||
family=AlgorithmFamily(data['family']),
|
||||
hybrid=data['hybrid']
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionParams:
|
||||
"""Session parameters after negotiation."""
|
||||
algorithm: PqAlgorithm
|
||||
session_key: Optional[bytes] = None
|
||||
expires_at: Optional[int] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'SessionParams':
|
||||
return cls(
|
||||
algorithm=PqAlgorithm(data['algorithm']),
|
||||
session_key=base64.b64decode(data['session_key']) if data.get('session_key') else None,
|
||||
expires_at=data.get('expires_at')
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Error Types
|
||||
# ============================================================================
|
||||
|
||||
class CryptoError(Exception):
|
||||
"""Crypto operation error."""
|
||||
|
||||
def __init__(self, message: str, code: Optional[str] = None):
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Constants
|
||||
# ============================================================================
|
||||
|
||||
class CryptoConstants:
|
||||
"""Crypto constants."""
|
||||
ED25519_PUBLIC_KEY_SIZE = 32
|
||||
ED25519_SECRET_KEY_SIZE = 32
|
||||
ED25519_SIGNATURE_SIZE = 64
|
||||
DILITHIUM3_PUBLIC_KEY_SIZE = 1952
|
||||
DILITHIUM3_SIGNATURE_SIZE = 3293
|
||||
HYBRID_SIGNATURE_SIZE = 64 + 3293
|
||||
FALCON512_SIGNATURE_SIZE = 690
|
||||
FALCON512_PUBLIC_KEY_SIZE = 897
|
||||
FALCON1024_SIGNATURE_SIZE = 1330
|
||||
FALCON1024_PUBLIC_KEY_SIZE = 1793
|
||||
SPHINCS128S_SIGNATURE_SIZE = 7856
|
||||
SPHINCS192S_SIGNATURE_SIZE = 16224
|
||||
SPHINCS256S_SIGNATURE_SIZE = 29792
|
||||
COIN_TYPE = 0x5359
|
||||
MIN_PBKDF2_ITERATIONS = 10000
|
||||
MIN_SALT_LENGTH = 8
|
||||
DEFAULT_ENDPOINT = "https://crypto.synor.io/v1"
|
||||
|
||||
|
||||
# Algorithm size comparison
|
||||
ALGORITHM_SIZES = {
|
||||
'ed25519': {'signature': 64, 'public_key': 32},
|
||||
'dilithium3': {'signature': 3293, 'public_key': 1952},
|
||||
'hybrid': {'signature': 3357, 'public_key': 1984},
|
||||
'falcon512': {'signature': 690, 'public_key': 897},
|
||||
'falcon1024': {'signature': 1330, 'public_key': 1793},
|
||||
'sphincs128s': {'signature': 7856, 'public_key': 32},
|
||||
'sphincs192s': {'signature': 16224, 'public_key': 48},
|
||||
'sphincs256s': {'signature': 29792, 'public_key': 64},
|
||||
}
|
||||
13
sdk/ruby/lib/synor/crypto.rb
Normal file
13
sdk/ruby/lib/synor/crypto.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'crypto/types'
|
||||
require_relative 'crypto/client'
|
||||
|
||||
module Synor
|
||||
# Synor Crypto SDK for Ruby
|
||||
#
|
||||
# Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
module Crypto
|
||||
VERSION = '0.1.0'
|
||||
end
|
||||
end
|
||||
334
sdk/ruby/lib/synor/crypto/client.rb
Normal file
334
sdk/ruby/lib/synor/crypto/client.rb
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'net/http'
|
||||
require 'json'
|
||||
require 'uri'
|
||||
require 'base64'
|
||||
|
||||
require_relative 'types'
|
||||
|
||||
module Synor
|
||||
module Crypto
|
||||
# Synor Crypto SDK for Ruby
|
||||
#
|
||||
# Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
#
|
||||
# @example
|
||||
# config = Synor::Crypto::CryptoConfig.new(api_key: 'your-api-key')
|
||||
# crypto = Synor::Crypto::SynorCrypto.new(config)
|
||||
#
|
||||
# # Generate a mnemonic
|
||||
# mnemonic = crypto.mnemonic.generate(24)
|
||||
# puts "Backup words: #{mnemonic.phrase}"
|
||||
#
|
||||
# # Create keypair from mnemonic
|
||||
# keypair = crypto.keypairs.from_mnemonic(mnemonic.phrase, '')
|
||||
# address = keypair.get_address(Synor::Crypto::Network::MAINNET)
|
||||
#
|
||||
# # Sign a message
|
||||
# signature = crypto.signing.sign(keypair, 'Hello!')
|
||||
#
|
||||
class SynorCrypto
|
||||
attr_reader :config, :mnemonic, :keypairs, :signing, :falcon, :sphincs, :kdf, :hash
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
@closed = false
|
||||
|
||||
@mnemonic = MnemonicClient.new(self)
|
||||
@keypairs = KeypairClient.new(self)
|
||||
@signing = SigningClient.new(self)
|
||||
@falcon = FalconClient.new(self)
|
||||
@sphincs = SphincsClient.new(self)
|
||||
@kdf = KdfClient.new(self)
|
||||
@hash = HashClient.new(self)
|
||||
end
|
||||
|
||||
def default_network
|
||||
config.default_network
|
||||
end
|
||||
|
||||
def health_check
|
||||
result = get('/health')
|
||||
result['status'] == 'healthy'
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
def get_info
|
||||
get('/info')
|
||||
end
|
||||
|
||||
def close
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
# @api private
|
||||
def get(path)
|
||||
check_closed!
|
||||
request(:get, path)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def post(path, body = {})
|
||||
check_closed!
|
||||
request(:post, path, body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, path, body = nil)
|
||||
uri = URI.parse("#{config.endpoint}#{path}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = uri.scheme == 'https'
|
||||
http.open_timeout = config.timeout / 1000.0
|
||||
http.read_timeout = config.timeout / 1000.0
|
||||
|
||||
request = case method
|
||||
when :get
|
||||
Net::HTTP::Get.new(uri.request_uri)
|
||||
when :post
|
||||
req = Net::HTTP::Post.new(uri.request_uri)
|
||||
req.body = JSON.generate(body || {})
|
||||
req
|
||||
end
|
||||
|
||||
request['Authorization'] = "Bearer #{config.api_key}"
|
||||
request['Content-Type'] = 'application/json'
|
||||
request['X-SDK-Version'] = 'ruby/0.1.0'
|
||||
|
||||
response = http.request(request)
|
||||
handle_response(response)
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
body = JSON.parse(response.body)
|
||||
|
||||
unless response.is_a?(Net::HTTPSuccess)
|
||||
message = body['message'] || "HTTP #{response.code}"
|
||||
code = body['code']
|
||||
raise CryptoError.new(message, code: code, http_status: response.code.to_i)
|
||||
end
|
||||
|
||||
body
|
||||
end
|
||||
|
||||
def check_closed!
|
||||
raise CryptoError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed
|
||||
end
|
||||
end
|
||||
|
||||
# Mnemonic sub-client
|
||||
class MnemonicClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def generate(word_count = 24)
|
||||
result = @crypto.post('/mnemonic/generate', { word_count: word_count })
|
||||
Mnemonic.from_hash(result)
|
||||
end
|
||||
|
||||
def from_phrase(phrase)
|
||||
result = @crypto.post('/mnemonic/from-phrase', { phrase: phrase })
|
||||
Mnemonic.from_hash(result)
|
||||
end
|
||||
|
||||
def validate(phrase)
|
||||
result = @crypto.post('/mnemonic/validate', { phrase: phrase })
|
||||
MnemonicValidation.from_hash(result)
|
||||
end
|
||||
|
||||
def to_seed(phrase, passphrase = '')
|
||||
result = @crypto.post('/mnemonic/to-seed', { phrase: phrase, passphrase: passphrase })
|
||||
Base64.strict_decode64(result['seed'])
|
||||
end
|
||||
|
||||
def suggest_words(partial, limit = 5)
|
||||
result = @crypto.post('/mnemonic/suggest', { partial: partial, limit: limit })
|
||||
result['suggestions'] || []
|
||||
end
|
||||
end
|
||||
|
||||
# Keypair sub-client
|
||||
class KeypairClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def generate
|
||||
result = @crypto.post('/keypair/generate')
|
||||
HybridKeypair.from_hash(result)
|
||||
end
|
||||
|
||||
def from_mnemonic(phrase, passphrase = '')
|
||||
result = @crypto.post('/keypair/from-mnemonic', { phrase: phrase, passphrase: passphrase })
|
||||
HybridKeypair.from_hash(result)
|
||||
end
|
||||
|
||||
def from_seed(seed)
|
||||
result = @crypto.post('/keypair/from-seed', { seed: Base64.strict_encode64(seed) })
|
||||
HybridKeypair.from_hash(result)
|
||||
end
|
||||
|
||||
def get_address(public_key, network)
|
||||
result = @crypto.post('/keypair/address', {
|
||||
public_key: public_key.to_h,
|
||||
network: network
|
||||
})
|
||||
Address.from_hash(result)
|
||||
end
|
||||
end
|
||||
|
||||
# Signing sub-client
|
||||
class SigningClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def sign(keypair, message)
|
||||
message_bytes = message.is_a?(String) ? message.encode('UTF-8').bytes.pack('C*') : message
|
||||
result = @crypto.post('/sign/hybrid', {
|
||||
secret_key: keypair.secret_key.to_h,
|
||||
message: Base64.strict_encode64(message_bytes)
|
||||
})
|
||||
HybridSignature.from_hash(result)
|
||||
end
|
||||
|
||||
def verify(public_key, message, signature)
|
||||
message_bytes = message.is_a?(String) ? message.encode('UTF-8').bytes.pack('C*') : message
|
||||
result = @crypto.post('/sign/verify', {
|
||||
public_key: public_key.to_h,
|
||||
message: Base64.strict_encode64(message_bytes),
|
||||
signature: signature.to_h
|
||||
})
|
||||
result['valid'] == true
|
||||
end
|
||||
|
||||
def sign_ed25519(secret_key, message)
|
||||
result = @crypto.post('/sign/ed25519', {
|
||||
secret_key: Base64.strict_encode64(secret_key),
|
||||
message: Base64.strict_encode64(message)
|
||||
})
|
||||
Base64.strict_decode64(result['signature'])
|
||||
end
|
||||
end
|
||||
|
||||
# Falcon sub-client
|
||||
class FalconClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def generate(variant = FalconVariant::FALCON512)
|
||||
result = @crypto.post('/falcon/generate', { variant: variant })
|
||||
FalconKeypair.from_hash(result)
|
||||
end
|
||||
|
||||
def sign(keypair, message)
|
||||
result = @crypto.post('/falcon/sign', {
|
||||
variant: keypair.variant,
|
||||
secret_key: Base64.strict_encode64(keypair.secret_key.key_bytes),
|
||||
message: Base64.strict_encode64(message)
|
||||
})
|
||||
FalconSignature.from_hash(result)
|
||||
end
|
||||
|
||||
def verify(public_key, message, signature)
|
||||
result = @crypto.post('/falcon/verify', {
|
||||
variant: signature.variant,
|
||||
public_key: Base64.strict_encode64(public_key),
|
||||
message: Base64.strict_encode64(message),
|
||||
signature: Base64.strict_encode64(signature.signature_bytes)
|
||||
})
|
||||
result['valid'] == true
|
||||
end
|
||||
end
|
||||
|
||||
# SPHINCS+ sub-client
|
||||
class SphincsClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def generate(variant = SphincsVariant::SHAKE128S)
|
||||
result = @crypto.post('/sphincs/generate', { variant: variant })
|
||||
SphincsKeypair.from_hash(result)
|
||||
end
|
||||
|
||||
def sign(keypair, message)
|
||||
result = @crypto.post('/sphincs/sign', {
|
||||
variant: keypair.variant,
|
||||
secret_key: Base64.strict_encode64(keypair.secret_key.key_bytes),
|
||||
message: Base64.strict_encode64(message)
|
||||
})
|
||||
SphincsSignature.from_hash(result)
|
||||
end
|
||||
|
||||
def verify(public_key, message, signature)
|
||||
result = @crypto.post('/sphincs/verify', {
|
||||
variant: signature.variant,
|
||||
public_key: Base64.strict_encode64(public_key),
|
||||
message: Base64.strict_encode64(message),
|
||||
signature: Base64.strict_encode64(signature.signature_bytes)
|
||||
})
|
||||
result['valid'] == true
|
||||
end
|
||||
end
|
||||
|
||||
# KDF sub-client
|
||||
class KdfClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def derive_key(seed, config = DerivationConfig.new)
|
||||
body = {
|
||||
seed: Base64.strict_encode64(seed),
|
||||
output_length: config.output_length
|
||||
}
|
||||
body[:salt] = Base64.strict_encode64(config.salt) if config.salt
|
||||
body[:info] = Base64.strict_encode64(config.info) if config.info
|
||||
|
||||
result = @crypto.post('/kdf/hkdf', body)
|
||||
Base64.strict_decode64(result['key'])
|
||||
end
|
||||
|
||||
def derive_from_password(password, config)
|
||||
result = @crypto.post('/kdf/pbkdf2', {
|
||||
password: Base64.strict_encode64(password),
|
||||
salt: Base64.strict_encode64(config.salt),
|
||||
iterations: config.iterations,
|
||||
output_length: config.output_length
|
||||
})
|
||||
Base64.strict_decode64(result['key'])
|
||||
end
|
||||
end
|
||||
|
||||
# Hash sub-client
|
||||
class HashClient
|
||||
def initialize(crypto)
|
||||
@crypto = crypto
|
||||
end
|
||||
|
||||
def sha3_256(data)
|
||||
result = @crypto.post('/hash/sha3-256', { data: Base64.strict_encode64(data) })
|
||||
Hash256.from_hash(result)
|
||||
end
|
||||
|
||||
def blake3(data)
|
||||
result = @crypto.post('/hash/blake3', { data: Base64.strict_encode64(data) })
|
||||
Hash256.from_hash(result)
|
||||
end
|
||||
|
||||
def keccak256(data)
|
||||
result = @crypto.post('/hash/keccak256', { data: Base64.strict_encode64(data) })
|
||||
Hash256.from_hash(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
576
sdk/ruby/lib/synor/crypto/types.rb
Normal file
576
sdk/ruby/lib/synor/crypto/types.rb
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'base64'
|
||||
|
||||
module Synor
|
||||
module Crypto
|
||||
# ============================================================================
|
||||
# Constants
|
||||
# ============================================================================
|
||||
|
||||
ED25519_PUBLIC_KEY_SIZE = 32
|
||||
ED25519_SECRET_KEY_SIZE = 32
|
||||
ED25519_SIGNATURE_SIZE = 64
|
||||
DILITHIUM3_PUBLIC_KEY_SIZE = 1952
|
||||
DILITHIUM3_SIGNATURE_SIZE = 3293
|
||||
HYBRID_SIGNATURE_SIZE = 64 + 3293
|
||||
COIN_TYPE = 0x5359
|
||||
MIN_PBKDF2_ITERATIONS = 10_000
|
||||
MIN_SALT_LENGTH = 8
|
||||
DEFAULT_ENDPOINT = 'https://crypto.synor.io/v1'
|
||||
|
||||
# ============================================================================
|
||||
# Enumerations
|
||||
# ============================================================================
|
||||
|
||||
module Network
|
||||
MAINNET = 'mainnet'
|
||||
TESTNET = 'testnet'
|
||||
DEVNET = 'devnet'
|
||||
|
||||
def self.all
|
||||
[MAINNET, TESTNET, DEVNET]
|
||||
end
|
||||
end
|
||||
|
||||
module FalconVariant
|
||||
FALCON512 = 'falcon512'
|
||||
FALCON1024 = 'falcon1024'
|
||||
|
||||
SIGNATURE_SIZES = {
|
||||
FALCON512 => 690,
|
||||
FALCON1024 => 1330
|
||||
}.freeze
|
||||
|
||||
PUBLIC_KEY_SIZES = {
|
||||
FALCON512 => 897,
|
||||
FALCON1024 => 1793
|
||||
}.freeze
|
||||
|
||||
SECURITY_LEVELS = {
|
||||
FALCON512 => 128,
|
||||
FALCON1024 => 256
|
||||
}.freeze
|
||||
|
||||
def self.signature_size(variant)
|
||||
SIGNATURE_SIZES[variant] || 690
|
||||
end
|
||||
|
||||
def self.public_key_size(variant)
|
||||
PUBLIC_KEY_SIZES[variant] || 897
|
||||
end
|
||||
|
||||
def self.security_level(variant)
|
||||
SECURITY_LEVELS[variant] || 128
|
||||
end
|
||||
end
|
||||
|
||||
module SphincsVariant
|
||||
SHAKE128S = 'shake128s'
|
||||
SHAKE192S = 'shake192s'
|
||||
SHAKE256S = 'shake256s'
|
||||
|
||||
SIGNATURE_SIZES = {
|
||||
SHAKE128S => 7856,
|
||||
SHAKE192S => 16_224,
|
||||
SHAKE256S => 29_792
|
||||
}.freeze
|
||||
|
||||
SECURITY_LEVELS = {
|
||||
SHAKE128S => 128,
|
||||
SHAKE192S => 192,
|
||||
SHAKE256S => 256
|
||||
}.freeze
|
||||
|
||||
def self.signature_size(variant)
|
||||
SIGNATURE_SIZES[variant] || 7856
|
||||
end
|
||||
|
||||
def self.security_level(variant)
|
||||
SECURITY_LEVELS[variant] || 128
|
||||
end
|
||||
end
|
||||
|
||||
module PqAlgorithm
|
||||
DILITHIUM3 = 'dilithium3'
|
||||
FALCON512 = 'falcon512'
|
||||
FALCON1024 = 'falcon1024'
|
||||
SPHINCS128S = 'sphincs128s'
|
||||
SPHINCS192S = 'sphincs192s'
|
||||
SPHINCS256S = 'sphincs256s'
|
||||
end
|
||||
|
||||
module AlgorithmFamily
|
||||
CLASSICAL = 'classical'
|
||||
LATTICE = 'lattice'
|
||||
HASH_BASED = 'hash_based'
|
||||
HYBRID = 'hybrid'
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Types
|
||||
# ============================================================================
|
||||
|
||||
# Configuration for the Crypto SDK.
|
||||
class CryptoConfig
|
||||
attr_accessor :api_key, :endpoint, :timeout, :retries, :debug, :default_network
|
||||
|
||||
def initialize(api_key:, endpoint: DEFAULT_ENDPOINT, timeout: 30_000, retries: 3, debug: false, default_network: Network::MAINNET)
|
||||
@api_key = api_key
|
||||
@endpoint = endpoint
|
||||
@timeout = timeout
|
||||
@retries = retries
|
||||
@debug = debug
|
||||
@default_network = default_network
|
||||
end
|
||||
end
|
||||
|
||||
# Configuration for key derivation.
|
||||
class DerivationConfig
|
||||
attr_accessor :salt, :info, :output_length
|
||||
|
||||
def initialize(salt: nil, info: nil, output_length: 32)
|
||||
@salt = salt
|
||||
@info = info
|
||||
@output_length = output_length
|
||||
end
|
||||
end
|
||||
|
||||
# Configuration for password-based key derivation.
|
||||
class PasswordDerivationConfig
|
||||
attr_accessor :salt, :iterations, :output_length
|
||||
|
||||
def initialize(salt:, iterations: 100_000, output_length: 32)
|
||||
@salt = salt
|
||||
@iterations = iterations
|
||||
@output_length = output_length
|
||||
end
|
||||
end
|
||||
|
||||
# BIP-44 derivation path.
|
||||
class DerivationPath
|
||||
COIN_TYPE = 0x5359
|
||||
|
||||
attr_reader :account, :change, :index
|
||||
|
||||
def initialize(account:, change:, index:)
|
||||
@account = account
|
||||
@change = change
|
||||
@index = index
|
||||
end
|
||||
|
||||
def self.external(account:, index:)
|
||||
new(account: account, change: 0, index: index)
|
||||
end
|
||||
|
||||
def self.internal(account:, index:)
|
||||
new(account: account, change: 1, index: index)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"m/44'/#{COIN_TYPE}'/#{account}'/#{change}/#{index}"
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ account: account, change: change, index: index }
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Key Types
|
||||
# ============================================================================
|
||||
|
||||
# Hybrid public key combining Ed25519 and Dilithium3.
|
||||
class HybridPublicKey
|
||||
attr_accessor :ed25519, :dilithium
|
||||
|
||||
def initialize(ed25519: nil, dilithium: nil)
|
||||
@ed25519 = ed25519
|
||||
@dilithium = dilithium
|
||||
end
|
||||
|
||||
def ed25519_bytes
|
||||
Base64.strict_decode64(@ed25519 || '')
|
||||
end
|
||||
|
||||
def dilithium_bytes
|
||||
Base64.strict_decode64(@dilithium || '')
|
||||
end
|
||||
|
||||
def size
|
||||
ed25519_bytes.bytesize + dilithium_bytes.bytesize
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ 'ed25519' => ed25519, 'dilithium' => dilithium }
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(ed25519: hash['ed25519'], dilithium: hash['dilithium'])
|
||||
end
|
||||
end
|
||||
|
||||
# Secret key for hybrid signatures.
|
||||
class SecretKey
|
||||
attr_accessor :ed25519_seed, :master_seed
|
||||
|
||||
def initialize(ed25519_seed: nil, master_seed: nil)
|
||||
@ed25519_seed = ed25519_seed
|
||||
@master_seed = master_seed
|
||||
end
|
||||
|
||||
def ed25519_seed_bytes
|
||||
Base64.strict_decode64(@ed25519_seed || '')
|
||||
end
|
||||
|
||||
def master_seed_bytes
|
||||
Base64.strict_decode64(@master_seed || '')
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ 'ed25519_seed' => ed25519_seed, 'master_seed' => master_seed }
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(ed25519_seed: hash['ed25519_seed'], master_seed: hash['master_seed'])
|
||||
end
|
||||
end
|
||||
|
||||
# Falcon public key.
|
||||
class FalconPublicKey
|
||||
attr_accessor :variant, :bytes
|
||||
|
||||
def initialize(variant: nil, bytes: nil)
|
||||
@variant = variant
|
||||
@bytes = bytes
|
||||
end
|
||||
|
||||
def key_bytes
|
||||
Base64.strict_decode64(@bytes || '')
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], bytes: hash['bytes'])
|
||||
end
|
||||
end
|
||||
|
||||
# Falcon secret key.
|
||||
class FalconSecretKey
|
||||
attr_accessor :variant, :bytes
|
||||
|
||||
def initialize(variant: nil, bytes: nil)
|
||||
@variant = variant
|
||||
@bytes = bytes
|
||||
end
|
||||
|
||||
def key_bytes
|
||||
Base64.strict_decode64(@bytes || '')
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], bytes: hash['bytes'])
|
||||
end
|
||||
end
|
||||
|
||||
# SPHINCS+ public key.
|
||||
class SphincsPublicKey
|
||||
attr_accessor :variant, :bytes
|
||||
|
||||
def initialize(variant: nil, bytes: nil)
|
||||
@variant = variant
|
||||
@bytes = bytes
|
||||
end
|
||||
|
||||
def key_bytes
|
||||
Base64.strict_decode64(@bytes || '')
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], bytes: hash['bytes'])
|
||||
end
|
||||
end
|
||||
|
||||
# SPHINCS+ secret key.
|
||||
class SphincsSecretKey
|
||||
attr_accessor :variant, :bytes
|
||||
|
||||
def initialize(variant: nil, bytes: nil)
|
||||
@variant = variant
|
||||
@bytes = bytes
|
||||
end
|
||||
|
||||
def key_bytes
|
||||
Base64.strict_decode64(@bytes || '')
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], bytes: hash['bytes'])
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Signature Types
|
||||
# ============================================================================
|
||||
|
||||
# Hybrid signature combining Ed25519 and Dilithium3.
|
||||
class HybridSignature
|
||||
attr_accessor :ed25519, :dilithium
|
||||
|
||||
def initialize(ed25519: nil, dilithium: nil)
|
||||
@ed25519 = ed25519
|
||||
@dilithium = dilithium
|
||||
end
|
||||
|
||||
def ed25519_bytes
|
||||
Base64.strict_decode64(@ed25519 || '')
|
||||
end
|
||||
|
||||
def dilithium_bytes
|
||||
Base64.strict_decode64(@dilithium || '')
|
||||
end
|
||||
|
||||
def size
|
||||
ed25519_bytes.bytesize + dilithium_bytes.bytesize
|
||||
end
|
||||
|
||||
def to_bytes
|
||||
ed25519_bytes + dilithium_bytes
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ 'ed25519' => ed25519, 'dilithium' => dilithium }
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(ed25519: hash['ed25519'], dilithium: hash['dilithium'])
|
||||
end
|
||||
end
|
||||
|
||||
# Falcon signature.
|
||||
class FalconSignature
|
||||
attr_accessor :variant, :signature
|
||||
|
||||
def initialize(variant: nil, signature: nil)
|
||||
@variant = variant
|
||||
@signature = signature
|
||||
end
|
||||
|
||||
def signature_bytes
|
||||
Base64.strict_decode64(@signature || '')
|
||||
end
|
||||
|
||||
def size
|
||||
signature_bytes.bytesize
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], signature: hash['signature'])
|
||||
end
|
||||
end
|
||||
|
||||
# SPHINCS+ signature.
|
||||
class SphincsSignature
|
||||
attr_accessor :variant, :signature
|
||||
|
||||
def initialize(variant: nil, signature: nil)
|
||||
@variant = variant
|
||||
@signature = signature
|
||||
end
|
||||
|
||||
def signature_bytes
|
||||
Base64.strict_decode64(@signature || '')
|
||||
end
|
||||
|
||||
def size
|
||||
signature_bytes.bytesize
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(variant: hash['variant'], signature: hash['signature'])
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Keypair Types
|
||||
# ============================================================================
|
||||
|
||||
# Hybrid keypair for Ed25519 + Dilithium3.
|
||||
class HybridKeypair
|
||||
attr_accessor :public_key, :secret_key, :addresses
|
||||
|
||||
def initialize(public_key: nil, secret_key: nil, addresses: {})
|
||||
@public_key = public_key
|
||||
@secret_key = secret_key
|
||||
@addresses = addresses
|
||||
end
|
||||
|
||||
def get_address(network)
|
||||
addresses[network] || ''
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(
|
||||
public_key: HybridPublicKey.from_hash(hash['public_key'] || {}),
|
||||
secret_key: SecretKey.from_hash(hash['secret_key'] || {}),
|
||||
addresses: hash['addresses'] || {}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Falcon keypair.
|
||||
class FalconKeypair
|
||||
attr_accessor :variant, :public_key, :secret_key
|
||||
|
||||
def initialize(variant: nil, public_key: nil, secret_key: nil)
|
||||
@variant = variant
|
||||
@public_key = public_key
|
||||
@secret_key = secret_key
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(
|
||||
variant: hash['variant'],
|
||||
public_key: FalconPublicKey.from_hash(hash['public_key'] || {}),
|
||||
secret_key: FalconSecretKey.from_hash(hash['secret_key'] || {})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# SPHINCS+ keypair.
|
||||
class SphincsKeypair
|
||||
attr_accessor :variant, :public_key, :secret_key
|
||||
|
||||
def initialize(variant: nil, public_key: nil, secret_key: nil)
|
||||
@variant = variant
|
||||
@public_key = public_key
|
||||
@secret_key = secret_key
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(
|
||||
variant: hash['variant'],
|
||||
public_key: SphincsPublicKey.from_hash(hash['public_key'] || {}),
|
||||
secret_key: SphincsSecretKey.from_hash(hash['secret_key'] || {})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Mnemonic Types
|
||||
# ============================================================================
|
||||
|
||||
# BIP-39 mnemonic phrase.
|
||||
class Mnemonic
|
||||
attr_accessor :phrase, :words, :word_count, :entropy
|
||||
|
||||
def initialize(phrase: nil, words: nil, word_count: 0, entropy: nil)
|
||||
@phrase = phrase
|
||||
@words = words
|
||||
@word_count = word_count
|
||||
@entropy = entropy
|
||||
end
|
||||
|
||||
def get_words
|
||||
words || phrase&.split(' ') || []
|
||||
end
|
||||
|
||||
def get_word_count
|
||||
word_count.positive? ? word_count : get_words.size
|
||||
end
|
||||
|
||||
def entropy_bytes
|
||||
entropy ? Base64.strict_decode64(entropy) : ''
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(
|
||||
phrase: hash['phrase'],
|
||||
words: hash['words'],
|
||||
word_count: hash['word_count'] || 0,
|
||||
entropy: hash['entropy']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Mnemonic validation result.
|
||||
class MnemonicValidation
|
||||
attr_accessor :valid, :error
|
||||
|
||||
def initialize(valid: false, error: nil)
|
||||
@valid = valid
|
||||
@error = error
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(valid: hash['valid'], error: hash['error'])
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Address Types
|
||||
# ============================================================================
|
||||
|
||||
# Blockchain address.
|
||||
class Address
|
||||
attr_accessor :address, :network, :pubkey_hash
|
||||
|
||||
def initialize(address: nil, network: nil, pubkey_hash: nil)
|
||||
@address = address
|
||||
@network = network
|
||||
@pubkey_hash = pubkey_hash
|
||||
end
|
||||
|
||||
def pubkey_hash_bytes
|
||||
pubkey_hash ? Base64.strict_decode64(pubkey_hash) : ''
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(
|
||||
address: hash['address'],
|
||||
network: hash['network'],
|
||||
pubkey_hash: hash['pubkey_hash']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Hash Types
|
||||
# ============================================================================
|
||||
|
||||
# 256-bit hash result.
|
||||
class Hash256
|
||||
attr_accessor :hash
|
||||
|
||||
def initialize(hash: nil)
|
||||
@hash = hash
|
||||
end
|
||||
|
||||
def hex
|
||||
hash
|
||||
end
|
||||
|
||||
def bytes
|
||||
[hash].pack('H*')
|
||||
end
|
||||
|
||||
def self.from_hash(hash_data)
|
||||
new(hash: hash_data['hash'])
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Error Types
|
||||
# ============================================================================
|
||||
|
||||
# Crypto SDK exception.
|
||||
class CryptoError < StandardError
|
||||
attr_reader :code, :http_status
|
||||
|
||||
def initialize(message, code: nil, http_status: nil)
|
||||
super(message)
|
||||
@code = code
|
||||
@http_status = http_status
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
673
sdk/rust/src/crypto/client.rs
Normal file
673
sdk/rust/src/crypto/client.rs
Normal file
|
|
@ -0,0 +1,673 @@
|
|||
//! Crypto SDK client implementation
|
||||
|
||||
use super::types::*;
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Main Synor Crypto client
|
||||
pub struct SynorCrypto {
|
||||
config: CryptoConfig,
|
||||
client: Client,
|
||||
closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl SynorCrypto {
|
||||
/// Creates a new client
|
||||
pub fn new(config: CryptoConfig) -> Self {
|
||||
let client = Client::builder()
|
||||
.timeout(std::time::Duration::from_millis(config.timeout_ms))
|
||||
.build()
|
||||
.expect("Failed to create HTTP client");
|
||||
|
||||
Self {
|
||||
config,
|
||||
client,
|
||||
closed: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default network
|
||||
pub fn default_network(&self) -> Network {
|
||||
self.config.default_network
|
||||
}
|
||||
|
||||
/// Returns the mnemonic client
|
||||
pub fn mnemonic(&self) -> MnemonicClient {
|
||||
MnemonicClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the keypair client
|
||||
pub fn keypairs(&self) -> KeypairClient {
|
||||
KeypairClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the signing client
|
||||
pub fn signing(&self) -> SigningClient {
|
||||
SigningClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the Falcon client
|
||||
pub fn falcon(&self) -> FalconClient {
|
||||
FalconClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the SPHINCS+ client
|
||||
pub fn sphincs(&self) -> SphincsClient {
|
||||
SphincsClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the KDF client
|
||||
pub fn kdf(&self) -> KdfClient {
|
||||
KdfClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Returns the hash client
|
||||
pub fn hash(&self) -> HashClient {
|
||||
HashClient { crypto: self }
|
||||
}
|
||||
|
||||
/// Checks service health
|
||||
pub async fn health_check(&self) -> Result<bool, CryptoError> {
|
||||
#[derive(Deserialize)]
|
||||
struct HealthResponse {
|
||||
status: String,
|
||||
}
|
||||
|
||||
match self.get::<HealthResponse>("/health").await {
|
||||
Ok(r) => Ok(r.status == "healthy"),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the client
|
||||
pub fn close(&self) {
|
||||
self.closed.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Returns true if closed
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.closed.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, CryptoError> {
|
||||
self.check_closed()?;
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.get(format!("{}{}", self.config.endpoint, path))
|
||||
.header("Authorization", format!("Bearer {}", self.config.api_key))
|
||||
.header("X-SDK-Version", "rust/0.1.0")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| CryptoError::NetworkError(e.to_string()))?;
|
||||
|
||||
self.handle_response(resp).await
|
||||
}
|
||||
|
||||
async fn post<T: for<'de> Deserialize<'de>, B: Serialize>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
) -> Result<T, CryptoError> {
|
||||
self.check_closed()?;
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.post(format!("{}{}", self.config.endpoint, path))
|
||||
.header("Authorization", format!("Bearer {}", self.config.api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-SDK-Version", "rust/0.1.0")
|
||||
.json(body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| CryptoError::NetworkError(e.to_string()))?;
|
||||
|
||||
self.handle_response(resp).await
|
||||
}
|
||||
|
||||
async fn handle_response<T: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
resp: reqwest::Response,
|
||||
) -> Result<T, CryptoError> {
|
||||
let status = resp.status();
|
||||
let text = resp.text().await.map_err(|e| CryptoError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
#[derive(Deserialize)]
|
||||
struct ErrorResponse {
|
||||
message: Option<String>,
|
||||
code: Option<String>,
|
||||
}
|
||||
|
||||
let err: ErrorResponse = serde_json::from_str(&text).unwrap_or(ErrorResponse {
|
||||
message: Some(format!("HTTP {}", status)),
|
||||
code: None,
|
||||
});
|
||||
|
||||
return Err(CryptoError::ApiError {
|
||||
message: err.message.unwrap_or_else(|| format!("HTTP {}", status)),
|
||||
code: err.code,
|
||||
});
|
||||
}
|
||||
|
||||
serde_json::from_str(&text).map_err(|e| CryptoError::NetworkError(e.to_string()))
|
||||
}
|
||||
|
||||
fn check_closed(&self) -> Result<(), CryptoError> {
|
||||
if self.is_closed() {
|
||||
return Err(CryptoError::ClientClosed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Client
|
||||
// ============================================================================
|
||||
|
||||
/// Mnemonic operations
|
||||
pub struct MnemonicClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> MnemonicClient<'a> {
|
||||
/// Generates a new random mnemonic
|
||||
pub async fn generate(&self, word_count: usize) -> Result<Mnemonic, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
word_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
phrase: String,
|
||||
words: Vec<String>,
|
||||
word_count: usize,
|
||||
entropy: Option<String>,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/mnemonic/generate", &Request { word_count }).await?;
|
||||
|
||||
Ok(Mnemonic {
|
||||
phrase: resp.phrase,
|
||||
words: resp.words,
|
||||
word_count: resp.word_count,
|
||||
entropy: resp.entropy.map(|e| BASE64.decode(e).unwrap_or_default()).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a mnemonic from a phrase
|
||||
pub async fn from_phrase(&self, phrase: &str) -> Result<Mnemonic, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request<'a> {
|
||||
phrase: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
phrase: String,
|
||||
words: Vec<String>,
|
||||
word_count: usize,
|
||||
entropy: Option<String>,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/mnemonic/from-phrase", &Request { phrase }).await?;
|
||||
|
||||
Ok(Mnemonic {
|
||||
phrase: resp.phrase,
|
||||
words: resp.words,
|
||||
word_count: resp.word_count,
|
||||
entropy: resp.entropy.map(|e| BASE64.decode(e).unwrap_or_default()).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates a mnemonic phrase
|
||||
pub async fn validate(&self, phrase: &str) -> Result<MnemonicValidation, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request<'a> {
|
||||
phrase: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
valid: bool,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/mnemonic/validate", &Request { phrase }).await?;
|
||||
|
||||
Ok(MnemonicValidation {
|
||||
valid: resp.valid,
|
||||
error: resp.error,
|
||||
})
|
||||
}
|
||||
|
||||
/// Derives a seed from a mnemonic
|
||||
pub async fn to_seed(&self, phrase: &str, passphrase: &str) -> Result<Vec<u8>, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request<'a> {
|
||||
phrase: &'a str,
|
||||
passphrase: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
seed: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/mnemonic/to-seed", &Request { phrase, passphrase }).await?;
|
||||
|
||||
BASE64.decode(resp.seed).map_err(|e| CryptoError::DerivationFailed(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Client
|
||||
// ============================================================================
|
||||
|
||||
/// Keypair operations
|
||||
pub struct KeypairClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> KeypairClient<'a> {
|
||||
/// Generates a new random keypair
|
||||
pub async fn generate(&self) -> Result<HybridKeypair, CryptoError> {
|
||||
let resp: KeypairResponse = self.crypto.post("/keypair/generate", &()).await?;
|
||||
Ok(resp.into_keypair())
|
||||
}
|
||||
|
||||
/// Creates a keypair from a mnemonic
|
||||
pub async fn from_mnemonic(&self, phrase: &str, passphrase: &str) -> Result<HybridKeypair, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request<'a> {
|
||||
phrase: &'a str,
|
||||
passphrase: &'a str,
|
||||
}
|
||||
|
||||
let resp: KeypairResponse = self.crypto.post("/keypair/from-mnemonic", &Request { phrase, passphrase }).await?;
|
||||
Ok(resp.into_keypair())
|
||||
}
|
||||
|
||||
/// Creates a keypair from a seed
|
||||
pub async fn from_seed(&self, seed: &[u8]) -> Result<HybridKeypair, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
seed: String,
|
||||
}
|
||||
|
||||
let resp: KeypairResponse = self.crypto.post("/keypair/from-seed", &Request {
|
||||
seed: BASE64.encode(seed),
|
||||
}).await?;
|
||||
Ok(resp.into_keypair())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct KeypairResponse {
|
||||
public_key: PublicKeyResponse,
|
||||
secret_key: SecretKeyResponse,
|
||||
addresses: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PublicKeyResponse {
|
||||
ed25519: String,
|
||||
dilithium: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SecretKeyResponse {
|
||||
ed25519_seed: String,
|
||||
master_seed: String,
|
||||
}
|
||||
|
||||
impl KeypairResponse {
|
||||
fn into_keypair(self) -> HybridKeypair {
|
||||
let ed25519 = BASE64.decode(&self.public_key.ed25519).unwrap_or_default();
|
||||
let dilithium = BASE64.decode(&self.public_key.dilithium).unwrap_or_default();
|
||||
let ed25519_seed = BASE64.decode(&self.secret_key.ed25519_seed).unwrap_or_default();
|
||||
let master_seed = BASE64.decode(&self.secret_key.master_seed).unwrap_or_default();
|
||||
|
||||
let mut addresses = HashMap::new();
|
||||
for (k, v) in self.addresses {
|
||||
if let Ok(network) = serde_json::from_str::<Network>(&format!("\"{}\"", k)) {
|
||||
addresses.insert(network, v);
|
||||
}
|
||||
}
|
||||
|
||||
HybridKeypair::new(
|
||||
HybridPublicKey { ed25519, dilithium },
|
||||
SecretKey { ed25519_seed, master_seed },
|
||||
addresses,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signing Client
|
||||
// ============================================================================
|
||||
|
||||
/// Signing operations
|
||||
pub struct SigningClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> SigningClient<'a> {
|
||||
/// Signs a message with a hybrid keypair
|
||||
pub async fn sign(&self, keypair: &HybridKeypair, message: &[u8]) -> Result<HybridSignature, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
secret_key: SecretKeyRequest,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SecretKeyRequest {
|
||||
ed25519_seed: String,
|
||||
master_seed: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
ed25519: String,
|
||||
dilithium: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/sign/hybrid", &Request {
|
||||
secret_key: SecretKeyRequest {
|
||||
ed25519_seed: BASE64.encode(&keypair.secret_key.ed25519_seed),
|
||||
master_seed: BASE64.encode(&keypair.secret_key.master_seed),
|
||||
},
|
||||
message: BASE64.encode(message),
|
||||
}).await?;
|
||||
|
||||
Ok(HybridSignature {
|
||||
ed25519: BASE64.decode(resp.ed25519).unwrap_or_default(),
|
||||
dilithium: BASE64.decode(resp.dilithium).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Verifies a hybrid signature
|
||||
pub async fn verify(
|
||||
&self,
|
||||
public_key: &HybridPublicKey,
|
||||
message: &[u8],
|
||||
signature: &HybridSignature,
|
||||
) -> Result<bool, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
public_key: PublicKeyRequest,
|
||||
message: String,
|
||||
signature: SignatureRequest,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PublicKeyRequest {
|
||||
ed25519: String,
|
||||
dilithium: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SignatureRequest {
|
||||
ed25519: String,
|
||||
dilithium: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/sign/verify", &Request {
|
||||
public_key: PublicKeyRequest {
|
||||
ed25519: BASE64.encode(&public_key.ed25519),
|
||||
dilithium: BASE64.encode(&public_key.dilithium),
|
||||
},
|
||||
message: BASE64.encode(message),
|
||||
signature: SignatureRequest {
|
||||
ed25519: BASE64.encode(&signature.ed25519),
|
||||
dilithium: BASE64.encode(&signature.dilithium),
|
||||
},
|
||||
}).await?;
|
||||
|
||||
Ok(resp.valid)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Falcon Client
|
||||
// ============================================================================
|
||||
|
||||
/// Falcon operations
|
||||
pub struct FalconClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> FalconClient<'a> {
|
||||
/// Generates a Falcon keypair
|
||||
pub async fn generate(&self, variant: FalconVariant) -> Result<FalconKeypair, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
variant: FalconVariant,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
variant: FalconVariant,
|
||||
public_key: KeyBytes,
|
||||
secret_key: KeyBytes,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct KeyBytes {
|
||||
bytes: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/falcon/generate", &Request { variant }).await?;
|
||||
|
||||
Ok(FalconKeypair {
|
||||
variant: resp.variant,
|
||||
public_key: FalconPublicKey {
|
||||
variant: resp.variant,
|
||||
bytes: BASE64.decode(resp.public_key.bytes).unwrap_or_default(),
|
||||
},
|
||||
secret_key: FalconSecretKey {
|
||||
variant: resp.variant,
|
||||
bytes: BASE64.decode(resp.secret_key.bytes).unwrap_or_default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Signs with Falcon
|
||||
pub async fn sign(&self, keypair: &FalconKeypair, message: &[u8]) -> Result<FalconSignature, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
variant: FalconVariant,
|
||||
secret_key: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
signature: String,
|
||||
variant: FalconVariant,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/falcon/sign", &Request {
|
||||
variant: keypair.variant,
|
||||
secret_key: BASE64.encode(&keypair.secret_key.bytes),
|
||||
message: BASE64.encode(message),
|
||||
}).await?;
|
||||
|
||||
Ok(FalconSignature {
|
||||
variant: resp.variant,
|
||||
bytes: BASE64.decode(resp.signature).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPHINCS+ Client
|
||||
// ============================================================================
|
||||
|
||||
/// SPHINCS+ operations
|
||||
pub struct SphincsClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> SphincsClient<'a> {
|
||||
/// Generates a SPHINCS+ keypair
|
||||
pub async fn generate(&self, variant: SphincsVariant) -> Result<SphincsKeypair, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
variant: SphincsVariant,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
variant: SphincsVariant,
|
||||
public_key: KeyBytes,
|
||||
secret_key: KeyBytes,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct KeyBytes {
|
||||
bytes: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/sphincs/generate", &Request { variant }).await?;
|
||||
|
||||
Ok(SphincsKeypair {
|
||||
variant: resp.variant,
|
||||
public_key: SphincsPublicKey {
|
||||
variant: resp.variant,
|
||||
bytes: BASE64.decode(resp.public_key.bytes).unwrap_or_default(),
|
||||
},
|
||||
secret_key: SphincsSecretKey {
|
||||
variant: resp.variant,
|
||||
bytes: BASE64.decode(resp.secret_key.bytes).unwrap_or_default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KDF Client
|
||||
// ============================================================================
|
||||
|
||||
/// Key derivation operations
|
||||
pub struct KdfClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> KdfClient<'a> {
|
||||
/// Derives a key using HKDF
|
||||
pub async fn derive_key(&self, seed: &[u8], config: &DerivationConfig) -> Result<Vec<u8>, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
seed: String,
|
||||
salt: Option<String>,
|
||||
info: Option<String>,
|
||||
output_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
key: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/kdf/hkdf", &Request {
|
||||
seed: BASE64.encode(seed),
|
||||
salt: config.salt.as_ref().map(|s| BASE64.encode(s)),
|
||||
info: config.info.as_ref().map(|i| BASE64.encode(i)),
|
||||
output_length: config.output_length,
|
||||
}).await?;
|
||||
|
||||
BASE64.decode(resp.key).map_err(|e| CryptoError::DerivationFailed(e.to_string()))
|
||||
}
|
||||
|
||||
/// Derives a key from a password
|
||||
pub async fn derive_from_password(
|
||||
&self,
|
||||
password: &[u8],
|
||||
config: &PasswordDerivationConfig,
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
password: String,
|
||||
salt: String,
|
||||
iterations: u32,
|
||||
output_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
key: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/kdf/pbkdf2", &Request {
|
||||
password: BASE64.encode(password),
|
||||
salt: BASE64.encode(&config.salt),
|
||||
iterations: config.iterations,
|
||||
output_length: config.output_length,
|
||||
}).await?;
|
||||
|
||||
BASE64.decode(resp.key).map_err(|e| CryptoError::DerivationFailed(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Client
|
||||
// ============================================================================
|
||||
|
||||
/// Hashing operations
|
||||
pub struct HashClient<'a> {
|
||||
crypto: &'a SynorCrypto,
|
||||
}
|
||||
|
||||
impl<'a> HashClient<'a> {
|
||||
/// Computes SHA3-256 hash
|
||||
pub async fn sha3_256(&self, data: &[u8]) -> Result<Hash256, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/hash/sha3-256", &Request {
|
||||
data: BASE64.encode(data),
|
||||
}).await?;
|
||||
|
||||
let bytes = hex::decode(&resp.hash).unwrap_or_default();
|
||||
Ok(Hash256 { bytes, hex: resp.hash })
|
||||
}
|
||||
|
||||
/// Computes BLAKE3 hash
|
||||
pub async fn blake3(&self, data: &[u8]) -> Result<Hash256, CryptoError> {
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
let resp: Response = self.crypto.post("/hash/blake3", &Request {
|
||||
data: BASE64.encode(data),
|
||||
}).await?;
|
||||
|
||||
let bytes = hex::decode(&resp.hash).unwrap_or_default();
|
||||
Ok(Hash256 { bytes, hex: resp.hash })
|
||||
}
|
||||
}
|
||||
42
sdk/rust/src/crypto/mod.rs
Normal file
42
sdk/rust/src/crypto/mod.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! Synor Crypto SDK for Rust
|
||||
//!
|
||||
//! Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! - Hybrid Ed25519 + Dilithium3 signatures (quantum-resistant)
|
||||
//! - BIP-39 mnemonic support (12-24 words)
|
||||
//! - BIP-44 hierarchical key derivation
|
||||
//! - Post-quantum algorithms: Falcon, SPHINCS+
|
||||
//! - Secure key derivation (HKDF, PBKDF2)
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use synor_crypto::{SynorCrypto, CryptoConfig, Network};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let config = CryptoConfig::new("your-api-key");
|
||||
//! let crypto = SynorCrypto::new(config);
|
||||
//!
|
||||
//! // Generate a mnemonic
|
||||
//! let mnemonic = crypto.mnemonic().generate(24).await?;
|
||||
//! println!("Backup words: {}", mnemonic.phrase);
|
||||
//!
|
||||
//! // Create keypair from mnemonic
|
||||
//! let keypair = crypto.keypairs().from_mnemonic(&mnemonic.phrase, "").await?;
|
||||
//! let address = keypair.address(Network::Mainnet);
|
||||
//!
|
||||
//! // Sign a message
|
||||
//! let signature = crypto.signing().sign(&keypair, b"Hello!").await?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod types;
|
||||
mod client;
|
||||
|
||||
pub use types::*;
|
||||
pub use client::*;
|
||||
537
sdk/rust/src/crypto/types.rs
Normal file
537
sdk/rust/src/crypto/types.rs
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
//! Crypto SDK types for Rust
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
/// Network type for address generation
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Network {
|
||||
Mainnet,
|
||||
Testnet,
|
||||
Devnet,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
fn default() -> Self {
|
||||
Network::Mainnet
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon variant selection
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FalconVariant {
|
||||
Falcon512,
|
||||
Falcon1024,
|
||||
}
|
||||
|
||||
impl Default for FalconVariant {
|
||||
fn default() -> Self {
|
||||
FalconVariant::Falcon512
|
||||
}
|
||||
}
|
||||
|
||||
impl FalconVariant {
|
||||
/// Returns the signature size for this variant
|
||||
pub const fn signature_size(&self) -> usize {
|
||||
match self {
|
||||
FalconVariant::Falcon512 => 690,
|
||||
FalconVariant::Falcon1024 => 1330,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the public key size for this variant
|
||||
pub const fn public_key_size(&self) -> usize {
|
||||
match self {
|
||||
FalconVariant::Falcon512 => 897,
|
||||
FalconVariant::Falcon1024 => 1793,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the security level in bits
|
||||
pub const fn security_level(&self) -> u16 {
|
||||
match self {
|
||||
FalconVariant::Falcon512 => 128,
|
||||
FalconVariant::Falcon1024 => 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ variant selection
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SphincsVariant {
|
||||
Shake128s,
|
||||
Shake192s,
|
||||
Shake256s,
|
||||
}
|
||||
|
||||
impl Default for SphincsVariant {
|
||||
fn default() -> Self {
|
||||
SphincsVariant::Shake128s
|
||||
}
|
||||
}
|
||||
|
||||
impl SphincsVariant {
|
||||
/// Returns the signature size for this variant
|
||||
pub const fn signature_size(&self) -> usize {
|
||||
match self {
|
||||
SphincsVariant::Shake128s => 7856,
|
||||
SphincsVariant::Shake192s => 16224,
|
||||
SphincsVariant::Shake256s => 29792,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the security level in bits
|
||||
pub const fn security_level(&self) -> u16 {
|
||||
match self {
|
||||
SphincsVariant::Shake128s => 128,
|
||||
SphincsVariant::Shake192s => 192,
|
||||
SphincsVariant::Shake256s => 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Post-quantum algorithm selection
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PqAlgorithm {
|
||||
Dilithium3,
|
||||
Falcon512,
|
||||
Falcon1024,
|
||||
Sphincs128s,
|
||||
Sphincs192s,
|
||||
Sphincs256s,
|
||||
}
|
||||
|
||||
/// Algorithm family classification
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlgorithmFamily {
|
||||
Classical,
|
||||
Lattice,
|
||||
HashBased,
|
||||
Hybrid,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Types
|
||||
// ============================================================================
|
||||
|
||||
/// Crypto SDK configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CryptoConfig {
|
||||
pub api_key: String,
|
||||
pub endpoint: String,
|
||||
pub timeout_ms: u64,
|
||||
pub retries: u32,
|
||||
pub debug: bool,
|
||||
pub default_network: Network,
|
||||
}
|
||||
|
||||
impl CryptoConfig {
|
||||
/// Creates a new config with the given API key
|
||||
pub fn new(api_key: impl Into<String>) -> Self {
|
||||
Self {
|
||||
api_key: api_key.into(),
|
||||
endpoint: "https://crypto.synor.io/v1".to_string(),
|
||||
timeout_ms: 30000,
|
||||
retries: 3,
|
||||
debug: false,
|
||||
default_network: Network::Mainnet,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the endpoint
|
||||
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
|
||||
self.endpoint = endpoint.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the timeout
|
||||
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
|
||||
self.timeout_ms = timeout_ms;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Key derivation configuration
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DerivationConfig {
|
||||
pub salt: Option<Vec<u8>>,
|
||||
pub info: Option<Vec<u8>>,
|
||||
pub output_length: usize,
|
||||
}
|
||||
|
||||
/// Password derivation configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PasswordDerivationConfig {
|
||||
pub salt: Vec<u8>,
|
||||
pub iterations: u32,
|
||||
pub output_length: usize,
|
||||
}
|
||||
|
||||
/// BIP-44 derivation path
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DerivationPath {
|
||||
pub account: u32,
|
||||
pub change: u32,
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl DerivationPath {
|
||||
/// Synor coin type for BIP-44
|
||||
pub const COIN_TYPE: u32 = 0x5359;
|
||||
|
||||
/// Creates a new derivation path
|
||||
pub fn new(account: u32, change: u32, index: u32) -> Self {
|
||||
Self { account, change, index }
|
||||
}
|
||||
|
||||
/// Creates a path for external addresses
|
||||
pub fn external(account: u32, index: u32) -> Self {
|
||||
Self { account, change: 0, index }
|
||||
}
|
||||
|
||||
/// Creates a path for internal (change) addresses
|
||||
pub fn internal(account: u32, index: u32) -> Self {
|
||||
Self { account, change: 1, index }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DerivationPath {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"m/44'/{}'/{}'/{}/{}",
|
||||
Self::COIN_TYPE,
|
||||
self.account,
|
||||
self.change,
|
||||
self.index
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid public key (Ed25519 + Dilithium3)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HybridPublicKey {
|
||||
/// Ed25519 component (32 bytes)
|
||||
pub ed25519: Vec<u8>,
|
||||
/// Dilithium3 component (~1952 bytes)
|
||||
pub dilithium: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HybridPublicKey {
|
||||
/// Returns the total size in bytes
|
||||
pub fn size(&self) -> usize {
|
||||
self.ed25519.len() + self.dilithium.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Secret key (master seed)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SecretKey {
|
||||
/// Ed25519 seed (32 bytes)
|
||||
pub ed25519_seed: Vec<u8>,
|
||||
/// Master seed (64 bytes)
|
||||
pub master_seed: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Falcon public key
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FalconPublicKey {
|
||||
pub variant: FalconVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Falcon secret key
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FalconSecretKey {
|
||||
pub variant: FalconVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// SPHINCS+ public key
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SphincsPublicKey {
|
||||
pub variant: SphincsVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// SPHINCS+ secret key
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SphincsSecretKey {
|
||||
pub variant: SphincsVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid signature (Ed25519 + Dilithium3)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HybridSignature {
|
||||
/// Ed25519 component (64 bytes)
|
||||
pub ed25519: Vec<u8>,
|
||||
/// Dilithium3 component (~3293 bytes)
|
||||
pub dilithium: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HybridSignature {
|
||||
/// Returns the total size in bytes
|
||||
pub fn size(&self) -> usize {
|
||||
self.ed25519.len() + self.dilithium.len()
|
||||
}
|
||||
|
||||
/// Serializes the signature to bytes
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::with_capacity(self.size());
|
||||
result.extend_from_slice(&self.ed25519);
|
||||
result.extend_from_slice(&self.dilithium);
|
||||
result
|
||||
}
|
||||
|
||||
/// Deserializes a signature from bytes
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, CryptoError> {
|
||||
if data.len() < 64 {
|
||||
return Err(CryptoError::InvalidSignature("Too short".to_string()));
|
||||
}
|
||||
Ok(Self {
|
||||
ed25519: data[..64].to_vec(),
|
||||
dilithium: data[64..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon signature
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FalconSignature {
|
||||
pub variant: FalconVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// SPHINCS+ signature
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SphincsSignature {
|
||||
pub variant: SphincsVariant,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Keypair Types
|
||||
// ============================================================================
|
||||
|
||||
/// Hybrid keypair (Ed25519 + Dilithium3)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HybridKeypair {
|
||||
pub public_key: HybridPublicKey,
|
||||
pub secret_key: SecretKey,
|
||||
addresses: HashMap<Network, String>,
|
||||
}
|
||||
|
||||
impl HybridKeypair {
|
||||
/// Returns the address for a network
|
||||
pub fn address(&self, network: Network) -> Option<&str> {
|
||||
self.addresses.get(&network).map(|s| s.as_str())
|
||||
}
|
||||
|
||||
/// Creates a new keypair with addresses
|
||||
pub fn new(
|
||||
public_key: HybridPublicKey,
|
||||
secret_key: SecretKey,
|
||||
addresses: HashMap<Network, String>,
|
||||
) -> Self {
|
||||
Self { public_key, secret_key, addresses }
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon keypair
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FalconKeypair {
|
||||
pub variant: FalconVariant,
|
||||
pub public_key: FalconPublicKey,
|
||||
pub secret_key: FalconSecretKey,
|
||||
}
|
||||
|
||||
/// SPHINCS+ keypair
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SphincsKeypair {
|
||||
pub variant: SphincsVariant,
|
||||
pub public_key: SphincsPublicKey,
|
||||
pub secret_key: SphincsSecretKey,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mnemonic Types
|
||||
// ============================================================================
|
||||
|
||||
/// BIP-39 mnemonic phrase
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mnemonic {
|
||||
pub phrase: String,
|
||||
pub words: Vec<String>,
|
||||
pub word_count: usize,
|
||||
pub entropy: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Mnemonic validation result
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MnemonicValidation {
|
||||
pub valid: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Address Types
|
||||
// ============================================================================
|
||||
|
||||
/// Blockchain address
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Address {
|
||||
pub address: String,
|
||||
pub network: Network,
|
||||
pub pubkey_hash: Vec<u8>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hash Types
|
||||
// ============================================================================
|
||||
|
||||
/// 256-bit hash
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Hash256 {
|
||||
pub bytes: Vec<u8>,
|
||||
pub hex: String,
|
||||
}
|
||||
|
||||
impl Hash256 {
|
||||
/// Creates a hash from bytes
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
let hex = hex::encode(&bytes);
|
||||
Self { bytes, hex }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Negotiation Types
|
||||
// ============================================================================
|
||||
|
||||
/// Algorithm capabilities for negotiation
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AlgorithmCapabilities {
|
||||
pub pq_algorithms: Vec<PqAlgorithm>,
|
||||
pub classical: bool,
|
||||
pub hybrid: bool,
|
||||
pub preferred: Option<PqAlgorithm>,
|
||||
}
|
||||
|
||||
/// Negotiation policy
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NegotiationPolicy {
|
||||
pub min_security_level: u16,
|
||||
pub prefer_compact: bool,
|
||||
pub allow_classical: bool,
|
||||
pub required_families: Vec<AlgorithmFamily>,
|
||||
}
|
||||
|
||||
impl Default for NegotiationPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
min_security_level: 128,
|
||||
prefer_compact: false,
|
||||
allow_classical: false,
|
||||
required_families: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Negotiation result
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NegotiationResult {
|
||||
pub algorithm: PqAlgorithm,
|
||||
pub security_level: u16,
|
||||
pub family: AlgorithmFamily,
|
||||
pub hybrid: bool,
|
||||
}
|
||||
|
||||
/// Session parameters after negotiation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionParams {
|
||||
pub algorithm: PqAlgorithm,
|
||||
pub session_key: Option<Vec<u8>>,
|
||||
pub expires_at: Option<i64>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
/// Crypto operation error
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CryptoError {
|
||||
#[error("Invalid mnemonic: {0}")]
|
||||
InvalidMnemonic(String),
|
||||
|
||||
#[error("Invalid key: {0}")]
|
||||
InvalidKey(String),
|
||||
|
||||
#[error("Invalid signature: {0}")]
|
||||
InvalidSignature(String),
|
||||
|
||||
#[error("Verification failed")]
|
||||
VerificationFailed,
|
||||
|
||||
#[error("Derivation failed: {0}")]
|
||||
DerivationFailed(String),
|
||||
|
||||
#[error("Algorithm mismatch")]
|
||||
AlgorithmMismatch,
|
||||
|
||||
#[error("Negotiation failed: {0}")]
|
||||
NegotiationFailed(String),
|
||||
|
||||
#[error("Network error: {0}")]
|
||||
NetworkError(String),
|
||||
|
||||
#[error("API error: {message} (code: {code:?})")]
|
||||
ApiError { message: String, code: Option<String> },
|
||||
|
||||
#[error("Client closed")]
|
||||
ClientClosed,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
/// Ed25519 public key size
|
||||
pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
|
||||
/// Ed25519 secret key size
|
||||
pub const ED25519_SECRET_KEY_SIZE: usize = 32;
|
||||
/// Ed25519 signature size
|
||||
pub const ED25519_SIGNATURE_SIZE: usize = 64;
|
||||
/// Dilithium3 public key size
|
||||
pub const DILITHIUM3_PUBLIC_KEY_SIZE: usize = 1952;
|
||||
/// Dilithium3 signature size
|
||||
pub const DILITHIUM3_SIGNATURE_SIZE: usize = 3293;
|
||||
/// Hybrid signature size
|
||||
pub const HYBRID_SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE + DILITHIUM3_SIGNATURE_SIZE;
|
||||
/// BIP-44 coin type for Synor
|
||||
pub const COIN_TYPE: u32 = 0x5359;
|
||||
/// Minimum PBKDF2 iterations
|
||||
pub const MIN_PBKDF2_ITERATIONS: u32 = 10000;
|
||||
/// Minimum salt length
|
||||
pub const MIN_SALT_LENGTH: usize = 8;
|
||||
/// Default API endpoint
|
||||
pub const DEFAULT_ENDPOINT: &str = "https://crypto.synor.io/v1";
|
||||
347
sdk/swift/Sources/SynorCrypto/SynorCrypto.swift
Normal file
347
sdk/swift/Sources/SynorCrypto/SynorCrypto.swift
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
import Foundation
|
||||
|
||||
/// Synor Crypto SDK for Swift
|
||||
///
|
||||
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
|
||||
///
|
||||
/// ```swift
|
||||
/// let config = CryptoConfig(apiKey: "your-api-key")
|
||||
/// let crypto = SynorCrypto(config: config)
|
||||
///
|
||||
/// // Generate a mnemonic
|
||||
/// let mnemonic = try await crypto.mnemonic.generate(wordCount: 24)
|
||||
/// print("Backup words: \(mnemonic.phrase)")
|
||||
///
|
||||
/// // Create keypair from mnemonic
|
||||
/// let keypair = try await crypto.keypairs.fromMnemonic(phrase: mnemonic.phrase, passphrase: "")
|
||||
/// let address = keypair.address(for: .mainnet)
|
||||
///
|
||||
/// // Sign a message
|
||||
/// let signature = try await crypto.signing.sign(keypair: keypair, message: "Hello!".data(using: .utf8)!)
|
||||
/// ```
|
||||
public class SynorCrypto {
|
||||
private let config: CryptoConfig
|
||||
private let session: URLSession
|
||||
private let encoder: JSONEncoder
|
||||
private let decoder: JSONDecoder
|
||||
private var _closed = false
|
||||
|
||||
public let mnemonic: MnemonicClient
|
||||
public let keypairs: KeypairClient
|
||||
public let signing: SigningClient
|
||||
public let falcon: FalconClient
|
||||
public let sphincs: SphincsClient
|
||||
public let kdf: KdfClient
|
||||
public let hash: HashClient
|
||||
|
||||
public var defaultNetwork: Network { config.defaultNetwork }
|
||||
public var isClosed: Bool { _closed }
|
||||
|
||||
public init(config: CryptoConfig) {
|
||||
self.config = config
|
||||
|
||||
let sessionConfig = URLSessionConfiguration.default
|
||||
sessionConfig.timeoutIntervalForRequest = config.timeout
|
||||
self.session = URLSession(configuration: sessionConfig)
|
||||
|
||||
self.encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
|
||||
self.decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
self.mnemonic = MnemonicClient()
|
||||
self.keypairs = KeypairClient()
|
||||
self.signing = SigningClient()
|
||||
self.falcon = FalconClient()
|
||||
self.sphincs = SphincsClient()
|
||||
self.kdf = KdfClient()
|
||||
self.hash = HashClient()
|
||||
|
||||
// Set parent references
|
||||
self.mnemonic.crypto = self
|
||||
self.keypairs.crypto = self
|
||||
self.signing.crypto = self
|
||||
self.falcon.crypto = self
|
||||
self.sphincs.crypto = self
|
||||
self.kdf.crypto = self
|
||||
self.hash.crypto = self
|
||||
}
|
||||
|
||||
public func healthCheck() async -> Bool {
|
||||
do {
|
||||
let result: [String: String] = try await get(path: "/health")
|
||||
return result["status"] == "healthy"
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func getInfo() async throws -> [String: Any] {
|
||||
try await get(path: "/info")
|
||||
}
|
||||
|
||||
public func close() {
|
||||
_closed = true
|
||||
session.invalidateAndCancel()
|
||||
}
|
||||
|
||||
// MARK: - Internal HTTP Methods
|
||||
|
||||
func get<T: Decodable>(path: String) async throws -> T {
|
||||
guard !_closed else {
|
||||
throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
|
||||
}
|
||||
|
||||
guard let url = URL(string: config.endpoint + path) else {
|
||||
throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
return try handleResponse(data: data, response: response)
|
||||
}
|
||||
|
||||
func post<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
|
||||
guard !_closed else {
|
||||
throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
|
||||
}
|
||||
|
||||
guard let url = URL(string: config.endpoint + path) else {
|
||||
throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
|
||||
|
||||
if let body = body {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||
} else {
|
||||
request.httpBody = "{}".data(using: .utf8)
|
||||
}
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
return try handleResponse(data: data, response: response)
|
||||
}
|
||||
|
||||
private func handleResponse<T: Decodable>(data: Data, response: URLResponse) throws -> T {
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw CryptoError(message: "Invalid response", code: "INVALID_RESPONSE")
|
||||
}
|
||||
|
||||
if httpResponse.statusCode >= 400 {
|
||||
if let errorDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||
let message = errorDict["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
|
||||
let code = errorDict["code"] as? String
|
||||
throw CryptoError(message: message, code: code)
|
||||
}
|
||||
throw CryptoError(message: "HTTP \(httpResponse.statusCode)", code: nil)
|
||||
}
|
||||
|
||||
return try decoder.decode(T.self, from: data)
|
||||
}
|
||||
|
||||
// MARK: - Sub-Clients
|
||||
|
||||
public class MnemonicClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func generate(wordCount: Int = 24) async throws -> Mnemonic {
|
||||
try await crypto!.post(path: "/mnemonic/generate", body: ["word_count": wordCount])
|
||||
}
|
||||
|
||||
public func fromPhrase(phrase: String) async throws -> Mnemonic {
|
||||
try await crypto!.post(path: "/mnemonic/from-phrase", body: ["phrase": phrase])
|
||||
}
|
||||
|
||||
public func validate(phrase: String) async throws -> MnemonicValidation {
|
||||
try await crypto!.post(path: "/mnemonic/validate", body: ["phrase": phrase])
|
||||
}
|
||||
|
||||
public func toSeed(phrase: String, passphrase: String = "") async throws -> Data {
|
||||
struct SeedResponse: Decodable { let seed: String }
|
||||
let result: SeedResponse = try await crypto!.post(
|
||||
path: "/mnemonic/to-seed",
|
||||
body: ["phrase": phrase, "passphrase": passphrase]
|
||||
)
|
||||
return Data(base64Encoded: result.seed) ?? Data()
|
||||
}
|
||||
|
||||
public func suggestWords(partial: String, limit: Int = 5) async throws -> [String] {
|
||||
struct SuggestResponse: Decodable { let suggestions: [String] }
|
||||
let result: SuggestResponse = try await crypto!.post(
|
||||
path: "/mnemonic/suggest",
|
||||
body: ["partial": partial, "limit": limit]
|
||||
)
|
||||
return result.suggestions
|
||||
}
|
||||
}
|
||||
|
||||
public class KeypairClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func generate() async throws -> HybridKeypair {
|
||||
try await crypto!.post(path: "/keypair/generate")
|
||||
}
|
||||
|
||||
public func fromMnemonic(phrase: String, passphrase: String = "") async throws -> HybridKeypair {
|
||||
try await crypto!.post(
|
||||
path: "/keypair/from-mnemonic",
|
||||
body: ["phrase": phrase, "passphrase": passphrase]
|
||||
)
|
||||
}
|
||||
|
||||
public func fromSeed(seed: Data) async throws -> HybridKeypair {
|
||||
try await crypto!.post(
|
||||
path: "/keypair/from-seed",
|
||||
body: ["seed": seed.base64EncodedString()]
|
||||
)
|
||||
}
|
||||
|
||||
public func getAddress(publicKey: HybridPublicKey, network: Network) async throws -> Address {
|
||||
try await crypto!.post(
|
||||
path: "/keypair/address",
|
||||
body: ["public_key": publicKey.toDict(), "network": network.rawValue]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public class SigningClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func sign(keypair: HybridKeypair, message: Data) async throws -> HybridSignature {
|
||||
try await crypto!.post(path: "/sign/hybrid", body: [
|
||||
"secret_key": keypair.secretKey.toDict(),
|
||||
"message": message.base64EncodedString()
|
||||
])
|
||||
}
|
||||
|
||||
public func verify(publicKey: HybridPublicKey, message: Data, signature: HybridSignature) async throws -> Bool {
|
||||
struct VerifyResponse: Decodable { let valid: Bool }
|
||||
let result: VerifyResponse = try await crypto!.post(path: "/sign/verify", body: [
|
||||
"public_key": publicKey.toDict(),
|
||||
"message": message.base64EncodedString(),
|
||||
"signature": signature.toDict()
|
||||
])
|
||||
return result.valid
|
||||
}
|
||||
|
||||
public func signEd25519(secretKey: Data, message: Data) async throws -> Data {
|
||||
struct SignResponse: Decodable { let signature: String }
|
||||
let result: SignResponse = try await crypto!.post(path: "/sign/ed25519", body: [
|
||||
"secret_key": secretKey.base64EncodedString(),
|
||||
"message": message.base64EncodedString()
|
||||
])
|
||||
return Data(base64Encoded: result.signature) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
public class FalconClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func generate(variant: FalconVariant = .falcon512) async throws -> FalconKeypair {
|
||||
try await crypto!.post(path: "/falcon/generate", body: ["variant": variant.rawValue])
|
||||
}
|
||||
|
||||
public func sign(keypair: FalconKeypair, message: Data) async throws -> FalconSignature {
|
||||
try await crypto!.post(path: "/falcon/sign", body: [
|
||||
"variant": keypair.variantEnum.rawValue,
|
||||
"secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
|
||||
"message": message.base64EncodedString()
|
||||
])
|
||||
}
|
||||
|
||||
public func verify(publicKey: Data, message: Data, signature: FalconSignature) async throws -> Bool {
|
||||
struct VerifyResponse: Decodable { let valid: Bool }
|
||||
let result: VerifyResponse = try await crypto!.post(path: "/falcon/verify", body: [
|
||||
"variant": signature.variantEnum.rawValue,
|
||||
"public_key": publicKey.base64EncodedString(),
|
||||
"message": message.base64EncodedString(),
|
||||
"signature": signature.signatureBytes.base64EncodedString()
|
||||
])
|
||||
return result.valid
|
||||
}
|
||||
}
|
||||
|
||||
public class SphincsClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func generate(variant: SphincsVariant = .shake128s) async throws -> SphincsKeypair {
|
||||
try await crypto!.post(path: "/sphincs/generate", body: ["variant": variant.rawValue])
|
||||
}
|
||||
|
||||
public func sign(keypair: SphincsKeypair, message: Data) async throws -> SphincsSignature {
|
||||
try await crypto!.post(path: "/sphincs/sign", body: [
|
||||
"variant": keypair.variantEnum.rawValue,
|
||||
"secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
|
||||
"message": message.base64EncodedString()
|
||||
])
|
||||
}
|
||||
|
||||
public func verify(publicKey: Data, message: Data, signature: SphincsSignature) async throws -> Bool {
|
||||
struct VerifyResponse: Decodable { let valid: Bool }
|
||||
let result: VerifyResponse = try await crypto!.post(path: "/sphincs/verify", body: [
|
||||
"variant": signature.variantEnum.rawValue,
|
||||
"public_key": publicKey.base64EncodedString(),
|
||||
"message": message.base64EncodedString(),
|
||||
"signature": signature.signatureBytes.base64EncodedString()
|
||||
])
|
||||
return result.valid
|
||||
}
|
||||
}
|
||||
|
||||
public class KdfClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func deriveKey(seed: Data, config: DerivationConfig = DerivationConfig()) async throws -> Data {
|
||||
var body: [String: Any] = [
|
||||
"seed": seed.base64EncodedString(),
|
||||
"output_length": config.outputLength
|
||||
]
|
||||
if let salt = config.salt {
|
||||
body["salt"] = salt.base64EncodedString()
|
||||
}
|
||||
if let info = config.info {
|
||||
body["info"] = info.base64EncodedString()
|
||||
}
|
||||
|
||||
struct KeyResponse: Decodable { let key: String }
|
||||
let result: KeyResponse = try await crypto!.post(path: "/kdf/hkdf", body: body)
|
||||
return Data(base64Encoded: result.key) ?? Data()
|
||||
}
|
||||
|
||||
public func deriveFromPassword(password: Data, config: PasswordDerivationConfig) async throws -> Data {
|
||||
struct KeyResponse: Decodable { let key: String }
|
||||
let result: KeyResponse = try await crypto!.post(path: "/kdf/pbkdf2", body: [
|
||||
"password": password.base64EncodedString(),
|
||||
"salt": config.salt.base64EncodedString(),
|
||||
"iterations": config.iterations,
|
||||
"output_length": config.outputLength
|
||||
])
|
||||
return Data(base64Encoded: result.key) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
public class HashClient {
|
||||
weak var crypto: SynorCrypto?
|
||||
|
||||
public func sha3_256(data: Data) async throws -> Hash256 {
|
||||
try await crypto!.post(path: "/hash/sha3-256", body: ["data": data.base64EncodedString()])
|
||||
}
|
||||
|
||||
public func blake3(data: Data) async throws -> Hash256 {
|
||||
try await crypto!.post(path: "/hash/blake3", body: ["data": data.base64EncodedString()])
|
||||
}
|
||||
|
||||
public func keccak256(data: Data) async throws -> Hash256 {
|
||||
try await crypto!.post(path: "/hash/keccak256", body: ["data": data.base64EncodedString()])
|
||||
}
|
||||
}
|
||||
}
|
||||
499
sdk/swift/Sources/SynorCrypto/Types.swift
Normal file
499
sdk/swift/Sources/SynorCrypto/Types.swift
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
import Foundation
|
||||
|
||||
// MARK: - Enumerations
|
||||
|
||||
/// Network type for the Synor blockchain.
|
||||
public enum Network: String, Codable, CaseIterable {
|
||||
case mainnet
|
||||
case testnet
|
||||
case devnet
|
||||
}
|
||||
|
||||
/// Falcon signature variant.
|
||||
public enum FalconVariant: String, Codable, CaseIterable {
|
||||
case falcon512
|
||||
case falcon1024
|
||||
|
||||
public var signatureSize: Int {
|
||||
switch self {
|
||||
case .falcon512: return 690
|
||||
case .falcon1024: return 1330
|
||||
}
|
||||
}
|
||||
|
||||
public var publicKeySize: Int {
|
||||
switch self {
|
||||
case .falcon512: return 897
|
||||
case .falcon1024: return 1793
|
||||
}
|
||||
}
|
||||
|
||||
public var securityLevel: Int {
|
||||
switch self {
|
||||
case .falcon512: return 128
|
||||
case .falcon1024: return 256
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ signature variant.
|
||||
public enum SphincsVariant: String, Codable, CaseIterable {
|
||||
case shake128s
|
||||
case shake192s
|
||||
case shake256s
|
||||
|
||||
public var signatureSize: Int {
|
||||
switch self {
|
||||
case .shake128s: return 7856
|
||||
case .shake192s: return 16224
|
||||
case .shake256s: return 29792
|
||||
}
|
||||
}
|
||||
|
||||
public var securityLevel: Int {
|
||||
switch self {
|
||||
case .shake128s: return 128
|
||||
case .shake192s: return 192
|
||||
case .shake256s: return 256
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Post-quantum algorithm types.
|
||||
public enum PqAlgorithm: String, Codable {
|
||||
case dilithium3
|
||||
case falcon512
|
||||
case falcon1024
|
||||
case sphincs128s
|
||||
case sphincs192s
|
||||
case sphincs256s
|
||||
}
|
||||
|
||||
/// Cryptographic algorithm family.
|
||||
public enum AlgorithmFamily: String, Codable {
|
||||
case classical
|
||||
case lattice
|
||||
case hashBased = "hash_based"
|
||||
case hybrid
|
||||
}
|
||||
|
||||
// MARK: - Configuration Types
|
||||
|
||||
/// Configuration for the Crypto SDK.
|
||||
public struct CryptoConfig {
|
||||
public let apiKey: String
|
||||
public var endpoint: String
|
||||
public var timeout: TimeInterval
|
||||
public var retries: Int
|
||||
public var debug: Bool
|
||||
public var defaultNetwork: Network
|
||||
|
||||
public init(
|
||||
apiKey: String,
|
||||
endpoint: String = "https://crypto.synor.io/v1",
|
||||
timeout: TimeInterval = 30.0,
|
||||
retries: Int = 3,
|
||||
debug: Bool = false,
|
||||
defaultNetwork: Network = .mainnet
|
||||
) {
|
||||
self.apiKey = apiKey
|
||||
self.endpoint = endpoint
|
||||
self.timeout = timeout
|
||||
self.retries = retries
|
||||
self.debug = debug
|
||||
self.defaultNetwork = defaultNetwork
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for key derivation.
|
||||
public struct DerivationConfig {
|
||||
public var salt: Data?
|
||||
public var info: Data?
|
||||
public var outputLength: Int
|
||||
|
||||
public init(salt: Data? = nil, info: Data? = nil, outputLength: Int = 32) {
|
||||
self.salt = salt
|
||||
self.info = info
|
||||
self.outputLength = outputLength
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for password-based key derivation.
|
||||
public struct PasswordDerivationConfig {
|
||||
public let salt: Data
|
||||
public var iterations: Int
|
||||
public var outputLength: Int
|
||||
|
||||
public init(salt: Data, iterations: Int = 100000, outputLength: Int = 32) {
|
||||
self.salt = salt
|
||||
self.iterations = iterations
|
||||
self.outputLength = outputLength
|
||||
}
|
||||
}
|
||||
|
||||
/// BIP-44 derivation path.
|
||||
public struct DerivationPath: CustomStringConvertible {
|
||||
public static let coinType = 0x5359
|
||||
|
||||
public let account: Int
|
||||
public let change: Int
|
||||
public let index: Int
|
||||
|
||||
public init(account: Int, change: Int, index: Int) {
|
||||
self.account = account
|
||||
self.change = change
|
||||
self.index = index
|
||||
}
|
||||
|
||||
public static func external(account: Int, index: Int) -> DerivationPath {
|
||||
DerivationPath(account: account, change: 0, index: index)
|
||||
}
|
||||
|
||||
public static func `internal`(account: Int, index: Int) -> DerivationPath {
|
||||
DerivationPath(account: account, change: 1, index: index)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
"m/44'/\(Self.coinType)'/\(account)'/\(change)/\(index)"
|
||||
}
|
||||
|
||||
public func toDict() -> [String: Int] {
|
||||
["account": account, "change": change, "index": index]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Key Types
|
||||
|
||||
/// Hybrid public key combining Ed25519 and Dilithium3.
|
||||
public struct HybridPublicKey: Codable {
|
||||
public let ed25519: String
|
||||
public let dilithium: String
|
||||
|
||||
public var ed25519Bytes: Data {
|
||||
Data(base64Encoded: ed25519) ?? Data()
|
||||
}
|
||||
|
||||
public var dilithiumBytes: Data {
|
||||
Data(base64Encoded: dilithium) ?? Data()
|
||||
}
|
||||
|
||||
public var size: Int {
|
||||
ed25519Bytes.count + dilithiumBytes.count
|
||||
}
|
||||
|
||||
public func toDict() -> [String: String] {
|
||||
["ed25519": ed25519, "dilithium": dilithium]
|
||||
}
|
||||
}
|
||||
|
||||
/// Secret key for hybrid signatures.
|
||||
public struct SecretKey: Codable {
|
||||
public let ed25519Seed: String
|
||||
public let masterSeed: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ed25519Seed = "ed25519_seed"
|
||||
case masterSeed = "master_seed"
|
||||
}
|
||||
|
||||
public var ed25519SeedBytes: Data {
|
||||
Data(base64Encoded: ed25519Seed) ?? Data()
|
||||
}
|
||||
|
||||
public var masterSeedBytes: Data {
|
||||
Data(base64Encoded: masterSeed) ?? Data()
|
||||
}
|
||||
|
||||
public func toDict() -> [String: String] {
|
||||
["ed25519_seed": ed25519Seed, "master_seed": masterSeed]
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon public key.
|
||||
public struct FalconPublicKey: Codable {
|
||||
public let variant: String
|
||||
public let bytes: String
|
||||
|
||||
public var variantEnum: FalconVariant {
|
||||
FalconVariant(rawValue: variant) ?? .falcon512
|
||||
}
|
||||
|
||||
public var keyBytes: Data {
|
||||
Data(base64Encoded: bytes) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon secret key.
|
||||
public struct FalconSecretKey: Codable {
|
||||
public let variant: String
|
||||
public let bytes: String
|
||||
|
||||
public var variantEnum: FalconVariant {
|
||||
FalconVariant(rawValue: variant) ?? .falcon512
|
||||
}
|
||||
|
||||
public var keyBytes: Data {
|
||||
Data(base64Encoded: bytes) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ public key.
|
||||
public struct SphincsPublicKey: Codable {
|
||||
public let variant: String
|
||||
public let bytes: String
|
||||
|
||||
public var variantEnum: SphincsVariant {
|
||||
SphincsVariant(rawValue: variant) ?? .shake128s
|
||||
}
|
||||
|
||||
public var keyBytes: Data {
|
||||
Data(base64Encoded: bytes) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ secret key.
|
||||
public struct SphincsSecretKey: Codable {
|
||||
public let variant: String
|
||||
public let bytes: String
|
||||
|
||||
public var variantEnum: SphincsVariant {
|
||||
SphincsVariant(rawValue: variant) ?? .shake128s
|
||||
}
|
||||
|
||||
public var keyBytes: Data {
|
||||
Data(base64Encoded: bytes) ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Signature Types
|
||||
|
||||
/// Hybrid signature combining Ed25519 and Dilithium3.
|
||||
public struct HybridSignature: Codable {
|
||||
public let ed25519: String
|
||||
public let dilithium: String
|
||||
|
||||
public var ed25519Bytes: Data {
|
||||
Data(base64Encoded: ed25519) ?? Data()
|
||||
}
|
||||
|
||||
public var dilithiumBytes: Data {
|
||||
Data(base64Encoded: dilithium) ?? Data()
|
||||
}
|
||||
|
||||
public var size: Int {
|
||||
ed25519Bytes.count + dilithiumBytes.count
|
||||
}
|
||||
|
||||
public var bytes: Data {
|
||||
var result = Data()
|
||||
result.append(ed25519Bytes)
|
||||
result.append(dilithiumBytes)
|
||||
return result
|
||||
}
|
||||
|
||||
public func toDict() -> [String: String] {
|
||||
["ed25519": ed25519, "dilithium": dilithium]
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon signature.
|
||||
public struct FalconSignature: Codable {
|
||||
public let variant: String
|
||||
public let signature: String
|
||||
|
||||
public var variantEnum: FalconVariant {
|
||||
FalconVariant(rawValue: variant) ?? .falcon512
|
||||
}
|
||||
|
||||
public var signatureBytes: Data {
|
||||
Data(base64Encoded: signature) ?? Data()
|
||||
}
|
||||
|
||||
public var size: Int {
|
||||
signatureBytes.count
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ signature.
|
||||
public struct SphincsSignature: Codable {
|
||||
public let variant: String
|
||||
public let signature: String
|
||||
|
||||
public var variantEnum: SphincsVariant {
|
||||
SphincsVariant(rawValue: variant) ?? .shake128s
|
||||
}
|
||||
|
||||
public var signatureBytes: Data {
|
||||
Data(base64Encoded: signature) ?? Data()
|
||||
}
|
||||
|
||||
public var size: Int {
|
||||
signatureBytes.count
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Keypair Types
|
||||
|
||||
/// Hybrid keypair for Ed25519 + Dilithium3.
|
||||
public struct HybridKeypair: Codable {
|
||||
public let publicKey: HybridPublicKey
|
||||
public let secretKey: SecretKey
|
||||
public let addresses: [String: String]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case publicKey = "public_key"
|
||||
case secretKey = "secret_key"
|
||||
case addresses
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
publicKey = try container.decode(HybridPublicKey.self, forKey: .publicKey)
|
||||
secretKey = try container.decode(SecretKey.self, forKey: .secretKey)
|
||||
addresses = try container.decodeIfPresent([String: String].self, forKey: .addresses) ?? [:]
|
||||
}
|
||||
|
||||
public func address(for network: Network) -> String {
|
||||
addresses[network.rawValue] ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
/// Falcon keypair.
|
||||
public struct FalconKeypair: Codable {
|
||||
public let variant: String
|
||||
public let publicKey: FalconPublicKey
|
||||
public let secretKey: FalconSecretKey
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case variant
|
||||
case publicKey = "public_key"
|
||||
case secretKey = "secret_key"
|
||||
}
|
||||
|
||||
public var variantEnum: FalconVariant {
|
||||
FalconVariant(rawValue: variant) ?? .falcon512
|
||||
}
|
||||
}
|
||||
|
||||
/// SPHINCS+ keypair.
|
||||
public struct SphincsKeypair: Codable {
|
||||
public let variant: String
|
||||
public let publicKey: SphincsPublicKey
|
||||
public let secretKey: SphincsSecretKey
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case variant
|
||||
case publicKey = "public_key"
|
||||
case secretKey = "secret_key"
|
||||
}
|
||||
|
||||
public var variantEnum: SphincsVariant {
|
||||
SphincsVariant(rawValue: variant) ?? .shake128s
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mnemonic Types
|
||||
|
||||
/// BIP-39 mnemonic phrase.
|
||||
public struct Mnemonic: Codable {
|
||||
public let phrase: String
|
||||
public let words: [String]?
|
||||
public let wordCount: Int?
|
||||
public let entropy: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case phrase
|
||||
case words
|
||||
case wordCount = "word_count"
|
||||
case entropy
|
||||
}
|
||||
|
||||
public var wordList: [String] {
|
||||
words ?? phrase.split(separator: " ").map(String.init)
|
||||
}
|
||||
|
||||
public var count: Int {
|
||||
wordCount ?? wordList.count
|
||||
}
|
||||
|
||||
public var entropyBytes: Data {
|
||||
entropy.flatMap { Data(base64Encoded: $0) } ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mnemonic validation result.
|
||||
public struct MnemonicValidation: Codable {
|
||||
public let valid: Bool
|
||||
public let error: String?
|
||||
}
|
||||
|
||||
// MARK: - Address Types
|
||||
|
||||
/// Blockchain address.
|
||||
public struct Address: Codable {
|
||||
public let address: String
|
||||
public let network: String
|
||||
public let pubkeyHash: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case address
|
||||
case network
|
||||
case pubkeyHash = "pubkey_hash"
|
||||
}
|
||||
|
||||
public var networkEnum: Network {
|
||||
Network(rawValue: network) ?? .mainnet
|
||||
}
|
||||
|
||||
public var pubkeyHashBytes: Data {
|
||||
pubkeyHash.flatMap { Data(base64Encoded: $0) } ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hash Types
|
||||
|
||||
/// 256-bit hash result.
|
||||
public struct Hash256: Codable {
|
||||
public let hash: String
|
||||
|
||||
public var hex: String { hash }
|
||||
|
||||
public var bytes: Data {
|
||||
var result = Data()
|
||||
var index = hash.startIndex
|
||||
while index < hash.endIndex {
|
||||
let nextIndex = hash.index(index, offsetBy: 2)
|
||||
if let byte = UInt8(hash[index..<nextIndex], radix: 16) {
|
||||
result.append(byte)
|
||||
}
|
||||
index = nextIndex
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error Types
|
||||
|
||||
/// Crypto SDK error.
|
||||
public struct CryptoError: Error, LocalizedError {
|
||||
public let message: String
|
||||
public let code: String?
|
||||
|
||||
public var errorDescription: String? { message }
|
||||
}
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
/// Cryptographic constants.
|
||||
public enum CryptoConstants {
|
||||
public static let ed25519PublicKeySize = 32
|
||||
public static let ed25519SecretKeySize = 32
|
||||
public static let ed25519SignatureSize = 64
|
||||
public static let dilithium3PublicKeySize = 1952
|
||||
public static let dilithium3SignatureSize = 3293
|
||||
public static let hybridSignatureSize = 64 + 3293
|
||||
public static let coinType = 0x5359
|
||||
public static let minPbkdf2Iterations = 10000
|
||||
public static let minSaltLength = 8
|
||||
public static let defaultEndpoint = "https://crypto.synor.io/v1"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue