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